mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
405 lines
14 KiB
C++
405 lines
14 KiB
C++
// 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 <string>
|
|
|
|
#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/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<void(FlutterTask)> on_forward_task)
|
|
: on_forward_task_(on_forward_task) {}
|
|
|
|
void SetForwardingTaskRunner(fml::RefPtr<fml::TaskRunner> 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<EmbedderTestTaskRunner*>(user_data)
|
|
->forwarding_target_->RunsTasksOnCurrentThread();
|
|
};
|
|
desc.post_task_callback = [](FlutterTask task, uint64_t target_time_nanos,
|
|
void* user_data) -> void {
|
|
auto runner = reinterpret_cast<EmbedderTestTaskRunner*>(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<fml::TaskRunner> forwarding_target_;
|
|
std::function<void(FlutterTask)> 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<const char*>(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<const char*>(kMessageData.data()),
|
|
reinterpret_cast<const char*>(data), size),
|
|
0);
|
|
auto captures = reinterpret_cast<Captures*>(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<const uint8_t*>(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<std::string>::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<const uint8_t*>(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();
|
|
}
|
|
|
|
} // namespace testing
|
|
} // namespace flutter
|