forked from flutter-team-archive/plugins
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathxctest_command.dart
More file actions
184 lines (170 loc) · 6.38 KB
/
Copy pathxctest_command.dart
File metadata and controls
184 lines (170 loc) · 6.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// Copyright 2017 The Chromium 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:async';
import 'dart:convert';
import 'dart:io' as io;
import 'package:file/file.dart';
import 'package:path/path.dart' as p;
import 'common.dart';
const String _kiOSDestination = 'ios-destination';
const String _kSkip = 'skip';
const String _kXcodeBuildCommand = 'xcodebuild';
const String _kXCRunCommand = 'xcrun';
const String _kFoundNoSimulatorsMessage =
'Cannot find any available simulators, tests failed';
/// The command to run iOS XCTests in plugins, this should work for both XCUnitTest and XCUITest targets.
/// The tests target have to be added to the xcode project of the example app. Usually at "example/ios/Runner.xcworkspace".
/// The static analyzer is also run.
class XCTestCommand extends PluginCommand {
XCTestCommand(
Directory packagesDir,
FileSystem fileSystem, {
ProcessRunner processRunner = const ProcessRunner(),
}) : super(packagesDir, fileSystem, processRunner: processRunner) {
argParser.addOption(
_kiOSDestination,
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.addMultiOption(_kSkip,
help: 'Plugins to skip while running this command. \n');
}
@override
final String name = 'xctest';
@override
final String description = 'Runs the xctests in the iOS example apps.\n\n'
'This command requires "flutter" and "xcrun" to be in your path.';
@override
Future<Null> run() async {
String destination = argResults[_kiOSDestination];
if (destination == null) {
String simulatorId = await _findAvailableIphoneSimulator();
if (simulatorId == null) {
print(_kFoundNoSimulatorsMessage);
throw ToolExit(1);
}
destination = 'id=$simulatorId';
}
checkSharding();
final List<String> skipped = argResults[_kSkip];
List<String> failingPackages = <String>[];
await for (Directory plugin in getPlugins()) {
// Start running for package.
final String packageName =
p.relative(plugin.path, from: packagesDir.path);
print('Start running for $packageName ...');
if (!isIosPlugin(plugin, fileSystem)) {
print('iOS is not supported by this plugin.');
print('\n\n');
continue;
}
if (skipped.contains(packageName)) {
print('$packageName was skipped with the --skip flag.');
print('\n\n');
continue;
}
for (Directory example in getExamplesForPlugin(plugin)) {
// Running tests and static analyzer.
print('Running tests and analyzer for $packageName ...');
int exitCode = await _runTests(true, destination, example);
// 66 = there is no test target (this fails fast). Try again with just the analyzer.
if (exitCode == 66) {
print('Tests not found for $packageName, running analyzer only...');
exitCode = await _runTests(false, destination, example);
}
if (exitCode == 0) {
print('Successfully ran xctest for $packageName');
} else {
failingPackages.add(packageName);
}
}
}
// Command end, print reports.
if (failingPackages.isEmpty) {
print("All XCTests have passed!");
} else {
print(
'The following packages are failing XCTests (see above for details):');
for (String package in failingPackages) {
print(' * $package');
}
throw ToolExit(1);
}
}
Future<int> _runTests(bool runTests, String destination, Directory example) {
final List<String> xctestArgs = <String>[
_kXcodeBuildCommand,
if (runTests)
'test',
'analyze',
'-workspace',
'ios/Runner.xcworkspace',
'-configuration',
'Debug',
'-scheme',
'Runner',
'-destination',
destination,
'CODE_SIGN_IDENTITY=""',
'CODE_SIGNING_REQUIRED=NO',
'GCC_TREAT_WARNINGS_AS_ERRORS=YES',
];
final String completeTestCommand =
'$_kXCRunCommand ${xctestArgs.join(' ')}';
print(completeTestCommand);
return processRunner
.runAndStream(_kXCRunCommand, xctestArgs, workingDir: example, exitOnError: false);
}
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) {
print('Error occurred while running "$findSimulatorCompleteCommand":\n'
'${findSimulatorsResult.stderr}');
throw ToolExit(1);
}
final Map<String, dynamic> simulatorListJson =
jsonDecode(findSimulatorsResult.stdout);
final List<dynamic> runtimes = simulatorListJson['runtimes'];
final Map<String, dynamic> devices = simulatorListJson['devices'];
if (runtimes.isEmpty || devices.isEmpty) {
return null;
}
String id;
// Looking for runtimes, trying to find one with highest OS version.
for (Map<String, dynamic> runtimeMap in runtimes.reversed) {
if (!runtimeMap['name'].contains('iOS')) {
continue;
}
final String runtimeID = runtimeMap['identifier'];
final List<dynamic> devicesForRuntime = devices[runtimeID];
if (devicesForRuntime.isEmpty) {
continue;
}
// Looking for runtimes, trying to find latest version of device.
for (Map<String, dynamic> device in devicesForRuntime.reversed) {
if (device['availabilityError'] != null ||
(device['isAvailable'] as bool == false)) {
continue;
}
id = device['udid'];
print('device selected: $device');
return id;
}
}
return null;
}
}