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// FLUTTER_NOLINT
5
6#define FML_USED_ON_EMBEDDER
7
8#include <time.h>
9#include <algorithm>
10#include <functional>
11#include <future>
12#include <memory>
13
14#include "flutter/flow/layers/layer_tree.h"
15#include "flutter/flow/layers/picture_layer.h"
16#include "flutter/flow/layers/transform_layer.h"
17#include "flutter/fml/command_line.h"
18#include "flutter/fml/dart/dart_converter.h"
19#include "flutter/fml/make_copyable.h"
20#include "flutter/fml/message_loop.h"
21#include "flutter/fml/synchronization/count_down_latch.h"
22#include "flutter/fml/synchronization/waitable_event.h"
23#include "flutter/runtime/dart_vm.h"
24#include "flutter/shell/common/persistent_cache.h"
25#include "flutter/shell/common/platform_view.h"
26#include "flutter/shell/common/rasterizer.h"
27#include "flutter/shell/common/shell_test.h"
28#include "flutter/shell/common/shell_test_external_view_embedder.h"
29#include "flutter/shell/common/shell_test_platform_view.h"
30#include "flutter/shell/common/switches.h"
31#include "flutter/shell/common/thread_host.h"
32#include "flutter/shell/common/vsync_waiter_fallback.h"
33#include "flutter/shell/version/version.h"
34#include "flutter/testing/testing.h"
35#include "third_party/rapidjson/include/rapidjson/writer.h"
36#include "third_party/skia/include/core/SkPictureRecorder.h"
37#include "third_party/tonic/converter/dart_converter.h"
38
39#ifdef SHELL_ENABLE_VULKAN
40#include "flutter/vulkan/vulkan_application.h" // nogncheck
41#endif
42
43namespace flutter {
44namespace testing {
45
46static bool ValidateShell(Shell* shell) {
47 if (!shell) {
48 return false;
49 }
50
51 if (!shell->IsSetup()) {
52 return false;
53 }
54
55 ShellTest::PlatformViewNotifyCreated(shell);
56
57 {
58 fml::AutoResetWaitableEvent latch;
59 fml::TaskRunner::RunNowOrPostTask(
60 shell->GetTaskRunners().GetPlatformTaskRunner(), [shell, &latch]() {
61 shell->GetPlatformView()->NotifyDestroyed();
62 latch.Signal();
63 });
64 latch.Wait();
65 }
66
67 return true;
68}
69
70TEST_F(ShellTest, InitializeWithInvalidThreads) {
71 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
72 Settings settings = CreateSettingsForFixture();
73 TaskRunners task_runners("test", nullptr, nullptr, nullptr, nullptr);
74 auto shell = CreateShell(std::move(settings), std::move(task_runners));
75 ASSERT_FALSE(shell);
76 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
77}
78
79TEST_F(ShellTest, InitializeWithDifferentThreads) {
80 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
81 Settings settings = CreateSettingsForFixture();
82 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
83 ThreadHost::Type::Platform | ThreadHost::Type::GPU |
84 ThreadHost::Type::IO | ThreadHost::Type::UI);
85 TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(),
86 thread_host.raster_thread->GetTaskRunner(),
87 thread_host.ui_thread->GetTaskRunner(),
88 thread_host.io_thread->GetTaskRunner());
89 auto shell = CreateShell(std::move(settings), std::move(task_runners));
90 ASSERT_TRUE(ValidateShell(shell.get()));
91 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
92 DestroyShell(std::move(shell), std::move(task_runners));
93 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
94}
95
96TEST_F(ShellTest, InitializeWithSingleThread) {
97 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
98 Settings settings = CreateSettingsForFixture();
99 ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
100 ThreadHost::Type::Platform);
101 auto task_runner = thread_host.platform_thread->GetTaskRunner();
102 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
103 task_runner);
104 auto shell = CreateShell(std::move(settings), task_runners);
105 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
106 ASSERT_TRUE(ValidateShell(shell.get()));
107 DestroyShell(std::move(shell), std::move(task_runners));
108 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
109}
110
111TEST_F(ShellTest, InitializeWithSingleThreadWhichIsTheCallingThread) {
112 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
113 Settings settings = CreateSettingsForFixture();
114 fml::MessageLoop::EnsureInitializedForCurrentThread();
115 auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
116 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
117 task_runner);
118 auto shell = CreateShell(std::move(settings), task_runners);
119 ASSERT_TRUE(ValidateShell(shell.get()));
120 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
121 DestroyShell(std::move(shell), std::move(task_runners));
122 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
123}
124
125TEST_F(ShellTest,
126 InitializeWithMultipleThreadButCallingThreadAsPlatformThread) {
127 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
128 Settings settings = CreateSettingsForFixture();
129 ThreadHost thread_host(
130 "io.flutter.test." + GetCurrentTestName() + ".",
131 ThreadHost::Type::GPU | ThreadHost::Type::IO | ThreadHost::Type::UI);
132 fml::MessageLoop::EnsureInitializedForCurrentThread();
133 TaskRunners task_runners("test",
134 fml::MessageLoop::GetCurrent().GetTaskRunner(),
135 thread_host.raster_thread->GetTaskRunner(),
136 thread_host.ui_thread->GetTaskRunner(),
137 thread_host.io_thread->GetTaskRunner());
138 auto shell = Shell::Create(
139 std::move(task_runners), settings,
140 [](Shell& shell) {
141 // This is unused in the platform view as we are not using the simulated
142 // vsync mechanism. We should have better DI in the tests.
143 const auto vsync_clock = std::make_shared<ShellTestVsyncClock>();
144 return ShellTestPlatformView::Create(
145 shell, shell.GetTaskRunners(), vsync_clock,
146 [task_runners = shell.GetTaskRunners()]() {
147 return static_cast<std::unique_ptr<VsyncWaiter>>(
148 std::make_unique<VsyncWaiterFallback>(task_runners));
149 },
150 ShellTestPlatformView::BackendType::kDefaultBackend, nullptr);
151 },
152 [](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
153 ASSERT_TRUE(ValidateShell(shell.get()));
154 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
155 DestroyShell(std::move(shell), std::move(task_runners));
156 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
157}
158
159TEST_F(ShellTest, InitializeWithGPUAndPlatformThreadsTheSame) {
160 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
161 Settings settings = CreateSettingsForFixture();
162 ThreadHost thread_host(
163 "io.flutter.test." + GetCurrentTestName() + ".",
164 ThreadHost::Type::Platform | ThreadHost::Type::IO | ThreadHost::Type::UI);
165 TaskRunners task_runners(
166 "test",
167 thread_host.platform_thread->GetTaskRunner(), // platform
168 thread_host.platform_thread->GetTaskRunner(), // raster
169 thread_host.ui_thread->GetTaskRunner(), // ui
170 thread_host.io_thread->GetTaskRunner() // io
171 );
172 auto shell = CreateShell(std::move(settings), std::move(task_runners));
173 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
174 ASSERT_TRUE(ValidateShell(shell.get()));
175 DestroyShell(std::move(shell), std::move(task_runners));
176 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
177}
178
179TEST_F(ShellTest, FixturesAreFunctional) {
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("fixturesAreFunctionalMain");
188
189 fml::AutoResetWaitableEvent main_latch;
190 AddNativeCallback(
191 "SayHiFromFixturesAreFunctionalMain",
192 CREATE_NATIVE_ENTRY([&main_latch](auto args) { main_latch.Signal(); }));
193
194 RunEngine(shell.get(), std::move(configuration));
195 main_latch.Wait();
196 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
197 DestroyShell(std::move(shell));
198 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
199}
200
201TEST_F(ShellTest, SecondaryIsolateBindingsAreSetupViaShellSettings) {
202 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
203 auto settings = CreateSettingsForFixture();
204 auto shell = CreateShell(settings);
205 ASSERT_TRUE(ValidateShell(shell.get()));
206
207 auto configuration = RunConfiguration::InferFromSettings(settings);
208 ASSERT_TRUE(configuration.IsValid());
209 configuration.SetEntrypoint("testCanLaunchSecondaryIsolate");
210
211 fml::CountDownLatch latch(2);
212 AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&latch](auto args) {
213 latch.CountDown();
214 }));
215
216 RunEngine(shell.get(), std::move(configuration));
217
218 latch.Wait();
219
220 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
221 DestroyShell(std::move(shell));
222 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
223}
224
225TEST_F(ShellTest, LastEntrypoint) {
226 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
227 auto settings = CreateSettingsForFixture();
228 auto shell = CreateShell(settings);
229 ASSERT_TRUE(ValidateShell(shell.get()));
230
231 auto configuration = RunConfiguration::InferFromSettings(settings);
232 ASSERT_TRUE(configuration.IsValid());
233 std::string entry_point = "fixturesAreFunctionalMain";
234 configuration.SetEntrypoint(entry_point);
235
236 fml::AutoResetWaitableEvent main_latch;
237 std::string last_entry_point;
238 AddNativeCallback(
239 "SayHiFromFixturesAreFunctionalMain", CREATE_NATIVE_ENTRY([&](auto args) {
240 last_entry_point = shell->GetEngine()->GetLastEntrypoint();
241 main_latch.Signal();
242 }));
243
244 RunEngine(shell.get(), std::move(configuration));
245 main_latch.Wait();
246 EXPECT_EQ(entry_point, last_entry_point);
247 ASSERT_TRUE(DartVMRef::IsInstanceRunning());
248 DestroyShell(std::move(shell));
249 ASSERT_FALSE(DartVMRef::IsInstanceRunning());
250}
251
252TEST(ShellTestNoFixture, EnableMirrorsIsAllowed) {
253 if (DartVM::IsRunningPrecompiledCode()) {
254 // This covers profile and release modes which use AOT (where this flag does
255 // not make sense anyway).
256 GTEST_SKIP();
257 return;
258 }
259#if FLUTTER_RELEASE
260 GTEST_SKIP();
261 return;
262#endif
263
264 const std::vector<fml::CommandLine::Option> options = {
265 fml::CommandLine::Option("dart-flags", "--enable_mirrors")};
266 fml::CommandLine command_line("", options, std::vector<std::string>());
267 flutter::Settings settings = flutter::SettingsFromCommandLine(command_line);
268 EXPECT_EQ(settings.dart_flags.size(), 1u);
269}
270
271TEST_F(ShellTest, DisallowedDartVMFlag) {
272 // Run this test in a thread-safe manner, otherwise gtest will complain.
273 ::testing::FLAGS_gtest_death_test_style = "threadsafe";
274
275 const std::vector<fml::CommandLine::Option> options = {
276 fml::CommandLine::Option("dart-flags", "--verify_after_gc")};
277 fml::CommandLine command_line("", options, std::vector<std::string>());
278
279 // Upon encountering a disallowed Dart flag the process terminates.
280 const char* expected =
281 "Encountered disallowed Dart VM flag: --verify_after_gc";
282 ASSERT_DEATH(flutter::SettingsFromCommandLine(command_line), expected);
283}
284
285TEST_F(ShellTest, AllowedDartVMFlag) {
286 const std::vector<fml::CommandLine::Option> options = {
287#if !FLUTTER_RELEASE
288 fml::CommandLine::Option("dart-flags",
289 "--max_profile_depth 1,--random_seed 42")
290#endif
291 };
292 fml::CommandLine command_line("", options, std::vector<std::string>());
293 flutter::Settings settings = flutter::SettingsFromCommandLine(command_line);
294
295#if !FLUTTER_RELEASE
296 EXPECT_EQ(settings.dart_flags.size(), 2u);
297 EXPECT_EQ(settings.dart_flags[0], "--max_profile_depth 1");
298 EXPECT_EQ(settings.dart_flags[1], "--random_seed 42");
299#else
300 EXPECT_EQ(settings.dart_flags.size(), 0u);
301#endif
302}
303
304TEST_F(ShellTest, NoNeedToReportTimingsByDefault) {
305 auto settings = CreateSettingsForFixture();
306 std::unique_ptr<Shell> shell = CreateShell(settings);
307
308 // Create the surface needed by rasterizer
309 PlatformViewNotifyCreated(shell.get());
310
311 auto configuration = RunConfiguration::InferFromSettings(settings);
312 configuration.SetEntrypoint("emptyMain");
313
314 RunEngine(shell.get(), std::move(configuration));
315 PumpOneFrame(shell.get());
316 ASSERT_FALSE(GetNeedsReportTimings(shell.get()));
317
318 // This assertion may or may not be the direct result of needs_report_timings_
319 // being false. The count could be 0 simply because we just cleared
320 // unreported timings by reporting them. Hence this can't replace the
321 // ASSERT_FALSE(GetNeedsReportTimings(shell.get())) check. We added
322 // this assertion for an additional confidence that we're not pushing
323 // back to unreported timings unnecessarily.
324 //
325 // Conversely, do not assert UnreportedTimingsCount(shell.get()) to be
326 // positive in any tests. Otherwise those tests will be flaky as the clearing
327 // of unreported timings is unpredictive.
328 ASSERT_EQ(UnreportedTimingsCount(shell.get()), 0);
329 DestroyShell(std::move(shell));
330}
331
332TEST_F(ShellTest, NeedsReportTimingsIsSetWithCallback) {
333 auto settings = CreateSettingsForFixture();
334 std::unique_ptr<Shell> shell = CreateShell(settings);
335
336 // Create the surface needed by rasterizer
337 PlatformViewNotifyCreated(shell.get());
338
339 auto configuration = RunConfiguration::InferFromSettings(settings);
340 configuration.SetEntrypoint("dummyReportTimingsMain");
341
342 RunEngine(shell.get(), std::move(configuration));
343 PumpOneFrame(shell.get());
344 ASSERT_TRUE(GetNeedsReportTimings(shell.get()));
345 DestroyShell(std::move(shell));
346}
347
348static void CheckFrameTimings(const std::vector<FrameTiming>& timings,
349 fml::TimePoint start,
350 fml::TimePoint finish) {
351 fml::TimePoint last_frame_start;
352 for (size_t i = 0; i < timings.size(); i += 1) {
353 // Ensure that timings are sorted.
354 ASSERT_TRUE(timings[i].Get(FrameTiming::kPhases[0]) >= last_frame_start);
355 last_frame_start = timings[i].Get(FrameTiming::kPhases[0]);
356
357 fml::TimePoint last_phase_time;
358 for (auto phase : FrameTiming::kPhases) {
359 ASSERT_TRUE(timings[i].Get(phase) >= start);
360 ASSERT_TRUE(timings[i].Get(phase) <= finish);
361
362 // phases should have weakly increasing time points
363 ASSERT_TRUE(last_phase_time <= timings[i].Get(phase));
364 last_phase_time = timings[i].Get(phase);
365 }
366 }
367}
368
369// TODO(43192): This test is disable because of flakiness.
370TEST_F(ShellTest, DISABLED_ReportTimingsIsCalled) {
371 fml::TimePoint start = fml::TimePoint::Now();
372 auto settings = CreateSettingsForFixture();
373 std::unique_ptr<Shell> shell = CreateShell(settings);
374
375 // Create the surface needed by rasterizer
376 PlatformViewNotifyCreated(shell.get());
377
378 auto configuration = RunConfiguration::InferFromSettings(settings);
379 ASSERT_TRUE(configuration.IsValid());
380 configuration.SetEntrypoint("reportTimingsMain");
381 fml::AutoResetWaitableEvent reportLatch;
382 std::vector<int64_t> timestamps;
383 auto nativeTimingCallback = [&reportLatch,
384 &timestamps](Dart_NativeArguments args) {
385 Dart_Handle exception = nullptr;
386 timestamps = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
387 args, 0, exception);
388 reportLatch.Signal();
389 };
390 AddNativeCallback("NativeReportTimingsCallback",
391 CREATE_NATIVE_ENTRY(nativeTimingCallback));
392 RunEngine(shell.get(), std::move(configuration));
393
394 // Pump many frames so we can trigger the report quickly instead of waiting
395 // for the 1 second threshold.
396 for (int i = 0; i < 200; i += 1) {
397 PumpOneFrame(shell.get());
398 }
399
400 reportLatch.Wait();
401 DestroyShell(std::move(shell));
402
403 fml::TimePoint finish = fml::TimePoint::Now();
404 ASSERT_TRUE(timestamps.size() > 0);
405 ASSERT_TRUE(timestamps.size() % FrameTiming::kCount == 0);
406 std::vector<FrameTiming> timings(timestamps.size() / FrameTiming::kCount);
407
408 for (size_t i = 0; i * FrameTiming::kCount < timestamps.size(); i += 1) {
409 for (auto phase : FrameTiming::kPhases) {
410 timings[i].Set(
411 phase,
412 fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMicroseconds(
413 timestamps[i * FrameTiming::kCount + phase])));
414 }
415 }
416 CheckFrameTimings(timings, start, finish);
417}
418
419TEST_F(ShellTest, FrameRasterizedCallbackIsCalled) {
420 fml::TimePoint start = fml::TimePoint::Now();
421
422 auto settings = CreateSettingsForFixture();
423 fml::AutoResetWaitableEvent timingLatch;
424 FrameTiming timing;
425
426 for (auto phase : FrameTiming::kPhases) {
427 timing.Set(phase, fml::TimePoint());
428 // Check that the time points are initially smaller than start, so
429 // CheckFrameTimings will fail if they're not properly set later.
430 ASSERT_TRUE(timing.Get(phase) < start);
431 }
432
433 settings.frame_rasterized_callback = [&timing,
434 &timingLatch](const FrameTiming& t) {
435 timing = t;
436 timingLatch.Signal();
437 };
438
439 std::unique_ptr<Shell> shell = CreateShell(settings);
440
441 // Create the surface needed by rasterizer
442 PlatformViewNotifyCreated(shell.get());
443
444 auto configuration = RunConfiguration::InferFromSettings(settings);
445 configuration.SetEntrypoint("onBeginFrameMain");
446
447 int64_t frame_target_time;
448 auto nativeOnBeginFrame = [&frame_target_time](Dart_NativeArguments args) {
449 Dart_Handle exception = nullptr;
450 frame_target_time =
451 tonic::DartConverter<int64_t>::FromArguments(args, 0, exception);
452 };
453 AddNativeCallback("NativeOnBeginFrame",
454 CREATE_NATIVE_ENTRY(nativeOnBeginFrame));
455
456 RunEngine(shell.get(), std::move(configuration));
457
458 PumpOneFrame(shell.get());
459
460 // Check that timing is properly set. This implies that
461 // settings.frame_rasterized_callback is called.
462 timingLatch.Wait();
463 fml::TimePoint finish = fml::TimePoint::Now();
464 std::vector<FrameTiming> timings = {timing};
465 CheckFrameTimings(timings, start, finish);
466
467 // Check that onBeginFrame, which is the frame_target_time, is after
468 // FrameTiming's build start
469 int64_t build_start =
470 timing.Get(FrameTiming::kBuildStart).ToEpochDelta().ToMicroseconds();
471 ASSERT_GT(frame_target_time, build_start);
472 DestroyShell(std::move(shell));
473}
474
475#if !defined(OS_FUCHSIA)
476// TODO(sanjayc77): https://github.com/flutter/flutter/issues/53179. Add
477// support for raster thread merger for Fuchsia.
478TEST_F(ShellTest,
479 ExternalEmbedderEndFrameIsCalledWhenPostPrerollResultIsResubmit) {
480 auto settings = CreateSettingsForFixture();
481 fml::AutoResetWaitableEvent endFrameLatch;
482 bool end_frame_called = false;
483 auto end_frame_callback = [&](bool should_resubmit_frame) {
484 ASSERT_TRUE(should_resubmit_frame);
485 end_frame_called = true;
486 endFrameLatch.Signal();
487 };
488 auto external_view_embedder = std::make_shared<ShellTestExternalViewEmbedder>(
489 end_frame_callback, PostPrerollResult::kResubmitFrame);
490 auto shell = CreateShell(std::move(settings), GetTaskRunnersForFixture(),
491 false, external_view_embedder);
492
493 // Create the surface needed by rasterizer
494 PlatformViewNotifyCreated(shell.get());
495
496 auto configuration = RunConfiguration::InferFromSettings(settings);
497 configuration.SetEntrypoint("emptyMain");
498
499 RunEngine(shell.get(), std::move(configuration));
500
501 LayerTreeBuilder builder = [&](std::shared_ptr<ContainerLayer> root) {
502 SkPictureRecorder recorder;
503 SkCanvas* recording_canvas =
504 recorder.beginRecording(SkRect::MakeXYWH(0, 0, 80, 80));
505 recording_canvas->drawRect(SkRect::MakeXYWH(0, 0, 80, 80),
506 SkPaint(SkColor4f::FromColor(SK_ColorRED)));
507 auto sk_picture = recorder.finishRecordingAsPicture();
508 fml::RefPtr<SkiaUnrefQueue> queue = fml::MakeRefCounted<SkiaUnrefQueue>(
509 this->GetCurrentTaskRunner(), fml::TimeDelta::FromSeconds(0));
510 auto picture_layer = std::make_shared<PictureLayer>(
511 SkPoint::Make(10, 10),
512 flutter::SkiaGPUObject<SkPicture>({sk_picture, queue}), false, false);
513 root->Add(picture_layer);
514 };
515
516 PumpOneFrame(shell.get(), 100, 100, builder);
517 endFrameLatch.Wait();
518
519 ASSERT_TRUE(end_frame_called);
520
521 DestroyShell(std::move(shell));
522}
523#endif
524
525TEST(SettingsTest, FrameTimingSetsAndGetsProperly) {
526 // Ensure that all phases are in kPhases.
527 ASSERT_EQ(sizeof(FrameTiming::kPhases),
528 FrameTiming::kCount * sizeof(FrameTiming::Phase));
529
530 int lastPhaseIndex = -1;
531 FrameTiming timing;
532 for (auto phase : FrameTiming::kPhases) {
533 ASSERT_TRUE(phase > lastPhaseIndex); // Ensure that kPhases are in order.
534 lastPhaseIndex = phase;
535 auto fake_time =
536 fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMicroseconds(phase));
537 timing.Set(phase, fake_time);
538 ASSERT_TRUE(timing.Get(phase) == fake_time);
539 }
540}
541
542#if FLUTTER_RELEASE
543TEST_F(ShellTest, ReportTimingsIsCalledLaterInReleaseMode) {
544#else
545TEST_F(ShellTest, ReportTimingsIsCalledSoonerInNonReleaseMode) {
546#endif
547 fml::TimePoint start = fml::TimePoint::Now();
548 auto settings = CreateSettingsForFixture();
549 std::unique_ptr<Shell> shell = CreateShell(settings);
550
551 // Create the surface needed by rasterizer
552 PlatformViewNotifyCreated(shell.get());
553
554 auto configuration = RunConfiguration::InferFromSettings(settings);
555 ASSERT_TRUE(configuration.IsValid());
556 configuration.SetEntrypoint("reportTimingsMain");
557
558 // Wait for 2 reports: the first one is the immediate callback of the first
559 // frame; the second one will exercise the batching logic.
560 fml::CountDownLatch reportLatch(2);
561 std::vector<int64_t> timestamps;
562 auto nativeTimingCallback = [&reportLatch,
563 &timestamps](Dart_NativeArguments args) {
564 Dart_Handle exception = nullptr;
565 timestamps = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
566 args, 0, exception);
567 reportLatch.CountDown();
568 };
569 AddNativeCallback("NativeReportTimingsCallback",
570 CREATE_NATIVE_ENTRY(nativeTimingCallback));
571 RunEngine(shell.get(), std::move(configuration));
572
573 PumpOneFrame(shell.get());
574 PumpOneFrame(shell.get());
575
576 reportLatch.Wait();
577 DestroyShell(std::move(shell));
578
579 fml::TimePoint finish = fml::TimePoint::Now();
580 fml::TimeDelta elapsed = finish - start;
581
582#if FLUTTER_RELEASE
583 // Our batch time is 1000ms. Hopefully the 800ms limit is relaxed enough to
584 // make it not too flaky.
585 ASSERT_TRUE(elapsed >= fml::TimeDelta::FromMilliseconds(800));
586#else
587 // Our batch time is 100ms. Hopefully the 500ms limit is relaxed enough to
588 // make it not too flaky.
589 ASSERT_TRUE(elapsed <= fml::TimeDelta::FromMilliseconds(500));
590#endif
591}
592
593TEST_F(ShellTest, ReportTimingsIsCalledImmediatelyAfterTheFirstFrame) {
594 auto settings = CreateSettingsForFixture();
595 std::unique_ptr<Shell> shell = CreateShell(settings);
596
597 // Create the surface needed by rasterizer
598 PlatformViewNotifyCreated(shell.get());
599
600 auto configuration = RunConfiguration::InferFromSettings(settings);
601 ASSERT_TRUE(configuration.IsValid());
602 configuration.SetEntrypoint("reportTimingsMain");
603 fml::AutoResetWaitableEvent reportLatch;
604 std::vector<int64_t> timestamps;
605 auto nativeTimingCallback = [&reportLatch,
606 &timestamps](Dart_NativeArguments args) {
607 Dart_Handle exception = nullptr;
608 timestamps = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
609 args, 0, exception);
610 reportLatch.Signal();
611 };
612 AddNativeCallback("NativeReportTimingsCallback",
613 CREATE_NATIVE_ENTRY(nativeTimingCallback));
614 RunEngine(shell.get(), std::move(configuration));
615
616 for (int i = 0; i < 10; i += 1) {
617 PumpOneFrame(shell.get());
618 }
619
620 reportLatch.Wait();
621 DestroyShell(std::move(shell));
622
623 // Check for the immediate callback of the first frame that doesn't wait for
624 // the other 9 frames to be rasterized.
625 ASSERT_EQ(timestamps.size(), FrameTiming::kCount);
626}
627
628TEST_F(ShellTest, ReloadSystemFonts) {
629 auto settings = CreateSettingsForFixture();
630
631 fml::MessageLoop::EnsureInitializedForCurrentThread();
632 auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
633 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
634 task_runner);
635 auto shell = CreateShell(std::move(settings), std::move(task_runners));
636
637 auto fontCollection = GetFontCollection(shell.get());
638 std::vector<std::string> families(1, "Robotofake");
639 auto font =
640 fontCollection->GetMinikinFontCollectionForFamilies(families, "en");
641 if (font == nullptr) {
642 // The system does not have default font. Aborts this test.
643 return;
644 }
645 unsigned int id = font->getId();
646 // The result should be cached.
647 font = fontCollection->GetMinikinFontCollectionForFamilies(families, "en");
648 ASSERT_EQ(font->getId(), id);
649 bool result = shell->ReloadSystemFonts();
650
651 // The cache is cleared, and FontCollection will be assigned a new id.
652 font = fontCollection->GetMinikinFontCollectionForFamilies(families, "en");
653 ASSERT_NE(font->getId(), id);
654 ASSERT_TRUE(result);
655 shell.reset();
656}
657
658TEST_F(ShellTest, WaitForFirstFrame) {
659 auto settings = CreateSettingsForFixture();
660 std::unique_ptr<Shell> shell = CreateShell(settings);
661
662 // Create the surface needed by rasterizer
663 PlatformViewNotifyCreated(shell.get());
664
665 auto configuration = RunConfiguration::InferFromSettings(settings);
666 configuration.SetEntrypoint("emptyMain");
667
668 RunEngine(shell.get(), std::move(configuration));
669 PumpOneFrame(shell.get());
670 fml::Status result =
671 shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1000));
672 ASSERT_TRUE(result.ok());
673
674 DestroyShell(std::move(shell));
675}
676
677TEST_F(ShellTest, WaitForFirstFrameZeroSizeFrame) {
678 auto settings = CreateSettingsForFixture();
679 std::unique_ptr<Shell> shell = CreateShell(settings);
680
681 // Create the surface needed by rasterizer
682 PlatformViewNotifyCreated(shell.get());
683
684 auto configuration = RunConfiguration::InferFromSettings(settings);
685 configuration.SetEntrypoint("emptyMain");
686
687 RunEngine(shell.get(), std::move(configuration));
688 PumpOneFrame(shell.get(), {1.0, 0.0, 0.0});
689 fml::Status result =
690 shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1000));
691 ASSERT_FALSE(result.ok());
692 ASSERT_EQ(result.code(), fml::StatusCode::kDeadlineExceeded);
693
694 DestroyShell(std::move(shell));
695}
696
697TEST_F(ShellTest, WaitForFirstFrameTimeout) {
698 auto settings = CreateSettingsForFixture();
699 std::unique_ptr<Shell> shell = CreateShell(settings);
700
701 // Create the surface needed by rasterizer
702 PlatformViewNotifyCreated(shell.get());
703
704 auto configuration = RunConfiguration::InferFromSettings(settings);
705 configuration.SetEntrypoint("emptyMain");
706
707 RunEngine(shell.get(), std::move(configuration));
708 fml::Status result =
709 shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(10));
710 ASSERT_FALSE(result.ok());
711 ASSERT_EQ(result.code(), fml::StatusCode::kDeadlineExceeded);
712
713 DestroyShell(std::move(shell));
714}
715
716TEST_F(ShellTest, WaitForFirstFrameMultiple) {
717 auto settings = CreateSettingsForFixture();
718 std::unique_ptr<Shell> shell = CreateShell(settings);
719
720 // Create the surface needed by rasterizer
721 PlatformViewNotifyCreated(shell.get());
722
723 auto configuration = RunConfiguration::InferFromSettings(settings);
724 configuration.SetEntrypoint("emptyMain");
725
726 RunEngine(shell.get(), std::move(configuration));
727 PumpOneFrame(shell.get());
728 fml::Status result =
729 shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1000));
730 ASSERT_TRUE(result.ok());
731 for (int i = 0; i < 100; ++i) {
732 result = shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1));
733 ASSERT_TRUE(result.ok());
734 }
735
736 DestroyShell(std::move(shell));
737}
738
739/// Makes sure that WaitForFirstFrame works if we rendered a frame with the
740/// single-thread setup.
741TEST_F(ShellTest, WaitForFirstFrameInlined) {
742 Settings settings = CreateSettingsForFixture();
743 auto task_runner = CreateNewThread();
744 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
745 task_runner);
746 std::unique_ptr<Shell> shell =
747 CreateShell(std::move(settings), std::move(task_runners));
748
749 // Create the surface needed by rasterizer
750 PlatformViewNotifyCreated(shell.get());
751
752 auto configuration = RunConfiguration::InferFromSettings(settings);
753 configuration.SetEntrypoint("emptyMain");
754
755 RunEngine(shell.get(), std::move(configuration));
756 PumpOneFrame(shell.get());
757 fml::AutoResetWaitableEvent event;
758 task_runner->PostTask([&shell, &event] {
759 fml::Status result =
760 shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1000));
761 ASSERT_FALSE(result.ok());
762 ASSERT_EQ(result.code(), fml::StatusCode::kFailedPrecondition);
763 event.Signal();
764 });
765 ASSERT_FALSE(event.WaitWithTimeout(fml::TimeDelta::FromMilliseconds(1000)));
766
767 DestroyShell(std::move(shell), std::move(task_runners));
768}
769
770static size_t GetRasterizerResourceCacheBytesSync(Shell& shell) {
771 size_t bytes = 0;
772 fml::AutoResetWaitableEvent latch;
773 fml::TaskRunner::RunNowOrPostTask(
774 shell.GetTaskRunners().GetRasterTaskRunner(), [&]() {
775 if (auto rasterizer = shell.GetRasterizer()) {
776 bytes = rasterizer->GetResourceCacheMaxBytes().value_or(0U);
777 }
778 latch.Signal();
779 });
780 latch.Wait();
781 return bytes;
782}
783
784TEST_F(ShellTest, SetResourceCacheSize) {
785 Settings settings = CreateSettingsForFixture();
786 auto task_runner = CreateNewThread();
787 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
788 task_runner);
789 std::unique_ptr<Shell> shell =
790 CreateShell(std::move(settings), std::move(task_runners));
791
792 // Create the surface needed by rasterizer
793 PlatformViewNotifyCreated(shell.get());
794
795 auto configuration = RunConfiguration::InferFromSettings(settings);
796 configuration.SetEntrypoint("emptyMain");
797
798 RunEngine(shell.get(), std::move(configuration));
799 PumpOneFrame(shell.get());
800
801 // The Vulkan and GL backends set different default values for the resource
802 // cache size.
803#ifdef SHELL_ENABLE_VULKAN
804 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
805 vulkan::kGrCacheMaxByteSize);
806#else
807 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
808 static_cast<size_t>(24 * (1 << 20)));
809#endif
810
811 fml::TaskRunner::RunNowOrPostTask(
812 shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
813 shell->GetPlatformView()->SetViewportMetrics({1.0, 400, 200});
814 });
815 PumpOneFrame(shell.get());
816
817 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), 3840000U);
818
819 std::string request_json = R"json({
820 "method": "Skia.setResourceCacheMaxBytes",
821 "args": 10000
822 })json";
823 std::vector<uint8_t> data(request_json.begin(), request_json.end());
824 auto platform_message = fml::MakeRefCounted<PlatformMessage>(
825 "flutter/skia", std::move(data), nullptr);
826 SendEnginePlatformMessage(shell.get(), std::move(platform_message));
827 PumpOneFrame(shell.get());
828 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), 10000U);
829
830 fml::TaskRunner::RunNowOrPostTask(
831 shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
832 shell->GetPlatformView()->SetViewportMetrics({1.0, 800, 400});
833 });
834 PumpOneFrame(shell.get());
835
836 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell), 10000U);
837 DestroyShell(std::move(shell), std::move(task_runners));
838}
839
840TEST_F(ShellTest, SetResourceCacheSizeEarly) {
841 Settings settings = CreateSettingsForFixture();
842 auto task_runner = CreateNewThread();
843 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
844 task_runner);
845 std::unique_ptr<Shell> shell =
846 CreateShell(std::move(settings), std::move(task_runners));
847
848 fml::TaskRunner::RunNowOrPostTask(
849 shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
850 shell->GetPlatformView()->SetViewportMetrics({1.0, 400, 200});
851 });
852 PumpOneFrame(shell.get());
853
854 // Create the surface needed by rasterizer
855 PlatformViewNotifyCreated(shell.get());
856
857 auto configuration = RunConfiguration::InferFromSettings(settings);
858 configuration.SetEntrypoint("emptyMain");
859
860 RunEngine(shell.get(), std::move(configuration));
861 PumpOneFrame(shell.get());
862
863 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
864 static_cast<size_t>(3840000U));
865 DestroyShell(std::move(shell), std::move(task_runners));
866}
867
868TEST_F(ShellTest, SetResourceCacheSizeNotifiesDart) {
869 Settings settings = CreateSettingsForFixture();
870 auto task_runner = CreateNewThread();
871 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
872 task_runner);
873 std::unique_ptr<Shell> shell =
874 CreateShell(std::move(settings), std::move(task_runners));
875
876 fml::TaskRunner::RunNowOrPostTask(
877 shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell]() {
878 shell->GetPlatformView()->SetViewportMetrics({1.0, 400, 200});
879 });
880 PumpOneFrame(shell.get());
881
882 // Create the surface needed by rasterizer
883 PlatformViewNotifyCreated(shell.get());
884
885 auto configuration = RunConfiguration::InferFromSettings(settings);
886 configuration.SetEntrypoint("testSkiaResourceCacheSendsResponse");
887
888 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
889 static_cast<size_t>(3840000U));
890
891 fml::AutoResetWaitableEvent latch;
892 AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&latch](auto args) {
893 latch.Signal();
894 }));
895
896 RunEngine(shell.get(), std::move(configuration));
897 PumpOneFrame(shell.get());
898
899 latch.Wait();
900
901 EXPECT_EQ(GetRasterizerResourceCacheBytesSync(*shell),
902 static_cast<size_t>(10000U));
903 DestroyShell(std::move(shell), std::move(task_runners));
904}
905
906TEST_F(ShellTest, CanCreateImagefromDecompressedBytes) {
907 Settings settings = CreateSettingsForFixture();
908 auto task_runner = CreateNewThread();
909
910 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
911 task_runner);
912
913 std::unique_ptr<Shell> shell =
914 CreateShell(std::move(settings), std::move(task_runners));
915
916 // Create the surface needed by rasterizer
917 PlatformViewNotifyCreated(shell.get());
918
919 auto configuration = RunConfiguration::InferFromSettings(settings);
920 configuration.SetEntrypoint("canCreateImageFromDecompressedData");
921
922 fml::AutoResetWaitableEvent latch;
923 AddNativeCallback("NotifyWidthHeight",
924 CREATE_NATIVE_ENTRY([&latch](auto args) {
925 auto width = tonic::DartConverter<int>::FromDart(
926 Dart_GetNativeArgument(args, 0));
927 auto height = tonic::DartConverter<int>::FromDart(
928 Dart_GetNativeArgument(args, 1));
929 ASSERT_EQ(width, 10);
930 ASSERT_EQ(height, 10);
931 latch.Signal();
932 }));
933
934 RunEngine(shell.get(), std::move(configuration));
935
936 latch.Wait();
937 DestroyShell(std::move(shell), std::move(task_runners));
938}
939
940class MockTexture : public Texture {
941 public:
942 MockTexture(int64_t textureId,
943 std::shared_ptr<fml::AutoResetWaitableEvent> latch)
944 : Texture(textureId), latch_(latch) {}
945
946 ~MockTexture() override = default;
947
948 // Called from raster thread.
949 void Paint(SkCanvas& canvas,
950 const SkRect& bounds,
951 bool freeze,
952 GrDirectContext* context,
953 SkFilterQuality filter_quality) override {}
954
955 void OnGrContextCreated() override {}
956
957 void OnGrContextDestroyed() override {}
958
959 void MarkNewFrameAvailable() override {
960 frames_available_++;
961 latch_->Signal();
962 }
963
964 void OnTextureUnregistered() override {
965 unregistered_ = true;
966 latch_->Signal();
967 }
968
969 bool unregistered() { return unregistered_; }
970 int frames_available() { return frames_available_; }
971
972 private:
973 bool unregistered_ = false;
974 int frames_available_ = 0;
975 std::shared_ptr<fml::AutoResetWaitableEvent> latch_;
976};
977
978TEST_F(ShellTest, TextureFrameMarkedAvailableAndUnregister) {
979 Settings settings = CreateSettingsForFixture();
980 auto configuration = RunConfiguration::InferFromSettings(settings);
981 auto task_runner = CreateNewThread();
982 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
983 task_runner);
984 std::unique_ptr<Shell> shell =
985 CreateShell(std::move(settings), std::move(task_runners));
986
987 ASSERT_TRUE(ValidateShell(shell.get()));
988 PlatformViewNotifyCreated(shell.get());
989
990 RunEngine(shell.get(), std::move(configuration));
991
992 std::shared_ptr<fml::AutoResetWaitableEvent> latch =
993 std::make_shared<fml::AutoResetWaitableEvent>();
994
995 std::shared_ptr<MockTexture> mockTexture =
996 std::make_shared<MockTexture>(0, latch);
997
998 fml::TaskRunner::RunNowOrPostTask(
999 shell->GetTaskRunners().GetRasterTaskRunner(), [&]() {
1000 shell->GetPlatformView()->RegisterTexture(mockTexture);
1001 shell->GetPlatformView()->MarkTextureFrameAvailable(0);
1002 });
1003 latch->Wait();
1004
1005 EXPECT_EQ(mockTexture->frames_available(), 1);
1006
1007 fml::TaskRunner::RunNowOrPostTask(
1008 shell->GetTaskRunners().GetRasterTaskRunner(),
1009 [&]() { shell->GetPlatformView()->UnregisterTexture(0); });
1010 latch->Wait();
1011
1012 EXPECT_EQ(mockTexture->unregistered(), true);
1013 DestroyShell(std::move(shell), std::move(task_runners));
1014}
1015
1016TEST_F(ShellTest, IsolateCanAccessPersistentIsolateData) {
1017 const std::string message = "dummy isolate launch data.";
1018
1019 Settings settings = CreateSettingsForFixture();
1020 settings.persistent_isolate_data =
1021 std::make_shared<fml::DataMapping>(message);
1022 TaskRunners task_runners("test", // label
1023 GetCurrentTaskRunner(), // platform
1024 CreateNewThread(), // raster
1025 CreateNewThread(), // ui
1026 CreateNewThread() // io
1027 );
1028
1029 fml::AutoResetWaitableEvent message_latch;
1030 AddNativeCallback("NotifyMessage",
1031 CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
1032 const auto message_from_dart =
1033 tonic::DartConverter<std::string>::FromDart(
1034 Dart_GetNativeArgument(args, 0));
1035 ASSERT_EQ(message, message_from_dart);
1036 message_latch.Signal();
1037 }));
1038
1039 std::unique_ptr<Shell> shell =
1040 CreateShell(std::move(settings), std::move(task_runners));
1041
1042 ASSERT_TRUE(shell->IsSetup());
1043 auto configuration = RunConfiguration::InferFromSettings(settings);
1044 configuration.SetEntrypoint("canAccessIsolateLaunchData");
1045
1046 fml::AutoResetWaitableEvent event;
1047 shell->RunEngine(std::move(configuration), [&](auto result) {
1048 ASSERT_EQ(result, Engine::RunStatus::Success);
1049 });
1050
1051 message_latch.Wait();
1052 DestroyShell(std::move(shell), std::move(task_runners));
1053}
1054
1055TEST_F(ShellTest, Screenshot) {
1056 auto settings = CreateSettingsForFixture();
1057 fml::AutoResetWaitableEvent firstFrameLatch;
1058 settings.frame_rasterized_callback =
1059 [&firstFrameLatch](const FrameTiming& t) { firstFrameLatch.Signal(); };
1060
1061 std::unique_ptr<Shell> shell = CreateShell(settings);
1062
1063 // Create the surface needed by rasterizer
1064 PlatformViewNotifyCreated(shell.get());
1065
1066 auto configuration = RunConfiguration::InferFromSettings(settings);
1067 configuration.SetEntrypoint("emptyMain");
1068
1069 RunEngine(shell.get(), std::move(configuration));
1070
1071 LayerTreeBuilder builder = [&](std::shared_ptr<ContainerLayer> root) {
1072 SkPictureRecorder recorder;
1073 SkCanvas* recording_canvas =
1074 recorder.beginRecording(SkRect::MakeXYWH(0, 0, 80, 80));
1075 recording_canvas->drawRect(SkRect::MakeXYWH(0, 0, 80, 80),
1076 SkPaint(SkColor4f::FromColor(SK_ColorRED)));
1077 auto sk_picture = recorder.finishRecordingAsPicture();
1078 fml::RefPtr<SkiaUnrefQueue> queue = fml::MakeRefCounted<SkiaUnrefQueue>(
1079 this->GetCurrentTaskRunner(), fml::TimeDelta::FromSeconds(0));
1080 auto picture_layer = std::make_shared<PictureLayer>(
1081 SkPoint::Make(10, 10),
1082 flutter::SkiaGPUObject<SkPicture>({sk_picture, queue}), false, false);
1083 root->Add(picture_layer);
1084 };
1085
1086 PumpOneFrame(shell.get(), 100, 100, builder);
1087 firstFrameLatch.Wait();
1088
1089 std::promise<Rasterizer::Screenshot> screenshot_promise;
1090 auto screenshot_future = screenshot_promise.get_future();
1091
1092 fml::TaskRunner::RunNowOrPostTask(
1093 shell->GetTaskRunners().GetRasterTaskRunner(),
1094 [&screenshot_promise, &shell]() {
1095 auto rasterizer = shell->GetRasterizer();
1096 screenshot_promise.set_value(rasterizer->ScreenshotLastLayerTree(
1097 Rasterizer::ScreenshotType::CompressedImage, false));
1098 });
1099
1100 auto fixtures_dir =
1101 fml::OpenDirectory(GetFixturesPath(), false, fml::FilePermission::kRead);
1102
1103 auto reference_png = fml::FileMapping::CreateReadOnly(
1104 fixtures_dir, "shelltest_screenshot.png");
1105
1106 // Use MakeWithoutCopy instead of MakeWithCString because we don't want to
1107 // encode the null sentinel
1108 sk_sp<SkData> reference_data = SkData::MakeWithoutCopy(
1109 reference_png->GetMapping(), reference_png->GetSize());
1110
1111 ASSERT_TRUE(reference_data->equals(screenshot_future.get().data.get()));
1112
1113 DestroyShell(std::move(shell));
1114}
1115
1116TEST_F(ShellTest, CanConvertToAndFromMappings) {
1117 const size_t buffer_size = 2 << 20;
1118
1119 uint8_t* buffer = static_cast<uint8_t*>(::malloc(buffer_size));
1120 ASSERT_NE(buffer, nullptr);
1121 ASSERT_TRUE(MemsetPatternSetOrCheck(
1122 buffer, buffer_size, MemsetPatternOp::kMemsetPatternOpSetBuffer));
1123
1124 std::unique_ptr<fml::Mapping> mapping =
1125 std::make_unique<fml::NonOwnedMapping>(
1126 buffer, buffer_size, [](const uint8_t* buffer, size_t size) {
1127 ::free(const_cast<uint8_t*>(buffer));
1128 });
1129
1130 ASSERT_EQ(mapping->GetSize(), buffer_size);
1131
1132 fml::AutoResetWaitableEvent latch;
1133 AddNativeCallback(
1134 "SendFixtureMapping", CREATE_NATIVE_ENTRY([&](auto args) {
1135 auto mapping_from_dart =
1136 tonic::DartConverter<std::unique_ptr<fml::Mapping>>::FromDart(
1137 Dart_GetNativeArgument(args, 0));
1138 ASSERT_NE(mapping_from_dart, nullptr);
1139 ASSERT_EQ(mapping_from_dart->GetSize(), buffer_size);
1140 ASSERT_TRUE(MemsetPatternSetOrCheck(
1141 const_cast<uint8_t*>(mapping_from_dart->GetMapping()), // buffer
1142 mapping_from_dart->GetSize(), // size
1143 MemsetPatternOp::kMemsetPatternOpCheckBuffer // op
1144 ));
1145 latch.Signal();
1146 }));
1147
1148 AddNativeCallback(
1149 "GetFixtureMapping", CREATE_NATIVE_ENTRY([&](auto args) {
1150 tonic::DartConverter<tonic::DartConverterMapping>::SetReturnValue(
1151 args, mapping);
1152 }));
1153
1154 auto settings = CreateSettingsForFixture();
1155 auto configuration = RunConfiguration::InferFromSettings(settings);
1156 configuration.SetEntrypoint("canConvertMappings");
1157 std::unique_ptr<Shell> shell = CreateShell(settings);
1158 ASSERT_NE(shell.get(), nullptr);
1159 RunEngine(shell.get(), std::move(configuration));
1160 latch.Wait();
1161 DestroyShell(std::move(shell));
1162}
1163
1164// Compares local times as seen by the dart isolate and as seen by this test
1165// fixture, to a resolution of 1 hour.
1166//
1167// This verifies that (1) the isolate is able to get a timezone (doesn't lock
1168// up for example), and (2) that the host and the isolate agree on what the
1169// timezone is.
1170TEST_F(ShellTest, LocaltimesMatch) {
1171 fml::AutoResetWaitableEvent latch;
1172 std::string dart_isolate_time_str;
1173
1174 // See fixtures/shell_test.dart, the callback NotifyLocalTime is declared
1175 // there.
1176 AddNativeCallback("NotifyLocalTime", CREATE_NATIVE_ENTRY([&](auto args) {
1177 dart_isolate_time_str =
1178 tonic::DartConverter<std::string>::FromDart(
1179 Dart_GetNativeArgument(args, 0));
1180 latch.Signal();
1181 }));
1182
1183 auto settings = CreateSettingsForFixture();
1184 auto configuration = RunConfiguration::InferFromSettings(settings);
1185 configuration.SetEntrypoint("localtimesMatch");
1186 std::unique_ptr<Shell> shell = CreateShell(settings);
1187 ASSERT_NE(shell.get(), nullptr);
1188 RunEngine(shell.get(), std::move(configuration));
1189 latch.Wait();
1190
1191 char timestr[200];
1192 const time_t timestamp = time(nullptr);
1193 const struct tm* local_time = localtime(&timestamp);
1194 ASSERT_NE(local_time, nullptr)
1195 << "Could not get local time: errno=" << errno << ": " << strerror(errno);
1196 // Example: "2020-02-26 14" for 2pm on February 26, 2020.
1197 const size_t format_size =
1198 strftime(timestr, sizeof(timestr), "%Y-%m-%d %H", local_time);
1199 ASSERT_NE(format_size, 0UL)
1200 << "strftime failed: host time: " << std::string(timestr)
1201 << " dart isolate time: " << dart_isolate_time_str;
1202
1203 const std::string host_local_time_str = timestr;
1204
1205 ASSERT_EQ(dart_isolate_time_str, host_local_time_str)
1206 << "Local times in the dart isolate and the local time seen by the test "
1207 << "differ by more than 1 hour, but are expected to be about equal";
1208
1209 DestroyShell(std::move(shell));
1210}
1211
1212TEST_F(ShellTest, CanDecompressImageFromAsset) {
1213 fml::AutoResetWaitableEvent latch;
1214 AddNativeCallback("NotifyWidthHeight", CREATE_NATIVE_ENTRY([&](auto args) {
1215 auto width = tonic::DartConverter<int>::FromDart(
1216 Dart_GetNativeArgument(args, 0));
1217 auto height = tonic::DartConverter<int>::FromDart(
1218 Dart_GetNativeArgument(args, 1));
1219 ASSERT_EQ(width, 100);
1220 ASSERT_EQ(height, 100);
1221 latch.Signal();
1222 }));
1223
1224 AddNativeCallback(
1225 "GetFixtureImage", CREATE_NATIVE_ENTRY([](auto args) {
1226 auto fixture = OpenFixtureAsMapping("shelltest_screenshot.png");
1227 tonic::DartConverter<tonic::DartConverterMapping>::SetReturnValue(
1228 args, fixture);
1229 }));
1230
1231 auto settings = CreateSettingsForFixture();
1232 auto configuration = RunConfiguration::InferFromSettings(settings);
1233 configuration.SetEntrypoint("canDecompressImageFromAsset");
1234 std::unique_ptr<Shell> shell = CreateShell(settings);
1235 ASSERT_NE(shell.get(), nullptr);
1236 RunEngine(shell.get(), std::move(configuration));
1237 latch.Wait();
1238 DestroyShell(std::move(shell));
1239}
1240
1241TEST_F(ShellTest, OnServiceProtocolGetSkSLsWorks) {
1242 fml::ScopedTemporaryDirectory base_dir;
1243 ASSERT_TRUE(base_dir.fd().is_valid());
1244 PersistentCache::SetCacheDirectoryPath(base_dir.path());
1245 PersistentCache::ResetCacheForProcess();
1246
1247 // Create 2 dummy SkSL cache file IE (base32 encoding of A), II (base32
1248 // encoding of B) with content x and y.
1249 std::vector<std::string> components = {
1250 "flutter_engine", GetFlutterEngineVersion(), "skia", GetSkiaVersion(),
1251 PersistentCache::kSkSLSubdirName};
1252 auto sksl_dir = fml::CreateDirectory(base_dir.fd(), components,
1253 fml::FilePermission::kReadWrite);
1254 const std::string x = "x";
1255 const std::string y = "y";
1256 auto x_data = std::make_unique<fml::DataMapping>(
1257 std::vector<uint8_t>{x.begin(), x.end()});
1258 auto y_data = std::make_unique<fml::DataMapping>(
1259 std::vector<uint8_t>{y.begin(), y.end()});
1260 ASSERT_TRUE(fml::WriteAtomically(sksl_dir, "IE", *x_data));
1261 ASSERT_TRUE(fml::WriteAtomically(sksl_dir, "II", *y_data));
1262
1263 Settings settings = CreateSettingsForFixture();
1264 std::unique_ptr<Shell> shell = CreateShell(settings);
1265 ServiceProtocol::Handler::ServiceProtocolMap empty_params;
1266 rapidjson::Document document;
1267 OnServiceProtocol(shell.get(), ServiceProtocolEnum::kGetSkSLs,
1268 shell->GetTaskRunners().GetIOTaskRunner(), empty_params,
1269 &document);
1270 rapidjson::StringBuffer buffer;
1271 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
1272 document.Accept(writer);
1273 DestroyShell(std::move(shell));
1274
1275 const std::string expected_json1 =
1276 "{\"type\":\"GetSkSLs\",\"SkSLs\":{\"II\":\"eQ==\",\"IE\":\"eA==\"}}";
1277 const std::string expected_json2 =
1278 "{\"type\":\"GetSkSLs\",\"SkSLs\":{\"IE\":\"eA==\",\"II\":\"eQ==\"}}";
1279 bool json_is_expected = (expected_json1 == buffer.GetString()) ||
1280 (expected_json2 == buffer.GetString());
1281 ASSERT_TRUE(json_is_expected) << buffer.GetString() << " is not equal to "
1282 << expected_json1 << " or " << expected_json2;
1283}
1284
1285TEST_F(ShellTest, RasterizerScreenshot) {
1286 Settings settings = CreateSettingsForFixture();
1287 auto configuration = RunConfiguration::InferFromSettings(settings);
1288 auto task_runner = CreateNewThread();
1289 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
1290 task_runner);
1291 std::unique_ptr<Shell> shell =
1292 CreateShell(std::move(settings), std::move(task_runners));
1293
1294 ASSERT_TRUE(ValidateShell(shell.get()));
1295 PlatformViewNotifyCreated(shell.get());
1296
1297 RunEngine(shell.get(), std::move(configuration));
1298
1299 auto latch = std::make_shared<fml::AutoResetWaitableEvent>();
1300
1301 PumpOneFrame(shell.get());
1302
1303 fml::TaskRunner::RunNowOrPostTask(
1304 shell->GetTaskRunners().GetRasterTaskRunner(), [&shell, &latch]() {
1305 Rasterizer::Screenshot screenshot =
1306 shell->GetRasterizer()->ScreenshotLastLayerTree(
1307 Rasterizer::ScreenshotType::CompressedImage, true);
1308 EXPECT_NE(screenshot.data, nullptr);
1309
1310 latch->Signal();
1311 });
1312 latch->Wait();
1313 DestroyShell(std::move(shell), std::move(task_runners));
1314}
1315
1316TEST_F(ShellTest, RasterizerMakeRasterSnapshot) {
1317 Settings settings = CreateSettingsForFixture();
1318 auto configuration = RunConfiguration::InferFromSettings(settings);
1319 auto task_runner = CreateNewThread();
1320 TaskRunners task_runners("test", task_runner, task_runner, task_runner,
1321 task_runner);
1322 std::unique_ptr<Shell> shell =
1323 CreateShell(std::move(settings), std::move(task_runners));
1324
1325 ASSERT_TRUE(ValidateShell(shell.get()));
1326 PlatformViewNotifyCreated(shell.get());
1327
1328 RunEngine(shell.get(), std::move(configuration));
1329
1330 auto latch = std::make_shared<fml::AutoResetWaitableEvent>();
1331
1332 PumpOneFrame(shell.get());
1333
1334 fml::TaskRunner::RunNowOrPostTask(
1335 shell->GetTaskRunners().GetRasterTaskRunner(), [&shell, &latch]() {
1336 SnapshotDelegate* delegate =
1337 reinterpret_cast<Rasterizer*>(shell->GetRasterizer().get());
1338 sk_sp<SkImage> image = delegate->MakeRasterSnapshot(
1339 SkPicture::MakePlaceholder({0, 0, 50, 50}), SkISize::Make(50, 50));
1340 EXPECT_NE(image, nullptr);
1341
1342 latch->Signal();
1343 });
1344 latch->Wait();
1345 DestroyShell(std::move(shell), std::move(task_runners));
1346}
1347
1348static sk_sp<SkPicture> MakeSizedPicture(int width, int height) {
1349 SkPictureRecorder recorder;
1350 SkCanvas* recording_canvas =
1351 recorder.beginRecording(SkRect::MakeXYWH(0, 0, width, height));
1352 recording_canvas->drawRect(SkRect::MakeXYWH(0, 0, width, height),
1353 SkPaint(SkColor4f::FromColor(SK_ColorRED)));
1354 return recorder.finishRecordingAsPicture();
1355}
1356
1357TEST_F(ShellTest, OnServiceProtocolEstimateRasterCacheMemoryWorks) {
1358 Settings settings = CreateSettingsForFixture();
1359 std::unique_ptr<Shell> shell = CreateShell(settings);
1360
1361 // 1. Construct a picture and a picture layer to be raster cached.
1362 sk_sp<SkPicture> picture = MakeSizedPicture(10, 10);
1363 fml::RefPtr<SkiaUnrefQueue> queue = fml::MakeRefCounted<SkiaUnrefQueue>(
1364 GetCurrentTaskRunner(), fml::TimeDelta::FromSeconds(0));
1365 auto picture_layer = std::make_shared<PictureLayer>(
1366 SkPoint::Make(0, 0),
1367 flutter::SkiaGPUObject<SkPicture>({MakeSizedPicture(100, 100), queue}),
1368 false, false);
1369 picture_layer->set_paint_bounds(SkRect::MakeWH(100, 100));
1370
1371 // 2. Rasterize the picture and the picture layer in the raster cache.
1372 std::promise<bool> rasterized;
1373 shell->GetTaskRunners().GetRasterTaskRunner()->PostTask(
1374 [&shell, &rasterized, &picture, &picture_layer] {
1375 auto* compositor_context = shell->GetRasterizer()->compositor_context();
1376 auto& raster_cache = compositor_context->raster_cache();
1377 // 2.1. Rasterize the picture. Call Draw multiple times to pass the
1378 // access threshold (default to 3) so a cache can be generated.
1379 SkCanvas dummy_canvas;
1380 bool picture_cache_generated;
1381 for (int i = 0; i < 4; i += 1) {
1382 picture_cache_generated =
1383 raster_cache.Prepare(nullptr, // GrDirectContext
1384 picture.get(), SkMatrix::I(),
1385 nullptr, // SkColorSpace
1386 true, // isComplex
1387 false // willChange
1388 );
1389 raster_cache.Draw(*picture, dummy_canvas);
1390 }
1391 ASSERT_TRUE(picture_cache_generated);
1392
1393 // 2.2. Rasterize the picture layer.
1394 Stopwatch raster_time;
1395 Stopwatch ui_time;
1396 MutatorsStack mutators_stack;
1397 TextureRegistry texture_registry;
1398 PrerollContext preroll_context = {
1399 nullptr, /* raster_cache */
1400 nullptr, /* gr_context */
1401 nullptr, /* external_view_embedder */
1402 mutators_stack, nullptr, /* color_space */
1403 kGiantRect, /* cull_rect */
1404 false, /* layer reads from surface */
1405 raster_time, ui_time, texture_registry,
1406 false, /* checkerboard_offscreen_layers */
1407 1.0f, /* frame_device_pixel_ratio */
1408 false, /* has_platform_view */
1409 };
1410 raster_cache.Prepare(&preroll_context, picture_layer.get(),
1411 SkMatrix::I());
1412 rasterized.set_value(true);
1413 });
1414 rasterized.get_future().wait();
1415
1416 // 3. Call the service protocol and check its output.
1417 ServiceProtocol::Handler::ServiceProtocolMap empty_params;
1418 rapidjson::Document document;
1419 OnServiceProtocol(
1420 shell.get(), ServiceProtocolEnum::kEstimateRasterCacheMemory,
1421 shell->GetTaskRunners().GetRasterTaskRunner(), empty_params, &document);
1422 rapidjson::StringBuffer buffer;
1423 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
1424 document.Accept(writer);
1425 std::string expected_json =
1426 "{\"type\":\"EstimateRasterCacheMemory\",\"layerBytes\":40000,\"picture"
1427 "Bytes\":400}";
1428 std::string actual_json = buffer.GetString();
1429 ASSERT_EQ(actual_json, expected_json);
1430
1431 DestroyShell(std::move(shell));
1432}
1433
1434} // namespace testing
1435} // namespace flutter
1436