mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[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:
parent
a6880c6c34
commit
ea8342fc2b
@ -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
|
||||
|
||||
@ -89,6 +89,9 @@ if (enable_unittests) {
|
||||
group("tests") {
|
||||
testonly = true
|
||||
|
||||
deps = [ "flutter:tests" ]
|
||||
deps = [
|
||||
"dart_runner:tests",
|
||||
"flutter:tests",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -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" ]
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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_;
|
||||
|
||||
@ -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
|
||||
@ -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_
|
||||
@ -17,6 +17,14 @@
|
||||
path: "/svc/fuchsia.component.runner.ComponentRunner",
|
||||
},
|
||||
],
|
||||
use: [
|
||||
{
|
||||
protocol: [
|
||||
"fuchsia.sys.Environment",
|
||||
"fuchsia.sys.Loader",
|
||||
],
|
||||
},
|
||||
],
|
||||
expose: [
|
||||
{
|
||||
runner: "dart_jit_runner",
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -153,6 +153,7 @@ template("_fuchsia_archive") {
|
||||
":${target_name}_dir",
|
||||
":${_dbg_symbols_target}",
|
||||
]
|
||||
|
||||
sources = copy_outputs
|
||||
|
||||
inputs = []
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user