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