Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit e11ce62

Browse files
committed
[Windows] Support native functions in test fixtures
Adds the ability to register native functions for use in test fixtures. This allows registering native C++ functions that can be invoked from Dart code to perform the following common actions: * Signal a waiting latch in the C++ part of the test. * Pass data back to the C++ part of the test. * Allow the C++ part of the test to pass data to the test. Fixes: flutter/flutter#109242 Fixes: flutter/flutter#87299
1 parent 18a2900 commit e11ce62

8 files changed

+175
-24
lines changed

shell/platform/windows/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ executable("flutter_windows_unittests") {
225225
"//flutter/shell/platform/embedder:embedder_as_internal_library",
226226
"//flutter/shell/platform/embedder:embedder_test_utils",
227227
"//flutter/testing",
228+
"//flutter/testing:dart",
229+
"//flutter/third_party/tonic",
228230
"//third_party/rapidjson",
229231
]
230232
}

shell/platform/windows/fixtures/main.dart

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,34 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
// Signals a waiting latch in the native test.
6+
void signal() native 'Signal';
7+
8+
// Signals a waiting latch in the native test, passing a boolean value.
9+
void signalBoolValue(bool value) native 'SignalBoolValue';
10+
11+
// Signals a waiting latch in the native test, which returns a value to the fixture.
12+
bool signalBoolReturn() native 'SignalBoolReturn';
13+
514
void main() {
6-
print('Hello windows engine test main!');
715
}
816

917
@pragma('vm:entry-point')
1018
void customEntrypoint() {
11-
print('Hello windows engine test customEntrypoint!');
19+
}
20+
21+
@pragma('vm:entry-point')
22+
void verifyNativeFunction() {
23+
signal();
24+
}
25+
26+
@pragma('vm:entry-point')
27+
void verifyNativeFunctionWithParameters() {
28+
signalBoolValue(true);
29+
}
30+
31+
@pragma('vm:entry-point')
32+
void verifyNativeFunctionWithReturn() {
33+
bool value = signalBoolReturn();
34+
signalBoolValue(value);
1235
}

shell/platform/windows/flutter_windows_engine.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,12 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) {
319319
host->accessibility_bridge_->AddFlutterSemanticsCustomActionUpdate(
320320
action);
321321
};
322+
args.root_isolate_create_callback = [](void* user_data) {
323+
auto host = static_cast<FlutterWindowsEngine*>(user_data);
324+
if (host->root_isolate_create_callback_) {
325+
host->root_isolate_create_callback_();
326+
}
327+
};
322328

323329
args.custom_task_runners = &custom_task_runners;
324330

shell/platform/windows/flutter_windows_engine.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <string_view>
1414
#include <vector>
1515

16+
#include "flutter/fml/closure.h"
1617
#include "flutter/shell/platform/common/accessibility_bridge.h"
1718
#include "flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h"
1819
#include "flutter/shell/platform/common/client_wrapper/include/flutter/basic_message_channel.h"
@@ -203,6 +204,19 @@ class FlutterWindowsEngine {
203204
// Returns the native accessibility node with the given id.
204205
gfx::NativeViewAccessible GetNativeAccessibleFromId(AccessibilityNodeId id);
205206

207+
// Register a root isolate create callback.
208+
//
209+
// The root isolate create callback is invoked at creation of the root Dart
210+
// isolate in the app. This may be used to be notified that execution of the
211+
// main Dart entrypoint is about to begin, and is used by test infrastructure
212+
// to register a native function resolver that can resolve functions marked as
213+
// native in the Dart code.
214+
//
215+
// This must be called before calling |Run|.
216+
void SetRootIsolateCreateCallback(const fml::closure& callback) {
217+
root_isolate_create_callback_ = callback;
218+
}
219+
206220
private:
207221
// Allows swapping out embedder_api_ calls in tests.
208222
friend class EngineModifier;
@@ -277,6 +291,9 @@ class FlutterWindowsEngine {
277291

278292
// The manager for WindowProc delegate registration and callbacks.
279293
std::unique_ptr<WindowProcDelegateManager> window_proc_delegate_manager_;
294+
295+
// The root isolate creation callback.
296+
fml::closure root_isolate_create_callback_;
280297
};
281298

282299
} // namespace flutter

shell/platform/windows/flutter_windows_unittests.cc

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@
66

77
#include <thread>
88

9+
#include "flutter/fml/synchronization/waitable_event.h"
910
#include "flutter/shell/platform/windows/testing/windows_test.h"
1011
#include "flutter/shell/platform/windows/testing/windows_test_config_builder.h"
1112
#include "flutter/shell/platform/windows/testing/windows_test_context.h"
1213
#include "gtest/gtest.h"
14+
#include "third_party/tonic/converter/dart_converter.h"
1315

