diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart index ffe426a0a2..8773ac2ed4 100644 --- a/packages/flutter_tools/lib/src/ios/simulators.dart +++ b/packages/flutter_tools/lib/src/ios/simulators.dart @@ -66,7 +66,7 @@ class IOSSimulatorUtils { final Xcode _xcode; Future> getAttachedDevices() async { - if (!_xcode.isInstalledAndMeetsVersionCheck) { + if (!_xcode.isInstalledAndMeetsVersionCheck || !_xcode.isSimctlInstalled) { return []; } @@ -96,7 +96,7 @@ class IOSSimulatorUtils { } Future> getAvailableIOSRuntimes() async { - if (!_xcode.isInstalledAndMeetsVersionCheck) { + if (!_xcode.isInstalledAndMeetsVersionCheck || !_xcode.isSimctlInstalled) { return []; } diff --git a/packages/flutter_tools/test/general.shard/ios/simulators_test.dart b/packages/flutter_tools/test/general.shard/ios/simulators_test.dart index 82e25f96b5..edf3d314f6 100644 --- a/packages/flutter_tools/test/general.shard/ios/simulators_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/simulators_test.dart @@ -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([ + const FakeCommand(command: ['which', 'sysctl']), + const FakeCommand( + command: ['sysctl', 'hw.optional.arm64'], + stdout: 'hw.optional.arm64: 0', + ), + const FakeCommand( + command: ['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: ['xcrun', 'simctl', 'list', 'devices', 'booted', 'iOS', '--json'], + stdout: validSimControlOutput, + ), + ); + + final List 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 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 runtimes = + await simulatorUtilsBadSimctl.getAvailableIOSRuntimes(); + + expect(runtimes, isEmpty); + expect(fakeProcessManager, hasNoRemainingExpectations); + }, + ); }); group('startApp', () {