Skip to content

Commit e65dfba

Browse files
Add Linux unit tests to plugin template (#120814)
* Add Linux unit tests to plugin template Adds an example native unit test to the plugin template for Linux, matching the structure we use for our 1P plugin 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. While ideally we would adjust the engine APIs first to allow for testing the method call handler directly, it's unclear when we will have time for that work, and for a complex plugin most of the testing wouldn't be at that layer anyway, so having the structure in place with the limitations documented is still a significant improvement over having nothing in the template. Part of flutter/flutter#82458 * Add creation test * Add integration tests * Missing newlines * test owner * Typo
1 parent 2b7d709 commit e65dfba

File tree

11 files changed

+177
-8
lines changed

11 files changed

+177
-8
lines changed

.ci.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,26 @@ targets:
697697
- bin/**
698698
- .ci.yaml
699699

700+
- name: Linux plugin_test_linux
701+
recipe: devicelab/devicelab_drone
702+
bringup: true # New task
703+
timeout: 60
704+
properties:
705+
dependencies: >-
706+
[
707+
{"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"},
708+
{"dependency": "cmake", "version": "version:3.16.1"},
709+
{"dependency": "ninja", "version": "version:1.9.0"}
710+
]
711+
tags: >
712+
["devicelab", "hostonly", "linux"]
713+
task_name: plugin_test_linux
714+
runIf:
715+
- dev/**
716+
- packages/flutter_tools/**
717+
- bin/**
718+
- .ci.yaml
719+
700720
- name: Linux run_debug_test_linux
701721
recipe: devicelab/devicelab_drone
702722
bringup: true

TESTOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@
252252
/dev/devicelab/bin/tasks/platform_view_win_desktop__start_up.dart @yaakovschectman @flutter/desktop
253253
/dev/devicelab/bin/tasks/plugin_lint_mac.dart @stuartmorgan @flutter/plugin
254254
/dev/devicelab/bin/tasks/plugin_test_ios.dart @jmagman @flutter/ios
255+
/dev/devicelab/bin/tasks/plugin_test_linux.dart @stuartmorgan @flutter/desktop
255256
/dev/devicelab/bin/tasks/plugin_test_macos.dart @jmagman @flutter/desktop
256257
/dev/devicelab/bin/tasks/plugin_test_windows.dart @stuartmorgan @flutter/desktop
257258
/dev/devicelab/bin/tasks/plugin_test.dart @stuartmorgan @flutter/plugin
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter_devicelab/framework/framework.dart';
6+
import 'package:flutter_devicelab/tasks/plugin_tests.dart';
7+
8+
Future<void> main() async {
9+
await task(combine(<TaskFunction>[
10+
PluginTest('linux', <String>['--platforms=linux']).call,
11+
// Test that Dart-only plugins are supported.
12+
PluginTest('linux', <String>['--platforms=linux'], dartOnlyPlugin: true).call,
13+
// Test that FFI plugins are supported.
14+
PluginTest('linux', <String>['--platforms=linux'], template: 'plugin_ffi').call,
15+
]));
16+
}

dev/devicelab/lib/tasks/plugin_tests.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,15 @@ public class $pluginClass: NSObject, FlutterPlugin {
276276
}
277277
});
278278
break;
279+
case 'linux':
280+
if (await exec(
281+
path.join(rootPath, 'build', 'linux', 'x64', 'release', 'plugins', 'plugintest', 'plugintest_plugin_test'),
282+
<String>[],
283+
canFail: true,
284+
) != 0) {
285+
throw TaskResult.failure('Platform unit tests failed');
286+
}
287+
break;
279288
case 'macos':
280289
if (!await runXcodeTests(
281290
platformDirectory: path.join(rootPath, 'macos'),

packages/flutter_tools/templates/app_shared/linux.tmpl/CMakeLists.txt.tmpl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ set_target_properties(${BINARY_NAME}
8686
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
8787
)
8888

89+
{{#withPlatformChannelPluginHook}}
90+
# Enable the test target.
91+
set(include_{{pluginProjectName}}_tests TRUE)
92+
{{/withPlatformChannelPluginHook}}
93+
8994
# Generated plugin build rules, which manage building the plugins and adding
9095
# them to the application.
9196
include(flutter/generated_plugins.cmake)

packages/flutter_tools/templates/plugin/linux.tmpl/CMakeLists.txt.tmpl

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@ project(${PROJECT_NAME} LANGUAGES CXX)
1111
# not be changed.
1212
set(PLUGIN_NAME "{{projectName}}_plugin")
1313

14+
# Any new source files that you add to the plugin should be added here.
15+
list(APPEND PLUGIN_SOURCES
16+
"{{pluginClassSnakeCase}}.cc"
17+
)
18+
1419
# Define the plugin library target. Its name must not be changed (see comment
1520
# on PLUGIN_NAME above).
16-
#
17-
# Any new source files that you add to the plugin should be added here.
1821
add_library(${PLUGIN_NAME} SHARED
19-
"{{pluginClassSnakeCase}}.cc"
22+
${PLUGIN_SOURCES}
2023
)
2124

2225
# Apply a standard set of build settings that are configured in the
@@ -45,3 +48,47 @@ set({{projectName}}_bundled_libraries
4548
""
4649
PARENT_SCOPE
4750
)
51+
52+
# === Tests ===
53+
# These unit tests can be run from a terminal after building the example.
54+
55+
# Only enable test builds when building the example (which sets this variable)
56+
# so that plugin clients aren't building the tests.
57+
if (${include_${PROJECT_NAME}_tests})
58+
if(${CMAKE_VERSION} VERSION_LESS "3.11.0")
59+
message("Unit tests require CMake 3.11.0 or later")
60+
else()
61+
set(TEST_RUNNER "${PROJECT_NAME}_test")
62+
enable_testing()
63+
64+
# Add the Google Test dependency.
65+
include(FetchContent)
66+
FetchContent_Declare(
67+
googletest
68+
URL https://github.com/google/googletest/archive/release-1.11.0.zip
69+
)
70+
# Prevent overriding the parent project's compiler/linker settings
71+
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
72+
# Disable install commands for gtest so it doesn't end up in the bundle.
73+
set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE)
74+
75+
FetchContent_MakeAvailable(googletest)
76+
77+
# The plugin's exported API is not very useful for unit testing, so build the
78+
# sources directly into the test binary rather than using the shared library.
79+
add_executable(${TEST_RUNNER}
80+
test/{{pluginClassSnakeCase}}_test.cc
81+
${PLUGIN_SOURCES}
82+
)
83+
apply_standard_settings(${TEST_RUNNER})
84+
target_include_directories(${TEST_RUNNER} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
85+
target_link_libraries(${TEST_RUNNER} PRIVATE flutter)
86+
target_link_libraries(${TEST_RUNNER} PRIVATE PkgConfig::GTK)
87+
target_link_libraries(${TEST_RUNNER} PRIVATE gtest_main gmock)
88+
89+
# Enable automatic test discovery.
90+
include(GoogleTest)
91+
gtest_discover_tests(${TEST_RUNNER})
92+
93+
endif() # CMake version check
94+
endif() # include_${PROJECT_NAME}_tests

packages/flutter_tools/templates/plugin/linux.tmpl/pluginClassSnakeCase.cc.tmpl

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
#include <cstring>
88

9+
#include "{{pluginClassSnakeCase}}_private.h"
10+
911
#define {{pluginClassCapitalSnakeCase}}(obj) \
1012
(G_TYPE_CHECK_INSTANCE_CAST((obj), {{pluginClassSnakeCase}}_get_type(), \
1113
{{pluginClass}}))
@@ -25,18 +27,22 @@ static void {{pluginClassSnakeCase}}_handle_method_call(
2527
const gchar* method = fl_method_call_get_name(method_call);
2628

2729
if (strcmp(method, "getPlatformVersion") == 0) {
28-
struct utsname uname_data = {};
29-
uname(&uname_data);
30-
g_autofree gchar *version = g_strdup_printf("Linux %s", uname_data.version);
31-
g_autoptr(FlValue) result = fl_value_new_string(version);
32-
response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
30+
response = get_platform_version();
3331
} else {
3432
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
3533
}
3634

3735
fl_method_call_respond(method_call, response, nullptr);
3836
}
3937

38+
FlMethodResponse* get_platform_version() {
39+
struct utsname uname_data = {};
40+
uname(&uname_data);
41+
g_autofree gchar *version = g_strdup_printf("Linux %s", uname_data.version);
42+
g_autoptr(FlValue) result = fl_value_new_string(version);
43+
return FL_METHOD_RESPONSE(fl_method_success_response_new(result));
44+
}
45+
4046
static void {{pluginClassSnakeCase}}_dispose(GObject* object) {
4147
G_OBJECT_CLASS({{pluginClassSnakeCase}}_parent_class)->dispose(object);
4248
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#include <flutter_linux/flutter_linux.h>
2+
3+
#include "include/{{projectName}}/{{pluginClassSnakeCase}}.h"
4+
5+
// This file exposes some plugin internals for unit testing. See
6+
// https://github.com/flutter/flutter/issues/88724 for current limitations
7+
// in the unit-testable API.
8+
9+
// Handles the getPlatformVersion method call.
10+
FlMethodResponse *get_platform_version();
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#include <flutter_linux/flutter_linux.h>
2+
#include <gmock/gmock.h>
3+
#include <gtest/gtest.h>
4+
5+
#include "include/{{projectName}}/{{pluginClassSnakeCase}}.h"
6+
#include "{{pluginClassSnakeCase}}_private.h"
7+
8+
// This demonstrates a simple unit test of the C portion of this plugin's
9+
// implementation.
10+
//
11+
// Once you have built the plugin's example app, you can run these tests
12+
// from the command line. For instance, for a plugin called my_plugin
13+
// built for x64 debug, run:
14+
// $ build/linux/x64/debug/plugins/my_plugin/my_plugin_test
15+
16+
namespace {{projectName}} {
17+
namespace test {
18+
19+
TEST({{pluginClass}}, GetPlatformVersion) {
20+
g_autoptr(FlMethodResponse) response = get_platform_version();
21+
ASSERT_NE(response, nullptr);
22+
ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
23+
FlValue* result = fl_method_success_response_get_result(
24+
FL_METHOD_SUCCESS_RESPONSE(response));
25+
ASSERT_EQ(fl_value_get_type(result), FL_VALUE_TYPE_STRING);
26+
// The full string varies, so just valiate that it has the right format.
27+
EXPECT_THAT(fl_value_get_string(result), testing::StartsWith("Linux "));
28+
}
29+
30+
} // namespace test
31+
} // namespace {{projectName}}

packages/flutter_tools/templates/template_manifest.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@
274274
"templates/plugin/linux.tmpl/CMakeLists.txt.tmpl",
275275
"templates/plugin/linux.tmpl/include/projectName.tmpl/pluginClassSnakeCase.h.tmpl",
276276
"templates/plugin/linux.tmpl/pluginClassSnakeCase.cc.tmpl",
277+
"templates/plugin/linux.tmpl/pluginClassSnakeCase_private.h.tmpl",
278+
"templates/plugin/linux.tmpl/test/pluginClassSnakeCase_test.cc.tmpl",
277279
"templates/plugin/macos.tmpl/Classes/pluginClass.swift.tmpl",
278280
"templates/plugin/README.md.tmpl",
279281
"templates/plugin/test/projectName_test.dart.tmpl",

packages/flutter_tools/test/commands.shard/permeable/create_test.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2555,6 +2555,28 @@ void main() {
25552555
Logger: () => logger,
25562556
});
25572557

2558+
testUsingContext('plugin includes native Linux unit tests', () async {
2559+
Cache.flutterRoot = '../..';
2560+
2561+
final CreateCommand command = CreateCommand();
2562+
final CommandRunner<void> runner = createTestCommandRunner(command);
2563+
2564+
await runner.run(<String>[
2565+
'create',
2566+
'--no-pub',
2567+
'--template=plugin',
2568+
'--platforms=linux',
2569+
projectDir.path]);
2570+
2571+
expect(projectDir
2572+
.childDirectory('linux')
2573+
.childDirectory('test')
2574+
.childFile('flutter_project_plugin_test.cc'), exists);
2575+
}, overrides: <Type, Generator>{
2576+
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
2577+
Logger: () => logger,
2578+
});
2579+
25582580
testUsingContext('create a module with --platforms throws error.', () async {
25592581
Cache.flutterRoot = '../..';
25602582

0 commit comments

Comments
 (0)