From 2ea839686087458245c5188503468a5825059129 Mon Sep 17 00:00:00 2001 From: gaaclarke <30870216+gaaclarke@users.noreply.github.com> Date: Fri, 7 Aug 2020 15:55:58 -0700 Subject: [PATCH] Added unit tests to the engine. (#20216) --- ci/licenses_golden/licenses_flutter | 1 + runtime/runtime_controller.cc | 4 + runtime/runtime_controller.h | 10 +- shell/common/BUILD.gn | 2 + shell/common/engine.cc | 44 ++++-- shell/common/engine.h | 20 +++ shell/common/engine_unittests.cc | 234 ++++++++++++++++++++++++++++ 7 files changed, 297 insertions(+), 18 deletions(-) create mode 100644 shell/common/engine_unittests.cc diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 2ec58e6f5..6449f00e4 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -600,6 +600,7 @@ FILE: ../../../flutter/shell/common/canvas_spy.h FILE: ../../../flutter/shell/common/canvas_spy_unittests.cc FILE: ../../../flutter/shell/common/engine.cc FILE: ../../../flutter/shell/common/engine.h +FILE: ../../../flutter/shell/common/engine_unittests.cc FILE: ../../../flutter/shell/common/fixtures/shell_test.dart FILE: ../../../flutter/shell/common/fixtures/shelltest_screenshot.png FILE: ../../../flutter/shell/common/input_events_unittests.cc diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index bc4d502e6..f66cfed77 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -16,6 +16,10 @@ namespace flutter { +RuntimeController::RuntimeController(RuntimeDelegate& client, + TaskRunners p_task_runners) + : client_(client), vm_(nullptr), task_runners_(p_task_runners) {} + RuntimeController::RuntimeController( RuntimeDelegate& p_client, DartVM* p_vm, diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index e67b5847c..18adbc2c1 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -38,7 +38,7 @@ class Window; /// used by the engine to copy the currently accumulated window state so it can /// be referenced by the new runtime controller. /// -class RuntimeController final : public PlatformConfigurationClient { +class RuntimeController : public PlatformConfigurationClient { public: //---------------------------------------------------------------------------- /// @brief Creates a new instance of a runtime controller. This is @@ -340,7 +340,7 @@ class RuntimeController final : public PlatformConfigurationClient { /// /// @return True if root isolate running, False otherwise. /// - bool IsRootIsolateRunning() const; + virtual bool IsRootIsolateRunning() const; //---------------------------------------------------------------------------- /// @brief Dispatch the specified platform message to running root @@ -351,7 +351,7 @@ class RuntimeController final : public PlatformConfigurationClient { /// @return If the message was dispatched to the running root isolate. /// This may fail is an isolate is not running. /// - bool DispatchPlatformMessage(fml::RefPtr message); + virtual bool DispatchPlatformMessage(fml::RefPtr message); //---------------------------------------------------------------------------- /// @brief Dispatch the specified pointer data message to the running @@ -440,6 +440,10 @@ class RuntimeController final : public PlatformConfigurationClient { /// std::pair GetRootIsolateReturnCode(); + protected: + /// Constructor for Mocks. + RuntimeController(RuntimeDelegate& client, TaskRunners p_task_runners); + private: struct Locale { Locale(std::string language_code_, diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index 28ca87584..8ef3f1c3c 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -258,6 +258,7 @@ if (enable_unittests) { sources = [ "animator_unittests.cc", "canvas_spy_unittests.cc", + "engine_unittests.cc", "input_events_unittests.cc", "persistent_cache_unittests.cc", "pipeline_unittests.cc", @@ -268,6 +269,7 @@ if (enable_unittests) { deps = [ "//flutter/assets", "//flutter/shell/version", + "//third_party/googletest:gmock", ] public_deps_legacy_and_next = [ ":shell_test_fixture_sources" ] diff --git a/shell/common/engine.cc b/shell/common/engine.cc index 79f885dcb..d4338c663 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -35,6 +35,27 @@ static constexpr char kLocalizationChannel[] = "flutter/localization"; static constexpr char kSettingsChannel[] = "flutter/settings"; static constexpr char kIsolateChannel[] = "flutter/isolate"; +Engine::Engine( + Delegate& delegate, + const PointerDataDispatcherMaker& dispatcher_maker, + std::shared_ptr image_decoder_task_runner, + TaskRunners task_runners, + Settings settings, + std::unique_ptr animator, + fml::WeakPtr io_manager, + std::unique_ptr runtime_controller) + : delegate_(delegate), + settings_(std::move(settings)), + animator_(std::move(animator)), + runtime_controller_(std::move(runtime_controller)), + activity_running_(true), + have_surface_(false), + image_decoder_(task_runners, image_decoder_task_runner, io_manager), + task_runners_(std::move(task_runners)), + weak_factory_(this) { + pointer_data_dispatcher_ = dispatcher_maker(*this); +} + Engine::Engine(Delegate& delegate, const PointerDataDispatcherMaker& dispatcher_maker, DartVM& vm, @@ -46,19 +67,14 @@ Engine::Engine(Delegate& delegate, fml::WeakPtr io_manager, fml::RefPtr unref_queue, fml::WeakPtr snapshot_delegate) - : delegate_(delegate), - settings_(std::move(settings)), - animator_(std::move(animator)), - activity_running_(true), - have_surface_(false), - image_decoder_(task_runners, - vm.GetConcurrentWorkerTaskRunner(), - io_manager), - task_runners_(std::move(task_runners)), - weak_factory_(this) { - // Runtime controller is initialized here because it takes a reference to this - // object as its delegate. The delegate may be called in the constructor and - // we want to be fully initilazed by that point. + : Engine(delegate, + dispatcher_maker, + vm.GetConcurrentWorkerTaskRunner(), + task_runners, + settings, + std::move(animator), + io_manager, + nullptr) { runtime_controller_ = std::make_unique( *this, // runtime delegate &vm, // VM @@ -76,8 +92,6 @@ Engine::Engine(Delegate& delegate, settings_.isolate_shutdown_callback, // isolate shutdown callback settings_.persistent_isolate_data // persistent isolate data ); - - pointer_data_dispatcher_ = dispatcher_maker(*this); } Engine::~Engine() = default; diff --git a/shell/common/engine.h b/shell/common/engine.h index bbbcc264c..d16ae0ffe 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -248,6 +248,20 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { const std::vector& supported_locale_data) = 0; }; + //---------------------------------------------------------------------------- + /// @brief Creates an instance of the engine with a supplied + /// `RuntimeController`. Use the other constructor except for + /// tests. + /// + Engine(Delegate& delegate, + const PointerDataDispatcherMaker& dispatcher_maker, + std::shared_ptr image_decoder_task_runner, + TaskRunners task_runners, + Settings settings, + std::unique_ptr animator, + fml::WeakPtr io_manager, + std::unique_ptr runtime_controller); + //---------------------------------------------------------------------------- /// @brief Creates an instance of the engine. This is done by the Shell /// on the UI task runner. @@ -756,6 +770,12 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { /// const std::string& GetLastEntrypointLibrary() const; + //---------------------------------------------------------------------------- + /// @brief Getter for the initial route. This can be set with a platform + /// message. + /// + const std::string& InitialRoute() const { return initial_route_; } + private: Engine::Delegate& delegate_; const Settings settings_; diff --git a/shell/common/engine_unittests.cc b/shell/common/engine_unittests.cc new file mode 100644 index 000000000..54f6f00d7 --- /dev/null +++ b/shell/common/engine_unittests.cc @@ -0,0 +1,234 @@ +// 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. +// FLUTTER_NOLINT + +#include "flutter/runtime/dart_vm_lifecycle.h" +#include "flutter/shell/common/engine.h" +#include "flutter/shell/common/thread_host.h" +#include "flutter/testing/testing.h" +#include "gmock/gmock.h" +#include "rapidjson/document.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/writer.h" + +///\note Deprecated MOCK_METHOD macros used until this issue is resolved: +// https://github.com/google/googletest/issues/2490 + +namespace flutter { + +namespace { +class MockDelegate : public Engine::Delegate { + MOCK_METHOD2(OnEngineUpdateSemantics, + void(SemanticsNodeUpdates, CustomAccessibilityActionUpdates)); + MOCK_METHOD1(OnEngineHandlePlatformMessage, + void(fml::RefPtr)); + MOCK_METHOD0(OnPreEngineRestart, void()); + MOCK_METHOD2(UpdateIsolateDescription, void(const std::string, int64_t)); + MOCK_METHOD1(SetNeedsReportTimings, void(bool)); + MOCK_METHOD1(ComputePlatformResolvedLocale, + std::unique_ptr>( + const std::vector&)); +}; + +class MockResponse : public PlatformMessageResponse { + public: + MOCK_METHOD1(Complete, void(std::unique_ptr data)); + MOCK_METHOD0(CompleteEmpty, void()); +}; + +class MockRuntimeDelegate : public RuntimeDelegate { + public: + MOCK_METHOD0(DefaultRouteName, std::string()); + MOCK_METHOD1(ScheduleFrame, void(bool)); + MOCK_METHOD1(Render, void(std::unique_ptr)); + MOCK_METHOD2(UpdateSemantics, + void(SemanticsNodeUpdates, CustomAccessibilityActionUpdates)); + MOCK_METHOD1(HandlePlatformMessage, void(fml::RefPtr)); + MOCK_METHOD0(GetFontCollection, FontCollection&()); + MOCK_METHOD2(UpdateIsolateDescription, void(const std::string, int64_t)); + MOCK_METHOD1(SetNeedsReportTimings, void(bool)); + MOCK_METHOD1(ComputePlatformResolvedLocale, + std::unique_ptr>( + const std::vector&)); +}; + +class MockRuntimeController : public RuntimeController { + public: + MockRuntimeController(RuntimeDelegate& client, TaskRunners p_task_runners) + : RuntimeController(client, p_task_runners) {} + MOCK_CONST_METHOD0(IsRootIsolateRunning, bool()); + MOCK_METHOD1(DispatchPlatformMessage, bool(fml::RefPtr)); +}; + +fml::RefPtr MakePlatformMessage( + const std::string& channel, + const std::map& values, + fml::RefPtr response) { + rapidjson::Document document; + auto& allocator = document.GetAllocator(); + document.SetObject(); + + for (const auto& pair : values) { + rapidjson::Value key(pair.first.c_str(), strlen(pair.first.c_str()), + allocator); + rapidjson::Value value(pair.second.c_str(), strlen(pair.second.c_str()), + allocator); + document.AddMember(key, value, allocator); + } + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + document.Accept(writer); + const uint8_t* data = reinterpret_cast(buffer.GetString()); + + fml::RefPtr message = fml::MakeRefCounted( + channel, std::vector(data, data + buffer.GetSize()), response); + return message; +} + +class EngineTest : public ::testing::Test { + public: + EngineTest() + : thread_host_("EngineTest", + ThreadHost::Type::Platform | ThreadHost::Type::IO | + ThreadHost::Type::UI | ThreadHost::Type::GPU), + task_runners_({ + "EngineTest", + thread_host_.platform_thread->GetTaskRunner(), // platform + thread_host_.raster_thread->GetTaskRunner(), // raster + thread_host_.ui_thread->GetTaskRunner(), // ui + thread_host_.io_thread->GetTaskRunner() // io + }) {} + + void PostUITaskSync(const std::function& function) { + fml::AutoResetWaitableEvent latch; + task_runners_.GetUITaskRunner()->PostTask([&] { + function(); + latch.Signal(); + }); + latch.Wait(); + } + + protected: + void SetUp() override { + dispatcher_maker_ = [](PointerDataDispatcher::Delegate&) { + return nullptr; + }; + } + + MockDelegate delegate_; + PointerDataDispatcherMaker dispatcher_maker_; + ThreadHost thread_host_; + TaskRunners task_runners_; + Settings settings_; + std::unique_ptr animator_; + fml::WeakPtr io_manager_; + std::unique_ptr runtime_controller_; + std::shared_ptr image_decoder_task_runner_; +}; +} // namespace + +TEST_F(EngineTest, Create) { + PostUITaskSync([this] { + auto engine = std::make_unique( + /*delegate=*/delegate_, + /*dispatcher_maker=*/dispatcher_maker_, + /*image_decoder_task_runner=*/image_decoder_task_runner_, + /*task_runners=*/task_runners_, + /*settings=*/settings_, + /*animator=*/std::move(animator_), + /*io_manager=*/io_manager_, + /*runtime_controller=*/std::move(runtime_controller_)); + EXPECT_TRUE(engine); + }); +} + +TEST_F(EngineTest, DispatchPlatformMessageUnknown) { + PostUITaskSync([this] { + MockRuntimeDelegate client; + auto mock_runtime_controller = + std::make_unique(client, task_runners_); + EXPECT_CALL(*mock_runtime_controller, IsRootIsolateRunning()) + .WillRepeatedly(::testing::Return(false)); + auto engine = std::make_unique( + /*delegate=*/delegate_, + /*dispatcher_maker=*/dispatcher_maker_, + /*image_decoder_task_runner=*/image_decoder_task_runner_, + /*task_runners=*/task_runners_, + /*settings=*/settings_, + /*animator=*/std::move(animator_), + /*io_manager=*/io_manager_, + /*runtime_controller=*/std::move(mock_runtime_controller)); + + fml::RefPtr response = + fml::MakeRefCounted(); + fml::RefPtr message = + fml::MakeRefCounted("foo", response); + engine->DispatchPlatformMessage(message); + }); +} + +TEST_F(EngineTest, DispatchPlatformMessageInitialRoute) { + PostUITaskSync([this] { + MockRuntimeDelegate client; + auto mock_runtime_controller = + std::make_unique(client, task_runners_); + EXPECT_CALL(*mock_runtime_controller, IsRootIsolateRunning()) + .WillRepeatedly(::testing::Return(false)); + auto engine = std::make_unique( + /*delegate=*/delegate_, + /*dispatcher_maker=*/dispatcher_maker_, + /*image_decoder_task_runner=*/image_decoder_task_runner_, + /*task_runners=*/task_runners_, + /*settings=*/settings_, + /*animator=*/std::move(animator_), + /*io_manager=*/io_manager_, + /*runtime_controller=*/std::move(mock_runtime_controller)); + + fml::RefPtr response = + fml::MakeRefCounted(); + std::map values{ + {"method", "setInitialRoute"}, + {"args", "test_initial_route"}, + }; + fml::RefPtr message = + MakePlatformMessage("flutter/navigation", values, response); + engine->DispatchPlatformMessage(message); + EXPECT_EQ(engine->InitialRoute(), "test_initial_route"); + }); +} + +TEST_F(EngineTest, DispatchPlatformMessageInitialRouteIgnored) { + PostUITaskSync([this] { + MockRuntimeDelegate client; + auto mock_runtime_controller = + std::make_unique(client, task_runners_); + EXPECT_CALL(*mock_runtime_controller, IsRootIsolateRunning()) + .WillRepeatedly(::testing::Return(true)); + EXPECT_CALL(*mock_runtime_controller, DispatchPlatformMessage(::testing::_)) + .WillRepeatedly(::testing::Return(true)); + auto engine = std::make_unique( + /*delegate=*/delegate_, + /*dispatcher_maker=*/dispatcher_maker_, + /*image_decoder_task_runner=*/image_decoder_task_runner_, + /*task_runners=*/task_runners_, + /*settings=*/settings_, + /*animator=*/std::move(animator_), + /*io_manager=*/io_manager_, + /*runtime_controller=*/std::move(mock_runtime_controller)); + + fml::RefPtr response = + fml::MakeRefCounted(); + std::map values{ + {"method", "setInitialRoute"}, + {"args", "test_initial_route"}, + }; + fml::RefPtr message = + MakePlatformMessage("flutter/navigation", values, response); + engine->DispatchPlatformMessage(message); + EXPECT_EQ(engine->InitialRoute(), ""); + }); +} + +} // namespace flutter -- GitLab