// 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. #define FML_USED_ON_EMBEDDER #include #include "embedder.h" #include "flutter/fml/file.h" #include "flutter/fml/make_copyable.h" #include "flutter/fml/mapping.h" #include "flutter/fml/message_loop.h" #include "flutter/fml/synchronization/waitable_event.h" #include "flutter/fml/thread.h" #include "flutter/runtime/dart_vm.h" #include "flutter/shell/platform/embedder/tests/embedder_config_builder.h" #include "flutter/shell/platform/embedder/tests/embedder_test.h" #include "flutter/testing/testing.h" #include "third_party/tonic/converter/dart_converter.h" namespace flutter { namespace testing { using EmbedderTest = testing::EmbedderTest; TEST(EmbedderTestNoFixture, MustNotRunWithInvalidArgs) { EmbedderContext context; EmbedderConfigBuilder builder( context, EmbedderConfigBuilder::InitializationPreference::kNoInitialize); auto engine = builder.LaunchEngine(); ASSERT_FALSE(engine.is_valid()); } TEST_F(EmbedderTest, CanLaunchAndShutdownWithValidProjectArgs) { auto& context = GetEmbedderContext(); fml::AutoResetWaitableEvent latch; context.AddIsolateCreateCallback([&latch]() { latch.Signal(); }); EmbedderConfigBuilder builder(context); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); // Wait for the root isolate to launch. latch.Wait(); engine.reset(); } TEST_F(EmbedderTest, CanLaunchAndShutdownMultipleTimes) { EmbedderConfigBuilder builder(GetEmbedderContext()); for (size_t i = 0; i < 3; ++i) { auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); FML_LOG(INFO) << "Engine launch count: " << i + 1; } } TEST_F(EmbedderTest, CanInvokeCustomEntrypoint) { auto& context = GetEmbedderContext(); static fml::AutoResetWaitableEvent latch; Dart_NativeFunction entrypoint = [](Dart_NativeArguments args) { latch.Signal(); }; context.AddNativeCallback("SayHiFromCustomEntrypoint", entrypoint); EmbedderConfigBuilder builder(context); builder.SetDartEntrypoint("customEntrypoint"); auto engine = builder.LaunchEngine(); latch.Wait(); ASSERT_TRUE(engine.is_valid()); } TEST_F(EmbedderTest, CanInvokeCustomEntrypointMacro) { auto& context = GetEmbedderContext(); fml::AutoResetWaitableEvent latch1; fml::AutoResetWaitableEvent latch2; fml::AutoResetWaitableEvent latch3; // Can be defined separately. auto entry1 = [&latch1](Dart_NativeArguments args) { FML_LOG(INFO) << "In Callback 1"; latch1.Signal(); }; auto native_entry1 = CREATE_NATIVE_ENTRY(entry1); context.AddNativeCallback("SayHiFromCustomEntrypoint1", native_entry1); // Can be wrapped in in the args. auto entry2 = [&latch2](Dart_NativeArguments args) { FML_LOG(INFO) << "In Callback 2"; latch2.Signal(); }; context.AddNativeCallback("SayHiFromCustomEntrypoint2", CREATE_NATIVE_ENTRY(entry2)); // Everything can be inline. context.AddNativeCallback( "SayHiFromCustomEntrypoint3", CREATE_NATIVE_ENTRY([&latch3](Dart_NativeArguments args) { FML_LOG(INFO) << "In Callback 3"; latch3.Signal(); })); EmbedderConfigBuilder builder(context); builder.SetDartEntrypoint("customEntrypoint1"); auto engine = builder.LaunchEngine(); latch1.Wait(); latch2.Wait(); latch3.Wait(); ASSERT_TRUE(engine.is_valid()); } class EmbedderTestTaskRunner { public: EmbedderTestTaskRunner(std::function on_forward_task) : on_forward_task_(on_forward_task) {} void SetForwardingTaskRunner(fml::RefPtr runner) { forwarding_target_ = std::move(runner); } FlutterTaskRunnerDescription GetEmbedderDescription() { FlutterTaskRunnerDescription desc; desc.struct_size = sizeof(desc); desc.user_data = this; desc.runs_task_on_current_thread_callback = [](void* user_data) -> bool { return reinterpret_cast(user_data) ->forwarding_target_->RunsTasksOnCurrentThread(); }; desc.post_task_callback = [](FlutterTask task, uint64_t target_time_nanos, void* user_data) -> void { auto runner = reinterpret_cast(user_data); auto target_time = fml::TimePoint::FromEpochDelta( fml::TimeDelta::FromNanoseconds(target_time_nanos)); runner->forwarding_target_->PostTaskForTime( [task, forwarder = runner->on_forward_task_]() { forwarder(task); }, target_time); }; return desc; } private: fml::RefPtr forwarding_target_; std::function on_forward_task_; FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestTaskRunner); }; TEST_F(EmbedderTest, CanSpecifyCustomTaskRunner) { auto& context = GetEmbedderContext(); fml::AutoResetWaitableEvent latch; // Run the test on its own thread with a message loop so that it san safely // pump its event loop while we wait for all the conditions to be checked. fml::Thread thread; UniqueEngine engine; bool signaled = false; EmbedderTestTaskRunner runner([&](FlutterTask task) { // There may be multiple tasks posted but we only need to check assertions // once. if (signaled) { // Since we have the baton, return it back to the engine. We don't care // about the return value because the engine could be shutting down an it // may not actually be able to accept the same. FlutterEngineRunTask(engine.get(), &task); return; } signaled = true; FML_LOG(INFO) << "Checking assertions."; ASSERT_TRUE(engine.is_valid()); ASSERT_EQ(FlutterEngineRunTask(engine.get(), &task), kSuccess); latch.Signal(); }); thread.GetTaskRunner()->PostTask([&]() { EmbedderConfigBuilder builder(context); const auto task_runner_description = runner.GetEmbedderDescription(); runner.SetForwardingTaskRunner( fml::MessageLoop::GetCurrent().GetTaskRunner()); builder.SetPlatformTaskRunner(&task_runner_description); builder.SetDartEntrypoint("invokePlatformTaskRunner"); engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); }); // Signaled when all the assertions are checked. latch.Wait(); FML_LOG(INFO) << "Assertions checked. Killing engine."; ASSERT_TRUE(engine.is_valid()); // Since the engine was started on its own thread, it must be killed there as // well. fml::AutoResetWaitableEvent kill_latch; thread.GetTaskRunner()->PostTask( fml::MakeCopyable([&engine, &kill_latch]() mutable { engine.reset(); FML_LOG(INFO) << "Engine killed."; kill_latch.Signal(); })); kill_latch.Wait(); ASSERT_TRUE(signaled); } TEST(EmbedderTestNoFixture, CanGetCurrentTimeInNanoseconds) { auto point1 = fml::TimePoint::FromEpochDelta( fml::TimeDelta::FromNanoseconds(FlutterEngineGetCurrentTime())); auto point2 = fml::TimePoint::Now(); ASSERT_LT((point2 - point1), fml::TimeDelta::FromMilliseconds(1)); } TEST_F(EmbedderTest, CanCreateOpenGLRenderingEngine) { EmbedderConfigBuilder builder(GetEmbedderContext()); builder.SetOpenGLRendererConfig(); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); } TEST_F(EmbedderTest, IsolateServiceIdSent) { auto& context = GetEmbedderContext(); fml::AutoResetWaitableEvent latch; fml::Thread thread; UniqueEngine engine; std::string isolate_message; EmbedderTestTaskRunner runner( [&](FlutterTask task) { FlutterEngineRunTask(engine.get(), &task); }); thread.GetTaskRunner()->PostTask([&]() { EmbedderConfigBuilder builder(context); const auto task_runner_description = runner.GetEmbedderDescription(); runner.SetForwardingTaskRunner( fml::MessageLoop::GetCurrent().GetTaskRunner()); builder.SetPlatformTaskRunner(&task_runner_description); builder.SetDartEntrypoint("main"); builder.SetPlatformMessageCallback( [&](const FlutterPlatformMessage* message) { if (strcmp(message->channel, "flutter/isolate") == 0) { isolate_message = {reinterpret_cast(message->message), message->message_size}; latch.Signal(); } }); engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); }); // Wait for the isolate ID message and check its format. latch.Wait(); ASSERT_EQ(isolate_message.find("isolates/"), 0ul); // Since the engine was started on its own thread, it must be killed there as // well. fml::AutoResetWaitableEvent kill_latch; thread.GetTaskRunner()->PostTask( fml::MakeCopyable([&engine, &kill_latch]() mutable { engine.reset(); kill_latch.Signal(); })); kill_latch.Wait(); } //------------------------------------------------------------------------------ /// Creates a platform message response callbacks, does NOT send them, and /// immediately collects the same. /// TEST_F(EmbedderTest, CanCreateAndCollectCallbacks) { auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); builder.SetDartEntrypoint("platform_messages_response"); context.AddNativeCallback( "SignalNativeTest", CREATE_NATIVE_ENTRY([](Dart_NativeArguments args) {})); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); FlutterPlatformMessageResponseHandle* response_handle = nullptr; auto callback = [](const uint8_t* data, size_t size, void* user_data) -> void {}; auto result = FlutterPlatformMessageCreateResponseHandle( engine.get(), callback, nullptr, &response_handle); ASSERT_EQ(result, kSuccess); ASSERT_NE(response_handle, nullptr); result = FlutterPlatformMessageReleaseResponseHandle(engine.get(), response_handle); ASSERT_EQ(result, kSuccess); } //------------------------------------------------------------------------------ /// Sends platform messages to Dart code than simply echoes the contents of the /// message back to the embedder. The embedder registers a native callback to /// intercept that message. /// TEST_F(EmbedderTest, PlatformMessagesCanReceiveResponse) { struct Captures { fml::AutoResetWaitableEvent latch; std::thread::id thread_id; }; Captures captures; GetThreadTaskRunner()->PostTask([&]() { captures.thread_id = std::this_thread::get_id(); auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); builder.SetDartEntrypoint("platform_messages_response"); fml::AutoResetWaitableEvent ready; context.AddNativeCallback( "SignalNativeTest", CREATE_NATIVE_ENTRY( [&ready](Dart_NativeArguments args) { ready.Signal(); })); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); static std::string kMessageData = "Hello from embedder."; FlutterPlatformMessageResponseHandle* response_handle = nullptr; auto callback = [](const uint8_t* data, size_t size, void* user_data) -> void { ASSERT_EQ(size, kMessageData.size()); ASSERT_EQ(strncmp(reinterpret_cast(kMessageData.data()), reinterpret_cast(data), size), 0); auto captures = reinterpret_cast(user_data); ASSERT_EQ(captures->thread_id, std::this_thread::get_id()); captures->latch.Signal(); }; auto result = FlutterPlatformMessageCreateResponseHandle( engine.get(), callback, &captures, &response_handle); ASSERT_EQ(result, kSuccess); FlutterPlatformMessage message = {}; message.struct_size = sizeof(FlutterPlatformMessage); message.channel = "test_channel"; message.message = reinterpret_cast(kMessageData.data()); message.message_size = kMessageData.size(); message.response_handle = response_handle; ready.Wait(); result = FlutterEngineSendPlatformMessage(engine.get(), &message); ASSERT_EQ(result, kSuccess); result = FlutterPlatformMessageReleaseResponseHandle(engine.get(), response_handle); ASSERT_EQ(result, kSuccess); }); captures.latch.Wait(); } //------------------------------------------------------------------------------ /// Tests that a platform message can be sent with no response handle. Instead /// of the platform message integrity checked via a response handle, a native /// callback with the response is invoked to assert integrity. /// TEST_F(EmbedderTest, PlatformMessagesCanBeSentWithoutResponseHandles) { auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); builder.SetDartEntrypoint("platform_messages_no_response"); const std::string message_data = "Hello but don't call me back."; fml::AutoResetWaitableEvent ready, message; context.AddNativeCallback( "SignalNativeTest", CREATE_NATIVE_ENTRY( [&ready](Dart_NativeArguments args) { ready.Signal(); })); context.AddNativeCallback( "SignalNativeMessage", CREATE_NATIVE_ENTRY( ([&message, &message_data](Dart_NativeArguments args) { auto received_message = tonic::DartConverter::FromDart( Dart_GetNativeArgument(args, 0)); ASSERT_EQ(received_message, message_data); message.Signal(); }))); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); ready.Wait(); FlutterPlatformMessage platform_message = {}; platform_message.struct_size = sizeof(FlutterPlatformMessage); platform_message.channel = "test_channel"; platform_message.message = reinterpret_cast(message_data.data()); platform_message.message_size = message_data.size(); platform_message.response_handle = nullptr; // No response needed. auto result = FlutterEngineSendPlatformMessage(engine.get(), &platform_message); ASSERT_EQ(result, kSuccess); message.Wait(); } //------------------------------------------------------------------------------ /// Tests that a null platform message can be sent. /// TEST_F(EmbedderTest, NullPlatformMessagesCanBeSent) { auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); builder.SetDartEntrypoint("null_platform_messages"); fml::AutoResetWaitableEvent ready, message; context.AddNativeCallback( "SignalNativeTest", CREATE_NATIVE_ENTRY( [&ready](Dart_NativeArguments args) { ready.Signal(); })); context.AddNativeCallback( "SignalNativeMessage", CREATE_NATIVE_ENTRY(([&message](Dart_NativeArguments args) { auto received_message = tonic::DartConverter::FromDart( Dart_GetNativeArgument(args, 0)); ASSERT_EQ("true", received_message); message.Signal(); }))); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); ready.Wait(); FlutterPlatformMessage platform_message = {}; platform_message.struct_size = sizeof(FlutterPlatformMessage); platform_message.channel = "test_channel"; platform_message.message = nullptr; platform_message.message_size = 0; platform_message.response_handle = nullptr; // No response needed. auto result = FlutterEngineSendPlatformMessage(engine.get(), &platform_message); ASSERT_EQ(result, kSuccess); message.Wait(); } //------------------------------------------------------------------------------ /// Tests that a null platform message cannot be send if the message_size /// isn't equals to 0. /// TEST_F(EmbedderTest, InvalidPlatformMessages) { auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); FlutterPlatformMessage platform_message = {}; platform_message.struct_size = sizeof(FlutterPlatformMessage); platform_message.channel = "test_channel"; platform_message.message = nullptr; platform_message.message_size = 1; platform_message.response_handle = nullptr; // No response needed. auto result = FlutterEngineSendPlatformMessage(engine.get(), &platform_message); ASSERT_EQ(result, kInvalidArguments); } //------------------------------------------------------------------------------ /// Asserts behavior of FlutterProjectArgs::shutdown_dart_vm_when_done (which is /// set to true by default in these unit-tests). /// TEST_F(EmbedderTest, VMShutsDownWhenNoEnginesInProcess) { auto& context = GetEmbedderContext(); EmbedderConfigBuilder builder(context); const auto launch_count = DartVM::GetVMLaunchCount(); { auto engine = builder.LaunchEngine(); ASSERT_EQ(launch_count + 1u, DartVM::GetVMLaunchCount()); } { auto engine = builder.LaunchEngine(); ASSERT_EQ(launch_count + 2u, DartVM::GetVMLaunchCount()); } } } // namespace testing } // namespace flutter