Add Windows unit tests to plugin template (#118638)

* Add Windows unit tests to plugin template

Adds an example native unit test to the plugin template for Windows,
matching the format we use for our 1P plugin example app unit tests.
Once these have been added for all platforms+languages, they will be
documented on a new plugin development page to explain their use.

Since we don't appear to be running our current plugin e2e tests for
Windows, this adds a new configuration to run them. I haven't
`led`-tested this, so it may not work, but this will give a starting
point for getting them running.

Part of https://github.com/flutter/flutter/issues/82458

* Minor fix

* Add test owner

* Fix typo

* Fix test feature flag
This commit is contained in:
stuartmorgan 2023-01-24 10:23:57 -08:00 committed by GitHub
parent 4d250302aa
commit e3c51a2f2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 159 additions and 3 deletions

View File

@ -4364,6 +4364,24 @@ targets:
- bin/**
- .ci.yaml
- name: Windows plugin_test_windows
bringup: true # New task
recipe: devicelab/devicelab_drone
timeout: 60
properties:
dependencies: >-
[
{"dependency": "vs_build", "version": "version:vs2019"}
]
tags: >
["devicelab", "hostonly", "windows"]
task_name: plugin_test_windows
runIf:
- dev/**
- packages/flutter_tools/**
- bin/**
- .ci.yaml
- name: Windows run_debug_test_windows
recipe: devicelab/devicelab_drone
presubmit: false

View File

@ -251,6 +251,7 @@
/dev/devicelab/bin/tasks/plugin_lint_mac.dart @stuartmorgan @flutter/plugin
/dev/devicelab/bin/tasks/plugin_test_ios.dart @jmagman @flutter/ios
/dev/devicelab/bin/tasks/plugin_test_macos.dart @jmagman @flutter/desktop
/dev/devicelab/bin/tasks/plugin_test_windows.dart @stuartmorgan @flutter/desktop
/dev/devicelab/bin/tasks/plugin_test.dart @stuartmorgan @flutter/plugin
/dev/devicelab/bin/tasks/run_debug_test_android.dart @zanderso @flutter/tool
/dev/devicelab/bin/tasks/run_debug_test_linux.dart @loic-sharma @flutter/tool

View File

@ -0,0 +1,16 @@
// Copyright 2014 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 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/plugin_tests.dart';
Future<void> main() async {
await task(combine(<TaskFunction>[
PluginTest('windows', <String>['--platforms=windows']).call,
// Test that Dart-only plugins are supported.
PluginTest('windows', <String>['--platforms=windows'], dartOnlyPlugin: true).call,
// Test that FFI plugins are supported.
PluginTest('windows', <String>['--platforms=windows'], template: 'plugin_ffi').call,
]));
}

View File

@ -274,6 +274,14 @@ public class $pluginClass: NSObject, FlutterPlugin {
)) {
throw TaskResult.failure('Platform unit tests failed');
}
} else if (buildTarget == 'windows') {
if (await exec(
path.join(rootPath, 'build', 'windows', 'plugins', 'plugintest', 'Release', 'plugintest_plugin_test'),
<String>[],
canFail: true,
) != 0) {
throw TaskResult.failure('Platform unit tests failed');
}
}
}

View File

@ -52,6 +52,11 @@ add_subdirectory(${FLUTTER_MANAGED_DIR})
# Application build; see runner/CMakeLists.txt.
add_subdirectory("runner")
{{#withPlatformChannelPluginHook}}
# Enable the test target.
set(include_{{pluginProjectName}}_tests TRUE)
{{/withPlatformChannelPluginHook}}
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)

View File

@ -51,3 +51,46 @@ set({{projectName}}_bundled_libraries
""
PARENT_SCOPE
)
# === Tests ===
# These unit tests can be run from a terminal after building the example, or
# from Visual Studio after opening the generated solution file.
# Only enable test builds when building the example (which sets this variable)
# so that plugin clients aren't building the tests.
if (${include_${PROJECT_NAME}_tests})
set(TEST_RUNNER "${PROJECT_NAME}_test")
enable_testing()
# Add the Google Test dependency.
include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/release-1.11.0.zip
)
# Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
# Disable install commands for gtest so it doesn't end up in the bundle.
set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE)
FetchContent_MakeAvailable(googletest)
# The plugin's C API is not very useful for unit testing, so build the sources
# directly into the test binary rather than using the DLL.
add_executable(${TEST_RUNNER}
test/{{pluginClassSnakeCase}}_test.cpp
${PLUGIN_SOURCES}
)
apply_standard_settings(${TEST_RUNNER})
target_include_directories(${TEST_RUNNER} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
target_link_libraries(${TEST_RUNNER} PRIVATE flutter_wrapper_plugin)
target_link_libraries(${TEST_RUNNER} PRIVATE gtest_main gmock)
# flutter_wrapper_plugin has link dependencies on the Flutter DLL.
add_custom_command(TARGET ${TEST_RUNNER} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${FLUTTER_LIBRARY}" $<TARGET_FILE_DIR:${TEST_RUNNER}>
)
# Enable automatic test discovery.
include(GoogleTest)
gtest_discover_tests(${TEST_RUNNER})
endif()

View File

@ -20,7 +20,6 @@ class {{pluginClass}} : public flutter::Plugin {
{{pluginClass}}(const {{pluginClass}}&) = delete;
{{pluginClass}}& operator=(const {{pluginClass}}&) = delete;
private:
// Called when a method is called on this plugin's channel from Dart.
void HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue> &method_call,

View File

@ -0,0 +1,43 @@
#include <flutter/method_call.h>
#include <flutter/method_result_functions.h>
#include <flutter/standard_method_codec.h>
#include <gtest/gtest.h>
#include <windows.h>
#include <memory>
#include <string>
#include <variant>
#include "{{pluginClassSnakeCase}}.h"
namespace {{projectName}} {
namespace test {
namespace {
using flutter::EncodableMap;
using flutter::EncodableValue;
using flutter::MethodCall;
using flutter::MethodResultFunctions;
} // namespace
TEST({{pluginClass}}, GetPlatformVersion) {
{{pluginClass}} plugin;
// Save the reply value from the success callback.
std::string result_string;
plugin.HandleMethodCall(
MethodCall("getPlatformVersion", std::make_unique<EncodableValue>()),
std::make_unique<MethodResultFunctions<>>(
[&result_string](const EncodableValue* result) {
result_string = std::get<std::string>(*result);
},
nullptr, nullptr));
// Since the exact string varies by host, just ensure that it's a string
// with the expected format.
EXPECT_TRUE(result_string.rfind("Windows ", 0) == 0);
}
} // namespace test
} // namespace {{projectName}}

View File

@ -278,6 +278,7 @@
"templates/plugin/test/projectName_method_channel_test.dart.tmpl",
"templates/plugin/windows.tmpl/CMakeLists.txt.tmpl",
"templates/plugin/windows.tmpl/include/projectName.tmpl/pluginClassSnakeCase_c_api.h.tmpl",
"templates/plugin/windows.tmpl/test/pluginClassSnakeCase_test.cpp.tmpl",
"templates/plugin/windows.tmpl/pluginClassSnakeCase.cpp.tmpl",
"templates/plugin/windows.tmpl/pluginClassSnakeCase.h.tmpl",
"templates/plugin/windows.tmpl/pluginClassSnakeCase_c_api.cpp.tmpl",

View File

@ -2429,7 +2429,7 @@ void main() {
androidIdentifier: 'com.example.flutter_project');
});
testUsingContext('plugin includes native unit tests', () async {
testUsingContext('plugin includes native Swift unit tests', () async {
Cache.flutterRoot = '../..';
final CreateCommand command = CreateCommand();
@ -2452,7 +2452,7 @@ void main() {
Logger: () => logger,
});
testUsingContext('plugin includes native Ojb-C unit tests', () async {
testUsingContext('plugin includes native Objective-C unit tests', () async {
Cache.flutterRoot = '../..';
final CreateCommand command = CreateCommand();
@ -2476,6 +2476,28 @@ void main() {
Logger: () => logger,
});
testUsingContext('plugin includes native Windows unit tests', () async {
Cache.flutterRoot = '../..';
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>[
'create',
'--no-pub',
'--template=plugin',
'--platforms=windows',
projectDir.path]);
expect(projectDir
.childDirectory('windows')
.childDirectory('test')
.childFile('flutter_project_plugin_test.cpp'), exists);
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
Logger: () => logger,
});
testUsingContext('create a module with --platforms throws error.', () async {
Cache.flutterRoot = '../..';