| // Copyright 2013 The Flutter Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import 'dart:convert'; | |
| import 'dart:ffi'; | |
| import 'package:args/command_runner.dart'; | |
| import 'package:file/file.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:git/git.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 _allAbiFlag = '-Ptarget-platform=android-arm,android-arm64,android-x64'; | |
| const String _androidIntegrationTestFilter = | |
| '-Pandroid.testInstrumentationRunnerArguments.' | |
| 'notAnnotation=io.flutter.plugins.DartIntegrationTest'; | |
| const String _simulatorDeviceId = '1E76A0FD-38AC-4537-A989-EA639D7D012A'; | |
| 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'; | |
| const String _archDirX64 = 'x64'; | |
| const String _archDirArm64 = 'arm64'; | |
| void _createFakeCMakeCache(RepositoryPackage plugin, NativePlatform platform, String archDir) { | |
| final project = CMakeProject( | |
| getExampleDir(plugin), | |
| platform: platform, | |
| buildMode: 'Release', | |
| arch: archDir, | |
| ); | |
| 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 kDestination = '--ios-destination'; | |
| group('test native_test_command on Posix', () { | |
| late NativePlatform mockPlatform; | |
| late Directory packagesDir; | |
| late CommandRunner<void> runner; | |
| late RecordingProcessRunner processRunner; | |
| late RecordingProcessRunner gitProcessRunner; | |
| setUp(() { | |
| // Currently nothing in the logic specifically checks for isMacOS, so just use Linux for both | |
| // test groups. | |
| mockPlatform = createMockPlatform(isLinux: true); | |
| final GitDir gitDir; | |
| (:packagesDir, :processRunner, :gitProcessRunner, :gitDir) = configureBaseCommandMocks( | |
| platform: mockPlatform, | |
| ); | |
| final command = NativeTestCommand( | |
| packagesDir, | |
| processRunner: processRunner, | |
| platform: mockPlatform, | |
| gitDir: gitDir, | |
| ); | |
| 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 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 generating the native project files | |
| // with a --config-only build on iOS or macOS. | |
| ProcessCall getConfigOnlyDarwinBuildCall(Directory package, FlutterPlatform platform) { | |
| return ProcessCall('flutter', <String>[ | |
| 'build', | |
| if (platform == FlutterPlatform.ios) 'ios' else 'macos', | |
| '--debug', | |
| '--config-only', | |
| ], package.path); | |
| } | |
| // 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>[], | |
| bool treatWarningsAsErrors = true, | |
| }) { | |
| return ProcessCall('xcrun', <String>[ | |
| 'xcodebuild', | |
| 'clean', | |
| 'test', | |
| '-workspace', | |
| '$platform/Runner.xcworkspace', | |
| '-scheme', | |
| 'Runner', | |
| '-configuration', | |
| 'Debug', | |
| if (destination != null) ...<String>['-destination', destination], | |
| ...extraFlags, | |
| if (treatWarningsAsErrors) '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', 'clean', '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'), | |
| getConfigOnlyDarwinBuildCall(pluginExampleDirectory, FlutterPlatform.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'), | |
| getConfigOnlyDarwinBuildCall(pluginExampleDirectory, FlutterPlatform.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'), | |
| getConfigOnlyDarwinBuildCall(pluginExampleDirectory, FlutterPlatform.ios), | |
| getRunTestCall(pluginExampleDirectory, 'ios', destination: 'id=$_simulatorDeviceId'), | |
| ]), | |
| ); | |
| }); | |
| group('file filtering', () { | |
| const files = <String>[ | |
| 'pubspec.yaml', | |
| 'foo.dart', | |
| 'foo.java', | |
| 'foo.kt', | |
| 'foo.m', | |
| 'foo.swift', | |
| 'foo.cc', | |
| 'foo.cpp', | |
| 'foo.h', | |
| ]; | |
| for (final file in files) { | |
| test('runs command for changes to $file', () async { | |
| createFakePackage('package_a', packagesDir); | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: | |
| ''' | |
| packages/package_a/$file | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| // The target platform is irrelevant here; because this repo's | |
| // packages are fully federated, there's no need to distinguish | |
| // the ignore list by target (e.g., skipping iOS tests if only Java or | |
| // Kotlin files change), because package-level filering will already | |
| // accomplish the same goal. | |
| final List<String> output = await runCapturingPrint(runner, <String>[ | |
| 'native-test', | |
| '--android', | |
| ]); | |
| expect(output, containsAllInOrder(<Matcher>[contains('Running for package_a')])); | |
| }); | |
| } | |
| test('skips commands if all files should be ignored', () async { | |
| createFakePackage('package_a', packagesDir); | |
| gitProcessRunner.mockProcessesForExecutable['git-diff'] = <FakeProcessInfo>[ | |
| FakeProcessInfo( | |
| MockProcess( | |
| stdout: ''' | |
| README.md | |
| SUGGESTED_REVIEWERS.md | |
| packages/package_a/CHANGELOG.md | |
| ''', | |
| ), | |
| ), | |
| ]; | |
| final List<String> output = await runCapturingPrint(runner, <String>[ | |
| 'native-test', | |
| 'android', | |
| ]); | |
| expect(output, isNot(containsAllInOrder(<Matcher>[contains('Running for package_a')]))); | |
| expect(output, containsAllInOrder(<Matcher>[contains('SKIPPING ALL PACKAGES')])); | |
| }); | |
| }); | |
| }); | |
| 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'), | |
| getConfigOnlyDarwinBuildCall(pluginExampleDirectory, FlutterPlatform.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, | |
| _allAbiFlag, | |
| ], androidFolder.path), | |
| ]), | |
| ); | |
| }); | |
| test('ignores Java integration test files using (or defining) DartIntegrationTest', () async { | |
| const 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 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, | |
| _allAbiFlag, | |
| ], 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, | |
| _allAbiFlag, | |
| ], 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 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, _archDirX64); | |
| 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 debugTestBinaryRelativePath = 'build/linux/x64/debug/bar/plugin_test'; | |
| const 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, _archDirX64); | |
| 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, _archDirX64); | |
| 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 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, _archDirX64); | |
| 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 or 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'), | |
| getConfigOnlyDarwinBuildCall(pluginExampleDirectory, FlutterPlatform.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'), | |
| getConfigOnlyDarwinBuildCall(pluginExampleDirectory, FlutterPlatform.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')]), | |
| ); | |
| }); | |
| test('Xcode warnings exceptions list', () 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', | |
| '--xcode-warnings-exceptions=plugin', | |
| ]); | |
| expect( | |
| processRunner.recordedCalls, | |
| contains( | |
| getRunTestCall( | |
| pluginExampleDirectory, | |
| 'ios', | |
| destination: 'id=$_simulatorDeviceId', | |
| treatWarningsAsErrors: false, | |
| ), | |
| ), | |
| ); | |
| }); | |
| test('Xcode warnings exceptions file', () async { | |
| final File configFile = packagesDir.childFile('exceptions.yaml'); | |
| await configFile.writeAsString('- plugin'); | |
| 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', | |
| '--xcode-warnings-exceptions=${configFile.path}', | |
| ]); | |
| expect( | |
| processRunner.recordedCalls, | |
| contains( | |
| getRunTestCall( | |
| pluginExampleDirectory, | |
| 'ios', | |
| destination: 'id=$_simulatorDeviceId', | |
| treatWarningsAsErrors: false, | |
| ), | |
| ), | |
| ); | |
| }); | |
| test('treat warnings as errors if plugin not on exceptions list', () 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', | |
| '--xcode-warnings-exceptions=foo,bar', | |
| ]); | |
| expect( | |
| processRunner.recordedCalls, | |
| contains( | |
| getRunTestCall(pluginExampleDirectory, 'ios', destination: 'id=$_simulatorDeviceId'), | |
| ), | |
| ); | |
| }); | |
| }); | |
| 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', 'clean', 'test']), // iOS run | |
| getMockXcodebuildListProcess(<String>['RunnerTests', 'RunnerUITests']), // macOS list | |
| FakeProcessInfo(MockProcess(), <String>['xcodebuild', 'clean', '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'), | |
| getConfigOnlyDarwinBuildCall(pluginExampleDirectory, FlutterPlatform.ios), | |
| getRunTestCall(pluginExampleDirectory, 'ios', destination: 'foo_destination'), | |
| getTargetCheckCall(pluginExampleDirectory, 'macos'), | |
| getConfigOnlyDarwinBuildCall(pluginExampleDirectory, FlutterPlatform.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'), | |
| getConfigOnlyDarwinBuildCall(pluginExampleDirectory, FlutterPlatform.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'), | |
| getConfigOnlyDarwinBuildCall(pluginExampleDirectory, FlutterPlatform.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 NativePlatform mockPlatform; | |
| late Directory packagesDir; | |
| late CommandRunner<void> runner; | |
| late RecordingProcessRunner processRunner; | |
| late GitDir gitDir; | |
| setUp(() { | |
| mockPlatform = createMockPlatform(isWindows: true); | |
| (:packagesDir, :processRunner, gitProcessRunner: _, :gitDir) = configureBaseCommandMocks( | |
| platform: mockPlatform, | |
| ); | |
| }); | |
| // Returns the ProcessCall to expect for build the Windows unit tests for | |
| // the given plugin. | |
| ProcessCall getWindowsBuildCall(RepositoryPackage plugin, String arch) { | |
| final Directory projectDir = getExampleDir( | |
| plugin, | |
| ).childDirectory('build').childDirectory('windows').childDirectory(arch); | |
| return ProcessCall(_fakeCmakeCommand, <String>[ | |
| '--build', | |
| projectDir.path, | |
| '--target', | |
| 'unit_tests', | |
| '--config', | |
| 'Debug', | |
| ], null); | |
| } | |
| group('Windows x64', () { | |
| setUp(() { | |
| final command = NativeTestCommand( | |
| packagesDir, | |
| processRunner: processRunner, | |
| platform: mockPlatform, | |
| gitDir: gitDir, | |
| abi: Abi.windowsX64, | |
| ); | |
| runner = CommandRunner<void>('native_test_command', 'Test for native_test_command'); | |
| runner.addCommand(command); | |
| }); | |
| test('runs unit tests', () async { | |
| const x64TestBinaryRelativePath = 'build/windows/x64/Debug/bar/plugin_test.exe'; | |
| const arm64TestBinaryRelativePath = 'build/windows/arm64/Debug/bar/plugin_test.exe'; | |
| final RepositoryPackage plugin = createFakePlugin( | |
| 'plugin', | |
| packagesDir, | |
| extraFiles: <String>[ | |
| 'example/$x64TestBinaryRelativePath', | |
| 'example/$arm64TestBinaryRelativePath', | |
| ], | |
| platformSupport: <String, PlatformDetails>{ | |
| platformWindows: const PlatformDetails(PlatformSupport.inline), | |
| }, | |
| ); | |
| _createFakeCMakeCache(plugin, mockPlatform, _archDirX64); | |
| final File testBinary = childFileWithSubcomponents(plugin.directory, <String>[ | |
| 'example', | |
| ...x64TestBinaryRelativePath.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, _archDirX64), | |
| ProcessCall(testBinary.path, const <String>[], null), | |
| ]), | |
| ); | |
| }); | |
| test('only runs debug unit tests', () async { | |
| const debugTestBinaryRelativePath = 'build/windows/x64/Debug/bar/plugin_test.exe'; | |
| const releaseTestBinaryRelativePath = 'build/windows/x64/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, _archDirX64); | |
| 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, _archDirX64), | |
| 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, _archDirX64); | |
| 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, _archDirX64)]), | |
| ); | |
| }); | |
| test('fails if a unit test fails', () async { | |
| const testBinaryRelativePath = 'build/windows/x64/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, _archDirX64); | |
| 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, _archDirX64), | |
| ProcessCall(testBinary.path, const <String>[], null), | |
| ]), | |
| ); | |
| }); | |
| }); | |
| group('Windows arm64', () { | |
| setUp(() { | |
| final command = NativeTestCommand( | |
| packagesDir, | |
| processRunner: processRunner, | |
| platform: mockPlatform, | |
| gitDir: gitDir, | |
| abi: Abi.windowsArm64, | |
| ); | |
| runner = CommandRunner<void>('native_test_command', 'Test for native_test_command'); | |
| runner.addCommand(command); | |
| }); | |
| test('runs unit tests', () async { | |
| const x64TestBinaryRelativePath = 'build/windows/x64/Debug/bar/plugin_test.exe'; | |
| const arm64TestBinaryRelativePath = 'build/windows/arm64/Debug/bar/plugin_test.exe'; | |
| final RepositoryPackage plugin = createFakePlugin( | |
| 'plugin', | |
| packagesDir, | |
| extraFiles: <String>[ | |
| 'example/$x64TestBinaryRelativePath', | |
| 'example/$arm64TestBinaryRelativePath', | |
| ], | |
| platformSupport: <String, PlatformDetails>{ | |
| platformWindows: const PlatformDetails(PlatformSupport.inline), | |
| }, | |
| ); | |
| _createFakeCMakeCache(plugin, mockPlatform, _archDirArm64); | |
| final File testBinary = childFileWithSubcomponents(plugin.directory, <String>[ | |
| 'example', | |
| ...arm64TestBinaryRelativePath.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, _archDirArm64), | |
| ProcessCall(testBinary.path, const <String>[], null), | |
| ]), | |
| ); | |
| }); | |
| test('falls back to x64 unit tests if arm64 is not built', () async { | |
| const x64TestBinaryRelativePath = 'build/windows/x64/Debug/bar/plugin_test.exe'; | |
| final RepositoryPackage plugin = createFakePlugin( | |
| 'plugin', | |
| packagesDir, | |
| extraFiles: <String>['example/$x64TestBinaryRelativePath'], | |
| platformSupport: <String, PlatformDetails>{ | |
| platformWindows: const PlatformDetails(PlatformSupport.inline), | |
| }, | |
| ); | |
| _createFakeCMakeCache(plugin, mockPlatform, _archDirX64); | |
| final File testBinary = childFileWithSubcomponents(plugin.directory, <String>[ | |
| 'example', | |
| ...x64TestBinaryRelativePath.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, _archDirX64), | |
| ProcessCall(testBinary.path, const <String>[], null), | |
| ]), | |
| ); | |
| }); | |
| test('only runs debug unit tests', () async { | |
| const debugTestBinaryRelativePath = 'build/windows/arm64/Debug/bar/plugin_test.exe'; | |
| const releaseTestBinaryRelativePath = 'build/windows/arm64/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, _archDirArm64); | |
| 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, _archDirArm64), | |
| ProcessCall(debugTestBinary.path, const <String>[], null), | |
| ]), | |
| ); | |
| }); | |
| }); | |
| }); | |
| } |