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

[Windows] Support native functions in test fixtures #35357

Merged
merged 1 commit into from
Aug 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions shell/platform/windows/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ executable("flutter_windows_unittests") {
"//flutter/shell/platform/embedder:embedder_as_internal_library",
"//flutter/shell/platform/embedder:embedder_test_utils",
"//flutter/testing",
"//flutter/testing:dart",
"//flutter/third_party/tonic",
"//third_party/rapidjson",
]
}
Expand Down
27 changes: 25 additions & 2 deletions shell/platform/windows/fixtures/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,34 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Signals a waiting latch in the native test.
void signal() native 'Signal';

// Signals a waiting latch in the native test, passing a boolean value.
void signalBoolValue(bool value) native 'SignalBoolValue';

// Signals a waiting latch in the native test, which returns a value to the fixture.
bool signalBoolReturn() native 'SignalBoolReturn';

void main() {
print('Hello windows engine test main!');
}

@pragma('vm:entry-point')
void customEntrypoint() {
print('Hello windows engine test customEntrypoint!');
}

@pragma('vm:entry-point')
void verifyNativeFunction() {
signal();
}

@pragma('vm:entry-point')
void verifyNativeFunctionWithParameters() {
signalBoolValue(true);
}

@pragma('vm:entry-point')
void verifyNativeFunctionWithReturn() {
bool value = signalBoolReturn();
signalBoolValue(value);
}
6 changes: 6 additions & 0 deletions shell/platform/windows/flutter_windows_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,12 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) {
host->accessibility_bridge_->AddFlutterSemanticsCustomActionUpdate(
action);
};
args.root_isolate_create_callback = [](void* user_data) {
auto host = static_cast<FlutterWindowsEngine*>(user_data);
if (host->root_isolate_create_callback_) {
host->root_isolate_create_callback_();
}
};

args.custom_task_runners = &custom_task_runners;

Expand Down
17 changes: 17 additions & 0 deletions shell/platform/windows/flutter_windows_engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <string_view>
#include <vector>

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

// Register a root isolate create callback.
//
// The root isolate create callback is invoked at creation of the root Dart
// isolate in the app. This may be used to be notified that execution of the
// main Dart entrypoint is about to begin, and is used by test infrastructure
// to register a native function resolver that can register and resolve
// functions marked as native in the Dart code.
//
// This must be called before calling |Run|.
void SetRootIsolateCreateCallback(const fml::closure& callback) {
root_isolate_create_callback_ = callback;
}

private:
// Allows swapping out embedder_api_ calls in tests.
friend class EngineModifier;
Expand Down Expand Up @@ -277,6 +291,9 @@ class FlutterWindowsEngine {

// The manager for WindowProc delegate registration and callbacks.
std::unique_ptr<WindowProcDelegateManager> window_proc_delegate_manager_;

// The root isolate creation callback.
fml::closure root_isolate_create_callback_;
};

} // namespace flutter
Expand Down
101 changes: 80 additions & 21 deletions shell/platform/windows/flutter_windows_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@

#include <thread>

#include "flutter/fml/synchronization/count_down_latch.h"
#include "flutter/fml/synchronization/waitable_event.h"
#include "flutter/shell/platform/windows/testing/windows_test.h"
#include "flutter/shell/platform/windows/testing/windows_test_config_builder.h"
#include "flutter/shell/platform/windows/testing/windows_test_context.h"
#include "gtest/gtest.h"
#include "third_party/tonic/converter/dart_converter.h"

namespace flutter {
namespace testing {

// Verify that we can fetch a texture registrar.
// Prevent regression: https://github.com/flutter/flutter/issues/86617
TEST(WindowsNoFixtureTest, GetTextureRegistrar) {
FlutterDesktopEngineProperties properties = {};
properties.assets_path = L"";
Expand All @@ -25,33 +30,21 @@ TEST(WindowsNoFixtureTest, GetTextureRegistrar) {
FlutterDesktopEngineDestroy(engine);
}

// Verify we can successfully launch main().
TEST_F(WindowsTest, LaunchMain) {
auto& context = GetContext();
WindowsConfigBuilder builder(context);
ViewControllerPtr controller{builder.Run()};
ASSERT_NE(controller, nullptr);

// Run for 1 second, then shut down.
//
// TODO(cbracken): Support registring a native function we can use to
// determine that execution has made it to a specific point in the Dart
// code. https://github.com/flutter/flutter/issues/109242
std::this_thread::sleep_for(std::chrono::seconds(1));
}

// Verify we can successfully launch a custom entry point.
TEST_F(WindowsTest, LaunchCustomEntrypoint) {
auto& context = GetContext();
WindowsConfigBuilder builder(context);
builder.SetDartEntrypoint("customEntrypoint");
ViewControllerPtr controller{builder.Run()};
ASSERT_NE(controller, nullptr);

// Run for 1 second, then shut down.
//
// TODO(cbracken): Support registring a native function we can use to
// determine that execution has made it to a specific point in the Dart
// code. https://github.com/flutter/flutter/issues/109242
std::this_thread::sleep_for(std::chrono::seconds(1));
}

// Verify that engine launches with the custom entrypoint specified in the
Expand All @@ -66,13 +59,6 @@ TEST_F(WindowsTest, LaunchCustomEntrypointInEngineRunInvocation) {
ASSERT_NE(engine, nullptr);

ASSERT_TRUE(FlutterDesktopEngineRun(engine.get(), "customEntrypoint"));

// Run for 1 second, then shut down.
//
// TODO(cbracken): Support registring a native function we can use to
// determine that execution has made it to a specific point in the Dart
// code. https://github.com/flutter/flutter/issues/109242
std::this_thread::sleep_for(std::chrono::seconds(1));
}

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

// Verify that native functions can be registered and resolved.
TEST_F(WindowsTest, VerifyNativeFunction) {
auto& context = GetContext();
WindowsConfigBuilder builder(context);
builder.SetDartEntrypoint("verifyNativeFunction");

fml::AutoResetWaitableEvent latch;
auto native_entry =
CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); });
context.AddNativeFunction("Signal", native_entry);

ViewControllerPtr controller{builder.Run()};
ASSERT_NE(controller, nullptr);

// Wait until signal has been called.
latch.Wait();
}

