From d6ead18f436fe439063190093ac2184559f30bb3 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Mon, 25 Mar 2019 11:58:38 -0700 Subject: [PATCH] Make it easy to write embedder unit tests by creating a fixture and config builder. (#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(); ``` --- shell/platform/embedder/BUILD.gn | 4 + .../embedder/tests/embedder_config_builder.cc | 67 +++++++++++++++ .../embedder/tests/embedder_config_builder.h | 55 ++++++++++++ .../platform/embedder/tests/embedder_test.cc | 86 +++++++++++++++++++ shell/platform/embedder/tests/embedder_test.h | 54 ++++++++++++ .../embedder/tests/embedder_unittests.cc | 53 +++++++++--- 6 files changed, 308 insertions(+), 11 deletions(-) create mode 100644 shell/platform/embedder/tests/embedder_config_builder.cc create mode 100644 shell/platform/embedder/tests/embedder_config_builder.h create mode 100644 shell/platform/embedder/tests/embedder_test.cc create mode 100644 shell/platform/embedder/tests/embedder_test.h diff --git a/shell/platform/embedder/BUILD.gn b/shell/platform/embedder/BUILD.gn index 94aa7affa..1b7a81b84 100644 --- a/shell/platform/embedder/BUILD.gn +++ b/shell/platform/embedder/BUILD.gn @@ -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", ] diff --git a/shell/platform/embedder/tests/embedder_config_builder.cc b/shell/platform/embedder/tests/embedder_config_builder.cc new file mode 100644 index 000000000..108535ea8 --- /dev/null +++ b/shell/platform/embedder/tests/embedder_config_builder.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 diff --git a/shell/platform/embedder/tests/embedder_config_builder.h b/shell/platform/embedder/tests/embedder_config_builder.h new file mode 100644 index 000000000..a474f3e3b --- /dev/null +++ b/shell/platform/embedder/tests/embedder_config_builder.h @@ -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; + +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_ diff --git a/shell/platform/embedder/tests/embedder_test.cc b/shell/platform/embedder/tests/embedder_test.cc new file mode 100644 index 000000000..044aacc4d --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test.cc @@ -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 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 mapping; + if (executable) { + mapping = std::make_unique( + file, std::initializer_list{Prot::kRead, Prot::kExecute}); + } else { + mapping = std::make_unique( + file, std::initializer_list{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 diff --git a/shell/platform/embedder/tests/embedder_test.h b/shell/platform/embedder/tests/embedder_test.h new file mode 100644 index 000000000..f861e3b72 --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test.h @@ -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 + +#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 vm_snapshot_data_; + std::unique_ptr vm_snapshot_instructions_; + std::unique_ptr isolate_snapshot_data_; + std::unique_ptr 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_ diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index f1345227c..7415183eb 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -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>& aot_mappings, - const fml::UniqueFD& fixtures_dir, - const char* path, - bool executable, - const uint8_t** data, - size_t* size) { +static void MapAOTAsset( + std::vector>& 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 mapping; @@ -34,8 +38,6 @@ void MapAOTAsset(std::vector>& 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(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> 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 \ No newline at end of file -- GitLab