diff --git a/packages/flutter_tools/lib/src/macos/xcode.dart b/packages/flutter_tools/lib/src/macos/xcode.dart index 491d5ecd89..e31d4735fd 100644 --- a/packages/flutter_tools/lib/src/macos/xcode.dart +++ b/packages/flutter_tools/lib/src/macos/xcode.dart @@ -172,22 +172,15 @@ class Xcode { /// Verifies that simctl is installed by trying to run it. bool get isSimctlInstalled { - if (_isSimctlInstalled == null) { - try { - // This command will error if additional components need to be installed in - // xcode 9.2 and above. - final RunResult result = _processUtils.runSync([ - ...xcrunCommand(), - 'simctl', - 'list', - 'devices', - 'booted', - ]); - _isSimctlInstalled = result.exitCode == 0; - } on ProcessException { - _isSimctlInstalled = false; - } - } + // This command will error if additional components need to be installed in + // xcode 9.2 and above. + _isSimctlInstalled ??= _processUtils.exitsHappySync([ + ...xcrunCommand(), + 'simctl', + 'list', + 'devices', + 'booted', + ]); return _isSimctlInstalled ?? false; } @@ -197,20 +190,15 @@ class Xcode { /// to run it. `devicectl` is made available in Xcode 15. bool get isDevicectlInstalled { if (_isDevicectlInstalled == null) { - try { - if (currentVersion == null || currentVersion!.major < 15) { - _isDevicectlInstalled = false; - return _isDevicectlInstalled!; - } - final RunResult result = _processUtils.runSync([ - ...xcrunCommand(), - 'devicectl', - '--version', - ]); - _isDevicectlInstalled = result.exitCode == 0; - } on ProcessException { + if (currentVersion == null || currentVersion!.major < 15) { _isDevicectlInstalled = false; + return _isDevicectlInstalled!; } + _isDevicectlInstalled = _processUtils.exitsHappySync([ + ...xcrunCommand(), + 'devicectl', + '--version', + ]); } return _isDevicectlInstalled ?? false; } diff --git a/packages/flutter_tools/test/general.shard/macos/xcode_test.dart b/packages/flutter_tools/test/general.shard/macos/xcode_test.dart index 3d7f8e3b7d..70d90a3b50 100644 --- a/packages/flutter_tools/test/general.shard/macos/xcode_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/xcode_test.dart @@ -78,18 +78,45 @@ void main() { fakeProcessManager.addCommand( const FakeCommand( command: ['xcrun', 'simctl', 'list', 'devices', 'booted'], + stderr: 'failed to run', exitCode: 1, ), ); final Xcode xcode = Xcode.test( processManager: fakeProcessManager, xcodeProjectInterpreter: xcodeProjectInterpreter, + logger: logger, ); expect(xcode.isSimctlInstalled, isFalse); expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.statusText, isEmpty); + expect(logger.errorText, isEmpty); }); + testWithoutContext( + 'isSimctlInstalled is false when simctl list throws process exception', + () { + fakeProcessManager.addCommand( + const FakeCommand( + command: ['xcrun', 'simctl', 'list', 'devices', 'booted'], + exception: ProcessException('xcrun', ['simctl']), + ), + ); + final Xcode xcode = Xcode.test( + processManager: fakeProcessManager, + xcodeProjectInterpreter: xcodeProjectInterpreter, + logger: logger, + ); + + expect(xcode.isSimctlInstalled, isFalse); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.statusText, isEmpty); + expect(logger.errorText, isEmpty); + expect(logger.traceText, contains('ProcessException')); + }, + ); + group('isDevicectlInstalled', () { testWithoutContext('is true when Xcode is 15+ and devicectl succeeds', () { fakeProcessManager.addCommand( @@ -113,10 +140,35 @@ void main() { final Xcode xcode = Xcode.test( processManager: fakeProcessManager, xcodeProjectInterpreter: xcodeProjectInterpreter, + logger: logger, ); expect(xcode.isDevicectlInstalled, isFalse); expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.statusText, isEmpty); + expect(logger.errorText, isEmpty); + }); + + testWithoutContext('is false when devicectl throws process exception', () { + fakeProcessManager.addCommand( + const FakeCommand( + command: ['xcrun', 'devicectl', '--version'], + exitCode: 1, + exception: ProcessException('xcrun', ['devicectl']), + ), + ); + xcodeProjectInterpreter.version = Version(15, 0, 0); + final Xcode xcode = Xcode.test( + processManager: fakeProcessManager, + xcodeProjectInterpreter: xcodeProjectInterpreter, + logger: logger, + ); + + expect(xcode.isDevicectlInstalled, isFalse); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.statusText, isEmpty); + expect(logger.errorText, isEmpty); + expect(logger.traceText, contains('ProcessException')); }); testWithoutContext('is false when Xcode is less than 15', () {