// Verify that native functions that pass parameters can be registered and
// resolved.
TEST_F(WindowsTest, VerifyNativeFunctionWithParameters) {
auto& context = GetContext();
WindowsConfigBuilder builder(context);
builder.SetDartEntrypoint("verifyNativeFunctionWithParameters");

bool bool_value = false;
fml::AutoResetWaitableEvent latch;
auto native_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
auto handle = Dart_GetNativeBooleanArgument(args, 0, &bool_value);
ASSERT_FALSE(Dart_IsError(handle));
latch.Signal();
});
context.AddNativeFunction("SignalBoolValue", native_entry);

ViewControllerPtr controller{builder.Run()};
ASSERT_NE(controller, nullptr);

// Wait until signalBoolValue has been called.
latch.Wait();
EXPECT_TRUE(bool_value);
}

// Verify that native functions that return values can be registered and
// resolved.
TEST_F(WindowsTest, VerifyNativeFunctionWithReturn) {
auto& context = GetContext();
WindowsConfigBuilder builder(context);
builder.SetDartEntrypoint("verifyNativeFunctionWithReturn");

bool bool_value_to_return = true;
fml::CountDownLatch latch(2);
auto bool_return_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
Dart_SetBooleanReturnValue(args, bool_value_to_return);
latch.CountDown();
});
context.AddNativeFunction("SignalBoolReturn", bool_return_entry);

bool bool_value_passed = false;
auto bool_pass_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
auto handle = Dart_GetNativeBooleanArgument(args, 0, &bool_value_passed);
ASSERT_FALSE(Dart_IsError(handle));
latch.CountDown();
});
context.AddNativeFunction("SignalBoolValue", bool_pass_entry);

ViewControllerPtr controller{builder.Run()};
ASSERT_NE(controller, nullptr);

// Wait until signalBoolReturn and signalBoolValue have been called.
latch.Wait();
EXPECT_TRUE(bool_value_passed);
}

} // namespace testing
} // namespace flutter
7 changes: 7 additions & 0 deletions shell/platform/windows/testing/windows_test_config_builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <vector>

#include "flutter/fml/logging.h"
#include "flutter/shell/platform/windows/flutter_windows_engine.h"
#include "flutter/shell/platform/windows/public/flutter_windows.h"
#include "flutter/shell/platform/windows/testing/windows_test_context.h"

Expand Down Expand Up @@ -78,6 +79,12 @@ ViewControllerPtr WindowsConfigBuilder::Run() const {
return {};
}

// Register native functions.
FlutterWindowsEngine* windows_engine =
reinterpret_cast<FlutterWindowsEngine*>(engine.get());
windows_engine->SetRootIsolateCreateCallback(
context_.GetRootIsolateCallback());

int width = 600;
int height = 400;
ViewControllerPtr controller(
Expand Down
24 changes: 23 additions & 1 deletion shell/platform/windows/testing/windows_test_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,16 @@ namespace flutter {
namespace testing {

WindowsTestContext::WindowsTestContext(std::string_view assets_path)
: assets_path_(fml::Utf8ToWideString(assets_path)) {}
: assets_path_(fml::Utf8ToWideString(assets_path)),
native_resolver_(std::make_shared<TestDartNativeResolver>()) {
isolate_create_callbacks_.push_back(
[weak_resolver =
std::weak_ptr<TestDartNativeResolver>{native_resolver_}]() {
if (auto resolver = weak_resolver.lock()) {
resolver->SetNativeResolverForIsolate();
}
});
}

WindowsTestContext::~WindowsTestContext() = default;

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

void WindowsTestContext::AddNativeFunction(std::string_view name,
Dart_NativeFunction function) {
native_resolver_->AddNativeCallback(std::string{name}, function);
}

fml::closure WindowsTestContext::GetRootIsolateCallback() {
return [this]() {
for (auto closure : this->isolate_create_callbacks_) {
closure();
}
};
}

} // namespace testing
} // namespace flutter
17 changes: 17 additions & 0 deletions shell/platform/windows/testing/windows_test_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@

#include <string>
#include <string_view>
#include <vector>

#include "flutter/fml/closure.h"
#include "flutter/fml/macros.h"
#include "flutter/testing/test_dart_native_resolver.h"

namespace flutter {
namespace testing {
Expand All @@ -27,9 +30,23 @@ class WindowsTestContext {

const std::wstring& GetIcuDataPath() const;

// Registers a native function callable from Dart code in test fixtures. In
// the Dart test fixture, the associated function can be declared as:
//
// ReturnType functionName() native 'IdentifyingName';
//
// where `IdentifyingName` matches the |name| parameter to this method.
void AddNativeFunction(std::string_view name, Dart_NativeFunction function);

// Returns the root isolate create callback to register with the Flutter
// runtime.
fml::closure GetRootIsolateCallback();

private:
std::wstring assets_path_;
std::wstring icu_data_path_ = L"icudtl.dat";
std::vector<fml::closure> isolate_create_callbacks_;
std::shared_ptr<TestDartNativeResolver> native_resolver_;

FML_DISALLOW_COPY_AND_ASSIGN(WindowsTestContext);
};
Expand Down