[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')