blob: 58c75b3292ead4e756ca54e553e5f8de0df7e49f [file] [log] [blame]
// 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"
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();
}
} // namespace testing
} // namespace flutter