[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 https://github.com/flutter/flutter/issues/82445
This commit is contained in:
stuartmorgan
2021-09-01 14:18:22 -04:00
committed by GitHub
parent 4a92b627b3
commit 0c3fb71cc7
3 changed files with 185 additions and 12 deletions

View File

@ -1,3 +1,7 @@
## NEXT
- `native-test` now supports `--linux` for unit tests.
## 0.6.0+1 ## 0.6.0+1
- Fixed `build-examples` to work for non-plugin packages. - Fixed `build-examples` to work for non-plugin packages.

View File

@ -21,7 +21,9 @@ const String _iosDestinationFlag = 'ios-destination';
const int _exitNoIosSimulators = 3; const int _exitNoIosSimulators = 3;
/// The command to run native tests for plugins: /// The command to run native tests for plugins:
/// - iOS and macOS: XCTests (XCUnitTest and XCUITest) in plugins. /// - iOS and macOS: XCTests (XCUnitTest and XCUITest)
/// - Android: JUnit tests
/// - Windows and Linux: GoogleTest tests
class NativeTestCommand extends PackageLoopingCommand { class NativeTestCommand extends PackageLoopingCommand {
/// Creates an instance of the test command. /// Creates an instance of the test command.
NativeTestCommand( NativeTestCommand(
@ -39,6 +41,7 @@ class NativeTestCommand extends PackageLoopingCommand {
); );
argParser.addFlag(kPlatformAndroid, help: 'Runs Android tests'); argParser.addFlag(kPlatformAndroid, help: 'Runs Android tests');
argParser.addFlag(kPlatformIos, help: 'Runs iOS tests'); argParser.addFlag(kPlatformIos, help: 'Runs iOS tests');
argParser.addFlag(kPlatformLinux, help: 'Runs Linux tests');
argParser.addFlag(kPlatformMacos, help: 'Runs macOS tests'); argParser.addFlag(kPlatformMacos, help: 'Runs macOS tests');
argParser.addFlag(kPlatformWindows, help: 'Runs Windows tests'); argParser.addFlag(kPlatformWindows, help: 'Runs Windows tests');
@ -63,9 +66,11 @@ class NativeTestCommand extends PackageLoopingCommand {
Runs native unit tests and native integration tests. Runs native unit tests and native integration tests.
Currently supported platforms: Currently supported platforms:
- Android (unit tests only) - Android
- iOS: requires 'xcrun' to be in your path. - iOS: requires 'xcrun' to be in your path.
- Linux (unit tests only)
- macOS: requires 'xcrun' to be in your path. - macOS: requires 'xcrun' to be in your path.
- Windows (unit tests only)
The example app(s) must be built for all targeted platforms before running The example app(s) must be built for all targeted platforms before running
this command. this command.
@ -80,6 +85,7 @@ this command.
_platforms = <String, _PlatformDetails>{ _platforms = <String, _PlatformDetails>{
kPlatformAndroid: _PlatformDetails('Android', _testAndroid), kPlatformAndroid: _PlatformDetails('Android', _testAndroid),
kPlatformIos: _PlatformDetails('iOS', _testIos), kPlatformIos: _PlatformDetails('iOS', _testIos),
kPlatformLinux: _PlatformDetails('Linux', _testLinux),
kPlatformMacos: _PlatformDetails('macOS', _testMacOS), kPlatformMacos: _PlatformDetails('macOS', _testMacOS),
kPlatformWindows: _PlatformDetails('Windows', _testWindows), kPlatformWindows: _PlatformDetails('Windows', _testWindows),
}; };
@ -103,6 +109,11 @@ this command.
'See https://github.com/flutter/flutter/issues/70233.'); 'See https://github.com/flutter/flutter/issues/70233.');
} }
if (getBoolArg(kPlatformLinux) && getBoolArg(_integrationTestFlag)) {
logWarning('This command currently only supports unit tests for Linux. '
'See https://github.com/flutter/flutter/issues/70235.');
}
// iOS-specific run-level state. // iOS-specific run-level state.
if (_requestedPlatforms.contains('ios')) { if (_requestedPlatforms.contains('ios')) {
String destination = getStringArg(_iosDestinationFlag); String destination = getStringArg(_iosDestinationFlag);
@ -418,6 +429,21 @@ this command.
buildDirectoryName: 'windows', isTestBinary: isTestBinary); buildDirectoryName: 'windows', isTestBinary: isTestBinary);
} }
Future<_PlatformResult> _testLinux(
RepositoryPackage plugin, _TestMode mode) async {
if (mode.integrationOnly) {
return _PlatformResult(RunState.skipped);
}
bool isTestBinary(File file) {
return file.basename.endsWith('_test') ||
file.basename.endsWith('_tests');
}
return _runGoogleTestTests(plugin,
buildDirectoryName: 'linux', isTestBinary: isTestBinary);
}
/// Finds every file in the [buildDirectoryName] subdirectory of [plugin]'s /// Finds every file in the [buildDirectoryName] subdirectory of [plugin]'s
/// build directory for which [isTestBinary] is true, and runs all of them, /// build directory for which [isTestBinary] is true, and runs all of them,
/// returning the overall result. /// returning the overall result.
@ -442,10 +468,11 @@ this command.
.whereType<File>() .whereType<File>()
.where(isTestBinary) .where(isTestBinary)
.where((File file) { .where((File file) {
// Only run the debug build of the unit tests, to avoid running the // Only run the release build of the unit tests, to avoid running the
// same tests multiple times. // same tests multiple times. Release is used rather than debug since
// `build-examples` builds release versions.
final List<String> components = path.split(file.path); final List<String> components = path.split(file.path);
return components.contains('debug') || components.contains('Debug'); return components.contains('release') || components.contains('Release');
})); }));
} }

