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>
32#include "third_party/dart/runtime/include/dart_tools_api.h"
46constexpr char kTmpPath[] =
"/tmp";
48constexpr zx::duration kIdleWaitDuration = zx::sec(2);
49constexpr zx::duration kIdleNotifyDuration = zx::msec(500);
50constexpr zx::duration kIdleSlack = zx::sec(1);
52constexpr zx::duration kTestTimeout = zx::sec(60);
54void AfterTask(async_loop_t*,
void*) {
60 queue->RunMicrotasks();
64constexpr async_loop_config_t kLoopConfig = {
67 .getter = async_get_default_dispatcher,
68 .setter = async_set_default_dispatcher,
70 .make_default_for_current_thread =
true,
71 .epilogue = &AfterTask,
76std::string GetLabelFromUrl(
const std::string& url) {
77 for (
size_t i = url.length() - 1;
i > 0;
i--) {
79 return url.substr(
i + 1, url.length() - 1);
87std::string GetComponentNameFromUrl(
const std::string& url) {
88 const std::string label = GetLabelFromUrl(url);
89 for (
size_t i = 0;
i < label.length(); ++
i) {
90 if (label[
i] ==
'.') {
91 return label.substr(0,
i);
100 fuchsia::component::runner::ComponentStartInfo start_info,
101 std::shared_ptr<sys::ServiceDirectory> runner_incoming_services,
102 fidl::InterfaceRequest<fuchsia::component::runner::ComponentController>
104 DoneCallback done_callback)
105 : loop_(new async::Loop(&kLoopConfig)),
106 executor_(loop_->dispatcher()),
107 label_(GetLabelFromUrl(start_info.resolved_url())),
108 url_(
std::move(start_info.resolved_url())),
109 runner_incoming_services_(runner_incoming_services),
110 start_info_(
std::move(start_info)),
112 done_callback_(
std::move(done_callback)) {
117 test_component_name_ = GetComponentNameFromUrl(url_);
118 data_path_ =
"pkg/data/" + test_component_name_;
120 if (controller.is_valid()) {
121 binding_.Bind(std::move(controller));
122 binding_.set_error_handler([
this](zx_status_t status) { Kill(); });
124 FML_LOG(ERROR) <<
"Fuchsia component controller endpoint is not valid.";
127 zx_status_t idle_timer_status =
128 zx::timer::create(ZX_TIMER_SLACK_LATE, ZX_CLOCK_MONOTONIC, &idle_timer_);
129 if (idle_timer_status != ZX_OK) {
130 FML_LOG(INFO) <<
"Idle timer creation failed: "
131 << zx_status_get_string(idle_timer_status);
133 idle_wait_.set_object(idle_timer_.get());
134 idle_wait_.set_trigger(ZX_TIMER_SIGNALED);
135 idle_wait_.Begin(async_get_default_dispatcher());
140 start_info_.clear_runtime_dir();
145 fdio_ns_destroy(namespace_);
146 namespace_ =
nullptr;
154 zx::thread::self()->set_property(ZX_PROP_NAME, label_.c_str(), label_.size());
155 Dart_SetThreadName(label_.c_str());
157 if (!CreateAndBindNamespace()) {
161 if (SetUpFromAppSnapshot()) {
162 FML_LOG(INFO) << url_ <<
" is running from an app snapshot";
163 }
else if (SetUpFromKernel()) {
164 FML_LOG(INFO) << url_ <<
" is running from kernel";
166 FML_LOG(ERROR) <<
"Failed to set up component controller for " << url_;
171 suite_context_ = sys::ComponentContext::Create();
172 suite_context_->outgoing()->AddPublicService(this->
GetHandler());
173 suite_context_->outgoing()->Serve(
174 std::move(*start_info_.mutable_outgoing_dir()), loop_->dispatcher());
179bool DartTestComponentController::CreateAndBindNamespace() {
180 if (!start_info_.has_ns()) {
181 FML_LOG(ERROR) <<
"Component start info does not have a namespace.";
185 const zx_status_t ns_create_status = fdio_ns_create(&namespace_);
186 if (ns_create_status != ZX_OK) {
187 FML_LOG(ERROR) <<
"Failed to create namespace: "
188 << zx_status_get_string(ns_create_status);
195 for (
auto& ns_entry : *start_info_.mutable_ns()) {
198 if (!ns_entry.has_path() || !ns_entry.has_directory()) {
202 if (ns_entry.path() == kTmpPath) {
209 fidl::InterfaceHandle<::fuchsia::io::Directory> dir =
210 std::move(*ns_entry.mutable_directory());
211 const std::string
path = std::move(*ns_entry.mutable_path());
213 const zx_status_t ns_bind_status =
214 fdio_ns_bind(namespace_,
path.c_str(), dir.TakeChannel().release());
215 if (ns_bind_status != ZX_OK) {
216 FML_LOG(ERROR) <<
"Failed to bind " <<
path <<
" to namespace: "
217 << zx_status_get_string(ns_bind_status);
225bool DartTestComponentController::SetUpFromKernel() {
228 namespace_, data_path_ +
"/app.dilplist", manifest)) {
233 nullptr,
"/pkg/data/isolate_core_snapshot_data.bin",
234 isolate_snapshot_data_)) {
238 std::string str(
reinterpret_cast<const char*
>(manifest.
address()),
240 Dart_Handle library = Dart_Null();
242 for (
size_t start = 0;
start < manifest.
size();) {
243 size_t end = str.find(
"\n", start);
244 if (
end == std::string::npos) {
245 FML_LOG(ERROR) <<
"Malformed manifest";
249 std::string
path = data_path_ +
"/" + str.substr(start,
end - start);
255 FML_LOG(ERROR) <<
"Cannot load kernel from namespace: " <<
path;
258 kernel_peices_.emplace_back(std::move(kernel));
261 if (!CreateIsolate(isolate_snapshot_data_.
address(),
268 for (
const auto& kernel : kernel_peices_) {
269 library = Dart_LoadLibraryFromKernel(kernel.
address(), kernel.
size());
270 if (Dart_IsError(library)) {
271 FML_LOG(ERROR) <<
"Cannot load library from kernel: "
272 << Dart_GetError(library);
277 Dart_SetRootLibrary(library);
279 Dart_Handle result = Dart_FinalizeLoading(
false);
280 if (Dart_IsError(result)) {
281 FML_LOG(ERROR) <<
"Failed to FinalizeLoading: " << Dart_GetError(result);
289bool DartTestComponentController::SetUpFromAppSnapshot() {
290#if !defined(AOT_RUNTIME)
295 const uint8_t *isolate_data, *isolate_instructions;
296 if (elf_snapshot_.
Load(namespace_, data_path_ +
"/app_aot_snapshot.so")) {
299 if (isolate_data ==
nullptr || isolate_instructions ==
nullptr) {
304 namespace_, data_path_ +
"/isolate_snapshot_data.bin",
305 isolate_snapshot_data_)) {
308 isolate_data = isolate_snapshot_data_.
address();
309 isolate_instructions =
nullptr;
311 return CreateIsolate(isolate_data, isolate_instructions);
315bool DartTestComponentController::CreateIsolate(
316 const uint8_t* isolate_snapshot_data,
317 const uint8_t* isolate_snapshot_instructions) {
319 char*
error =
nullptr;
322 intptr_t namespace_fd = -1;
326 namespace_fd, [
this](Dart_Handle result) { MessageEpilogue(result); }));
328 Dart_IsolateFlags isolate_flags;
329 Dart_IsolateFlagsInitialize(&isolate_flags);
330 isolate_flags.null_safety =
true;
332 isolate_ = Dart_CreateIsolateGroup(
333 url_.c_str(), label_.c_str(), isolate_snapshot_data,
334 isolate_snapshot_instructions, &isolate_flags, state, state, &
error);
336 FML_LOG(ERROR) <<
"Dart_CreateIsolateGroup failed: " <<
error;
340 state->get()->SetIsolate(isolate_);
343 [loop = loop_.get()](
auto callback) {
344 async::PostTask(loop->dispatcher(), std::move(
callback));
346 state->get()->message_handler().Initialize(dispatcher);
348 state->get()->SetReturnCodeCallback([
this](uint32_t return_code) {
349 return_code_ = return_code;
350 auto ret_status = return_code == 0 ? fuchsia::test::Status::PASSED
351 : fuchsia::test::Status::FAILED;
352 fuchsia::test::Result result;
353 result.set_status(ret_status);
354 case_listener_->Finished(std::move(result));
361DartTestComponentController::CaseIterator::CaseIterator(
362 fidl::InterfaceRequest<fuchsia::test::CaseIterator> request,
363 async_dispatcher_t* dispatcher,
364 std::string test_component_name,
365 fit::function<
void(CaseIterator*)> done_callback)
367 test_component_name_(test_component_name),
368 done_callback_(
std::move(done_callback)) {}
371void DartTestComponentController::CaseIterator::GetNext(
376 fuchsia::test::Case test_case;
377 test_case.set_name(test_component_name_);
378 test_case.set_enabled(
true);
379 std::vector<fuchsia::test::Case> cases;
380 cases.push_back(std::move(test_case));
386 std::vector<fuchsia::test::Case> cases;
388 done_callback_(
this);
393std::unique_ptr<DartTestComponentController::CaseIterator>
394DartTestComponentController::RemoveCaseInterator(CaseIterator* case_iterator) {
395 auto it = case_iterators_.find(case_iterator);
396 std::unique_ptr<DartTestComponentController::CaseIterator> case_iterator_ptr;
397 if (it != case_iterators_.end()) {
398 case_iterator_ptr = std::move(it->second);
399 case_iterators_.erase(it);
401 return case_iterator_ptr;
406 fidl::InterfaceRequest<fuchsia::test::CaseIterator> iterator) {
407 auto case_iterator = std::make_unique<CaseIterator>(
408 std::move(iterator), loop_->dispatcher(), test_component_name_,
409 [
this](CaseIterator* case_iterator) {
410 RemoveCaseInterator(case_iterator);
412 case_iterators_.emplace(case_iterator.get(), std::move(case_iterator));
417 std::vector<fuchsia::test::Invocation> tests,
418 fuchsia::test::RunOptions options,
419 fidl::InterfaceHandle<fuchsia::test::RunListener> listener) {
420 std::vector<std::string>
args;
421 if (options.has_arguments()) {
422 args = std::move(*options.mutable_arguments());
425 auto listener_proxy = listener.Bind();
431 for (
auto it = tests.begin(); it != tests.end(); it++) {
432 auto invocation = std::move(*it);
433 std::string test_case_name;
434 if (invocation.has_name()) {
435 test_case_name = invocation.name();
440 auto status = zx::socket::create(0, &out_, &out_client_);
441 if (status != ZX_OK) {
442 FML_LOG(FATAL) <<
"cannot create out socket: "
443 << zx_status_get_string(status);
446 status = fdio_fd_create(out_.release(), &stdout_fd_);
447 if (status != ZX_OK) {
448 FML_LOG(FATAL) <<
"failed to extract output fd: "
449 << zx_status_get_string(status);
452 status = zx::socket::create(0, &err_, &err_client_);
453 if (status != ZX_OK) {
454 FML_LOG(FATAL) <<
"cannot create error socket: "
455 << zx_status_get_string(status);
458 status = fdio_fd_create(err_.release(), &stderr_fd_);
459 if (status != ZX_OK) {
460 FML_LOG(FATAL) <<
"failed to extract error fd: "
461 << zx_status_get_string(status);
466 fuchsia::test::StdHandles std_handles;
467 std_handles.set_out(std::move(out_client_));
468 std_handles.set_err(std::move(err_client_));
470 listener_proxy->OnTestCaseStarted(std::move(invocation),
471 std::move(std_handles),
472 case_listener_.NewRequest());
476 auto dart_main_promise = fpromise::make_promise([&] { RunDartMain(); });
478 executor_.schedule_task(std::move(dart_main_promise));
479 while (!dart_state->has_set_return_code()) {
480 loop_->Run(zx::deadline_after(kTestTimeout),
true);
484 listener_proxy->OnFinished();
487 if (binding_.is_bound()) {
495 if (return_code_ == 0) {
496 binding_.Close(ZX_OK);
498 binding_.Close(zx_status_t(fuchsia::component::Error::INTERNAL));
506fpromise::promise<> DartTestComponentController::RunDartMain() {
512 fidl::InterfaceRequest<fuchsia::io::Directory> outgoing_dir =
513 std::move(*start_info_.mutable_outgoing_dir());
515 outgoing_dir.TakeChannel(),
520 char*
error = Dart_IsolateMakeRunnable(isolate_);
521 if (
error !=
nullptr) {
522 Dart_EnterIsolate(isolate_);
523 Dart_ShutdownIsolate();
524 FML_LOG(ERROR) <<
"Unable to make isolate runnable: " <<
error;
526 return fpromise::make_error_promise();
528 Dart_EnterIsolate(isolate_);
532 Dart_Handle corelib = Dart_LookupLibrary(ToDart(
"dart:core"));
533 Dart_Handle string_type =
534 Dart_GetNonNullableType(corelib, ToDart(
"String"), 0, NULL);
535 Dart_Handle dart_arguments =
536 Dart_NewListOfTypeFilled(string_type, Dart_EmptyString(), 0);
538 if (Dart_IsError(dart_arguments)) {
539 FML_LOG(ERROR) <<
"Failed to allocate Dart arguments list: "
540 << Dart_GetError(dart_arguments);
542 return fpromise::make_error_promise();
545 Dart_Handle user_main = Dart_GetField(Dart_RootLibrary(),
ToDart(
"main"));
547 if (Dart_IsError(user_main)) {
548 FML_LOG(ERROR) <<
"Failed to locate user_main in the root library: "
549 << Dart_GetError(user_main);
551 return fpromise::make_error_promise();
554 Dart_Handle fuchsia_lib = Dart_LookupLibrary(
tonic::ToDart(
"dart:fuchsia"));
556 if (Dart_IsError(fuchsia_lib)) {
557 FML_LOG(ERROR) <<
"Failed to locate dart:fuchsia: "
558 << Dart_GetError(fuchsia_lib);
560 return fpromise::make_error_promise();
564 fuchsia_lib,
"_runUserMainForDartRunner", {user_main, dart_arguments});
566 if (Dart_IsError(main_result)) {
568 if (!dart_state->has_set_return_code()) {
570 FML_LOG(ERROR) << Dart_GetError(main_result);
577 return fpromise::make_error_promise();
581 return fpromise::make_ok_promise();
584void DartTestComponentController::Kill() {
585 done_callback_(
this);
588 suite_bindings_.CloseAll();
589 if (Dart_CurrentIsolate()) {
601 Dart_SetMessageNotifyCallback(
nullptr);
603 Dart_ShutdownIsolate();
607void DartTestComponentController::MessageEpilogue(Dart_Handle result) {
612 if (dart_state->has_set_return_code()) {
613 Dart_ShutdownIsolate();
621 if (return_code_ != 0) {
622 Dart_ShutdownIsolate();
626 idle_start_ = zx::clock::get_monotonic();
628 idle_timer_.set(idle_start_ + kIdleWaitDuration, kIdleSlack);
629 if (status != ZX_OK) {
630 FML_LOG(INFO) <<
"Idle timer set failed: " << zx_status_get_string(status);
634void DartTestComponentController::Stop() {
638void DartTestComponentController::OnIdleTimer(async_dispatcher_t* dispatcher,
639 async::WaitBase* wait,
641 const zx_packet_signal* signal) {
642 if ((status != ZX_OK) || !(signal->observed & ZX_TIMER_SIGNALED) ||
643 !Dart_CurrentIsolate()) {
648 zx::time deadline = idle_start_ + kIdleWaitDuration;
649 zx::time now = zx::clock::get_monotonic();
650 if (now >= deadline) {
653 Dart_NotifyIdle((now + kIdleNotifyDuration).get());
654 idle_start_ = zx::time(0);
655 idle_timer_.cancel();
658 zx_status_t status = idle_timer_.set(deadline, kIdleSlack);
659 if (status != ZX_OK) {
660 FML_LOG(INFO) <<
"Idle timer set failed: "
661 << zx_status_get_string(status);
664 wait->Begin(dispatcher);
669 bool method_has_response) {
670 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)