Unverified Commit 167655e8 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Only show iOS simulators, reduce output spew in verbose (#108345)

parent fe16c206
...@@ -25,7 +25,7 @@ class IOSWorkflow implements Workflow { ...@@ -25,7 +25,7 @@ class IOSWorkflow implements Workflow {
// We need xcode (+simctl) to list simulator devices, and libimobiledevice to list real devices. // We need xcode (+simctl) to list simulator devices, and libimobiledevice to list real devices.
@override @override
bool get canListDevices => appliesToHostPlatform && _xcode.isInstalledAndMeetsVersionCheck && _xcode.isSimctlInstalled; bool get canListDevices => appliesToHostPlatform && _xcode.isSimctlInstalled;
// We need xcode to launch simulator devices, and ios-deploy // We need xcode to launch simulator devices, and ios-deploy
// for real devices. // for real devices.
......
...@@ -71,8 +71,8 @@ class IOSSimulatorUtils { ...@@ -71,8 +71,8 @@ class IOSSimulatorUtils {
return <IOSSimulator>[]; return <IOSSimulator>[];
} }
final List<SimDevice> connected = await _simControl.getConnectedDevices(); final List<BootedSimDevice> connected = await _simControl.getConnectedDevices();
return connected.map<IOSSimulator?>((SimDevice device) { return connected.map<IOSSimulator?>((BootedSimDevice device) {
final String? udid = device.udid; final String? udid = device.udid;
final String? name = device.name; final String? name = device.name;
if (udid == null) { if (udid == null) {
...@@ -109,30 +109,45 @@ class SimControl { ...@@ -109,30 +109,45 @@ class SimControl {
/// Runs `simctl list --json` and returns the JSON of the corresponding /// Runs `simctl list --json` and returns the JSON of the corresponding
/// [section]. /// [section].
Future<Map<String, Object?>> _list(SimControlListSection section) async { Future<Map<String, Object?>> _listBootedDevices() async {
// Sample output from `simctl list --json`: // Sample output from `simctl list available booted --json`:
// //
// { // {
// "devicetypes": { ... },
// "runtimes": { ... },
// "devices" : { // "devices" : {
// "com.apple.CoreSimulator.SimRuntime.iOS-8-2" : [ // "com.apple.CoreSimulator.SimRuntime.iOS-14-0" : [
// { // {
// "state" : "Shutdown", // "lastBootedAt" : "2022-07-26T01:46:23Z",
// "availability" : " (unavailable, runtime profile not found)", // "dataPath" : "\/Users\/magder\/Library\/Developer\/CoreSimulator\/Devices\/9EC90A99-6924-472D-8CDD-4D8234AB4779\/data",
// "name" : "iPhone 4s", // "dataPathSize" : 1620578304,
// "udid" : "1913014C-6DCB-485D-AC6B-7CD76D322F5B" // "logPath" : "\/Users\/magder\/Library\/Logs\/CoreSimulator\/9EC90A99-6924-472D-8CDD-4D8234AB4779",
// }, // "udid" : "9EC90A99-6924-472D-8CDD-4D8234AB4779",
// ... // "isAvailable" : true,
// }, // "logPathSize" : 9740288,
// "pairs": { ... }, // "deviceTypeIdentifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-11",
// "state" : "Booted",
// "name" : "iPhone 11"
// }
// ],
// "com.apple.CoreSimulator.SimRuntime.iOS-13-0" : [
//
// ],
// "com.apple.CoreSimulator.SimRuntime.iOS-12-4" : [
//
// ],
// "com.apple.CoreSimulator.SimRuntime.iOS-16-0" : [
//
// ]
// }
// }
final List<String> command = <String>[ final List<String> command = <String>[
..._xcode.xcrunCommand(), ..._xcode.xcrunCommand(),
'simctl', 'simctl',
'list', 'list',
'devices',
'booted',
'iOS',
'--json', '--json',
section.name,
]; ];
_logger.printTrace(command.join(' ')); _logger.printTrace(command.join(' '));
final RunResult results = await _processUtils.run(command); final RunResult results = await _processUtils.run(command);
...@@ -141,7 +156,7 @@ class SimControl { ...@@ -141,7 +156,7 @@ class SimControl {
return <String, Map<String, Object?>>{}; return <String, Map<String, Object?>>{};
} }
try { try {
final Object? decodeResult = (json.decode(results.stdout) as Map<String, Object?>)[section.name]; final Object? decodeResult = (json.decode(results.stdout) as Map<String, Object?>)['devices'];
if (decodeResult is Map<String, Object?>) { if (decodeResult is Map<String, Object?>) {
return decodeResult; return decodeResult;
} }
...@@ -156,17 +171,17 @@ class SimControl { ...@@ -156,17 +171,17 @@ class SimControl {
} }
} }
/// Returns a list of all available devices, both potential and connected. /// Returns all the connected simulator devices.
Future<List<SimDevice>> getDevices() async { Future<List<BootedSimDevice>> getConnectedDevices() async {
final List<SimDevice> devices = <SimDevice>[]; final List<BootedSimDevice> devices = <BootedSimDevice>[];
final Map<String, Object?> devicesSection = await _list(SimControlListSection.devices); final Map<String, Object?> devicesSection = await _listBootedDevices();
for (final String deviceCategory in devicesSection.keys) { for (final String deviceCategory in devicesSection.keys) {
final Object? devicesData = devicesSection[deviceCategory]; final Object? devicesData = devicesSection[deviceCategory];
if (devicesData != null && devicesData is List<Object?>) { if (devicesData != null && devicesData is List<Object?>) {
for (final Map<String, Object?> data in devicesData.map<Map<String, Object?>?>(castStringKeyedMap).whereType<Map<String, Object?>>()) { for (final Map<String, Object?> data in devicesData.map<Map<String, Object?>?>(castStringKeyedMap).whereType<Map<String, Object?>>()) {
devices.add(SimDevice(deviceCategory, data)); devices.add(BootedSimDevice(deviceCategory, data));
} }
} }
} }
...@@ -174,12 +189,6 @@ class SimControl { ...@@ -174,12 +189,6 @@ class SimControl {
return devices; return devices;
} }
/// Returns all the connected simulator devices.
Future<List<SimDevice>> getConnectedDevices() async {
final List<SimDevice> simDevices = await getDevices();
return simDevices.where((SimDevice device) => device.isBooted).toList();
}
Future<bool> isInstalled(String deviceId, String appId) { Future<bool> isInstalled(String deviceId, String appId) {
return _processUtils.exitsHappy(<String>[ return _processUtils.exitsHappy(<String>[
..._xcode.xcrunCommand(), ..._xcode.xcrunCommand(),
...@@ -267,54 +276,15 @@ class SimControl { ...@@ -267,54 +276,15 @@ class SimControl {
} }
} }
/// Enumerates all data sections of `xcrun simctl list --json` command.
class SimControlListSection {
const SimControlListSection._(this.name);
final String name;
static const SimControlListSection devices = SimControlListSection._('devices');
static const SimControlListSection devicetypes = SimControlListSection._('devicetypes');
static const SimControlListSection runtimes = SimControlListSection._('runtimes');
static const SimControlListSection pairs = SimControlListSection._('pairs');
}
/// A simulated device type.
///
/// Simulated device types can be listed using the command
/// `xcrun simctl list devicetypes`.
class SimDeviceType {
SimDeviceType(this.name, this.identifier);
/// The name of the device type.
///
/// Examples:
///
/// "iPhone 6s"
/// "iPhone 6 Plus"
final String name;
/// The identifier of the device type.
///
/// Examples:
///
/// "com.apple.CoreSimulator.SimDeviceType.iPhone-6s"
/// "com.apple.CoreSimulator.SimDeviceType.iPhone-6-Plus"
final String identifier;
}
class SimDevice { class BootedSimDevice {
SimDevice(this.category, this.data); BootedSimDevice(this.category, this.data);
final String category; final String category;
final Map<String, Object?> data; final Map<String, Object?> data;
String? get state => data['state']?.toString();
String? get availability => data['availability']?.toString();
String? get name => data['name']?.toString(); String? get name => data['name']?.toString();
String? get udid => data['udid']?.toString(); String? get udid => data['udid']?.toString();
bool get isBooted => state == 'Booted';
} }
class IOSSimulator extends Device { class IOSSimulator extends Device {
......
...@@ -136,7 +136,7 @@ class Xcode { ...@@ -136,7 +136,7 @@ class Xcode {
// This command will error if additional components need to be installed in // This command will error if additional components need to be installed in
// xcode 9.2 and above. // xcode 9.2 and above.
final RunResult result = _processUtils.runSync( final RunResult result = _processUtils.runSync(
<String>[...xcrunCommand(), 'simctl', 'list'], <String>[...xcrunCommand(), 'simctl', 'list', 'devices', 'booted'],
); );
_isSimctlInstalled = result.exitCode == 0; _isSimctlInstalled = result.exitCode == 0;
} on ProcessException { } on ProcessException {
......
...@@ -49,10 +49,18 @@ void main() { ...@@ -49,10 +49,18 @@ void main() {
expect(iosWorkflow.canListDevices, false); expect(iosWorkflow.canListDevices, false);
}); });
testWithoutContext('iOS workflow applies on macOS, no Xcode', () { testWithoutContext('iOS workflow applies on macOS, no Xcode or simctl', () {
final FakeProcessManager xcodeProcessManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>[
'xcrun', 'simctl', 'list', 'devices', 'booted',
],
exitCode: 1,
),
]);
final IOSWorkflow iosWorkflow = IOSWorkflow( final IOSWorkflow iosWorkflow = IOSWorkflow(
platform: FakePlatform(operatingSystem: 'macos'), platform: FakePlatform(operatingSystem: 'macos'),
xcode: Xcode.test(processManager: FakeProcessManager.any(), xcode: Xcode.test(processManager: xcodeProcessManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test( xcodeProjectInterpreter: XcodeProjectInterpreter.test(
processManager: FakeProcessManager.any(), processManager: FakeProcessManager.any(),
version: null, version: null,
...@@ -65,9 +73,33 @@ void main() { ...@@ -65,9 +73,33 @@ void main() {
expect(iosWorkflow.canLaunchDevices, false); expect(iosWorkflow.canLaunchDevices, false);
expect(iosWorkflow.canListDevices, false); expect(iosWorkflow.canListDevices, false);
expect(iosWorkflow.canListEmulators, false); expect(iosWorkflow.canListEmulators, false);
expect(xcodeProcessManager, hasNoRemainingExpectations);
});
testWithoutContext('iOS workflow can list devices even when Xcode version is too low', () {
final Xcode xcode = Xcode.test(
processManager: FakeProcessManager.any(),
xcodeProjectInterpreter: XcodeProjectInterpreter.test(
processManager: FakeProcessManager.any(),
version: Version(1, 0, 0)
),
);
final IOSWorkflow iosWorkflow = IOSWorkflow(
platform: FakePlatform(operatingSystem: 'macos'),
xcode: xcode,
featureFlags: TestFeatureFlags(),
);
// Make sure we're testing the right Xcode state.
// expect(xcode.isInstalledAndMeetsVersionCheck, true);
expect(xcode.isSimctlInstalled, true);
expect(iosWorkflow.canLaunchDevices, false);
expect(iosWorkflow.canListDevices, true);
expect(iosWorkflow.canListEmulators, false);
}); });
testWithoutContext('iOS workflow can launch and list devices when Xcode is set up', () { testWithoutContext('iOS workflow can launch devices when Xcode is set up', () {
final Xcode xcode = Xcode.test( final Xcode xcode = Xcode.test(
processManager: FakeProcessManager.any(), processManager: FakeProcessManager.any(),
xcodeProjectInterpreter: XcodeProjectInterpreter.test( xcodeProjectInterpreter: XcodeProjectInterpreter.test(
......
...@@ -724,28 +724,43 @@ Dec 20 17:04:32 md32-11-vm1 Another App[88374]: Ignore this text''' ...@@ -724,28 +724,43 @@ Dec 20 17:04:32 md32-11-vm1 Another App[88374]: Ignore this text'''
const String validSimControlOutput = ''' const String validSimControlOutput = '''
{ {
"devices" : { "devices" : {
"watchOS 4.3" : [ "com.apple.CoreSimulator.SimRuntime.iOS-14-0" : [
{ {
"state" : "Shutdown", "dataPathSize" : 1734569984,
"availability" : "(available)", "udid" : "iPhone 11-UDID",
"name" : "Apple Watch - 38mm", "isAvailable" : true,
"udid" : "TEST-WATCH-UDID" "logPathSize" : 9506816,
"deviceTypeIdentifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-11",
"state" : "Booted",
"name" : "iPhone 11"
} }
], ],
"iOS 11.4" : [ "com.apple.CoreSimulator.SimRuntime.iOS-13-0" : [
],
"com.apple.CoreSimulator.SimRuntime.iOS-12-4" : [
],
"com.apple.CoreSimulator.SimRuntime.tvOS-16-0" : [
],
"com.apple.CoreSimulator.SimRuntime.watchOS-9-0" : [
],
"com.apple.CoreSimulator.SimRuntime.iOS-16-0" : [
{ {
"dataPathSize" : 552366080,
"udid" : "Phone w Watch-UDID",
"isAvailable" : true,
"logPathSize" : 90112,
"deviceTypeIdentifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-11",
"state" : "Booted", "state" : "Booted",
"availability" : "(available)", "name" : "Phone w Watch"
"name" : "iPhone 5s", },
"udid" : "TEST-PHONE-UDID"
}
],
"tvOS 11.4" : [
{ {
"state" : "Shutdown", "dataPathSize" : 2186457088,
"availability" : "(available)", "udid" : "iPhone 13-UDID",
"name" : "Apple TV", "isAvailable" : true,
"udid" : "TEST-TV-UDID" "logPathSize" : 151552,
"deviceTypeIdentifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-13",
"state" : "Booted",
"name" : "iPhone 13"
} }
] ]
} }
...@@ -768,59 +783,54 @@ Dec 20 17:04:32 md32-11-vm1 Another App[88374]: Ignore this text''' ...@@ -768,59 +783,54 @@ Dec 20 17:04:32 md32-11-vm1 Another App[88374]: Ignore this text'''
); );
}); });
testWithoutContext('getDevices succeeds', () async { testWithoutContext('getConnectedDevices succeeds', () async {
fakeProcessManager.addCommand(const FakeCommand( fakeProcessManager.addCommand(const FakeCommand(
command: <String>[ command: <String>[
'xcrun', 'xcrun',
'simctl', 'simctl',
'list', 'list',
'--json',
'devices', 'devices',
'booted',
'iOS',
'--json',
], ],
stdout: validSimControlOutput, stdout: validSimControlOutput,
)); ));
final List<SimDevice> devices = await simControl.getDevices(); final List<BootedSimDevice> devices = await simControl.getConnectedDevices();
final SimDevice watch = devices[0]; final BootedSimDevice phone1 = devices[0];
expect(watch.category, 'watchOS 4.3'); expect(phone1.category, 'com.apple.CoreSimulator.SimRuntime.iOS-14-0');
expect(watch.state, 'Shutdown'); expect(phone1.name, 'iPhone 11');
expect(watch.availability, '(available)'); expect(phone1.udid, 'iPhone 11-UDID');
expect(watch.name, 'Apple Watch - 38mm');
expect(watch.udid, 'TEST-WATCH-UDID'); final BootedSimDevice phone2 = devices[1];
expect(watch.isBooted, isFalse); expect(phone2.category, 'com.apple.CoreSimulator.SimRuntime.iOS-16-0');
expect(phone2.name, 'Phone w Watch');
final SimDevice phone = devices[1]; expect(phone2.udid, 'Phone w Watch-UDID');
expect(phone.category, 'iOS 11.4');
expect(phone.state, 'Booted'); final BootedSimDevice phone3 = devices[2];
expect(phone.availability, '(available)'); expect(phone3.category, 'com.apple.CoreSimulator.SimRuntime.iOS-16-0');
expect(phone.name, 'iPhone 5s'); expect(phone3.name, 'iPhone 13');
expect(phone.udid, 'TEST-PHONE-UDID'); expect(phone3.udid, 'iPhone 13-UDID');
expect(phone.isBooted, isTrue);
final SimDevice tv = devices[2];
expect(tv.category, 'tvOS 11.4');
expect(tv.state, 'Shutdown');
expect(tv.availability, '(available)');
expect(tv.name, 'Apple TV');
expect(tv.udid, 'TEST-TV-UDID');
expect(tv.isBooted, isFalse);
expect(fakeProcessManager.hasRemainingExpectations, isFalse); expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}); });
testWithoutContext('getDevices handles bad simctl output', () async { testWithoutContext('getConnectedDevices handles bad simctl output', () async {
fakeProcessManager.addCommand(const FakeCommand( fakeProcessManager.addCommand(const FakeCommand(
command: <String>[ command: <String>[
'xcrun', 'xcrun',
'simctl', 'simctl',
'list', 'list',
'--json',
'devices', 'devices',
'booted',
'iOS',
'--json',
], ],
stdout: 'Install Started', stdout: 'Install Started',
)); ));
final List<SimDevice> devices = await simControl.getDevices(); final List<BootedSimDevice> devices = await simControl.getConnectedDevices();
expect(devices, isEmpty); expect(devices, isEmpty);
expect(fakeProcessManager.hasRemainingExpectations, isFalse); expect(fakeProcessManager.hasRemainingExpectations, isFalse);
......
...@@ -59,6 +59,8 @@ void main() { ...@@ -59,6 +59,8 @@ void main() {
'xcrun', 'xcrun',
'simctl', 'simctl',
'list', 'list',
'devices',
'booted',
], ],
), ),
); );
...@@ -78,6 +80,8 @@ void main() { ...@@ -78,6 +80,8 @@ void main() {
'xcrun', 'xcrun',
'simctl', 'simctl',
'list', 'list',
'devices',
'booted',
], ],
exitCode: 1, exitCode: 1,
), ),
......
...@@ -98,7 +98,7 @@ void main() { ...@@ -98,7 +98,7 @@ void main() {
'Xcode EULA has not been accepted.\nLaunch Xcode and accept the license.', 'Xcode EULA has not been accepted.\nLaunch Xcode and accept the license.',
), ),
const FakeCommand( const FakeCommand(
command: <String>['xcrun', 'simctl', 'list'], command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted'],
), ),
]); ]);
final Xcode xcode = Xcode.test( final Xcode xcode = Xcode.test(
...@@ -135,7 +135,7 @@ void main() { ...@@ -135,7 +135,7 @@ void main() {
command: <String>['xcrun', 'clang'], command: <String>['xcrun', 'clang'],
), ),
const FakeCommand( const FakeCommand(
command: <String>['xcrun', 'simctl', 'list'], command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted'],
exitCode: 1, exitCode: 1,
), ),
]); ]);
...@@ -173,7 +173,7 @@ void main() { ...@@ -173,7 +173,7 @@ void main() {
command: <String>['xcrun', 'clang'], command: <String>['xcrun', 'clang'],
), ),
const FakeCommand( const FakeCommand(
command: <String>['xcrun', 'simctl', 'list'], command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted'],
), ),
]); ]);
final Xcode xcode = Xcode.test( final Xcode xcode = Xcode.test(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment