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

Commit df75b01

Browse files
[flutter_plugin_tools] Add Linux support to native-test (#4294)
- Adds a minimal unit test to url_launcher_linux as a proof of concept. This uses almost exactly the same CMake structure as the Windows version that was added recently. - Adds Linux support for unit tests to `native-test`, sharing almost all of the existing Windows codepath. - Fixes the fact that it it was running the debug version of the unit tests, but `build-examples` only builds release. (On other platforms we run debug unit tests, but on those platforms the test command internally builds the requested unit tests, so the mismatch doesn't matter.) - Enables the new test in CI. Also opportunistically fixes some documentation in `native_test_command.dart` that wasn't updated as more platform support was added. Linux portion of flutter/flutter#82445
1 parent 9ef18bc commit df75b01

File tree

10 files changed

+312
-15
lines changed

10 files changed

+312
-15
lines changed

.cirrus.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ task:
127127
build_script:
128128
- flutter config --enable-linux-desktop
129129
- ./script/tool_runner.sh build-examples --linux
130+
native_test_script:
131+
- ./script/tool_runner.sh native-test --linux --no-integration
130132
drive_script:
131133
- xvfb-run ./script/tool_runner.sh drive-examples --linux
132134

packages/url_launcher/url_launcher_linux/example/linux/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ target_link_libraries(${BINARY_NAME} PRIVATE flutter)
4343
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
4444
add_dependencies(${BINARY_NAME} flutter_assemble)
4545

46+
# Enable the test target.
47+
set(include_url_launcher_linux_tests TRUE)
48+
4649
# Generated plugin build rules, which manage building the plugins and adding
4750
# them to the application.
4851
include(flutter/generated_plugins.cmake)

packages/url_launcher/url_launcher_linux/example/linux/flutter/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ add_custom_command(
7878
COMMAND ${CMAKE_COMMAND} -E env
7979
${FLUTTER_TOOL_ENVIRONMENT}
8080
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
81-
linux-x64 ${CMAKE_BUILD_TYPE}
81+
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
82+
VERBATIM
8283
)
8384
add_custom_target(flutter_assemble DEPENDS
8485
"${FLUTTER_LIBRARY}"

packages/url_launcher/url_launcher_linux/linux/CMakeLists.txt

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ project(${PROJECT_NAME} LANGUAGES CXX)
44

55
set(PLUGIN_NAME "${PROJECT_NAME}_plugin")
66

7-
add_library(${PLUGIN_NAME} SHARED
7+
list(APPEND PLUGIN_SOURCES
88
"url_launcher_plugin.cc"
99
)
10+
11+
add_library(${PLUGIN_NAME} SHARED
12+
${PLUGIN_SOURCES}
13+
)
1014
apply_standard_settings(${PLUGIN_NAME})
1115
set_target_properties(${PLUGIN_NAME} PROPERTIES
1216
CXX_VISIBILITY_PRESET hidden)
@@ -15,3 +19,44 @@ target_include_directories(${PLUGIN_NAME} INTERFACE
1519
"${CMAKE_CURRENT_SOURCE_DIR}/include")
1620
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter)
1721
target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK)
22+
23+
24+
# === Tests ===
25+
26+
if (${include_${PROJECT_NAME}_tests})
27+
if(${CMAKE_VERSION} VERSION_LESS "3.11.0")
28+
message("Unit tests require CMake 3.11.0 or later")
29+
else()
30+
set(TEST_RUNNER "${PROJECT_NAME}_test")
31+
enable_testing()
32+
# TODO(stuartmorgan): Consider using a single shared, pre-checked-in googletest
33+
# instance rather than downloading for each plugin. This approach makes sense
34+
# for a template, but not for a monorepo with many plugins.
35+
include(FetchContent)
36+
FetchContent_Declare(
37+
googletest
38+
URL https://github.com/google/googletest/archive/release-1.11.0.zip
39+
)
40+
# Prevent overriding the parent project's compiler/linker settings
41+
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
42+
# Disable install commands for gtest so it doesn't end up in the bundle.
43+
set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE)
44+
45+
FetchContent_MakeAvailable(googletest)
46+
47+
# The plugin's exported API is not very useful for unit testing, so build the
48+
# sources directly into the test binary rather than using the shared library.
49+
add_executable(${TEST_RUNNER}
50+
test/url_launcher_linux_test.cc
51+
${PLUGIN_SOURCES}
52+
)
53+
apply_standard_settings(${TEST_RUNNER})
54+
target_include_directories(${TEST_RUNNER} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
55+
target_link_libraries(${TEST_RUNNER} PRIVATE flutter)
56+
target_link_libraries(${TEST_RUNNER} PRIVATE PkgConfig::GTK)
57+
target_link_libraries(${TEST_RUNNER} PRIVATE gtest_main gmock)
58+
59+
include(GoogleTest)
60+
gtest_discover_tests(${TEST_RUNNER})
61+
endif() # CMake version check
62+
endif() # include_${PROJECT_NAME}_tests
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2013 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+
#include <flutter_linux/flutter_linux.h>
5+
#include <gmock/gmock.h>
6+
#include <gtest/gtest.h>
7+
8+
#include <memory>
9+
#include <string>
10+
11+
#include "include/url_launcher_linux/url_launcher_plugin.h"
12+
#include "url_launcher_plugin_private.h"
13+
14+
namespace url_launcher_plugin {
15+
namespace test {
16+
17+
TEST(UrlLauncherPlugin, CanLaunchSuccess) {
18+
g_autoptr(FlValue) args = fl_value_new_map();
19+
fl_value_set_string_take(args, "url",
20+
fl_value_new_string("https://flutter.dev"));
21+
FlMethodResponse* response = can_launch(nullptr, args);
22+
ASSERT_NE(response, nullptr);
23+
ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
24+
g_autoptr(FlValue) expected = fl_value_new_bool(true);
25+
EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result(
26+
FL_METHOD_SUCCESS_RESPONSE(response)),
27+
expected));
28+
}
29+
30+
TEST(UrlLauncherPlugin, CanLaunchFailureUnhandled) {
31+
g_autoptr(FlValue) args = fl_value_new_map();
32+
fl_value_set_string_take(args, "url", fl_value_new_string("madeup:scheme"));
33+
FlMethodResponse* response = can_launch(nullptr, args);
34+
ASSERT_NE(response, nullptr);
35+
ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
36+
g_autoptr(FlValue) expected = fl_value_new_bool(false);
37+
EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result(
38+
FL_METHOD_SUCCESS_RESPONSE(response)),
39+
expected));
40+
}
41+
42+
// For consistency with the established mobile implementations,
43+
// an invalid URL should return false, not an error.
44+
TEST(UrlLauncherPlugin, CanLaunchFailureInvalidUrl) {
45+
g_autoptr(FlValue) args = fl_value_new_map();
46+
fl_value_set_string_take(args, "url", fl_value_new_string(""));
47+
FlMethodResponse* response = can_launch(nullptr, args);
48+
ASSERT_NE(response, nullptr);
49+
ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
50+
g_autoptr(FlValue) expected = fl_value_new_bool(false);
51+
EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result(
52+
FL_METHOD_SUCCESS_RESPONSE(response)),
53+
expected));
54+
}
55+
56+
} // namespace test
57+
} // namespace url_launcher_plugin

packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
#include <cstring>
1111

12+
#include "url_launcher_plugin_private.h"
13+
1214
// See url_launcher_channel.dart for documentation.
1315
const char kChannelName[] = "plugins.flutter.io/url_launcher";
1416
const char kBadArgumentsError[] = "Bad Arguments";
@@ -44,7 +46,7 @@ static gchar* get_url(FlValue* args, GError** error) {
4446
}
4547

4648
// Called to check if a URL can be launched.
47-
static FlMethodResponse* can_launch(FlUrlLauncherPlugin* self, FlValue* args) {
49+
FlMethodResponse* can_launch(FlUrlLauncherPlugin* self, FlValue* args) {
4850
g_autoptr(GError) error = nullptr;
4951
g_autofree gchar* url = get_url(args, &error);
5052
if (url == nullptr) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2013 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+
#include <flutter_linux/flutter_linux.h>
6+
7+
#include "include/url_launcher_linux/url_launcher_plugin.h"
8+
9+
// TODO(stuartmorgan): Remove this private header and change the below back to
10+
// a static function once https://github.com/flutter/flutter/issues/88724
11+
// is fixed, and test through the public API instead.
12+
13+
// Handles the canLaunch method call.
14+
FlMethodResponse* can_launch(FlUrlLauncherPlugin* self, FlValue* args);

script/tool/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## NEXT
2+
3+
- `native-test` now supports `--linux` for unit tests.
4+
15
## 0.6.0+1
26

37
- Fixed `build-examples` to work for non-plugin packages.

script/tool/lib/src/native_test_command.dart

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ const String _iosDestinationFlag = 'ios-destination';
2121
const int _exitNoIosSimulators = 3;
2222

2323
/// The command to run native tests for plugins:
24-
/// - iOS and macOS: XCTests (XCUnitTest and XCUITest) in plugins.
24+
/// - iOS and macOS: XCTests (XCUnitTest and XCUITest)
25+
/// - Android: JUnit tests
26+
/// - Windows and Linux: GoogleTest tests
2527
class NativeTestCommand extends PackageLoopingCommand {
2628
/// Creates an instance of the test command.
2729
NativeTestCommand(
@@ -39,6 +41,7 @@ class NativeTestCommand extends PackageLoopingCommand {
3941
);
4042
argParser.addFlag(kPlatformAndroid, help: 'Runs Android tests');
4143
argParser.addFlag(kPlatformIos, help: 'Runs iOS tests');
44+
argParser.addFlag(kPlatformLinux, help: 'Runs Linux tests');
4245
argParser.addFlag(kPlatformMacos, help: 'Runs macOS tests');
4346
argParser.addFlag(kPlatformWindows, help: 'Runs Windows tests');
4447

@@ -63,9 +66,11 @@ class NativeTestCommand extends PackageLoopingCommand {
6366
Runs native unit tests and native integration tests.
6467
6568
Currently supported platforms:
66-
- Android (unit tests only)
69+
- Android
6770
- iOS: requires 'xcrun' to be in your path.
71+
- Linux (unit tests only)
6872
- macOS: requires 'xcrun' to be in your path.
73+
- Windows (unit tests only)
6974
7075
The example app(s) must be built for all targeted platforms before running
7176
this command.
@@ -80,6 +85,7 @@ this command.
8085
_platforms = <String, _PlatformDetails>{
8186
kPlatformAndroid: _PlatformDetails('Android', _testAndroid),
8287
kPlatformIos: _PlatformDetails('iOS', _testIos),
88+
kPlatformLinux: _PlatformDetails('Linux', _testLinux),
8389
kPlatformMacos: _PlatformDetails('macOS', _testMacOS),
8490
kPlatformWindows: _PlatformDetails('Windows', _testWindows),
8591
};
@@ -103,6 +109,11 @@ this command.
103109
'See https://github.com/flutter/flutter/issues/70233.');
104110
}
105111

112+
if (getBoolArg(kPlatformLinux) && getBoolArg(_integrationTestFlag)) {
113+
logWarning('This command currently only supports unit tests for Linux. '
114+
'See https://github.com/flutter/flutter/issues/70235.');
115+
}
116+
106117
// iOS-specific run-level state.
107118
if (_requestedPlatforms.contains('ios')) {
108119
String destination = getStringArg(_iosDestinationFlag);
@@ -418,6 +429,21 @@ this command.
418429
buildDirectoryName: 'windows', isTestBinary: isTestBinary);
419430
}
420431

432+
Future<_PlatformResult> _testLinux(
433+
RepositoryPackage plugin, _TestMode mode) async {
434+
if (mode.integrationOnly) {
435+
return _PlatformResult(RunState.skipped);
436+
}
437+
438+
bool isTestBinary(File file) {
439+
return file.basename.endsWith('_test') ||
440+
file.basename.endsWith('_tests');
441+
}
442+
443+
return _runGoogleTestTests(plugin,
444+
buildDirectoryName: 'linux', isTestBinary: isTestBinary);
445+
}
446+
421447
/// Finds every file in the [buildDirectoryName] subdirectory of [plugin]'s
422448
/// build directory for which [isTestBinary] is true, and runs all of them,
423449
/// returning the overall result.
@@ -442,10 +468,11 @@ this command.
442468
.whereType<File>()
443469
.where(isTestBinary)
444470
.where((File file) {
445-
// Only run the debug build of the unit tests, to avoid running the
446-
// same tests multiple times.
471+
// Only run the release build of the unit tests, to avoid running the
472+
// same tests multiple times. Release is used rather than debug since
473+
// `build-examples` builds release versions.
447474
final List<String> components = path.split(file.path);
448-
return components.contains('debug') || components.contains('Debug');
475+
return components.contains('release') || components.contains('Release');
449476
}));
450477
}
451478

0 commit comments

Comments
 (0)