[dart_test_runner] Adapt the dart runner to implement the suite fidl protocol so that it can be used in the v2 testing framework. (flutter/engine#32751)

This commit is contained in:
Naud Ghebre 2022-04-28 15:55:55 -04:00 committed by GitHub
parent a6880c6c34
commit ea8342fc2b
15 changed files with 1092 additions and 56 deletions

View File

@ -1890,6 +1890,8 @@ FILE: ../../../flutter/shell/platform/fuchsia/dart_runner/dart_component_control
FILE: ../../../flutter/shell/platform/fuchsia/dart_runner/dart_component_controller_v2.h
FILE: ../../../flutter/shell/platform/fuchsia/dart_runner/dart_runner.cc
FILE: ../../../flutter/shell/platform/fuchsia/dart_runner/dart_runner.h
FILE: ../../../flutter/shell/platform/fuchsia/dart_runner/dart_test_component_controller_v2.cc
FILE: ../../../flutter/shell/platform/fuchsia/dart_runner/dart_test_component_controller_v2.h
FILE: ../../../flutter/shell/platform/fuchsia/dart_runner/embedder/builtin.dart
FILE: ../../../flutter/shell/platform/fuchsia/dart_runner/embedder/script_runner_snapshot.dart
FILE: ../../../flutter/shell/platform/fuchsia/dart_runner/embedder/shim.dart

View File

@ -89,6 +89,9 @@ if (enable_unittests) {
group("tests") {
testonly = true
deps = [ "flutter:tests" ]
deps = [
"dart_runner:tests",
"flutter:tests",
]
}
}

View File

@ -6,12 +6,85 @@ assert(is_fuchsia)
import("//build/fuchsia/sdk.gni")
import("//flutter/common/fuchsia_config.gni")
import("//flutter/testing/testing.gni")
import("//flutter/tools/fuchsia/dart.gni")
import("//flutter/tools/fuchsia/fuchsia_archive.gni")
import("//flutter/tools/fuchsia/fuchsia_libs.gni")
template("runner_sources") {
assert(defined(invoker.product), "runner_sources must define product")
source_set(target_name) {
sources = [
"builtin_libraries.cc",
"builtin_libraries.h",
"dart_component_controller.cc",
"dart_component_controller.h",
"dart_component_controller_v2.cc",
"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",
"service_isolate.cc",
"service_isolate.h",
]
dart_public_deps = []
if (!invoker.product) {
dart_public_deps += [
"//flutter/shell/platform/fuchsia/runtime/dart/utils:utils",
"//third_party/dart/runtime/bin:dart_io_api",
]
} else {
dart_public_deps += [
"//flutter/shell/platform/fuchsia/runtime/dart/utils:utils_product",
"//third_party/dart/runtime/bin:dart_io_api_product",
]
}
public_deps = [
"$fuchsia_sdk_root/pkg:sys_cpp",
"//flutter/fml",
] + dart_public_deps
deps = [
"$fuchsia_sdk_root/fidl:fuchsia.component.runner",
"$fuchsia_sdk_root/fidl:fuchsia.logger",
"$fuchsia_sdk_root/fidl:fuchsia.test",
"$fuchsia_sdk_root/pkg:async",
"$fuchsia_sdk_root/pkg:async-cpp",
"$fuchsia_sdk_root/pkg:async-default",
"$fuchsia_sdk_root/pkg:async-loop",
"$fuchsia_sdk_root/pkg:async-loop-cpp",
"$fuchsia_sdk_root/pkg:async-loop-default",
"$fuchsia_sdk_root/pkg:fidl_cpp",
"$fuchsia_sdk_root/pkg:sys_cpp",
"$fuchsia_sdk_root/pkg:sys_cpp_testing",
"$fuchsia_sdk_root/pkg:sys_inspect_cpp",
"$fuchsia_sdk_root/pkg:syslog",
"$fuchsia_sdk_root/pkg:trace",
"$fuchsia_sdk_root/pkg:vfs_cpp",
"$fuchsia_sdk_root/pkg:zx",
"//flutter/common",
"//flutter/shell/platform/fuchsia/dart-pkg/fuchsia",
"//flutter/shell/platform/fuchsia/dart-pkg/zircon",
"//flutter/third_party/tonic",
]
}
}
runner_sources("dart_runner_sources") {
product = false
}
runner_sources("dart_runner_sources_product") {
product = true
}
template("runner") {
assert(defined(invoker.product), "The parameter 'product' must be defined")
assert(defined(invoker.product), "The parameter 'product' must be defined.")
assert(defined(invoker.output_name),
"The parameter 'output_name' must be defined")
@ -22,60 +95,23 @@ template("runner") {
extra_defines += [ "DEBUG" ] # Needed due to direct dart dependencies.
}
product_suffix = ""
if (invoker.product) {
product_suffix = "_product"
}
executable(target_name) {
output_name = invoker_output_name
sources = [
"builtin_libraries.cc",
"builtin_libraries.h",
"dart_component_controller.cc",
"dart_component_controller.h",
"dart_component_controller_v2.cc",
"dart_component_controller_v2.h",
"dart_runner.cc",
"dart_runner.h",
"logging.h",
"main.cc",
"service_isolate.cc",
"service_isolate.h",
]
sources = [ "main.cc" ]
defines = extra_defines
dart_deps = []
if (!invoker.product) {
dart_deps += [
"//flutter/shell/platform/fuchsia/runtime/dart/utils:utils",
"//third_party/dart/runtime/bin:dart_io_api",
]
} else {
dart_deps += [
"//flutter/shell/platform/fuchsia/runtime/dart/utils:utils_product",
"//third_party/dart/runtime/bin:dart_io_api_product",
]
}
deps = [
"//flutter/common",
"//flutter/fml",
"//flutter/shell/platform/fuchsia/dart-pkg/fuchsia",
"//flutter/shell/platform/fuchsia/dart-pkg/zircon",
"$fuchsia_sdk_root/fidl:fuchsia.component.runner",
"$fuchsia_sdk_root/pkg:async",
"$fuchsia_sdk_root/pkg:async-cpp",
"$fuchsia_sdk_root/pkg:async-default",
"$fuchsia_sdk_root/pkg:async-loop",
"$fuchsia_sdk_root/pkg:async-loop-cpp",
"$fuchsia_sdk_root/pkg:async-loop-default",
"$fuchsia_sdk_root/pkg:fidl_cpp",
":dart_runner_sources${product_suffix}",
"$fuchsia_sdk_root/pkg:sys_inspect_cpp",
"$fuchsia_sdk_root/pkg:sys_cpp",
"$fuchsia_sdk_root/pkg:syslog",
"$fuchsia_sdk_root/pkg:trace",
"$fuchsia_sdk_root/pkg:trace-provider-so",
"$fuchsia_sdk_root/pkg:vfs_cpp",
"//flutter/third_party/tonic",
] + dart_deps + extra_deps
] + extra_deps
}
}
@ -273,3 +309,38 @@ jit_runner_package("dart_jit_runner") {
jit_runner_package("dart_jit_product_runner") {
product = true
}
if (enable_unittests) {
executable("dart_test_runner_unittests") {
testonly = true
output_name = "dart_runner_tests"
sources = [ "tests/suite_impl_unittests.cc" ]
# This is needed for //third_party/googletest for linking zircon symbols.
libs = [ "$fuchsia_sdk_path/arch/$target_cpu/sysroot/lib/libzircon.so" ]
deps = [
":dart_runner_sources",
"//flutter/fml",
"//third_party/dart/runtime:libdart_jit",
"//third_party/dart/runtime/platform:libdart_platform_jit",
"//third_party/googletest:gtest_main",
]
}
fuchsia_test_archive("dart_runner_tests") {
deps = [ ":dart_test_runner_unittests" ]
binary = "$target_name"
}
# When adding a new dep here, please also ensure the dep is added to
# testing/fuchsia/test_suites.yaml.
group("tests") {
testonly = true
deps = [ ":dart_runner_tests" ]
}
}

View File

@ -18,6 +18,9 @@
#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"
@ -116,7 +119,7 @@ void RunApplicationV1(
bool success = app.Setup();
int64_t end = Dart_TimelineGetMicros();
Dart_TimelineEvent("DartComponentController::Setup", start, end,
Dart_TimelineEvent("DartComponentController::SetUp", start, end,
Dart_Timeline_Event_Duration, 0, NULL, NULL);
if (success) {
app.Run();
@ -141,7 +144,7 @@ void RunApplicationV2(
const bool success = app.SetUp();
const int64_t end = Dart_TimelineGetMicros();
Dart_TimelineEvent("DartComponentControllerV2::Setup", start, end,
Dart_TimelineEvent("DartComponentControllerV2::SetUp", start, end,
Dart_Timeline_Event_Duration, 0, NULL, NULL);
if (success) {
app.Run();
@ -152,6 +155,31 @@ void RunApplicationV2(
}
}
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::shared_ptr<DartTestComponentControllerV2>)>
component_created_callback,
fit::function<void(DartTestComponentControllerV2*)> done_callback) {
const int64_t start = Dart_TimelineGetMicros();
auto test_component = std::make_shared<DartTestComponentControllerV2>(
std::move(start_info), runner_incoming_services, std::move(controller),
std::move(done_callback));
component_created_callback(test_component);
// Start up the dart isolate and serve the suite protocol.
test_component->SetUp();
const int64_t end = Dart_TimelineGetMicros();
Dart_TimelineEvent("DartTestComponentControllerV2::SetUp", start, end,
Dart_Timeline_Event_Duration, 0, NULL, NULL);
}
bool EntropySource(uint8_t* buffer, intptr_t count) {
zx_cprng_draw(buffer, count);
return true;
@ -159,6 +187,33 @@ bool EntropySource(uint8_t* buffer, intptr_t count) {
} // namespace
// "args" are how the component specifies arguments to the runner.
constexpr char kArgsKey[] = "args";
/// Parses the |args| field from the "program" field to determine
/// if a test component is being executed.
bool IsTestProgram(const fuchsia::data::Dictionary& program_metadata) {
for (const auto& entry : program_metadata.entries()) {
if (entry.key.compare(kArgsKey) != 0 || entry.value == nullptr) {
continue;
}
auto args = entry.value->str_vec();
// 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_str;
return parsed_args.GetOptionValue("is_test", &is_test_str) &&
is_test_str == "true";
}
return false;
}
DartRunner::DartRunner(sys::ComponentContext* context) : context_(context) {
context_->outgoing()->AddPublicService<fuchsia::sys::Runner>(
[this](fidl::InterfaceRequest<fuchsia::sys::Runner> request) {
@ -248,11 +303,34 @@ void DartRunner::Start(
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();
// Parse the program field of the component's cml and check if it is a test
// component. If so, serve the |fuchsia.test.Suite| protocol from the
// component's outgoing directory, via DartTestComponentControllerV2.
if (IsTestProgram(start_info.program())) {
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),
// component_created_callback
[this](std::shared_ptr<DartTestComponentControllerV2> ptr) {
test_components_.emplace(ptr.get(), std::move(ptr));
},
// done_callback
[this](DartTestComponentControllerV2* ptr) {
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

View File

@ -10,6 +10,7 @@
#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"
namespace dart_runner {
@ -33,6 +34,12 @@ class DartRunner : public fuchsia::sys::Runner,
fidl::InterfaceRequest<fuchsia::component::runner::ComponentController>
controller) override;
// Add test components to this map to ensure it is kept alive in memory for
// the duration of test execution and retrieval of exit code.
std::map<DartTestComponentControllerV2*,
std::shared_ptr<DartTestComponentControllerV2>>
test_components_;
// Not owned by DartRunner.
sys::ComponentContext* context_;
fidl::BindingSet<fuchsia::sys::Runner> bindings_;

View File

@ -0,0 +1,649 @@
// 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/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/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 char kTestCaseName[] = "dart_test_v2";
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_);
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.");
}
zx_status_t idle_timer_status =
zx::timer::create(ZX_TIMER_SLACK_LATE, ZX_CLOCK_MONOTONIC, &idle_timer_);
if (idle_timer_status != ZX_OK) {
FX_LOGF(INFO, LOG_TAG, "Idle timer creation failed: %s",
zx_status_get_string(idle_timer_status));
} else {
idle_wait_.set_object(idle_timer_.get());
idle_wait_.set_trigger(ZX_TIMER_SIGNALED);
idle_wait_.Begin(async_get_default_dispatcher());
}
}
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;
}
// Serve |fuchsia::test::Suite| on outgoing directory.
suite_context_ = sys::ComponentContext::Create();
suite_context_->outgoing()->AddPublicService(this->GetHandler());
suite_context_->outgoing()->Serve(
start_info_.mutable_outgoing_dir()->TakeChannel(), loop_->dispatcher());
loop_->Run();
}
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;
auto ret_status = return_code == 0 ? fuchsia::test::Status::PASSED
: fuchsia::test::Status::FAILED;
fuchsia::test::Result result;
result.set_status(ret_status);
case_listener_->Finished(std::move(result));
});
return true;
}
// |fuchsia::test::CaseIterator|
DartTestComponentControllerV2::CaseIterator::CaseIterator(
fidl::InterfaceRequest<fuchsia::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)) {}
// |fuchsia::test::CaseIterator|
void DartTestComponentControllerV2::CaseIterator::GetNext(
GetNextCallback callback) {
// Dart test suites run as multiple tests behind one
// test case. Flip flag once the one test case has been retrieved.
if (first_case_) {
fuchsia::test::Case test_case;
test_case.set_name(std::string(kTestCaseName));
test_case.set_enabled(true);
std::vector<fuchsia::test::Case> cases;
cases.push_back(std::move(test_case));
callback(std::move(cases));
first_case_ = false;
} else {
// Return an pass an empty vector to the callback to indicate there
// are no more tests to be executed.
std::vector<fuchsia::test::Case> cases;
callback(std::move(cases));
done_callback_(this);
}
}
// |fuchsia::test::CaseIterator|
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::GetTests(
fidl::InterfaceRequest<fuchsia::test::CaseIterator> iterator) {
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));
}
// |fuchsia::test::Suite|
void DartTestComponentControllerV2::Run(
std::vector<fuchsia::test::Invocation> tests,
fuchsia::test::RunOptions options,
fidl::InterfaceHandle<fuchsia::test::RunListener> listener) {
RunDartMain();
std::vector<std::string> args;
if (options.has_arguments()) {
args = std::move(*options.mutable_arguments());
}
auto listener_proxy = listener.Bind();
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();
}
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 out socket: "
<< zx_status_get_string(status);
}
status = zx::socket::create(0, &err, &err_client);
if (status != ZX_OK) {
FML_LOG(FATAL) << "cannot create error socket: "
<< zx_status_get_string(status);
}
fuchsia::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());
}
listener_proxy->OnFinished();
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 {
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());
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() {
done_callback_(this);
suite_bindings_.CloseAll();
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();
}
}
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

