• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #define FML_USED_ON_EMBEDDER
6 
7 #include <functional>
8 #include <future>
9 #include <memory>
10 
11 #include "flutter/flow/layers/layer_tree.h"
12 #include "flutter/flow/layers/transform_layer.h"
13 #include "flutter/fml/command_line.h"
14 #include "flutter/fml/make_copyable.h"
15 #include "flutter/fml/message_loop.h"
16 #include "flutter/fml/synchronization/count_down_latch.h"
17 #include "flutter/fml/synchronization/waitable_event.h"
18 #include "flutter/runtime/dart_vm.h"
19 #include "flutter/shell/common/platform_view.h"
20 #include "flutter/shell/common/rasterizer.h"
21 #include "flutter/shell/common/shell_test.h"
22 #include "flutter/shell/common/switches.h"
23 #include "flutter/shell/common/thread_host.h"
24 #include "flutter/testing/testing.h"
25 #include "third_party/tonic/converter/dart_converter.h"
26 
27 namespace flutter {
28 namespace testing {
29 
ValidateShell(Shell * shell)30 static bool ValidateShell(Shell* shell) {
31   if (!shell) {
32     return false;
33   }
34 
35   if (!shell->IsSetup()) {
36     return false;
37   }
38 
39   ShellTest::PlatformViewNotifyCreated(shell);
40 
41   {
42     fml::AutoResetWaitableEvent latch;
43     fml::TaskRunner::RunNowOrPostTask(
44         shell->GetTaskRunners().GetPlatformTaskRunner(), [shell, &latch]() {
45           shell->GetPlatformView()->NotifyDestroyed();
46           latch.Signal();
47         });
48     latch.Wait();
49   }
50 
51   return true;
52 }
53 
TEST_F(ShellTest,InitializeWithInvalidThreads)54 TEST_F(ShellTest, InitializeWithInvalidThreads) {
55   ASSERT_FALSE(DartVMRef::IsInstanceRunning());
56   Settings settings = CreateSettingsForFixture();
57   TaskRunners task_runners("test", nullptr, nullptr, nullptr, nullptr);
58   auto shell = CreateShell(std::move(settings), std::move(task_runners));
59   ASSERT_FALSE(shell);
60   ASSERT_FALSE(DartVMRef::IsInstanceRunning());
61 }
62 
TEST_F(ShellTest,InitializeWithDifferentThreads)63 TEST_F(ShellTest, InitializeWithDifferentThreads) {
64   ASSERT_FALSE(DartVMRef::IsInstanceRunning());
65   Settings settings = CreateSettingsForFixture();
66   ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
67                          ThreadHost::Type::Platform | ThreadHost::Type::GPU |
68                              ThreadHost::Type::IO | ThreadHost::Type::UI);
69   TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
70                            thread_host.gpu_thread->GetTaskRunner(),
71                            thread_host.ui_thread->GetTaskRunner(),
72                            thread_host.io_thread->GetTaskRunner());
73   auto shell = CreateShell(std::move(settings), std::move(task_runners));
74   ASSERT_TRUE(ValidateShell(shell.get()));
75   ASSERT_TRUE(DartVMRef::IsInstanceRunning());
76   shell.reset();
77   ASSERT_FALSE(DartVMRef::IsInstanceRunning());
78 }
79 
TEST_F(ShellTest,InitializeWithSingleThread)80 TEST_F(ShellTest, InitializeWithSingleThread) {
81   ASSERT_FALSE(DartVMRef::IsInstanceRunning());
82   Settings settings = CreateSettingsForFixture();
83   ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
84                          ThreadHost::Type::Platform);
85   auto task_runner = thread_host.platform_thread->GetTaskRunner();
86   TaskRunners task_runners("test", task_runner, task_runner, task_runner,
87                            task_runner);
88   auto shell = CreateShell(std::move(settings), std::move(task_runners));
89   ASSERT_TRUE(DartVMRef::IsInstanceRunning());
90   ASSERT_TRUE(ValidateShell(shell.get()));
91   shell.reset();
92   ASSERT_FALSE(DartVMRef::IsInstanceRunning());
93 }
94 
TEST_F(ShellTest,InitializeWithSingleThreadWhichIsTheCallingThread)95 TEST_F(ShellTest, InitializeWithSingleThreadWhichIsTheCallingThread) {
96   ASSERT_FALSE(DartVMRef::IsInstanceRunning());
97   Settings settings = CreateSettingsForFixture();
98   fml::MessageLoop::EnsureInitializedForCurrentThread();
99   auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
100   TaskRunners task_runners("test", task_runner, task_runner, task_runner,
101                            task_runner);
102   auto shell = CreateShell(std::move(settings), std::move(task_runners));
103   ASSERT_TRUE(ValidateShell(shell.get()));
104   ASSERT_TRUE(DartVMRef::IsInstanceRunning());
105   shell.reset();
106   ASSERT_FALSE(DartVMRef::IsInstanceRunning());
107 }
108 
TEST_F(ShellTest,InitializeWithMultipleThreadButCallingThreadAsPlatformThread)109 TEST_F(ShellTest,
110        InitializeWithMultipleThreadButCallingThreadAsPlatformThread) {
111   ASSERT_FALSE(DartVMRef::IsInstanceRunning());
112   Settings settings = CreateSettingsForFixture();
113   ThreadHost thread_host(
114       "io.flutter.test." + GetCurrentTestName() + ".",
115       ThreadHost::Type::GPU | ThreadHost::Type::IO | ThreadHost::Type::UI);
116   fml::MessageLoop::EnsureInitializedForCurrentThread();
117   TaskRunners task_runners("test",
118                            fml::MessageLoop::GetCurrent().GetTaskRunner(),
119                            thread_host.gpu_thread->GetTaskRunner(),
120                            thread_host.ui_thread->GetTaskRunner(),
121                            thread_host.io_thread->GetTaskRunner());
122   auto shell = Shell::Create(
123       std::move(task_runners), settings,
124       [](Shell& shell) {
125         return std::make_unique<ShellTestPlatformView>(shell,
126                                                        shell.GetTaskRunners());
127       },
128       [](Shell& shell) {
129         return std::make_unique<Rasterizer>(shell, shell.GetTaskRunners());
130       });
131   ASSERT_TRUE(ValidateShell(shell.get()));
132   ASSERT_TRUE(DartVMRef::IsInstanceRunning());
133   shell.reset();
134   ASSERT_FALSE(DartVMRef::IsInstanceRunning());
135 }
136 
TEST_F(ShellTest,InitializeWithGPUAndPlatformThreadsTheSame)137 TEST_F(ShellTest, InitializeWithGPUAndPlatformThreadsTheSame) {
138   ASSERT_FALSE(DartVMRef::IsInstanceRunning());
139   Settings settings = CreateSettingsForFixture();
140   ThreadHost thread_host(
141       "io.flutter.test." + GetCurrentTestName() + ".",
142       ThreadHost::Type::Platform | ThreadHost::Type::IO | ThreadHost::Type::UI);
143   TaskRunners task_runners(
144       "test",
145       thread_host.platform_thread->GetTaskRunner(),  // platform
146       thread_host.platform_thread->GetTaskRunner(),  // gpu
147       thread_host.ui_thread->GetTaskRunner(),        // ui
148       thread_host.io_thread->GetTaskRunner()         // io
149   );
150   auto shell = CreateShell(std::move(settings), std::move(task_runners));
151   ASSERT_TRUE(DartVMRef::IsInstanceRunning());
152   ASSERT_TRUE(ValidateShell(shell.get()));
153   shell.reset();
154   ASSERT_FALSE(DartVMRef::IsInstanceRunning());
155 }
156 
TEST_F(ShellTest,FixturesAreFunctional)157 TEST_F(ShellTest, FixturesAreFunctional) {
158   ASSERT_FALSE(DartVMRef::IsInstanceRunning());
159   auto settings = CreateSettingsForFixture();
160   auto shell = CreateShell(settings);
161   ASSERT_TRUE(ValidateShell(shell.get()));
162 
163   auto configuration = RunConfiguration::InferFromSettings(settings);
164   ASSERT_TRUE(configuration.IsValid());
165   configuration.SetEntrypoint("fixturesAreFunctionalMain");
166 
167   fml::AutoResetWaitableEvent main_latch;
168   AddNativeCallback(
169       "SayHiFromFixturesAreFunctionalMain",
170       CREATE_NATIVE_ENTRY([&main_latch](auto args) { main_latch.Signal(); }));
171 
172   RunEngine(shell.get(), std::move(configuration));
173   main_latch.Wait();
174   ASSERT_TRUE(DartVMRef::IsInstanceRunning());
175   shell.reset();
176   ASSERT_FALSE(DartVMRef::IsInstanceRunning());
177 }
178 
TEST_F(ShellTest,SecondaryIsolateBindingsAreSetupViaShellSettings)179 TEST_F(ShellTest, SecondaryIsolateBindingsAreSetupViaShellSettings) {
180   ASSERT_FALSE(DartVMRef::IsInstanceRunning());
181   auto settings = CreateSettingsForFixture();
182   auto shell = CreateShell(settings);
183   ASSERT_TRUE(ValidateShell(shell.get()));
184 
185   auto configuration = RunConfiguration::InferFromSettings(settings);
186   ASSERT_TRUE(configuration.IsValid());
187   configuration.SetEntrypoint("testCanLaunchSecondaryIsolate");
188 
189   fml::CountDownLatch latch(2);
190   AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&latch](auto args) {
191                       latch.CountDown();
192                     }));
193 
194   RunEngine(shell.get(), std::move(configuration));
195 
196   latch.Wait();
197 
198   ASSERT_TRUE(DartVMRef::IsInstanceRunning());
199   shell.reset();
200   ASSERT_FALSE(DartVMRef::IsInstanceRunning());
201 }
202 
TEST(ShellTestNoFixture,EnableMirrorsIsWhitelisted)203 TEST(ShellTestNoFixture, EnableMirrorsIsWhitelisted) {
204   if (DartVM::IsRunningPrecompiledCode()) {
205     // This covers profile and release modes which use AOT (where this flag does
206     // not make sense anyway).
207     GTEST_SKIP();
208     return;
209   }
210 
211   const std::vector<fml::CommandLine::Option> options = {
212       fml::CommandLine::Option("dart-flags", "--enable_mirrors")};
213   fml::CommandLine command_line("", options, std::vector<std::string>());
214   flutter::Settings settings = flutter::SettingsFromCommandLine(command_line);
215   EXPECT_EQ(settings.dart_flags.size(), 1u);
216 }
217 
TEST_F(ShellTest,BlacklistedDartVMFlag)218 TEST_F(ShellTest, BlacklistedDartVMFlag) {
219   // Run this test in a thread-safe manner, otherwise gtest will complain.
220   ::testing::FLAGS_gtest_death_test_style = "threadsafe";
221 
222   const std::vector<fml::CommandLine::Option> options = {
223       fml::CommandLine::Option("dart-flags", "--verify_after_gc")};
224   fml::CommandLine command_line("", options, std::vector<std::string>());
225 
226 #if FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_RELEASE
227   // Upon encountering a non-whitelisted Dart flag the process terminates.
228   const char* expected =
229       "Encountered blacklisted Dart VM flag: --verify_after_gc";
230   ASSERT_DEATH(flutter::SettingsFromCommandLine(command_line), expected);
231 #else
232   flutter::Settings settings = flutter::SettingsFromCommandLine(command_line);
233   EXPECT_EQ(settings.dart_flags.size(), 0u);
234 #endif
235 }
236 
TEST_F(ShellTest,WhitelistedDartVMFlag)237 TEST_F(ShellTest, WhitelistedDartVMFlag) {
238   const std::vector<fml::CommandLine::Option> options = {
239       fml::CommandLine::Option("dart-flags",
240                                "--max_profile_depth 1,--random_seed 42")};
241   fml::CommandLine command_line("", options, std::vector<std::string>());
242   flutter::Settings settings = flutter::SettingsFromCommandLine(command_line);
243 
244 #if FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_RELEASE
245   EXPECT_EQ(settings.dart_flags.size(), 2u);
246   EXPECT_EQ(settings.dart_flags[0], "--max_profile_depth 1");
247   EXPECT_EQ(settings.dart_flags[1], "--random_seed 42");
248 #else
249   EXPECT_EQ(settings.dart_flags.size(), 0u);
250 #endif
251 }
252 
TEST_F(ShellTest,NoNeedToReportTimingsByDefault)253 TEST_F(ShellTest, NoNeedToReportTimingsByDefault) {
254   auto settings = CreateSettingsForFixture();
255   std::unique_ptr<Shell> shell = CreateShell(settings);
256 
257   // Create the surface needed by rasterizer
258   PlatformViewNotifyCreated(shell.get());
259 
260   auto configuration = RunConfiguration::InferFromSettings(settings);
261   configuration.SetEntrypoint("emptyMain");
262 
263   RunEngine(shell.get(), std::move(configuration));
264   PumpOneFrame(shell.get());
265   ASSERT_FALSE(GetNeedsReportTimings(shell.get()));
266 
267   // This assertion may or may not be the direct result of needs_report_timings_
268   // being false. The count could be 0 simply because we just cleared
269   // unreported timings by reporting them. Hence this can't replace the
270   // ASSERT_FALSE(GetNeedsReportTimings(shell.get())) check. We added
271   // this assertion for an additional confidence that we're not pushing
272   // back to unreported timings unnecessarily.
273   //
274   // Conversely, do not assert UnreportedTimingsCount(shell.get()) to be
275   // positive in any tests. Otherwise those tests will be flaky as the clearing
276   // of unreported timings is unpredictive.
277   ASSERT_EQ(UnreportedTimingsCount(shell.get()), 0);
278 }
279 
TEST_F(ShellTest,NeedsReportTimingsIsSetWithCallback)280 TEST_F(ShellTest, NeedsReportTimingsIsSetWithCallback) {
281   auto settings = CreateSettingsForFixture();
282   std::unique_ptr<Shell> shell = CreateShell(settings);
283 
284   // Create the surface needed by rasterizer
285   PlatformViewNotifyCreated(shell.get());
286 
287   auto configuration = RunConfiguration::InferFromSettings(settings);
288   configuration.SetEntrypoint("dummyReportTimingsMain");
289 
290   RunEngine(shell.get(), std::move(configuration));
291   PumpOneFrame(shell.get());
292   ASSERT_TRUE(GetNeedsReportTimings(shell.get()));
293 }
294 
CheckFrameTimings(const std::vector<FrameTiming> & timings,fml::TimePoint start,fml::TimePoint finish)295 static void CheckFrameTimings(const std::vector<FrameTiming>& timings,
296                               fml::TimePoint start,
297                               fml::TimePoint finish) {
298   fml::TimePoint last_frame_start;
299   for (size_t i = 0; i < timings.size(); i += 1) {
300     // Ensure that timings are sorted.
301     ASSERT_TRUE(timings[i].Get(FrameTiming::kPhases[0]) >= last_frame_start);
302     last_frame_start = timings[i].Get(FrameTiming::kPhases[0]);
303 
304     fml::TimePoint last_phase_time;
305     for (auto phase : FrameTiming::kPhases) {
306       ASSERT_TRUE(timings[i].Get(phase) >= start);
307       ASSERT_TRUE(timings[i].Get(phase) <= finish);
308 
309       // phases should have weakly increasing time points
310       ASSERT_TRUE(last_phase_time <= timings[i].Get(phase));
311       last_phase_time = timings[i].Get(phase);
312     }
313   }
314 }
315 
TEST_F(ShellTest,ReportTimingsIsCalled)316 TEST_F(ShellTest, ReportTimingsIsCalled) {
317   fml::TimePoint start = fml::TimePoint::Now();
318   auto settings = CreateSettingsForFixture();
319   std::unique_ptr<Shell> shell = CreateShell(settings);
320 
321   // Create the surface needed by rasterizer
322   PlatformViewNotifyCreated(shell.get());
323 
324   auto configuration = RunConfiguration::InferFromSettings(settings);
325   ASSERT_TRUE(configuration.IsValid());
326   configuration.SetEntrypoint("reportTimingsMain");
327   fml::AutoResetWaitableEvent reportLatch;
328   std::vector<int64_t> timestamps;
329   auto nativeTimingCallback = [&reportLatch,
330                                &timestamps](Dart_NativeArguments args) {
331     Dart_Handle exception = nullptr;
332     timestamps = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
333         args, 0, exception);
334     reportLatch.Signal();
335   };
336   AddNativeCallback("NativeReportTimingsCallback",
337                     CREATE_NATIVE_ENTRY(nativeTimingCallback));
338   RunEngine(shell.get(), std::move(configuration));
339 
340   // Pump many frames so we can trigger the report quickly instead of waiting
341   // for the 1 second threshold.
342   for (int i = 0; i < 200; i += 1) {
343     PumpOneFrame(shell.get());
344   }
345 
346   reportLatch.Wait();
347   shell.reset();
348 
349   fml::TimePoint finish = fml::TimePoint::Now();
350   ASSERT_TRUE(timestamps.size() > 0);
351   ASSERT_TRUE(timestamps.size() % FrameTiming::kCount == 0);
352   std::vector<FrameTiming> timings(timestamps.size() / FrameTiming::kCount);
353 
354   for (size_t i = 0; i * FrameTiming::kCount < timestamps.size(); i += 1) {
355     for (auto phase : FrameTiming::kPhases) {
356       timings[i].Set(
357           phase,
358           fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMicroseconds(
359               timestamps[i * FrameTiming::kCount + phase])));
360     }
361   }
362   CheckFrameTimings(timings, start, finish);
363 }
364 
TEST_F(ShellTest,FrameRasterizedCallbackIsCalled)365 TEST_F(ShellTest, FrameRasterizedCallbackIsCalled) {
366   fml::TimePoint start = fml::TimePoint::Now();
367 
368   auto settings = CreateSettingsForFixture();
369   fml::AutoResetWaitableEvent timingLatch;
370   FrameTiming timing;
371 
372   for (auto phase : FrameTiming::kPhases) {
373     timing.Set(phase, fml::TimePoint());
374     // Check that the time points are initially smaller than start, so
375     // CheckFrameTimings will fail if they're not properly set later.
376     ASSERT_TRUE(timing.Get(phase) < start);
377   }
378 
379   settings.frame_rasterized_callback = [&timing,
380                                         &timingLatch](const FrameTiming& t) {
381     timing = t;
382     timingLatch.Signal();
383   };
384 
385   std::unique_ptr<Shell> shell = CreateShell(settings);
386 
387   // Create the surface needed by rasterizer
388   PlatformViewNotifyCreated(shell.get());
389 
390   auto configuration = RunConfiguration::InferFromSettings(settings);
391   configuration.SetEntrypoint("onBeginFrameMain");
392 
393   int64_t begin_frame;
394   auto nativeOnBeginFrame = [&begin_frame](Dart_NativeArguments args) {
395     Dart_Handle exception = nullptr;
396     begin_frame =
397         tonic::DartConverter<int64_t>::FromArguments(args, 0, exception);
398   };
399   AddNativeCallback("NativeOnBeginFrame",
400                     CREATE_NATIVE_ENTRY(nativeOnBeginFrame));
401 
402   RunEngine(shell.get(), std::move(configuration));
403 
404   PumpOneFrame(shell.get());
405 
406   // Check that timing is properly set. This implies that
407   // settings.frame_rasterized_callback is called.
408   timingLatch.Wait();
409   fml::TimePoint finish = fml::TimePoint::Now();
410   std::vector<FrameTiming> timings = {timing};
411   CheckFrameTimings(timings, start, finish);
412 
413   // Check that onBeginFrame has the same timestamp as FrameTiming's build start
414   int64_t build_start =
415       timing.Get(FrameTiming::kBuildStart).ToEpochDelta().ToMicroseconds();
416   ASSERT_EQ(build_start, begin_frame);
417 }
418 
TEST(SettingsTest,FrameTimingSetsAndGetsProperly)419 TEST(SettingsTest, FrameTimingSetsAndGetsProperly) {
420   // Ensure that all phases are in kPhases.
421   ASSERT_EQ(sizeof(FrameTiming::kPhases),
422             FrameTiming::kCount * sizeof(FrameTiming::Phase));
423 
424   int lastPhaseIndex = -1;
425   FrameTiming timing;
426   for (auto phase : FrameTiming::kPhases) {
427     ASSERT_TRUE(phase > lastPhaseIndex);  // Ensure that kPhases are in order.
428     lastPhaseIndex = phase;
429     auto fake_time =
430         fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMicroseconds(phase));
431     timing.Set(phase, fake_time);
432     ASSERT_TRUE(timing.Get(phase) == fake_time);
433   }
434 }
435 
436 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_RELEASE
TEST_F(ShellTest,ReportTimingsIsCalledLaterInReleaseMode)437 TEST_F(ShellTest, ReportTimingsIsCalledLaterInReleaseMode) {
438 #else
439 TEST_F(ShellTest, ReportTimingsIsCalledSoonerInNonReleaseMode) {
440 #endif
441   fml::TimePoint start = fml::TimePoint::Now();
442   auto settings = CreateSettingsForFixture();
443   std::unique_ptr<Shell> shell = CreateShell(settings);
444 
445   // Create the surface needed by rasterizer
446   PlatformViewNotifyCreated(shell.get());
447 
448   auto configuration = RunConfiguration::InferFromSettings(settings);
449   ASSERT_TRUE(configuration.IsValid());
450   configuration.SetEntrypoint("reportTimingsMain");
451 
452   // Wait for 2 reports: the first one is the immediate callback of the first
453   // frame; the second one will exercise the batching logic.
454   fml::CountDownLatch reportLatch(2);
455   std::vector<int64_t> timestamps;
456   auto nativeTimingCallback = [&reportLatch,
457                                &timestamps](Dart_NativeArguments args) {
458     Dart_Handle exception = nullptr;
459     timestamps = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
460         args, 0, exception);
461     reportLatch.CountDown();
462   };
463   AddNativeCallback("NativeReportTimingsCallback",
464                     CREATE_NATIVE_ENTRY(nativeTimingCallback));
465   RunEngine(shell.get(), std::move(configuration));
466 
467   PumpOneFrame(shell.get());
468   PumpOneFrame(shell.get());
469 
470   reportLatch.Wait();
471   shell.reset();
472 
473   fml::TimePoint finish = fml::TimePoint::Now();
474   fml::TimeDelta ellapsed = finish - start;
475 
476 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_RELEASE
477   // Our batch time is 1000ms. Hopefully the 800ms limit is relaxed enough to
478   // make it not too flaky.
479   ASSERT_TRUE(ellapsed >= fml::TimeDelta::FromMilliseconds(800));
480 #else
481   // Our batch time is 100ms. Hopefully the 500ms limit is relaxed enough to
482   // make it not too flaky.
483   ASSERT_TRUE(ellapsed <= fml::TimeDelta::FromMilliseconds(500));
484 #endif
485 }
486 
487 TEST_F(ShellTest, ReportTimingsIsCalledImmediatelyAfterTheFirstFrame) {
488   auto settings = CreateSettingsForFixture();
489   std::unique_ptr<Shell> shell = CreateShell(settings);
490 
491   // Create the surface needed by rasterizer
492   PlatformViewNotifyCreated(shell.get());
493 
494   auto configuration = RunConfiguration::InferFromSettings(settings);
495   ASSERT_TRUE(configuration.IsValid());
496   configuration.SetEntrypoint("reportTimingsMain");
497   fml::AutoResetWaitableEvent reportLatch;
498   std::vector<int64_t> timestamps;
499   auto nativeTimingCallback = [&reportLatch,
500                                &timestamps](Dart_NativeArguments args) {
501     Dart_Handle exception = nullptr;
502     timestamps = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
503         args, 0, exception);
504     reportLatch.Signal();
505   };
506   AddNativeCallback("NativeReportTimingsCallback",
507                     CREATE_NATIVE_ENTRY(nativeTimingCallback));
508   RunEngine(shell.get(), std::move(configuration));
509 
510   for (int i = 0; i < 10; i += 1) {
511     PumpOneFrame(shell.get());
512   }
513 
514   reportLatch.Wait();
515   shell.reset();
516 
517   // Check for the immediate callback of the first frame that doesn't wait for
518   // the other 9 frames to be rasterized.
519   ASSERT_EQ(timestamps.size(), FrameTiming::kCount);
520 }
521 
522 TEST_F(ShellTest, WaitForFirstFrame) {
523   auto settings = CreateSettingsForFixture();
524   std::unique_ptr<Shell> shell = CreateShell(settings);
525 
526   // Create the surface needed by rasterizer
527   PlatformViewNotifyCreated(shell.get());
528 
529   auto configuration = RunConfiguration::InferFromSettings(settings);
530   configuration.SetEntrypoint("emptyMain");
531 
532   RunEngine(shell.get(), std::move(configuration));
533   PumpOneFrame(shell.get());
534   fml::Status result =
535       shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1000));
536   ASSERT_TRUE(result.ok());
537 }
538 
539 TEST_F(ShellTest, WaitForFirstFrameTimeout) {
540   auto settings = CreateSettingsForFixture();
541   std::unique_ptr<Shell> shell = CreateShell(settings);
542 
543   // Create the surface needed by rasterizer
544   PlatformViewNotifyCreated(shell.get());
545 
546   auto configuration = RunConfiguration::InferFromSettings(settings);
547   configuration.SetEntrypoint("emptyMain");
548 
549   RunEngine(shell.get(), std::move(configuration));
550   fml::Status result =
551       shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(10));
552   ASSERT_EQ(result.code(), fml::StatusCode::kDeadlineExceeded);
553 }
554 
555 TEST_F(ShellTest, WaitForFirstFrameMultiple) {
556   auto settings = CreateSettingsForFixture();
557   std::unique_ptr<Shell> shell = CreateShell(settings);
558 
559   // Create the surface needed by rasterizer
560   PlatformViewNotifyCreated(shell.get());
561 
562   auto configuration = RunConfiguration::InferFromSettings(settings);
563   configuration.SetEntrypoint("emptyMain");
564 
565   RunEngine(shell.get(), std::move(configuration));
566   PumpOneFrame(shell.get());
567   fml::Status result =
568       shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1000));
569   ASSERT_TRUE(result.ok());
570   for (int i = 0; i < 100; ++i) {
571     result = shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1));
572     ASSERT_TRUE(result.ok());
573   }
574 }
575 
576 /// Makes sure that WaitForFirstFrame works if we rendered a frame with the
577 /// single-thread setup.
578 TEST_F(ShellTest, WaitForFirstFrameInlined) {
579   Settings settings = CreateSettingsForFixture();
580   auto task_runner = GetThreadTaskRunner();
581   TaskRunners task_runners("test", task_runner, task_runner, task_runner,
582                            task_runner);
583   std::unique_ptr<Shell> shell =
584       CreateShell(std::move(settings), std::move(task_runners));
585 
586   // Create the surface needed by rasterizer
587   PlatformViewNotifyCreated(shell.get());
588 
589   auto configuration = RunConfiguration::InferFromSettings(settings);
590   configuration.SetEntrypoint("emptyMain");
591 
592   RunEngine(shell.get(), std::move(configuration));
593   PumpOneFrame(shell.get());
594   fml::AutoResetWaitableEvent event;
595   task_runner->PostTask([&shell, &event] {
596     fml::Status result =
597         shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1000));
598     ASSERT_EQ(result.code(), fml::StatusCode::kFailedPrecondition);
599     event.Signal();
600   });
601   ASSERT_FALSE(event.WaitWithTimeout(fml::TimeDelta::FromMilliseconds(1000)));
602 }
603 
604 static size_t GetRasterizerResourceCacheBytesSync(Shell& shell) {
605   size_t bytes = 0;
606   fml::AutoResetWaitableEvent latch;
607   fml::TaskRunner::RunNowOrPostTask(
608       shell.GetTaskRunners().GetGPUTaskRunner(), [&]() {
609         if (auto rasterizer = shell.GetRasterizer()) {
610           bytes = rasterizer->GetResourceCacheMaxBytes().value_or(0U);
611         }
612         latch.Signal();
613       });
614   latch.Wait();
615   return bytes;
616 }
617 
618 TEST_F(ShellTest, SetResourceCacheSize) {
619   Settings settings = CreateSettingsForFixture();
620   auto task_runner = GetThreadTaskRunner();
621   TaskRunners task_runners("test", task_runner, task_runner, task_runner,
622                            task_runner);
623   std::unique_ptr<Shell> shell =
624       CreateShell(std::move(settings), std::move(task_runners));
625 
626   // Create the surface needed by rasterizer
627   PlatformViewNotifyCreated(shell.get());
628 
629   auto configuration = RunConfiguration::InferFromSettings(settings);
630   configuration.SetEntrypoint("emptyMain");
631 
632   RunEngine(shell.get(), std::move(configuration));
633   PumpOneFrame(shell.get());
634 
635   EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
636             static_cast<size_t>(24 * (1 << 20)));
637 
638   fml::TaskRunner::RunNowOrPostTask(
639       shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
640         shell->GetPlatformView()->SetViewportMetrics(
641             {1.0, 400, 200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
642       });
643   PumpOneFrame(shell.get());
644 
645   EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), 3840000U);
646 
647   std::string request_json = R"json({
648                                 "method": "Skia.setResourceCacheMaxBytes",
649                                 "args": 10000
650                               })json";
651   std::vector<uint8_t> data(request_json.begin(), request_json.end());
652   auto platform_message = fml::MakeRefCounted<PlatformMessage>(
653       "flutter/skia", std::move(data), nullptr);
654   SendEnginePlatformMessage(shell.get(), std::move(platform_message));
655   PumpOneFrame(shell.get());
656   EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), 10000U);
657 
658   fml::TaskRunner::RunNowOrPostTask(
659       shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
660         shell->GetPlatformView()->SetViewportMetrics(
661             {1.0, 800, 400, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
662       });
663   PumpOneFrame(shell.get());
664 
665   EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), 10000U);
666 }
667 
668 TEST_F(ShellTest, SetResourceCacheSizeEarly) {
669   Settings settings = CreateSettingsForFixture();
670   auto task_runner = GetThreadTaskRunner();
671   TaskRunners task_runners("test", task_runner, task_runner, task_runner,
672                            task_runner);
673   std::unique_ptr<Shell> shell =
674       CreateShell(std::move(settings), std::move(task_runners));
675 
676   fml::TaskRunner::RunNowOrPostTask(
677       shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
678         shell->GetPlatformView()->SetViewportMetrics(
679             {1.0, 400, 200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
680       });
681   PumpOneFrame(shell.get());
682 
683   // Create the surface needed by rasterizer
684   PlatformViewNotifyCreated(shell.get());
685 
686   auto configuration = RunConfiguration::InferFromSettings(settings);
687   configuration.SetEntrypoint("emptyMain");
688 
689   RunEngine(shell.get(), std::move(configuration));
690   PumpOneFrame(shell.get());
691 
692   EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
693             static_cast<size_t>(3840000U));
694 }
695 
696 TEST_F(ShellTest, SetResourceCacheSizeNotifiesDart) {
697   Settings settings = CreateSettingsForFixture();
698   auto task_runner = GetThreadTaskRunner();
699   TaskRunners task_runners("test", task_runner, task_runner, task_runner,
700                            task_runner);
701   std::unique_ptr<Shell> shell =
702       CreateShell(std::move(settings), std::move(task_runners));
703 
704   fml::TaskRunner::RunNowOrPostTask(
705       shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
706         shell->GetPlatformView()->SetViewportMetrics(
707             {1.0, 400, 200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
708       });
709   PumpOneFrame(shell.get());
710 
711   // Create the surface needed by rasterizer
712   PlatformViewNotifyCreated(shell.get());
713 
714   auto configuration = RunConfiguration::InferFromSettings(settings);
715   configuration.SetEntrypoint("testSkiaResourceCacheSendsResponse");
716 
717   EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
718             static_cast<size_t>(3840000U));
719 
720   fml::AutoResetWaitableEvent latch;
721   AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&latch](auto args) {
722                       latch.Signal();
723                     }));
724 
725   RunEngine(shell.get(), std::move(configuration));
726   PumpOneFrame(shell.get());
727 
728   latch.Wait();
729 
730   EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
731             static_cast<size_t>(10000U));
732 }
733 
734 TEST_F(ShellTest, CanCreateImagefromDecompressedBytes) {
735   Settings settings = CreateSettingsForFixture();
736   auto task_runner = GetThreadTaskRunner();
737 
738   TaskRunners task_runners("test", task_runner, task_runner, task_runner,
739                            task_runner);
740 
741   std::unique_ptr<Shell> shell =
742       CreateShell(std::move(settings), std::move(task_runners));
743 
744   // Create the surface needed by rasterizer
745   PlatformViewNotifyCreated(shell.get());
746 
747   auto configuration = RunConfiguration::InferFromSettings(settings);
748   configuration.SetEntrypoint("canCreateImageFromDecompressedData");
749 
750   fml::AutoResetWaitableEvent latch;
751   AddNativeCallback("NotifyWidthHeight",
752                     CREATE_NATIVE_ENTRY([&latch](auto args) {
753                       auto width = tonic::DartConverter<int>::FromDart(
754                           Dart_GetNativeArgument(args, 0));
755                       auto height = tonic::DartConverter<int>::FromDart(
756                           Dart_GetNativeArgument(args, 1));
757                       ASSERT_EQ(width, 10);
758                       ASSERT_EQ(height, 10);
759                       latch.Signal();
760                     }));
761 
762   RunEngine(shell.get(), std::move(configuration));
763 
764   latch.Wait();
765 }
766 
767 }  // namespace testing
768 }  // namespace flutter
769