View File

@ -736,6 +736,147 @@ void main() {
}); });
}); });
group('Linux', () {
test('runs unit tests', () async {
const String testBinaryRelativePath =
'build/linux/foo/release/bar/plugin_test';
final Directory pluginDirectory =
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/$testBinaryRelativePath'
], platformSupport: <String, PlatformDetails>{
kPlatformLinux: const PlatformDetails(PlatformSupport.inline),
});
final File testBinary = childFileWithSubcomponents(pluginDirectory,
<String>['example', ...testBinaryRelativePath.split('/')]);
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--linux',
'--no-integration',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running plugin_test...'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(testBinary.path, const <String>[], null),
]));
});
test('only runs release unit tests', () async {
const String debugTestBinaryRelativePath =
'build/linux/foo/debug/bar/plugin_test';
const String releaseTestBinaryRelativePath =
'build/linux/foo/release/bar/plugin_test';
final Directory pluginDirectory =
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/$debugTestBinaryRelativePath',
'example/$releaseTestBinaryRelativePath'
], platformSupport: <String, PlatformDetails>{
kPlatformLinux: const PlatformDetails(PlatformSupport.inline),
});
final File releaseTestBinary = childFileWithSubcomponents(
pluginDirectory,
<String>['example', ...releaseTestBinaryRelativePath.split('/')]);
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--linux',
'--no-integration',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running plugin_test...'),
contains('No issues found!'),
]),
);
// Only the release version should be run.
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(releaseTestBinary.path, const <String>[], null),
]));
});
test('fails if there are no unit tests', () async {
createFakePlugin('plugin', packagesDir,
platformSupport: <String, PlatformDetails>{
kPlatformLinux: const PlatformDetails(PlatformSupport.inline),
});
Error? commandError;
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--linux',
'--no-integration',
], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('No test binaries found.'),
]),
);
expect(processRunner.recordedCalls, orderedEquals(<ProcessCall>[]));
});
test('fails if a unit test fails', () async {
const String testBinaryRelativePath =
'build/linux/foo/release/bar/plugin_test';
final Directory pluginDirectory =
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/$testBinaryRelativePath'
], platformSupport: <String, PlatformDetails>{
kPlatformLinux: const PlatformDetails(PlatformSupport.inline),
});
final File testBinary = childFileWithSubcomponents(pluginDirectory,
<String>['example', ...testBinaryRelativePath.split('/')]);
processRunner.mockProcessesForExecutable[testBinary.path] =
<io.Process>[MockProcess(exitCode: 1)];
Error? commandError;
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--linux',
'--no-integration',
], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running plugin_test...'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(testBinary.path, const <String>[], null),
]));
});
});
// Tests behaviors of implementation that is shared between iOS and macOS. // Tests behaviors of implementation that is shared between iOS and macOS.
group('iOS/macOS', () { group('iOS/macOS', () {
test('fails if xcrun fails', () async { test('fails if xcrun fails', () async {
@ -1352,7 +1493,7 @@ void main() {
group('Windows', () { group('Windows', () {
test('runs unit tests', () async { test('runs unit tests', () async {
const String testBinaryRelativePath = const String testBinaryRelativePath =
'build/windows/foo/Debug/bar/plugin_test.exe'; 'build/windows/foo/Release/bar/plugin_test.exe';
final Directory pluginDirectory = final Directory pluginDirectory =
createFakePlugin('plugin', packagesDir, extraFiles: <String>[ createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/$testBinaryRelativePath' 'example/$testBinaryRelativePath'
@ -1384,7 +1525,7 @@ void main() {
])); ]));
}); });
test('only runs debug unit tests', () async { test('only runs release unit tests', () async {
const String debugTestBinaryRelativePath = const String debugTestBinaryRelativePath =
'build/windows/foo/Debug/bar/plugin_test.exe'; 'build/windows/foo/Debug/bar/plugin_test.exe';
const String releaseTestBinaryRelativePath = const String releaseTestBinaryRelativePath =
@ -1397,8 +1538,9 @@ void main() {
kPlatformWindows: const PlatformDetails(PlatformSupport.inline), kPlatformWindows: const PlatformDetails(PlatformSupport.inline),
}); });
final File debugTestBinary = childFileWithSubcomponents(pluginDirectory, final File releaseTestBinary = childFileWithSubcomponents(
<String>['example', ...debugTestBinaryRelativePath.split('/')]); pluginDirectory,
<String>['example', ...releaseTestBinaryRelativePath.split('/')]);
final List<String> output = await runCapturingPrint(runner, <String>[ final List<String> output = await runCapturingPrint(runner, <String>[
'native-test', 'native-test',
@ -1414,11 +1556,11 @@ void main() {
]), ]),
); );
// Only the debug version should be run. // Only the release version should be run.
expect( expect(
processRunner.recordedCalls, processRunner.recordedCalls,
orderedEquals(<ProcessCall>[ orderedEquals(<ProcessCall>[
ProcessCall(debugTestBinary.path, const <String>[], null), ProcessCall(releaseTestBinary.path, const <String>[], null),
])); ]));
}); });
@ -1450,7 +1592,7 @@ void main() {
test('fails if a unit test fails', () async { test('fails if a unit test fails', () async {
const String testBinaryRelativePath = const String testBinaryRelativePath =
'build/windows/foo/Debug/bar/plugin_test.exe'; 'build/windows/foo/Release/bar/plugin_test.exe';
final Directory pluginDirectory = final Directory pluginDirectory =
createFakePlugin('plugin', packagesDir, extraFiles: <String>[ createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/$testBinaryRelativePath' 'example/$testBinaryRelativePath'