View File

@ -0,0 +1,163 @@
// 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/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"
namespace dart_runner {
/// Starts a Dart test component written in CFv2. It's different from
/// DartComponentControllerV2 in that it must implement the
/// |fuchsia.test.Suite| protocol. It was forked to avoid a naming clash
/// between the two classes' methods as the Suite protocol requires a Run()
/// method for the test_manager to call on. This way, we avoid an extra layer
/// between the test_manager and actual test execution.
/// TODO(fxb/98369): Look into combining the two component classes once dart
/// testing is stable.
class DartTestComponentControllerV2
: public fuchsia::component::runner::ComponentController,
public fuchsia::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();
/// |Suite| protocol implementation.
void GetTests(
fidl::InterfaceRequest<fuchsia::test::CaseIterator> iterator) override;
/// |Suite| protocol implementation.
void Run(std::vector<fuchsia::test::Invocation> tests,
fuchsia::test::RunOptions options,
fidl::InterfaceHandle<fuchsia::test::RunListener> listener) override;
fidl::InterfaceRequestHandler<fuchsia::test::Suite> GetHandler() {
return suite_bindings_.GetHandler(this, loop_->dispatcher());
}
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);
// |CaseIterator|
class CaseIterator final : public fuchsia::test::CaseIterator {
public:
CaseIterator(fidl::InterfaceRequest<fuchsia::test::CaseIterator> request,
async_dispatcher_t* dispatcher,
fit::function<void(CaseIterator*)> done_callback);
void GetNext(GetNextCallback callback) override;
private:
bool first_case_ = true;
fidl::Binding<fuchsia::test::CaseIterator> binding_;
fit::function<void(CaseIterator*)> done_callback_;
};
std::unique_ptr<CaseIterator> RemoveCaseInterator(CaseIterator*);
// We only need one case_listener currently as dart tests are run as one
// large test file. In future iterations, case_listeners must be
// created per test case.
fidl::InterfacePtr<fuchsia::test::CaseListener> case_listener_;
std::map<CaseIterator*, std::unique_ptr<CaseIterator>> case_iterators_;
// |Suite|
/// Exposes suite protocol on behalf of test component.
std::unique_ptr<sys::ComponentContext> suite_context_;
fidl::BindingSet<fuchsia::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_

