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 | |
30 | namespace flutter { |
31 | |
32 | // Checks whether the engine's main Dart isolate has no pending work. If so, |
33 | // then exit the given message loop. |
34 | class 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). |
86 | static 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 | |
95 | int 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 | |
262 | int 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 | |