[dart_test_runner] Adapt the dart runner to implement the suite fidl protocol so that it can be used the the v2 testing framework.
diff --git a/shell/platform/fuchsia/dart_runner/BUILD.gn b/shell/platform/fuchsia/dart_runner/BUILD.gn index 3d3fad3..be67387 100644 --- a/shell/platform/fuchsia/dart_runner/BUILD.gn +++ b/shell/platform/fuchsia/dart_runner/BUILD.gn
@@ -34,10 +34,14 @@ "dart_component_controller_v2.h", "dart_runner.cc", "dart_runner.h", + "dart_test_component_controller_v2.cc", + "dart_test_component_controller_v2.h", "logging.h", "main.cc", "service_isolate.cc", "service_isolate.h", + "string_printf.cc", + "string_printf.h", ] defines = extra_defines @@ -61,6 +65,8 @@ "//flutter/shell/platform/fuchsia/dart-pkg/fuchsia", "//flutter/shell/platform/fuchsia/dart-pkg/zircon", "$fuchsia_sdk_root/fidl:fuchsia.component.runner", + "$fuchsia_sdk_root/fidl:fuchsia.logger", + "$fuchsia_sdk_root/fidl:fuchsia.sys.test", "$fuchsia_sdk_root/pkg:async", "$fuchsia_sdk_root/pkg:async-cpp", "$fuchsia_sdk_root/pkg:async-default", @@ -70,10 +76,12 @@ "$fuchsia_sdk_root/pkg:fidl_cpp", "$fuchsia_sdk_root/pkg:sys_inspect_cpp", "$fuchsia_sdk_root/pkg:sys_cpp", + "$fuchsia_sdk_root/pkg:sys_cpp_testing", "$fuchsia_sdk_root/pkg:syslog", "$fuchsia_sdk_root/pkg:trace", "$fuchsia_sdk_root/pkg:trace-provider-so", "$fuchsia_sdk_root/pkg:vfs_cpp", + "$fuchsia_sdk_root/pkg:zx", "//flutter/third_party/tonic", ] + dart_deps + extra_deps }
diff --git a/shell/platform/fuchsia/dart_runner/dart_runner.cc b/shell/platform/fuchsia/dart_runner/dart_runner.cc index fba5805..c089f63 100644 --- a/shell/platform/fuchsia/dart_runner/dart_runner.cc +++ b/shell/platform/fuchsia/dart_runner/dart_runner.cc
@@ -13,25 +13,113 @@ #include <cerrno> #include <memory> +#include <sstream> #include <thread> #include <utility> #include "dart_component_controller.h" #include "dart_component_controller_v2.h" +#include "dart_test_component_controller_v2.h" +#include "flutter/fml/command_line.h" +#include "flutter/fml/logging.h" #include "flutter/fml/trace_event.h" #include "logging.h" #include "runtime/dart/utils/inlines.h" +#include "runtime/dart/utils/program_metadata.h" #include "runtime/dart/utils/vmservice_object.h" #include "service_isolate.h" #include "third_party/dart/runtime/include/bin/dart_io_api.h" #include "third_party/tonic/dart_microtask_queue.h" #include "third_party/tonic/dart_state.h" +// #include "fuchsia_pkg_url.h" + #if defined(AOT_RUNTIME) extern "C" uint8_t _kDartVmSnapshotData[]; extern "C" uint8_t _kDartVmSnapshotInstructions[]; #endif +namespace { +struct ComponentArgs { + std::string legacy_url; + std::shared_ptr<sys::ServiceDirectory> test_component_svc; + fidl::InterfaceHandle<fuchsia::io::Directory> component_pkg; + std::vector<fuchsia::component::runner::ComponentNamespaceEntry> ns; +}; + +// fpromise::result<ComponentArgs, fuchsia::component::Error> GetComponentArgs( +// fuchsia::component::runner::ComponentStartInfo& start_info) { +// // component::FuchsiaPkgUrl url; +// // if (!url.Parse(start_info.resolved_url())) { +// // // FX_LOGS(WARNING) << "cannot run test: " << +// start_info.resolved_url() +// // // << ", as we cannot parse url."; +// // return fpromise::error(fuchsia::component::Error::INVALID_ARGUMENTS); +// // } +// FML_LOG(INFO) << "DART RUNNER : GET COMPONENT ARGS "; +// if (!start_info.program().has_entries()) { +// FML_LOG(INFO) << "cannot run test: " << start_info.resolved_url() +// << ", as it has no program entry."; +// return fpromise::error(fuchsia::component::Error::INVALID_ARGUMENTS); +// } + +// auto ns = std::move(*start_info.mutable_ns()); +// // const std::string& legacy_manifest = it->value->str(); +// FML_LOG(INFO) << "DART RUNNER : GET COMPONENT ARGS 2"; + +// auto pkg_it = std::find_if( +// ns.begin(), ns.end(), +// [](fuchsia::component::runner::ComponentNamespaceEntry& entry) { +// return entry.path() == "/pkg"; +// }); +// FML_LOG(INFO) << "DART RUNNER : GET COMPONENT ARGS 3"; + +// auto component_pkg = std::move(*pkg_it->mutable_directory()); +// FML_LOG(INFO) << "DART RUNNER : GET COMPONENT ARGS 4"; + +// // auto fd = fsl::OpenChannelAsFileDescriptor(component_pkg.TakeChannel()); +// // fsl::SizedVmo vmo; +// // if (!fsl::VmoFromFilenameAt(fd.get(), legacy_manifest, &vmo)) { +// // FX_LOGS(WARNING) << "cannot run test: " << start_info.resolved_url() +// // << ", as cannot read legacy manifest file."; +// // return +// fpromise::error(fuchsia::component::Error::INSTANCE_CANNOT_START); +// // } + +// // const uint64_t size = vmo.size(); +// // std::string cmx_str(size, ' '); +// // auto status = vmo.vmo().read(cmx_str.data(), 0, size); +// // if (status != ZX_OK) { +// // FX_LOGS(WARNING) << "cannot run test: " << start_info.resolved_url() +// // << ", as cannot read legacy manifest file: " +// // << zx_status_get_string(status) << "."; +// // return +// fpromise::error(fuchsia::component::Error::INSTANCE_CANNOT_START); +// // } +// // auto legacy_url = url.package_path() + "#" + legacy_manifest; + +// auto legacy_url = start_info.resolved_url(); +// FML_LOG(INFO) << "DART RUNNER : GET COMPONENT ARGS 5"; +// auto svc_it = std::find_if( +// ns.begin(), ns.end(), +// [](fuchsia::component::runner::ComponentNamespaceEntry& entry) { +// return entry.path() == "/svc"; +// }); + +// FML_LOG(INFO) << "DART RUNNER : GET COMPONENT ARGS 6"; + +// auto component_svc = std::make_shared<sys::ServiceDirectory>( +// std::move(*svc_it->mutable_directory())); +// FML_LOG(INFO) << "DART RUNNER : GET COMPONENT ARGS 7"; +// return fpromise::ok( +// ComponentArgs{.legacy_url = std::move(legacy_url), +// .test_component_svc = std::move(component_svc), +// .component_pkg = std::move(component_pkg), +// .ns = std::move(ns)}); +// } + +} // namespace + namespace dart_runner { namespace { @@ -152,6 +240,39 @@ } } +void RunTestApplicationV2( + DartRunner* runner, + fuchsia::component::runner::ComponentStartInfo start_info, + std::shared_ptr<sys::ServiceDirectory> runner_incoming_services, + fidl::InterfaceRequest<fuchsia::component::runner::ComponentController> + controller, + fit::function<void(std::unique_ptr<DartTestComponentControllerV2>)> + create_callback, + fit::function<void(DartTestComponentControllerV2*)> done_callback) { + const int64_t start = Dart_TimelineGetMicros(); + + auto test_component = std::make_unique<DartTestComponentControllerV2>( + std::move(start_info), runner_incoming_services, std::move(controller), + std::move(done_callback)); + + // DartTestComponentControllerV2 app( + // std::move(start_info), runner_incoming_services, + // std::move(controller)); + test_component->SetUp(); + // const bool success = app.SetUp(); + FML_LOG(INFO) << "DART COMPONENT SET UP: "; + const int64_t end = Dart_TimelineGetMicros(); + Dart_TimelineEvent("DartTestComponentControllerV2::Setup", start, end, + Dart_Timeline_Event_Duration, 0, NULL, NULL); + create_callback(std::move(test_component)); + + // app.Run(); + + if (Dart_CurrentIsolate()) { + Dart_ShutdownIsolate(); + } +} + bool EntropySource(uint8_t* buffer, intptr_t count) { zx_cprng_draw(buffer, count); return true; @@ -159,6 +280,86 @@ } // namespace +// "data" and "assets" are arguments that are specific to the Flutter runner. +// They will likely go away if we migrate to the ELF runner. +constexpr char kDataKey[] = "data"; +constexpr char kAssetsKey[] = "assets"; + +// "args" are how the component specifies arguments to the runner. +constexpr char kArgsKey[] = "args"; +constexpr char kOldGenHeapSizeKey[] = "old_gen_heap_size"; +constexpr char kExposeDirsKey[] = "expose_dirs"; + +// constexpr char kTmpPath[] = "/tmp"; +// constexpr char kServiceRootPath[] = "/svc"; +// constexpr char kRunnerConfigPath[] = "/config/data/flutter_runner_config"; + +/// Parses the |args| field from the "program" field into +/// |metadata|. +void ParseArgs(std::vector<std::string>& args, ProgramMetadata* metadata) { + // fml::CommandLine expects the first argument to be the name of the program, + // so we prepend a dummy argument so we can use fml::CommandLine to parse the + // arguments for us. + std::vector<std::string> command_line_args = {""}; + command_line_args.insert(command_line_args.end(), args.begin(), args.end()); + fml::CommandLine parsed_args = fml::CommandLineFromIterators( + command_line_args.begin(), command_line_args.end()); + + std::string is_test; + if (parsed_args.GetOptionValue("is_test", &is_test)) { + // FML_LOG(INFO) << "HIT"; + // FML_LOG(INFO) << is_test; + metadata->is_test = is_test == "true"; + } + + std::string old_gen_heap_size_option; + if (parsed_args.GetOptionValue(kOldGenHeapSizeKey, + &old_gen_heap_size_option)) { + int64_t specified_old_gen_heap_size = strtol( + old_gen_heap_size_option.c_str(), nullptr /* endptr */, 10 /* base */); + if (specified_old_gen_heap_size != 0) { + metadata->old_gen_heap_size = specified_old_gen_heap_size; + } else { + // FML_LOG(ERROR) << "Invalid old_gen_heap_size: " + // << old_gen_heap_size_option; + } + } + + std::string expose_dirs_option; + if (parsed_args.GetOptionValue(kExposeDirsKey, &expose_dirs_option)) { + // Parse the comma delimited string + std::vector<std::string> expose_dirs; + std::stringstream s(expose_dirs_option); + while (s.good()) { + std::string dir; + getline(s, dir, ','); // get first string delimited by comma + metadata->expose_dirs.push_back(dir); + } + } +} + +ProgramMetadata ParseProgramMetadata( + const fuchsia::data::Dictionary& program_metadata) { + ProgramMetadata result; + + for (const auto& entry : program_metadata.entries()) { + if (entry.key.compare(kDataKey) == 0 && entry.value != nullptr) { + result.data_path = "pkg/" + entry.value->str(); + } else if (entry.key.compare(kAssetsKey) == 0 && entry.value != nullptr) { + result.assets_path = "pkg/" + entry.value->str(); + } else if (entry.key.compare(kArgsKey) == 0 && entry.value != nullptr) { + ParseArgs(entry.value->str_vec(), &result); + } + } + + // assets_path defaults to the same as data_path if omitted. + if (result.assets_path.empty()) { + result.assets_path = result.data_path; + } + + return result; +} + DartRunner::DartRunner(sys::ComponentContext* context) : context_(context) { context_->outgoing()->AddPublicService<fuchsia::sys::Runner>( [this](fidl::InterfaceRequest<fuchsia::sys::Runner> request) { @@ -236,6 +437,7 @@ // move or change, so we make a copy to pass to TRACE_DURATION. // TODO(PT-169): Remove this copy when TRACE_DURATION reads string arguments // eagerly. + FML_LOG(INFO) << "RESOLVED URL V1: " << package.resolved_url; std::string url_copy = package.resolved_url; TRACE_EVENT1("dart", "StartComponent", "url", url_copy.c_str()); std::thread thread(RunApplicationV1, this, std::move(package), @@ -248,11 +450,36 @@ fuchsia::component::runner::ComponentStartInfo start_info, fidl::InterfaceRequest<fuchsia::component::runner::ComponentController> controller) { - std::string url_copy = start_info.resolved_url(); - TRACE_EVENT1("dart", "Start", "url", url_copy.c_str()); - std::thread thread(RunApplicationV2, this, std::move(start_info), - context_->svc(), std::move(controller)); - thread.detach(); + ProgramMetadata metadata = ParseProgramMetadata(start_info.program()); + FML_LOG(INFO) << "RESOLVED URL V2: " << start_info.resolved_url(); + + if (metadata.is_test) { + FML_LOG(INFO) << "RESOLVED URL: " << start_info.resolved_url(); + + std::string url_copy = start_info.resolved_url(); + TRACE_EVENT1("dart", "Start", "url", url_copy.c_str()); + std::thread thread( + RunTestApplicationV2, this, std::move(start_info), context_->svc(), + std::move(controller), + [this](std::unique_ptr<DartTestComponentControllerV2> ptr) { + FML_LOG(INFO) << "TEST COMPONENT SAVED"; + test_components_.emplace(ptr.get(), std::move(ptr)); + }, + [this](DartTestComponentControllerV2* ptr) { + FML_LOG(INFO) << "TEST COMPONENT ERASED"; + auto it = test_components_.find(ptr); + if (it != test_components_.end()) { + test_components_.erase(it); + } + }); + thread.detach(); + } else { + std::string url_copy = start_info.resolved_url(); + TRACE_EVENT1("dart", "Start", "url", url_copy.c_str()); + std::thread thread(RunApplicationV2, this, std::move(start_info), + context_->svc(), std::move(controller)); + thread.detach(); + } } } // namespace dart_runner
diff --git a/shell/platform/fuchsia/dart_runner/dart_runner.h b/shell/platform/fuchsia/dart_runner/dart_runner.h index ce47eec..5688ca5 100644 --- a/shell/platform/fuchsia/dart_runner/dart_runner.h +++ b/shell/platform/fuchsia/dart_runner/dart_runner.h
@@ -10,7 +10,9 @@ #include <lib/fidl/cpp/binding_set.h> #include <lib/sys/cpp/component_context.h> +#include "dart_test_component_controller_v2.h" #include "runtime/dart/utils/mapped_resource.h" +// #include "test_component.h" namespace dart_runner { @@ -33,12 +35,18 @@ fidl::InterfaceRequest<fuchsia::component::runner::ComponentController> controller) override; + std::map<DartTestComponentControllerV2*, + std::shared_ptr<DartTestComponentControllerV2>> + test_components_; + // Not owned by DartRunner. sys::ComponentContext* context_; fidl::BindingSet<fuchsia::sys::Runner> bindings_; fidl::BindingSet<fuchsia::component::runner::ComponentRunner> component_runner_bindings_; + std::shared_ptr<sys::ServiceDirectory> svc_; + #if !defined(AOT_RUNTIME) dart_utils::MappedResource vm_snapshot_data_; dart_utils::MappedResource vm_snapshot_instructions_;
diff --git a/shell/platform/fuchsia/dart_runner/dart_test_component_controller_v2.cc b/shell/platform/fuchsia/dart_runner/dart_test_component_controller_v2.cc new file mode 100644 index 0000000..30136a7 --- /dev/null +++ b/shell/platform/fuchsia/dart_runner/dart_test_component_controller_v2.cc
@@ -0,0 +1,690 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "dart_test_component_controller_v2.h" + +#include <fcntl.h> +#include <fml/logging.h> +#include <fuchsia/sys/test/cpp/fidl.h> +#include <lib/async-loop/loop.h> +#include <lib/async/cpp/task.h> +#include <lib/async/default.h> +#include <lib/fdio/directory.h> +#include <lib/fdio/fd.h> +#include <lib/fdio/namespace.h> +#include <lib/fidl/cpp/string.h> +#include <lib/fpromise/barrier.h> +#include <lib/sys/cpp/service_directory.h> +#include <lib/syslog/global.h> +#include <lib/zx/clock.h> +#include <lib/zx/thread.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <zircon/status.h> + +#include <regex> +#include <utility> + +#include "runtime/dart/utils/files.h" +#include "runtime/dart/utils/handle_exception.h" +#include "runtime/dart/utils/inlines.h" +#include "runtime/dart/utils/tempfs.h" +#include "third_party/dart/runtime/include/dart_tools_api.h" +#include "third_party/tonic/converter/dart_converter.h" +#include "third_party/tonic/dart_message_handler.h" +#include "third_party/tonic/dart_microtask_queue.h" +#include "third_party/tonic/dart_state.h" +#include "third_party/tonic/logging/dart_error.h" +#include "third_party/tonic/logging/dart_invoke.h" + +#include "builtin_libraries.h" +#include "logging.h" + +using tonic::ToDart; + +namespace dart_runner { + +namespace { + +constexpr char kTmpPath[] = "/tmp"; + +constexpr zx::duration kIdleWaitDuration = zx::sec(2); +constexpr zx::duration kIdleNotifyDuration = zx::msec(500); +constexpr zx::duration kIdleSlack = zx::sec(1); + +void AfterTask(async_loop_t*, void*) { + tonic::DartMicrotaskQueue* queue = + tonic::DartMicrotaskQueue::GetForCurrentThread(); + // Verify that the queue exists, as this method could have been called back as + // part of the exit routine, after the destruction of the microtask queue. + if (queue) { + queue->RunMicrotasks(); + } +} + +constexpr async_loop_config_t kLoopConfig = { + .default_accessors = + { + .getter = async_get_default_dispatcher, + .setter = async_set_default_dispatcher, + }, + .make_default_for_current_thread = true, + .epilogue = &AfterTask, +}; + +// Find the last path of the component. +// fuchsia-pkg://fuchsia.com/hello_dart#meta/hello_dart.cmx -> hello_dart.cmx +std::string GetLabelFromUrl(const std::string& url) { + for (size_t i = url.length() - 1; i > 0; i--) { + if (url[i] == '/') { + return url.substr(i + 1, url.length() - 1); + } + } + return url; +} + +// Find the name of the component. +// fuchsia-pkg://fuchsia.com/hello_dart#meta/hello_dart.cm -> hello_dart +std::string GetComponentNameFromUrl(const std::string& url) { + const std::string label = GetLabelFromUrl(url); + for (size_t i = 0; i < label.length(); ++i) { + if (label[i] == '.') { + return label.substr(0, i); + } + } + return label; +} + +} // namespace + +DartTestComponentControllerV2::DartTestComponentControllerV2( + fuchsia::component::runner::ComponentStartInfo start_info, + std::shared_ptr<sys::ServiceDirectory> runner_incoming_services, + fidl::InterfaceRequest<fuchsia::component::runner::ComponentController> + controller, + DoneCallback done_callback) + : loop_(new async::Loop(&kLoopConfig)), + label_(GetLabelFromUrl(start_info.resolved_url())), + url_(std::move(start_info.resolved_url())), + runner_incoming_services_(runner_incoming_services), + start_info_(std::move(start_info)), + binding_(this), + done_callback_(std::move(done_callback)) { + // TODO(fxb/84537): This data path is configured based how we build Flutter + // applications in tree currently, but the way we build the Flutter + // application may change. We should avoid assuming the data path and let the + // CML file specify this data path instead. + const std::string component_name = GetComponentNameFromUrl(url_); + FX_LOGF(INFO, LOG_TAG, "Component Name from URL: %s.", + component_name.c_str()); + data_path_ = "pkg/data/" + component_name; + + if (controller.is_valid()) { + binding_.Bind(std::move(controller)); + binding_.set_error_handler([this](zx_status_t status) { Kill(); }); + } else { + FX_LOG(ERROR, LOG_TAG, + "Fuchsia component controller endpoint is not valid."); + } +} + +DartTestComponentControllerV2::~DartTestComponentControllerV2() { + if (namespace_) { + fdio_ns_destroy(namespace_); + namespace_ = nullptr; + } + close(stdout_fd_); + close(stderr_fd_); +} + +void DartTestComponentControllerV2::SetUp() { + // Name the thread after the url of the component being launched. + zx::thread::self()->set_property(ZX_PROP_NAME, label_.c_str(), label_.size()); + Dart_SetThreadName(label_.c_str()); + + if (!CreateAndBindNamespace()) { + return; + } + + if (SetUpFromAppSnapshot()) { + FX_LOGF(INFO, LOG_TAG, "%s is running from an app snapshot", url_.c_str()); + } else if (SetUpFromKernel()) { + FX_LOGF(INFO, LOG_TAG, "%s is running from kernel", url_.c_str()); + } else { + FX_LOGF(ERROR, LOG_TAG, "Failed to set up component controller for %s.", + url_.c_str()); + return; + // return false; + } + + // return true; + // suite_ = std::make_unique<Suite>(url_, loop_); + suite_context_ = sys::ComponentContext::Create(); + suite_context_->outgoing()->AddPublicService( + suite_bindings_.GetHandler(this, loop_->dispatcher())); + suite_context_->outgoing()->Serve( + start_info_.mutable_outgoing_dir()->TakeChannel(), loop_->dispatcher()); +} + +bool DartTestComponentControllerV2::CreateAndBindNamespace() { + if (!start_info_.has_ns()) { + FX_LOG(ERROR, LOG_TAG, "Component start info does not have a namespace."); + return false; + } + + const zx_status_t ns_create_status = fdio_ns_create(&namespace_); + if (ns_create_status != ZX_OK) { + FX_LOGF(ERROR, LOG_TAG, "Failed to create namespace: %s", + zx_status_get_string(ns_create_status)); + } + + dart_utils::RunnerTemp::SetupComponent(namespace_); + + // Bind each directory in start_info's namespace to the controller's namespace + // instance. + for (auto& ns_entry : *start_info_.mutable_ns()) { + // TODO(akbiggs): Under what circumstances does a namespace entry not + // have a path or directory? Should we log an error for these? + if (!ns_entry.has_path() || !ns_entry.has_directory()) { + continue; + } + + if (ns_entry.path() == kTmpPath) { + // /tmp is covered by the local memfs. + continue; + } + + // We move ownership of the directory & path since RAII is used to keep + // the handle open. + fidl::InterfaceHandle<::fuchsia::io::Directory> dir = + std::move(*ns_entry.mutable_directory()); + const std::string path = std::move(*ns_entry.mutable_path()); + + const zx_status_t ns_bind_status = + fdio_ns_bind(namespace_, path.c_str(), dir.TakeChannel().release()); + if (ns_bind_status != ZX_OK) { + FX_LOGF(ERROR, LOG_TAG, "Failed to bind %s to namespace: %s", + path.c_str(), zx_status_get_string(ns_bind_status)); + return false; + } + } + + return true; +} + +bool DartTestComponentControllerV2::SetUpFromKernel() { + dart_utils::MappedResource manifest; + if (!dart_utils::MappedResource::LoadFromNamespace( + namespace_, data_path_ + "/app.dilplist", manifest)) { + return false; + } + + if (!dart_utils::MappedResource::LoadFromNamespace( + nullptr, "/pkg/data/isolate_core_snapshot_data.bin", + isolate_snapshot_data_)) { + return false; + } + if (!dart_utils::MappedResource::LoadFromNamespace( + nullptr, "/pkg/data/isolate_core_snapshot_instructions.bin", + isolate_snapshot_instructions_, true /* executable */)) { + return false; + } + + if (!CreateIsolate(isolate_snapshot_data_.address(), + isolate_snapshot_instructions_.address())) { + return false; + } + + Dart_EnterScope(); + + std::string str(reinterpret_cast<const char*>(manifest.address()), + manifest.size()); + Dart_Handle library = Dart_Null(); + for (size_t start = 0; start < manifest.size();) { + size_t end = str.find("\n", start); + if (end == std::string::npos) { + FX_LOG(ERROR, LOG_TAG, "Malformed manifest"); + Dart_ExitScope(); + return false; + } + + std::string path = data_path_ + "/" + str.substr(start, end - start); + start = end + 1; + + dart_utils::MappedResource kernel; + if (!dart_utils::MappedResource::LoadFromNamespace(namespace_, path, + kernel)) { + FX_LOGF(ERROR, LOG_TAG, "Cannot load kernel from namespace: %s", + path.c_str()); + Dart_ExitScope(); + return false; + } + library = Dart_LoadLibraryFromKernel(kernel.address(), kernel.size()); + if (Dart_IsError(library)) { + FX_LOGF(ERROR, LOG_TAG, "Cannot load library from kernel: %s", + Dart_GetError(library)); + Dart_ExitScope(); + return false; + } + + kernel_peices_.emplace_back(std::move(kernel)); + } + Dart_SetRootLibrary(library); + + Dart_Handle result = Dart_FinalizeLoading(false); + if (Dart_IsError(result)) { + FX_LOGF(ERROR, LOG_TAG, "Failed to FinalizeLoading: %s", + Dart_GetError(result)); + Dart_ExitScope(); + return false; + } + + return true; +} + +bool DartTestComponentControllerV2::SetUpFromAppSnapshot() { +#if !defined(AOT_RUNTIME) + return false; +#else + // Load the ELF snapshot as available, and fall back to a blobs snapshot + // otherwise. + const uint8_t *isolate_data, *isolate_instructions; + if (elf_snapshot_.Load(namespace_, data_path_ + "/app_aot_snapshot.so")) { + isolate_data = elf_snapshot_.IsolateData(); + isolate_instructions = elf_snapshot_.IsolateInstrs(); + if (isolate_data == nullptr || isolate_instructions == nullptr) { + return false; + } + } else { + if (!dart_utils::MappedResource::LoadFromNamespace( + namespace_, data_path_ + "/isolate_snapshot_data.bin", + isolate_snapshot_data_)) { + return false; + } + if (!dart_utils::MappedResource::LoadFromNamespace( + namespace_, data_path_ + "/isolate_snapshot_instructions.bin", + isolate_snapshot_instructions_, true /* executable */)) { + return false; + } + } + return CreateIsolate(isolate_data, isolate_instructions); +#endif // defined(AOT_RUNTIME) +} + +bool DartTestComponentControllerV2::CreateIsolate( + const uint8_t* isolate_snapshot_data, + const uint8_t* isolate_snapshot_instructions) { + // Create the isolate from the snapshot. + char* error = nullptr; + + // TODO(dart_runner): Pass if we start using tonic's loader. + intptr_t namespace_fd = -1; + + // Freed in IsolateShutdownCallback. + auto state = new std::shared_ptr<tonic::DartState>(new tonic::DartState( + namespace_fd, [this](Dart_Handle result) { MessageEpilogue(result); })); + + isolate_ = Dart_CreateIsolateGroup( + url_.c_str(), label_.c_str(), isolate_snapshot_data, + isolate_snapshot_instructions, nullptr /* flags */, state, state, &error); + if (!isolate_) { + FX_LOGF(ERROR, LOG_TAG, "Dart_CreateIsolateGroup failed: %s", error); + return false; + } + + state->get()->SetIsolate(isolate_); + + tonic::DartMessageHandler::TaskDispatcher dispatcher = + [loop = loop_.get()](auto callback) { + async::PostTask(loop->dispatcher(), std::move(callback)); + }; + state->get()->message_handler().Initialize(dispatcher); + + state->get()->SetReturnCodeCallback( + [this](uint32_t return_code) { return_code_ = return_code; }); + + return true; +} + +// void DartTestComponentControllerV2::Run() { +// async::PostTask(loop_->dispatcher(), [loop = loop_.get(), app = this] { +// if (!app->RunDartMain()) { +// loop->Quit(); +// } +// }); +// loop_->Run(); + +// if (binding_.is_bound()) { +// // From the documentation for ComponentController, ZX_OK should be sent +// when +// // the ComponentController receives a termination request. However, if +// the +// // component exited with a non-zero return code, we indicate this by +// sending +// // an INTERNAL epitaph instead. +// // +// // TODO(fxb/86666): Communicate return code from the ComponentController +// // once v2 has support. +// if (return_code_ == 0) { +// binding_.Close(ZX_OK); +// } else { +// FML_LOG(ERROR) << "Component exited with non-zero return code: " +// << return_code_; +// binding_.Close(zx_status_t(fuchsia::component::Error::INTERNAL)); +// } +// } +// } + +// |fuchsia::test::Suite| +DartTestComponentControllerV2::CaseIterator::CaseIterator( + fidl::InterfaceRequest<fuchsia::sys::test::CaseIterator> request, + async_dispatcher_t* dispatcher, + fit::function<void(CaseIterator*)> done_callback) + : binding_(this, std::move(request), dispatcher), + done_callback_(std::move(done_callback)) {} + +static const char kTestCaseName[] = "dart_test"; +void DartTestComponentControllerV2::CaseIterator::GetNext( + GetNextCallback callback) { + FML_LOG(INFO) << "DartTestComponentControllerV2 GetNext() called"; + if (get_next_call_count == 0) { + fuchsia::sys::test::Case test_case; + test_case.set_name(std::string(kTestCaseName)); + test_case.set_enabled(true); + std::vector<fuchsia::sys::test::Case> cases; + cases.push_back(std::move(test_case)); + callback(std::move(cases)); + get_next_call_count++; + + } else { + std::vector<fuchsia::sys::test::Case> cases; + callback(std::move(cases)); + // this would be removed + done_callback_(this); + } +} + +void DartTestComponentControllerV2::GetTests( + fidl::InterfaceRequest<fuchsia::sys::test::CaseIterator> iterator) { + FML_LOG(INFO) << "DART RUNNER HIT SUITE GetTests()"; + + auto case_iterator = + std::make_unique<CaseIterator>(std::move(iterator), loop_->dispatcher(), + [this](CaseIterator* case_iterator) { + RemoveCaseInterator(case_iterator); + }); + case_iterators_.emplace(case_iterator.get(), std::move(case_iterator)); +} + +std::unique_ptr<DartTestComponentControllerV2::CaseIterator> +DartTestComponentControllerV2::RemoveCaseInterator( + CaseIterator* case_iterator) { + auto it = case_iterators_.find(case_iterator); + std::unique_ptr<DartTestComponentControllerV2::CaseIterator> + case_iterator_ptr; + if (it != case_iterators_.end()) { + case_iterator_ptr = std::move(it->second); + case_iterators_.erase(it); + } + return case_iterator_ptr; +} + +// |fuchsia::test::Suite| +void DartTestComponentControllerV2::Run( + std::vector<fuchsia::sys::test::Invocation> tests, + fuchsia::sys::test::RunOptions options, + fidl::InterfaceHandle<fuchsia::sys::test::RunListener> listener) { + FML_LOG(INFO) << "DART RUNNER HIT SUITE Run()"; + + std::vector<std::string> args; + if (options.has_arguments()) { + args = std::move(*options.mutable_arguments()); + } + + if (options.has_parallel()) { + FML_LOG(WARNING) << "Ignoring 'parallel'. Pass test specific flags, eg: " + "for rust test pass in " + "--test-threads=" + << options.parallel(); + } + + auto listener_proxy = listener.Bind(); + fpromise::barrier barrier; + for (auto it = tests.begin(); it != tests.end(); it++) { + auto invocation = std::move(*it); + std::string test_case_name; + if (invocation.has_name()) { + test_case_name = invocation.name(); + } + FML_LOG(INFO) << "DART RUNNER HIT SUITE RUN - TEST CASE: " + << test_case_name; + zx::socket out, err, out_client, err_client; + auto status = zx::socket::create(0, &out, &out_client); + if (status != ZX_OK) { + FML_LOG(FATAL) << "cannot create socket: " + << zx_status_get_string(status); + } + status = zx::socket::create(0, &err, &err_client); + if (status != ZX_OK) { + FML_LOG(FATAL) << "cannot create socket: " + << zx_status_get_string(status); + } + + fidl::InterfacePtr<fuchsia::sys::test::CaseListener> case_listener; + + fuchsia::sys::test::StdHandles std_handles; + std_handles.set_err(std::move(err_client)); + std_handles.set_out(std::move(out_client)); + + listener_proxy->OnTestCaseStarted(std::move(invocation), + std::move(std_handles), + case_listener.NewRequest()); + + async::PostTask(loop_->dispatcher(), [loop = loop_.get(), app = this] { + if (!app->RunDartMain()) { + loop->Quit(); + } + }); + loop_->Run(); + + auto ret_status = return_code_ == 0 ? fuchsia::sys::test::Status::PASSED + : fuchsia::sys::test::Status::FAILED; + fuchsia::sys::test::Result result; + result.set_status(ret_status); + FML_LOG(INFO) << "Sending finished event for dart tests"; + case_listener->Finished(std::move(result)); + } + + if (binding_.is_bound()) { + // From the documentation for ComponentController, ZX_OK should be sent when + // the ComponentController receives a termination request. However, if the + // component exited with a non-zero return code, we indicate this by sending + // an INTERNAL epitaph instead. + // + // TODO(fxb/86666): Communicate return code from the ComponentController + // once v2 has support. + if (return_code_ == 0) { + binding_.Close(ZX_OK); + } else { + FML_LOG(ERROR) << "Component exited with non-zero return code: " + << return_code_; + binding_.Close(zx_status_t(fuchsia::component::Error::INTERNAL)); + } + } +} + +bool DartTestComponentControllerV2::RunDartMain() { + FML_CHECK(namespace_ != nullptr); + Dart_EnterScope(); + + tonic::DartMicrotaskQueue::StartForCurrentThread(); + + // TODO(fxb/88384): Create a file descriptor for each component that is + // launched and listen for anything that is written to the component. When + // something is written to the component, forward that message along to the + // Fuchsia logger and decorate it with the tag that it came from the + // component. + stdout_fd_ = fileno(stdout); + stderr_fd_ = fileno(stderr); + + fidl::InterfaceRequest<fuchsia::io::Directory> outgoing_dir = + std::move(*start_info_.mutable_outgoing_dir()); + FML_LOG(INFO) << "DART TEST COMPONENT CONTROLLER: RUNMAIN()"; + FML_LOG(INFO) << url_; + InitBuiltinLibrariesForIsolate( + url_, namespace_, stdout_fd_, stderr_fd_, nullptr /* environment */, + outgoing_dir.TakeChannel(), false /* service_isolate */); + + Dart_ExitScope(); + Dart_ExitIsolate(); + char* error = Dart_IsolateMakeRunnable(isolate_); + if (error != nullptr) { + Dart_EnterIsolate(isolate_); + Dart_ShutdownIsolate(); + FX_LOGF(ERROR, LOG_TAG, "Unable to make isolate runnable: %s", error); + free(error); + return false; + } + Dart_EnterIsolate(isolate_); + Dart_EnterScope(); + + // TODO(fxb/88383): Support argument passing. + Dart_Handle corelib = Dart_LookupLibrary(ToDart("dart:core")); + Dart_Handle string_type = + Dart_GetNonNullableType(corelib, ToDart("String"), 0, NULL); + Dart_Handle dart_arguments = + Dart_NewListOfTypeFilled(string_type, Dart_EmptyString(), 0); + + if (Dart_IsError(dart_arguments)) { + FX_LOGF(ERROR, LOG_TAG, "Failed to allocate Dart arguments list: %s", + Dart_GetError(dart_arguments)); + Dart_ExitScope(); + return false; + } + + Dart_Handle user_main = Dart_GetField(Dart_RootLibrary(), ToDart("main")); + + if (Dart_IsError(user_main)) { + FX_LOGF(ERROR, LOG_TAG, + "Failed to locate user_main in the root library: %s", + Dart_GetError(user_main)); + Dart_ExitScope(); + return false; + } + + Dart_Handle fuchsia_lib = Dart_LookupLibrary(tonic::ToDart("dart:fuchsia")); + + if (Dart_IsError(fuchsia_lib)) { + FX_LOGF(ERROR, LOG_TAG, "Failed to locate dart:fuchsia: %s", + Dart_GetError(fuchsia_lib)); + Dart_ExitScope(); + return false; + } + + Dart_Handle main_result = tonic::DartInvokeField( + fuchsia_lib, "_runUserMainForDartRunner", {user_main, dart_arguments}); + + if (Dart_IsError(main_result)) { + auto dart_state = tonic::DartState::Current(); + if (!dart_state->has_set_return_code()) { + // The program hasn't set a return code meaning this exit is unexpected. + FX_LOG(ERROR, LOG_TAG, Dart_GetError(main_result)); + return_code_ = tonic::GetErrorExitCode(main_result); + + dart_utils::HandleIfException(runner_incoming_services_, url_, + main_result); + } + Dart_ExitScope(); + return false; + } + + Dart_ExitScope(); + return true; +} + +void DartTestComponentControllerV2::Kill() { + FML_LOG(INFO) << "DartTestComponentControllerV2 KILL CALLED"; + if (Dart_CurrentIsolate()) { + tonic::DartMicrotaskQueue* queue = + tonic::DartMicrotaskQueue::GetForCurrentThread(); + if (queue) { + queue->Destroy(); + } + + loop_->Quit(); + + // TODO(rosswang): The docs warn of threading issues if doing this again, + // but without this, attempting to shut down the isolate finalizes app + // contexts that can't tell a shutdown is in progress and so fatal. + Dart_SetMessageNotifyCallback(nullptr); + + Dart_ShutdownIsolate(); + } + done_callback_(this); +} + +void DartTestComponentControllerV2::MessageEpilogue(Dart_Handle result) { + auto dart_state = tonic::DartState::Current(); + // If the Dart program has set a return code, then it is intending to shut + // down by way of a fatal error, and so there is no need to override + // return_code_. + if (dart_state->has_set_return_code()) { + Dart_ShutdownIsolate(); + return; + } + + dart_utils::HandleIfException(runner_incoming_services_, url_, result); + + // Otherwise, see if there was any other error. + return_code_ = tonic::GetErrorExitCode(result); + if (return_code_ != 0) { + Dart_ShutdownIsolate(); + return; + } + + idle_start_ = zx::clock::get_monotonic(); + zx_status_t status = + idle_timer_.set(idle_start_ + kIdleWaitDuration, kIdleSlack); + if (status != ZX_OK) { + FX_LOGF(INFO, LOG_TAG, "Idle timer set failed: %s", + zx_status_get_string(status)); + } +} + +void DartTestComponentControllerV2::Stop() { + Kill(); +} + +void DartTestComponentControllerV2::OnIdleTimer( + async_dispatcher_t* dispatcher, + async::WaitBase* wait, + zx_status_t status, + const zx_packet_signal* signal) { + if ((status != ZX_OK) || !(signal->observed & ZX_TIMER_SIGNALED) || + !Dart_CurrentIsolate()) { + // Timer closed or isolate shutdown. + return; + } + + zx::time deadline = idle_start_ + kIdleWaitDuration; + zx::time now = zx::clock::get_monotonic(); + if (now >= deadline) { + // No Dart message has been processed for kIdleWaitDuration: assume we'll + // stay idle for kIdleNotifyDuration. + Dart_NotifyIdle((now + kIdleNotifyDuration).get()); + idle_start_ = zx::time(0); + idle_timer_.cancel(); // De-assert signal. + } else { + // Early wakeup or message pushed idle time forward: reschedule. + zx_status_t status = idle_timer_.set(deadline, kIdleSlack); + if (status != ZX_OK) { + FX_LOGF(INFO, LOG_TAG, "Idle timer set failed: %s", + zx_status_get_string(status)); + } + } + wait->Begin(dispatcher); // ignore errors +} + +} // namespace dart_runner
diff --git a/shell/platform/fuchsia/dart_runner/dart_test_component_controller_v2.h b/shell/platform/fuchsia/dart_runner/dart_test_component_controller_v2.h new file mode 100644 index 0000000..c15d498 --- /dev/null +++ b/shell/platform/fuchsia/dart_runner/dart_test_component_controller_v2.h
@@ -0,0 +1,166 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_DART_TEST_COMPONENT_CONTROLLER_V2_H_ +#define FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_DART_TEST_COMPONENT_CONTROLLER_V2_H_ + +#include <memory> + +#include <fuchsia/component/runner/cpp/fidl.h> +#include <fuchsia/sys/cpp/fidl.h> +#include <fuchsia/sys/test/cpp/fidl.h> +#include <lib/async-loop/cpp/loop.h> +#include <lib/async/cpp/wait.h> +#include <lib/fdio/namespace.h> +#include <lib/sys/cpp/component_context.h> +#include <lib/sys/cpp/service_directory.h> +#include <lib/zx/timer.h> + +#include <lib/fidl/cpp/binding_set.h> +#include "lib/fidl/cpp/binding.h" +#include "runtime/dart/utils/mapped_resource.h" +#include "third_party/dart/runtime/include/dart_api.h" + +// #include "suite.h" +namespace dart_runner { + +// struct TestComponentArgs { +// std::string legacy_url; +// zx::channel outgoing_dir; +// fuchsia::sys::EnvironmentPtr parent_env; +// std::shared_ptr<sys::ServiceDirectory> parent_env_svc; +// std::shared_ptr<sys::ServiceDirectory> test_component_svc; + +// async_dispatcher_t* dispatcher; +// }; + +/// Starts a Dart component written in CFv2. +class DartTestComponentControllerV2 + : public fuchsia::component::runner::ComponentController, + public fuchsia::sys::test::Suite { + using DoneCallback = fit::function<void(DartTestComponentControllerV2*)>; + + public: + DartTestComponentControllerV2( + fuchsia::component::runner::ComponentStartInfo start_info, + std::shared_ptr<sys::ServiceDirectory> runner_incoming_services, + fidl::InterfaceRequest<fuchsia::component::runner::ComponentController> + controller, + DoneCallback done_callback); + + ~DartTestComponentControllerV2() override; + + /// Sets up the controller. + /// + /// This should be called before |Run|. + void SetUp(); + + /// Runs the Dart component in a task, sending the return code back to + /// the Fuchsia component controller. + /// + /// This should be called after |SetUp|. + // void Run(); + + /// |Suite| protocol implementation. + void GetTests(fidl::InterfaceRequest<fuchsia::sys::test::CaseIterator> + iterator) override; + + /// |Suite| protocol implementation. + void Run( + std::vector<fuchsia::sys::test::Invocation> tests, + fuchsia::sys::test::RunOptions options, + fidl::InterfaceHandle<fuchsia::sys::test::RunListener> listener) override; + + private: + /// Helper for actually running the Dart main. Returns true if successful, + /// false otherwise. + bool RunDartMain(); + + /// Creates and binds the namespace for this component. Returns true if + /// successful, false otherwise. + bool CreateAndBindNamespace(); + + bool SetUpFromKernel(); + bool SetUpFromAppSnapshot(); + + bool CreateIsolate(const uint8_t* isolate_snapshot_data, + const uint8_t* isolate_snapshot_instructions); + + // |ComponentController| + void Kill() override; + void Stop() override; + + // Idle notification. + void MessageEpilogue(Dart_Handle result); + void OnIdleTimer(async_dispatcher_t* dispatcher, + async::WaitBase* wait, + zx_status_t status, + const zx_packet_signal* signal); + + class CaseIterator final : public fuchsia::sys::test::CaseIterator { + public: + CaseIterator( + fidl::InterfaceRequest<fuchsia::sys::test::CaseIterator> request, + async_dispatcher_t* dispatcher, + fit::function<void(CaseIterator*)> done_callback); + + void GetNext(GetNextCallback callback) override; + + private: + int get_next_call_count = 0; + fidl::Binding<fuchsia::sys::test::CaseIterator> binding_; + fit::function<void(CaseIterator*)> done_callback_; + }; + + std::unique_ptr<CaseIterator> RemoveCaseInterator(CaseIterator*); + + // |Suite| + // async_dispatcher_t* dispatcher_; + std::unique_ptr<Suite> suite_; + std::map<CaseIterator*, std::unique_ptr<CaseIterator>> case_iterators_; + /// Exposes suite protocol on behalf of test component. + std::unique_ptr<sys::ComponentContext> suite_context_; + fidl::BindingSet<fuchsia::sys::test::Suite> suite_bindings_; + + // The loop must be the first declared member so that it gets destroyed after + // binding_ which expects the existence of a loop. + std::unique_ptr<async::Loop> loop_; + + std::string label_; + std::string url_; + std::shared_ptr<sys::ServiceDirectory> runner_incoming_services_; + std::string data_path_; + std::unique_ptr<sys::ComponentContext> context_; + + fuchsia::component::runner::ComponentStartInfo start_info_; + fidl::Binding<fuchsia::component::runner::ComponentController> binding_; + DoneCallback done_callback_; + + fdio_ns_t* namespace_ = nullptr; + int stdout_fd_ = -1; + int stderr_fd_ = -1; + + dart_utils::ElfSnapshot elf_snapshot_; // AOT snapshot + dart_utils::MappedResource isolate_snapshot_data_; // JIT snapshot + dart_utils::MappedResource isolate_snapshot_instructions_; // JIT snapshot + std::vector<dart_utils::MappedResource> kernel_peices_; + + Dart_Isolate isolate_; + int32_t return_code_ = 0; + + zx::time idle_start_{0}; + zx::timer idle_timer_; + async::WaitMethod<DartTestComponentControllerV2, + &DartTestComponentControllerV2::OnIdleTimer> + idle_wait_{this}; + + // Disallow copy and assignment. + DartTestComponentControllerV2(const DartTestComponentControllerV2&) = delete; + DartTestComponentControllerV2& operator=( + const DartTestComponentControllerV2&) = delete; +}; + +} // namespace dart_runner + +#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_DART_TEST_COMPONENT_CONTROLLER_V2_H_
diff --git a/shell/platform/fuchsia/dart_runner/fuchsia_pkg_url.cc b/shell/platform/fuchsia/dart_runner/fuchsia_pkg_url.cc new file mode 100644 index 0000000..8ac5ad2 --- /dev/null +++ b/shell/platform/fuchsia/dart_runner/fuchsia_pkg_url.cc
@@ -0,0 +1,122 @@ +// Copyright 2022 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "fuchsia_pkg_url.h" + +// #include <re2/re2.h> +#include <regex> +#include <string> +#include <string_view> + +namespace component { + +// Subsitutue functions copied from fxl. +static std::string SubstituteWithArray(std::string_view format, + std::string_view* args, + size_t nargs); + +std::string Substitute(std::string_view format, std::string_view arg0) { + std::array<std::string_view, 1> arr = {arg0}; + return SubstituteWithArray(format, arr.data(), arr.size()); +} + +std::string Substitute(std::string_view format, + std::string_view arg0, + std::string_view arg1) { + std::array<std::string_view, 2> arr = {arg0, arg1}; + return SubstituteWithArray(format, arr.data(), arr.size()); +} + +std::string Substitute(std::string_view format, + std::string_view arg0, + std::string_view arg1, + std::string_view arg2) { + std::array<std::string_view, 3> arr = {arg0, arg1, arg2}; + return SubstituteWithArray(format, arr.data(), arr.size()); +} + +static const std::string kFuchsiaPkgPrefix = "fuchsia-pkg://"; + +// kFuchsiaPkgRexp has the following group matches: +// 1: user/domain/port/etc (everything after scheme, before path) +// 2: package name +// 3: package variant +// 4: package merkle-root hash +// 5: resource path +static const std::regex* kFuchsiaPkgRexp = std::regex( + "^fuchsia-pkg://([^/]+)/([^/#?]+)(?:/([^/" + "#?]+))?(?:\\?hash=([^&#]+))?(?:#(.+))?$"); + +// static +bool FuchsiaPkgUrl::IsFuchsiaPkgScheme(const std::string& url) { + return url.compare(0, kFuchsiaPkgPrefix.length(), kFuchsiaPkgPrefix) == 0; +} + +std::string FuchsiaPkgUrl::GetDefaultComponentCmxPath() const { + return fxl::Substitute("meta/$0.cmx", package_name()); +} + +bool FuchsiaPkgUrl::Parse(const std::string& url) { + package_name_.clear(); + resource_path_.clear(); + + // if (!re2::RE2::FullMatch(url, *kFuchsiaPkgRexp, &host_name_, + // &package_name_, + // &variant_, &hash_, &resource_path_)) { + // return false; + // } + + char* cstr = new char[url.size()]; + std::copy(url.begin(), url.end(), cstr); + + std::cmatch cm; + bool full_match = std::regex_match(cstr, cm, kFuchsiaPkgRexp); + if (!full_match) { + return false; + } + + host_name_ = cm[1]; + package_name_ = cm[2]; + variant_ = cm[3]; + hash_ = cm[4]; + resource_path_ = cm[5]; + + url_ = url; + + if (variant_.empty()) { + // TODO(fxbug.dev/4002): Currently this defaults to "0" if not present, but + // variant will eventually be required in fuchsia-pkg URLs. + variant_ = "0"; + } + + return true; +} + +bool FuchsiaPkgUrl::operator==(const FuchsiaPkgUrl& rhs) const { + return (this->host_name() == rhs.host_name() && + this->package_name() == rhs.package_name() && + this->variant() == rhs.variant() && + this->resource_path() == rhs.resource_path() && + this->hash() == rhs.hash()); +} + +std::string FuchsiaPkgUrl::pkgfs_dir_path() const { + return fxl::Substitute("/pkgfs/packages/$0/$1", package_name_, variant_); +} + +std::string FuchsiaPkgUrl::package_path() const { + std::string query = ""; + if (!hash_.empty()) { + query = fxl::Substitute("?hash=$0", hash_); + } + + return fxl::Substitute("fuchsia-pkg://$0/$1/$2$3", host_name_, package_name_, + variant_, query); +} + +const std::string& FuchsiaPkgUrl::ToString() const { + return url_; +} + +} // namespace component
diff --git a/shell/platform/fuchsia/dart_runner/fuchsia_pkg_url.h b/shell/platform/fuchsia/dart_runner/fuchsia_pkg_url.h new file mode 100644 index 0000000..8a05ebe --- /dev/null +++ b/shell/platform/fuchsia/dart_runner/fuchsia_pkg_url.h
@@ -0,0 +1,54 @@ +// Copyright 2022 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_FUCHSIA_PKG_URL_H_ +#define FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_FUCHSIA_PKG_URL_H_ + +#include <string> + +// #include "src/lib/fxl/macros.h" + +namespace component { + +class FuchsiaPkgUrl { + public: + FuchsiaPkgUrl() = default; + FuchsiaPkgUrl(const FuchsiaPkgUrl&) = default; + FuchsiaPkgUrl& operator=(const FuchsiaPkgUrl&) = default; + FuchsiaPkgUrl(FuchsiaPkgUrl&&) = default; + FuchsiaPkgUrl& operator=(FuchsiaPkgUrl&&) = default; + + static bool IsFuchsiaPkgScheme(const std::string& url); + + // Returns the default component's .cmx path of this package, i.e. + // meta/<package_name>.cmx + std::string GetDefaultComponentCmxPath() const; + + bool Parse(const std::string& url); + + bool operator==(const FuchsiaPkgUrl&) const; + bool operator!=(const FuchsiaPkgUrl& rhs) const { return !(*this == rhs); } + + const std::string& host_name() const { return host_name_; } + const std::string& package_name() const { return package_name_; } + const std::string& variant() const { return variant_; } + const std::string& hash() const { return hash_; } + const std::string& resource_path() const { return resource_path_; } + std::string package_path() const; + std::string pkgfs_dir_path() const; + + const std::string& ToString() const; + + private: + std::string url_; + std::string host_name_; + std::string package_name_; + std::string variant_; + std::string hash_; + std::string resource_path_; +}; + +} // namespace component + +#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_FUCHSIA_PKG_URL_H_
diff --git a/shell/platform/fuchsia/dart_runner/meta/dart_jit_runner.cml b/shell/platform/fuchsia/dart_runner/meta/dart_jit_runner.cml index 2552f7e..545ed80 100644 --- a/shell/platform/fuchsia/dart_runner/meta/dart_jit_runner.cml +++ b/shell/platform/fuchsia/dart_runner/meta/dart_jit_runner.cml
@@ -17,6 +17,14 @@ path: "/svc/fuchsia.component.runner.ComponentRunner", }, ], + use: [ + { + protocol: [ + "fuchsia.sys.Environment", + "fuchsia.sys.Loader", + ], + }, + ], expose: [ { runner: "dart_jit_runner",
diff --git a/shell/platform/fuchsia/dart_runner/string_printf.cc b/shell/platform/fuchsia/dart_runner/string_printf.cc new file mode 100644 index 0000000..801f54e --- /dev/null +++ b/shell/platform/fuchsia/dart_runner/string_printf.cc
@@ -0,0 +1,90 @@ +// Copyright 2016 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "string_printf.h" + +#include <assert.h> +#include <errno.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> + +#include <memory> + +namespace fxl { +namespace { + +void StringVAppendfHelper(std::string* dest, const char* format, va_list ap) { + // Size of the small stack buffer to use first. This should be kept in sync + // with the numbers in StringPrintfTest.StringPrintf_Boundary. + constexpr size_t kStackBufferSize = 1024u; + + // First, try with a small buffer on the stack. + char stack_buf[kStackBufferSize]; + // Copy |ap| (which can only be used once), in case we need to retry. + va_list ap_copy; + va_copy(ap_copy, ap); + int result = vsnprintf(stack_buf, kStackBufferSize, format, ap_copy); + va_end(ap_copy); + if (result < 0) { + // As far as I can tell, we'd only get |EOVERFLOW| if the result is so large + // that it can't be represented by an |int| (in which case retrying would be + // futile), so Chromium's implementation is wrong. + return; + } + // |result| should be the number of characters we need, not including the + // terminating null. However, |vsnprintf()| always null-terminates! + size_t output_size = static_cast<size_t>(result); + // Check if the output fit into our stack buffer. This is "<" not "<=", since + // |vsnprintf()| will null-terminate. + if (output_size < kStackBufferSize) { + // It fit. + dest->append(stack_buf, static_cast<size_t>(result)); + return; + } + + // Since we have the required output size, we can just heap allocate that. + // (Add 1 because |vsnprintf()| will always null-terminate.) + size_t heap_buf_size = output_size + 1u; + std::unique_ptr<char[]> heap_buf(new char[heap_buf_size]); + result = vsnprintf(heap_buf.get(), heap_buf_size, format, ap); + if (result < 0 || static_cast<size_t>(result) > output_size) { + assert(false); + return; + } + assert(static_cast<size_t>(result) == output_size); + dest->append(heap_buf.get(), static_cast<size_t>(result)); +} + +} // namespace + +std::string StringPrintf(const char* format, ...) { + va_list ap; + va_start(ap, format); + std::string rv; + StringVAppendf(&rv, format, ap); + va_end(ap); + return rv; +} + +std::string StringVPrintf(const char* format, va_list ap) { + std::string rv; + StringVAppendf(&rv, format, ap); + return rv; +} + +void StringAppendf(std::string* dest, const char* format, ...) { + va_list ap; + va_start(ap, format); + StringVAppendf(dest, format, ap); + va_end(ap); +} + +void StringVAppendf(std::string* dest, const char* format, va_list ap) { + int old_errno = errno; + StringVAppendfHelper(dest, format, ap); + errno = old_errno; +} + +} // namespace fxl
diff --git a/shell/platform/fuchsia/dart_runner/string_printf.h b/shell/platform/fuchsia/dart_runner/string_printf.h new file mode 100644 index 0000000..7ec78e9 --- /dev/null +++ b/shell/platform/fuchsia/dart_runner/string_printf.h
@@ -0,0 +1,37 @@ +// Copyright 2022 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// |printf()|-like formatting functions that output/append to C++ strings. + +#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_STRING_PRINTF_H_ +#define FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_STRING_PRINTF_H_ + +#include <stdarg.h> + +#include <string> + +// #include "src/lib/fxl/fxl_export.h" +// #include "src/lib/fxl/macros.h" + +namespace fxl { + +// Formats |printf()|-like input and returns it as an |std::string|. +[[nodiscard, gnu::format(printf, 1, 2)]] std::string StringPrintf( + const char* format, + ...); + +// Formats |vprintf()|-like input and returns it as an |std::string|. +[[nodiscard]] std::string StringVPrintf(const char* format, va_list ap); + +// Formats |printf()|-like input and appends it to |*dest|. +[[gnu::format(printf, 2, 3)]] void StringAppendf(std::string* dest, + const char* format, + ...); + +// Formats |vprintf()|-like input and appends it to |*dest|. +void StringVAppendf(std::string* dest, const char* format, va_list ap); + +} // namespace fxl + +#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_STRING_PRINTF_H_
diff --git a/shell/platform/fuchsia/dart_runner/suite.cc b/shell/platform/fuchsia/dart_runner/suite.cc new file mode 100644 index 0000000..0645dbc --- /dev/null +++ b/shell/platform/fuchsia/dart_runner/suite.cc
@@ -0,0 +1,297 @@ +// Copyright 2022 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "suite.h" + +#include <fuchsia/component/runner/cpp/fidl.h> +#include <fuchsia/io/cpp/fidl.h> +#include <fuchsia/logger/cpp/fidl.h> +#include <fuchsia/sys/cpp/fidl.h> +#include <fuchsia/sys/test/cpp/fidl.h> +#include <lib/async/cpp/executor.h> +#include <lib/async/cpp/task.h> +#include <lib/async/dispatcher.h> +#include <lib/fidl/cpp/interface_handle.h> +#include <lib/fidl/cpp/interface_ptr.h> +#include <lib/fidl/cpp/interface_request.h> +#include <lib/fpromise/barrier.h> +#include <lib/fpromise/bridge.h> +#include <lib/fpromise/promise.h> +#include <lib/sys/cpp/service_directory.h> +#include <lib/sys/cpp/termination_reason.h> +#include <lib/sys/cpp/testing/enclosing_environment.h> +// #include <lib/syslog/cpp/macros.h> +#include <lib/zx/socket.h> +#include <unistd.h> +#include <zircon/processargs.h> +#include <zircon/status.h> + +#include <map> +#include <memory> +#include <set> +#include <utility> +#include <vector> + +#include "flutter/fml/logging.h" +#include "string_printf.h" + +static const char kTestCaseName[] = "dart_test"; +// constexpr char kEnvPrefix[] = "test_env_"; + +Suite::Suite(std::string legacy_url, async_dispatcher_t* dispatcher) + : legacy_url_(std::move(legacy_url)), + test_components_(std::make_shared<ComponentMap>()), + dispatcher_(dispatcher), + executor_(dispatcher) { + FML_LOG(INFO) << "SUITE CLASS CREATED"; +} + +Suite::~Suite() = default; + +Suite::CaseIterator::CaseIterator( + fidl::InterfaceRequest<fuchsia::sys::test::CaseIterator> request, + async_dispatcher_t* dispatcher, + fit::function<void(CaseIterator*)> done_callback) + : binding_(this, std::move(request), dispatcher), + done_callback_(std::move(done_callback)) {} + +void Suite::CaseIterator::GetNext(GetNextCallback callback) { + if (get_next_call_count == 0) { + fuchsia::sys::test::Case test_case; + test_case.set_name(std::string(kTestCaseName)); + test_case.set_enabled(true); + std::vector<fuchsia::sys::test::Case> cases; + cases.push_back(std::move(test_case)); + callback(std::move(cases)); + get_next_call_count++; + + } else { + std::vector<fuchsia::sys::test::Case> cases; + callback(std::move(cases)); + // this would be removed + done_callback_(this); + } +} + +void Suite::GetTests( + fidl::InterfaceRequest<fuchsia::sys::test::CaseIterator> iterator) { + FML_LOG(INFO) << "DART RUNNER HIT SUITE GETTESTS"; + auto case_iterator = std::make_unique<CaseIterator>( + std::move(iterator), dispatcher_, [this](CaseIterator* case_iterator) { + RemoveCaseInterator(case_iterator); + }); + case_iterators_.emplace(case_iterator.get(), std::move(case_iterator)); +} + +std::unique_ptr<Suite::CaseIterator> Suite::RemoveCaseInterator( + CaseIterator* case_iterator) { + auto it = case_iterators_.find(case_iterator); + std::unique_ptr<Suite::CaseIterator> case_iterator_ptr; + if (it != case_iterators_.end()) { + case_iterator_ptr = std::move(it->second); + case_iterators_.erase(it); + } + return case_iterator_ptr; +} + +void Suite::Run( + std::vector<fuchsia::sys::test::Invocation> tests, + fuchsia::sys::test::RunOptions options, + fidl::InterfaceHandle<fuchsia::sys::test::RunListener> listener) { + FML_LOG(INFO) << "DART RUNNER HIT SUITE RUN"; + auto listener_proxy = listener.Bind(); + std::vector<std::string> args; + if (options.has_arguments()) { + args = std::move(*options.mutable_arguments()); + } + + if (options.has_parallel()) { + FML_LOG(WARNING) << "Ignoring 'parallel'. Pass test specific flags, eg: " + "for rust test pass in " + "--test-threads=" + << options.parallel(); + } + + fpromise::barrier barrier; + for (auto it = tests.begin(); it != tests.end(); it++) { + auto invocation = std::move(*it); + std::string test_case_name; + if (invocation.has_name()) { + test_case_name = invocation.name(); + } + FML_LOG(INFO) << "DART RUNNER HIT SUITE RUN - TEST CASE: " + << test_case_name; + zx::socket out, err, out_client, err_client; + auto status = zx::socket::create(0, &out, &out_client); + if (status != ZX_OK) { + FML_LOG(FATAL) << "cannot create socket: " + << zx_status_get_string(status); + } + status = zx::socket::create(0, &err, &err_client); + if (status != ZX_OK) { + FML_LOG(FATAL) << "cannot create socket: " + << zx_status_get_string(status); + } + + fidl::InterfacePtr<fuchsia::sys::test::CaseListener> case_listener; + + fuchsia::sys::test::StdHandles std_handles; + std_handles.set_err(std::move(err_client)); + std_handles.set_out(std::move(out_client)); + + listener_proxy->OnTestCaseStarted(std::move(invocation), + std::move(std_handles), + case_listener.NewRequest()); + // if (test_case_name != kTestCaseName) { + // FML_LOG(INFO) << "TEST CASE: " << test_case_name; + // FML_LOG(INFO) << "kTEST CASE: " << kTestCaseName; + // std::string msg = + // fxl::StringPrintf("Invalid test case, expected: %s, got: %s\n", + // kTestCaseName, test_case_name.c_str()); + // err.write(0, msg.c_str(), msg.length(), nullptr); + // fuchsia::sys::test::Result result; + // result.set_status(fuchsia::sys::test::Status::FAILED); + // case_listener->Finished(std::move(result)); + // } else { + // auto promise = RunTest(std::move(out), std::move(err), args, + // std::move(case_listener)) + // .wrap_with(barrier); + // executor_.schedule_task(std::move(promise)); + // } + } + + auto sync_promise = + fpromise::make_promise([listener_proxy = std::move(listener_proxy)] { + FML_LOG(INFO) << "Sending OnFinished for legacy tests"; + listener_proxy->OnFinished(); + }); + executor_.schedule_task(barrier.sync().and_then(std::move(sync_promise))); +} + +// fpromise::promise<> Suite::RunTest( +// zx::socket out, +// zx::socket err, +// const std::vector<std::string>& arguments, +// fidl::InterfacePtr<fuchsia::sys::test::CaseListener> case_listener) { +// sys::testing::EnvironmentServices::ParentOverrides parent_overrides; +// parent_overrides.debug_data_service_ = std::make_shared<vfs::Service>( +// [namespace_services = test_component_svc_]( +// zx::channel channel, async_dispatcher_t* /*unused*/) { +// // namespace_services->Connect( +// // fuchsia::sys::test::DebugData::Name_, +// // std::move(channel)); +// }); + +// auto test_env_services = +// sys::testing::EnvironmentServices::CreateWithParentOverrides( +// parent_env_, std::move(parent_overrides)); + +// // Add these services to the environment if they are not injected and test +// // doesn't request sys version of them. +// std::set<std::string> services_to_add = { +// fuchsia::logger::LogSink::Name_, fuchsia::logger::Log::Name_, +// fuchsia::diagnostics::ArchiveAccessor::Name_}; + +// // Compute a common random suffix for the environments created to run the +// // test. +// uint32_t env_rand_suffix; +// zx_cprng_draw(&env_rand_suffix, sizeof(env_rand_suffix)); + +// for (const auto& service : services_to_add) { +// test_env_services->AddService<void>( +// [namespace_services = test_component_svc_, +// service](fidl::InterfaceRequest<void> request) { +// namespace_services->Connect(service, request.TakeChannel()); +// }, +// service); +// } + +// fuchsia::sys::EnvironmentOptions env_opt; +// // std::string env_label = std::format("%s%08x", kEnvPrefix, +// // env_rand_suffix); +// std::string env_label = kEnvPrefix; +// env_opt.delete_storage_on_death = true; + +// auto enclosing_env = sys::testing::EnclosingEnvironment::Create( +// env_label, parent_env_, std::move(test_env_services), env_opt); + +// auto launcher = enclosing_env->launcher_ptr(); + +// auto info = +// fuchsia::sys::LaunchInfo{.url = legacy_url_, .arguments = arguments}; +// auto out_collector = AddOutputFileDescriptor(STDOUT_FILENO, std::move(out), +// dispatcher_, &info.out); +// auto err_collector = AddOutputFileDescriptor(STDERR_FILENO, std::move(err), +// dispatcher_, &info.err); + +// auto svc = +// sys::ServiceDirectory::CreateWithRequest(&info.directory_request); + +// // fuchsia::component::runner::ComponentControllerPtr contoller; +// fuchsia::sys::ComponentControllerPtr contoller; +// launcher->CreateComponent(std::move(info), contoller.NewRequest()); +// auto test_component = std::make_unique<run::Component>( +// std::move(out_collector), std::move(err_collector), +// std::move(contoller), std::move(svc)); +// fpromise::bridge<> bridge; +// test_component->controller().events().OnTerminated = +// [this, url = legacy_url_, enclosing_env = std::move(enclosing_env), +// completer = std::move(bridge.completer), +// case_listener = std::move(case_listener), +// this_ptr = test_component.get()]( +// int64_t return_code, +// fuchsia::sys::TerminationReason termination_reason) mutable { +// if (termination_reason != fuchsia::sys::TerminationReason::EXITED) { +// FML_LOG(WARNING) << "Test " << url << " failed with " +// << sys::HumanReadableTerminationReason( +// termination_reason); +// } + +// FML_LOG(INFO) << "Legacy test exited with return code " << +// return_code +// << ", collecting stdout"; + +// auto status = return_code == 0 ? fuchsia::sys::test::Status::PASSED +// : fuchsia::sys::test::Status::FAILED; + +// auto output_promise = this_ptr->SignalWhenOutputCollected(); + +// FML_LOG(INFO) << "Killing environment for legacy test"; +// fpromise::bridge<> bridge; +// enclosing_env->Kill(bridge.completer.bind()); +// auto promise = bridge.consumer.promise().and_then( +// [enclosing_env = std::move(enclosing_env), +// case_listener = std::move(case_listener), status = status, +// completer = std::move(completer)]() mutable { +// fuchsia::sys::test::Result result; +// result.set_status(status); +// FML_LOG(INFO) << "Sending finished event for legacy tests"; +// case_listener->Finished(std::move(result)); +// completer.complete_ok(); +// }); +// this->executor_.schedule_task(output_promise.and_then([this, +// this_ptr]() { +// FML_LOG(INFO) << "Done collecting standard output for legacy tests +// "; RemoveComponent(this_ptr); +// })); +// this->executor_.schedule_task(std::move(promise)); +// }; + +// test_components_->emplace(test_component.get(), std::move(test_component)); + +// return bridge.consumer.promise(); +// } + +// std::unique_ptr<run::Component> Suite::RemoveComponent(run::Component* ptr) { +// if (!test_components_) { +// return nullptr; +// } +// auto it = test_components_->find(ptr); +// std::unique_ptr<run::Component> test_component_ptr; +// if (it != test_components_->end()) { +// test_component_ptr = std::move(it->second); +// test_components_->erase(it); +// } +// return test_component_ptr; +// }
diff --git a/shell/platform/fuchsia/dart_runner/suite.h b/shell/platform/fuchsia/dart_runner/suite.h new file mode 100644 index 0000000..30052e0 --- /dev/null +++ b/shell/platform/fuchsia/dart_runner/suite.h
@@ -0,0 +1,84 @@ +// Copyright 2022 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_SUITE_H_ +#define FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_SUITE_H_ + +#include <fuchsia/sys/cpp/fidl.h> +#include <fuchsia/sys/test/cpp/fidl.h> +#include <lib/async/cpp/executor.h> +#include <lib/fidl/cpp/binding.h> +#include <lib/fidl/cpp/binding_set.h> +#include <lib/fidl/cpp/interface_request.h> +#include <lib/fpromise/promise.h> +#include <lib/sys/cpp/service_directory.h> + +#include <map> +#include <memory> +#include <vector> + +/// Implement and expose Suite protocol on behalf of wrapped legacy dart test +/// component. +class Suite final : public fuchsia::sys::test::Suite { + using ComponentMap = + std::map<run::Component*, std::unique_ptr<run::Component>>; + + public: + Suite(std::string legacy_url, std::unique_ptr<async::Loop> loop); + ~Suite() override; + + void GetTests(fidl::InterfaceRequest<fuchsia::sys::test::CaseIterator> + iterator) override; + + void Run( + std::vector<fuchsia::sys::test::Invocation> tests, + fuchsia::sys::test::RunOptions options, + fidl::InterfaceHandle<fuchsia::sys::test::RunListener> listener) override; + + fidl::InterfaceRequestHandler<fuchsia::sys::test::Suite> GetHandler() { + return bindings_.GetHandler(this, dispatcher_); + } + + void AddBinding(zx::channel request) { + bindings_.AddBinding( + this, + fidl::InterfaceRequest<fuchsia::sys::test::Suite>(std::move(request)), + dispatcher_); + } + + private: + fpromise::promise<> RunTest( + zx::socket out, + zx::socket err, + const std::vector<std::string>& arguments, + fidl::InterfacePtr<fuchsia::sys::test::CaseListener> case_listener); + + class CaseIterator final : public fuchsia::sys::test::CaseIterator { + public: + CaseIterator( + fidl::InterfaceRequest<fuchsia::sys::test::CaseIterator> request, + async_dispatcher_t* dispatcher, + fit::function<void(CaseIterator*)> done_callback); + + void GetNext(GetNextCallback callback) override; + + private: + int get_next_call_count = 0; + fidl::Binding<fuchsia::sys::test::CaseIterator> binding_; + fit::function<void(CaseIterator*)> done_callback_; + }; + + // const std::shared_ptr<sys::ServiceDirectory> test_component_svc_; + const std::string legacy_url_; + std::shared_ptr<ComponentMap> test_components_; + std::map<CaseIterator*, std::unique_ptr<CaseIterator>> case_iterators_; + async_dispatcher_t* dispatcher_; + fidl::BindingSet<fuchsia::sys::test::Suite> bindings_; + async::Executor executor_; + + std::unique_ptr<CaseIterator> RemoveCaseInterator(CaseIterator*); + std::unique_ptr<run::Component> RemoveComponent(run::Component* ptr); +}; + +#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_SUITE_H_
diff --git a/shell/platform/fuchsia/dart_runner/test_component.cc b/shell/platform/fuchsia/dart_runner/test_component.cc new file mode 100644 index 0000000..0a5660d --- /dev/null +++ b/shell/platform/fuchsia/dart_runner/test_component.cc
@@ -0,0 +1,36 @@ +// Copyright 2022 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "test_component.h" + +#include <lib/fidl/cpp/interface_request.h> +#include <lib/sys/cpp/component_context.h> + +#include "flutter/fml/logging.h" +#include "suite.h" + +TestComponent::TestComponent(TestComponentArgs args, DoneCallback done_callback) + : dispatcher_(args.dispatcher), + binding_(this, std::move(args.request), args.dispatcher), + done_callback_(std::move(done_callback)) { + suite_ = std::make_unique<Suite>(std::move(args.parent_env_svc), + std::move(args.parent_env), + std::move(args.test_component_svc), + std::move(args.legacy_url), dispatcher_); + suite_context_ = sys::ComponentContext::Create(); + suite_context_->outgoing()->AddPublicService(suite_->GetHandler()); + suite_context_->outgoing()->Serve(std::move(args.outgoing_dir), dispatcher_); +} + +TestComponent::~TestComponent() = default; + +void TestComponent::Stop() { + Kill(); +} + +void TestComponent::Kill() { + binding_.Close(ZX_OK); + // this object would be killed after this call + done_callback_(this); +}
diff --git a/shell/platform/fuchsia/dart_runner/test_component.h b/shell/platform/fuchsia/dart_runner/test_component.h new file mode 100644 index 0000000..cf9c0a5 --- /dev/null +++ b/shell/platform/fuchsia/dart_runner/test_component.h
@@ -0,0 +1,58 @@ +// Copyright 2022 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_TEST_COMPONENT_H_ +#define FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_TEST_COMPONENT_H_ + +#include <fuchsia/component/runner/cpp/fidl.h> +#include <fuchsia/sys/cpp/fidl.h> +#include <lib/fidl/cpp/binding.h> +#include <lib/fidl/cpp/interface_request.h> +#include <lib/sys/cpp/component_context.h> +#include <lib/sys/cpp/service_directory.h> + +#include <memory> + +#include "suite.h" + +using ComponentController = fuchsia::component::runner::ComponentController; + +struct TestComponentArgs { + std::string legacy_url; + zx::channel outgoing_dir; + fuchsia::sys::EnvironmentPtr parent_env; + std::shared_ptr<sys::ServiceDirectory> parent_env_svc; + std::shared_ptr<sys::ServiceDirectory> test_component_svc; + + fidl::InterfaceRequest<ComponentController> request; + async_dispatcher_t* dispatcher; +}; + +/// Implements component controller on behalf of the runner and also +/// stores/controls running test component. +class TestComponent final : public ComponentController { + using DoneCallback = fit::function<void(TestComponent*)>; + + public: + explicit TestComponent(TestComponentArgs args, DoneCallback done_callback); + ~TestComponent() override; + + void Stop() override; + + void Kill() override; + + private: + async_dispatcher_t* dispatcher_; + fidl::Binding<ComponentController> binding_; + + /// For safe keeping while the component is running. + std::unique_ptr<Suite> suite_; + + /// Exposes suite protocol on behalf of test component. + std::unique_ptr<sys::ComponentContext> suite_context_; + + DoneCallback done_callback_; +}; + +#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_TEST_COMPONENT_H_
diff --git a/shell/platform/fuchsia/flutter/component_v2.cc b/shell/platform/fuchsia/flutter/component_v2.cc index b068369..532da61 100644 --- a/shell/platform/fuchsia/flutter/component_v2.cc +++ b/shell/platform/fuchsia/flutter/component_v2.cc
@@ -116,6 +116,16 @@ result.assets_path = "pkg/" + entry.value->str(); } else if (entry.key.compare(kArgsKey) == 0 && entry.value != nullptr) { ParseArgs(entry.value->str_vec(), &result); + FML_LOG(INFO) << "FLUTTER RUNNER PROGRAM ARGS: "; + std::string s; + for (const auto& piece : entry.value->str_vec()) + s += piece; + FML_LOG(INFO) << s; + + std::string d; + for (const auto& piece : result.expose_dirs) + d += piece; + FML_LOG(INFO) << d; } }
diff --git a/shell/platform/fuchsia/flutter/runner.cc b/shell/platform/fuchsia/flutter/runner.cc index 6c59e6c..377ec61 100644 --- a/shell/platform/fuchsia/flutter/runner.cc +++ b/shell/platform/fuchsia/flutter/runner.cc
@@ -308,6 +308,7 @@ // change, so we make a copy to pass to TRACE_DURATION. // TODO(PT-169): Remove this copy when TRACE_DURATION reads string arguments // eagerly. + FML_LOG(INFO) << "RESOLVED URL V2: " << start_info.resolved_url(); const std::string url_copy = start_info.resolved_url(); TRACE_EVENT1("flutter", "Start", "url", url_copy.c_str());
diff --git a/shell/platform/fuchsia/runtime/dart/utils/program_metadata.h b/shell/platform/fuchsia/runtime/dart/utils/program_metadata.h new file mode 100644 index 0000000..afb2a50 --- /dev/null +++ b/shell/platform/fuchsia/runtime/dart/utils/program_metadata.h
@@ -0,0 +1,38 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_RUNTIME_DART_UTILS_PROGRAM_METADATA_H_ +#define FLUTTER_SHELL_PLATFORM_FUCHSIA_RUNTIME_DART_UTILS_PROGRAM_METADATA_H_ + +#include <optional> +#include <string> + +namespace dart_runner { + +/// The metadata that can be passed by a Flutter component via +/// the `program` field. +struct ProgramMetadata { + /// The path where data for the Flutter component should + /// be stored. + std::string data_path = ""; + + bool is_test = false; + + /// The path where assets for the Flutter component should + /// be stored. + /// + /// TODO(fxb/89246): No components appear to be using this field. We + /// may be able to get rid of this. + std::string assets_path = ""; + + /// The preferred heap size for the Flutter component in megabytes. + std::optional<int64_t> old_gen_heap_size = std::nullopt; + + /// A list of additional directories that we will expose in out/ + std::vector<std::string> expose_dirs; +}; + +} // namespace dart_runner + +#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_RUNTIME_DART_UTILS_PROGRAM_METADATA_H_
diff --git a/tools/gn b/tools/gn index abf2836..8b07b21 100755 --- a/tools/gn +++ b/tools/gn
@@ -414,7 +414,7 @@ gn_args['fuchsia_target_api_level'] = int(f.read().strip()) # Impeller flags. - gn_args['impeller_enable_playground'] = args.enable_impeller_playground + # gn_args['impeller_enable_playground'] = args.enable_impeller_playground return gn_args @@ -528,8 +528,8 @@ parser.add_argument('--fuchsia-target-api-level', dest='fuchsia_target_api_level') # Impeller flags. - parser.add_argument('--enable-impeller-playground', default=False, action='store_true', - help='Whether impeller unit tests run in playground mode') + # parser.add_argument('--enable-impeller-playground', default=False, action='store_true', + # help='Whether impeller unit tests run in playground mode') # Sanitizers. parser.add_argument('--asan', default=False, action='store_true')