Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Prev Previous commit
Next Next commit
Skip, rather than failing, when a package doesn't have a requested te…
…st-target
  • Loading branch information
stuartmorgan-g committed Jul 16, 2021
commit 02945c9d6657abc017a548d07d158f95a325afc0
36 changes: 33 additions & 3 deletions script/tool/lib/src/common/xcode.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,52 @@ class Xcode {
String? configuration,
List<String> extraFlags = const <String>[],
}) {
final List<String> xctestArgs = <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 ${xctestArgs.join(' ')}';
final String completeTestCommand = '$_xcRunCommand ${args.join(' ')}';
if (log) {
print(completeTestCommand);
}
return processRunner.runAndStream(_xcRunCommand, xctestArgs,
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 {
Expand Down
19 changes: 18 additions & 1 deletion script/tool/lib/src/xctest_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,24 @@ class XCTestCommand extends PackageLoopingCommand {
for (final Directory example in getExamplesForPlugin(plugin)) {
final String examplePath =
getRelativePosixPath(example, from: plugin.parent);
print('Running $platform tests and analyzer for $examplePath...');

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;
}
}

print('Running $platform tests for $examplePath...');
final int exitCode = await _xcode.runXcodeBuild(
example,
actions: <String>['test'],
Expand Down
149 changes: 149 additions & 0 deletions script/tool/test/common/xcode_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -321,4 +321,153 @@ void main() {
]));
});
});

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),
]));
});
});
}
105 changes: 105 additions & 0 deletions script/tool/test/xctest_command_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ void main() {
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',
Expand All @@ -111,6 +114,19 @@ void main() {
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall(
'xcrun',
<String>[
'xcodebuild',
'-list',
'-json',
'-project',
pluginExampleDirectory
.childDirectory('macos')
.childDirectory('Runner.xcodeproj')
.path,
],
null),
ProcessCall(
'xcrun',
const <String>[
Expand All @@ -129,6 +145,95 @@ void main() {
]));
});

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>{
Expand Down