View File

@ -17,6 +17,14 @@
path: "/svc/fuchsia.component.runner.ComponentRunner",
},
],
use: [
{
protocol: [
"fuchsia.sys.Environment",
"fuchsia.sys.Loader",
],
},
],
expose: [
{
runner: "dart_jit_runner",

View File

@ -0,0 +1,23 @@
// Copyright 2022 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_runner/dart_test_component_controller_v2.h"
#include "gtest/gtest.h"
namespace dart_runner::testing {
namespace {
std::string GetCurrentTestName() {
return ::testing::UnitTest::GetInstance()->current_test_info()->name();
}
} // namespace
// TODO(naudzghebre): Add unit tests for the dart_test_runner.
TEST(SuiteImplTest, EQUALITY) {
EXPECT_EQ(1, 1) << GetCurrentTestName();
}
} // namespace dart_runner::testing

View File

@ -811,7 +811,7 @@ if (enable_unittests) {
}
# When adding a new dep here, please also ensure the dep is added to
# testing/fuchsia/run_tests.sh and testing/fuchsia/test_fars
# testing/fuchsia/test_suites.yaml.
group("tests") {
testonly = true

View File

@ -5324,6 +5324,34 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
engine
Copyright 2022 The Flutter Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
expat
Copyright (c) 1997-2000 Thai Open Source Software Center Ltd

View File

@ -12,6 +12,8 @@
- flutter_jit_runner-0.far
# v2 components.
- test_command: run-test-suite fuchsia-pkg://fuchsia.com/dart_runner_tests#meta/dart_runner_tests.cm
package: dart_runner_tests-0.far
- test_command: run-test-suite fuchsia-pkg://fuchsia.com/flutter_runner_tzdata_tests#meta/flutter_runner_tzdata_tests.cm
package: flutter_runner_tzdata_tests-0.far
- test_command: run-test-suite fuchsia-pkg://fuchsia.com/fml_tests#meta/fml_tests.cm -- --gtest_filter=-MessageLoop.TimeSensitiveTest_*:FileTest.CanTruncateAndWrite:FileTest.CreateDirectoryStructure

View File

@ -1,5 +1,6 @@
dart_aot_runner_package_manifest.json
dart_jit_runner_package_manifest.json
dart_runner_tests_package_manifest.json
embedder_tests_package_manifest.json
flow_tests_package_manifest.json
flutter_aot_runner_package_manifest.json

View File

@ -153,6 +153,7 @@ template("_fuchsia_archive") {
":${target_name}_dir",
":${_dbg_symbols_target}",
]
sources = copy_outputs
inputs = []

View File

@ -70,8 +70,8 @@ def main():
assert os.path.exists(args.pm_bin)
assert os.path.exists(args.package_dir)
pkg_dir = args.package_dir
if not os.path.exists(os.path.join(pkg_dir, 'meta', 'package')):
CreateMetaPackage(pkg_dir, args.far_name)