Files
packages/script/tool/test/native_test_command_test.dart
stuartmorgan ff00264ec5 [ci] Add partial LUCI Android platform tests (#4381)
Adds an initial Android platform tests LUCI script, and initial targets in bringup mode (using 6 shards instead of the 8 we have in Cirrus since the added shards were to try to address what in retrospect was likely a device availability issue, and since for now this will be running fewer tests; once everything is migrated we can evaluate whether we need more shards).

Rather than wait for emulator and/or FTL support in LUCI to do the migration of this target, this will partially migrate; the script currently does only the parts that don't require any kind of device. That will let us set up a baseline of LUCI Android platform tests bots to easily expand from as we figure out those pieces, and we can turn down these parts of the tests in Cirrus once these come out of bringup mode to minimize duplication.

To avoid having to run a full `flutter build` for both versions, this also updates the repo tooling to use the new `flutter build apk --config-only` option to create `gradlew` without doing a full build.

Part of https://github.com/flutter/flutter/issues/114373
2023-07-06 22:48:01 +00:00

1971 lines
66 KiB
Dart

// Copyright 2013 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 'dart:convert';
import 'package:args/command_runner.dart';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_plugin_tools/src/common/cmake.dart';
import 'package:flutter_plugin_tools/src/common/core.dart';
import 'package:flutter_plugin_tools/src/common/file_utils.dart';
import 'package:flutter_plugin_tools/src/common/plugin_utils.dart';
import 'package:flutter_plugin_tools/src/native_test_command.dart';
import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:test/test.dart';
import 'mocks.dart';
import 'util.dart';
const String _androidIntegrationTestFilter =
'-Pandroid.testInstrumentationRunnerArguments.'
'notAnnotation=io.flutter.plugins.DartIntegrationTest';
final Map<String, dynamic> _kDeviceListMap = <String, dynamic>{
'runtimes': <Map<String, dynamic>>[
<String, dynamic>{
'bundlePath':
'/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.4.simruntime',
'buildversion': '17L255',
'runtimeRoot':
'/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.4.simruntime/Contents/Resources/RuntimeRoot',
'identifier': 'com.apple.CoreSimulator.SimRuntime.iOS-13-4',
'version': '13.4',
'isAvailable': true,
'name': 'iOS 13.4'
},
],
'devices': <String, dynamic>{
'com.apple.CoreSimulator.SimRuntime.iOS-13-4': <Map<String, dynamic>>[
<String, dynamic>{
'dataPath':
'/Users/xxx/Library/Developer/CoreSimulator/Devices/1E76A0FD-38AC-4537-A989-EA639D7D012A/data',
'logPath':
'/Users/xxx/Library/Logs/CoreSimulator/1E76A0FD-38AC-4537-A989-EA639D7D012A',
'udid': '1E76A0FD-38AC-4537-A989-EA639D7D012A',
'isAvailable': true,
'deviceTypeIdentifier':
'com.apple.CoreSimulator.SimDeviceType.iPhone-8-Plus',
'state': 'Shutdown',
'name': 'iPhone 8 Plus'
}
]
}
};
const String _fakeCmakeCommand = 'path/to/cmake';
void _createFakeCMakeCache(RepositoryPackage plugin, Platform platform) {
final CMakeProject project = CMakeProject(getExampleDir(plugin),
platform: platform, buildMode: 'Release');
final File cache = project.buildDirectory.childFile('CMakeCache.txt');
cache.createSync(recursive: true);
cache.writeAsStringSync('CMAKE_COMMAND:INTERNAL=$_fakeCmakeCommand');
}
// TODO(stuartmorgan): Rework these tests to use a mock Xcode instead of
// doing all the process mocking and validation.
void main() {
const String kDestination = '--ios-destination';
group('test native_test_command on Posix', () {
late FileSystem fileSystem;
late MockPlatform mockPlatform;
late Directory packagesDir;
late CommandRunner<void> runner;
late RecordingProcessRunner processRunner;
setUp(() {
fileSystem = MemoryFileSystem();
// iOS and macOS tests expect macOS, Linux tests expect Linux; nothing
// needs to distinguish between Linux and macOS, so set both to true to
// allow them to share a setup group.
mockPlatform = MockPlatform(isMacOS: true, isLinux: true);
packagesDir = createPackagesDirectory(fileSystem: fileSystem);
processRunner = RecordingProcessRunner();
final NativeTestCommand command = NativeTestCommand(packagesDir,
processRunner: processRunner, platform: mockPlatform);
runner = CommandRunner<void>(
'native_test_command', 'Test for native_test_command');
runner.addCommand(command);
});
// Returns a FakeProcessInfo to provide for "xcrun xcodebuild -list" for a
// project that contains [targets].
FakeProcessInfo getMockXcodebuildListProcess(List<String> targets) {
final Map<String, dynamic> projects = <String, dynamic>{
'project': <String, dynamic>{
'targets': targets,
}
};
return FakeProcessInfo(MockProcess(stdout: jsonEncode(projects)),
<String>['xcodebuild', '-list']);
}
// Returns the ProcessCall to expect for checking the targets present in
// the [package]'s [platform]/Runner.xcodeproj.
ProcessCall getTargetCheckCall(Directory package, String platform) {
return ProcessCall(
'xcrun',
<String>[
'xcodebuild',
'-list',
'-json',
'-project',
package
.childDirectory(platform)
.childDirectory('Runner.xcodeproj')
.path,
],
null);
}
// Returns the ProcessCall to expect for running the tests in the
// workspace [platform]/Runner.xcworkspace, with the given extra flags.
ProcessCall getRunTestCall(
Directory package,
String platform, {
String? destination,
List<String> extraFlags = const <String>[],
}) {
return ProcessCall(
'xcrun',
<String>[
'xcodebuild',
'test',
'-workspace',
'$platform/Runner.xcworkspace',
'-scheme',
'Runner',
'-configuration',
'Debug',
if (destination != null) ...<String>['-destination', destination],
...extraFlags,
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
],
package.path);
}
// Returns the ProcessCall to expect for build the Linux unit tests for the
// given plugin.
ProcessCall getLinuxBuildCall(RepositoryPackage plugin) {
return ProcessCall(
'cmake',
<String>[
'--build',
getExampleDir(plugin)
.childDirectory('build')
.childDirectory('linux')
.childDirectory('x64')
.childDirectory('release')
.path,
'--target',
'unit_tests'
],
null);
}
test('fails if no platforms are provided', () async {
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['native-test'], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('At least one platform flag must be provided.'),
]),
);
});
test('fails if all test types are disabled', () async {
Error? commandError;
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--macos',
'--no-unit',
'--no-integration',
], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('At least one test type must be enabled.'),
]),
);
});
test('reports skips with no tests', () async {
final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir,
platformSupport: <String, PlatformDetails>{
platformMacOS: const PlatformDetails(PlatformSupport.inline),
});
final Directory pluginExampleDirectory = getExampleDir(plugin);
processRunner.mockProcessesForExecutable['xcrun'] = <FakeProcessInfo>[
getMockXcodebuildListProcess(<String>['RunnerTests', 'RunnerUITests']),
// Exit code 66 from testing indicates no tests.
FakeProcessInfo(
MockProcess(exitCode: 66), <String>['xcodebuild', 'test']),
];
final List<String> output = await runCapturingPrint(
runner, <String>['native-test', '--macos', '--no-unit']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('No tests found.'),
contains('Skipped 1 package(s)'),
]));
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getTargetCheckCall(pluginExampleDirectory, 'macos'),
getRunTestCall(pluginExampleDirectory, 'macos',
extraFlags: <String>['-only-testing:RunnerUITests']),
]));
});
group('iOS', () {
test('skip if iOS is not supported', () async {
createFakePlugin('plugin', packagesDir,
platformSupport: <String, PlatformDetails>{
platformMacOS: const PlatformDetails(PlatformSupport.inline),
});
final List<String> output = await runCapturingPrint(runner,
<String>['native-test', '--ios', kDestination, 'foo_destination']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('No implementation for iOS.'),
contains('SKIPPING: Nothing to test for target platform(s).'),
]));
expect(processRunner.recordedCalls, orderedEquals(<ProcessCall>[]));
});
test('skip if iOS is implemented in a federated package', () async {
createFakePlugin('plugin', packagesDir,
platformSupport: <String, PlatformDetails>{
platformIOS: const PlatformDetails(PlatformSupport.federated)
});
final List<String> output = await runCapturingPrint(runner,
<String>['native-test', '--ios', kDestination, 'foo_destination']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('No implementation for iOS.'),
contains('SKIPPING: Nothing to test for target platform(s).'),
]));
expect(processRunner.recordedCalls, orderedEquals(<ProcessCall>[]));
});
test('running with correct destination', () async {
final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir,
platformSupport: <String, PlatformDetails>{
platformIOS: const PlatformDetails(PlatformSupport.inline)
});
final Directory pluginExampleDirectory = getExampleDir(plugin);
processRunner.mockProcessesForExecutable['xcrun'] = <FakeProcessInfo>[
getMockXcodebuildListProcess(
<String>['RunnerTests', 'RunnerUITests']),
];
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--ios',
kDestination,
'foo_destination',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('Successfully ran iOS xctest for plugin/example')
]));
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getTargetCheckCall(pluginExampleDirectory, 'ios'),
getRunTestCall(pluginExampleDirectory, 'ios',
destination: 'foo_destination'),
]));
});
test('Not specifying --ios-destination assigns an available simulator',
() async {
final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir,
platformSupport: <String, PlatformDetails>{
platformIOS: const PlatformDetails(PlatformSupport.inline)
});
final Directory pluginExampleDirectory = getExampleDir(plugin);
processRunner.mockProcessesForExecutable['xcrun'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: jsonEncode(_kDeviceListMap)),
<String>['simctl', 'list']),
getMockXcodebuildListProcess(
<String>['RunnerTests', 'RunnerUITests']),
];
await runCapturingPrint(runner, <String>['native-test', '--ios']);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
const ProcessCall(
'xcrun',
<String>[
'simctl',
'list',
'devices',
'runtimes',
'available',
'--json',
],
null),
getTargetCheckCall(pluginExampleDirectory, 'ios'),
getRunTestCall(pluginExampleDirectory, 'ios',
destination: 'id=1E76A0FD-38AC-4537-A989-EA639D7D012A'),
]));
});
});
group('macOS', () {
test('skip if macOS is not supported', () async {
createFakePlugin('plugin', packagesDir);
final List<String> output =
await runCapturingPrint(runner, <String>['native-test', '--macos']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('No implementation for macOS.'),
contains('SKIPPING: Nothing to test for target platform(s).'),
]));
expect(processRunner.recordedCalls, orderedEquals(<ProcessCall>[]));
});
test('skip if macOS is implemented in a federated package', () async {
createFakePlugin('plugin', packagesDir,
platformSupport: <String, PlatformDetails>{
platformMacOS: const PlatformDetails(PlatformSupport.federated),
});
final List<String> output =
await runCapturingPrint(runner, <String>['native-test', '--macos']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('No implementation for macOS.'),
contains('SKIPPING: Nothing to test for target platform(s).'),
]));
expect(processRunner.recordedCalls, orderedEquals(<ProcessCall>[]));
});
test('runs for macOS plugin', () async {
final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir,
platformSupport: <String, PlatformDetails>{
platformMacOS: const PlatformDetails(PlatformSupport.inline),
});
final Directory pluginExampleDirectory = getExampleDir(plugin);
processRunner.mockProcessesForExecutable['xcrun'] = <FakeProcessInfo>[
getMockXcodebuildListProcess(
<String>['RunnerTests', 'RunnerUITests']),
];
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--macos',
]);
expect(
output,
contains(
contains('Successfully ran macOS xctest for plugin/example')));
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getTargetCheckCall(pluginExampleDirectory, 'macos'),
getRunTestCall(pluginExampleDirectory, 'macos'),
]));
});
});
group('Android', () {
test('runs Java unit tests in Android implementation folder', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline)
},
extraFiles: <String>[
'example/android/gradlew',
'android/src/test/example_test.java',
],
);
await runCapturingPrint(runner, <String>['native-test', '--android']);
final Directory androidFolder = plugin
.getExamples()
.first
.platformDirectory(FlutterPlatform.android);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
androidFolder.childFile('gradlew').path,
const <String>[
'app:testDebugUnitTest',
'plugin:testDebugUnitTest',
],
androidFolder.path,
),
]),
);
});
test('runs Java unit tests in example folder', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline)
},
extraFiles: <String>[
'example/android/gradlew',
'example/android/app/src/test/example_test.java',
],
);
await runCapturingPrint(runner, <String>['native-test', '--android']);
final Directory androidFolder = plugin
.getExamples()
.first
.platformDirectory(FlutterPlatform.android);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
androidFolder.childFile('gradlew').path,
const <String>[
'app:testDebugUnitTest',
'plugin:testDebugUnitTest',
],
androidFolder.path,
),
]),
);
});
test('only runs plugin-level unit tests once', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline)
},
examples: <String>['example1', 'example2'],
extraFiles: <String>[
'example/example1/android/gradlew',
'example/example1/android/app/src/test/example_test.java',
'example/example2/android/gradlew',
'example/example2/android/app/src/test/example_test.java',
],
);
await runCapturingPrint(runner, <String>['native-test', '--android']);
final List<RepositoryPackage> examples = plugin.getExamples().toList();
final Directory androidFolder1 =
examples[0].platformDirectory(FlutterPlatform.android);
final Directory androidFolder2 =
examples[1].platformDirectory(FlutterPlatform.android);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
androidFolder1.childFile('gradlew').path,
const <String>[
'app:testDebugUnitTest',
'plugin:testDebugUnitTest',
],
androidFolder1.path,
),
ProcessCall(
androidFolder2.childFile('gradlew').path,
const <String>[
'app:testDebugUnitTest',
],
androidFolder2.path,
),
]),
);
});
test('runs Java integration tests', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline)
},
extraFiles: <String>[
'example/android/gradlew',
'example/android/app/src/androidTest/IntegrationTest.java',
],
);
await runCapturingPrint(
runner, <String>['native-test', '--android', '--no-unit']);
final Directory androidFolder = plugin
.getExamples()
.first
.platformDirectory(FlutterPlatform.android);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
androidFolder.childFile('gradlew').path,
const <String>[
'app:connectedAndroidTest',
_androidIntegrationTestFilter,
],
androidFolder.path,
),
]),
);
});
test(
'ignores Java integration test files using (or defining) DartIntegrationTest',
() async {
const String dartTestDriverRelativePath =
'android/app/src/androidTest/java/io/flutter/plugins/plugin/FlutterActivityTest.java';
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline)
},
extraFiles: <String>[
'example/android/gradlew',
'example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java',
'example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.kt',
'example/$dartTestDriverRelativePath',
],
);
final File dartTestDriverFile = childFileWithSubcomponents(
plugin.getExamples().first.directory,
p.posix.split(dartTestDriverRelativePath));
dartTestDriverFile.writeAsStringSync('''
import io.flutter.plugins.DartIntegrationTest;
import org.junit.runner.RunWith;
@DartIntegrationTest
@RunWith(FlutterTestRunner.class)
public class FlutterActivityTest {
}
''');
await runCapturingPrint(
runner, <String>['native-test', '--android', '--no-unit']);
// Nothing should run since those files are all
// integration_test-specific.
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[]),
);
});
test(
'fails for Java integration tests Using FlutterTestRunner without @DartIntegrationTest',
() async {
const String dartTestDriverRelativePath =
'android/app/src/androidTest/java/io/flutter/plugins/plugin/FlutterActivityTest.java';
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline)
},
extraFiles: <String>[
'example/android/gradlew',
'example/$dartTestDriverRelativePath',
],
);
final File dartTestDriverFile = childFileWithSubcomponents(
plugin.getExamples().first.directory,
p.posix.split(dartTestDriverRelativePath));
dartTestDriverFile.writeAsStringSync('''
import io.flutter.plugins.DartIntegrationTest;
import org.junit.runner.RunWith;
@RunWith(FlutterTestRunner.class)
public class FlutterActivityTest {
}
''');
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['native-test', '--android', '--no-unit'],
errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
contains(
contains(misconfiguredJavaIntegrationTestErrorExplanation)));
expect(
output,
contains(contains(
'example/android/app/src/androidTest/java/io/flutter/plugins/plugin/FlutterActivityTest.java')));
});
test('runs all tests when present', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline)
},
extraFiles: <String>[
'android/src/test/example_test.java',
'example/android/gradlew',
'example/android/app/src/androidTest/IntegrationTest.java',
],
);
await runCapturingPrint(runner, <String>['native-test', '--android']);
final Directory androidFolder = plugin
.getExamples()
.first
.platformDirectory(FlutterPlatform.android);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
androidFolder.childFile('gradlew').path,
const <String>[
'app:testDebugUnitTest',
'plugin:testDebugUnitTest',
],
androidFolder.path,
),
ProcessCall(
androidFolder.childFile('gradlew').path,
const <String>[
'app:connectedAndroidTest',
_androidIntegrationTestFilter,
],
androidFolder.path,
),
]),
);
});
test('honors --no-unit', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline)
},
extraFiles: <String>[
'android/src/test/example_test.java',
'example/android/gradlew',
'example/android/app/src/androidTest/IntegrationTest.java',
],
);
await runCapturingPrint(
runner, <String>['native-test', '--android', '--no-unit']);
final Directory androidFolder = plugin
.getExamples()
.first
.platformDirectory(FlutterPlatform.android);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
androidFolder.childFile('gradlew').path,
const <String>[
'app:connectedAndroidTest',
_androidIntegrationTestFilter,
],
androidFolder.path,
),
]),
);
});
test('honors --no-integration', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline)
},
extraFiles: <String>[
'android/src/test/example_test.java',
'example/android/gradlew',
'example/android/app/src/androidTest/IntegrationTest.java',
],
);
await runCapturingPrint(
runner, <String>['native-test', '--android', '--no-integration']);
final Directory androidFolder = plugin
.getExamples()
.first
.platformDirectory(FlutterPlatform.android);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
androidFolder.childFile('gradlew').path,
const <String>[
'app:testDebugUnitTest',
'plugin:testDebugUnitTest',
],
androidFolder.path,
),
]),
);
});
test('runs a config-only build when the app needs to be built', () async {
final RepositoryPackage package = createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline)
},
extraFiles: <String>[
'example/android/app/src/test/example_test.java',
],
);
final RepositoryPackage example = package.getExamples().first;
final Directory androidFolder =
example.platformDirectory(FlutterPlatform.android);
await runCapturingPrint(runner, <String>['native-test', '--android']);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
getFlutterCommand(mockPlatform),
const <String>['build', 'apk', '--config-only'],
example.path,
),
ProcessCall(
androidFolder.childFile('gradlew').path,
const <String>[
'app:testDebugUnitTest',
'plugin:testDebugUnitTest',
],
androidFolder.path,
),
]),
);
});
test('fails when the gradlew generation fails', () async {
createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline)
},
extraFiles: <String>[
'example/android/app/src/test/example_test.java',
],
);
processRunner
.mockProcessesForExecutable[getFlutterCommand(mockPlatform)] =
<FakeProcessInfo>[FakeProcessInfo(MockProcess(exitCode: 1))];
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['native-test', '--android'],
errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Unable to configure Gradle project'),
]),
);
});
test('logs missing test types', () async {
// No unit tests.
createFakePlugin(
'plugin1',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline)
},
extraFiles: <String>[
'example/android/gradlew',
'example/android/app/src/androidTest/IntegrationTest.java',
],
);
// No integration tests.
createFakePlugin(
'plugin2',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline)
},
extraFiles: <String>[
'android/src/test/example_test.java',
'example/android/gradlew',
],
);
final List<String> output = await runCapturingPrint(
runner, <String>['native-test', '--android'],
errorHandler: (Error e) {
// Having no unit tests is fatal, but that's not the point of this
// test so just ignore the failure.
});
expect(
output,
containsAllInOrder(<Matcher>[
contains('No Android unit tests found for plugin1/example'),
contains('Running integration tests...'),
contains(
'No Android integration tests found for plugin2/example'),
contains('Running unit tests...'),
]));
});
test('fails when a unit test fails', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline)
},
extraFiles: <String>[
'example/android/gradlew',
'example/android/app/src/test/example_test.java',
],
);
final String gradlewPath = plugin
.getExamples()
.first
.platformDirectory(FlutterPlatform.android)
.childFile('gradlew')
.path;
processRunner.mockProcessesForExecutable[gradlewPath] =
<FakeProcessInfo>[FakeProcessInfo(MockProcess(exitCode: 1))];
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['native-test', '--android'],
errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('plugin/example unit tests failed.'),
contains('The following packages had errors:'),
contains('plugin')
]),
);
});
test('fails when an integration test fails', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline)
},
extraFiles: <String>[
'example/android/gradlew',
'example/android/app/src/test/example_test.java',
'example/android/app/src/androidTest/IntegrationTest.java',
],
);
final String gradlewPath = plugin
.getExamples()
.first
.platformDirectory(FlutterPlatform.android)
.childFile('gradlew')
.path;
processRunner.mockProcessesForExecutable[gradlewPath] =
<FakeProcessInfo>[
FakeProcessInfo(
MockProcess(), <String>['app:testDebugUnitTest']), // unit passes
FakeProcessInfo(MockProcess(exitCode: 1),
<String>['app:connectedAndroidTest']), // integration fails
];
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['native-test', '--android'],
errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('plugin/example integration tests failed.'),
contains('The following packages had errors:'),
contains('plugin')
]),
);
});
test('fails if there are no unit tests', () async {
createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline)
},
extraFiles: <String>[
'example/android/gradlew',
'example/android/app/src/androidTest/IntegrationTest.java',
],
);
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['native-test', '--android'],
errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('No Android unit tests found for plugin/example'),
contains(
'No unit tests ran. Plugins are required to have unit tests.'),
contains('The following packages had errors:'),
contains('plugin:\n'
' No unit tests ran (use --exclude if this is intentional).')
]),
);
});
test('skips if Android is not supported', () async {
createFakePlugin(
'plugin',
packagesDir,
);
final List<String> output = await runCapturingPrint(
runner, <String>['native-test', '--android']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('No implementation for Android.'),
contains('SKIPPING: Nothing to test for target platform(s).'),
]),
);
});
test('skips when running no tests in integration-only mode', () async {
createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline)
},
);
final List<String> output = await runCapturingPrint(
runner, <String>['native-test', '--android', '--no-unit']);
expect(
output,
containsAllInOrder(<Matcher>[
contains('No Android integration tests found for plugin/example'),
contains('SKIPPING: No tests found.'),
]),
);
});
});
group('Linux', () {
test('builds and runs unit tests', () async {
const String testBinaryRelativePath =
'build/linux/x64/release/bar/plugin_test';
final RepositoryPackage plugin =
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/$testBinaryRelativePath'
], platformSupport: <String, PlatformDetails>{
platformLinux: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform);
final File testBinary = childFileWithSubcomponents(plugin.directory,
<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>[
getLinuxBuildCall(plugin),
ProcessCall(testBinary.path, const <String>[], null),
]));
});
test('only runs release unit tests', () async {
const String debugTestBinaryRelativePath =
'build/linux/x64/debug/bar/plugin_test';
const String releaseTestBinaryRelativePath =
'build/linux/x64/release/bar/plugin_test';
final RepositoryPackage plugin =
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/$debugTestBinaryRelativePath',
'example/$releaseTestBinaryRelativePath'
], platformSupport: <String, PlatformDetails>{
platformLinux: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform);
final File releaseTestBinary = childFileWithSubcomponents(
plugin.directory,
<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!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getLinuxBuildCall(plugin),
ProcessCall(releaseTestBinary.path, const <String>[], null),
]));
});
test('fails if CMake has not been configured', () async {
createFakePlugin('plugin', packagesDir,
platformSupport: <String, PlatformDetails>{
platformLinux: 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('plugin:\n'
' Examples must be built before testing.')
]),
);
expect(processRunner.recordedCalls, orderedEquals(<ProcessCall>[]));
});
test('fails if there are no unit tests', () async {
final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir,
platformSupport: <String, PlatformDetails>{
platformLinux: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform);
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>[
getLinuxBuildCall(plugin),
]));
});
test('fails if a unit test fails', () async {
const String testBinaryRelativePath =
'build/linux/x64/release/bar/plugin_test';
final RepositoryPackage plugin =
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/$testBinaryRelativePath'
], platformSupport: <String, PlatformDetails>{
platformLinux: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform);
final File testBinary = childFileWithSubcomponents(plugin.directory,
<String>['example', ...testBinaryRelativePath.split('/')]);
processRunner.mockProcessesForExecutable[testBinary.path] =
<FakeProcessInfo>[
FakeProcessInfo(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>[
getLinuxBuildCall(plugin),
ProcessCall(testBinary.path, const <String>[], null),
]));
});
});
// Tests behaviors of implementation that is shared between iOS and macOS.
group('iOS/macOS', () {
test('fails if xcrun fails', () async {
createFakePlugin('plugin', packagesDir,
platformSupport: <String, PlatformDetails>{
platformMacOS: const PlatformDetails(PlatformSupport.inline),
});
processRunner.mockProcessesForExecutable['xcrun'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(exitCode: 1))
];
Error? commandError;
final List<String> output =
await runCapturingPrint(runner, <String>['native-test', '--macos'],
errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('The following packages had errors:'),
contains(' plugin'),
]),
);
});
test('honors unit-only', () async {
final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir,
platformSupport: <String, PlatformDetails>{
platformMacOS: const PlatformDetails(PlatformSupport.inline),
});
final Directory pluginExampleDirectory = getExampleDir(plugin);
processRunner.mockProcessesForExecutable['xcrun'] = <FakeProcessInfo>[
getMockXcodebuildListProcess(
<String>['RunnerTests', 'RunnerUITests']),
];
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--macos',
'--no-integration',
]);
expect(
output,
contains(
contains('Successfully ran macOS xctest for plugin/example')));
// --no-integration should translate to '-only-testing:RunnerTests'.
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getTargetCheckCall(pluginExampleDirectory, 'macos'),
getRunTestCall(pluginExampleDirectory, 'macos',
extraFlags: <String>['-only-testing:RunnerTests']),
]));
});
test('honors integration-only', () async {
final RepositoryPackage plugin1 = createFakePlugin(
'plugin', packagesDir,
platformSupport: <String, PlatformDetails>{
platformMacOS: const PlatformDetails(PlatformSupport.inline),
});
final Directory pluginExampleDirectory = getExampleDir(plugin1);
processRunner.mockProcessesForExecutable['xcrun'] = <FakeProcessInfo>[
getMockXcodebuildListProcess(
<String>['RunnerTests', 'RunnerUITests']),
];
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--macos',
'--no-unit',
]);
expect(
output,
contains(
contains('Successfully ran macOS xctest for plugin/example')));
// --no-unit should translate to '-only-testing:RunnerUITests'.
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getTargetCheckCall(pluginExampleDirectory, 'macos'),
getRunTestCall(pluginExampleDirectory, 'macos',
extraFlags: <String>['-only-testing:RunnerUITests']),
]));
});
test('skips when the requested target is not present', () async {
final RepositoryPackage plugin1 = createFakePlugin(
'plugin', packagesDir,
platformSupport: <String, PlatformDetails>{
platformMacOS: const PlatformDetails(PlatformSupport.inline),
});
final Directory pluginExampleDirectory = getExampleDir(plugin1);
// Simulate a project with unit tests but no integration tests...
processRunner.mockProcessesForExecutable['xcrun'] = <FakeProcessInfo>[
getMockXcodebuildListProcess(<String>['RunnerTests']),
];
// ... then try to run only integration tests.
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--macos',
'--no-unit',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains(
'No "RunnerUITests" target in plugin/example; skipping.'),
]));
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getTargetCheckCall(pluginExampleDirectory, 'macos'),
]));
});
test('fails if there are no unit tests', () async {
final RepositoryPackage plugin1 = createFakePlugin(
'plugin', packagesDir,
platformSupport: <String, PlatformDetails>{
platformMacOS: const PlatformDetails(PlatformSupport.inline),
});
final Directory pluginExampleDirectory = getExampleDir(plugin1);
processRunner.mockProcessesForExecutable['xcrun'] = <FakeProcessInfo>[
getMockXcodebuildListProcess(<String>['RunnerUITests']),
];
Error? commandError;
final List<String> output =
await runCapturingPrint(runner, <String>['native-test', '--macos'],
errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('No "RunnerTests" target in plugin/example; skipping.'),
contains(
'No unit tests ran. Plugins are required to have unit tests.'),
contains('The following packages had errors:'),
contains('plugin:\n'
' No unit tests ran (use --exclude if this is intentional).'),
]));
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getTargetCheckCall(pluginExampleDirectory, 'macos'),
]));
});
test('fails if unable to check for requested target', () async {
final RepositoryPackage plugin1 = createFakePlugin(
'plugin', packagesDir,
platformSupport: <String, PlatformDetails>{
platformMacOS: const PlatformDetails(PlatformSupport.inline),
});
final Directory pluginExampleDirectory = getExampleDir(plugin1);
processRunner.mockProcessesForExecutable['xcrun'] = <FakeProcessInfo>[
FakeProcessInfo(
MockProcess(exitCode: 1), <String>['xcodebuild', '-list']),
];
Error? commandError;
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--macos',
'--no-integration',
], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Unable to check targets for plugin/example.'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getTargetCheckCall(pluginExampleDirectory, 'macos'),
]));
});
});
group('multiplatform', () {
test('runs all platfroms when supported', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
extraFiles: <String>[
'example/android/gradlew',
'android/src/test/example_test.java',
],
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline),
platformIOS: const PlatformDetails(PlatformSupport.inline),
platformMacOS: const PlatformDetails(PlatformSupport.inline),
},
);
final Directory pluginExampleDirectory = getExampleDir(plugin);
final Directory androidFolder =
pluginExampleDirectory.childDirectory('android');
processRunner.mockProcessesForExecutable['xcrun'] = <FakeProcessInfo>[
getMockXcodebuildListProcess(
<String>['RunnerTests', 'RunnerUITests']), // iOS list
FakeProcessInfo(
MockProcess(), <String>['xcodebuild', 'test']), // iOS run
getMockXcodebuildListProcess(
<String>['RunnerTests', 'RunnerUITests']), // macOS list
FakeProcessInfo(
MockProcess(), <String>['xcodebuild', 'test']), // macOS run
];
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--android',
'--ios',
'--macos',
kDestination,
'foo_destination',
]);
expect(
output,
containsAll(<Matcher>[
contains('Running Android tests for plugin/example'),
contains('Successfully ran iOS xctest for plugin/example'),
contains('Successfully ran macOS xctest for plugin/example'),
]));
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
androidFolder.childFile('gradlew').path,
const <String>[
'app:testDebugUnitTest',
'plugin:testDebugUnitTest',
],
androidFolder.path),
getTargetCheckCall(pluginExampleDirectory, 'ios'),
getRunTestCall(pluginExampleDirectory, 'ios',
destination: 'foo_destination'),
getTargetCheckCall(pluginExampleDirectory, 'macos'),
getRunTestCall(pluginExampleDirectory, 'macos'),
]));
});
test('runs only macOS for a macOS plugin', () async {
final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir,
platformSupport: <String, PlatformDetails>{
platformMacOS: const PlatformDetails(PlatformSupport.inline),
});
final Directory pluginExampleDirectory = getExampleDir(plugin);
processRunner.mockProcessesForExecutable['xcrun'] = <FakeProcessInfo>[
getMockXcodebuildListProcess(
<String>['RunnerTests', 'RunnerUITests']),
];
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--ios',
'--macos',
kDestination,
'foo_destination',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('No implementation for iOS.'),
contains('Successfully ran macOS xctest for plugin/example'),
]));
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getTargetCheckCall(pluginExampleDirectory, 'macos'),
getRunTestCall(pluginExampleDirectory, 'macos'),
]));
});
test('runs only iOS for a iOS plugin', () async {
final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir,
platformSupport: <String, PlatformDetails>{
platformIOS: const PlatformDetails(PlatformSupport.inline)
});
final Directory pluginExampleDirectory = getExampleDir(plugin);
processRunner.mockProcessesForExecutable['xcrun'] = <FakeProcessInfo>[
getMockXcodebuildListProcess(
<String>['RunnerTests', 'RunnerUITests']),
];
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--ios',
'--macos',
kDestination,
'foo_destination',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('No implementation for macOS.'),
contains('Successfully ran iOS xctest for plugin/example')
]));
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getTargetCheckCall(pluginExampleDirectory, 'ios'),
getRunTestCall(pluginExampleDirectory, 'ios',
destination: 'foo_destination'),
]));
});
test('skips when nothing is supported', () async {
createFakePlugin('plugin', packagesDir);
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--android',
'--ios',
'--macos',
'--windows',
kDestination,
'foo_destination',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('No implementation for Android.'),
contains('No implementation for iOS.'),
contains('No implementation for macOS.'),
contains('SKIPPING: Nothing to test for target platform(s).'),
]));
expect(processRunner.recordedCalls, orderedEquals(<ProcessCall>[]));
});
test('skips Dart-only plugins', () async {
createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformMacOS: const PlatformDetails(PlatformSupport.inline,
hasDartCode: true, hasNativeCode: false),
platformWindows: const PlatformDetails(PlatformSupport.inline,
hasDartCode: true, hasNativeCode: false),
},
);
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--macos',
'--windows',
kDestination,
'foo_destination',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('No native code for macOS.'),
contains('No native code for Windows.'),
contains('SKIPPING: Nothing to test for target platform(s).'),
]));
expect(processRunner.recordedCalls, orderedEquals(<ProcessCall>[]));
});
test('failing one platform does not stop the tests', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline),
platformIOS: const PlatformDetails(PlatformSupport.inline),
},
extraFiles: <String>[
'example/android/gradlew',
'example/android/app/src/test/example_test.java',
],
);
processRunner.mockProcessesForExecutable['xcrun'] = <FakeProcessInfo>[
getMockXcodebuildListProcess(
<String>['RunnerTests', 'RunnerUITests']),
];
// Simulate failing Android, but not iOS.
final String gradlewPath = plugin
.getExamples()
.first
.platformDirectory(FlutterPlatform.android)
.childFile('gradlew')
.path;
processRunner.mockProcessesForExecutable[gradlewPath] =
<FakeProcessInfo>[FakeProcessInfo(MockProcess(exitCode: 1))];
Error? commandError;
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--android',
'--ios',
'--ios-destination',
'foo_destination',
], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running tests for Android...'),
contains('plugin/example unit tests failed.'),
contains('Running tests for iOS...'),
contains('Successfully ran iOS xctest for plugin/example'),
contains('The following packages had errors:'),
contains('plugin:\n'
' Android')
]),
);
});
test('failing multiple platforms reports multiple failures', () async {
final RepositoryPackage plugin = createFakePlugin(
'plugin',
packagesDir,
platformSupport: <String, PlatformDetails>{
platformAndroid: const PlatformDetails(PlatformSupport.inline),
platformIOS: const PlatformDetails(PlatformSupport.inline),
},
extraFiles: <String>[
'example/android/gradlew',
'example/android/app/src/test/example_test.java',
],
);
// Simulate failing Android.
final String gradlewPath = plugin
.getExamples()
.first
.platformDirectory(FlutterPlatform.android)
.childFile('gradlew')
.path;
processRunner.mockProcessesForExecutable[gradlewPath] =
<FakeProcessInfo>[FakeProcessInfo(MockProcess(exitCode: 1))];
// Simulate failing iOS.
processRunner.mockProcessesForExecutable['xcrun'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(exitCode: 1))
];
Error? commandError;
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--android',
'--ios',
'--ios-destination',
'foo_destination',
], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running tests for Android...'),
contains('Running tests for iOS...'),
contains('The following packages had errors:'),
contains('plugin:\n'
' Android\n'
' iOS')
]),
);
});
});
});
group('test native_test_command on Windows', () {
late FileSystem fileSystem;
late MockPlatform mockPlatform;
late Directory packagesDir;
late CommandRunner<void> runner;
late RecordingProcessRunner processRunner;
setUp(() {
fileSystem = MemoryFileSystem(style: FileSystemStyle.windows);
mockPlatform = MockPlatform(isWindows: true);
packagesDir = createPackagesDirectory(fileSystem: fileSystem);
processRunner = RecordingProcessRunner();
final NativeTestCommand command = NativeTestCommand(packagesDir,
processRunner: processRunner, platform: mockPlatform);
runner = CommandRunner<void>(
'native_test_command', 'Test for native_test_command');
runner.addCommand(command);
});
// Returns the ProcessCall to expect for build the Windows unit tests for
// the given plugin.
ProcessCall getWindowsBuildCall(RepositoryPackage plugin) {
return ProcessCall(
_fakeCmakeCommand,
<String>[
'--build',
getExampleDir(plugin)
.childDirectory('build')
.childDirectory('windows')
.path,
'--target',
'unit_tests',
'--config',
'Debug'
],
null);
}
group('Windows', () {
test('runs unit tests', () async {
const String testBinaryRelativePath =
'build/windows/Debug/bar/plugin_test.exe';
final RepositoryPackage plugin =
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/$testBinaryRelativePath'
], platformSupport: <String, PlatformDetails>{
platformWindows: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform);
final File testBinary = childFileWithSubcomponents(plugin.directory,
<String>['example', ...testBinaryRelativePath.split('/')]);
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--windows',
'--no-integration',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running plugin_test.exe...'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getWindowsBuildCall(plugin),
ProcessCall(testBinary.path, const <String>[], null),
]));
});
test('only runs debug unit tests', () async {
const String debugTestBinaryRelativePath =
'build/windows/Debug/bar/plugin_test.exe';
const String releaseTestBinaryRelativePath =
'build/windows/Release/bar/plugin_test.exe';
final RepositoryPackage plugin =
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/$debugTestBinaryRelativePath',
'example/$releaseTestBinaryRelativePath'
], platformSupport: <String, PlatformDetails>{
platformWindows: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform);
final File debugTestBinary = childFileWithSubcomponents(
plugin.directory,
<String>['example', ...debugTestBinaryRelativePath.split('/')]);
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--windows',
'--no-integration',
]);
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running plugin_test.exe...'),
contains('No issues found!'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getWindowsBuildCall(plugin),
ProcessCall(debugTestBinary.path, const <String>[], null),
]));
});
test('fails if CMake has not been configured', () async {
createFakePlugin('plugin', packagesDir,
platformSupport: <String, PlatformDetails>{
platformWindows: const PlatformDetails(PlatformSupport.inline),
});
Error? commandError;
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--windows',
'--no-integration',
], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('plugin:\n'
' Examples must be built before testing.')
]),
);
expect(processRunner.recordedCalls, orderedEquals(<ProcessCall>[]));
});
test('fails if there are no unit tests', () async {
final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir,
platformSupport: <String, PlatformDetails>{
platformWindows: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform);
Error? commandError;
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--windows',
'--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>[
getWindowsBuildCall(plugin),
]));
});
test('fails if a unit test fails', () async {
const String testBinaryRelativePath =
'build/windows/Debug/bar/plugin_test.exe';
final RepositoryPackage plugin =
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
'example/$testBinaryRelativePath'
], platformSupport: <String, PlatformDetails>{
platformWindows: const PlatformDetails(PlatformSupport.inline),
});
_createFakeCMakeCache(plugin, mockPlatform);
final File testBinary = childFileWithSubcomponents(plugin.directory,
<String>['example', ...testBinaryRelativePath.split('/')]);
processRunner.mockProcessesForExecutable[testBinary.path] =
<FakeProcessInfo>[
FakeProcessInfo(MockProcess(exitCode: 1)),
];
Error? commandError;
final List<String> output = await runCapturingPrint(runner, <String>[
'native-test',
'--windows',
'--no-integration',
], errorHandler: (Error e) {
commandError = e;
});
expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Running plugin_test.exe...'),
]),
);
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
getWindowsBuildCall(plugin),
ProcessCall(testBinary.path, const <String>[], null),
]));
});
});
});
}