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 {
// We need xcode (+simctl) to list simulator devices, and libimobiledevice to list real devices.
@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
// for real devices.
......
......@@ -71,8 +71,8 @@ class IOSSimulatorUtils {
return <IOSSimulator>[];
}
final List<SimDevice> connected = await _simControl.getConnectedDevices();
return connected.map<IOSSimulator?>((SimDevice device) {
final List<BootedSimDevice> connected = await _simControl.getConnectedDevices();
return connected.map<IOSSimulator?>((BootedSimDevice device) {
final String? udid = device.udid;
final String? name = device.name;
if (udid == null) {
......@@ -109,30 +109,45 @@ class SimControl {
/// Runs `simctl list --json` and returns the JSON of the corresponding
/// [section].
Future<Map<String, Object?>> _list(SimControlListSection section) async {
// Sample output from `simctl list --json`:
Future<Map<String, Object?>> _listBootedDevices() async {
// Sample output from `simctl list available booted --json`:
//
// {
// "devicetypes": { ... },
// "runtimes": { ... },
// "devices" : {
// "com.apple.CoreSimulator.SimRuntime.iOS-8-2" : [
// "com.apple.CoreSimulator.SimRuntime.iOS-14-0" : [
// {
// "state" : "Shutdown",
// "availability" : " (unavailable, runtime profile not found)",
// "name" : "iPhone 4s",
// "udid" : "1913014C-6DCB-485D-AC6B-7CD76D322F5B"
// },
// ...
// },
// "pairs": { ... },
// "lastBootedAt" : "2022-07-26T01:46:23Z",
// "dataPath" : "\/Users\/magder\/Library\/Developer\/CoreSimulator\/Devices\/9EC90A99-6924-472D-8CDD-4D8234AB4779\/data",
// "dataPathSize" : 1620578304,
// "logPath" : "\/Users\/magder\/Library\/Logs\/CoreSimulator\/9EC90A99-6924-472D-8CDD-4D8234AB4779",
// "udid" : "9EC90A99-6924-472D-8CDD-4D8234AB4779",
// "isAvailable" : true,
// "logPathSize" : 9740288,
// "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>[
..._xcode.xcrunCommand(),
'simctl',
'list',
'devices',
'booted',
'iOS',
'--json',
section.name,
];
_logger.printTrace(command.join(' '));
final RunResult results = await _processUtils.run(command);
......@@ -141,7 +156,7 @@ class SimControl {
return <String, Map<String, Object?>>{};
}
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?>) {
return decodeResult;
}
......@@ -156,17 +171,17 @@ class SimControl {
}
}
/// Returns a list of all available devices, both potential and connected.
Future<List<SimDevice>> getDevices() async {
final List<SimDevice> devices = <SimDevice>[];
/// Returns all the connected simulator devices.
Future<List<BootedSimDevice>> getConnectedDevices() async {
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) {
final Object? devicesData = devicesSection[deviceCategory];
if (devicesData != null && devicesData is List<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 {
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) {
return _processUtils.exitsHappy(<String>[
..._xcode.xcrunCommand(),
......@@ -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 {
SimDevice(this.category, this.data);
class BootedSimDevice {
BootedSimDevice(this.category, this.data);
final String category;
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 udid => data['udid']?.toString();
bool get isBooted => state == 'Booted';
}
class IOSSimulator extends Device {
......
......@@ -136,7 +136,7 @@ class Xcode {
// This command will error if additional components need to be installed in
// xcode 9.2 and above.
final RunResult result = _processUtils.runSync(
<String>[...xcrunCommand(), 'simctl', 'list'],
<String>[...xcrunCommand(), 'simctl', 'list', 'devices', 'booted'],
);
_isSimctlInstalled = result.exitCode == 0;
} on ProcessException {
......
......@@ -49,10 +49,18 @@ void main() {
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(
platform: FakePlatform(operatingSystem: 'macos'),
xcode: Xcode.test(processManager: FakeProcessManager.any(),
xcode: Xcode.test(processManager: xcodeProcessManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test(
processManager: FakeProcessManager.any(),
version: null,
......@@ -65,9 +73,33 @@ void main() {
expect(iosWorkflow.canLaunchDevices, false);
expect(iosWorkflow.canListDevices, 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(
processManager: FakeProcessManager.any(),
xcodeProjectInterpreter: XcodeProjectInterpreter.test(
......
......@@ -724,28 +724,43 @@ Dec 20 17:04:32 md32-11-vm1 Another App[88374]: Ignore this text'''
const String validSimControlOutput = '''
{
"devices" : {
"watchOS 4.3" : [
"com.apple.CoreSimulator.SimRuntime.iOS-14-0" : [
{
"state" : "Shutdown",
"availability" : "(available)",
"name" : "Apple Watch - 38mm",
"udid" : "TEST-WATCH-UDID"
"dataPathSize" : 1734569984,
"udid" : "iPhone 11-UDID",
"isAvailable" : true,
"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",
"availability" : "(available)",
"name" : "iPhone 5s",
"udid" : "TEST-PHONE-UDID"
}
],
"tvOS 11.4" : [
"name" : "Phone w Watch"
},
{
"state" : "Shutdown",
"availability" : "(available)",
"name" : "Apple TV",
"udid" : "TEST-TV-UDID"
"dataPathSize" : 2186457088,
"udid" : "iPhone 13-UDID",
"isAvailable" : true,
"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'''
);
});
testWithoutContext('getDevices succeeds', () async {
testWithoutContext('getConnectedDevices succeeds', () async {
fakeProcessManager.addCommand(const FakeCommand(
command: <String>[
'xcrun',
'simctl',
'list',
'--json',
'devices',
'booted',
'iOS',
'--json',
],
stdout: validSimControlOutput,
));
final List<SimDevice> devices = await simControl.getDevices();
final SimDevice watch = devices[0];
expect(watch.category, 'watchOS 4.3');
expect(watch.state, 'Shutdown');
expect(watch.availability, '(available)');
expect(watch.name, 'Apple Watch - 38mm');
expect(watch.udid, 'TEST-WATCH-UDID');
expect(watch.isBooted, isFalse);
final SimDevice phone = devices[1];
expect(phone.category, 'iOS 11.4');
expect(phone.state, 'Booted');
expect(phone.availability, '(available)');
expect(phone.name, 'iPhone 5s');
expect(phone.udid, 'TEST-PHONE-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);
final List<BootedSimDevice> devices = await simControl.getConnectedDevices();
final BootedSimDevice phone1 = devices[0];
expect(phone1.category, 'com.apple.CoreSimulator.SimRuntime.iOS-14-0');
expect(phone1.name, 'iPhone 11');
expect(phone1.udid, 'iPhone 11-UDID');
final BootedSimDevice phone2 = devices[1];
expect(phone2.category, 'com.apple.CoreSimulator.SimRuntime.iOS-16-0');
expect(phone2.name, 'Phone w Watch');
expect(phone2.udid, 'Phone w Watch-UDID');
final BootedSimDevice phone3 = devices[2];
expect(phone3.category, 'com.apple.CoreSimulator.SimRuntime.iOS-16-0');
expect(phone3.name, 'iPhone 13');
expect(phone3.udid, 'iPhone 13-UDID');
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
});
testWithoutContext('getDevices handles bad simctl output', () async {
testWithoutContext('getConnectedDevices handles bad simctl output', () async {
fakeProcessManager.addCommand(const FakeCommand(
command: <String>[
'xcrun',
'simctl',
'list',
'--json',
'devices',
'booted',
'iOS',
'--json',
],
stdout: 'Install Started',
));
final List<SimDevice> devices = await simControl.getDevices();
final List<BootedSimDevice> devices = await simControl.getConnectedDevices();
expect(devices, isEmpty);
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
......
......@@ -59,6 +59,8 @@ void main() {
'xcrun',
'simctl',
'list',
'devices',
'booted',
],
),
);
......@@ -78,6 +80,8 @@ void main() {
'xcrun',
'simctl',
'list',
'devices',
'booted',
],
exitCode: 1,
),
......
......@@ -98,7 +98,7 @@ void main() {
'Xcode EULA has not been accepted.\nLaunch Xcode and accept the license.',
),
const FakeCommand(
command: <String>['xcrun', 'simctl', 'list'],
command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted'],
),
]);
final Xcode xcode = Xcode.test(
......@@ -135,7 +135,7 @@ void main() {
command: <String>['xcrun', 'clang'],
),
const FakeCommand(
command: <String>['xcrun', 'simctl', 'list'],
command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted'],
exitCode: 1,
),
]);
......@@ -173,7 +173,7 @@ void main() {
command: <String>['xcrun', 'clang'],
),
const FakeCommand(
command: <String>['xcrun', 'simctl', 'list'],
command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted'],
),
]);
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