mirror of
https://github.com/flutter/packages.git
synced 2025-06-20 14:23:54 +08:00
[flutter_plugin_tests] Split analyze out of xctest (#4161)
To prep for making a combined command to run native tests across different platforms, rework `xctest`: - Split analyze out into a new `xcode-analyze` command: - Since the analyze step runs a new build over everything with different flags, this is only a small amount slower than the combined version - This makes the logic easier to follow - This allows us to meaningfully report skips, to better notice missing tests. - Add the ability to target specific test bundles (RunnerTests or RunnerUITests) To share code between the commands, this extracts a new `Xcode` helper class. Part of https://github.com/flutter/flutter/issues/84392 and https://github.com/flutter/flutter/issues/86489
This commit is contained in:
@ -1,3 +1,9 @@
|
||||
## NEXT
|
||||
|
||||
- Added an `xctest` flag to select specific test targets, to allow running only
|
||||
unit tests or integration tests.
|
||||
- Split Xcode analysis out of `xctest` and into a new `xcode-analyze` command.
|
||||
|
||||
## 0.4.1
|
||||
|
||||
- Improved `license-check` output.
|
||||
|
159
script/tool/lib/src/common/xcode.dart
Normal file
159
script/tool/lib/src/common/xcode.dart
Normal file
@ -0,0 +1,159 @@
|
||||
// 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 'dart:io' as io;
|
||||
|
||||
import 'package:file/file.dart';
|
||||
|
||||
import 'core.dart';
|
||||
import 'process_runner.dart';
|
||||
|
||||
const String _xcodeBuildCommand = 'xcodebuild';
|
||||
const String _xcRunCommand = 'xcrun';
|
||||
|
||||
/// A utility class for interacting with the installed version of Xcode.
|
||||
class Xcode {
|
||||
/// Creates an instance that runs commends with the given [processRunner].
|
||||
///
|
||||
/// If [log] is true, commands run by this instance will long various status
|
||||
/// messages.
|
||||
Xcode({
|
||||
this.processRunner = const ProcessRunner(),
|
||||
this.log = false,
|
||||
});
|
||||
|
||||
/// The [ProcessRunner] used to run commands. Overridable for testing.
|
||||
final ProcessRunner processRunner;
|
||||
|
||||
/// Whether or not to log when running commands.
|
||||
final bool log;
|
||||
|
||||
/// Runs an `xcodebuild` in [directory] with the given parameters.
|
||||
Future<int> runXcodeBuild(
|
||||
Directory directory, {
|
||||
List<String> actions = const <String>['build'],
|
||||
required String workspace,
|
||||
required String scheme,
|
||||
String? configuration,
|
||||
List<String> extraFlags = const <String>[],
|
||||
}) {
|
||||
final List<String> args = <String>[
|
||||
_xcodeBuildCommand,
|
||||
...actions,
|
||||
if (workspace != null) ...<String>['-workspace', workspace],
|
||||
if (scheme != null) ...<String>['-scheme', scheme],
|
||||
if (configuration != null) ...<String>['-configuration', configuration],
|
||||
...extraFlags,
|
||||
];
|
||||
final String completeTestCommand = '$_xcRunCommand ${args.join(' ')}';
|
||||
if (log) {
|
||||
print(completeTestCommand);
|
||||
}
|
||||
return processRunner.runAndStream(_xcRunCommand, args,
|
||||
workingDir: directory);
|
||||
}
|
||||
|
||||
/// Returns true if [project], which should be an .xcodeproj directory,
|
||||
/// contains a target called [target], false if it does not, and null if the
|
||||
/// check fails (e.g., if [project] is not an Xcode project).
|
||||
Future<bool?> projectHasTarget(Directory project, String target) async {
|
||||
final io.ProcessResult result =
|
||||
await processRunner.run(_xcRunCommand, <String>[
|
||||
_xcodeBuildCommand,
|
||||
'-list',
|
||||
'-json',
|
||||
'-project',
|
||||
project.path,
|
||||
]);
|
||||
if (result.exitCode != 0) {
|
||||
return null;
|
||||
}
|
||||
Map<String, dynamic>? projectInfo;
|
||||
try {
|
||||
projectInfo = (jsonDecode(result.stdout as String)
|
||||
as Map<String, dynamic>)['project'] as Map<String, dynamic>?;
|
||||
} on FormatException {
|
||||
return null;
|
||||
}
|
||||
if (projectInfo == null) {
|
||||
return null;
|
||||
}
|
||||
final List<String>? targets =
|
||||
(projectInfo['targets'] as List<dynamic>?)?.cast<String>();
|
||||
return targets?.contains(target) ?? false;
|
||||
}
|
||||
|
||||
/// Returns the newest available simulator (highest OS version, with ties
|
||||
/// broken in favor of newest device), if any.
|
||||
Future<String?> findBestAvailableIphoneSimulator() async {
|
||||
final List<String> findSimulatorsArguments = <String>[
|
||||
'simctl',
|
||||
'list',
|
||||
'devices',
|
||||
'runtimes',
|
||||
'available',
|
||||
'--json',
|
||||
];
|
||||
final String findSimulatorCompleteCommand =
|
||||
'$_xcRunCommand ${findSimulatorsArguments.join(' ')}';
|
||||
if (log) {
|
||||
print('Looking for available simulators...');
|
||||
print(findSimulatorCompleteCommand);
|
||||
}
|
||||
final io.ProcessResult findSimulatorsResult =
|
||||
await processRunner.run(_xcRunCommand, findSimulatorsArguments);
|
||||
if (findSimulatorsResult.exitCode != 0) {
|
||||
if (log) {
|
||||
printError(
|
||||
'Error occurred while running "$findSimulatorCompleteCommand":\n'
|
||||
'${findSimulatorsResult.stderr}');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
final Map<String, dynamic> simulatorListJson =
|
||||
jsonDecode(findSimulatorsResult.stdout as String)
|
||||
as Map<String, dynamic>;
|
||||
final List<Map<String, dynamic>> runtimes =
|
||||
(simulatorListJson['runtimes'] as List<dynamic>)
|
||||
.cast<Map<String, dynamic>>();
|
||||
final Map<String, Object> devices =
|
||||
(simulatorListJson['devices'] as Map<String, dynamic>)
|
||||
.cast<String, Object>();
|
||||
if (runtimes.isEmpty || devices.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
String? id;
|
||||
// Looking for runtimes, trying to find one with highest OS version.
|
||||
for (final Map<String, dynamic> rawRuntimeMap in runtimes.reversed) {
|
||||
final Map<String, Object> runtimeMap =
|
||||
rawRuntimeMap.cast<String, Object>();
|
||||
if ((runtimeMap['name'] as String?)?.contains('iOS') != true) {
|
||||
continue;
|
||||
}
|
||||
final String? runtimeID = runtimeMap['identifier'] as String?;
|
||||
if (runtimeID == null) {
|
||||
continue;
|
||||
}
|
||||
final List<Map<String, dynamic>>? devicesForRuntime =
|
||||
(devices[runtimeID] as List<dynamic>?)?.cast<Map<String, dynamic>>();
|
||||
if (devicesForRuntime == null || devicesForRuntime.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
// Looking for runtimes, trying to find latest version of device.
|
||||
for (final Map<String, dynamic> rawDevice in devicesForRuntime.reversed) {
|
||||
final Map<String, Object> device = rawDevice.cast<String, Object>();
|
||||
id = device['udid'] as String?;
|
||||
if (id == null) {
|
||||
continue;
|
||||
}
|
||||
if (log) {
|
||||
print('device selected: $device');
|
||||
}
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ import 'publish_plugin_command.dart';
|
||||
import 'pubspec_check_command.dart';
|
||||
import 'test_command.dart';
|
||||
import 'version_check_command.dart';
|
||||
import 'xcode_analyze_command.dart';
|
||||
import 'xctest_command.dart';
|
||||
|
||||
void main(List<String> args) {
|
||||
@ -59,6 +60,7 @@ void main(List<String> args) {
|
||||
..addCommand(PubspecCheckCommand(packagesDir))
|
||||
..addCommand(TestCommand(packagesDir))
|
||||
..addCommand(VersionCheckCommand(packagesDir))
|
||||
..addCommand(XcodeAnalyzeCommand(packagesDir))
|
||||
..addCommand(XCTestCommand(packagesDir));
|
||||
|
||||
commandRunner.run(args).catchError((Object e) {
|
||||
|
111
script/tool/lib/src/xcode_analyze_command.dart
Normal file
111
script/tool/lib/src/xcode_analyze_command.dart
Normal file
@ -0,0 +1,111 @@
|
||||
// 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 'package:file/file.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
import 'common/core.dart';
|
||||
import 'common/package_looping_command.dart';
|
||||
import 'common/plugin_utils.dart';
|
||||
import 'common/process_runner.dart';
|
||||
import 'common/xcode.dart';
|
||||
|
||||
/// The command to run Xcode's static analyzer on plugins.
|
||||
class XcodeAnalyzeCommand extends PackageLoopingCommand {
|
||||
/// Creates an instance of the test command.
|
||||
XcodeAnalyzeCommand(
|
||||
Directory packagesDir, {
|
||||
ProcessRunner processRunner = const ProcessRunner(),
|
||||
Platform platform = const LocalPlatform(),
|
||||
}) : _xcode = Xcode(processRunner: processRunner, log: true),
|
||||
super(packagesDir, processRunner: processRunner, platform: platform) {
|
||||
argParser.addFlag(kPlatformIos, help: 'Analyze iOS');
|
||||
argParser.addFlag(kPlatformMacos, help: 'Analyze macOS');
|
||||
}
|
||||
|
||||
final Xcode _xcode;
|
||||
|
||||
@override
|
||||
final String name = 'xcode-analyze';
|
||||
|
||||
@override
|
||||
final String description =
|
||||
'Runs Xcode analysis on the iOS and/or macOS example apps.';
|
||||
|
||||
@override
|
||||
Future<void> initializeRun() async {
|
||||
if (!(getBoolArg(kPlatformIos) || getBoolArg(kPlatformMacos))) {
|
||||
printError('At least one platform flag must be provided.');
|
||||
throw ToolExit(exitInvalidArguments);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PackageResult> runForPackage(Directory package) async {
|
||||
final bool testIos = getBoolArg(kPlatformIos) &&
|
||||
pluginSupportsPlatform(kPlatformIos, package,
|
||||
requiredMode: PlatformSupport.inline);
|
||||
final bool testMacos = getBoolArg(kPlatformMacos) &&
|
||||
pluginSupportsPlatform(kPlatformMacos, package,
|
||||
requiredMode: PlatformSupport.inline);
|
||||
|
||||
final bool multiplePlatformsRequested =
|
||||
getBoolArg(kPlatformIos) && getBoolArg(kPlatformMacos);
|
||||
if (!(testIos || testMacos)) {
|
||||
return PackageResult.skip('Not implemented for target platform(s).');
|
||||
}
|
||||
|
||||
final List<String> failures = <String>[];
|
||||
if (testIos &&
|
||||
!await _analyzePlugin(package, 'iOS', extraFlags: <String>[
|
||||
'-destination',
|
||||
'generic/platform=iOS Simulator'
|
||||
])) {
|
||||
failures.add('iOS');
|
||||
}
|
||||
if (testMacos && !await _analyzePlugin(package, 'macOS')) {
|
||||
failures.add('macOS');
|
||||
}
|
||||
|
||||
// Only provide the failing platform in the failure details if testing
|
||||
// multiple platforms, otherwise it's just noise.
|
||||
return failures.isEmpty
|
||||
? PackageResult.success()
|
||||
: PackageResult.fail(
|
||||
multiplePlatformsRequested ? failures : <String>[]);
|
||||
}
|
||||
|
||||
/// Analyzes [plugin] for [platform], returning true if it passed analysis.
|
||||
Future<bool> _analyzePlugin(
|
||||
Directory plugin,
|
||||
String platform, {
|
||||
List<String> extraFlags = const <String>[],
|
||||
}) async {
|
||||
bool passing = true;
|
||||
for (final Directory example in getExamplesForPlugin(plugin)) {
|
||||
// Running tests and static analyzer.
|
||||
final String examplePath =
|
||||
getRelativePosixPath(example, from: plugin.parent);
|
||||
print('Running $platform tests and analyzer for $examplePath...');
|
||||
final int exitCode = await _xcode.runXcodeBuild(
|
||||
example,
|
||||
actions: <String>['analyze'],
|
||||
workspace: '${platform.toLowerCase()}/Runner.xcworkspace',
|
||||
scheme: 'Runner',
|
||||
configuration: 'Debug',
|
||||
extraFlags: <String>[
|
||||
...extraFlags,
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
],
|
||||
);
|
||||
if (exitCode == 0) {
|
||||
printSuccess('$examplePath ($platform) passed analysis.');
|
||||
} else {
|
||||
printError('$examplePath ($platform) failed analysis.');
|
||||
passing = false;
|
||||
}
|
||||
}
|
||||
return passing;
|
||||
}
|
||||
}
|
@ -2,9 +2,6 @@
|
||||
// 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:io' as io;
|
||||
|
||||
import 'package:file/file.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
@ -12,35 +9,39 @@ import 'common/core.dart';
|
||||
import 'common/package_looping_command.dart';
|
||||
import 'common/plugin_utils.dart';
|
||||
import 'common/process_runner.dart';
|
||||
import 'common/xcode.dart';
|
||||
|
||||
const String _kiOSDestination = 'ios-destination';
|
||||
const String _kXcodeBuildCommand = 'xcodebuild';
|
||||
const String _kXCRunCommand = 'xcrun';
|
||||
const String _kFoundNoSimulatorsMessage =
|
||||
'Cannot find any available simulators, tests failed';
|
||||
const String _iosDestinationFlag = 'ios-destination';
|
||||
const String _testTargetFlag = 'test-target';
|
||||
|
||||
const int _exitFindingSimulatorsFailed = 3;
|
||||
const int _exitNoSimulators = 4;
|
||||
// The exit code from 'xcodebuild test' when there are no tests.
|
||||
const int _xcodebuildNoTestExitCode = 66;
|
||||
|
||||
const int _exitNoSimulators = 3;
|
||||
|
||||
/// The command to run XCTests (XCUnitTest and XCUITest) in plugins.
|
||||
/// The tests target have to be added to the Xcode project of the example app,
|
||||
/// usually at "example/{ios,macos}/Runner.xcworkspace".
|
||||
///
|
||||
/// The static analyzer is also run.
|
||||
class XCTestCommand extends PackageLoopingCommand {
|
||||
/// Creates an instance of the test command.
|
||||
XCTestCommand(
|
||||
Directory packagesDir, {
|
||||
ProcessRunner processRunner = const ProcessRunner(),
|
||||
Platform platform = const LocalPlatform(),
|
||||
}) : super(packagesDir, processRunner: processRunner, platform: platform) {
|
||||
}) : _xcode = Xcode(processRunner: processRunner, log: true),
|
||||
super(packagesDir, processRunner: processRunner, platform: platform) {
|
||||
argParser.addOption(
|
||||
_kiOSDestination,
|
||||
_iosDestinationFlag,
|
||||
help:
|
||||
'Specify the destination when running the test, used for -destination flag for xcodebuild command.\n'
|
||||
'this is passed to the `-destination` argument in xcodebuild command.\n'
|
||||
'See https://developer.apple.com/library/archive/technotes/tn2339/_index.html#//apple_ref/doc/uid/DTS40014588-CH1-UNIT for details on how to specify the destination.',
|
||||
);
|
||||
argParser.addOption(
|
||||
_testTargetFlag,
|
||||
help:
|
||||
'Limits the tests to a specific target (e.g., RunnerTests or RunnerUITests)',
|
||||
);
|
||||
argParser.addFlag(kPlatformIos, help: 'Runs the iOS tests');
|
||||
argParser.addFlag(kPlatformMacos, help: 'Runs the macOS tests');
|
||||
}
|
||||
@ -48,6 +49,8 @@ class XCTestCommand extends PackageLoopingCommand {
|
||||
// The device destination flags for iOS tests.
|
||||
List<String> _iosDestinationFlags = <String>[];
|
||||
|
||||
final Xcode _xcode;
|
||||
|
||||
@override
|
||||
final String name = 'xctest';
|
||||
|
||||
@ -56,9 +59,6 @@ class XCTestCommand extends PackageLoopingCommand {
|
||||
'Runs the xctests in the iOS and/or macOS example apps.\n\n'
|
||||
'This command requires "flutter" and "xcrun" to be in your path.';
|
||||
|
||||
@override
|
||||
String get failureListHeader => 'The following packages are failing XCTests:';
|
||||
|
||||
@override
|
||||
Future<void> initializeRun() async {
|
||||
final bool shouldTestIos = getBoolArg(kPlatformIos);
|
||||
@ -70,11 +70,12 @@ class XCTestCommand extends PackageLoopingCommand {
|
||||
}
|
||||
|
||||
if (shouldTestIos) {
|
||||
String destination = getStringArg(_kiOSDestination);
|
||||
String destination = getStringArg(_iosDestinationFlag);
|
||||
if (destination.isEmpty) {
|
||||
final String? simulatorId = await _findAvailableIphoneSimulator();
|
||||
final String? simulatorId =
|
||||
await _xcode.findBestAvailableIphoneSimulator();
|
||||
if (simulatorId == null) {
|
||||
printError(_kFoundNoSimulatorsMessage);
|
||||
printError('Cannot find any available simulators, tests failed');
|
||||
throw ToolExit(_exitNoSimulators);
|
||||
}
|
||||
destination = 'id=$simulatorId';
|
||||
@ -115,15 +116,26 @@ class XCTestCommand extends PackageLoopingCommand {
|
||||
}
|
||||
|
||||
final List<String> failures = <String>[];
|
||||
if (testIos &&
|
||||
!await _testPlugin(package, 'iOS',
|
||||
extraXcrunFlags: _iosDestinationFlags)) {
|
||||
failures.add('iOS');
|
||||
bool ranTests = false;
|
||||
if (testIos) {
|
||||
final RunState result = await _testPlugin(package, 'iOS',
|
||||
extraXcrunFlags: _iosDestinationFlags);
|
||||
ranTests |= result != RunState.skipped;
|
||||
if (result == RunState.failed) {
|
||||
failures.add('iOS');
|
||||
}
|
||||
}
|
||||
if (testMacos && !await _testPlugin(package, 'macOS')) {
|
||||
failures.add('macOS');
|
||||
if (testMacos) {
|
||||
final RunState result = await _testPlugin(package, 'macOS');
|
||||
ranTests |= result != RunState.skipped;
|
||||
if (result == RunState.failed) {
|
||||
failures.add('macOS');
|
||||
}
|
||||
}
|
||||
|
||||
if (!ranTests) {
|
||||
return PackageResult.skip('No tests found.');
|
||||
}
|
||||
// Only provide the failing platform in the failure details if testing
|
||||
// multiple platforms, otherwise it's just noise.
|
||||
return failures.isEmpty
|
||||
@ -133,124 +145,67 @@ class XCTestCommand extends PackageLoopingCommand {
|
||||
}
|
||||
|
||||
/// Runs all applicable tests for [plugin], printing status and returning
|
||||
/// success if the tests passed.
|
||||
Future<bool> _testPlugin(
|
||||
/// the test result.
|
||||
Future<RunState> _testPlugin(
|
||||
Directory plugin,
|
||||
String platform, {
|
||||
List<String> extraXcrunFlags = const <String>[],
|
||||
}) async {
|
||||
bool passing = true;
|
||||
final String testTarget = getStringArg(_testTargetFlag);
|
||||
|
||||
// Assume skipped until at least one test has run.
|
||||
RunState overallResult = RunState.skipped;
|
||||
for (final Directory example in getExamplesForPlugin(plugin)) {
|
||||
// Running tests and static analyzer.
|
||||
final String examplePath =
|
||||
getRelativePosixPath(example, from: plugin.parent);
|
||||
print('Running $platform tests and analyzer for $examplePath...');
|
||||
int exitCode =
|
||||
await _runTests(true, example, platform, extraFlags: extraXcrunFlags);
|
||||
// 66 = there is no test target (this fails fast). Try again with just the analyzer.
|
||||
if (exitCode == 66) {
|
||||
print('Tests not found for $examplePath, running analyzer only...');
|
||||
exitCode = await _runTests(false, example, platform,
|
||||
extraFlags: extraXcrunFlags);
|
||||
}
|
||||
if (exitCode == 0) {
|
||||
printSuccess('Successfully ran $platform xctest for $examplePath');
|
||||
} else {
|
||||
passing = false;
|
||||
}
|
||||
}
|
||||
return passing;
|
||||
}
|
||||
|
||||
Future<int> _runTests(
|
||||
bool runTests,
|
||||
Directory example,
|
||||
String platform, {
|
||||
List<String> extraFlags = const <String>[],
|
||||
}) {
|
||||
final List<String> xctestArgs = <String>[
|
||||
_kXcodeBuildCommand,
|
||||
if (runTests) 'test',
|
||||
'analyze',
|
||||
'-workspace',
|
||||
'${platform.toLowerCase()}/Runner.xcworkspace',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'-scheme',
|
||||
'Runner',
|
||||
...extraFlags,
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
];
|
||||
final String completeTestCommand =
|
||||
'$_kXCRunCommand ${xctestArgs.join(' ')}';
|
||||
print(completeTestCommand);
|
||||
return processRunner.runAndStream(_kXCRunCommand, xctestArgs,
|
||||
workingDir: example);
|
||||
}
|
||||
|
||||
Future<String?> _findAvailableIphoneSimulator() async {
|
||||
// Find the first available destination if not specified.
|
||||
final List<String> findSimulatorsArguments = <String>[
|
||||
'simctl',
|
||||
'list',
|
||||
'--json'
|
||||
];
|
||||
final String findSimulatorCompleteCommand =
|
||||
'$_kXCRunCommand ${findSimulatorsArguments.join(' ')}';
|
||||
print('Looking for available simulators...');
|
||||
print(findSimulatorCompleteCommand);
|
||||
final io.ProcessResult findSimulatorsResult =
|
||||
await processRunner.run(_kXCRunCommand, findSimulatorsArguments);
|
||||
if (findSimulatorsResult.exitCode != 0) {
|
||||
printError(
|
||||
'Error occurred while running "$findSimulatorCompleteCommand":\n'
|
||||
'${findSimulatorsResult.stderr}');
|
||||
throw ToolExit(_exitFindingSimulatorsFailed);
|
||||
}
|
||||
final Map<String, dynamic> simulatorListJson =
|
||||
jsonDecode(findSimulatorsResult.stdout as String)
|
||||
as Map<String, dynamic>;
|
||||
final List<Map<String, dynamic>> runtimes =
|
||||
(simulatorListJson['runtimes'] as List<dynamic>)
|
||||
.cast<Map<String, dynamic>>();
|
||||
final Map<String, Object> devices =
|
||||
(simulatorListJson['devices'] as Map<String, dynamic>)
|
||||
.cast<String, Object>();
|
||||
if (runtimes.isEmpty || devices.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
String? id;
|
||||
// Looking for runtimes, trying to find one with highest OS version.
|
||||
for (final Map<String, dynamic> rawRuntimeMap in runtimes.reversed) {
|
||||
final Map<String, Object> runtimeMap =
|
||||
rawRuntimeMap.cast<String, Object>();
|
||||
if ((runtimeMap['name'] as String?)?.contains('iOS') != true) {
|
||||
continue;
|
||||
}
|
||||
final String? runtimeID = runtimeMap['identifier'] as String?;
|
||||
if (runtimeID == null) {
|
||||
continue;
|
||||
}
|
||||
final List<Map<String, dynamic>>? devicesForRuntime =
|
||||
(devices[runtimeID] as List<dynamic>?)?.cast<Map<String, dynamic>>();
|
||||
if (devicesForRuntime == null || devicesForRuntime.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
// Looking for runtimes, trying to find latest version of device.
|
||||
for (final Map<String, dynamic> rawDevice in devicesForRuntime.reversed) {
|
||||
final Map<String, Object> device = rawDevice.cast<String, Object>();
|
||||
if (device['availabilityError'] != null ||
|
||||
(device['isAvailable'] as bool?) == false) {
|
||||
if (testTarget.isNotEmpty) {
|
||||
final Directory project = example
|
||||
.childDirectory(platform.toLowerCase())
|
||||
.childDirectory('Runner.xcodeproj');
|
||||
final bool? hasTarget =
|
||||
await _xcode.projectHasTarget(project, testTarget);
|
||||
if (hasTarget == null) {
|
||||
printError('Unable to check targets for $examplePath.');
|
||||
overallResult = RunState.failed;
|
||||
continue;
|
||||
} else if (!hasTarget) {
|
||||
print('No "$testTarget" target in $examplePath; skipping.');
|
||||
continue;
|
||||
}
|
||||
id = device['udid'] as String?;
|
||||
if (id == null) {
|
||||
}
|
||||
|
||||
print('Running $platform tests for $examplePath...');
|
||||
final int exitCode = await _xcode.runXcodeBuild(
|
||||
example,
|
||||
actions: <String>['test'],
|
||||
workspace: '${platform.toLowerCase()}/Runner.xcworkspace',
|
||||
scheme: 'Runner',
|
||||
configuration: 'Debug',
|
||||
extraFlags: <String>[
|
||||
if (testTarget.isNotEmpty) '-only-testing:$testTarget',
|
||||
...extraXcrunFlags,
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
],
|
||||
);
|
||||
|
||||
switch (exitCode) {
|
||||
case _xcodebuildNoTestExitCode:
|
||||
print('No tests found for $examplePath');
|
||||
continue;
|
||||
}
|
||||
print('device selected: $device');
|
||||
return id;
|
||||
case 0:
|
||||
printSuccess('Successfully ran $platform xctest for $examplePath');
|
||||
// If this is the first test, assume success until something fails.
|
||||
if (overallResult == RunState.skipped) {
|
||||
overallResult = RunState.succeeded;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Any failure means a failure overall.
|
||||
overallResult = RunState.failed;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return overallResult;
|
||||
}
|
||||
}
|
||||
|
396
script/tool/test/common/xcode_test.dart
Normal file
396
script/tool/test/common/xcode_test.dart
Normal file
@ -0,0 +1,396 @@
|
||||
// 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 'dart:io' as io;
|
||||
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/local.dart';
|
||||
import 'package:flutter_plugin_tools/src/common/xcode.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../mocks.dart';
|
||||
import '../util.dart';
|
||||
|
||||
void main() {
|
||||
late RecordingProcessRunner processRunner;
|
||||
late Xcode xcode;
|
||||
|
||||
setUp(() {
|
||||
processRunner = RecordingProcessRunner();
|
||||
xcode = Xcode(processRunner: processRunner);
|
||||
});
|
||||
|
||||
group('findBestAvailableIphoneSimulator', () {
|
||||
test('finds the newest device', () async {
|
||||
const String expectedDeviceId = '1E76A0FD-38AC-4537-A989-EA639D7D012A';
|
||||
// Note: This uses `dynamic` deliberately, and should not be updated to
|
||||
// Object, in order to ensure that the code correctly handles this return
|
||||
// type from JSON decoding.
|
||||
final Map<String, dynamic> devices = <String, dynamic>{
|
||||
'runtimes': <Map<String, dynamic>>[
|
||||
<String, dynamic>{
|
||||
'bundlePath':
|
||||
'/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.0.simruntime',
|
||||
'buildversion': '17A577',
|
||||
'runtimeRoot':
|
||||
'/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.0.simruntime/Contents/Resources/RuntimeRoot',
|
||||
'identifier': 'com.apple.CoreSimulator.SimRuntime.iOS-13-0',
|
||||
'version': '13.0',
|
||||
'isAvailable': true,
|
||||
'name': 'iOS 13.0'
|
||||
},
|
||||
<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'
|
||||
},
|
||||
<String, dynamic>{
|
||||
'bundlePath':
|
||||
'/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime',
|
||||
'buildversion': '17T531',
|
||||
'runtimeRoot':
|
||||
'/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime/Contents/Resources/RuntimeRoot',
|
||||
'identifier': 'com.apple.CoreSimulator.SimRuntime.watchOS-6-2',
|
||||
'version': '6.2.1',
|
||||
'isAvailable': true,
|
||||
'name': 'watchOS 6.2'
|
||||
}
|
||||
],
|
||||
'devices': <String, dynamic>{
|
||||
'com.apple.CoreSimulator.SimRuntime.iOS-13-4': <Map<String, dynamic>>[
|
||||
<String, dynamic>{
|
||||
'dataPath':
|
||||
'/Users/xxx/Library/Developer/CoreSimulator/Devices/2706BBEB-1E01-403E-A8E9-70E8E5A24774/data',
|
||||
'logPath':
|
||||
'/Users/xxx/Library/Logs/CoreSimulator/2706BBEB-1E01-403E-A8E9-70E8E5A24774',
|
||||
'udid': '2706BBEB-1E01-403E-A8E9-70E8E5A24774',
|
||||
'isAvailable': true,
|
||||
'deviceTypeIdentifier':
|
||||
'com.apple.CoreSimulator.SimDeviceType.iPhone-8',
|
||||
'state': 'Shutdown',
|
||||
'name': 'iPhone 8'
|
||||
},
|
||||
<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': expectedDeviceId,
|
||||
'isAvailable': true,
|
||||
'deviceTypeIdentifier':
|
||||
'com.apple.CoreSimulator.SimDeviceType.iPhone-8-Plus',
|
||||
'state': 'Shutdown',
|
||||
'name': 'iPhone 8 Plus'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
processRunner.processToReturn = MockProcess.succeeding();
|
||||
processRunner.resultStdout = jsonEncode(devices);
|
||||
|
||||
expect(await xcode.findBestAvailableIphoneSimulator(), expectedDeviceId);
|
||||
});
|
||||
|
||||
test('ignores non-iOS runtimes', () async {
|
||||
// Note: This uses `dynamic` deliberately, and should not be updated to
|
||||
// Object, in order to ensure that the code correctly handles this return
|
||||
// type from JSON decoding.
|
||||
final Map<String, dynamic> devices = <String, dynamic>{
|
||||
'runtimes': <Map<String, dynamic>>[
|
||||
<String, dynamic>{
|
||||
'bundlePath':
|
||||
'/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime',
|
||||
'buildversion': '17T531',
|
||||
'runtimeRoot':
|
||||
'/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime/Contents/Resources/RuntimeRoot',
|
||||
'identifier': 'com.apple.CoreSimulator.SimRuntime.watchOS-6-2',
|
||||
'version': '6.2.1',
|
||||
'isAvailable': true,
|
||||
'name': 'watchOS 6.2'
|
||||
}
|
||||
],
|
||||
'devices': <String, dynamic>{
|
||||
'com.apple.CoreSimulator.SimRuntime.watchOS-6-2':
|
||||
<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.Apple-Watch-38mm',
|
||||
'state': 'Shutdown',
|
||||
'name': 'Apple Watch'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
processRunner.processToReturn = MockProcess.succeeding();
|
||||
processRunner.resultStdout = jsonEncode(devices);
|
||||
|
||||
expect(await xcode.findBestAvailableIphoneSimulator(), null);
|
||||
});
|
||||
|
||||
test('returns null if simctl fails', () async {
|
||||
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
|
||||
MockProcess.failing(),
|
||||
];
|
||||
|
||||
expect(await xcode.findBestAvailableIphoneSimulator(), null);
|
||||
});
|
||||
});
|
||||
|
||||
group('runXcodeBuild', () {
|
||||
test('handles minimal arguments', () async {
|
||||
final Directory directory = const LocalFileSystem().currentDirectory;
|
||||
|
||||
final int exitCode = await xcode.runXcodeBuild(
|
||||
directory,
|
||||
workspace: 'A.xcworkspace',
|
||||
scheme: 'AScheme',
|
||||
);
|
||||
|
||||
expect(exitCode, 0);
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall(
|
||||
'xcrun',
|
||||
const <String>[
|
||||
'xcodebuild',
|
||||
'build',
|
||||
'-workspace',
|
||||
'A.xcworkspace',
|
||||
'-scheme',
|
||||
'AScheme',
|
||||
],
|
||||
directory.path),
|
||||
]));
|
||||
});
|
||||
|
||||
test('handles all arguments', () async {
|
||||
final Directory directory = const LocalFileSystem().currentDirectory;
|
||||
|
||||
final int exitCode = await xcode.runXcodeBuild(directory,
|
||||
actions: <String>['action1', 'action2'],
|
||||
workspace: 'A.xcworkspace',
|
||||
scheme: 'AScheme',
|
||||
configuration: 'Debug',
|
||||
extraFlags: <String>['-a', '-b', 'c=d']);
|
||||
|
||||
expect(exitCode, 0);
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall(
|
||||
'xcrun',
|
||||
const <String>[
|
||||
'xcodebuild',
|
||||
'action1',
|
||||
'action2',
|
||||
'-workspace',
|
||||
'A.xcworkspace',
|
||||
'-scheme',
|
||||
'AScheme',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'-a',
|
||||
'-b',
|
||||
'c=d',
|
||||
],
|
||||
directory.path),
|
||||
]));
|
||||
});
|
||||
|
||||
test('returns error codes', () async {
|
||||
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
|
||||
MockProcess.failing(),
|
||||
];
|
||||
final Directory directory = const LocalFileSystem().currentDirectory;
|
||||
|
||||
final int exitCode = await xcode.runXcodeBuild(
|
||||
directory,
|
||||
workspace: 'A.xcworkspace',
|
||||
scheme: 'AScheme',
|
||||
);
|
||||
|
||||
expect(exitCode, 1);
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall(
|
||||
'xcrun',
|
||||
const <String>[
|
||||
'xcodebuild',
|
||||
'build',
|
||||
'-workspace',
|
||||
'A.xcworkspace',
|
||||
'-scheme',
|
||||
'AScheme',
|
||||
],
|
||||
directory.path),
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
group('projectHasTarget', () {
|
||||
test('returns true when present', () async {
|
||||
processRunner.processToReturn = MockProcess.succeeding();
|
||||
processRunner.resultStdout = '''
|
||||
{
|
||||
"project" : {
|
||||
"configurations" : [
|
||||
"Debug",
|
||||
"Release"
|
||||
],
|
||||
"name" : "Runner",
|
||||
"schemes" : [
|
||||
"Runner"
|
||||
],
|
||||
"targets" : [
|
||||
"Runner",
|
||||
"RunnerTests",
|
||||
"RunnerUITests"
|
||||
]
|
||||
}
|
||||
}''';
|
||||
|
||||
final Directory project =
|
||||
const LocalFileSystem().directory('/foo.xcodeproj');
|
||||
expect(await xcode.projectHasTarget(project, 'RunnerTests'), true);
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall(
|
||||
'xcrun',
|
||||
<String>[
|
||||
'xcodebuild',
|
||||
'-list',
|
||||
'-json',
|
||||
'-project',
|
||||
project.path,
|
||||
],
|
||||
null),
|
||||
]));
|
||||
});
|
||||
|
||||
test('returns false when not present', () async {
|
||||
processRunner.processToReturn = MockProcess.succeeding();
|
||||
processRunner.resultStdout = '''
|
||||
{
|
||||
"project" : {
|
||||
"configurations" : [
|
||||
"Debug",
|
||||
"Release"
|
||||
],
|
||||
"name" : "Runner",
|
||||
"schemes" : [
|
||||
"Runner"
|
||||
],
|
||||
"targets" : [
|
||||
"Runner",
|
||||
"RunnerUITests"
|
||||
]
|
||||
}
|
||||
}''';
|
||||
|
||||
final Directory project =
|
||||
const LocalFileSystem().directory('/foo.xcodeproj');
|
||||
expect(await xcode.projectHasTarget(project, 'RunnerTests'), false);
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall(
|
||||
'xcrun',
|
||||
<String>[
|
||||
'xcodebuild',
|
||||
'-list',
|
||||
'-json',
|
||||
'-project',
|
||||
project.path,
|
||||
],
|
||||
null),
|
||||
]));
|
||||
});
|
||||
|
||||
test('returns null for unexpected output', () async {
|
||||
processRunner.processToReturn = MockProcess.succeeding();
|
||||
processRunner.resultStdout = '{}';
|
||||
|
||||
final Directory project =
|
||||
const LocalFileSystem().directory('/foo.xcodeproj');
|
||||
expect(await xcode.projectHasTarget(project, 'RunnerTests'), null);
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall(
|
||||
'xcrun',
|
||||
<String>[
|
||||
'xcodebuild',
|
||||
'-list',
|
||||
'-json',
|
||||
'-project',
|
||||
project.path,
|
||||
],
|
||||
null),
|
||||
]));
|
||||
});
|
||||
|
||||
test('returns null for invalid output', () async {
|
||||
processRunner.processToReturn = MockProcess.succeeding();
|
||||
processRunner.resultStdout = ':)';
|
||||
|
||||
final Directory project =
|
||||
const LocalFileSystem().directory('/foo.xcodeproj');
|
||||
expect(await xcode.projectHasTarget(project, 'RunnerTests'), null);
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall(
|
||||
'xcrun',
|
||||
<String>[
|
||||
'xcodebuild',
|
||||
'-list',
|
||||
'-json',
|
||||
'-project',
|
||||
project.path,
|
||||
],
|
||||
null),
|
||||
]));
|
||||
});
|
||||
|
||||
test('returns null for failure', () async {
|
||||
processRunner.processToReturn = MockProcess.failing();
|
||||
|
||||
final Directory project =
|
||||
const LocalFileSystem().directory('/foo.xcodeproj');
|
||||
expect(await xcode.projectHasTarget(project, 'RunnerTests'), null);
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall(
|
||||
'xcrun',
|
||||
<String>[
|
||||
'xcodebuild',
|
||||
'-list',
|
||||
'-json',
|
||||
'-project',
|
||||
project.path,
|
||||
],
|
||||
null),
|
||||
]));
|
||||
});
|
||||
});
|
||||
}
|
416
script/tool/test/xcode_analyze_command_test.dart
Normal file
416
script/tool/test/xcode_analyze_command_test.dart
Normal file
@ -0,0 +1,416 @@
|
||||
// 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:io' as io;
|
||||
|
||||
import 'package:args/command_runner.dart';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_plugin_tools/src/common/core.dart';
|
||||
import 'package:flutter_plugin_tools/src/common/plugin_utils.dart';
|
||||
import 'package:flutter_plugin_tools/src/xcode_analyze_command.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'mocks.dart';
|
||||
import 'util.dart';
|
||||
|
||||
// TODO(stuartmorgan): Rework these tests to use a mock Xcode instead of
|
||||
// doing all the process mocking and validation.
|
||||
void main() {
|
||||
group('test xcode_analyze_command', () {
|
||||
late FileSystem fileSystem;
|
||||
late MockPlatform mockPlatform;
|
||||
late Directory packagesDir;
|
||||
late CommandRunner<void> runner;
|
||||
late RecordingProcessRunner processRunner;
|
||||
|
||||
setUp(() {
|
||||
fileSystem = MemoryFileSystem();
|
||||
mockPlatform = MockPlatform(isMacOS: true);
|
||||
packagesDir = createPackagesDirectory(fileSystem: fileSystem);
|
||||
processRunner = RecordingProcessRunner();
|
||||
final XcodeAnalyzeCommand command = XcodeAnalyzeCommand(packagesDir,
|
||||
processRunner: processRunner, platform: mockPlatform);
|
||||
|
||||
runner = CommandRunner<void>(
|
||||
'xcode_analyze_command', 'Test for xcode_analyze_command');
|
||||
runner.addCommand(command);
|
||||
});
|
||||
|
||||
test('Fails if no platforms are provided', () async {
|
||||
Error? commandError;
|
||||
final List<String> output = await runCapturingPrint(
|
||||
runner, <String>['xcode-analyze'], errorHandler: (Error e) {
|
||||
commandError = e;
|
||||
});
|
||||
|
||||
expect(commandError, isA<ToolExit>());
|
||||
expect(
|
||||
output,
|
||||
containsAllInOrder(<Matcher>[
|
||||
contains('At least one platform flag must be provided'),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
group('iOS', () {
|
||||
test('skip if iOS is not supported', () async {
|
||||
createFakePlugin('plugin', packagesDir,
|
||||
platformSupport: <String, PlatformSupport>{
|
||||
kPlatformMacos: PlatformSupport.inline,
|
||||
});
|
||||
|
||||
final List<String> output =
|
||||
await runCapturingPrint(runner, <String>['xcode-analyze', '--ios']);
|
||||
expect(output,
|
||||
contains(contains('Not implemented 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, PlatformSupport>{
|
||||
kPlatformIos: PlatformSupport.federated
|
||||
});
|
||||
|
||||
final List<String> output =
|
||||
await runCapturingPrint(runner, <String>['xcode-analyze', '--ios']);
|
||||
expect(output,
|
||||
contains(contains('Not implemented for target platform(s).')));
|
||||
expect(processRunner.recordedCalls, orderedEquals(<ProcessCall>[]));
|
||||
});
|
||||
|
||||
test('runs for iOS plugin', () async {
|
||||
final Directory pluginDirectory = createFakePlugin(
|
||||
'plugin', packagesDir, platformSupport: <String, PlatformSupport>{
|
||||
kPlatformIos: PlatformSupport.inline
|
||||
});
|
||||
|
||||
final Directory pluginExampleDirectory =
|
||||
pluginDirectory.childDirectory('example');
|
||||
|
||||
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||
'xcode-analyze',
|
||||
'--ios',
|
||||
]);
|
||||
|
||||
expect(
|
||||
output,
|
||||
containsAllInOrder(<Matcher>[
|
||||
contains('Running for plugin'),
|
||||
contains('plugin/example (iOS) passed analysis.')
|
||||
]));
|
||||
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall(
|
||||
'xcrun',
|
||||
const <String>[
|
||||
'xcodebuild',
|
||||
'analyze',
|
||||
'-workspace',
|
||||
'ios/Runner.xcworkspace',
|
||||
'-scheme',
|
||||
'Runner',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'-destination',
|
||||
'generic/platform=iOS Simulator',
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
],
|
||||
pluginExampleDirectory.path),
|
||||
]));
|
||||
});
|
||||
|
||||
test('fails if xcrun fails', () async {
|
||||
createFakePlugin('plugin', packagesDir,
|
||||
platformSupport: <String, PlatformSupport>{
|
||||
kPlatformIos: PlatformSupport.inline
|
||||
});
|
||||
|
||||
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
|
||||
MockProcess.failing()
|
||||
];
|
||||
|
||||
Error? commandError;
|
||||
final List<String> output = await runCapturingPrint(
|
||||
runner,
|
||||
<String>[
|
||||
'xcode-analyze',
|
||||
'--ios',
|
||||
],
|
||||
errorHandler: (Error e) {
|
||||
commandError = e;
|
||||
},
|
||||
);
|
||||
|
||||
expect(commandError, isA<ToolExit>());
|
||||
expect(
|
||||
output,
|
||||
containsAllInOrder(<Matcher>[
|
||||
contains('The following packages had errors:'),
|
||||
contains(' plugin'),
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
group('macOS', () {
|
||||
test('skip if macOS is not supported', () async {
|
||||
createFakePlugin(
|
||||
'plugin',
|
||||
packagesDir,
|
||||
);
|
||||
|
||||
final List<String> output = await runCapturingPrint(
|
||||
runner, <String>['xcode-analyze', '--macos']);
|
||||
expect(output,
|
||||
contains(contains('Not implemented 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, PlatformSupport>{
|
||||
kPlatformMacos: PlatformSupport.federated,
|
||||
});
|
||||
|
||||
final List<String> output = await runCapturingPrint(
|
||||
runner, <String>['xcode-analyze', '--macos']);
|
||||
expect(output,
|
||||
contains(contains('Not implemented for target platform(s).')));
|
||||
expect(processRunner.recordedCalls, orderedEquals(<ProcessCall>[]));
|
||||
});
|
||||
|
||||
test('runs for macOS plugin', () async {
|
||||
final Directory pluginDirectory1 = createFakePlugin(
|
||||
'plugin', packagesDir,
|
||||
platformSupport: <String, PlatformSupport>{
|
||||
kPlatformMacos: PlatformSupport.inline,
|
||||
});
|
||||
|
||||
final Directory pluginExampleDirectory =
|
||||
pluginDirectory1.childDirectory('example');
|
||||
|
||||
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||
'xcode-analyze',
|
||||
'--macos',
|
||||
]);
|
||||
|
||||
expect(output,
|
||||
contains(contains('plugin/example (macOS) passed analysis.')));
|
||||
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall(
|
||||
'xcrun',
|
||||
const <String>[
|
||||
'xcodebuild',
|
||||
'analyze',
|
||||
'-workspace',
|
||||
'macos/Runner.xcworkspace',
|
||||
'-scheme',
|
||||
'Runner',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
],
|
||||
pluginExampleDirectory.path),
|
||||
]));
|
||||
});
|
||||
|
||||
test('fails if xcrun fails', () async {
|
||||
createFakePlugin('plugin', packagesDir,
|
||||
platformSupport: <String, PlatformSupport>{
|
||||
kPlatformMacos: PlatformSupport.inline,
|
||||
});
|
||||
|
||||
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
|
||||
MockProcess.failing()
|
||||
];
|
||||
|
||||
Error? commandError;
|
||||
final List<String> output = await runCapturingPrint(
|
||||
runner, <String>['xcode-analyze', '--macos'],
|
||||
errorHandler: (Error e) {
|
||||
commandError = e;
|
||||
});
|
||||
|
||||
expect(commandError, isA<ToolExit>());
|
||||
expect(
|
||||
output,
|
||||
containsAllInOrder(<Matcher>[
|
||||
contains('The following packages had errors:'),
|
||||
contains(' plugin'),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('combined', () {
|
||||
test('runs both iOS and macOS when supported', () async {
|
||||
final Directory pluginDirectory1 = createFakePlugin(
|
||||
'plugin', packagesDir,
|
||||
platformSupport: <String, PlatformSupport>{
|
||||
kPlatformIos: PlatformSupport.inline,
|
||||
kPlatformMacos: PlatformSupport.inline,
|
||||
});
|
||||
|
||||
final Directory pluginExampleDirectory =
|
||||
pluginDirectory1.childDirectory('example');
|
||||
|
||||
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||
'xcode-analyze',
|
||||
'--ios',
|
||||
'--macos',
|
||||
]);
|
||||
|
||||
expect(
|
||||
output,
|
||||
containsAll(<Matcher>[
|
||||
contains('plugin/example (iOS) passed analysis.'),
|
||||
contains('plugin/example (macOS) passed analysis.'),
|
||||
]));
|
||||
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall(
|
||||
'xcrun',
|
||||
const <String>[
|
||||
'xcodebuild',
|
||||
'analyze',
|
||||
'-workspace',
|
||||
'ios/Runner.xcworkspace',
|
||||
'-scheme',
|
||||
'Runner',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'-destination',
|
||||
'generic/platform=iOS Simulator',
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
],
|
||||
pluginExampleDirectory.path),
|
||||
ProcessCall(
|
||||
'xcrun',
|
||||
const <String>[
|
||||
'xcodebuild',
|
||||
'analyze',
|
||||
'-workspace',
|
||||
'macos/Runner.xcworkspace',
|
||||
'-scheme',
|
||||
'Runner',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
],
|
||||
pluginExampleDirectory.path),
|
||||
]));
|
||||
});
|
||||
|
||||
test('runs only macOS for a macOS plugin', () async {
|
||||
final Directory pluginDirectory1 = createFakePlugin(
|
||||
'plugin', packagesDir,
|
||||
platformSupport: <String, PlatformSupport>{
|
||||
kPlatformMacos: PlatformSupport.inline,
|
||||
});
|
||||
|
||||
final Directory pluginExampleDirectory =
|
||||
pluginDirectory1.childDirectory('example');
|
||||
|
||||
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||
'xcode-analyze',
|
||||
'--ios',
|
||||
'--macos',
|
||||
]);
|
||||
|
||||
expect(
|
||||
output,
|
||||
containsAllInOrder(<Matcher>[
|
||||
contains('plugin/example (macOS) passed analysis.'),
|
||||
]));
|
||||
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall(
|
||||
'xcrun',
|
||||
const <String>[
|
||||
'xcodebuild',
|
||||
'analyze',
|
||||
'-workspace',
|
||||
'macos/Runner.xcworkspace',
|
||||
'-scheme',
|
||||
'Runner',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
],
|
||||
pluginExampleDirectory.path),
|
||||
]));
|
||||
});
|
||||
|
||||
test('runs only iOS for a iOS plugin', () async {
|
||||
final Directory pluginDirectory = createFakePlugin(
|
||||
'plugin', packagesDir, platformSupport: <String, PlatformSupport>{
|
||||
kPlatformIos: PlatformSupport.inline
|
||||
});
|
||||
|
||||
final Directory pluginExampleDirectory =
|
||||
pluginDirectory.childDirectory('example');
|
||||
|
||||
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||
'xcode-analyze',
|
||||
'--ios',
|
||||
'--macos',
|
||||
]);
|
||||
|
||||
expect(
|
||||
output,
|
||||
containsAllInOrder(
|
||||
<Matcher>[contains('plugin/example (iOS) passed analysis.')]));
|
||||
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall(
|
||||
'xcrun',
|
||||
const <String>[
|
||||
'xcodebuild',
|
||||
'analyze',
|
||||
'-workspace',
|
||||
'ios/Runner.xcworkspace',
|
||||
'-scheme',
|
||||
'Runner',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'-destination',
|
||||
'generic/platform=iOS Simulator',
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
],
|
||||
pluginExampleDirectory.path),
|
||||
]));
|
||||
});
|
||||
|
||||
test('skips when neither are supported', () async {
|
||||
createFakePlugin('plugin', packagesDir);
|
||||
|
||||
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||
'xcode-analyze',
|
||||
'--ios',
|
||||
'--macos',
|
||||
]);
|
||||
|
||||
expect(
|
||||
output,
|
||||
containsAllInOrder(<Matcher>[
|
||||
contains('SKIPPING: Not implemented for target platform(s).'),
|
||||
]));
|
||||
|
||||
expect(processRunner.recordedCalls, orderedEquals(<ProcessCall>[]));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -16,22 +16,8 @@ import 'package:test/test.dart';
|
||||
import 'mocks.dart';
|
||||
import 'util.dart';
|
||||
|
||||
// Note: This uses `dynamic` deliberately, and should not be updated to Object,
|
||||
// in order to ensure that the code correctly handles this return type from
|
||||
// JSON decoding.
|
||||
final Map<String, dynamic> _kDeviceListMap = <String, dynamic>{
|
||||
'runtimes': <Map<String, dynamic>>[
|
||||
<String, dynamic>{
|
||||
'bundlePath':
|
||||
'/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.0.simruntime',
|
||||
'buildversion': '17A577',
|
||||
'runtimeRoot':
|
||||
'/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.0.simruntime/Contents/Resources/RuntimeRoot',
|
||||
'identifier': 'com.apple.CoreSimulator.SimRuntime.iOS-13-0',
|
||||
'version': '13.0',
|
||||
'isAvailable': true,
|
||||
'name': 'iOS 13.0'
|
||||
},
|
||||
<String, dynamic>{
|
||||
'bundlePath':
|
||||
'/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.4.simruntime',
|
||||
@ -43,32 +29,9 @@ final Map<String, dynamic> _kDeviceListMap = <String, dynamic>{
|
||||
'isAvailable': true,
|
||||
'name': 'iOS 13.4'
|
||||
},
|
||||
<String, dynamic>{
|
||||
'bundlePath':
|
||||
'/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime',
|
||||
'buildversion': '17T531',
|
||||
'runtimeRoot':
|
||||
'/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime/Contents/Resources/RuntimeRoot',
|
||||
'identifier': 'com.apple.CoreSimulator.SimRuntime.watchOS-6-2',
|
||||
'version': '6.2.1',
|
||||
'isAvailable': true,
|
||||
'name': 'watchOS 6.2'
|
||||
}
|
||||
],
|
||||
'devices': <String, dynamic>{
|
||||
'com.apple.CoreSimulator.SimRuntime.iOS-13-4': <Map<String, dynamic>>[
|
||||
<String, dynamic>{
|
||||
'dataPath':
|
||||
'/Users/xxx/Library/Developer/CoreSimulator/Devices/2706BBEB-1E01-403E-A8E9-70E8E5A24774/data',
|
||||
'logPath':
|
||||
'/Users/xxx/Library/Logs/CoreSimulator/2706BBEB-1E01-403E-A8E9-70E8E5A24774',
|
||||
'udid': '2706BBEB-1E01-403E-A8E9-70E8E5A24774',
|
||||
'isAvailable': true,
|
||||
'deviceTypeIdentifier':
|
||||
'com.apple.CoreSimulator.SimDeviceType.iPhone-8',
|
||||
'state': 'Shutdown',
|
||||
'name': 'iPhone 8'
|
||||
},
|
||||
<String, dynamic>{
|
||||
'dataPath':
|
||||
'/Users/xxx/Library/Developer/CoreSimulator/Devices/1E76A0FD-38AC-4537-A989-EA639D7D012A/data',
|
||||
@ -85,6 +48,8 @@ final Map<String, dynamic> _kDeviceListMap = <String, dynamic>{
|
||||
}
|
||||
};
|
||||
|
||||
// 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';
|
||||
|
||||
@ -123,13 +88,198 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
test('allows target filtering', () async {
|
||||
final Directory pluginDirectory1 = createFakePlugin('plugin', packagesDir,
|
||||
platformSupport: <String, PlatformSupport>{
|
||||
kPlatformMacos: PlatformSupport.inline,
|
||||
});
|
||||
|
||||
final Directory pluginExampleDirectory =
|
||||
pluginDirectory1.childDirectory('example');
|
||||
|
||||
processRunner.processToReturn = MockProcess.succeeding();
|
||||
processRunner.resultStdout = '{"project":{"targets":["RunnerTests"]}}';
|
||||
|
||||
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||
'xctest',
|
||||
'--macos',
|
||||
'--test-target=RunnerTests',
|
||||
]);
|
||||
|
||||
expect(
|
||||
output,
|
||||
contains(
|
||||
contains('Successfully ran macOS xctest for plugin/example')));
|
||||
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall(
|
||||
'xcrun',
|
||||
<String>[
|
||||
'xcodebuild',
|
||||
'-list',
|
||||
'-json',
|
||||
'-project',
|
||||
pluginExampleDirectory
|
||||
.childDirectory('macos')
|
||||
.childDirectory('Runner.xcodeproj')
|
||||
.path,
|
||||
],
|
||||
null),
|
||||
ProcessCall(
|
||||
'xcrun',
|
||||
const <String>[
|
||||
'xcodebuild',
|
||||
'test',
|
||||
'-workspace',
|
||||
'macos/Runner.xcworkspace',
|
||||
'-scheme',
|
||||
'Runner',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'-only-testing:RunnerTests',
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
],
|
||||
pluginExampleDirectory.path),
|
||||
]));
|
||||
});
|
||||
|
||||
test('skips when the requested target is not present', () async {
|
||||
final Directory pluginDirectory1 = createFakePlugin('plugin', packagesDir,
|
||||
platformSupport: <String, PlatformSupport>{
|
||||
kPlatformMacos: PlatformSupport.inline,
|
||||
});
|
||||
|
||||
final Directory pluginExampleDirectory =
|
||||
pluginDirectory1.childDirectory('example');
|
||||
|
||||
processRunner.processToReturn = MockProcess.succeeding();
|
||||
processRunner.resultStdout = '{"project":{"targets":["Runner"]}}';
|
||||
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||
'xctest',
|
||||
'--macos',
|
||||
'--test-target=RunnerTests',
|
||||
]);
|
||||
|
||||
expect(
|
||||
output,
|
||||
containsAllInOrder(<Matcher>[
|
||||
contains('No "RunnerTests" target in plugin/example; skipping.'),
|
||||
]));
|
||||
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall(
|
||||
'xcrun',
|
||||
<String>[
|
||||
'xcodebuild',
|
||||
'-list',
|
||||
'-json',
|
||||
'-project',
|
||||
pluginExampleDirectory
|
||||
.childDirectory('macos')
|
||||
.childDirectory('Runner.xcodeproj')
|
||||
.path,
|
||||
],
|
||||
null),
|
||||
]));
|
||||
});
|
||||
|
||||
test('fails if unable to check for requested target', () async {
|
||||
final Directory pluginDirectory1 = createFakePlugin('plugin', packagesDir,
|
||||
platformSupport: <String, PlatformSupport>{
|
||||
kPlatformMacos: PlatformSupport.inline,
|
||||
});
|
||||
|
||||
final Directory pluginExampleDirectory =
|
||||
pluginDirectory1.childDirectory('example');
|
||||
|
||||
processRunner.processToReturn = MockProcess.failing();
|
||||
|
||||
Error? commandError;
|
||||
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||
'xctest',
|
||||
'--macos',
|
||||
'--test-target=RunnerTests',
|
||||
], 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>[
|
||||
ProcessCall(
|
||||
'xcrun',
|
||||
<String>[
|
||||
'xcodebuild',
|
||||
'-list',
|
||||
'-json',
|
||||
'-project',
|
||||
pluginExampleDirectory
|
||||
.childDirectory('macos')
|
||||
.childDirectory('Runner.xcodeproj')
|
||||
.path,
|
||||
],
|
||||
null),
|
||||
]));
|
||||
});
|
||||
|
||||
test('reports skips with no tests', () async {
|
||||
final Directory pluginDirectory1 = createFakePlugin('plugin', packagesDir,
|
||||
platformSupport: <String, PlatformSupport>{
|
||||
kPlatformMacos: PlatformSupport.inline,
|
||||
});
|
||||
|
||||
final Directory pluginExampleDirectory =
|
||||
pluginDirectory1.childDirectory('example');
|
||||
|
||||
// Exit code 66 from testing indicates no tests.
|
||||
final MockProcess noTestsProcessResult = MockProcess();
|
||||
noTestsProcessResult.exitCodeCompleter.complete(66);
|
||||
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
|
||||
noTestsProcessResult,
|
||||
];
|
||||
final List<String> output =
|
||||
await runCapturingPrint(runner, <String>['xctest', '--macos']);
|
||||
|
||||
expect(output, contains(contains('No tests found.')));
|
||||
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
ProcessCall(
|
||||
'xcrun',
|
||||
const <String>[
|
||||
'xcodebuild',
|
||||
'test',
|
||||
'-workspace',
|
||||
'macos/Runner.xcworkspace',
|
||||
'-scheme',
|
||||
'Runner',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
],
|
||||
pluginExampleDirectory.path),
|
||||
]));
|
||||
});
|
||||
|
||||
group('iOS', () {
|
||||
test('skip if iOS is not supported', () async {
|
||||
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
|
||||
'example/test',
|
||||
], platformSupport: <String, PlatformSupport>{
|
||||
kPlatformMacos: PlatformSupport.inline,
|
||||
});
|
||||
createFakePlugin('plugin', packagesDir,
|
||||
platformSupport: <String, PlatformSupport>{
|
||||
kPlatformMacos: PlatformSupport.inline,
|
||||
});
|
||||
|
||||
final List<String> output = await runCapturingPrint(runner,
|
||||
<String>['xctest', '--ios', _kDestination, 'foo_destination']);
|
||||
@ -141,11 +291,10 @@ void main() {
|
||||
});
|
||||
|
||||
test('skip if iOS is implemented in a federated package', () async {
|
||||
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
|
||||
'example/test',
|
||||
], platformSupport: <String, PlatformSupport>{
|
||||
kPlatformIos: PlatformSupport.federated
|
||||
});
|
||||
createFakePlugin('plugin', packagesDir,
|
||||
platformSupport: <String, PlatformSupport>{
|
||||
kPlatformIos: PlatformSupport.federated
|
||||
});
|
||||
|
||||
final List<String> output = await runCapturingPrint(runner,
|
||||
<String>['xctest', '--ios', _kDestination, 'foo_destination']);
|
||||
@ -157,19 +306,14 @@ void main() {
|
||||
});
|
||||
|
||||
test('running with correct destination', () async {
|
||||
final Directory pluginDirectory =
|
||||
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
|
||||
'example/test',
|
||||
], platformSupport: <String, PlatformSupport>{
|
||||
final Directory pluginDirectory = createFakePlugin(
|
||||
'plugin', packagesDir, platformSupport: <String, PlatformSupport>{
|
||||
kPlatformIos: PlatformSupport.inline
|
||||
});
|
||||
|
||||
final Directory pluginExampleDirectory =
|
||||
pluginDirectory.childDirectory('example');
|
||||
|
||||
processRunner.processToReturn = MockProcess.succeeding();
|
||||
processRunner.resultStdout =
|
||||
'{"project":{"targets":["bar_scheme", "foo_scheme"]}}';
|
||||
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||
'xctest',
|
||||
'--ios',
|
||||
@ -192,13 +336,12 @@ void main() {
|
||||
const <String>[
|
||||
'xcodebuild',
|
||||
'test',
|
||||
'analyze',
|
||||
'-workspace',
|
||||
'ios/Runner.xcworkspace',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'-scheme',
|
||||
'Runner',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'-destination',
|
||||
'foo_destination',
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
@ -209,45 +352,43 @@ void main() {
|
||||
|
||||
test('Not specifying --ios-destination assigns an available simulator',
|
||||
() async {
|
||||
final Directory pluginDirectory =
|
||||
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
|
||||
'example/test',
|
||||
], platformSupport: <String, PlatformSupport>{
|
||||
final Directory pluginDirectory = createFakePlugin(
|
||||
'plugin', packagesDir, platformSupport: <String, PlatformSupport>{
|
||||
kPlatformIos: PlatformSupport.inline
|
||||
});
|
||||
|
||||
final Directory pluginExampleDirectory =
|
||||
pluginDirectory.childDirectory('example');
|
||||
|
||||
final Map<String, dynamic> schemeCommandResult = <String, dynamic>{
|
||||
'project': <String, dynamic>{
|
||||
'targets': <String>['bar_scheme', 'foo_scheme']
|
||||
}
|
||||
};
|
||||
processRunner.processToReturn = MockProcess.succeeding();
|
||||
// For simplicity of the test, we combine all the mock results into a single mock result, each internal command
|
||||
// will get this result and they should still be able to parse them correctly.
|
||||
processRunner.resultStdout =
|
||||
jsonEncode(schemeCommandResult..addAll(_kDeviceListMap));
|
||||
processRunner.resultStdout = jsonEncode(_kDeviceListMap);
|
||||
await runCapturingPrint(runner, <String>['xctest', '--ios']);
|
||||
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
const ProcessCall(
|
||||
'xcrun', <String>['simctl', 'list', '--json'], null),
|
||||
'xcrun',
|
||||
<String>[
|
||||
'simctl',
|
||||
'list',
|
||||
'devices',
|
||||
'runtimes',
|
||||
'available',
|
||||
'--json',
|
||||
],
|
||||
null),
|
||||
ProcessCall(
|
||||
'xcrun',
|
||||
const <String>[
|
||||
'xcodebuild',
|
||||
'test',
|
||||
'analyze',
|
||||
'-workspace',
|
||||
'ios/Runner.xcworkspace',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'-scheme',
|
||||
'Runner',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'-destination',
|
||||
'id=1E76A0FD-38AC-4537-A989-EA639D7D012A',
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
@ -257,15 +398,11 @@ void main() {
|
||||
});
|
||||
|
||||
test('fails if xcrun fails', () async {
|
||||
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
|
||||
'example/test',
|
||||
], platformSupport: <String, PlatformSupport>{
|
||||
kPlatformIos: PlatformSupport.inline
|
||||
});
|
||||
createFakePlugin('plugin', packagesDir,
|
||||
platformSupport: <String, PlatformSupport>{
|
||||
kPlatformIos: PlatformSupport.inline
|
||||
});
|
||||
|
||||
processRunner.processToReturn = MockProcess.succeeding();
|
||||
processRunner.resultStdout =
|
||||
'{"project":{"targets":["bar_scheme", "foo_scheme"]}}';
|
||||
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
|
||||
MockProcess.failing()
|
||||
];
|
||||
@ -288,7 +425,7 @@ void main() {
|
||||
expect(
|
||||
output,
|
||||
containsAllInOrder(<Matcher>[
|
||||
contains('The following packages are failing XCTests:'),
|
||||
contains('The following packages had errors:'),
|
||||
contains(' plugin'),
|
||||
]));
|
||||
});
|
||||
@ -296,16 +433,10 @@ void main() {
|
||||
|
||||
group('macOS', () {
|
||||
test('skip if macOS is not supported', () async {
|
||||
createFakePlugin(
|
||||
'plugin',
|
||||
packagesDir,
|
||||
extraFiles: <String>[
|
||||
'example/test',
|
||||
],
|
||||
);
|
||||
createFakePlugin('plugin', packagesDir);
|
||||
|
||||
final List<String> output = await runCapturingPrint(runner,
|
||||
<String>['xctest', '--macos', _kDestination, 'foo_destination']);
|
||||
final List<String> output =
|
||||
await runCapturingPrint(runner, <String>['xctest', '--macos']);
|
||||
expect(
|
||||
output,
|
||||
contains(
|
||||
@ -314,14 +445,13 @@ void main() {
|
||||
});
|
||||
|
||||
test('skip if macOS is implemented in a federated package', () async {
|
||||
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
|
||||
'example/test',
|
||||
], platformSupport: <String, PlatformSupport>{
|
||||
kPlatformMacos: PlatformSupport.federated,
|
||||
});
|
||||
createFakePlugin('plugin', packagesDir,
|
||||
platformSupport: <String, PlatformSupport>{
|
||||
kPlatformMacos: PlatformSupport.federated,
|
||||
});
|
||||
|
||||
final List<String> output = await runCapturingPrint(runner,
|
||||
<String>['xctest', '--macos', _kDestination, 'foo_destination']);
|
||||
final List<String> output =
|
||||
await runCapturingPrint(runner, <String>['xctest', '--macos']);
|
||||
expect(
|
||||
output,
|
||||
contains(
|
||||
@ -330,19 +460,15 @@ void main() {
|
||||
});
|
||||
|
||||
test('runs for macOS plugin', () async {
|
||||
final Directory pluginDirectory1 =
|
||||
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
|
||||
'example/test',
|
||||
], platformSupport: <String, PlatformSupport>{
|
||||
kPlatformMacos: PlatformSupport.inline,
|
||||
});
|
||||
final Directory pluginDirectory1 = createFakePlugin(
|
||||
'plugin', packagesDir,
|
||||
platformSupport: <String, PlatformSupport>{
|
||||
kPlatformMacos: PlatformSupport.inline,
|
||||
});
|
||||
|
||||
final Directory pluginExampleDirectory =
|
||||
pluginDirectory1.childDirectory('example');
|
||||
|
||||
processRunner.processToReturn = MockProcess.succeeding();
|
||||
processRunner.resultStdout =
|
||||
'{"project":{"targets":["bar_scheme", "foo_scheme"]}}';
|
||||
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||
'xctest',
|
||||
'--macos',
|
||||
@ -361,13 +487,12 @@ void main() {
|
||||
const <String>[
|
||||
'xcodebuild',
|
||||
'test',
|
||||
'analyze',
|
||||
'-workspace',
|
||||
'macos/Runner.xcworkspace',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'-scheme',
|
||||
'Runner',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
],
|
||||
pluginExampleDirectory.path),
|
||||
@ -375,15 +500,11 @@ void main() {
|
||||
});
|
||||
|
||||
test('fails if xcrun fails', () async {
|
||||
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
|
||||
'example/test',
|
||||
], platformSupport: <String, PlatformSupport>{
|
||||
kPlatformMacos: PlatformSupport.inline,
|
||||
});
|
||||
createFakePlugin('plugin', packagesDir,
|
||||
platformSupport: <String, PlatformSupport>{
|
||||
kPlatformMacos: PlatformSupport.inline,
|
||||
});
|
||||
|
||||
processRunner.processToReturn = MockProcess.succeeding();
|
||||
processRunner.resultStdout =
|
||||
'{"project":{"targets":["bar_scheme", "foo_scheme"]}}';
|
||||
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
|
||||
MockProcess.failing()
|
||||
];
|
||||
@ -398,7 +519,7 @@ void main() {
|
||||
expect(
|
||||
output,
|
||||
containsAllInOrder(<Matcher>[
|
||||
contains('The following packages are failing XCTests:'),
|
||||
contains('The following packages had errors:'),
|
||||
contains(' plugin'),
|
||||
]),
|
||||
);
|
||||
@ -407,20 +528,16 @@ void main() {
|
||||
|
||||
group('combined', () {
|
||||
test('runs both iOS and macOS when supported', () async {
|
||||
final Directory pluginDirectory1 =
|
||||
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
|
||||
'example/test',
|
||||
], platformSupport: <String, PlatformSupport>{
|
||||
kPlatformIos: PlatformSupport.inline,
|
||||
kPlatformMacos: PlatformSupport.inline,
|
||||
});
|
||||
final Directory pluginDirectory1 = createFakePlugin(
|
||||
'plugin', packagesDir,
|
||||
platformSupport: <String, PlatformSupport>{
|
||||
kPlatformIos: PlatformSupport.inline,
|
||||
kPlatformMacos: PlatformSupport.inline,
|
||||
});
|
||||
|
||||
final Directory pluginExampleDirectory =
|
||||
pluginDirectory1.childDirectory('example');
|
||||
|
||||
processRunner.processToReturn = MockProcess.succeeding();
|
||||
processRunner.resultStdout =
|
||||
'{"project":{"targets":["bar_scheme", "foo_scheme"]}}';
|
||||
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||
'xctest',
|
||||
'--ios',
|
||||
@ -444,13 +561,12 @@ void main() {
|
||||
const <String>[
|
||||
'xcodebuild',
|
||||
'test',
|
||||
'analyze',
|
||||
'-workspace',
|
||||
'ios/Runner.xcworkspace',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'-scheme',
|
||||
'Runner',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'-destination',
|
||||
'foo_destination',
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
@ -461,13 +577,12 @@ void main() {
|
||||
const <String>[
|
||||
'xcodebuild',
|
||||
'test',
|
||||
'analyze',
|
||||
'-workspace',
|
||||
'macos/Runner.xcworkspace',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'-scheme',
|
||||
'Runner',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
],
|
||||
pluginExampleDirectory.path),
|
||||
@ -475,19 +590,15 @@ void main() {
|
||||
});
|
||||
|
||||
test('runs only macOS for a macOS plugin', () async {
|
||||
final Directory pluginDirectory1 =
|
||||
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
|
||||
'example/test',
|
||||
], platformSupport: <String, PlatformSupport>{
|
||||
kPlatformMacos: PlatformSupport.inline,
|
||||
});
|
||||
final Directory pluginDirectory1 = createFakePlugin(
|
||||
'plugin', packagesDir,
|
||||
platformSupport: <String, PlatformSupport>{
|
||||
kPlatformMacos: PlatformSupport.inline,
|
||||
});
|
||||
|
||||
final Directory pluginExampleDirectory =
|
||||
pluginDirectory1.childDirectory('example');
|
||||
|
||||
processRunner.processToReturn = MockProcess.succeeding();
|
||||
processRunner.resultStdout =
|
||||
'{"project":{"targets":["bar_scheme", "foo_scheme"]}}';
|
||||
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||
'xctest',
|
||||
'--ios',
|
||||
@ -511,13 +622,12 @@ void main() {
|
||||
const <String>[
|
||||
'xcodebuild',
|
||||
'test',
|
||||
'analyze',
|
||||
'-workspace',
|
||||
'macos/Runner.xcworkspace',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'-scheme',
|
||||
'Runner',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
],
|
||||
pluginExampleDirectory.path),
|
||||
@ -525,19 +635,14 @@ void main() {
|
||||
});
|
||||
|
||||
test('runs only iOS for a iOS plugin', () async {
|
||||
final Directory pluginDirectory =
|
||||
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
|
||||
'example/test',
|
||||
], platformSupport: <String, PlatformSupport>{
|
||||
final Directory pluginDirectory = createFakePlugin(
|
||||
'plugin', packagesDir, platformSupport: <String, PlatformSupport>{
|
||||
kPlatformIos: PlatformSupport.inline
|
||||
});
|
||||
|
||||
final Directory pluginExampleDirectory =
|
||||
pluginDirectory.childDirectory('example');
|
||||
|
||||
processRunner.processToReturn = MockProcess.succeeding();
|
||||
processRunner.resultStdout =
|
||||
'{"project":{"targets":["bar_scheme", "foo_scheme"]}}';
|
||||
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||
'xctest',
|
||||
'--ios',
|
||||
@ -561,13 +666,12 @@ void main() {
|
||||
const <String>[
|
||||
'xcodebuild',
|
||||
'test',
|
||||
'analyze',
|
||||
'-workspace',
|
||||
'ios/Runner.xcworkspace',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'-scheme',
|
||||
'Runner',
|
||||
'-configuration',
|
||||
'Debug',
|
||||
'-destination',
|
||||
'foo_destination',
|
||||
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
|
||||
@ -577,13 +681,8 @@ void main() {
|
||||
});
|
||||
|
||||
test('skips when neither are supported', () async {
|
||||
createFakePlugin('plugin', packagesDir, extraFiles: <String>[
|
||||
'example/test',
|
||||
]);
|
||||
createFakePlugin('plugin', packagesDir);
|
||||
|
||||
processRunner.processToReturn = MockProcess.succeeding();
|
||||
processRunner.resultStdout =
|
||||
'{"project":{"targets":["bar_scheme", "foo_scheme"]}}';
|
||||
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||
'xctest',
|
||||
'--ios',
|
||||
|
Reference in New Issue
Block a user