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 <cstdlib>
9
10#include "flutter/assets/asset_manager.h"
11#include "flutter/assets/directory_asset_bundle.h"
12#include "flutter/fml/file.h"
13#include "flutter/fml/make_copyable.h"
14#include "flutter/fml/message_loop.h"
15#include "flutter/fml/paths.h"
16#include "flutter/fml/synchronization/waitable_event.h"
17#include "flutter/fml/task_runner.h"
18#include "flutter/shell/common/platform_view.h"
19#include "flutter/shell/common/rasterizer.h"
20#include "flutter/shell/common/shell.h"
21#include "flutter/shell/common/switches.h"
22#include "flutter/shell/common/thread_host.h"
23#include "third_party/dart/runtime/include/bin/dart_io_api.h"
24#include "third_party/dart/runtime/include/dart_api.h"
25
26#if defined(OS_POSIX)
27#include <signal.h>
28#endif // defined(OS_POSIX)
29
30namespace flutter {
31
32// Checks whether the engine's main Dart isolate has no pending work. If so,
33// then exit the given message loop.
34class ScriptCompletionTaskObserver {
35 public:
36 ScriptCompletionTaskObserver(Shell& shell,
37 fml::RefPtr<fml::TaskRunner> main_task_runner,
38 bool run_forever)
39 : shell_(shell),
40 main_task_runner_(std::move(main_task_runner)),
41 run_forever_(run_forever) {}
42
43 int GetExitCodeForLastError() const {
44 return static_cast<int>(last_error_.value_or(DartErrorCode::NoError));
45 }
46
47 void DidProcessTask() {
48 last_error_ = shell_.GetUIIsolateLastError();
49 if (shell_.EngineHasLivePorts()) {
50 // The UI isolate still has live ports and is running. Nothing to do
51 // just yet.
52 return;
53 }
54
55 if (run_forever_) {
56 // We need this script to run forever. We have already recorded the last
57 // error. Keep going.
58 return;
59 }
60
61 if (!has_terminated) {
62 // Only try to terminate the loop once.
63 has_terminated = true;
64 fml::TaskRunner::RunNowOrPostTask(main_task_runner_, []() {
65 fml::MessageLoop::GetCurrent().Terminate();
66 });
67 }
68 }
69
70 private:
71 Shell& shell_;
72 fml::RefPtr<fml::TaskRunner> main_task_runner_;
73 bool run_forever_ = false;
74 std::optional<DartErrorCode> last_error_;
75 bool has_terminated = false;
76
77 FML_DISALLOW_COPY_AND_ASSIGN(ScriptCompletionTaskObserver);
78};
79
80// Processes spawned via dart:io inherit their signal handling from the parent
81// process. As part of spawning, the spawner blocks signals temporarily, so we
82// need to explicitly unblock the signals we care about in the new process. In
83// particular, we need to unblock SIGPROF for CPU profiling to work on the
84// mutator thread in the main isolate in this process (threads spawned by the VM
85// know about this limitation and automatically have this signal unblocked).
86static void UnblockSIGPROF() {
87#if defined(OS_POSIX)
88 sigset_t set;
89 sigemptyset(&set);
90 sigaddset(&set, SIGPROF);
91 pthread_sigmask(SIG_UNBLOCK, &set, NULL);
92#endif // defined(OS_POSIX)
93}
94
95int RunTester(const flutter::Settings& settings,
96 bool run_forever,
97 bool multithreaded) {
98 const auto thread_label = "io.flutter.test.";
99
100 // Necessary if we want to use the CPU profiler on the main isolate's mutator
101 // thread.
102 //
103 // OSX WARNING: avoid spawning additional threads before this call due to a
104 // kernel bug that may enable SIGPROF on an unintended thread in the process.
105 UnblockSIGPROF();
106
107 fml::MessageLoop::EnsureInitializedForCurrentThread();
108
109 auto current_task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
110
111 std::unique_ptr<ThreadHost> threadhost;
112 fml::RefPtr<fml::TaskRunner> platform_task_runner;
113 fml::RefPtr<fml::TaskRunner> raster_task_runner;
114 fml::RefPtr<fml::TaskRunner> ui_task_runner;
115 fml::RefPtr<fml::TaskRunner> io_task_runner;
116
117 if (multithreaded) {
118 threadhost = std::make_unique<ThreadHost>(
119 thread_label, ThreadHost::Type::Platform | ThreadHost::Type::IO |
120 ThreadHost::Type::UI | ThreadHost::Type::GPU);
121 platform_task_runner = current_task_runner;
122 raster_task_runner = threadhost->raster_thread->GetTaskRunner();
123 ui_task_runner = threadhost->ui_thread->GetTaskRunner();
124 io_task_runner = threadhost->io_thread->GetTaskRunner();
125 } else {
126 platform_task_runner = raster_task_runner = ui_task_runner =
127 io_task_runner = current_task_runner;
128 }
129
130 const flutter::TaskRunners task_runners(thread_label, // dart thread label
131 platform_task_runner, // platform
132 raster_task_runner, // raster
133 ui_task_runner, // ui
134 io_task_runner // io
135 );
136
137 Shell::CreateCallback<PlatformView> on_create_platform_view =
138 [](Shell& shell) {
139 return std::make_unique<PlatformView>(shell, shell.GetTaskRunners());
140 };
141
142 Shell::CreateCallback<Rasterizer> on_create_rasterizer = [](Shell& shell) {
143 return std::make_unique<Rasterizer>(shell);
144 };
145
146 auto shell = Shell::Create(task_runners, //
147 settings, //
148 on_create_platform_view, //
149 on_create_rasterizer //
150 );
151
152 if (!shell || !shell->IsSetup()) {
153 FML_LOG(ERROR) << "Could not setup the shell.";
154 return EXIT_FAILURE;
155 }
156
157 if (settings.application_kernel_asset.empty()) {
158 FML_LOG(ERROR) << "Dart kernel file not specified.";
159 return EXIT_FAILURE;
160 }
161
162 // Initialize default testing locales. There is no platform to
163 // pass locales on the tester, so to retain expected locale behavior,
164 // we emulate it in here by passing in 'en_US' and 'zh_CN' as test locales.
165 const char* locale_json =
166 "{\"method\":\"setLocale\",\"args\":[\"en\",\"US\",\"\",\"\",\"zh\","
167 "\"CN\",\"\",\"\"]}";
168 std::vector<uint8_t> locale_bytes(locale_json,
169 locale_json + std::strlen(locale_json));
170 fml::RefPtr<flutter::PlatformMessageResponse> response;
171 shell->GetPlatformView()->DispatchPlatformMessage(
172 fml::MakeRefCounted<flutter::PlatformMessage>("flutter/localization",
173 locale_bytes, response));
174
175 std::initializer_list<fml::FileMapping::Protection> protection = {
176 fml::FileMapping::Protection::kRead};
177 auto main_dart_file_mapping = std::make_unique<fml::FileMapping>(
178 fml::OpenFile(
179 fml::paths::AbsolutePath(settings.application_kernel_asset).c_str(),
180 false, fml::FilePermission::kRead),
181 protection);
182
183 auto isolate_configuration =
184 IsolateConfiguration::CreateForKernel(std::move(main_dart_file_mapping));
185
186 if (!isolate_configuration) {
187 FML_LOG(ERROR) << "Could create isolate configuration.";
188 return EXIT_FAILURE;
189 }
190
191 auto asset_manager = std::make_shared<flutter::AssetManager>();
192 asset_manager->PushBack(std::make_unique<flutter::DirectoryAssetBundle>(
193 fml::Duplicate(settings.assets_dir)));
194 asset_manager->PushBack(
195 std::make_unique<flutter::DirectoryAssetBundle>(fml::OpenDirectory(
196 settings.assets_path.c_str(), false, fml::FilePermission::kRead)));
197
198 RunConfiguration run_configuration(std::move(isolate_configuration),
199 std::move(asset_manager));
200
201 // The script completion task observer that will be installed on the UI thread
202 // that watched if the engine has any live ports.
203 ScriptCompletionTaskObserver completion_observer(
204 *shell, // a valid shell
205 fml::MessageLoop::GetCurrent()
206 .GetTaskRunner(), // the message loop to terminate
207 run_forever // should the exit be ignored
208 );
209
210 bool engine_did_run = false;
211
212 fml::AutoResetWaitableEvent latch;
213 auto task_observer_add = [&completion_observer]() {
214 fml::MessageLoop::GetCurrent().AddTaskObserver(
215 reinterpret_cast<intptr_t>(&completion_observer),
216 [&completion_observer]() { completion_observer.DidProcessTask(); });
217 };
218
219 auto task_observer_remove = [&completion_observer, &latch]() {
220 fml::MessageLoop::GetCurrent().RemoveTaskObserver(
221 reinterpret_cast<intptr_t>(&completion_observer));
222 latch.Signal();
223 };
224
225 shell->RunEngine(std::move(run_configuration),
226 [&engine_did_run, &ui_task_runner,
227 &task_observer_add](Engine::RunStatus run_status) mutable {
228 if (run_status != flutter::Engine::RunStatus::Failure) {
229 engine_did_run = true;
230 // Now that our engine is initialized we can install the
231 // ScriptCompletionTaskObserver
232 fml::TaskRunner::RunNowOrPostTask(ui_task_runner,
233 task_observer_add);
234 }
235 });
236
237 flutter::ViewportMetrics metrics{};
238 metrics.device_pixel_ratio = 3.0;
239 metrics.physical_width = 2400.0; // 800 at 3x resolution.
240 metrics.physical_height = 1800.0; // 600 at 3x resolution.
241 shell->GetPlatformView()->SetViewportMetrics(metrics);
242
243 // Run the message loop and wait for the script to do its thing.
244 fml::MessageLoop::GetCurrent().Run();
245
246 // Cleanup the completion observer synchronously as it is living on the
247 // stack.
248 fml::TaskRunner::RunNowOrPostTask(ui_task_runner, task_observer_remove);
249 latch.Wait();
250
251 if (!engine_did_run) {
252 // If the engine itself didn't have a chance to run, there is no point in
253 // asking it if there was an error. Signal a failure unconditionally.
254 return EXIT_FAILURE;
255 }
256
257 return completion_observer.GetExitCodeForLastError();
258}
259
260} // namespace flutter
261
262int main(int argc, char* argv[]) {
263 dart::bin::SetExecutableName(argv[0]);
264 dart::bin::SetExecutableArguments(argc - 1, argv);
265
266 auto command_line = fml::CommandLineFromArgcArgv(argc, argv);
267
268 if (command_line.HasOption(flutter::FlagForSwitch(flutter::Switch::Help))) {
269 flutter::PrintUsage("flutter_tester");
270 return EXIT_SUCCESS;
271 }
272
273 auto settings = flutter::SettingsFromCommandLine(command_line);
274 if (command_line.positional_args().size() > 0) {
275 // The tester may not use the switch for the main dart file path. Specifying
276 // it as a positional argument instead.
277 settings.application_kernel_asset = command_line.positional_args()[0];
278 }
279
280 if (settings.application_kernel_asset.size() == 0) {
281 FML_LOG(ERROR) << "Dart kernel file not specified.";
282 return EXIT_FAILURE;
283 }
284
285 if (settings.icu_data_path.size() == 0) {
286 settings.icu_data_path = "icudtl.dat";
287 }
288
289 // The tools that read logs get confused if there is a log tag specified.
290 settings.log_tag = "";
291
292 settings.task_observer_add = [](intptr_t key, fml::closure callback) {
293 fml::MessageLoop::GetCurrent().AddTaskObserver(key, std::move(callback));
294 };
295
296 settings.task_observer_remove = [](intptr_t key) {
297 fml::MessageLoop::GetCurrent().RemoveTaskObserver(key);
298 };
299
300 settings.unhandled_exception_callback = [](const std::string& error,
301 const std::string& stack_trace) {
302 FML_LOG(ERROR) << "Unhandled exception" << std::endl
303 << "Exception: " << error << std::endl
304 << "Stack trace: " << stack_trace;
305 ::exit(1);
306 return true;
307 };
308
309 return flutter::RunTester(settings,
310 command_line.HasOption(flutter::FlagForSwitch(
311 flutter::Switch::RunForever)),
312 command_line.HasOption(flutter::FlagForSwitch(
313 flutter::Switch::ForceMultithreading)));
314}
315