Make it easy to write embedder unit tests by creating a fixture and config builder. (flutter/engine#8276)
All embedder unit-tests have to setup the Flutter project arguments from scratch before launching the engine. The boilerplate and having to deal with the low level C API during each engine launch is a hinderance to writing tests. This patch introduces an EmbedderTest fixture that sets up all the embedder side snapshots before allowing the unit test to create a FlutterConfigBuilder` that the test can use to incrementally build and edit the Flutter project configuration. From the given state state of a configuration, multiple engines can be launched with their lifecylces managed by appropriate RAII wrappers. This allows the a fully configured Flutter engine to be launched using 4 lines of code in a fixture. ``` EmbedderConfigBuilder builder; builder.SetSoftwareRendererConfig(); builder.SetAssetsPathFromFixture(this); builder.SetSnapshotsFromFixture(this); auto engine = builder.LaunchEngine(); ```
This commit is contained in:
@@ -68,6 +68,10 @@ executable("embedder_unittests") {
|
||||
include_dirs = [ "." ]
|
||||
|
||||
sources = [
|
||||
"tests/embedder_config_builder.cc",
|
||||
"tests/embedder_config_builder.h",
|
||||
"tests/embedder_test.cc",
|
||||
"tests/embedder_test.h",
|
||||
"tests/embedder_unittests.cc",
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// 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/shell/platform/embedder/tests/embedder_config_builder.h"
|
||||
|
||||
namespace shell {
|
||||
namespace testing {
|
||||
|
||||
EmbedderConfigBuilder::EmbedderConfigBuilder() {
|
||||
project_args_.struct_size = sizeof(project_args_);
|
||||
|
||||
software_renderer_config_.struct_size = sizeof(FlutterSoftwareRendererConfig);
|
||||
software_renderer_config_.surface_present_callback =
|
||||
[](void*, const void*, size_t, size_t) { return true; };
|
||||
}
|
||||
|
||||
EmbedderConfigBuilder::~EmbedderConfigBuilder() = default;
|
||||
|
||||
void EmbedderConfigBuilder::SetSoftwareRendererConfig() {
|
||||
renderer_config_.type = FlutterRendererType::kSoftware;
|
||||
renderer_config_.software = software_renderer_config_;
|
||||
}
|
||||
|
||||
void EmbedderConfigBuilder::SetAssetsPathFromFixture(
|
||||
const EmbedderTest* fixture) {
|
||||
assets_path_ = fixture->GetAssetsPath();
|
||||
project_args_.assets_path = assets_path_.c_str();
|
||||
}
|
||||
|
||||
void EmbedderConfigBuilder::SetSnapshotsFromFixture(
|
||||
const EmbedderTest* fixture) {
|
||||
if (auto mapping = fixture->GetVMSnapshotData()) {
|
||||
project_args_.vm_snapshot_data = mapping->GetMapping();
|
||||
project_args_.vm_snapshot_data_size = mapping->GetSize();
|
||||
}
|
||||
|
||||
if (auto mapping = fixture->GetVMSnapshotInstructions()) {
|
||||
project_args_.vm_snapshot_instructions = mapping->GetMapping();
|
||||
project_args_.vm_snapshot_instructions_size = mapping->GetSize();
|
||||
}
|
||||
|
||||
if (auto mapping = fixture->GetIsolateSnapshotData()) {
|
||||
project_args_.isolate_snapshot_data = mapping->GetMapping();
|
||||
project_args_.isolate_snapshot_data_size = mapping->GetSize();
|
||||
}
|
||||
|
||||
if (auto mapping = fixture->GetIsolateSnapshotInstructions()) {
|
||||
project_args_.isolate_snapshot_instructions = mapping->GetMapping();
|
||||
project_args_.isolate_snapshot_instructions_size = mapping->GetSize();
|
||||
}
|
||||
}
|
||||
|
||||
UniqueEngine EmbedderConfigBuilder::LaunchEngine(void* user_data) const {
|
||||
FlutterEngine engine = nullptr;
|
||||
auto result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &renderer_config_,
|
||||
&project_args_, user_data, &engine);
|
||||
|
||||
if (result != kSuccess) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return UniqueEngine{engine};
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace shell
|
||||
@@ -0,0 +1,55 @@
|
||||
// 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.
|
||||
|
||||
#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_CONFIG_BUILDER_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_CONFIG_BUILDER_H_
|
||||
|
||||
#include "flutter/fml/macros.h"
|
||||
#include "flutter/fml/unique_object.h"
|
||||
#include "flutter/shell/platform/embedder/embedder.h"
|
||||
#include "flutter/shell/platform/embedder/tests/embedder_test.h"
|
||||
|
||||
namespace shell {
|
||||
namespace testing {
|
||||
|
||||
struct UniqueEngineTraits {
|
||||
static FlutterEngine InvalidValue() { return nullptr; }
|
||||
|
||||
static bool IsValid(const FlutterEngine& value) { return value != nullptr; }
|
||||
|
||||
static void Free(FlutterEngine engine) {
|
||||
auto result = FlutterEngineShutdown(engine);
|
||||
FML_CHECK(result == kSuccess);
|
||||
}
|
||||
};
|
||||
|
||||
using UniqueEngine = fml::UniqueObject<FlutterEngine, UniqueEngineTraits>;
|
||||
|
||||
class EmbedderConfigBuilder {
|
||||
public:
|
||||
EmbedderConfigBuilder();
|
||||
|
||||
~EmbedderConfigBuilder();
|
||||
|
||||
void SetSoftwareRendererConfig();
|
||||
|
||||
void SetAssetsPathFromFixture(const EmbedderTest* fixture);
|
||||
|
||||
void SetSnapshotsFromFixture(const EmbedderTest* fixture);
|
||||
|
||||
UniqueEngine LaunchEngine(void* user_data = nullptr) const;
|
||||
|
||||
private:
|
||||
FlutterProjectArgs project_args_ = {};
|
||||
FlutterRendererConfig renderer_config_ = {};
|
||||
FlutterSoftwareRendererConfig software_renderer_config_ = {};
|
||||
std::string assets_path_;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(EmbedderConfigBuilder);
|
||||
};
|
||||
|
||||
} // namespace testing
|
||||
} // namespace shell
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_CONFIG_BUILDER_H_
|
||||
@@ -0,0 +1,86 @@
|
||||
// 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/shell/platform/embedder/tests/embedder_test.h"
|
||||
|
||||
namespace shell {
|
||||
namespace testing {
|
||||
|
||||
static std::unique_ptr<fml::Mapping> GetMapping(const fml::UniqueFD& directory,
|
||||
const char* path,
|
||||
bool executable) {
|
||||
fml::UniqueFD file = fml::OpenFile(directory, path, false /* create */,
|
||||
fml::FilePermission::kRead);
|
||||
if (!file.is_valid()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
using Prot = fml::FileMapping::Protection;
|
||||
std::unique_ptr<fml::FileMapping> mapping;
|
||||
if (executable) {
|
||||
mapping = std::make_unique<fml::FileMapping>(
|
||||
file, std::initializer_list<Prot>{Prot::kRead, Prot::kExecute});
|
||||
} else {
|
||||
mapping = std::make_unique<fml::FileMapping>(
|
||||
file, std::initializer_list<Prot>{Prot::kRead});
|
||||
}
|
||||
|
||||
if (mapping->GetSize() == 0 || mapping->GetMapping() == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
EmbedderTest::EmbedderTest() = default;
|
||||
|
||||
EmbedderTest::~EmbedderTest() = default;
|
||||
|
||||
std::string EmbedderTest::GetFixturesDirectory() const {
|
||||
return ::testing::GetFixturesPath();
|
||||
}
|
||||
|
||||
std::string EmbedderTest::GetAssetsPath() const {
|
||||
return GetFixturesDirectory();
|
||||
}
|
||||
|
||||
const fml::Mapping* EmbedderTest::GetVMSnapshotData() const {
|
||||
return vm_snapshot_data_.get();
|
||||
}
|
||||
|
||||
const fml::Mapping* EmbedderTest::GetVMSnapshotInstructions() const {
|
||||
return vm_snapshot_instructions_.get();
|
||||
}
|
||||
|
||||
const fml::Mapping* EmbedderTest::GetIsolateSnapshotData() const {
|
||||
return isolate_snapshot_data_.get();
|
||||
}
|
||||
|
||||
const fml::Mapping* EmbedderTest::GetIsolateSnapshotInstructions() const {
|
||||
return isolate_snapshot_instructions_.get();
|
||||
}
|
||||
|
||||
// |testing::Test|
|
||||
void EmbedderTest::SetUp() {
|
||||
auto fixures_dir = fml::OpenDirectory(GetFixturesDirectory().c_str(), false,
|
||||
fml::FilePermission::kRead);
|
||||
vm_snapshot_data_ = GetMapping(fixures_dir, "vm_snapshot_data", false);
|
||||
vm_snapshot_instructions_ =
|
||||
GetMapping(fixures_dir, "vm_snapshot_instr", true);
|
||||
isolate_snapshot_data_ =
|
||||
GetMapping(fixures_dir, "isolate_snapshot_data", false);
|
||||
isolate_snapshot_instructions_ =
|
||||
GetMapping(fixures_dir, "isolate_snapshot_instr", true);
|
||||
}
|
||||
|
||||
// |testing::Test|
|
||||
void EmbedderTest::TearDown() {
|
||||
vm_snapshot_data_.reset();
|
||||
vm_snapshot_instructions_.reset();
|
||||
isolate_snapshot_data_.reset();
|
||||
isolate_snapshot_instructions_.reset();
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace shell
|
||||
@@ -0,0 +1,54 @@
|
||||
// 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.
|
||||
|
||||
#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "flutter/fml/file.h"
|
||||
#include "flutter/fml/macros.h"
|
||||
#include "flutter/fml/mapping.h"
|
||||
#include "flutter/testing/testing.h"
|
||||
|
||||
namespace shell {
|
||||
namespace testing {
|
||||
|
||||
class EmbedderTest : public ::testing::Test {
|
||||
public:
|
||||
EmbedderTest();
|
||||
|
||||
~EmbedderTest() override;
|
||||
|
||||
std::string GetFixturesDirectory() const;
|
||||
|
||||
std::string GetAssetsPath() const;
|
||||
|
||||
const fml::Mapping* GetVMSnapshotData() const;
|
||||
|
||||
const fml::Mapping* GetVMSnapshotInstructions() const;
|
||||
|
||||
const fml::Mapping* GetIsolateSnapshotData() const;
|
||||
|
||||
const fml::Mapping* GetIsolateSnapshotInstructions() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<fml::Mapping> vm_snapshot_data_;
|
||||
std::unique_ptr<fml::Mapping> vm_snapshot_instructions_;
|
||||
std::unique_ptr<fml::Mapping> isolate_snapshot_data_;
|
||||
std::unique_ptr<fml::Mapping> isolate_snapshot_instructions_;
|
||||
|
||||
// |testing::Test|
|
||||
void SetUp() override;
|
||||
|
||||
// |testing::Test|
|
||||
void TearDown() override;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTest);
|
||||
};
|
||||
|
||||
} // namespace testing
|
||||
} // namespace shell
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_H_
|
||||
@@ -6,16 +6,20 @@
|
||||
#include "embedder.h"
|
||||
#include "flutter/fml/file.h"
|
||||
#include "flutter/fml/mapping.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 {
|
||||
namespace shell {
|
||||
namespace testing {
|
||||
|
||||
void MapAOTAsset(std::vector<std::unique_ptr<fml::FileMapping>>& aot_mappings,
|
||||
const fml::UniqueFD& fixtures_dir,
|
||||
const char* path,
|
||||
bool executable,
|
||||
const uint8_t** data,
|
||||
size_t* size) {
|
||||
static void MapAOTAsset(
|
||||
std::vector<std::unique_ptr<fml::FileMapping>>& aot_mappings,
|
||||
const fml::UniqueFD& fixtures_dir,
|
||||
const char* path,
|
||||
bool executable,
|
||||
const uint8_t** data,
|
||||
size_t* size) {
|
||||
fml::UniqueFD file =
|
||||
fml::OpenFile(fixtures_dir, path, false, fml::FilePermission::kRead);
|
||||
std::unique_ptr<fml::FileMapping> mapping;
|
||||
@@ -34,8 +38,6 @@ void MapAOTAsset(std::vector<std::unique_ptr<fml::FileMapping>>& aot_mappings,
|
||||
aot_mappings.emplace_back(std::move(mapping));
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
TEST(EmbedderTest, MustNotRunWithInvalidArgs) {
|
||||
FlutterEngine engine = nullptr;
|
||||
FlutterRendererConfig config = {};
|
||||
@@ -58,14 +60,14 @@ TEST(EmbedderTest, CanLaunchAndShutdownWithValidProjectArgs) {
|
||||
|
||||
FlutterProjectArgs args = {};
|
||||
args.struct_size = sizeof(FlutterProjectArgs);
|
||||
args.assets_path = testing::GetFixturesPath();
|
||||
args.assets_path = ::testing::GetFixturesPath();
|
||||
args.root_isolate_create_callback = [](void* data) {
|
||||
std::string str_data = reinterpret_cast<char*>(data);
|
||||
ASSERT_EQ(str_data, "Data");
|
||||
};
|
||||
|
||||
fml::UniqueFD fixtures_dir = fml::OpenDirectory(
|
||||
testing::GetFixturesPath(), false, fml::FilePermission::kRead);
|
||||
::testing::GetFixturesPath(), false, fml::FilePermission::kRead);
|
||||
std::vector<std::unique_ptr<fml::FileMapping>> aot_mappings;
|
||||
if (fml::FileExists(fixtures_dir, "vm_snapshot_data")) {
|
||||
MapAOTAsset(aot_mappings, fixtures_dir, "vm_snapshot_data", false,
|
||||
@@ -90,3 +92,32 @@ TEST(EmbedderTest, CanLaunchAndShutdownWithValidProjectArgs) {
|
||||
result = FlutterEngineShutdown(engine);
|
||||
ASSERT_EQ(result, FlutterEngineResult::kSuccess);
|
||||
}
|
||||
|
||||
using EmbedderFixture = testing::EmbedderTest;
|
||||
|
||||
TEST_F(EmbedderFixture, CanLaunchAndShutdownWithFixture) {
|
||||
EmbedderConfigBuilder builder;
|
||||
|
||||
builder.SetSoftwareRendererConfig();
|
||||
builder.SetAssetsPathFromFixture(this);
|
||||
builder.SetSnapshotsFromFixture(this);
|
||||
|
||||
auto engine = builder.LaunchEngine();
|
||||
ASSERT_TRUE(engine.is_valid());
|
||||
}
|
||||
|
||||
TEST_F(EmbedderFixture, CanLaunchAndShutdownWithFixtureMultipleTimes) {
|
||||
EmbedderConfigBuilder builder;
|
||||
|
||||
builder.SetSoftwareRendererConfig();
|
||||
builder.SetAssetsPathFromFixture(this);
|
||||
builder.SetSnapshotsFromFixture(this);
|
||||
for (size_t i = 0; i < 100; ++i) {
|
||||
auto engine = builder.LaunchEngine();
|
||||
ASSERT_TRUE(engine.is_valid());
|
||||
FML_LOG(INFO) << "Engine launch count: " << i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace shell
|
||||
Reference in New Issue
Block a user