8#include <fuchsia/test/cpp/fidl.h>
9#include <lib/async-loop/loop.h>
10#include <lib/async/cpp/task.h>
11#include <lib/async/default.h>
12#include <lib/fdio/directory.h>
13#include <lib/fdio/fd.h>
14#include <lib/fdio/namespace.h>
15#include <lib/fidl/cpp/string.h>
16#include <lib/fpromise/promise.h>
17#include <lib/sys/cpp/service_directory.h>
18#include <lib/zx/clock.h>
19#include <lib/zx/thread.h>
23#include <zircon/status.h>
33#include "third_party/dart/runtime/include/dart_tools_api.h"
49constexpr char kTmpPath[] =
"/tmp";
51constexpr zx::duration kIdleWaitDuration = zx::sec(2);
52constexpr zx::duration kIdleNotifyDuration = zx::msec(500);
53constexpr zx::duration kIdleSlack = zx::sec(1);
55constexpr zx::duration kTestTimeout = zx::sec(60);
57void AfterTask(async_loop_t*,
void*) {
63 queue->RunMicrotasks();
67constexpr async_loop_config_t kLoopConfig = {
70 .getter = async_get_default_dispatcher,
71 .setter = async_set_default_dispatcher,
73 .make_default_for_current_thread =
true,
74 .epilogue = &AfterTask,
79std::string GetLabelFromUrl(
const std::string& url) {
80 for (
size_t i = url.length() - 1;
i > 0;
i--) {
82 return url.substr(
i + 1, url.length() - 1);
90std::string GetComponentNameFromUrl(
const std::string& url) {
91 const std::string label = GetLabelFromUrl(url);
92 for (
size_t i = 0;
i < label.length(); ++
i) {
93 if (label[
i] ==
'.') {
94 return label.substr(0,
i);
103 fuchsia::component::runner::ComponentStartInfo start_info,
104 std::shared_ptr<sys::ServiceDirectory> runner_incoming_services,
105 fidl::InterfaceRequest<fuchsia::component::runner::ComponentController>
107 DoneCallback done_callback)
108 : loop_(new async::Loop(&kLoopConfig)),
109 executor_(loop_->dispatcher()),
110 label_(GetLabelFromUrl(start_info.resolved_url())),
111 url_(
std::move(start_info.resolved_url())),
112 runner_incoming_services_(runner_incoming_services),
113 start_info_(
std::move(start_info)),
115 done_callback_(
std::move(done_callback)) {
120 test_component_name_ = GetComponentNameFromUrl(url_);
121 data_path_ =
"pkg/data/" + test_component_name_;
123 if (controller.is_valid()) {
124 binding_.Bind(std::move(controller));
125 binding_.set_error_handler([
this](zx_status_t status) { Kill(); });
127 FML_LOG(ERROR) <<
"Fuchsia component controller endpoint is not valid.";
130 zx_status_t idle_timer_status =
131 zx::timer::create(ZX_TIMER_SLACK_LATE, ZX_CLOCK_MONOTONIC, &idle_timer_);
132 if (idle_timer_status != ZX_OK) {
133 FML_LOG(INFO) <<
"Idle timer creation failed: "
134 << zx_status_get_string(idle_timer_status);
136 idle_wait_.set_object(idle_timer_.get());
137 idle_wait_.set_trigger(ZX_TIMER_SIGNALED);
138 idle_wait_.Begin(async_get_default_dispatcher());
143 start_info_.clear_runtime_dir();
148 fdio_ns_destroy(namespace_);
149 namespace_ =
nullptr;
157 zx::thread::self()->set_property(ZX_PROP_NAME, label_.c_str(), label_.size());
158 Dart_SetThreadName(label_.c_str());
160 if (!CreateAndBindNamespace()) {
164 if (SetUpFromAppSnapshot()) {
165 FML_LOG(INFO) << url_ <<
" is running from an app snapshot";
166 }
else if (SetUpFromKernel()) {
167 FML_LOG(INFO) << url_ <<
" is running from kernel";
169 FML_LOG(ERROR) <<
"Failed to set up component controller for " << url_;
174 suite_context_ = sys::ComponentContext::Create();
175 suite_context_->outgoing()->AddPublicService(this->
GetHandler());
176 suite_context_->outgoing()->Serve(
177 std::move(*start_info_.mutable_outgoing_dir()), loop_->dispatcher());
182bool DartTestComponentController::CreateAndBindNamespace() {
183 if (!start_info_.has_ns()) {
184 FML_LOG(ERROR) <<
"Component start info does not have a namespace.";
188 const zx_status_t ns_create_status = fdio_ns_create(&namespace_);
189 if (ns_create_status != ZX_OK) {
190 FML_LOG(ERROR) <<
"Failed to create namespace: "
191 << zx_status_get_string(ns_create_status);
198 for (
auto& ns_entry : *start_info_.mutable_ns()) {
201 if (!ns_entry.has_path() || !ns_entry.has_directory()) {
205 if (ns_entry.path() == kTmpPath) {
212 fidl::InterfaceHandle<::fuchsia::io::Directory> dir =
213 std::move(*ns_entry.mutable_directory());
214 const std::string
path = std::move(*ns_entry.mutable_path());
216 const zx_status_t ns_bind_status =
217 fdio_ns_bind(namespace_,
path.c_str(), dir.TakeChannel().release());
218 if (ns_bind_status != ZX_OK) {
219 FML_LOG(ERROR) <<
"Failed to bind " <<
path <<
" to namespace: "
220 << zx_status_get_string(ns_bind_status);
228bool DartTestComponentController::SetUpFromKernel() {
231 namespace_, data_path_ +
"/app.dilplist", manifest)) {
236 nullptr,
"/pkg/data/isolate_core_snapshot_data.bin",
237 isolate_snapshot_data_)) {
241 std::string str(
reinterpret_cast<const char*
>(manifest.
address()),
243 Dart_Handle library = Dart_Null();
245 for (
size_t start = 0;
start < manifest.
size();) {
246 size_t end = str.find(
"\n", start);
247 if (
end == std::string::npos) {
248 FML_LOG(ERROR) <<
"Malformed manifest";
252 std::string
path = data_path_ +
"/" + str.substr(start,
end - start);
258 FML_LOG(ERROR) <<
"Cannot load kernel from namespace: " <<
path;
261 kernel_peices_.emplace_back(std::move(kernel));
264 if (!CreateIsolate(isolate_snapshot_data_.
address(),
271 for (
const auto& kernel : kernel_peices_) {
272 library = Dart_LoadLibraryFromKernel(kernel.
address(), kernel.
size());
273 if (Dart_IsError(library)) {
274 FML_LOG(ERROR) <<
"Cannot load library from kernel: "
275 << Dart_GetError(library);
280 Dart_SetRootLibrary(library);
282 Dart_Handle result = Dart_FinalizeLoading(
false);
283 if (Dart_IsError(result)) {
284 FML_LOG(ERROR) <<
"Failed to FinalizeLoading: " << Dart_GetError(result);
292bool DartTestComponentController::SetUpFromAppSnapshot() {
293#if !defined(AOT_RUNTIME)
298 const uint8_t *isolate_data, *isolate_instructions;
299 if (elf_snapshot_.
Load(namespace_, data_path_ +
"/app_aot_snapshot.so")) {
302 if (isolate_data ==
nullptr || isolate_instructions ==
nullptr) {
307 namespace_, data_path_ +
"/isolate_snapshot_data.bin",
308 isolate_snapshot_data_)) {
311 isolate_data = isolate_snapshot_data_.
address();
312 isolate_instructions =
nullptr;
314 return CreateIsolate(isolate_data, isolate_instructions);
318bool DartTestComponentController::CreateIsolate(
319 const uint8_t* isolate_snapshot_data,
320 const uint8_t* isolate_snapshot_instructions) {
322 char*
error =
nullptr;
325 intptr_t namespace_fd = -1;
329 namespace_fd, [
this](Dart_Handle result) { MessageEpilogue(result); }));
331 Dart_IsolateFlags isolate_flags;
332 Dart_IsolateFlagsInitialize(&isolate_flags);
333 isolate_flags.null_safety =
true;
335 isolate_ = Dart_CreateIsolateGroup(
336 url_.c_str(), label_.c_str(), isolate_snapshot_data,
337 isolate_snapshot_instructions, &isolate_flags, state, state, &
error);
339 FML_LOG(ERROR) <<
"Dart_CreateIsolateGroup failed: " <<
error;
343 state->get()->SetIsolate(isolate_);
346 [loop = loop_.get()](
auto callback) {
347 async::PostTask(loop->dispatcher(), std::move(
callback));
349 state->get()->message_handler().Initialize(dispatcher);
351 state->get()->SetReturnCodeCallback([
this](uint32_t return_code) {
352 return_code_ = return_code;
353 auto ret_status = return_code == 0 ? fuchsia::test::Status::PASSED
354 : fuchsia::test::Status::FAILED;
355 fuchsia::test::Result result;
356 result.set_status(ret_status);
357 case_listener_->Finished(std::move(result));
364DartTestComponentController::CaseIterator::CaseIterator(
365 fidl::InterfaceRequest<fuchsia::test::CaseIterator> request,
366 async_dispatcher_t* dispatcher,
367 std::string test_component_name,
368 fit::function<
void(CaseIterator*)> done_callback)
370 test_component_name_(test_component_name),
371 done_callback_(
std::move(done_callback)) {}
374void DartTestComponentController::CaseIterator::GetNext(
379 fuchsia::test::Case test_case;
380 test_case.set_name(test_component_name_);
381 test_case.set_enabled(
true);
382 std::vector<fuchsia::test::Case> cases;
383 cases.push_back(std::move(test_case));
389 std::vector<fuchsia::test::Case> cases;
391 done_callback_(
this);
396std::unique_ptr<DartTestComponentController::CaseIterator>
397DartTestComponentController::RemoveCaseInterator(CaseIterator* case_iterator) {
398 auto it = case_iterators_.find(case_iterator);
399 std::unique_ptr<DartTestComponentController::CaseIterator> case_iterator_ptr;
400 if (it != case_iterators_.end()) {
401 case_iterator_ptr = std::move(it->second);
402 case_iterators_.erase(it);
404 return case_iterator_ptr;
409 fidl::InterfaceRequest<fuchsia::test::CaseIterator> iterator) {
410 auto case_iterator = std::make_unique<CaseIterator>(
411 std::move(iterator), loop_->dispatcher(), test_component_name_,
412 [
this](CaseIterator* case_iterator) {
413 RemoveCaseInterator(case_iterator);
415 case_iterators_.emplace(case_iterator.get(), std::move(case_iterator));
420 std::vector<fuchsia::test::Invocation> tests,
421 fuchsia::test::RunOptions options,
422 fidl::InterfaceHandle<fuchsia::test::RunListener> listener) {
423 std::vector<std::string>
args;
424 if (options.has_arguments()) {
425 args = std::move(*options.mutable_arguments());
428 auto listener_proxy = listener.Bind();
434 for (
auto it = tests.begin(); it != tests.end(); it++) {
435 auto invocation = std::move(*it);
436 std::string test_case_name;
437 if (invocation.has_name()) {
438 test_case_name = invocation.name();
443 auto status = zx::socket::create(0, &out_, &out_client_);
444 if (status != ZX_OK) {
445 FML_LOG(FATAL) <<
"cannot create out socket: "
446 << zx_status_get_string(status);
449 status = fdio_fd_create(out_.release(), &stdout_fd_);
450 if (status != ZX_OK) {
451 FML_LOG(FATAL) <<
"failed to extract output fd: "
452 << zx_status_get_string(status);
455 status = zx::socket::create(0, &err_, &err_client_);
456 if (status != ZX_OK) {
457 FML_LOG(FATAL) <<
"cannot create error socket: "
458 << zx_status_get_string(status);
461 status = fdio_fd_create(err_.release(), &stderr_fd_);
462 if (status != ZX_OK) {
463 FML_LOG(FATAL) <<
"failed to extract error fd: "
464 << zx_status_get_string(status);
469 fuchsia::test::StdHandles std_handles;
470 std_handles.set_out(std::move(out_client_));
471 std_handles.set_err(std::move(err_client_));
473 listener_proxy->OnTestCaseStarted(std::move(invocation),
474 std::move(std_handles),
475 case_listener_.NewRequest());
479 auto dart_main_promise = fpromise::make_promise([&] { RunDartMain(); });
481 executor_.schedule_task(std::move(dart_main_promise));
482 while (!dart_state->has_set_return_code()) {
483 loop_->Run(zx::deadline_after(kTestTimeout),
true);
487 listener_proxy->OnFinished();
490 if (binding_.is_bound()) {
498 if (return_code_ == 0) {
499 binding_.Close(ZX_OK);
501 binding_.Close(zx_status_t(fuchsia::component::Error::INTERNAL));
509fpromise::promise<> DartTestComponentController::RunDartMain() {
515 fidl::InterfaceRequest<fuchsia::io::Directory> outgoing_dir =
516 std::move(*start_info_.mutable_outgoing_dir());
518 outgoing_dir.TakeChannel(),
523 char*
error = Dart_IsolateMakeRunnable(isolate_);
524 if (
error !=
nullptr) {
525 Dart_EnterIsolate(isolate_);
526 Dart_ShutdownIsolate();
527 FML_LOG(ERROR) <<
"Unable to make isolate runnable: " <<
error;
529 return fpromise::make_error_promise();
531 Dart_EnterIsolate(isolate_);
535 Dart_Handle corelib = Dart_LookupLibrary(ToDart(
"dart:core"));
536 Dart_Handle string_type =
537 Dart_GetNonNullableType(corelib, ToDart(
"String"), 0, NULL);
538 Dart_Handle dart_arguments =
539 Dart_NewListOfTypeFilled(string_type, Dart_EmptyString(), 0);
541 if (Dart_IsError(dart_arguments)) {
542 FML_LOG(ERROR) <<
"Failed to allocate Dart arguments list: "
543 << Dart_GetError(dart_arguments);
545 return fpromise::make_error_promise();
548 Dart_Handle user_main = Dart_GetField(Dart_RootLibrary(),
ToDart(
"main"));
550 if (Dart_IsError(user_main)) {
551 FML_LOG(ERROR) <<
"Failed to locate user_main in the root library: "
552 << Dart_GetError(user_main);
554 return fpromise::make_error_promise();
557 Dart_Handle fuchsia_lib = Dart_LookupLibrary(
tonic::ToDart(
"dart:fuchsia"));
559 if (Dart_IsError(fuchsia_lib)) {
560 FML_LOG(ERROR) <<
"Failed to locate dart:fuchsia: "
561 << Dart_GetError(fuchsia_lib);
563 return fpromise::make_error_promise();
567 fuchsia_lib,
"_runUserMainForDartRunner", {user_main, dart_arguments});
569 if (Dart_IsError(main_result)) {
571 if (!dart_state->has_set_return_code()) {
573 FML_LOG(ERROR) << Dart_GetError(main_result);
580 return fpromise::make_error_promise();
584 return fpromise::make_ok_promise();
587void DartTestComponentController::Kill() {
588 done_callback_(
this);
591 suite_bindings_.CloseAll();
592 if (Dart_CurrentIsolate()) {
604 Dart_SetMessageNotifyCallback(
nullptr);
606 Dart_ShutdownIsolate();
610void DartTestComponentController::MessageEpilogue(Dart_Handle result) {
615 if (dart_state->has_set_return_code()) {
616 Dart_ShutdownIsolate();
624 if (return_code_ != 0) {
625 Dart_ShutdownIsolate();
629 idle_start_ = zx::clock::get_monotonic();
631 idle_timer_.set(idle_start_ + kIdleWaitDuration, kIdleSlack);
632 if (status != ZX_OK) {
633 FML_LOG(INFO) <<
"Idle timer set failed: " << zx_status_get_string(status);
637void DartTestComponentController::Stop() {
641void DartTestComponentController::OnIdleTimer(async_dispatcher_t* dispatcher,
642 async::WaitBase* wait,
644 const zx_packet_signal* signal) {
645 if ((status != ZX_OK) || !(signal->observed & ZX_TIMER_SIGNALED) ||
646 !Dart_CurrentIsolate()) {
651 zx::time deadline = idle_start_ + kIdleWaitDuration;
652 zx::time now = zx::clock::get_monotonic();
653 if (now >= deadline) {
656 Dart_NotifyIdle((now + kIdleNotifyDuration).get());
657 idle_start_ = zx::time(0);
658 idle_timer_.cancel();
661 zx_status_t status = idle_timer_.set(deadline, kIdleSlack);
662 if (status != ZX_OK) {
663 FML_LOG(INFO) <<
"Idle timer set failed: "
664 << zx_status_get_string(status);
667 wait->Begin(dispatcher);
672 bool method_has_response) {
673 FML_LOG(ERROR) <<
"Unknown method called on DartTestComponentController. "
void Run(std::vector< fuchsia::test::Invocation > tests, fuchsia::test::RunOptions options, fidl::InterfaceHandle< fuchsia::test::RunListener > listener) override
|Suite| protocol implementation.
void handle_unknown_method(uint64_t ordinal, bool method_has_response) override
~DartTestComponentController() override
fidl::InterfaceRequestHandler< fuchsia::test::Suite > GetHandler()
void GetTests(fidl::InterfaceRequest< fuchsia::test::CaseIterator > iterator) override
|Suite| protocol implementation.
DartTestComponentController(fuchsia::component::runner::ComponentStartInfo start_info, std::shared_ptr< sys::ServiceDirectory > runner_incoming_services, fidl::InterfaceRequest< fuchsia::component::runner::ComponentController > controller, DoneCallback done_callback)
bool Load(fdio_ns_t *namespc, const std::string &path)
const uint8_t * IsolateData() const
const uint8_t * IsolateInstrs() const
static bool LoadFromNamespace(fdio_ns_t *namespc, const std::string &path, MappedResource &resource, bool executable=false)
const uint8_t * address() const
std::function< void(std::function< void(void)>)> TaskDispatcher
static DartMicrotaskQueue * GetForCurrentThread()
static void StartForCurrentThread()
static DartState * Current()
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
const uint8_t uint32_t uint32_t GError ** error
FlutterDesktopBinaryReply callback
#define FML_LOG(severity)
#define FML_CHECK(condition)
void InitBuiltinLibrariesForIsolate(const std::string &script_uri, fdio_ns_t *namespc, int stdoutfd, int stderrfd, zx::channel directory_request, bool service_isolate)
void HandleIfException(std::shared_ptr<::sys::ServiceDirectory > services, const std::string &component_url, Dart_Handle result)
void BindTemp(fdio_ns_t *ns)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Dart_Handle ToDart(const T &object)
int GetErrorExitCode(Dart_Handle handle)
Dart_Handle DartInvokeField(Dart_Handle target, const char *name, std::initializer_list< Dart_Handle > args)