| // 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) |