Flutter Engine
The Flutter Engine
flutter-embedder-test.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <fuchsia/inspect/cpp/fidl.h>
6#include <fuchsia/logger/cpp/fidl.h>
7#include <fuchsia/sysmem/cpp/fidl.h>
8#include <fuchsia/sysmem2/cpp/fidl.h>
9#include <fuchsia/tracing/provider/cpp/fidl.h>
10#include <fuchsia/ui/app/cpp/fidl.h>
11#include <fuchsia/ui/composition/cpp/fidl.h>
12#include <fuchsia/ui/display/singleton/cpp/fidl.h>
13#include <fuchsia/ui/observation/geometry/cpp/fidl.h>
14#include <fuchsia/ui/test/input/cpp/fidl.h>
15#include <fuchsia/ui/test/scene/cpp/fidl.h>
16#include <lib/async-loop/testing/cpp/real_loop.h>
17#include <lib/async/cpp/task.h>
18#include <lib/fidl/cpp/binding_set.h>
19#include <lib/sys/component/cpp/testing/realm_builder.h>
20#include <lib/sys/component/cpp/testing/realm_builder_types.h>
21#include <lib/zx/clock.h>
22#include <zircon/status.h>
23#include <zircon/time.h>
24
25#include <optional>
26#include <vector>
27
28#include "flutter/fml/logging.h"
29#include "gtest/gtest.h"
30
31#include "flutter/shell/platform/fuchsia/flutter/tests/integration/utils/check_view.h"
32#include "flutter/shell/platform/fuchsia/flutter/tests/integration/utils/screenshot.h"
33
35namespace {
36
37// Types imported for the realm_builder library.
38using component_testing::ChildOptions;
39using component_testing::ChildRef;
40using component_testing::DirectoryContents;
41using component_testing::ParentRef;
42using component_testing::Protocol;
43using component_testing::RealmRoot;
44using component_testing::Route;
45using component_testing::StartupMode;
46
48
49// The FIDL bindings for this service are not exposed in the Fuchsia SDK, so we
50// must encode the name manually here.
51constexpr auto kVulkanLoaderServiceName = "fuchsia.vulkan.loader.Loader";
52
53constexpr auto kFlutterJitRunnerUrl =
54 "fuchsia-pkg://fuchsia.com/oot_flutter_jit_runner#meta/"
55 "flutter_jit_runner.cm";
56constexpr auto kFlutterJitProductRunnerUrl =
57 "fuchsia-pkg://fuchsia.com/oot_flutter_jit_product_runner#meta/"
58 "flutter_jit_product_runner.cm";
59constexpr auto kFlutterAotRunnerUrl =
60 "fuchsia-pkg://fuchsia.com/oot_flutter_aot_runner#meta/"
61 "flutter_aot_runner.cm";
62constexpr auto kFlutterAotProductRunnerUrl =
63 "fuchsia-pkg://fuchsia.com/oot_flutter_aot_product_runner#meta/"
64 "flutter_aot_product_runner.cm";
65constexpr char kChildViewUrl[] =
66 "fuchsia-pkg://fuchsia.com/child-view#meta/child-view.cm";
67constexpr char kParentViewUrl[] =
68 "fuchsia-pkg://fuchsia.com/parent-view#meta/parent-view.cm";
69static constexpr auto kTestUiStackUrl =
70 "fuchsia-pkg://fuchsia.com/flatland-scene-manager-test-ui-stack#meta/"
71 "test-ui-stack.cm";
72
73constexpr auto kFlutterRunnerEnvironment = "flutter_runner_env";
74constexpr auto kFlutterJitRunner = "flutter_jit_runner";
75constexpr auto kFlutterJitRunnerRef = ChildRef{kFlutterJitRunner};
76constexpr auto kFlutterJitProductRunner = "flutter_jit_product_runner";
77constexpr auto kFlutterJitProductRunnerRef = ChildRef{kFlutterJitProductRunner};
78constexpr auto kFlutterAotRunner = "flutter_aot_runner";
79constexpr auto kFlutterAotRunnerRef = ChildRef{kFlutterAotRunner};
80constexpr auto kFlutterAotProductRunner = "flutter_aot_product_runner";
81constexpr auto kFlutterAotProductRunnerRef = ChildRef{kFlutterAotProductRunner};
82constexpr auto kChildView = "child_view";
83constexpr auto kChildViewRef = ChildRef{kChildView};
84constexpr auto kParentView = "parent_view";
85constexpr auto kParentViewRef = ChildRef{kParentView};
86constexpr auto kTestUiStack = "ui";
87constexpr auto kTestUiStackRef = ChildRef{kTestUiStack};
88
89// Background and foreground color values.
90const fuchsia_test_utils::Pixel kParentBackgroundColor(0xFF,
91 0x00,
92 0x00,
93 0xFF); // Blue
94const fuchsia_test_utils::Pixel kChildBackgroundColor(0xFF,
95 0x00,
96 0xFF,
97 0xFF); // Pink
98const fuchsia_test_utils::Pixel kFlatlandOverlayColor(0x00,
99 0xFF,
100 0x00,
101 0xFF); // Green
102
103static uint32_t OverlayPixelCount(
104 std::map<fuchsia_test_utils::Pixel, uint32_t>& histogram) {
105 return histogram[kFlatlandOverlayColor];
106}
107
108// Timeout for |TakeScreenshot| FIDL call.
109constexpr zx::duration kScreenshotTimeout = zx::sec(10);
110// Timeout to fail the test if it goes beyond this duration.
111constexpr zx::duration kTestTimeout = zx::min(1);
112
113} // namespace
114
115class FlutterEmbedderTest : public ::loop_fixture::RealLoop,
116 public ::testing::Test {
117 public:
119 : realm_builder_(component_testing::RealmBuilder::Create()) {
120 FML_VLOG(1) << "Setting up base realm";
121 SetUpRealmBase();
122
123 // Post a "just in case" quit task, if the test hangs.
124 async::PostDelayedTask(
125 dispatcher(),
126 [] {
128 << "\n\n>> Test did not complete in time, terminating. <<\n\n";
129 },
130 kTestTimeout);
131 }
132
133 bool HasViewConnected(
134 const fuchsia::ui::observation::geometry::ViewTreeWatcherPtr&
135 view_tree_watcher,
136 std::optional<fuchsia::ui::observation::geometry::WatchResponse>&
137 watch_response,
138 zx_koid_t view_ref_koid);
139
141 const std::vector<std::string>& component_args = {});
142
144
147 fit::function<void(std::map<fuchsia_test_utils::Pixel, uint32_t>)>
148 callback = nullptr,
149 zx::duration timeout = kTestTimeout);
150
151 private:
152 void SetUpRealmBase();
153
154 fuchsia::ui::test::scene::ControllerPtr scene_provider_;
155 fuchsia::ui::observation::geometry::ViewTreeWatcherPtr view_tree_watcher_;
156 fuchsia::ui::composition::ScreenshotPtr screenshot_;
157
158 // Wrapped in optional since the view is not created until the middle of SetUp
159 component_testing::RealmBuilder realm_builder_;
160 std::unique_ptr<component_testing::RealmRoot> realm_;
161
162 uint64_t display_width_ = 0;
163 uint64_t display_height_ = 0;
164};
165
166void FlutterEmbedderTest::SetUpRealmBase() {
167 FML_LOG(INFO) << "Setting up realm base.";
168
169 // First, add the flutter runner(s) as children.
170 realm_builder_.AddChild(kFlutterJitRunner, kFlutterJitRunnerUrl);
171 realm_builder_.AddChild(kFlutterJitProductRunner,
172 kFlutterJitProductRunnerUrl);
173 realm_builder_.AddChild(kFlutterAotRunner, kFlutterAotRunnerUrl);
174 realm_builder_.AddChild(kFlutterAotProductRunner,
175 kFlutterAotProductRunnerUrl);
176
177 // Then, add an environment providing them.
178 fuchsia::component::decl::Environment flutter_runner_environment;
179 flutter_runner_environment.set_name(kFlutterRunnerEnvironment);
180 flutter_runner_environment.set_extends(
181 fuchsia::component::decl::EnvironmentExtends::REALM);
182 flutter_runner_environment.set_runners({});
183 auto environment_runners = flutter_runner_environment.mutable_runners();
184 fuchsia::component::decl::RunnerRegistration flutter_jit_runner_reg;
185 flutter_jit_runner_reg.set_source(fuchsia::component::decl::Ref::WithChild(
186 fuchsia::component::decl::ChildRef{.name = kFlutterJitRunner}));
187 flutter_jit_runner_reg.set_source_name(kFlutterJitRunner);
188 flutter_jit_runner_reg.set_target_name(kFlutterJitRunner);
189 environment_runners->push_back(std::move(flutter_jit_runner_reg));
190 fuchsia::component::decl::RunnerRegistration flutter_jit_product_runner_reg;
191 flutter_jit_product_runner_reg.set_source(
192 fuchsia::component::decl::Ref::WithChild(
193 fuchsia::component::decl::ChildRef{.name =
194 kFlutterJitProductRunner}));
195 flutter_jit_product_runner_reg.set_source_name(kFlutterJitProductRunner);
196 flutter_jit_product_runner_reg.set_target_name(kFlutterJitProductRunner);
197 environment_runners->push_back(std::move(flutter_jit_product_runner_reg));
198 fuchsia::component::decl::RunnerRegistration flutter_aot_runner_reg;
199 flutter_aot_runner_reg.set_source(fuchsia::component::decl::Ref::WithChild(
200 fuchsia::component::decl::ChildRef{.name = kFlutterAotRunner}));
201 flutter_aot_runner_reg.set_source_name(kFlutterAotRunner);
202 flutter_aot_runner_reg.set_target_name(kFlutterAotRunner);
203 environment_runners->push_back(std::move(flutter_aot_runner_reg));
204 fuchsia::component::decl::RunnerRegistration flutter_aot_product_runner_reg;
205 flutter_aot_product_runner_reg.set_source(
206 fuchsia::component::decl::Ref::WithChild(
207 fuchsia::component::decl::ChildRef{.name =
208 kFlutterAotProductRunner}));
209 flutter_aot_product_runner_reg.set_source_name(kFlutterAotProductRunner);
210 flutter_aot_product_runner_reg.set_target_name(kFlutterAotProductRunner);
211 environment_runners->push_back(std::move(flutter_aot_product_runner_reg));
212 auto realm_decl = realm_builder_.GetRealmDecl();
213 if (!realm_decl.has_environments()) {
214 realm_decl.set_environments({});
215 }
216 auto realm_environments = realm_decl.mutable_environments();
217 realm_environments->push_back(std::move(flutter_runner_environment));
218 realm_builder_.ReplaceRealmDecl(std::move(realm_decl));
219
220 // Add test UI stack component.
221 realm_builder_.AddChild(kTestUiStack, kTestUiStackUrl);
222
223 // Add embedded parent and child components.
224 realm_builder_.AddChild(kChildView, kChildViewUrl,
225 ChildOptions{
226 .environment = kFlutterRunnerEnvironment,
227 });
228 realm_builder_.AddChild(kParentView, kParentViewUrl,
229 ChildOptions{
230 .environment = kFlutterRunnerEnvironment,
231 });
232
233 // Route base system services to flutter runners.
234 realm_builder_.AddRoute(
235 Route{.capabilities =
236 {
237 Protocol{fuchsia::logger::LogSink::Name_},
238 Protocol{fuchsia::inspect::InspectSink::Name_},
239 Protocol{fuchsia::sysmem::Allocator::Name_},
240
241 // Replace "fuchsia.sysmem2.Allocator" with
242 // fuchsia::sysmem2::Allocator::Name_
243 // when available (fuchsia SDK >= 19).
244 Protocol{"fuchsia.sysmem2.Allocator"},
245 Protocol{fuchsia::tracing::provider::Registry::Name_},
246 Protocol{kVulkanLoaderServiceName},
247 },
248 .source = ParentRef{},
249 .targets = {kFlutterJitRunnerRef, kFlutterJitProductRunnerRef,
250 kFlutterAotRunnerRef, kFlutterAotProductRunnerRef}});
251
252 // Route base system services to the test UI stack.
253 realm_builder_.AddRoute(Route{
254 .capabilities = {Protocol{fuchsia::logger::LogSink::Name_},
255 Protocol{fuchsia::inspect::InspectSink::Name_},
256 Protocol{fuchsia::sysmem::Allocator::Name_},
257
258 // Replace "fuchsia.sysmem2.Allocator" with
259 // fuchsia::sysmem2::Allocator::Name_
260 // when available (fuchsia SDK >= 19).
261 Protocol{"fuchsia.sysmem2.Allocator"},
262 Protocol{fuchsia::tracing::provider::Registry::Name_},
263 Protocol{kVulkanLoaderServiceName}},
264 .source = ParentRef{},
265 .targets = {kTestUiStackRef}});
266
267 // Route UI capabilities from test UI stack to flutter runners.
268 realm_builder_.AddRoute(Route{
269 .capabilities = {Protocol{fuchsia::ui::composition::Allocator::Name_},
270 Protocol{fuchsia::ui::composition::Flatland::Name_}},
271 .source = kTestUiStackRef,
272 .targets = {kFlutterJitRunnerRef, kFlutterJitProductRunnerRef,
273 kFlutterAotRunnerRef, kFlutterAotProductRunnerRef}});
274
275 // Route test capabilities from test UI stack to test driver.
276 realm_builder_.AddRoute(Route{
277 .capabilities = {Protocol{fuchsia::ui::composition::Screenshot::Name_},
278 Protocol{fuchsia::ui::test::input::Registry::Name_},
279 Protocol{fuchsia::ui::test::scene::Controller::Name_},
280 Protocol{fuchsia::ui::display::singleton::Info::Name_}},
281 .source = kTestUiStackRef,
282 .targets = {ParentRef()}});
283
284 // Route ViewProvider from child to parent, and parent to test.
285 realm_builder_.AddRoute(
286 Route{.capabilities = {Protocol{fuchsia::ui::app::ViewProvider::Name_}},
287 .source = kParentViewRef,
288 .targets = {ParentRef()}});
289 realm_builder_.AddRoute(
290 Route{.capabilities = {Protocol{fuchsia::ui::app::ViewProvider::Name_}},
291 .source = kChildViewRef,
292 .targets = {kParentViewRef}});
293}
294
295// Checks whether the view with |view_ref_koid| has connected to the view tree.
296// The response of a f.u.o.g.Provider.Watch call is stored in |watch_response|
297// if it contains |view_ref_koid|.
299 const fuchsia::ui::observation::geometry::ViewTreeWatcherPtr&
300 view_tree_watcher,
301 std::optional<fuchsia::ui::observation::geometry::WatchResponse>&
302 watch_response,
303 zx_koid_t view_ref_koid) {
304 std::optional<fuchsia::ui::observation::geometry::WatchResponse> watch_result;
305 view_tree_watcher->Watch(
306 [&watch_result](auto response) { watch_result = std::move(response); });
307 FML_LOG(INFO) << "Waiting for view tree watch result";
308 RunLoopUntil([&watch_result] { return watch_result.has_value(); });
309 FML_LOG(INFO) << "Received for view tree watch result";
310 if (CheckViewExistsInUpdates(watch_result->updates(), view_ref_koid)) {
311 watch_response = std::move(watch_result);
312 };
313 return watch_response.has_value();
314}
315
317 const std::vector<std::string>& component_args) {
318 FML_LOG(INFO) << "Launching parent-view";
319
320 if (!component_args.empty()) {
321 // Construct a args.csv file containing the specified comma-separated
322 // component args.
323 std::string csv;
324 for (const auto& arg : component_args) {
325 csv += arg + ',';
326 }
327 // Remove last comma.
328 csv.pop_back();
329
330 auto config_directory_contents = DirectoryContents();
331 config_directory_contents.AddFile("args.csv", csv);
332 realm_builder_.RouteReadOnlyDirectory("config-data", {kParentViewRef},
333 std::move(config_directory_contents));
334 }
335 realm_ = std::make_unique<RealmRoot>(realm_builder_.Build());
336
337 // Get the display information using the |fuchsia.ui.display.singleton.Info|.
338 std::optional<bool> display_metrics_obtained;
339 fuchsia::ui::display::singleton::InfoPtr display_info =
340 realm_->component().Connect<fuchsia::ui::display::singleton::Info>();
341 display_info->GetMetrics([this, &display_metrics_obtained](auto info) {
342 display_width_ = info.extent_in_px().width;
343 display_height_ = info.extent_in_px().height;
344 display_metrics_obtained = true;
345 });
346 RunLoopUntil([&display_metrics_obtained] {
347 return display_metrics_obtained.has_value();
348 });
349 FML_LOG(INFO) << "Got display_width " << display_width_ << " display_height "
350 << display_height_;
351
352 // Instruct Test UI Stack to present parent-view's View.
353 std::optional<zx_koid_t> view_ref_koid;
354 scene_provider_ =
355 realm_->component().Connect<fuchsia::ui::test::scene::Controller>();
356 scene_provider_.set_error_handler(
357 [](auto) { FML_LOG(ERROR) << "Error from test scene provider"; });
358 fuchsia::ui::test::scene::ControllerAttachClientViewRequest request;
359 request.set_view_provider(
360 realm_->component().Connect<fuchsia::ui::app::ViewProvider>());
361 scene_provider_->RegisterViewTreeWatcher(view_tree_watcher_.NewRequest(),
362 []() {});
363 scene_provider_->AttachClientView(
364 std::move(request), [&view_ref_koid](auto client_view_ref_koid) {
365 view_ref_koid = client_view_ref_koid;
366 });
367
368 FML_LOG(INFO) << "Waiting for client view ref koid";
369 RunLoopUntil([&view_ref_koid] { return view_ref_koid.has_value(); });
370
371 // Wait for the client view to get attached to the view tree.
372 std::optional<fuchsia::ui::observation::geometry::WatchResponse>
373 watch_response;
374 FML_LOG(INFO) << "Waiting for client view to render; koid is "
375 << (view_ref_koid.has_value() ? view_ref_koid.value() : 0);
376 RunLoopUntil([this, &watch_response, &view_ref_koid] {
377 return HasViewConnected(view_tree_watcher_, watch_response, *view_ref_koid);
378 });
379 FML_LOG(INFO) << "Client view has rendered";
380
381 screenshot_ =
382 realm_->component().Connect<fuchsia::ui::composition::Screenshot>();
383 FML_LOG(INFO) << "Launched parent-view";
384}
385
387 FML_LOG(INFO) << "Taking screenshot... ";
388
389 fuchsia::ui::composition::ScreenshotTakeRequest request;
390 request.set_format(fuchsia::ui::composition::ScreenshotFormat::BGRA_RAW);
391
392 std::optional<fuchsia::ui::composition::ScreenshotTakeResponse> response;
393 screenshot_->Take(std::move(request), [this, &response](auto screenshot) {
394 response = std::move(screenshot);
395 QuitLoop();
396 });
397
398 EXPECT_FALSE(RunLoopWithTimeout(kScreenshotTimeout))
399 << "Timed out waiting for screenshot.";
400 FML_LOG(INFO) << "Screenshot captured.";
401
403 response->vmo(), display_width_, display_height_, /*display_rotation*/ 0);
404}
405
408 fit::function<void(std::map<fuchsia_test_utils::Pixel, uint32_t>)> callback,
410 return RunLoopWithTimeoutOrUntil(
411 [this, &callback, &color] {
412 auto screenshot = TakeScreenshot();
413 auto histogram = screenshot.Histogram();
414
415 bool color_found = histogram[color] > 0;
416 if (color_found && callback != nullptr) {
417 callback(std::move(histogram));
418 }
419 return color_found;
420 },
421 timeout);
422}
423
425 LaunchParentViewInRealm();
426
427 // Take screenshot until we see the child-view's embedded color.
428 ASSERT_TRUE(TakeScreenshotUntil(
429 kChildBackgroundColor,
430 [](std::map<fuchsia_test_utils::Pixel, uint32_t> histogram) {
431 // Expect parent and child background colors, with parent color > child
432 // color.
433 EXPECT_GT(histogram[kParentBackgroundColor], 0u);
434 EXPECT_GT(histogram[kChildBackgroundColor], 0u);
435 EXPECT_GT(histogram[kParentBackgroundColor],
436 histogram[kChildBackgroundColor]);
437 }));
438}
439
440TEST_F(FlutterEmbedderTest, EmbeddingWithOverlay) {
441 LaunchParentViewInRealm({"--showOverlay"});
442
443 // Take screenshot until we see the child-view's embedded color.
444 ASSERT_TRUE(TakeScreenshotUntil(
445 kChildBackgroundColor,
446 [](std::map<fuchsia_test_utils::Pixel, uint32_t> histogram) {
447 // Expect parent, overlay and child background colors.
448 // With parent color > child color and overlay color > child color.
449 const uint32_t overlay_pixel_count = OverlayPixelCount(histogram);
450 EXPECT_GT(histogram[kParentBackgroundColor], 0u);
451 EXPECT_GT(overlay_pixel_count, 0u);
452 EXPECT_GT(histogram[kChildBackgroundColor], 0u);
453 EXPECT_GT(histogram[kParentBackgroundColor],
454 histogram[kChildBackgroundColor]);
455 EXPECT_GT(overlay_pixel_count, histogram[kChildBackgroundColor]);
456 }));
457}
458
459} // namespace flutter_embedder_test
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
static sk_sp< Effect > Create()
Definition: RefCntTest.cpp:117
fuchsia_test_utils::Screenshot TakeScreenshot()
bool HasViewConnected(const fuchsia::ui::observation::geometry::ViewTreeWatcherPtr &view_tree_watcher, std::optional< fuchsia::ui::observation::geometry::WatchResponse > &watch_response, zx_koid_t view_ref_koid)
bool TakeScreenshotUntil(fuchsia_test_utils::Pixel color, fit::function< void(std::map< fuchsia_test_utils::Pixel, uint32_t >)> callback=nullptr, zx::duration timeout=kTestTimeout)
void LaunchParentViewInRealm(const std::vector< std::string > &component_args={})
DlColor color
double duration
Definition: examples.cpp:30
#define FATAL(error)
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
#define FML_VLOG(verbose_level)
Definition: logging.h:98
#define FML_LOG(severity)
Definition: logging.h:82
Dart_NativeFunction function
Definition: fuchsia.cc:51
static float min(float r, float g, float b)
Definition: hsl.cpp:48
TEST_F(FlutterEmbedderTest, Embedding)
bool CheckViewExistsInUpdates(const std::vector< fuchsia::ui::observation::geometry::ViewTreeSnapshot > &updates, zx_koid_t view_ref_koid)
Definition: check_view.cc:27
def timeout(deadline, cmd)
#define ERROR(message)
Definition: elf_loader.cc:260