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