1416
namespace flutter {
1517
namespace testing {
1618

19+
// Verify that we can fetch a texture registrar.
20+
// Prevent regression: https://github.com/flutter/flutter/issues/86617
1721
TEST(WindowsNoFixtureTest, GetTextureRegistrar) {
1822
FlutterDesktopEngineProperties properties = {};
1923
properties.assets_path = L"";
@@ -25,33 +29,21 @@ TEST(WindowsNoFixtureTest, GetTextureRegistrar) {
2529
FlutterDesktopEngineDestroy(engine);
2630
}
2731

32+
// Verify we can successfully launch main().
2833
TEST_F(WindowsTest, LaunchMain) {
2934
auto& context = GetContext();
3035
WindowsConfigBuilder builder(context);
3136
ViewControllerPtr controller{builder.Run()};
3237
ASSERT_NE(controller, nullptr);
33-
34-
// Run for 1 second, then shut down.
35-
//
36-
// TODO(cbracken): Support registring a native function we can use to
37-
// determine that execution has made it to a specific point in the Dart
38-
// code. https://github.com/flutter/flutter/issues/109242
39-
std::this_thread::sleep_for(std::chrono::seconds(1));
4038
}
4139

40+
// Verify we can successfully launch a custom entry point.
4241
TEST_F(WindowsTest, LaunchCustomEntrypoint) {
4342
auto& context = GetContext();
4443
WindowsConfigBuilder builder(context);
4544
builder.SetDartEntrypoint("customEntrypoint");
4645
ViewControllerPtr controller{builder.Run()};
4746
ASSERT_NE(controller, nullptr);
48-
49-
// Run for 1 second, then shut down.
50-
//
51-
// TODO(cbracken): Support registring a native function we can use to
52-
// determine that execution has made it to a specific point in the Dart
53-
// code. https://github.com/flutter/flutter/issues/109242
54-
std::this_thread::sleep_for(std::chrono::seconds(1));
5547
}
5648

5749
// Verify that engine launches with the custom entrypoint specified in the
@@ -66,13 +58,6 @@ TEST_F(WindowsTest, LaunchCustomEntrypointInEngineRunInvocation) {
6658
ASSERT_NE(engine, nullptr);
6759

6860
ASSERT_TRUE(FlutterDesktopEngineRun(engine.get(), "customEntrypoint"));
69-
70-
// Run for 1 second, then shut down.
71-
//
72-
// TODO(cbracken): Support registring a native function we can use to
73-
// determine that execution has made it to a specific point in the Dart
74-
// code. https://github.com/flutter/flutter/issues/109242
75-
std::this_thread::sleep_for(std::chrono::seconds(1));
7661
}
7762

7863
// Verify that engine fails to launch when a conflicting entrypoint in
@@ -90,5 +75,76 @@ TEST_F(WindowsTest, LaunchConflictingCustomEntrypoints) {
9075
ASSERT_FALSE(FlutterDesktopEngineRun(engine.get(), "conflictingEntrypoint"));
9176
}
9277

78+
// Verify that native functions can be registered and resolved.
79+
TEST_F(WindowsTest, VerifyNativeFunction) {
80+
auto& context = GetContext();
81+
WindowsConfigBuilder builder(context);
82+
builder.SetDartEntrypoint("verifyNativeFunction");
83+
84+
static fml::AutoResetWaitableEvent latch;
85+
context.AddNativeFunction("Signal", [](Dart_NativeArguments args) {
86+
latch.Signal();
87+
});
88+
89+
ViewControllerPtr controller{builder.Run()};
90+
ASSERT_NE(controller, nullptr);
91+
92+
// Wait until signal has been called.
93+
latch.Wait();
94+
}
95+
96+
// Verify that native functions that pass parameters can be registered and
97+
// resolved.
98+
TEST_F(WindowsTest, VerifyNativeFunctionWithParameters) {
99+
auto& context = GetContext();
100+
WindowsConfigBuilder builder(context);
101+
builder.SetDartEntrypoint("verifyNativeFunctionWithParameters");
102+
103+
static fml::AutoResetWaitableEvent latch;
104+
context.AddNativeFunction("SignalBoolValue", [](Dart_NativeArguments args) {
105+
bool bool_value = false;
106+
auto handle = Dart_GetNativeBooleanArgument(args, 0, &bool_value);
107+
ASSERT_FALSE(Dart_IsError(handle));
108+
EXPECT_TRUE(bool_value);
109+
latch.Signal();
110+
});
111+
112+
ViewControllerPtr controller{builder.Run()};
113+
ASSERT_NE(controller, nullptr);
114+
115+
// Wait until signalBoolValue has been called.
116+
latch.Wait();
117+
}
118+
119+
// Verify that native functions that return values can be registered and
120+
// resolved.
121+
TEST_F(WindowsTest, VerifyNativeFunctionWithReturn) {
122+
auto& context = GetContext();
123+
WindowsConfigBuilder builder(context);
124+
builder.SetDartEntrypoint("verifyNativeFunctionWithReturn");
125+
126+
static fml::AutoResetWaitableEvent latch;
127+
context.AddNativeFunction("SignalBoolReturn", [](Dart_NativeArguments args) {
128+
Dart_SetBooleanReturnValue(args, true);
129+
latch.Signal();
130+
});
131+
context.AddNativeFunction("SignalBoolValue", [](Dart_NativeArguments args) {
132+
bool bool_value = false;
133+
auto handle = Dart_GetNativeBooleanArgument(args, 0, &bool_value);
134+
ASSERT_FALSE(Dart_IsError(handle));
135+
EXPECT_TRUE(bool_value);
136+
latch.Signal();
137+
});
138+
139+
ViewControllerPtr controller{builder.Run()};
140+
ASSERT_NE(controller, nullptr);
141+
142+
// Wait until signalBoolReturn has been called.
143+
latch.Wait();
144+
145+
// Wait until signalBoolValue has been called.
146+
latch.Wait();
147+
}
148+
93149
} // namespace testing
94150
} // namespace flutter

shell/platform/windows/testing/windows_test_config_builder.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <vector>
1212

1313
#include "flutter/fml/logging.h"
14+
#include "flutter/shell/platform/windows/flutter_windows_engine.h"
1415
#include "flutter/shell/platform/windows/public/flutter_windows.h"
1516
#include "flutter/shell/platform/windows/testing/windows_test_context.h"
1617

@@ -78,6 +79,12 @@ ViewControllerPtr WindowsConfigBuilder::Run() const {
7879
return {};
7980
}
8081

82+
// Register native functions.
83+
FlutterWindowsEngine* windows_engine =
84+
reinterpret_cast<FlutterWindowsEngine*>(engine.get());
85+
windows_engine->SetRootIsolateCreateCallback(
86+
context_.GetRootIsolateCallback());
87+
8188
int width = 600;
8289
int height = 400;
8390
ViewControllerPtr controller(

shell/platform/windows/testing/windows_test_context.cc

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,16 @@ namespace flutter {
1010
namespace testing {
1111

1212
WindowsTestContext::WindowsTestContext(std::string_view assets_path)
13-
: assets_path_(fml::Utf8ToWideString(assets_path)) {}
13+
: assets_path_(fml::Utf8ToWideString(assets_path)),
14+
native_resolver_(std::make_shared<TestDartNativeResolver>()) {
15+
isolate_create_callbacks_.push_back(
16+
[weak_resolver =
17+
std::weak_ptr<TestDartNativeResolver>{native_resolver_}]() {
18+
if (auto resolver = weak_resolver.lock()) {
19+
resolver->SetNativeResolverForIsolate();
20+
}
21+
});
22+
}
1423

1524
WindowsTestContext::~WindowsTestContext() = default;
1625

@@ -22,5 +31,18 @@ const std::wstring& WindowsTestContext::GetIcuDataPath() const {
2231
return icu_data_path_;
2332
}
2433

34+
void WindowsTestContext::AddNativeFunction(std::string_view name,
35+
Dart_NativeFunction function) {
36+
native_resolver_->AddNativeCallback(std::string{name}, function);
37+
}
38+
39+
fml::closure WindowsTestContext::GetRootIsolateCallback() {
40+
return [this]() {
41+
for (auto closure : this->isolate_create_callbacks_) {
42+
closure();
43+
}
44+
};
45+
}
46+
2547
} // namespace testing
2648
} // namespace flutter

shell/platform/windows/testing/windows_test_context.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@
77

88
#include <string>
99
#include <string_view>
10+
#include <vector>
1011

12+
#include "flutter/fml/closure.h"
1113
#include "flutter/fml/macros.h"
14+
#include "flutter/testing/test_dart_native_resolver.h"
1215

1316
namespace flutter {
1417
namespace testing {
@@ -27,9 +30,24 @@ class WindowsTestContext {
2730

2831
const std::wstring& GetIcuDataPath() const;
2932

33+
// Registers a native function callable from Dart code in test fixtures. In
34+
// the Dart test fixture, the associated function can be declared as:
35+
//
36+
// ReturnType functionName() native 'IdentifyingName';
37+
//
38+
// where `IdentifyingName` matches the |name| parameter to this method.
39+
void AddNativeFunction(std::string_view name,
40+
Dart_NativeFunction function);
41+
42+
// Returns the root isolate create callback to register with the Flutter
43+
// runtime.
44+
fml::closure GetRootIsolateCallback();
45+
3046
private:
3147
std::wstring assets_path_;
3248
std::wstring icu_data_path_ = L"icudtl.dat";
49+
std::vector<fml::closure> isolate_create_callbacks_;
50+
std::shared_ptr<TestDartNativeResolver> native_resolver_;
3351

3452
FML_DISALLOW_COPY_AND_ASSIGN(WindowsTestContext);
3553
};

0 commit comments

Comments
 (0)