From a877ccf4ee73c8e7cb992a80ff35b08b8c6dad5b Mon Sep 17 00:00:00 2001 From: George Wright Date: Wed, 9 Jun 2021 17:28:13 -0700 Subject: [PATCH] Add support for native callbacks to the macOS embedder test harness (flutter/engine#26623) --- .../ci/licenses_golden/licenses_flutter | 3 + .../shell/platform/darwin/macos/BUILD.gn | 2 + .../Source/FlutterDartProject_Internal.h | 5 ++ .../macos/framework/Source/FlutterEngine.mm | 1 + .../framework/Source/FlutterEngineTest.mm | 57 +++++++++---------- .../Source/FlutterEngineTestContext.mm | 21 +++++++ .../framework/Source/FlutterEngineTestUtils.h | 34 +++++++++++ .../Source/FlutterEngineTestUtils.mm | 41 +++++++++++++ .../Source/fixtures/flutter_desktop_test.dart | 7 +++ 9 files changed, 142 insertions(+), 29 deletions(-) create mode 100644 engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestContext.mm create mode 100644 engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.h create mode 100644 engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.mm diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index 4c9741ac0af..9c9364c45b6 100755 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -1160,6 +1160,9 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEmbed FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestContext.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.mm diff --git a/engine/src/flutter/shell/platform/darwin/macos/BUILD.gn b/engine/src/flutter/shell/platform/darwin/macos/BUILD.gn index 26b14025fe7..487e3e731ad 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/BUILD.gn +++ b/engine/src/flutter/shell/platform/darwin/macos/BUILD.gn @@ -176,6 +176,8 @@ executable("flutter_desktop_darwin_unittests") { "framework/Source/FlutterEmbedderExternalTextureUnittests.mm", "framework/Source/FlutterEmbedderKeyResponderUnittests.mm", "framework/Source/FlutterEngineTest.mm", + "framework/Source/FlutterEngineTestUtils.h", + "framework/Source/FlutterEngineTestUtils.mm", "framework/Source/FlutterGLCompositorUnittests.mm", "framework/Source/FlutterKeyboardManagerUnittests.mm", "framework/Source/FlutterMetalCompositorUnittests.mm", diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h index ecdfa8c4904..d19589b6ce6 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h @@ -30,6 +30,11 @@ */ @property(nonatomic, readonly) std::vector switches; +/** + * The callback invoked by the engine in root isolate scope. + */ +@property(nonatomic, nullable) void (*rootIsolateCreateCallback)(void* _Nullable); + /** * Instead of looking up the assets and ICU data path in the application bundle, this initializer * allows callers to create a Dart project with custom locations specified for the both. diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 891292392d1..66b5fa88f4f 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -235,6 +235,7 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi flutterArguments.shutdown_dart_vm_when_done = true; flutterArguments.dart_entrypoint_argc = dartEntrypointArgs.size(); flutterArguments.dart_entrypoint_argv = dartEntrypointArgs.data(); + flutterArguments.root_isolate_create_callback = _project.rootIsolateCreateCallback; static size_t sTaskRunnerIdentifiers = 0; const FlutterTaskRunnerDescription cocoa_task_runner_description = { diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm index 42664867cb7..2f92f4486eb 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm @@ -6,12 +6,12 @@ #include "flutter/shell/platform/common/accessibility_bridge.h" #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h" #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h" -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.h" #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" -#include "flutter/testing/testing.h" +#include "flutter/testing/test_dart_native_resolver.h" @interface FlutterEngine (Test) /** @@ -25,26 +25,14 @@ namespace flutter::testing { -namespace { -// Returns an engine configured for the test fixture resource configuration. -FlutterEngine* CreateTestEngine() { - NSString* fixtures = @(testing::GetFixturesPath()); - FlutterDartProject* project = [[FlutterDartProject alloc] - initWithAssetsPath:fixtures - ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]]; - return [[FlutterEngine alloc] initWithName:@"test" project:project allowHeadlessExecution:true]; -} -} // namespace - -TEST(FlutterEngine, CanLaunch) { - FlutterEngine* engine = CreateTestEngine(); +TEST_F(FlutterEngineTest, CanLaunch) { + FlutterEngine* engine = GetFlutterEngine(); EXPECT_TRUE([engine runWithEntrypoint:@"main"]); EXPECT_TRUE(engine.running); - [engine shutDownEngine]; } -TEST(FlutterEngine, MessengerSend) { - FlutterEngine* engine = CreateTestEngine(); +TEST_F(FlutterEngineTest, MessengerSend) { + FlutterEngine* engine = GetFlutterEngine(); EXPECT_TRUE([engine runWithEntrypoint:@"main"]); NSData* test_message = [@"a message" dataUsingEncoding:NSUTF8StringEncoding]; @@ -60,12 +48,10 @@ TEST(FlutterEngine, MessengerSend) { [engine.binaryMessenger sendOnChannel:@"test" message:test_message]; EXPECT_TRUE(called); - - [engine shutDownEngine]; } -TEST(FlutterEngine, CanToggleAccessibility) { - FlutterEngine* engine = CreateTestEngine(); +TEST_F(FlutterEngineTest, CanToggleAccessibility) { + FlutterEngine* engine = GetFlutterEngine(); // Capture the update callbacks before the embedder API initializes. auto original_init = engine.embedderAPI.Initialize; std::function update_node_callback; @@ -162,11 +148,10 @@ TEST(FlutterEngine, CanToggleAccessibility) { EXPECT_EQ([engine.viewController.flutterView.accessibilityChildren count], 0u); [engine setViewController:nil]; - [engine shutDownEngine]; } -TEST(FlutterEngine, CanToggleAccessibilityWhenHeadless) { - FlutterEngine* engine = CreateTestEngine(); +TEST_F(FlutterEngineTest, CanToggleAccessibilityWhenHeadless) { + FlutterEngine* engine = GetFlutterEngine(); // Capture the update callbacks before the embedder API initializes. auto original_init = engine.embedderAPI.Initialize; std::function update_node_callback; @@ -245,11 +230,10 @@ TEST(FlutterEngine, CanToggleAccessibilityWhenHeadless) { EXPECT_FALSE(semanticsEnabled); // Still no crashes EXPECT_EQ(engine.viewController, nil); - [engine shutDownEngine]; } -TEST(FlutterEngine, ResetsAccessibilityBridgeWhenSetsNewViewController) { - FlutterEngine* engine = CreateTestEngine(); +TEST_F(FlutterEngineTest, ResetsAccessibilityBridgeWhenSetsNewViewController) { + FlutterEngine* engine = GetFlutterEngine(); // Capture the update callbacks before the embedder API initializes. auto original_init = engine.embedderAPI.Initialize; std::function update_node_callback; @@ -336,7 +320,22 @@ TEST(FlutterEngine, ResetsAccessibilityBridgeWhenSetsNewViewController) { EXPECT_TRUE(native_root.expired()); [engine setViewController:nil]; - [engine shutDownEngine]; +} + +TEST_F(FlutterEngineTest, NativeCallbacks) { + FlutterEngine* engine = GetFlutterEngine(); + EXPECT_TRUE([engine runWithEntrypoint:@"native_callback"]); + EXPECT_TRUE(engine.running); + + fml::AutoResetWaitableEvent latch; + bool latch_called = false; + + AddNativeCallback("SignalNativeTest", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + latch_called = true; + latch.Signal(); + })); + latch.Wait(); + ASSERT_TRUE(latch_called); } TEST(FlutterEngine, Compositor) { diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestContext.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestContext.mm new file mode 100644 index 00000000000..e199a19b444 --- /dev/null +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestContext.mm @@ -0,0 +1,21 @@ +// 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. + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestContext.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h" + +#include "flutter/testing/testing.h" + +namespace flutter::testing { + +class FlutterEngineTestContext { + public: + FlutterEngineTestContext(std::string assets_path = ""); + virtual ~FlutterEngineTestContext(); + + private: + static IsolateCreateCallback(); +}; + +} // namespace flutter::testing diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.h b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.h new file mode 100644 index 00000000000..2242165cacc --- /dev/null +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.h @@ -0,0 +1,34 @@ +// 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. + +#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h" + +#include "flutter/testing/test_dart_native_resolver.h" +#include "gtest/gtest.h" + +namespace flutter::testing { + +class FlutterEngineTest : public ::testing::Test { + public: + FlutterEngineTest(); + + FlutterEngine* GetFlutterEngine() { return engine_; }; + + void SetUp() override; + void TearDown() override; + + void AddNativeCallback(const char* name, Dart_NativeFunction function); + + static void IsolateCreateCallback(void* user_data); + + private: + inline static std::shared_ptr native_resolver_; + + FlutterDartProject* project_; + FlutterEngine* engine_; + + FML_DISALLOW_COPY_AND_ASSIGN(FlutterEngineTest); +}; + +} // namespace flutter::testing diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.mm new file mode 100644 index 00000000000..ede40a79e1d --- /dev/null +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.mm @@ -0,0 +1,41 @@ + +// 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. + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h" + +#include "flutter/testing/testing.h" + +namespace flutter::testing { + +FlutterEngineTest::FlutterEngineTest() = default; + +void FlutterEngineTest::SetUp() { + native_resolver_ = std::make_shared(); + NSString* fixtures = @(testing::GetFixturesPath()); + project_ = [[FlutterDartProject alloc] + initWithAssetsPath:fixtures + ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]]; + project_.rootIsolateCreateCallback = FlutterEngineTest::IsolateCreateCallback; + engine_ = [[FlutterEngine alloc] initWithName:@"test" + project:project_ + allowHeadlessExecution:true]; +} + +void FlutterEngineTest::TearDown() { + [engine_ shutDownEngine]; + engine_ = nil; + native_resolver_.reset(); +} + +void FlutterEngineTest::IsolateCreateCallback(void* user_data) { + native_resolver_->SetNativeResolverForIsolate(); +} + +void FlutterEngineTest::AddNativeCallback(const char* name, Dart_NativeFunction function) { + native_resolver_->AddNativeCallback({name}, function); +} + +} // namespace flutter::testing diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart index eed6a499007..c7d3cdbc08e 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart @@ -28,3 +28,10 @@ void can_composite_platform_views() { }; PlatformDispatcher.instance.scheduleFrame(); } + +void signalNativeTest() native 'SignalNativeTest'; + +@pragma('vm:entry-point') +void native_callback() { + signalNativeTest(); +}