Check if simctl is installed before trying to list devices or runtimes (#163895)

After https://github.com/flutter/flutter/pull/163785 there was still an
unexpected `xcrun simctl` output on a machine with Xcode only half
installed https://github.com/flutter/flutter/issues/161655.

Check `simctl` is installed before trying to list booted simulator
devices or runtimes.

Should address https://github.com/flutter/flutter/issues/161655.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
Jenn Magder 2025-02-25 14:24:49 -08:00 committed by GitHub
parent d0e8fa157a
commit 898ab2479e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 77 additions and 2 deletions

View File

@ -66,7 +66,7 @@ class IOSSimulatorUtils {
final Xcode _xcode;
Future<List<IOSSimulator>> getAttachedDevices() async {
if (!_xcode.isInstalledAndMeetsVersionCheck) {
if (!_xcode.isInstalledAndMeetsVersionCheck || !_xcode.isSimctlInstalled) {
return <IOSSimulator>[];
}
@ -96,7 +96,7 @@ class IOSSimulatorUtils {
}
Future<List<IOSSimulatorRuntime>> getAvailableIOSRuntimes() async {
if (!_xcode.isInstalledAndMeetsVersionCheck) {
if (!_xcode.isInstalledAndMeetsVersionCheck || !_xcode.isSimctlInstalled) {
return <IOSSimulatorRuntime>[];
}

View File

@ -894,7 +894,10 @@ Dec 20 17:04:32 md32-11-vm1 Another App[88374]: Ignore this text''',
late FakeProcessManager fakeProcessManager;
Xcode xcode;
Xcode xcodeBadSimctl;
late SimControl simControl;
late IOSSimulatorUtils simulatorUtils;
late IOSSimulatorUtils simulatorUtilsBadSimctl;
late BufferLogger logger;
const String deviceId = 'smart-phone';
const String appId = 'flutterApp';
@ -902,8 +905,32 @@ Dec 20 17:04:32 md32-11-vm1 Another App[88374]: Ignore this text''',
setUp(() {
fakeProcessManager = FakeProcessManager.empty();
xcode = Xcode.test(processManager: FakeProcessManager.any());
final FakeProcessManager fakeProcessManagerBadSimctl = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(command: <String>['which', 'sysctl']),
const FakeCommand(
command: <String>['sysctl', 'hw.optional.arm64'],
stdout: 'hw.optional.arm64: 0',
),
const FakeCommand(
command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted'],
stderr: 'failed to run',
exitCode: 1,
),
]);
xcodeBadSimctl = Xcode.test(processManager: fakeProcessManagerBadSimctl);
logger = BufferLogger.test();
simControl = SimControl(logger: logger, processManager: fakeProcessManager, xcode: xcode);
simulatorUtils = IOSSimulatorUtils(
logger: logger,
processManager: fakeProcessManager,
xcode: xcode,
);
simulatorUtilsBadSimctl = IOSSimulatorUtils(
logger: logger,
processManager: fakeProcessManager,
xcode: xcodeBadSimctl,
);
});
testWithoutContext('getConnectedDevices succeeds', () async {
@ -933,6 +960,33 @@ Dec 20 17:04:32 md32-11-vm1 Another App[88374]: Ignore this text''',
expect(fakeProcessManager, hasNoRemainingExpectations);
});
testWithoutContext('IOSSimulatorUtils.getAttachedDevices succeeds', () async {
fakeProcessManager.addCommand(
const FakeCommand(
command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted', 'iOS', '--json'],
stdout: validSimControlOutput,
),
);
final List<IOSSimulator> devices = await simulatorUtils.getAttachedDevices();
final IOSSimulator phone1 = devices[0];
expect(phone1.category, Category.mobile);
expect(phone1.name, 'iPhone 11');
expect(phone1.simulatorCategory, 'com.apple.CoreSimulator.SimRuntime.iOS-14-0');
final IOSSimulator phone2 = devices[1];
expect(phone2.category, Category.mobile);
expect(phone2.name, 'Phone w Watch');
expect(phone2.simulatorCategory, 'com.apple.CoreSimulator.SimRuntime.iOS-16-0');
final IOSSimulator phone3 = devices[2];
expect(phone3.category, Category.mobile);
expect(phone3.name, 'iPhone 13');
expect(phone3.simulatorCategory, 'com.apple.CoreSimulator.SimRuntime.iOS-16-0');
expect(fakeProcessManager, hasNoRemainingExpectations);
});
testWithoutContext('getConnectedDevices handles bad simctl output', () async {
fakeProcessManager.addCommand(
const FakeCommand(
@ -947,6 +1001,16 @@ Dec 20 17:04:32 md32-11-vm1 Another App[88374]: Ignore this text''',
expect(fakeProcessManager, hasNoRemainingExpectations);
});
testWithoutContext(
'IOSSimulatorUtils.getAttachedDevices handles simctl not properly installed',
() async {
final List<IOSSimulator> devices = await simulatorUtilsBadSimctl.getAttachedDevices();
expect(devices, isEmpty);
expect(fakeProcessManager, hasNoRemainingExpectations);
},
);
testWithoutContext('sdkMajorVersion defaults to 11 when sdkNameAndVersion is junk', () async {
final IOSSimulator iosSimulatorA = IOSSimulator(
'x',
@ -1186,6 +1250,17 @@ Dec 20 17:04:32 md32-11-vm1 Another App[88374]: Ignore this text''',
expect(logger.errorText, contains('simctl returned non-JSON response:'));
expect(fakeProcessManager, hasNoRemainingExpectations);
});
testWithoutContext(
'IOSSimulatorUtils.getAvailableIOSRuntimes handles simctl not properly installed',
() async {
final List<IOSSimulatorRuntime> runtimes =
await simulatorUtilsBadSimctl.getAvailableIOSRuntimes();
expect(runtimes, isEmpty);
expect(fakeProcessManager, hasNoRemainingExpectations);
},
);
});
group('startApp', () {