blob: 845d7237d431b431d5d93ebdbcb307f677caf39e [file] [log] [blame] [edit]
// 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 "flutter/fml/mapping.h"
#include "flutter/fml/synchronization/count_down_latch.h"
#include "flutter/fml/synchronization/waitable_event.h"
#include "flutter/lib/ui/window/platform_message.h"
#include "flutter/runtime/dart_isolate.h"
#include "flutter/runtime/dart_vm.h"
#include "flutter/runtime/dart_vm_lifecycle.h"
#include "flutter/runtime/isolate_configuration.h"
#include "flutter/runtime/platform_isolate_manager.h"
#include "flutter/testing/dart_isolate_runner.h"
#include "flutter/testing/fixture_test.h"
#include "flutter/testing/testing.h"
#include "third_party/dart/runtime/include/dart_api.h"
#include "third_party/tonic/converter/dart_converter.h"
#include "third_party/tonic/scopes/dart_isolate_scope.h"
// CREATE_NATIVE_ENTRY is leaky by design
// NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
namespace flutter {
namespace testing {
class DartIsolateTest : public FixtureTest {
public:
DartIsolateTest() {}
void Wait() { latch_.Wait(); }
void Signal() { latch_.Signal(); }
private:
fml::AutoResetWaitableEvent latch_;
FML_DISALLOW_COPY_AND_ASSIGN(DartIsolateTest);
};
TEST_F(DartIsolateTest, RootIsolateCreationAndShutdown) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
auto settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(settings);
ASSERT_TRUE(vm_ref);
auto vm_data = vm_ref.GetVMData();
ASSERT_TRUE(vm_data);
TaskRunners task_runners(GetCurrentTestName(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner() //
);
auto isolate_configuration =
IsolateConfiguration::InferFromSettings(settings);
UIDartState::Context context(task_runners);
context.advisory_script_uri = "main.dart";
context.advisory_script_entrypoint = "main";
auto weak_isolate = DartIsolate::CreateRunningRootIsolate(
vm_data->GetSettings(), // settings
vm_data->GetIsolateSnapshot(), // isolate snapshot
nullptr, // platform configuration
DartIsolate::Flags{}, // flags
nullptr, // root_isolate_create_callback
settings.isolate_create_callback, // isolate create callback
settings.isolate_shutdown_callback, // isolate shutdown callback
"main", // dart entrypoint
std::nullopt, // dart entrypoint library
{}, // dart entrypoint arguments
std::move(isolate_configuration), // isolate configuration
context // engine context
);
auto root_isolate = weak_isolate.lock();
ASSERT_TRUE(root_isolate);
ASSERT_EQ(root_isolate->GetPhase(), DartIsolate::Phase::Running);
ASSERT_TRUE(root_isolate->Shutdown());
}
TEST_F(DartIsolateTest, IsolateShutdownCallbackIsInIsolateScope) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
auto settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(settings);
ASSERT_TRUE(vm_ref);
auto vm_data = vm_ref.GetVMData();
ASSERT_TRUE(vm_data);
TaskRunners task_runners(GetCurrentTestName(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner() //
);
auto isolate_configuration =
IsolateConfiguration::InferFromSettings(settings);
UIDartState::Context context(task_runners);
context.advisory_script_uri = "main.dart";
context.advisory_script_entrypoint = "main";
auto weak_isolate = DartIsolate::CreateRunningRootIsolate(
vm_data->GetSettings(), // settings
vm_data->GetIsolateSnapshot(), // isolate snapshot
nullptr, // platform configuration
DartIsolate::Flags{}, // flags
nullptr, // root_isolate_create_callback
settings.isolate_create_callback, // isolate create callback
settings.isolate_shutdown_callback, // isolate shutdown callback
"main", // dart entrypoint
std::nullopt, // dart entrypoint library
{}, // dart entrypoint arguments
std::move(isolate_configuration), // isolate configuration
context // engine context
);
auto root_isolate = weak_isolate.lock();
ASSERT_TRUE(root_isolate);
ASSERT_EQ(root_isolate->GetPhase(), DartIsolate::Phase::Running);
size_t destruction_callback_count = 0;
root_isolate->AddIsolateShutdownCallback([&destruction_callback_count]() {
ASSERT_NE(Dart_CurrentIsolate(), nullptr);
destruction_callback_count++;
});
ASSERT_TRUE(root_isolate->Shutdown());
ASSERT_EQ(destruction_callback_count, 1u);
}
TEST_F(DartIsolateTest, IsolateCanLoadAndRunDartCode) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
const auto settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(settings);
TaskRunners task_runners(GetCurrentTestName(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner() //
);
auto isolate = RunDartCodeInIsolate(vm_ref, settings, task_runners, "main",
{}, GetDefaultKernelFilePath());
ASSERT_TRUE(isolate);
ASSERT_EQ(isolate->get()->GetPhase(), DartIsolate::Phase::Running);
}
TEST_F(DartIsolateTest, IsolateCannotLoadAndRunUnknownDartEntrypoint) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
const auto settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(settings);
TaskRunners task_runners(GetCurrentTestName(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner() //
);
auto isolate =
RunDartCodeInIsolate(vm_ref, settings, task_runners, "thisShouldNotExist",
{}, GetDefaultKernelFilePath());
ASSERT_FALSE(isolate);
}
TEST_F(DartIsolateTest, CanRunDartCodeCodeSynchronously) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
const auto settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(settings);
TaskRunners task_runners(GetCurrentTestName(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner() //
);
auto isolate = RunDartCodeInIsolate(vm_ref, settings, task_runners, "main",
{}, GetDefaultKernelFilePath());
ASSERT_TRUE(isolate);
ASSERT_EQ(isolate->get()->GetPhase(), DartIsolate::Phase::Running);
ASSERT_TRUE(isolate->RunInIsolateScope([]() -> bool {
if (tonic::CheckAndHandleError(::Dart_Invoke(
Dart_RootLibrary(), tonic::ToDart("sayHi"), 0, nullptr))) {
return false;
}
return true;
}));
}
TEST_F(DartIsolateTest, ImpellerFlagIsCorrectWhenTrue) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
auto settings = CreateSettingsForFixture();
settings.enable_impeller = true;
auto vm_ref = DartVMRef::Create(settings);
TaskRunners task_runners(GetCurrentTestName(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner() //
);
auto isolate = RunDartCodeInIsolate(vm_ref, settings, task_runners, "main",
{}, GetDefaultKernelFilePath());
ASSERT_TRUE(isolate);
ASSERT_EQ(isolate->get()->GetPhase(), DartIsolate::Phase::Running);
ASSERT_TRUE(isolate->RunInIsolateScope([settings]() -> bool {
Dart_Handle dart_ui = ::Dart_LookupLibrary(tonic::ToDart("dart:ui"));
if (tonic::CheckAndHandleError(dart_ui)) {
return false;
}
Dart_Handle impeller_enabled =
::Dart_GetField(dart_ui, tonic::ToDart("_impellerEnabled"));
if (tonic::CheckAndHandleError(impeller_enabled)) {
return false;
}
bool result;
if (tonic::CheckAndHandleError(
Dart_BooleanValue(impeller_enabled, &result))) {
return false;
}
return result == settings.enable_impeller;
}));
}
TEST_F(DartIsolateTest, ImpellerFlagIsCorrectWhenFalse) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
auto settings = CreateSettingsForFixture();
settings.enable_impeller = false;
auto vm_ref = DartVMRef::Create(settings);
TaskRunners task_runners(GetCurrentTestName(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner() //
);
auto isolate = RunDartCodeInIsolate(vm_ref, settings, task_runners, "main",
{}, GetDefaultKernelFilePath());
ASSERT_TRUE(isolate);
ASSERT_EQ(isolate->get()->GetPhase(), DartIsolate::Phase::Running);
ASSERT_TRUE(isolate->RunInIsolateScope([settings]() -> bool {
Dart_Handle dart_ui = ::Dart_LookupLibrary(tonic::ToDart("dart:ui"));
if (tonic::CheckAndHandleError(dart_ui)) {
return false;
}
Dart_Handle impeller_enabled =
::Dart_GetField(dart_ui, tonic::ToDart("_impellerEnabled"));
if (tonic::CheckAndHandleError(impeller_enabled)) {
return false;
}
bool result;
if (tonic::CheckAndHandleError(
Dart_BooleanValue(impeller_enabled, &result))) {
return false;
}
return result == settings.enable_impeller;
}));
}
TEST_F(DartIsolateTest, CanRegisterNativeCallback) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
AddNativeCallback(
"NotifyNative",
CREATE_NATIVE_ENTRY(([this](Dart_NativeArguments args) { Signal(); })));
const auto settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(settings);
auto thread = CreateNewThread();
TaskRunners task_runners(GetCurrentTestName(), //
thread, //
thread, //
thread, //
thread //
);
auto isolate = RunDartCodeInIsolate(vm_ref, settings, task_runners,
"canRegisterNativeCallback", {},
GetDefaultKernelFilePath());
ASSERT_TRUE(isolate);
ASSERT_EQ(isolate->get()->GetPhase(), DartIsolate::Phase::Running);
Wait();
}
class DartSecondaryIsolateTest : public FixtureTest {
public:
DartSecondaryIsolateTest() : latch_(3) {}
void LatchCountDown() { latch_.CountDown(); }
void LatchWait() { latch_.Wait(); }
void ChildShutdownSignal() { child_shutdown_latch_.Signal(); }
void ChildShutdownWait() { child_shutdown_latch_.Wait(); }
void RootIsolateShutdownSignal() { root_isolate_shutdown_latch_.Signal(); }
bool RootIsolateIsSignaled() {
return root_isolate_shutdown_latch_.IsSignaledForTest();
}
private:
fml::CountDownLatch latch_;
fml::AutoResetWaitableEvent child_shutdown_latch_;
fml::AutoResetWaitableEvent root_isolate_shutdown_latch_;
FML_DISALLOW_COPY_AND_ASSIGN(DartSecondaryIsolateTest);
};
TEST_F(DartSecondaryIsolateTest, CanLaunchSecondaryIsolates) {
AddNativeCallback("NotifyNative",
CREATE_NATIVE_ENTRY(([this](Dart_NativeArguments args) {
LatchCountDown();
})));
AddNativeCallback(
"PassMessage", CREATE_NATIVE_ENTRY(([this](Dart_NativeArguments args) {
auto message = tonic::DartConverter<std::string>::FromDart(
Dart_GetNativeArgument(args, 0));
ASSERT_EQ("Hello from code is secondary isolate.", message);
LatchCountDown();
})));
auto settings = CreateSettingsForFixture();
settings.root_isolate_shutdown_callback = [this]() {
RootIsolateShutdownSignal();
};
settings.isolate_shutdown_callback = [this]() { ChildShutdownSignal(); };
auto vm_ref = DartVMRef::Create(settings);
auto thread = CreateNewThread();
TaskRunners task_runners(GetCurrentTestName(), //
thread, //
thread, //
thread, //
thread //
);
auto isolate = RunDartCodeInIsolate(vm_ref, settings, task_runners,
"testCanLaunchSecondaryIsolate", {},
GetDefaultKernelFilePath());
ASSERT_TRUE(isolate);
ASSERT_EQ(isolate->get()->GetPhase(), DartIsolate::Phase::Running);
ChildShutdownWait(); // wait for child isolate to shutdown first
ASSERT_FALSE(RootIsolateIsSignaled());
LatchWait(); // wait for last NotifyNative called by main isolate
// root isolate will be auto-shutdown
}
/// Tests error handling path of `Isolate.spawn()` in the engine.
class IsolateStartupFailureTest : public FixtureTest {
public:
IsolateStartupFailureTest() : latch_(1) {}
void NotifyDone() { latch_.CountDown(); }
void WaitForDone() { latch_.Wait(); }
private:
fml::CountDownLatch latch_;
FML_DISALLOW_COPY_AND_ASSIGN(IsolateStartupFailureTest);
};
TEST_F(IsolateStartupFailureTest,
HandlesIsolateInitializationFailureCorrectly) {
AddNativeCallback("MakeNextIsolateSpawnFail",
CREATE_NATIVE_ENTRY(([](Dart_NativeArguments args) {
Dart_SetRootLibrary(Dart_Null());
})));
AddNativeCallback("NotifyNative",
CREATE_NATIVE_ENTRY(
([this](Dart_NativeArguments args) { NotifyDone(); })));
auto settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(settings);
auto thread = CreateNewThread();
TaskRunners task_runners(GetCurrentTestName(), //
thread, //
thread, //
thread, //
thread //
);
auto isolate = RunDartCodeInIsolate(vm_ref, settings, task_runners,
"testIsolateStartupFailure", {},
GetDefaultKernelFilePath());
ASSERT_TRUE(isolate);
ASSERT_EQ(isolate->get()->GetPhase(), DartIsolate::Phase::Running);
WaitForDone();
}
TEST_F(DartIsolateTest, CanReceiveArguments) {
AddNativeCallback("NotifyNative",
CREATE_NATIVE_ENTRY(([this](Dart_NativeArguments args) {
ASSERT_TRUE(tonic::DartConverter<bool>::FromDart(
Dart_GetNativeArgument(args, 0)));
Signal();
})));
const auto settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(settings);
auto thread = CreateNewThread();
TaskRunners task_runners(GetCurrentTestName(), //
thread, //
thread, //
thread, //
thread //
);
auto isolate = RunDartCodeInIsolate(vm_ref, settings, task_runners,
"testCanReceiveArguments", {"arg1"},
GetDefaultKernelFilePath());
ASSERT_TRUE(isolate);
ASSERT_EQ(isolate->get()->GetPhase(), DartIsolate::Phase::Running);
Wait();
}
TEST_F(DartIsolateTest, CanCreateServiceIsolate) {
#if (FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_DEBUG) && \
(FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_PROFILE)
GTEST_SKIP();
#endif
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
fml::AutoResetWaitableEvent service_isolate_latch;
auto settings = CreateSettingsForFixture();
settings.enable_vm_service = true;
settings.vm_service_port = 0;
settings.vm_service_host = "127.0.0.1";
settings.enable_service_port_fallback = true;
settings.service_isolate_create_callback = [&service_isolate_latch]() {
service_isolate_latch.Signal();
};
auto vm_ref = DartVMRef::Create(settings);
ASSERT_TRUE(vm_ref);
auto vm_data = vm_ref.GetVMData();
ASSERT_TRUE(vm_data);
TaskRunners task_runners(GetCurrentTestName(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner() //
);
auto isolate_configuration =
IsolateConfiguration::InferFromSettings(settings);
UIDartState::Context context(task_runners);
context.advisory_script_uri = "main.dart";
context.advisory_script_entrypoint = "main";
auto weak_isolate = DartIsolate::CreateRunningRootIsolate(
vm_data->GetSettings(), // settings
vm_data->GetIsolateSnapshot(), // isolate snapshot
nullptr, // platform configuration
DartIsolate::Flags{}, // flags
nullptr, // root_isolate_create_callback
settings.isolate_create_callback, // isolate create callback
settings.isolate_shutdown_callback, // isolate shutdown callback
"main", // dart entrypoint
std::nullopt, // dart entrypoint library
{}, // dart entrypoint arguments
std::move(isolate_configuration), // isolate configuration
context // engine context
);
auto root_isolate = weak_isolate.lock();
ASSERT_TRUE(root_isolate);
ASSERT_EQ(root_isolate->GetPhase(), DartIsolate::Phase::Running);
service_isolate_latch.Wait();
ASSERT_TRUE(root_isolate->Shutdown());
}
TEST_F(DartIsolateTest,
RootIsolateCreateCallbackIsMadeOnceAndBeforeIsolateRunning) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
auto settings = CreateSettingsForFixture();
size_t create_callback_count = 0u;
settings.root_isolate_create_callback =
[&create_callback_count](const auto& isolate) {
ASSERT_EQ(isolate.GetPhase(), DartIsolate::Phase::Ready);
create_callback_count++;
ASSERT_NE(::Dart_CurrentIsolate(), nullptr);
};
auto vm_ref = DartVMRef::Create(settings);
TaskRunners task_runners(GetCurrentTestName(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner() //
);
{
auto isolate = RunDartCodeInIsolate(vm_ref, settings, task_runners, "main",
{}, GetDefaultKernelFilePath());
ASSERT_TRUE(isolate);
ASSERT_EQ(isolate->get()->GetPhase(), DartIsolate::Phase::Running);
}
ASSERT_EQ(create_callback_count, 1u);
}
TEST_F(DartIsolateTest,
IsolateCreateCallbacksTakeInstanceSettingsInsteadOfVMSettings) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
auto vm_settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(vm_settings);
auto instance_settings = vm_settings;
size_t create_callback_count = 0u;
instance_settings.root_isolate_create_callback =
[&create_callback_count](const auto& isolate) {
ASSERT_EQ(isolate.GetPhase(), DartIsolate::Phase::Ready);
create_callback_count++;
ASSERT_NE(::Dart_CurrentIsolate(), nullptr);
};
TaskRunners task_runners(GetCurrentTestName(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner() //
);
{
auto isolate = RunDartCodeInIsolate(vm_ref, instance_settings, task_runners,
"main", {}, GetDefaultKernelFilePath());
ASSERT_TRUE(isolate);
ASSERT_EQ(isolate->get()->GetPhase(), DartIsolate::Phase::Running);
}
ASSERT_EQ(create_callback_count, 1u);
}
TEST_F(DartIsolateTest, InvalidLoadingUnitFails) {
if (!DartVM::IsRunningPrecompiledCode()) {
FML_LOG(INFO) << "Split AOT does not work in JIT mode";
return;
}
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
auto settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(settings);
ASSERT_TRUE(vm_ref);
auto vm_data = vm_ref.GetVMData();
ASSERT_TRUE(vm_data);
TaskRunners task_runners(GetCurrentTestName(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner() //
);
auto isolate_configuration =
IsolateConfiguration::InferFromSettings(settings);
UIDartState::Context context(task_runners);
context.advisory_script_uri = "main.dart";
context.advisory_script_entrypoint = "main";
auto weak_isolate = DartIsolate::CreateRunningRootIsolate(
vm_data->GetSettings(), // settings
vm_data->GetIsolateSnapshot(), // isolate snapshot
nullptr, // platform configuration
DartIsolate::Flags{}, // flags
nullptr, // root_isolate_create_callback
settings.isolate_create_callback, // isolate create callback
settings.isolate_shutdown_callback, // isolate shutdown callback
"main", // dart entrypoint
std::nullopt, // dart entrypoint library
{}, // dart entrypoint arguments
std::move(isolate_configuration), // isolate configuration
context // engine context
);
auto root_isolate = weak_isolate.lock();
ASSERT_TRUE(root_isolate);
ASSERT_EQ(root_isolate->GetPhase(), DartIsolate::Phase::Running);
auto isolate_data = std::make_unique<const fml::NonOwnedMapping>(
split_aot_symbols_.vm_isolate_data, 0);
auto isolate_instructions = std::make_unique<const fml::NonOwnedMapping>(
split_aot_symbols_.vm_isolate_instrs, 0);
// Invalid loading unit should fail gracefully with error message.
ASSERT_FALSE(root_isolate->LoadLoadingUnit(3, std::move(isolate_data),
std::move(isolate_instructions)));
ASSERT_TRUE(root_isolate->Shutdown());
}
TEST_F(DartIsolateTest, DartPluginRegistrantIsCalled) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
std::vector<std::string> messages;
fml::AutoResetWaitableEvent latch;
AddNativeCallback(
"PassMessage",
CREATE_NATIVE_ENTRY(([&latch, &messages](Dart_NativeArguments args) {
auto message = tonic::DartConverter<std::string>::FromDart(
Dart_GetNativeArgument(args, 0));
messages.push_back(message);
latch.Signal();
})));
const auto settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(settings);
auto thread = CreateNewThread();
TaskRunners task_runners(GetCurrentTestName(), //
thread, //
thread, //
thread, //
thread //
);
auto isolate = RunDartCodeInIsolate(vm_ref, settings, task_runners,
"mainForPluginRegistrantTest", {},
GetDefaultKernelFilePath());
ASSERT_TRUE(isolate);
ASSERT_EQ(isolate->get()->GetPhase(), DartIsolate::Phase::Running);
latch.Wait();
ASSERT_EQ(messages.size(), 1u);
ASSERT_EQ(messages[0], "_PluginRegistrant.register() was called");
}
TEST_F(DartIsolateTest, SpawningAnIsolateDoesNotReloadKernel) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
auto settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(settings);
ASSERT_TRUE(vm_ref);
auto vm_data = vm_ref.GetVMData();
ASSERT_TRUE(vm_data);
TaskRunners task_runners(GetCurrentTestName(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner(), //
GetCurrentTaskRunner() //
);
size_t get_kernel_count = 0u;
if (!DartVM::IsRunningPrecompiledCode()) {
ASSERT_TRUE(settings.application_kernels);
auto mappings = settings.application_kernels();
ASSERT_EQ(mappings.size(), 1u);
// This feels a little brittle, but the alternative seems to be making
// DartIsolate have virtual methods so it can be mocked or exposing weird
// test-only API on IsolateConfiguration.
settings.application_kernels = fml::MakeCopyable(
[&get_kernel_count,
mapping = std::move(mappings.front())]() mutable -> Mappings {
get_kernel_count++;
EXPECT_EQ(get_kernel_count, 1u)
<< "Unexpectedly got more than one call for the kernel mapping.";
EXPECT_TRUE(mapping);
std::vector<std::unique_ptr<const fml::Mapping>> kernel_mappings;
if (mapping) {
kernel_mappings.emplace_back(std::move(mapping));
}
return kernel_mappings;
});
}
std::shared_ptr<DartIsolate> root_isolate;
{
auto isolate_configuration =
IsolateConfiguration::InferFromSettings(settings);
UIDartState::Context context(task_runners);
context.advisory_script_uri = "main.dart";
context.advisory_script_entrypoint = "main";
auto weak_isolate = DartIsolate::CreateRunningRootIsolate(
/*settings=*/vm_data->GetSettings(),
/*isolate_snapshot=*/vm_data->GetIsolateSnapshot(),
/*platform_configuration=*/nullptr,
/*flags=*/DartIsolate::Flags{},
/*root_isolate_create_callback=*/nullptr,
/*isolate_create_callback=*/settings.isolate_create_callback,
/*isolate_shutdown_callback=*/settings.isolate_shutdown_callback,
/*dart_entrypoint=*/"main",
/*dart_entrypoint_library=*/std::nullopt,
/*dart_entrypoint_args=*/{},
/*isolate_configuration=*/std::move(isolate_configuration),
/*context=*/context);
root_isolate = weak_isolate.lock();
}
ASSERT_TRUE(root_isolate);
ASSERT_EQ(root_isolate->GetPhase(), DartIsolate::Phase::Running);
if (!DartVM::IsRunningPrecompiledCode()) {
ASSERT_EQ(get_kernel_count, 1u);
}
{
auto isolate_configuration = IsolateConfiguration::InferFromSettings(
/*settings=*/settings,
/*asset_manager=*/nullptr,
/*io_worker=*/nullptr,
/*launch_type=*/IsolateLaunchType::kExistingGroup);
UIDartState::Context context(task_runners);
context.advisory_script_uri = "main.dart";
context.advisory_script_entrypoint = "main";
auto weak_isolate = DartIsolate::CreateRunningRootIsolate(
/*settings=*/vm_data->GetSettings(),
/*isolate_snapshot=*/vm_data->GetIsolateSnapshot(),
/*platform_configuration=*/nullptr,
/*flags=*/DartIsolate::Flags{},
/*root_isolate_create_callback=*/nullptr,
/*isolate_create_callback=*/settings.isolate_create_callback,
/*isolate_shutdown_callback=*/settings.isolate_shutdown_callback,
/*dart_entrypoint=*/"main",
/*dart_entrypoint_library=*/std::nullopt,
/*dart_entrypoint_args=*/{},
/*isolate_configuration=*/std::move(isolate_configuration),
/*context=*/context,
/*spawning_isolate=*/root_isolate.get());
auto spawned_isolate = weak_isolate.lock();
ASSERT_TRUE(spawned_isolate);
ASSERT_EQ(spawned_isolate->GetPhase(), DartIsolate::Phase::Running);
if (!DartVM::IsRunningPrecompiledCode()) {
ASSERT_EQ(get_kernel_count, 1u);
}
ASSERT_TRUE(spawned_isolate->Shutdown());
}
ASSERT_TRUE(root_isolate->Shutdown());
}
class FakePlatformConfigurationClient : public PlatformConfigurationClient {
public:
std::shared_ptr<PlatformIsolateManager> mgr =
std::shared_ptr<PlatformIsolateManager>(new PlatformIsolateManager());
std::shared_ptr<PlatformIsolateManager> GetPlatformIsolateManager() override {
return mgr;
}
std::string DefaultRouteName() override { return ""; }
void ScheduleFrame() override {}
void EndWarmUpFrame() override {}
void Render(int64_t view_id,
Scene* scene,
double width,
double height) override {}
void UpdateSemantics(SemanticsUpdate* update) override {}
void HandlePlatformMessage(
std::unique_ptr<PlatformMessage> message) override {}
FontCollection& GetFontCollection() override {
FML_UNREACHABLE();
return *(FontCollection*)(this);
}
std::shared_ptr<AssetManager> GetAssetManager() override { return nullptr; }
void UpdateIsolateDescription(const std::string isolate_name,
int64_t isolate_port) override {}
void SetNeedsReportTimings(bool value) override {}
std::shared_ptr<const fml::Mapping> GetPersistentIsolateData() override {
return nullptr;
}
std::unique_ptr<std::vector<std::string>> ComputePlatformResolvedLocale(
const std::vector<std::string>& supported_locale_data) override {
return nullptr;
}
void RequestDartDeferredLibrary(intptr_t loading_unit_id) override {}
void SendChannelUpdate(std::string name, bool listening) override {}
double GetScaledFontSize(double unscaled_font_size,
int configuration_id) const override {
return 0;
}
};
TEST_F(DartIsolateTest, PlatformIsolateCreationAndShutdown) {
fml::AutoResetWaitableEvent message_latch;
AddNativeCallback(
"PassMessage",
CREATE_NATIVE_ENTRY(([&message_latch](Dart_NativeArguments args) {
auto message = tonic::DartConverter<std::string>::FromDart(
Dart_GetNativeArgument(args, 0));
ASSERT_EQ("Platform isolate is ready", message);
message_latch.Signal();
})));
FakePlatformConfigurationClient client;
auto platform_configuration =
std::make_unique<PlatformConfiguration>(&client);
Dart_Isolate platform_isolate = nullptr;
{
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
auto settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(settings);
ASSERT_TRUE(vm_ref);
auto vm_data = vm_ref.GetVMData();
ASSERT_TRUE(vm_data);
auto platform_thread = CreateNewThread();
auto ui_thread = CreateNewThread();
TaskRunners task_runners(GetCurrentTestName(), // label
platform_thread, // platform
ui_thread, // raster
ui_thread, // ui
ui_thread // io
);
auto isolate =
RunDartCodeInIsolate(vm_ref, settings, task_runners, "emptyMain", {},
GetDefaultKernelFilePath(), {}, nullptr,
std::move(platform_configuration));
ASSERT_TRUE(isolate);
auto root_isolate = isolate->get();
ASSERT_EQ(root_isolate->GetPhase(), DartIsolate::Phase::Running);
EXPECT_FALSE(
client.mgr->IsRegisteredForTestingOnly(root_isolate->isolate()));
// Post a task to the platform_thread that just waits, to delay execution of
// the platform isolate until we're ready.
fml::AutoResetWaitableEvent platform_thread_latch;
fml::TaskRunner::RunNowOrPostTask(
platform_thread, fml::MakeCopyable([&platform_thread_latch]() mutable {
platform_thread_latch.Wait();
}));
fml::AutoResetWaitableEvent ui_thread_latch;
fml::TaskRunner::RunNowOrPostTask(
ui_thread, fml::MakeCopyable([&]() mutable {
ASSERT_TRUE(
isolate->RunInIsolateScope([root_isolate, &platform_isolate]() {
Dart_Handle lib = Dart_RootLibrary();
Dart_Handle entry_point = Dart_GetField(
lib, tonic::ToDart("mainForPlatformIsolates"));
char* error = nullptr;
platform_isolate =
root_isolate->CreatePlatformIsolate(entry_point, &error);
EXPECT_FALSE(error);
EXPECT_TRUE(platform_isolate);
EXPECT_EQ(Dart_CurrentIsolate(), root_isolate->isolate());
return true;
}));
ui_thread_latch.Signal();
}));
ui_thread_latch.Wait();
ASSERT_TRUE(platform_isolate);
EXPECT_TRUE(client.mgr->IsRegisteredForTestingOnly(platform_isolate));
// Allow the platform isolate to run.
platform_thread_latch.Signal();
// Wait for a message from the platform isolate.
message_latch.Wait();
// root isolate will be auto-shutdown
}
EXPECT_FALSE(client.mgr->IsRegisteredForTestingOnly(platform_isolate));
}
TEST_F(DartIsolateTest, PlatformIsolateEarlyShutdown) {
FakePlatformConfigurationClient client;
auto platform_configuration =
std::make_unique<PlatformConfiguration>(&client);
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
auto settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(settings);
ASSERT_TRUE(vm_ref);
auto vm_data = vm_ref.GetVMData();
ASSERT_TRUE(vm_data);
auto platform_thread = CreateNewThread();
auto ui_thread = CreateNewThread();
TaskRunners task_runners(GetCurrentTestName(), // label
platform_thread, // platform
ui_thread, // raster
ui_thread, // ui
ui_thread // io
);
auto isolate =
RunDartCodeInIsolate(vm_ref, settings, task_runners, "emptyMain", {},
GetDefaultKernelFilePath(), {}, nullptr,
std::move(platform_configuration));
ASSERT_TRUE(isolate);
auto root_isolate = isolate->get();
ASSERT_EQ(root_isolate->GetPhase(), DartIsolate::Phase::Running);
EXPECT_FALSE(client.mgr->IsRegisteredForTestingOnly(root_isolate->isolate()));
fml::AutoResetWaitableEvent ui_thread_latch;
Dart_Isolate platform_isolate = nullptr;
fml::TaskRunner::RunNowOrPostTask(
ui_thread, fml::MakeCopyable([&]() mutable {
ASSERT_TRUE(
isolate->RunInIsolateScope([root_isolate, &platform_isolate]() {
Dart_Handle lib = Dart_RootLibrary();
Dart_Handle entry_point =
Dart_GetField(lib, tonic::ToDart("emptyMain"));
char* error = nullptr;
platform_isolate =
root_isolate->CreatePlatformIsolate(entry_point, &error);
EXPECT_FALSE(error);
EXPECT_TRUE(platform_isolate);
EXPECT_EQ(Dart_CurrentIsolate(), root_isolate->isolate());
return true;
}));
ui_thread_latch.Signal();
}));
ui_thread_latch.Wait();
ASSERT_TRUE(platform_isolate);
EXPECT_TRUE(client.mgr->IsRegisteredForTestingOnly(platform_isolate));
// Post a task to the platform thread to shut down the platform isolate.
fml::AutoResetWaitableEvent platform_thread_latch;
fml::TaskRunner::RunNowOrPostTask(
platform_thread,
fml::MakeCopyable([&platform_thread_latch, platform_isolate]() mutable {
Dart_EnterIsolate(platform_isolate);
Dart_ShutdownIsolate();
platform_thread_latch.Signal();
}));
platform_thread_latch.Wait();
// The platform isolate should be shut down.
EXPECT_FALSE(client.mgr->IsRegisteredForTestingOnly(platform_isolate));
// root isolate will be auto-shutdown
}
TEST_F(DartIsolateTest, PlatformIsolateSendAndReceive) {
fml::AutoResetWaitableEvent message_latch;
AddNativeCallback(
"PassMessage",
CREATE_NATIVE_ENTRY(([&message_latch](Dart_NativeArguments args) {
auto message = tonic::DartConverter<std::string>::FromDart(
Dart_GetNativeArgument(args, 0));
ASSERT_EQ("Platform isolate received: Hello from root isolate!",
message);
message_latch.Signal();
})));
FakePlatformConfigurationClient client;
auto platform_configuration =
std::make_unique<PlatformConfiguration>(&client);
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
auto settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(settings);
ASSERT_TRUE(vm_ref);
auto vm_data = vm_ref.GetVMData();
ASSERT_TRUE(vm_data);
auto platform_thread = CreateNewThread();
auto ui_thread = CreateNewThread();
TaskRunners task_runners(GetCurrentTestName(), // label
platform_thread, // platform
ui_thread, // raster
ui_thread, // ui
ui_thread // io
);
auto isolate =
RunDartCodeInIsolate(vm_ref, settings, task_runners, "emptyMain", {},
GetDefaultKernelFilePath(), {}, nullptr,
std::move(platform_configuration));
ASSERT_TRUE(isolate);
auto root_isolate = isolate->get();
ASSERT_EQ(root_isolate->GetPhase(), DartIsolate::Phase::Running);
fml::AutoResetWaitableEvent ui_thread_latch;
Dart_Isolate platform_isolate = nullptr;
fml::TaskRunner::RunNowOrPostTask(
ui_thread, fml::MakeCopyable([&]() mutable {
ASSERT_TRUE(isolate->RunInIsolateScope([root_isolate,
&platform_isolate]() {
Dart_Handle lib = Dart_RootLibrary();
Dart_Handle entry_point = Dart_Invoke(
lib, tonic::ToDart("createEntryPointForPlatIsoSendAndRecvTest"),
0, nullptr);
char* error = nullptr;
platform_isolate =
root_isolate->CreatePlatformIsolate(entry_point, &error);
EXPECT_FALSE(error);
return true;
}));
ui_thread_latch.Signal();
}));
ui_thread_latch.Wait();
// Wait for a message from the platform isolate.
message_latch.Wait();
// Post a task to the platform_thread that runs after the platform isolate's
// entry point and all messages, and wait for it to run.
fml::AutoResetWaitableEvent epilogue_latch;
fml::TaskRunner::RunNowOrPostTask(
platform_thread, fml::MakeCopyable([&epilogue_latch]() mutable {
epilogue_latch.Signal();
}));
epilogue_latch.Wait();
// root isolate will be auto-shutdown
}
TEST_F(DartIsolateTest, PlatformIsolateCreationAfterManagerShutdown) {
AddNativeCallback("PassMessage",
CREATE_NATIVE_ENTRY((
[](Dart_NativeArguments args) { FML_UNREACHABLE(); })));
FakePlatformConfigurationClient client;
auto platform_configuration =
std::make_unique<PlatformConfiguration>(&client);
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
auto settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(settings);
ASSERT_TRUE(vm_ref);
auto vm_data = vm_ref.GetVMData();
ASSERT_TRUE(vm_data);
auto platform_thread = CreateNewThread();
auto ui_thread = CreateNewThread();
TaskRunners task_runners(GetCurrentTestName(), // label
platform_thread, // platform
ui_thread, // raster
ui_thread, // ui
ui_thread // io
);
auto isolate =
RunDartCodeInIsolate(vm_ref, settings, task_runners, "emptyMain", {},
GetDefaultKernelFilePath(), {}, nullptr,
std::move(platform_configuration));
ASSERT_TRUE(isolate);
auto root_isolate = isolate->get();
ASSERT_EQ(root_isolate->GetPhase(), DartIsolate::Phase::Running);
// Shut down the manager on the platform thread.
fml::AutoResetWaitableEvent manager_shutdown_latch;
fml::TaskRunner::RunNowOrPostTask(
platform_thread,
fml::MakeCopyable([&manager_shutdown_latch, &client]() mutable {
client.mgr->ShutdownPlatformIsolates();
manager_shutdown_latch.Signal();
}));
manager_shutdown_latch.Wait();
fml::AutoResetWaitableEvent ui_thread_latch;
fml::TaskRunner::RunNowOrPostTask(
ui_thread, fml::MakeCopyable([&]() mutable {
ASSERT_TRUE(isolate->RunInIsolateScope([root_isolate]() {
Dart_Handle lib = Dart_RootLibrary();
Dart_Handle entry_point =
Dart_GetField(lib, tonic::ToDart("mainForPlatformIsolates"));
char* error = nullptr;
Dart_Isolate platform_isolate =
root_isolate->CreatePlatformIsolate(entry_point, &error);
// Failed to create a platform isolate, but we've still re-entered the
// root isolate.
EXPECT_FALSE(error);
EXPECT_FALSE(platform_isolate);
EXPECT_EQ(Dart_CurrentIsolate(), root_isolate->isolate());
return true;
}));
ui_thread_latch.Signal();
}));
ui_thread_latch.Wait();
// root isolate will be auto-shutdown
}
TEST_F(DartIsolateTest, PlatformIsolateManagerShutdownBeforeMainRuns) {
AddNativeCallback("PassMessage",
CREATE_NATIVE_ENTRY((
[](Dart_NativeArguments args) { FML_UNREACHABLE(); })));
FakePlatformConfigurationClient client;
auto platform_configuration =
std::make_unique<PlatformConfiguration>(&client);
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
auto settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(settings);
ASSERT_TRUE(vm_ref);
auto vm_data = vm_ref.GetVMData();
ASSERT_TRUE(vm_data);
auto platform_thread = CreateNewThread();
auto ui_thread = CreateNewThread();
TaskRunners task_runners(GetCurrentTestName(), // label
platform_thread, // platform
ui_thread, // raster
ui_thread, // ui
ui_thread // io
);
auto isolate =
RunDartCodeInIsolate(vm_ref, settings, task_runners, "emptyMain", {},
GetDefaultKernelFilePath(), {}, nullptr,
std::move(platform_configuration));
ASSERT_TRUE(isolate);
auto root_isolate = isolate->get();
ASSERT_EQ(root_isolate->GetPhase(), DartIsolate::Phase::Running);
Dart_Isolate platform_isolate = nullptr;
// Post a task to the platform_thread that just waits, to delay execution of
// the platform isolate until we're ready, and shutdown the manager just
// before it runs.
fml::AutoResetWaitableEvent platform_thread_latch;
fml::TaskRunner::RunNowOrPostTask(
platform_thread, fml::MakeCopyable([&platform_thread_latch, &client,
&platform_isolate]() mutable {
platform_thread_latch.Wait();
client.mgr->ShutdownPlatformIsolates();
EXPECT_TRUE(platform_isolate);
EXPECT_FALSE(client.mgr->IsRegisteredForTestingOnly(platform_isolate));
}));
fml::AutoResetWaitableEvent ui_thread_latch;
fml::TaskRunner::RunNowOrPostTask(
ui_thread, fml::MakeCopyable([&]() mutable {
ASSERT_TRUE(
isolate->RunInIsolateScope([root_isolate, &platform_isolate]() {
Dart_Handle lib = Dart_RootLibrary();
Dart_Handle entry_point =
Dart_GetField(lib, tonic::ToDart("mainForPlatformIsolates"));
char* error = nullptr;
platform_isolate =
root_isolate->CreatePlatformIsolate(entry_point, &error);
EXPECT_FALSE(error);
EXPECT_TRUE(platform_isolate);
EXPECT_EQ(Dart_CurrentIsolate(), root_isolate->isolate());
return true;
}));
ui_thread_latch.Signal();
}));
ui_thread_latch.Wait();
ASSERT_TRUE(platform_isolate);
EXPECT_TRUE(client.mgr->IsRegisteredForTestingOnly(platform_isolate));
// Allow the platform isolate to run, but its main is never run.
platform_thread_latch.Signal();
// Post a task to the platform_thread that runs after the platform isolate's
// entry point, and wait for it to run.
fml::AutoResetWaitableEvent epilogue_latch;
fml::TaskRunner::RunNowOrPostTask(
platform_thread, fml::MakeCopyable([&epilogue_latch]() mutable {
epilogue_latch.Signal();
}));
epilogue_latch.Wait();
// root isolate will be auto-shutdown
}
TEST_F(DartIsolateTest, PlatformIsolateMainThrowsError) {
AddNativeCallback("PassMessage",
CREATE_NATIVE_ENTRY((
[](Dart_NativeArguments args) { FML_UNREACHABLE(); })));
FakePlatformConfigurationClient client;
auto platform_configuration =
std::make_unique<PlatformConfiguration>(&client);
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
auto settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(settings);
ASSERT_TRUE(vm_ref);
auto vm_data = vm_ref.GetVMData();
ASSERT_TRUE(vm_data);
auto platform_thread = CreateNewThread();
auto ui_thread = CreateNewThread();
TaskRunners task_runners(GetCurrentTestName(), // label
platform_thread, // platform
ui_thread, // raster
ui_thread, // ui
ui_thread // io
);
auto isolate =
RunDartCodeInIsolate(vm_ref, settings, task_runners, "emptyMain", {},
GetDefaultKernelFilePath(), {}, nullptr,
std::move(platform_configuration));
ASSERT_TRUE(isolate);
auto root_isolate = isolate->get();
ASSERT_EQ(root_isolate->GetPhase(), DartIsolate::Phase::Running);
Dart_Isolate platform_isolate = nullptr;
fml::AutoResetWaitableEvent ui_thread_latch;
fml::TaskRunner::RunNowOrPostTask(
ui_thread, fml::MakeCopyable([&]() mutable {
ASSERT_TRUE(
isolate->RunInIsolateScope([root_isolate, &platform_isolate]() {
Dart_Handle lib = Dart_RootLibrary();
Dart_Handle entry_point = Dart_GetField(
lib, tonic::ToDart("mainForPlatformIsolatesThrowError"));
char* error = nullptr;
platform_isolate =
root_isolate->CreatePlatformIsolate(entry_point, &error);
EXPECT_FALSE(error);
EXPECT_TRUE(platform_isolate);
EXPECT_EQ(Dart_CurrentIsolate(), root_isolate->isolate());
return true;
}));
ui_thread_latch.Signal();
}));
ui_thread_latch.Wait();
ASSERT_TRUE(platform_isolate);
EXPECT_TRUE(client.mgr->IsRegisteredForTestingOnly(platform_isolate));
// Post a task to the platform_thread that runs after the platform isolate's
// entry point, and wait for it to run.
fml::AutoResetWaitableEvent epilogue_latch;
fml::TaskRunner::RunNowOrPostTask(
platform_thread, fml::MakeCopyable([&epilogue_latch]() mutable {
epilogue_latch.Signal();
}));
epilogue_latch.Wait();
// root isolate will be auto-shutdown
}
} // namespace testing
} // namespace flutter
// NOLINTEND(clang-analyzer-core.StackAddressEscape)