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 <cstdlib>
8#include <cstring>
9#include <iostream>
10
11#include "flutter/assets/asset_manager.h"
12#include "flutter/assets/directory_asset_bundle.h"
13#include "flutter/flow/embedded_views.h"
14#include "flutter/fml/build_config.h"
15#include "flutter/fml/file.h"
16#include "flutter/fml/make_copyable.h"
17#include "flutter/fml/message_loop.h"
18#include "flutter/fml/paths.h"
19#include "flutter/fml/synchronization/waitable_event.h"
20#include "flutter/fml/task_runner.h"
21#include "flutter/shell/common/platform_view.h"
22#include "flutter/shell/common/rasterizer.h"
23#include "flutter/shell/common/shell.h"
24#include "flutter/shell/common/switches.h"
25#include "flutter/shell/common/thread_host.h"
26#include "flutter/shell/gpu/gpu_surface_software.h"
27
28#include "third_party/dart/runtime/include/bin/dart_io_api.h"
29#include "third_party/dart/runtime/include/dart_api.h"
30#include "third_party/skia/include/core/SkSurface.h"
31
32#if defined(FML_OS_WIN)
33#include <combaseapi.h>
34#endif // defined(FML_OS_WIN)
35
36#if defined(FML_OS_POSIX)
37#include <signal.h>
38#endif // defined(FML_OS_POSIX)
39
40namespace flutter {
41
42static constexpr int64_t kImplicitViewId = 0ll;
43
44class TesterExternalViewEmbedder : public ExternalViewEmbedder {
45 // |ExternalViewEmbedder|
46 DlCanvas* GetRootCanvas() override { return nullptr; }
47
48 // |ExternalViewEmbedder|
49 void CancelFrame() override {}
50
51 // |ExternalViewEmbedder|
52 void BeginFrame(
53 SkISize frame_size,
54 GrDirectContext* context,
55 double device_pixel_ratio,
56 fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) override {}
57
58 // |ExternalViewEmbedder|
59 void PrerollCompositeEmbeddedView(
60 int64_t view_id,
61 std::unique_ptr<EmbeddedViewParams> params) override {}
62
63 // |ExternalViewEmbedder|
64 DlCanvas* CompositeEmbeddedView(int64_t view_id) override {
65 return &builder_;
66 }
67
68 private:
69 DisplayListBuilder builder_;
70};
71
72class TesterGPUSurfaceSoftware : public GPUSurfaceSoftware {
73 public:
74 TesterGPUSurfaceSoftware(GPUSurfaceSoftwareDelegate* delegate,
75 bool render_to_surface)
76 : GPUSurfaceSoftware(delegate, render_to_surface) {}
77
78 bool EnableRasterCache() const override { return false; }
79};
80
81class TesterPlatformView : public PlatformView,
82 public GPUSurfaceSoftwareDelegate {
83 public:
84 TesterPlatformView(Delegate& delegate, const TaskRunners& task_runners)
85 : PlatformView(delegate, task_runners) {}
86
87 // |PlatformView|
88 std::unique_ptr<Surface> CreateRenderingSurface() override {
89 auto surface = std::make_unique<TesterGPUSurfaceSoftware>(
90 args: this, args: true /* render to surface */);
91 FML_DCHECK(surface->IsValid());
92 return surface;
93 }
94
95 // |GPUSurfaceSoftwareDelegate|
96 sk_sp<SkSurface> AcquireBackingStore(const SkISize& size) override {
97 if (sk_surface_ != nullptr &&
98 SkISize::Make(w: sk_surface_->width(), h: sk_surface_->height()) == size) {
99 // The old and new surface sizes are the same. Nothing to do here.
100 return sk_surface_;
101 }
102
103 SkImageInfo info =
104 SkImageInfo::MakeN32(width: size.fWidth, height: size.fHeight, at: kPremul_SkAlphaType,
105 cs: SkColorSpace::MakeSRGB());
106 sk_surface_ = SkSurfaces::Raster(imageInfo: info, props: nullptr);
107
108 if (sk_surface_ == nullptr) {
109 FML_LOG(ERROR)
110 << "Could not create backing store for software rendering.";
111 return nullptr;
112 }
113
114 return sk_surface_;
115 }
116
117 // |GPUSurfaceSoftwareDelegate|
118 bool PresentBackingStore(sk_sp<SkSurface> backing_store) override {
119 return true;
120 }
121
122 // |PlatformView|
123 std::shared_ptr<ExternalViewEmbedder> CreateExternalViewEmbedder() override {
124 return external_view_embedder_;
125 }
126
127 private:
128 sk_sp<SkSurface> sk_surface_ = nullptr;
129 std::shared_ptr<TesterExternalViewEmbedder> external_view_embedder_ =
130 std::make_shared<TesterExternalViewEmbedder>();
131};
132
133// Checks whether the engine's main Dart isolate has no pending work. If so,
134// then exit the given message loop.
135class ScriptCompletionTaskObserver {
136 public:
137 ScriptCompletionTaskObserver(Shell& shell,
138 fml::RefPtr<fml::TaskRunner> main_task_runner,
139 bool run_forever)
140 : shell_(shell),
141 main_task_runner_(std::move(main_task_runner)),
142 run_forever_(run_forever) {}
143
144 int GetExitCodeForLastError() const {
145 return static_cast<int>(last_error_.value_or(v: DartErrorCode::NoError));
146 }
147
148 void DidProcessTask() {
149 last_error_ = shell_.GetUIIsolateLastError();
150 if (shell_.EngineHasLivePorts()) {
151 // The UI isolate still has live ports and is running. Nothing to do
152 // just yet.
153 return;
154 }
155
156 if (run_forever_) {
157 // We need this script to run forever. We have already recorded the last
158 // error. Keep going.
159 return;
160 }
161
162 if (!has_terminated_) {
163 // Only try to terminate the loop once.
164 has_terminated_ = true;
165 fml::TaskRunner::RunNowOrPostTask(runner: main_task_runner_, task: []() {
166 fml::MessageLoop::GetCurrent().Terminate();
167 });
168 }
169 }
170
171 private:
172 Shell& shell_;
173 fml::RefPtr<fml::TaskRunner> main_task_runner_;
174 bool run_forever_ = false;
175 std::optional<DartErrorCode> last_error_;
176 bool has_terminated_ = false;
177
178 FML_DISALLOW_COPY_AND_ASSIGN(ScriptCompletionTaskObserver);
179};
180
181// Processes spawned via dart:io inherit their signal handling from the parent
182// process. As part of spawning, the spawner blocks signals temporarily, so we
183// need to explicitly unblock the signals we care about in the new process. In
184// particular, we need to unblock SIGPROF for CPU profiling to work on the
185// mutator thread in the main isolate in this process (threads spawned by the VM
186// know about this limitation and automatically have this signal unblocked).
187static void UnblockSIGPROF() {
188#if defined(FML_OS_POSIX)
189 sigset_t set;
190 sigemptyset(set: &set);
191 sigaddset(set: &set, SIGPROF);
192 pthread_sigmask(SIG_UNBLOCK, newmask: &set, NULL);
193#endif // defined(FML_OS_POSIX)
194}
195
196int RunTester(const flutter::Settings& settings,
197 bool run_forever,
198 bool multithreaded) {
199 const auto thread_label = "io.flutter.test.";
200
201 // Necessary if we want to use the CPU profiler on the main isolate's mutator
202 // thread.
203 //
204 // OSX WARNING: avoid spawning additional threads before this call due to a
205 // kernel bug that may enable SIGPROF on an unintended thread in the process.
206 UnblockSIGPROF();
207
208 fml::MessageLoop::EnsureInitializedForCurrentThread();
209
210 auto current_task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
211
212 std::unique_ptr<ThreadHost> threadhost;
213 fml::RefPtr<fml::TaskRunner> platform_task_runner;
214 fml::RefPtr<fml::TaskRunner> raster_task_runner;
215 fml::RefPtr<fml::TaskRunner> ui_task_runner;
216 fml::RefPtr<fml::TaskRunner> io_task_runner;
217
218 if (multithreaded) {
219 threadhost = std::make_unique<ThreadHost>(
220 args: thread_label, args: ThreadHost::Type::Platform | ThreadHost::Type::IO |
221 ThreadHost::Type::UI | ThreadHost::Type::RASTER);
222 platform_task_runner = current_task_runner;
223 raster_task_runner = threadhost->raster_thread->GetTaskRunner();
224 ui_task_runner = threadhost->ui_thread->GetTaskRunner();
225 io_task_runner = threadhost->io_thread->GetTaskRunner();
226 } else {
227 platform_task_runner = raster_task_runner = ui_task_runner =
228 io_task_runner = current_task_runner;
229 }
230
231 const flutter::TaskRunners task_runners(thread_label, // dart thread label
232 platform_task_runner, // platform
233 raster_task_runner, // raster
234 ui_task_runner, // ui
235 io_task_runner // io
236 );
237
238 Shell::CreateCallback<PlatformView> on_create_platform_view =
239 [](Shell& shell) {
240 return std::make_unique<TesterPlatformView>(args&: shell,
241 args: shell.GetTaskRunners());
242 };
243
244 Shell::CreateCallback<Rasterizer> on_create_rasterizer = [](Shell& shell) {
245 return std::make_unique<Rasterizer>(
246 args&: shell, args: Rasterizer::MakeGpuImageBehavior::kBitmap);
247 };
248
249 auto shell = Shell::Create(platform_data: flutter::PlatformData(), //
250 task_runners, //
251 settings, //
252 on_create_platform_view, //
253 on_create_rasterizer //
254 );
255
256 if (!shell || !shell->IsSetup()) {
257 FML_LOG(ERROR) << "Could not set up the shell.";
258 return EXIT_FAILURE;
259 }
260
261 if (settings.application_kernel_asset.empty()) {
262 FML_LOG(ERROR) << "Dart kernel file not specified.";
263 return EXIT_FAILURE;
264 }
265
266 shell->GetPlatformView()->NotifyCreated();
267
268 // Initialize default testing locales. There is no platform to
269 // pass locales on the tester, so to retain expected locale behavior,
270 // we emulate it in here by passing in 'en_US' and 'zh_CN' as test locales.
271 const char* locale_json =
272 "{\"method\":\"setLocale\",\"args\":[\"en\",\"US\",\"\",\"\",\"zh\","
273 "\"CN\",\"\",\"\"]}";
274 auto locale_bytes = fml::MallocMapping::Copy(
275 begin: locale_json, end: locale_json + std::strlen(s: locale_json));
276 fml::RefPtr<flutter::PlatformMessageResponse> response;
277 shell->GetPlatformView()->DispatchPlatformMessage(
278 message: std::make_unique<flutter::PlatformMessage>(
279 args: "flutter/localization", args: std::move(locale_bytes), args&: response));
280
281 std::initializer_list<fml::FileMapping::Protection> protection = {
282 fml::FileMapping::Protection::kRead};
283 auto main_dart_file_mapping = std::make_unique<fml::FileMapping>(
284 args: fml::OpenFile(
285 path: fml::paths::AbsolutePath(path: settings.application_kernel_asset).c_str(),
286 create_if_necessary: false, permission: fml::FilePermission::kRead),
287 args&: protection);
288
289 auto isolate_configuration =
290 IsolateConfiguration::CreateForKernel(kernel: std::move(main_dart_file_mapping));
291
292 if (!isolate_configuration) {
293 FML_LOG(ERROR) << "Could create isolate configuration.";
294 return EXIT_FAILURE;
295 }
296
297 auto asset_manager = std::make_shared<flutter::AssetManager>();
298 asset_manager->PushBack(resolver: std::make_unique<flutter::DirectoryAssetBundle>(
299 args: fml::Duplicate(descriptor: settings.assets_dir), args: true));
300 asset_manager->PushBack(resolver: std::make_unique<flutter::DirectoryAssetBundle>(
301 args: fml::OpenDirectory(path: settings.assets_path.c_str(), create_if_necessary: false,
302 permission: fml::FilePermission::kRead),
303 args: true));
304
305 RunConfiguration run_configuration(std::move(isolate_configuration),
306 std::move(asset_manager));
307
308 // The script completion task observer that will be installed on the UI thread
309 // that watched if the engine has any live ports.
310 ScriptCompletionTaskObserver completion_observer(
311 *shell, // a valid shell
312 fml::MessageLoop::GetCurrent()
313 .GetTaskRunner(), // the message loop to terminate
314 run_forever // should the exit be ignored
315 );
316
317 bool engine_did_run = false;
318
319 fml::AutoResetWaitableEvent latch;
320 auto task_observer_add = [&completion_observer]() {
321 fml::MessageLoop::GetCurrent().AddTaskObserver(
322 key: reinterpret_cast<intptr_t>(&completion_observer),
323 callback: [&completion_observer]() { completion_observer.DidProcessTask(); });
324 };
325
326 auto task_observer_remove = [&completion_observer, &latch]() {
327 fml::MessageLoop::GetCurrent().RemoveTaskObserver(
328 key: reinterpret_cast<intptr_t>(&completion_observer));
329 latch.Signal();
330 };
331
332 shell->RunEngine(run_configuration: std::move(run_configuration),
333 result_callback: [&engine_did_run, &ui_task_runner,
334 &task_observer_add](Engine::RunStatus run_status) mutable {
335 if (run_status != flutter::Engine::RunStatus::Failure) {
336 engine_did_run = true;
337 // Now that our engine is initialized we can install the
338 // ScriptCompletionTaskObserver
339 fml::TaskRunner::RunNowOrPostTask(runner: ui_task_runner,
340 task: task_observer_add);
341 }
342 });
343
344 auto device_pixel_ratio = 3.0;
345 auto physical_width = 2400.0; // 800 at 3x resolution.
346 auto physical_height = 1800.0; // 600 at 3x resolution.
347
348 std::vector<std::unique_ptr<Display>> displays;
349 displays.push_back(x: std::make_unique<Display>(
350 args: 0, args: 60, args&: physical_width, args&: physical_height, args&: device_pixel_ratio));
351 shell->OnDisplayUpdates(displays: std::move(displays));
352
353 flutter::ViewportMetrics metrics{};
354 metrics.device_pixel_ratio = device_pixel_ratio;
355 metrics.physical_width = physical_width;
356 metrics.physical_height = physical_height;
357 metrics.display_id = 0;
358 shell->GetPlatformView()->SetViewportMetrics(view_id: kImplicitViewId, metrics);
359
360 // Run the message loop and wait for the script to do its thing.
361 fml::MessageLoop::GetCurrent().Run();
362
363 // Cleanup the completion observer synchronously as it is living on the
364 // stack.
365 fml::TaskRunner::RunNowOrPostTask(runner: ui_task_runner, task: task_observer_remove);
366 latch.Wait();
367
368 if (!engine_did_run) {
369 // If the engine itself didn't have a chance to run, there is no point in
370 // asking it if there was an error. Signal a failure unconditionally.
371 return EXIT_FAILURE;
372 }
373
374 return completion_observer.GetExitCodeForLastError();
375}
376
377} // namespace flutter
378
379int main(int argc, char* argv[]) {
380 dart::bin::SetExecutableName(argv[0]);
381 dart::bin::SetExecutableArguments(script_index: argc - 1, argv);
382
383 auto command_line = fml::CommandLineFromPlatformOrArgcArgv(argc, argv);
384
385 if (command_line.HasOption(name: flutter::FlagForSwitch(swtch: flutter::Switch::Help))) {
386 flutter::PrintUsage(executable_name: "flutter_tester");
387 return EXIT_SUCCESS;
388 }
389
390 auto settings = flutter::SettingsFromCommandLine(command_line);
391 if (!command_line.positional_args().empty()) {
392 // The tester may not use the switch for the main dart file path. Specifying
393 // it as a positional argument instead.
394 settings.application_kernel_asset = command_line.positional_args()[0];
395 }
396
397 if (settings.application_kernel_asset.empty()) {
398 FML_LOG(ERROR) << "Dart kernel file not specified.";
399 return EXIT_FAILURE;
400 }
401
402 settings.leak_vm = false;
403
404 if (settings.icu_data_path.empty()) {
405 settings.icu_data_path = "icudtl.dat";
406 }
407
408 // The tools that read logs get confused if there is a log tag specified.
409 settings.log_tag = "";
410
411 settings.log_message_callback = [](const std::string& tag,
412 const std::string& message) {
413 if (!tag.empty()) {
414 std::cout << tag << ": ";
415 }
416 std::cout << message << std::endl;
417 };
418
419 settings.task_observer_add = [](intptr_t key, const fml::closure& callback) {
420 fml::MessageLoop::GetCurrent().AddTaskObserver(key, callback);
421 };
422
423 settings.task_observer_remove = [](intptr_t key) {
424 fml::MessageLoop::GetCurrent().RemoveTaskObserver(key);
425 };
426
427 settings.unhandled_exception_callback = [](const std::string& error,
428 const std::string& stack_trace) {
429 FML_LOG(ERROR) << "Unhandled exception" << std::endl
430 << "Exception: " << error << std::endl
431 << "Stack trace: " << stack_trace;
432 ::exit(status: 1);
433 return true;
434 };
435
436#if defined(FML_OS_WIN)
437 CoInitializeEx(nullptr, COINIT_MULTITHREADED);
438#endif // defined(FML_OS_WIN)
439
440 return flutter::RunTester(settings,
441 run_forever: command_line.HasOption(name: flutter::FlagForSwitch(
442 swtch: flutter::Switch::RunForever)),
443 multithreaded: command_line.HasOption(name: flutter::FlagForSwitch(
444 swtch: flutter::Switch::ForceMultithreading)));
445}
446

source code of flutter_engine/flutter/shell/testing/tester_main.cc