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 ×tamps](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 ×tamps](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 ×tamps](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