mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Port over C++ components of the one-flutter test (flutter/engine#36546)
This commit is contained in:
parent
859eb85262
commit
b797d69385
@ -11,5 +11,6 @@ group("integration") {
|
||||
deps = [
|
||||
"embedder:tests",
|
||||
"text-input:tests",
|
||||
"touch-input:tests",
|
||||
]
|
||||
}
|
||||
|
||||
@ -0,0 +1,71 @@
|
||||
# Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
assert(is_fuchsia)
|
||||
|
||||
import("//build/fuchsia/sdk.gni")
|
||||
import("//flutter/tools/fuchsia/fuchsia_archive.gni")
|
||||
import("//flutter/tools/fuchsia/gn-sdk/package.gni")
|
||||
|
||||
group("tests") {
|
||||
testonly = true
|
||||
deps = [ ":touch-input-test" ]
|
||||
}
|
||||
|
||||
executable("touch-input-test-bin") {
|
||||
testonly = true
|
||||
output_name = "touch-input-test"
|
||||
sources = [ "touch-input-test.cc" ]
|
||||
|
||||
# This is needed for //third_party/googletest for linking zircon symbols.
|
||||
libs = [ "$fuchsia_sdk_path/arch/$target_cpu/sysroot/lib/libzircon.so" ]
|
||||
|
||||
deps = [
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.accessibility.semantics",
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.buildinfo",
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.component",
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.fonts",
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.intl",
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.kernel",
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.memorypressure",
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.metrics",
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.net.interfaces",
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.tracing.provider",
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.ui.app",
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.ui.input",
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.ui.policy",
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.ui.scenic",
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.ui.test.input",
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.ui.test.scene",
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.ui.test.scene",
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.web",
|
||||
"$fuchsia_sdk_root/pkg:async",
|
||||
"$fuchsia_sdk_root/pkg:async-loop-testing",
|
||||
"$fuchsia_sdk_root/pkg:fidl_cpp",
|
||||
"$fuchsia_sdk_root/pkg:scenic_cpp",
|
||||
"$fuchsia_sdk_root/pkg:sys_component_cpp_testing",
|
||||
"$fuchsia_sdk_root/pkg:zx",
|
||||
"//build/fuchsia/fidl:fuchsia.ui.gfx",
|
||||
"//flutter/fml",
|
||||
"//flutter/shell/platform/fuchsia/flutter/tests/integration/utils:portable_ui_test",
|
||||
"//third_party/googletest:gtest",
|
||||
"//third_party/googletest:gtest_main",
|
||||
]
|
||||
}
|
||||
|
||||
fuchsia_test_archive("touch-input-test") {
|
||||
testonly = true
|
||||
deps = [
|
||||
":touch-input-test-bin",
|
||||
"one-flutter:package",
|
||||
|
||||
# "OOT" copies of the runners used by tests, to avoid conflicting with the
|
||||
# runners in the base fuchsia image.
|
||||
# TODO(fxbug.dev/106575): Fix this with subpackages.
|
||||
"//flutter/shell/platform/fuchsia/flutter:oot_flutter_jit_runner",
|
||||
]
|
||||
|
||||
binary = "$target_name"
|
||||
cml_file = rebase_path("meta/$target_name.cml")
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
{
|
||||
program: {
|
||||
runner: "gtest_runner",
|
||||
},
|
||||
capabilities: [
|
||||
{ protocol: "fuchsia.test.Suite" },
|
||||
],
|
||||
expose: [
|
||||
{
|
||||
protocol: "fuchsia.test.Suite",
|
||||
from: "self",
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
{
|
||||
include: [
|
||||
"gtest_runner.shard.cml",
|
||||
"sys/component/realm_builder_absolute.shard.cml",
|
||||
|
||||
"syslog/client.shard.cml",
|
||||
"vulkan/client.shard.cml",
|
||||
|
||||
// This test needs both the vulkan facet and the hermetic-tier-2 facet,
|
||||
// so we are forced to make it a system test.
|
||||
"sys/testing/system-test.shard.cml",
|
||||
],
|
||||
program: {
|
||||
binary: "bin/app",
|
||||
},
|
||||
offer: [
|
||||
{
|
||||
// Offer capabilities needed by components in this test realm.
|
||||
// Keep it minimal, describe only what's actually needed.
|
||||
// TODO(fxbug.dev/81446): Remove this list.
|
||||
protocol: [
|
||||
"fuchsia.kernel.RootJobForInspect",
|
||||
"fuchsia.kernel.Stats",
|
||||
"fuchsia.logger.LogSink",
|
||||
"fuchsia.scheduler.ProfileProvider",
|
||||
"fuchsia.sysmem.Allocator",
|
||||
"fuchsia.tracing.provider.Registry",
|
||||
"fuchsia.ui.input.ImeService",
|
||||
"fuchsia.vulkan.loader.Loader",
|
||||
],
|
||||
from: "parent",
|
||||
to: "#realm_builder",
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
# Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import("//build/fuchsia/sdk.gni")
|
||||
import("//flutter/tools/fuchsia/dart/dart_library.gni")
|
||||
import("//flutter/tools/fuchsia/flutter/flutter_component.gni")
|
||||
import("//flutter/tools/fuchsia/gn-sdk/component.gni")
|
||||
import("//flutter/tools/fuchsia/gn-sdk/package.gni")
|
||||
|
||||
dart_library("lib") {
|
||||
package_name = "one-flutter"
|
||||
sources = [ "one-flutter.dart" ]
|
||||
deps = [
|
||||
"//flutter/tools/fuchsia/dart:fuchsia_services",
|
||||
"//flutter/tools/fuchsia/dart:zircon",
|
||||
"//flutter/tools/fuchsia/fidl:fuchsia.ui.test.input",
|
||||
]
|
||||
}
|
||||
|
||||
flutter_component("component") {
|
||||
testonly = true
|
||||
component_name = "one-flutter-view"
|
||||
manifest = rebase_path("meta/one-flutter-view.cml")
|
||||
main_package = "one-flutter"
|
||||
main_dart = "one-flutter.dart"
|
||||
deps = [ ":lib" ]
|
||||
}
|
||||
|
||||
fuchsia_component("realm") {
|
||||
testonly = true
|
||||
manifest = "meta/one-flutter-realm.cml"
|
||||
manifest_output_name = "one-flutter-realm.cml"
|
||||
deps = [ ":component" ]
|
||||
}
|
||||
|
||||
fuchsia_package("package") {
|
||||
testonly = true
|
||||
deps = [
|
||||
":component",
|
||||
":realm",
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// TODO(https://fxbug.dev/84961): Fix null safety and remove this language version.
|
||||
// @dart=2.9
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:fidl_fuchsia_ui_test_input/fidl_async.dart';
|
||||
import 'package:fuchsia_services/services.dart';
|
||||
|
||||
int main() {
|
||||
print('touch-input-view: starting');
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
{
|
||||
children: [
|
||||
{
|
||||
name: "flutter_jit_runner",
|
||||
url: "fuchsia-pkg://fuchsia.com/flutter_jit_runner#meta/flutter_jit_runner.cm",
|
||||
},
|
||||
{
|
||||
name: "one_flutter_view",
|
||||
url: "#meta/one-flutter-view.cm",
|
||||
environment: "#one_flutter_view_env",
|
||||
},
|
||||
],
|
||||
offer: [
|
||||
{
|
||||
protocol: [
|
||||
"fuchsia.logger.LogSink",
|
||||
"fuchsia.sysmem.Allocator",
|
||||
"fuchsia.tracing.provider.Registry",
|
||||
"fuchsia.ui.scenic.Scenic",
|
||||
"fuchsia.vulkan.loader.Loader",
|
||||
],
|
||||
from: "parent",
|
||||
to: [
|
||||
"#flutter_jit_runner",
|
||||
"#one_flutter_view",
|
||||
],
|
||||
},
|
||||
{
|
||||
protocol: [ "fuchsia.ui.test.input.TouchInputListener" ],
|
||||
from: "parent",
|
||||
to: "#one_flutter_view",
|
||||
},
|
||||
],
|
||||
expose: [
|
||||
{
|
||||
protocol: [ "fuchsia.ui.app.ViewProvider" ],
|
||||
from: "#one_flutter_view",
|
||||
to: "parent",
|
||||
},
|
||||
],
|
||||
environments: [
|
||||
{
|
||||
name: "one_flutter_view_env",
|
||||
extends: "realm",
|
||||
runners: [
|
||||
{
|
||||
runner: "flutter_jit_runner",
|
||||
from: "#flutter_jit_runner",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
{
|
||||
include: [
|
||||
"sys/component/realm_builder_absolute.shard.cml",
|
||||
"syslog/client.shard.cml",
|
||||
],
|
||||
program: {
|
||||
data: "data/one-flutter",
|
||||
|
||||
// Always use the jit runner for now.
|
||||
// TODO(fxbug.dev/106577): Implement manifest merging build rules for V2 components.
|
||||
runner: "flutter_jit_runner",
|
||||
},
|
||||
capabilities: [
|
||||
{
|
||||
protocol: [ "fuchsia.ui.app.ViewProvider" ],
|
||||
},
|
||||
],
|
||||
use: [
|
||||
{
|
||||
protocol: [
|
||||
"fuchsia.sysmem.Allocator",
|
||||
"fuchsia.tracing.provider.Registry",
|
||||
"fuchsia.ui.scenic.Scenic",
|
||||
"fuchsia.ui.test.input.TouchInputListener",
|
||||
"fuchsia.vulkan.loader.Loader",
|
||||
],
|
||||
},
|
||||
],
|
||||
expose: [
|
||||
{
|
||||
protocol: [ "fuchsia.ui.app.ViewProvider" ],
|
||||
from: "self",
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -0,0 +1,400 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <fuchsia/accessibility/semantics/cpp/fidl.h>
|
||||
#include <fuchsia/buildinfo/cpp/fidl.h>
|
||||
#include <fuchsia/component/cpp/fidl.h>
|
||||
#include <fuchsia/fonts/cpp/fidl.h>
|
||||
#include <fuchsia/intl/cpp/fidl.h>
|
||||
#include <fuchsia/kernel/cpp/fidl.h>
|
||||
#include <fuchsia/memorypressure/cpp/fidl.h>
|
||||
#include <fuchsia/metrics/cpp/fidl.h>
|
||||
#include <fuchsia/net/interfaces/cpp/fidl.h>
|
||||
#include <fuchsia/sysmem/cpp/fidl.h>
|
||||
#include <fuchsia/tracing/provider/cpp/fidl.h>
|
||||
#include <fuchsia/ui/app/cpp/fidl.h>
|
||||
#include <fuchsia/ui/input/cpp/fidl.h>
|
||||
#include <fuchsia/ui/policy/cpp/fidl.h>
|
||||
#include <fuchsia/ui/scenic/cpp/fidl.h>
|
||||
#include <fuchsia/ui/test/input/cpp/fidl.h>
|
||||
#include <fuchsia/ui/test/scene/cpp/fidl.h>
|
||||
#include <fuchsia/web/cpp/fidl.h>
|
||||
#include <lib/async-loop/testing/cpp/real_loop.h>
|
||||
#include <lib/async/cpp/task.h>
|
||||
#include <lib/fidl/cpp/binding_set.h>
|
||||
#include <lib/sys/component/cpp/testing/realm_builder.h>
|
||||
#include <lib/sys/component/cpp/testing/realm_builder_types.h>
|
||||
#include <lib/sys/cpp/component_context.h>
|
||||
#include <lib/ui/scenic/cpp/resources.h>
|
||||
#include <lib/ui/scenic/cpp/session.h>
|
||||
#include <lib/ui/scenic/cpp/view_token_pair.h>
|
||||
#include <lib/zx/clock.h>
|
||||
#include <lib/zx/time.h>
|
||||
#include <zircon/status.h>
|
||||
#include <zircon/types.h>
|
||||
#include <zircon/utc.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "flutter/fml/logging.h"
|
||||
#include "flutter/shell/platform/fuchsia/flutter/tests/integration/utils/portable_ui_test.h"
|
||||
|
||||
// This test exercises the touch input dispatch path from Input Pipeline to a
|
||||
// Scenic client. It is a multi-component test, and carefully avoids sleeping or
|
||||
// polling for component coordination.
|
||||
// - It runs real Root Presenter, Input Pipeline, and Scenic components.
|
||||
// - It uses a fake display controller; the physical device is unused.
|
||||
//
|
||||
// Components involved
|
||||
// - This test program
|
||||
// - Input Pipeline
|
||||
// - Root Presenter
|
||||
// - Scenic
|
||||
// - Child view, a Scenic client
|
||||
//
|
||||
// Touch dispatch path
|
||||
// - Test program's injection -> Input Pipeline -> Scenic -> Child view
|
||||
//
|
||||
// Setup sequence
|
||||
// - The test sets up this view hierarchy:
|
||||
// - Top level scene, owned by Root Presenter.
|
||||
// - Child view, owned by the ui client.
|
||||
// - The test waits for a Scenic event that verifies the child has UI content in
|
||||
// the scene graph.
|
||||
// - The test injects input into Input Pipeline, emulating a display's touch
|
||||
// report.
|
||||
// - Input Pipeline dispatches the touch event to Scenic, which in turn
|
||||
// dispatches it to the child.
|
||||
// - The child receives the touch event and reports back to the test over a
|
||||
// custom test-only FIDL.
|
||||
// - Test waits for the child to report a touch; when the test receives the
|
||||
// report, the test quits
|
||||
// successfully.
|
||||
//
|
||||
// This test uses the realm_builder library to construct the topology of
|
||||
// components and routes services between them. For v2 components, every test
|
||||
// driver component sits as a child of test_manager in the topology. Thus, the
|
||||
// topology of a test driver component such as this one looks like this:
|
||||
//
|
||||
// test_manager
|
||||
// |
|
||||
// touch-input-test.cml (this component)
|
||||
//
|
||||
// With the usage of the realm_builder library, we construct a realm during
|
||||
// runtime and then extend the topology to look like:
|
||||
//
|
||||
// test_manager
|
||||
// |
|
||||
// touch-input-test.cml (this component)
|
||||
// |
|
||||
// <created realm root>
|
||||
// / \
|
||||
// scenic input-pipeline
|
||||
//
|
||||
// For more information about testing v2 components and realm_builder,
|
||||
// visit the following links:
|
||||
//
|
||||
// Testing: https://fuchsia.dev/fuchsia-src/concepts/testing/v2
|
||||
// Realm Builder:
|
||||
// https://fuchsia.dev/fuchsia-src/development/components/v2/realm_builder
|
||||
|
||||
namespace touch_input_test::testing {
|
||||
namespace {
|
||||
// Types imported for the realm_builder library.
|
||||
using component_testing::ChildRef;
|
||||
using component_testing::ConfigValue;
|
||||
using component_testing::LocalComponent;
|
||||
using component_testing::LocalComponentHandles;
|
||||
using component_testing::ParentRef;
|
||||
using component_testing::Protocol;
|
||||
using component_testing::Realm;
|
||||
using component_testing::RealmRoot;
|
||||
using component_testing::Route;
|
||||
|
||||
using fuchsia_test_utils::PortableUITest;
|
||||
|
||||
using RealmBuilder = component_testing::RealmBuilder;
|
||||
// Alias for Component child name as provided to Realm Builder.
|
||||
using ChildName = std::string;
|
||||
// Alias for Component Legacy URL as provided to Realm Builder.
|
||||
using LegacyUrl = std::string;
|
||||
|
||||
// Max timeout in failure cases.
|
||||
// Set this as low as you can that still works across all test platforms.
|
||||
constexpr zx::duration kTimeout = zx::min(5);
|
||||
|
||||
constexpr auto kMockResponseListener = "response_listener";
|
||||
|
||||
enum class TapLocation { kTopLeft, kTopRight };
|
||||
|
||||
// Combines all vectors in `vecs` into one.
|
||||
template <typename T>
|
||||
std::vector<T> merge(std::initializer_list<std::vector<T>> vecs) {
|
||||
std::vector<T> result;
|
||||
for (auto v : vecs) {
|
||||
result.insert(result.end(), v.begin(), v.end());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CompareDouble(double f0, double f1, double epsilon) {
|
||||
return std::abs(f0 - f1) <= epsilon;
|
||||
}
|
||||
|
||||
// // This component implements the test.touch.ResponseListener protocol
|
||||
// // and the interface for a RealmBuilder LocalComponent. A LocalComponent
|
||||
// // is a component that is implemented here in the test, as opposed to
|
||||
// elsewhere
|
||||
// // in the system. When it's inserted to the realm, it will act like a proper
|
||||
// // component. This is accomplished, in part, because the realm_builder
|
||||
// // library creates the necessary plumbing. It creates a manifest for the
|
||||
// // component and routes all capabilities to and from it.
|
||||
class ResponseListenerServer
|
||||
: public fuchsia::ui::test::input::TouchInputListener,
|
||||
public LocalComponent {
|
||||
public:
|
||||
explicit ResponseListenerServer(async_dispatcher_t* dispatcher)
|
||||
: dispatcher_(dispatcher) {}
|
||||
|
||||
// |fuchsia::ui::test::input::TouchInputListener|
|
||||
void ReportTouchInput(
|
||||
fuchsia::ui::test::input::TouchInputListenerReportTouchInputRequest
|
||||
request) override {
|
||||
events_received_.push_back(std::move(request));
|
||||
}
|
||||
|
||||
// |LocalComponent::Start|
|
||||
// When the component framework requests for this component to start, this
|
||||
// method will be invoked by the realm_builder library.
|
||||
void Start(std::unique_ptr<LocalComponentHandles> local_handles) override {
|
||||
// When this component starts, add a binding to the
|
||||
// test.touch.ResponseListener protocol to this component's outgoing
|
||||
// directory.
|
||||
ASSERT_EQ(ZX_OK, local_handles->outgoing()->AddPublicService(
|
||||
fidl::InterfaceRequestHandler<
|
||||
fuchsia::ui::test::input::TouchInputListener>(
|
||||
[this](auto request) {
|
||||
bindings_.AddBinding(this, std::move(request),
|
||||
dispatcher_);
|
||||
})));
|
||||
local_handles_.emplace_back(std::move(local_handles));
|
||||
}
|
||||
|
||||
const std::vector<
|
||||
fuchsia::ui::test::input::TouchInputListenerReportTouchInputRequest>&
|
||||
events_received() {
|
||||
return events_received_;
|
||||
}
|
||||
|
||||
private:
|
||||
async_dispatcher_t* dispatcher_ = nullptr;
|
||||
std::vector<std::unique_ptr<LocalComponentHandles>> local_handles_;
|
||||
fidl::BindingSet<fuchsia::ui::test::input::TouchInputListener> bindings_;
|
||||
std::vector<
|
||||
fuchsia::ui::test::input::TouchInputListenerReportTouchInputRequest>
|
||||
events_received_;
|
||||
};
|
||||
|
||||
class FlutterTapTest : public PortableUITest,
|
||||
public ::testing::Test,
|
||||
public ::testing::WithParamInterface<std::string> {
|
||||
protected:
|
||||
~FlutterTapTest() override {
|
||||
FML_CHECK(touch_injection_request_count() > 0)
|
||||
<< "Injection expected but didn't happen.";
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
PortableUITest::SetUp();
|
||||
|
||||
// Post a "just in case" quit task, if the test hangs.
|
||||
async::PostDelayedTask(
|
||||
dispatcher(),
|
||||
[] {
|
||||
FML_LOG(FATAL)
|
||||
<< "\n\n>> Test did not complete in time, terminating. <<\n\n";
|
||||
},
|
||||
kTimeout);
|
||||
|
||||
// Get the display dimensions.
|
||||
FML_LOG(INFO) << "Waiting for scenic display info";
|
||||
scenic_ = realm_root()->template Connect<fuchsia::ui::scenic::Scenic>();
|
||||
scenic_->GetDisplayInfo([this](fuchsia::ui::gfx::DisplayInfo display_info) {
|
||||
display_width_ = display_info.width_in_px;
|
||||
display_height_ = display_info.height_in_px;
|
||||
FML_LOG(INFO) << "Got display_width = " << display_width_
|
||||
<< " and display_height = " << display_height_;
|
||||
});
|
||||
RunLoopUntil(
|
||||
[this] { return display_width_ != 0 && display_height_ != 0; });
|
||||
|
||||
// Register input injection device.
|
||||
FML_LOG(INFO) << "Registering input injection device";
|
||||
RegisterTouchScreen();
|
||||
}
|
||||
|
||||
// Routes needed to setup Flutter client.
|
||||
static std::vector<Route> GetFlutterRoutes(ChildRef target) {
|
||||
return {
|
||||
{.capabilities = {Protocol{
|
||||
fuchsia::ui::test::input::TouchInputListener::Name_}},
|
||||
.source = ChildRef{kMockResponseListener},
|
||||
.targets = {target}},
|
||||
{.capabilities = {Protocol{fuchsia::logger::LogSink::Name_},
|
||||
Protocol{fuchsia::sysmem::Allocator::Name_},
|
||||
Protocol{
|
||||
fuchsia::tracing::provider::Registry::Name_}},
|
||||
.source = ParentRef(),
|
||||
.targets = {target}},
|
||||
{.capabilities = {Protocol{fuchsia::ui::scenic::Scenic::Name_}},
|
||||
.source = kTestUIStackRef,
|
||||
.targets = {target}},
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<Route> GetTestRoutes() {
|
||||
return merge(
|
||||
{GetFlutterRoutes(ChildRef{kFlutterRealm}),
|
||||
{
|
||||
{.capabilities = {Protocol{fuchsia::ui::app::ViewProvider::Name_}},
|
||||
.source = ChildRef{kFlutterRealm},
|
||||
.targets = {ParentRef()}},
|
||||
}});
|
||||
}
|
||||
|
||||
std::vector<std::pair<ChildName, LegacyUrl>> GetTestV2Components() {
|
||||
return {
|
||||
std::make_pair(kFlutterRealm, kFlutterRealmUrl),
|
||||
};
|
||||
};
|
||||
|
||||
bool LastEventReceivedMatches(float expected_x,
|
||||
float expected_y,
|
||||
std::string component_name) {
|
||||
const auto& events_received = response_listener_server_->events_received();
|
||||
if (events_received.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& last_event = events_received.back();
|
||||
|
||||
auto pixel_scale = last_event.has_device_pixel_ratio()
|
||||
? last_event.device_pixel_ratio()
|
||||
: 1;
|
||||
|
||||
auto actual_x = pixel_scale * last_event.local_x();
|
||||
auto actual_y = pixel_scale * last_event.local_y();
|
||||
|
||||
FML_LOG(INFO) << "Expecting event for component " << component_name
|
||||
<< " at (" << expected_x << ", " << expected_y << ")";
|
||||
FML_LOG(INFO) << "Received event for component " << component_name
|
||||
<< " at (" << actual_x << ", " << actual_y
|
||||
<< "), accounting for pixel scale of " << pixel_scale;
|
||||
|
||||
return CompareDouble(actual_x, expected_x, pixel_scale) &&
|
||||
CompareDouble(actual_y, expected_y, pixel_scale) &&
|
||||
last_event.component_name() == component_name;
|
||||
}
|
||||
|
||||
void InjectInput(TapLocation tap_location) {
|
||||
// The /config/data/display_rotation (90) specifies how many degrees to
|
||||
// rotate the presentation child view, counter-clockwise, in a
|
||||
// right-handed coordinate system. Thus, the user observes the child
|
||||
// view to rotate *clockwise* by that amount (90).
|
||||
//
|
||||
// Hence, a tap in the center of the display's top-right quadrant is
|
||||
// observed by the child view as a tap in the center of its top-left
|
||||
// quadrant.
|
||||
auto touch = std::make_unique<fuchsia::ui::input::TouchscreenReport>();
|
||||
switch (tap_location) {
|
||||
case TapLocation::kTopLeft:
|
||||
// center of top right quadrant -> ends up as center of top left
|
||||
// quadrant
|
||||
InjectTap(/* x = */ 500, /* y = */ -500);
|
||||
break;
|
||||
case TapLocation::kTopRight:
|
||||
// center of bottom right quadrant -> ends up as center of top right
|
||||
// quadrant
|
||||
InjectTap(/* x = */ 500, /* y = */ 500);
|
||||
break;
|
||||
default:
|
||||
FML_CHECK(false) << "Received invalid TapLocation";
|
||||
}
|
||||
}
|
||||
|
||||
// Guaranteed to be initialized after SetUp().
|
||||
uint32_t display_width() const { return display_width_; }
|
||||
uint32_t display_height() const { return display_height_; }
|
||||
|
||||
static constexpr auto kFlutterRealm = "flutter-realm";
|
||||
static constexpr auto kFlutterRealmUrl =
|
||||
"fuchsia-pkg://fuchsia.com/one-flutter#meta/one-flutter-realm.cm";
|
||||
|
||||
private:
|
||||
void ExtendRealm() override {
|
||||
// Key part of service setup: have this test component vend the
|
||||
// |ResponseListener| service in the constructed realm.
|
||||
response_listener_server_ =
|
||||
std::make_unique<ResponseListenerServer>(dispatcher());
|
||||
realm_builder()->AddLocalChild(kMockResponseListener,
|
||||
response_listener_server_.get());
|
||||
|
||||
realm_builder()->AddRoute(
|
||||
{.capabilities = {Protocol{fuchsia::ui::scenic::Scenic::Name_}},
|
||||
.source = kTestUIStackRef,
|
||||
.targets = {ParentRef()}});
|
||||
|
||||
// Add components specific for this test case to the realm.
|
||||
for (const auto& [name, component] : GetTestV2Components()) {
|
||||
realm_builder()->AddChild(name, component);
|
||||
}
|
||||
|
||||
// Add the necessary routing for each of the extra components added
|
||||
// above.
|
||||
for (const auto& route : GetTestRoutes()) {
|
||||
realm_builder()->AddRoute(route);
|
||||
}
|
||||
}
|
||||
|
||||
ParamType GetTestUIStackUrl() override { return GetParam(); };
|
||||
|
||||
std::unique_ptr<ResponseListenerServer> response_listener_server_;
|
||||
|
||||
fuchsia::ui::scenic::ScenicPtr scenic_;
|
||||
uint32_t display_width_ = 0;
|
||||
uint32_t display_height_ = 0;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
FlutterTapTestParameterized,
|
||||
FlutterTapTest,
|
||||
::testing::Values(
|
||||
"fuchsia-pkg://fuchsia.com/gfx-root-presenter-test-ui-stack#meta/"
|
||||
"test-ui-stack.cm"));
|
||||
|
||||
TEST_P(FlutterTapTest, FlutterTap) {
|
||||
// Launch client view, and wait until it's rendering to proceed with the test.
|
||||
FML_LOG(INFO) << "Initializing scene";
|
||||
LaunchClient();
|
||||
FML_LOG(INFO) << "Client launched";
|
||||
|
||||
InjectInput(TapLocation::kTopLeft);
|
||||
RunLoopUntil([this] {
|
||||
return LastEventReceivedMatches(
|
||||
/*expected_x=*/static_cast<float>(display_height()) / 4.f,
|
||||
/*expected_y=*/static_cast<float>(display_width()) / 4.f,
|
||||
/*component_name=*/"one-flutter");
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace touch_input_test::testing
|
||||
@ -40,3 +40,19 @@ source_set("screenshot") {
|
||||
"//flutter/fml",
|
||||
]
|
||||
}
|
||||
|
||||
source_set("portable_ui_test") {
|
||||
testonly = true
|
||||
sources = [
|
||||
"portable_ui_test.cc",
|
||||
"portable_ui_test.h",
|
||||
]
|
||||
|
||||
deps = [
|
||||
":check_view",
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.ui.observation.geometry",
|
||||
"$fuchsia_sdk_root/pkg:async-loop-testing",
|
||||
"$fuchsia_sdk_root/pkg:sys_component_cpp_testing",
|
||||
"//flutter/fml",
|
||||
]
|
||||
}
|
||||
|
||||
@ -0,0 +1,164 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "portable_ui_test.h"
|
||||
|
||||
#include <fuchsia/logger/cpp/fidl.h>
|
||||
#include <fuchsia/tracing/provider/cpp/fidl.h>
|
||||
#include <fuchsia/ui/app/cpp/fidl.h>
|
||||
#include <lib/async/cpp/task.h>
|
||||
#include <lib/sys/component/cpp/testing/realm_builder.h>
|
||||
#include <lib/sys/component/cpp/testing/realm_builder_types.h>
|
||||
|
||||
#include "check_view.h"
|
||||
#include "flutter/fml/logging.h"
|
||||
|
||||
namespace fuchsia_test_utils {
|
||||
namespace {
|
||||
|
||||
// Types imported for the realm_builder library.
|
||||
using component_testing::ChildRef;
|
||||
using component_testing::ParentRef;
|
||||
using component_testing::Protocol;
|
||||
using component_testing::RealmRoot;
|
||||
using component_testing::Route;
|
||||
|
||||
using fuchsia_test_utils::CheckViewExistsInSnapshot;
|
||||
|
||||
} // namespace
|
||||
|
||||
void PortableUITest::SetUp() {
|
||||
SetUpRealmBase();
|
||||
|
||||
ExtendRealm();
|
||||
|
||||
realm_ = std::make_unique<RealmRoot>(realm_builder_.Build());
|
||||
}
|
||||
|
||||
void PortableUITest::SetUpRealmBase() {
|
||||
FML_LOG(INFO) << "Setting up realm base";
|
||||
|
||||
// Add test UI stack component.
|
||||
realm_builder_.AddChild(kTestUIStack, GetTestUIStackUrl());
|
||||
|
||||
// Route base system services to flutter and the test UI stack.
|
||||
realm_builder_.AddRoute(
|
||||
Route{.capabilities =
|
||||
{
|
||||
Protocol{fuchsia::logger::LogSink::Name_},
|
||||
Protocol{fuchsia::sys::Environment::Name_},
|
||||
Protocol{fuchsia::sysmem::Allocator::Name_},
|
||||
Protocol{fuchsia::tracing::provider::Registry::Name_},
|
||||
Protocol{kVulkanLoaderServiceName},
|
||||
Protocol{kProfileProviderServiceName},
|
||||
},
|
||||
.source = ParentRef{},
|
||||
.targets = {kTestUIStackRef}});
|
||||
|
||||
// Capabilities routed to test driver.
|
||||
realm_builder_.AddRoute(Route{
|
||||
.capabilities = {Protocol{fuchsia::ui::test::input::Registry::Name_},
|
||||
Protocol{fuchsia::ui::test::scene::Controller::Name_}},
|
||||
.source = kTestUIStackRef,
|
||||
.targets = {ParentRef{}}});
|
||||
}
|
||||
|
||||
void PortableUITest::ProcessViewGeometryResponse(
|
||||
fuchsia::ui::observation::geometry::WatchResponse response) {
|
||||
// Process update if no error
|
||||
if (!response.has_error()) {
|
||||
std::vector<fuchsia::ui::observation::geometry::ViewTreeSnapshot>* updates =
|
||||
response.mutable_updates();
|
||||
if (updates && !updates->empty()) {
|
||||
last_view_tree_snapshot_ = std::move(updates->back());
|
||||
}
|
||||
} else {
|
||||
// Otherwise process error
|
||||
const auto& error = response.error();
|
||||
if (error | fuchsia::ui::observation::geometry::Error::CHANNEL_OVERFLOW) {
|
||||
FML_LOG(INFO) << "View Tree watcher channel overflowed";
|
||||
} else if (error |
|
||||
fuchsia::ui::observation::geometry::Error::BUFFER_OVERFLOW) {
|
||||
FML_LOG(INFO) << "View Tree watcher buffer overflowed";
|
||||
} else if (error |
|
||||
fuchsia::ui::observation::geometry::Error::VIEWS_OVERFLOW) {
|
||||
// This one indicates some possible data loss, so we log with a high
|
||||
// severity
|
||||
FML_LOG(WARNING)
|
||||
<< "View Tree watcher attempted to report too many views";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PortableUITest::WatchViewGeometry() {
|
||||
FML_CHECK(view_tree_watcher_)
|
||||
<< "View Tree watcher must be registered before calling Watch()";
|
||||
|
||||
view_tree_watcher_->Watch([this](auto response) {
|
||||
ProcessViewGeometryResponse(std::move(response));
|
||||
WatchViewGeometry();
|
||||
});
|
||||
}
|
||||
|
||||
bool PortableUITest::HasViewConnected(zx_koid_t view_ref_koid) {
|
||||
return last_view_tree_snapshot_.has_value() &&
|
||||
CheckViewExistsInSnapshot(*last_view_tree_snapshot_, view_ref_koid);
|
||||
}
|
||||
|
||||
void PortableUITest::LaunchClient() {
|
||||
scene_provider_ = realm_->Connect<fuchsia::ui::test::scene::Controller>();
|
||||
scene_provider_.set_error_handler(
|
||||
[](auto) { FML_LOG(ERROR) << "Error from test scene provider"; });
|
||||
fuchsia::ui::test::scene::ControllerAttachClientViewRequest request;
|
||||
request.set_view_provider(realm_->Connect<fuchsia::ui::app::ViewProvider>());
|
||||
scene_provider_->RegisterViewTreeWatcher(view_tree_watcher_.NewRequest(),
|
||||
[]() {});
|
||||
scene_provider_->AttachClientView(
|
||||
std::move(request), [this](auto client_view_ref_koid) {
|
||||
client_root_view_ref_koid_ = client_view_ref_koid;
|
||||
});
|
||||
|
||||
FML_LOG(INFO) << "Waiting for client view ref koid";
|
||||
RunLoopUntil([this] { return client_root_view_ref_koid_.has_value(); });
|
||||
|
||||
WatchViewGeometry();
|
||||
|
||||
FML_LOG(INFO) << "Waiting for client view to connect";
|
||||
RunLoopUntil(
|
||||
[this] { return HasViewConnected(*client_root_view_ref_koid_); });
|
||||
FML_LOG(INFO) << "Client view has rendered";
|
||||
}
|
||||
|
||||
void PortableUITest::RegisterTouchScreen() {
|
||||
FML_LOG(INFO) << "Registering fake touch screen";
|
||||
input_registry_ = realm_->Connect<fuchsia::ui::test::input::Registry>();
|
||||
input_registry_.set_error_handler(
|
||||
[](auto) { FML_LOG(ERROR) << "Error from input helper"; });
|
||||
|
||||
bool touchscreen_registered = false;
|
||||
fuchsia::ui::test::input::RegistryRegisterTouchScreenRequest request;
|
||||
request.set_device(fake_touchscreen_.NewRequest());
|
||||
input_registry_->RegisterTouchScreen(
|
||||
std::move(request),
|
||||
[&touchscreen_registered]() { touchscreen_registered = true; });
|
||||
|
||||
RunLoopUntil([&touchscreen_registered] { return touchscreen_registered; });
|
||||
FML_LOG(INFO) << "Touchscreen registered";
|
||||
}
|
||||
|
||||
void PortableUITest::InjectTap(int32_t x, int32_t y) {
|
||||
fuchsia::ui::test::input::TouchScreenSimulateTapRequest tap_request;
|
||||
tap_request.mutable_tap_location()->x = x;
|
||||
tap_request.mutable_tap_location()->y = y;
|
||||
|
||||
FML_LOG(INFO) << "Injecting tap at (" << tap_request.tap_location().x << ", "
|
||||
<< tap_request.tap_location().y << ")";
|
||||
fake_touchscreen_->SimulateTap(std::move(tap_request), [this]() {
|
||||
++touch_injection_request_count_;
|
||||
FML_LOG(INFO) << "*** Tap injected, count: "
|
||||
<< touch_injection_request_count_;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace fuchsia_test_utils
|
||||
@ -0,0 +1,99 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_FLUTTER_TESTS_INTEGRATION_UTILS_PORTABLE_UI_TEST_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_FUCHSIA_FLUTTER_TESTS_INTEGRATION_UTILS_PORTABLE_UI_TEST_H_
|
||||
|
||||
#include <fuchsia/sysmem/cpp/fidl.h>
|
||||
#include <fuchsia/ui/test/input/cpp/fidl.h>
|
||||
#include <fuchsia/ui/test/scene/cpp/fidl.h>
|
||||
#include <lib/async-loop/testing/cpp/real_loop.h>
|
||||
#include <lib/sys/component/cpp/testing/realm_builder.h>
|
||||
#include <lib/sys/component/cpp/testing/realm_builder_types.h>
|
||||
#include <zircon/status.h>
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace fuchsia_test_utils {
|
||||
class PortableUITest : public ::loop_fixture::RealLoop {
|
||||
public:
|
||||
// The FIDL bindings for these services are not exposed in the Fuchsia SDK so
|
||||
// we must encode the names manually here.
|
||||
static constexpr auto kVulkanLoaderServiceName =
|
||||
"fuchsia.vulkan.loader.Loader";
|
||||
static constexpr auto kProfileProviderServiceName =
|
||||
"fuchsia.sheduler.ProfileProvider";
|
||||
static constexpr auto kTestUIStack = "ui";
|
||||
static constexpr auto kTestUIStackRef =
|
||||
component_testing::ChildRef{kTestUIStack};
|
||||
|
||||
void SetUp();
|
||||
|
||||
// Attaches a client view to the scene, and waits for it to render.
|
||||
void LaunchClient();
|
||||
|
||||
// Returns true when the specified view is fully connected to the scene AND
|
||||
// has presented at least one frame of content.
|
||||
bool HasViewConnected(zx_koid_t view_ref_koid);
|
||||
|
||||
// Registers a fake touch screen device with an injection coordinate space
|
||||
// spanning [-1000, 1000] on both axes.
|
||||
void RegisterTouchScreen();
|
||||
|
||||
// Simulates a tap at location (x, y).
|
||||
void InjectTap(int32_t x, int32_t y);
|
||||
|
||||
protected:
|
||||
component_testing::RealmBuilder* realm_builder() { return &realm_builder_; }
|
||||
component_testing::RealmRoot* realm_root() { return realm_.get(); }
|
||||
|
||||
int touch_injection_request_count() const {
|
||||
return touch_injection_request_count_;
|
||||
}
|
||||
|
||||
private:
|
||||
void SetUpRealmBase();
|
||||
|
||||
// Configures the test-specific component topology.
|
||||
virtual void ExtendRealm() = 0;
|
||||
|
||||
// Returns the test-ui-stack component url to use in this test.
|
||||
virtual std::string GetTestUIStackUrl() = 0;
|
||||
|
||||
// Helper method to watch watch for view geometry updates.
|
||||
void WatchViewGeometry();
|
||||
|
||||
// Helper method to process a view geometry update.
|
||||
void ProcessViewGeometryResponse(
|
||||
fuchsia::ui::observation::geometry::WatchResponse response);
|
||||
|
||||
fuchsia::ui::test::input::RegistryPtr input_registry_;
|
||||
fuchsia::ui::test::input::TouchScreenPtr fake_touchscreen_;
|
||||
fuchsia::ui::test::scene::ControllerPtr scene_provider_;
|
||||
fuchsia::ui::observation::geometry::ViewTreeWatcherPtr view_tree_watcher_;
|
||||
|
||||
component_testing::RealmBuilder realm_builder_ =
|
||||
component_testing::RealmBuilder::Create();
|
||||
std::unique_ptr<component_testing::RealmRoot> realm_;
|
||||
|
||||
// Counts the number of completed requests to inject touch reports into input
|
||||
// pipeline.
|
||||
int touch_injection_request_count_ = 0;
|
||||
|
||||
// The KOID of the client root view's `ViewRef`.
|
||||
std::optional<zx_koid_t> client_root_view_ref_koid_;
|
||||
|
||||
// Holds the most recent view tree snapshot received from the view tree
|
||||
// watcher.
|
||||
//
|
||||
// From this snapshot, we can retrieve relevant view tree state on demand,
|
||||
// e.g. if the client view is rendering content.
|
||||
std::optional<fuchsia::ui::observation::geometry::ViewTreeSnapshot>
|
||||
last_view_tree_snapshot_;
|
||||
};
|
||||
|
||||
} // namespace fuchsia_test_utils
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_FLUTTER_TESTS_INTEGRATION_UTILS_PORTABLE_UI_TEST_H_
|
||||
Loading…
x
Reference in New Issue
Block a user