From cdacae89e1092346d13051003e288f79f08da369 Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Wed, 16 Dec 2020 11:52:59 -0800 Subject: [PATCH] Revert "Launch named iOS simulators (#72323)" (#72447) This reverts commit 84a7a611b0989d2d7e93d6b98aad2f6b68596b9f. --- .../lib/src/android/android_emulator.dart | 2 +- .../lib/src/commands/daemon.dart | 2 +- .../lib/src/commands/emulators.dart | 2 +- packages/flutter_tools/lib/src/device.dart | 4 + packages/flutter_tools/lib/src/emulator.dart | 4 +- .../lib/src/ios/ios_emulators.dart | 60 ++++++---- .../flutter_tools/lib/src/ios/simulators.dart | 83 +++----------- .../android/android_emulator_test.dart | 2 +- .../test/general.shard/emulator_test.dart | 89 +++++++-------- .../general.shard/ios/simulators_test.dart | 104 +----------------- packages/flutter_tools/test/src/context.dart | 3 +- 11 files changed, 111 insertions(+), 244 deletions(-) diff --git a/packages/flutter_tools/lib/src/android/android_emulator.dart b/packages/flutter_tools/lib/src/android/android_emulator.dart index 3b1162139875..aee624a55c2a 100644 --- a/packages/flutter_tools/lib/src/android/android_emulator.dart +++ b/packages/flutter_tools/lib/src/android/android_emulator.dart @@ -142,7 +142,7 @@ class AndroidEmulator extends Emulator { Category get category => Category.mobile; @override - String get platformDisplay => PlatformType.android.toString(); + PlatformType get platformType => PlatformType.android; String _prop(String name) => _properties != null ? _properties[name] : null; diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart index f72b135f50de..7b28ea87fa57 100644 --- a/packages/flutter_tools/lib/src/commands/daemon.dart +++ b/packages/flutter_tools/lib/src/commands/daemon.dart @@ -933,7 +933,7 @@ Map _emulatorToMap(Emulator emulator) { 'id': emulator.id, 'name': emulator.name, 'category': emulator.category?.toString(), - 'platformType': emulator.platformDisplay, + 'platformType': emulator.platformType?.toString(), }; } diff --git a/packages/flutter_tools/lib/src/commands/emulators.dart b/packages/flutter_tools/lib/src/commands/emulators.dart index 859e8f65cbc4..580e3223b040 100644 --- a/packages/flutter_tools/lib/src/commands/emulators.dart +++ b/packages/flutter_tools/lib/src/commands/emulators.dart @@ -27,7 +27,7 @@ class EmulatorsCommand extends FlutterCommand { final String description = 'List, launch and create emulators.'; @override - final List aliases = ['emulator', 'simulators', 'simulator']; + final List aliases = ['emulator']; @override Future runCommand() async { diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 579b70aa043a..019c129f097f 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -614,6 +614,10 @@ abstract class Device { /// Check if the device is supported by Flutter. bool isSupported(); + // String meant to be displayed to the user indicating if the device is + // supported by Flutter, and, if not, why. + String supportMessage() => isSupported() ? 'Supported' : 'Unsupported'; + /// The device's platform. Future get targetPlatform; diff --git a/packages/flutter_tools/lib/src/emulator.dart b/packages/flutter_tools/lib/src/emulator.dart index d0c2500c6aac..f8600eb1d554 100644 --- a/packages/flutter_tools/lib/src/emulator.dart +++ b/packages/flutter_tools/lib/src/emulator.dart @@ -252,7 +252,7 @@ abstract class Emulator { String get name; String get manufacturer; Category get category; - String get platformDisplay; + PlatformType get platformType; @override int get hashCode => id.hashCode; @@ -283,7 +283,7 @@ abstract class Emulator { emulator.id ?? '', emulator.name ?? '', emulator.manufacturer ?? '', - emulator.platformDisplay ?? '', + emulator.platformType?.toString() ?? '', ], ]; diff --git a/packages/flutter_tools/lib/src/ios/ios_emulators.dart b/packages/flutter_tools/lib/src/ios/ios_emulators.dart index dcb7b081eb2e..f54748b1b663 100644 --- a/packages/flutter_tools/lib/src/ios/ios_emulators.dart +++ b/packages/flutter_tools/lib/src/ios/ios_emulators.dart @@ -16,26 +16,17 @@ class IOSEmulators extends EmulatorDiscovery { bool get canListAnything => globals.iosWorkflow.canListEmulators; @override - Future> get emulators async { - final List simulators = await globals.iosSimulatorUtils.getAvailableDevices(); - return simulators.map((IOSSimulator device) { - return IOSEmulator(device); - }).toList(); - } + Future> get emulators async => getEmulators(); @override bool get canLaunchAnything => canListAnything; } class IOSEmulator extends Emulator { - IOSEmulator(IOSSimulator simulator) - : _simulator = simulator, - super(simulator.id, true); - - final IOSSimulator _simulator; + const IOSEmulator(String id) : super(id, true); @override - String get name => _simulator.name; + String get name => 'iOS Simulator'; @override String get manufacturer => 'Apple'; @@ -44,20 +35,43 @@ class IOSEmulator extends Emulator { Category get category => Category.mobile; @override - String get platformDisplay => - // com.apple.CoreSimulator.SimRuntime.iOS-10-3 => iOS-10-3 - _simulator.simulatorCategory?.split('.')?.last ?? 'ios'; + PlatformType get platformType => PlatformType.ios; @override Future launch() async { - final RunResult launchResult = await globals.processUtils.run([ - 'open', - '-a', - globals.xcode.getSimulatorPath(), - ]); - if (launchResult.exitCode != 0) { - globals.printError('$launchResult'); + Future launchSimulator(List additionalArgs) async { + final List args = [ + 'open', + ...additionalArgs, + '-a', + globals.xcode.getSimulatorPath(), + ]; + + final RunResult launchResult = await globals.processUtils.run(args); + if (launchResult.exitCode != 0) { + globals.printError('$launchResult'); + return false; + } + return true; + } + + // First run with `-n` to force a device to boot if there isn't already one + if (!await launchSimulator(['-n'])) { + return; } - return _simulator.boot(); + + // Run again to force it to Foreground (using -n doesn't force existing + // devices to the foreground) + await launchSimulator([]); } } + +/// Return the list of iOS Simulators (there can only be zero or one). +List getEmulators() { + final String simulatorPath = globals.xcode.getSimulatorPath(); + if (simulatorPath == null) { + return []; + } + + return [const IOSEmulator(iosSimulatorId)]; +} diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart index 7ad881e2ab1d..5e326157b53b 100644 --- a/packages/flutter_tools/lib/src/ios/simulators.dart +++ b/packages/flutter_tools/lib/src/ios/simulators.dart @@ -66,9 +66,7 @@ class IOSSimulatorUtils { return []; } - final List connected = (await _simControl.getAvailableDevices()) - .where((SimDevice device) => device.isBooted) - .toList(); + final List connected = await _simControl.getConnectedDevices(); return connected.map((SimDevice device) { return IOSSimulator( device.udid, @@ -79,26 +77,6 @@ class IOSSimulatorUtils { ); }).toList(); } - - Future> getAvailableDevices() async { - if (!_xcode.isInstalledAndMeetsVersionCheck) { - return []; - } - - final List available = await _simControl.getAvailableDevices(); - return available - .map((SimDevice device) { - return IOSSimulator( - device.udid, - name: device.name, - simControl: _simControl, - simulatorCategory: device.category, - xcode: _xcode, - ); - }) - .where((IOSSimulator simulator) => simulator.isSupported()) - .toList(); - } } /// A wrapper around the `simctl` command line tool. @@ -111,23 +89,6 @@ class SimControl { _xcode = xcode, _processUtils = ProcessUtils(processManager: processManager, logger: logger); - /// Create a [SimControl] for testing. - /// - /// Defaults to a buffer logger. - @visibleForTesting - factory SimControl.test({ - @required ProcessManager processManager, - Logger logger, - Xcode xcode, - }) { - logger ??= BufferLogger.test(); - return SimControl( - logger: logger, - xcode: xcode, - processManager: processManager, - ); - } - final Logger _logger; final ProcessUtils _processUtils; final Xcode _xcode; @@ -199,10 +160,10 @@ class SimControl { return devices; } - /// Returns all the available simulator devices. - Future> getAvailableDevices() async { + /// Returns all the connected simulator devices. + Future> getConnectedDevices() async { final List simDevices = await getDevices(); - return simDevices.where((SimDevice device) => device.isAvailable).toList(); + return simDevices.where((SimDevice device) => device.isBooted).toList(); } Future isInstalled(String deviceId, String appId) { @@ -273,17 +234,6 @@ class SimControl { return result; } - Future boot(String deviceId) { - return _processUtils.run( - [ - ..._xcode.xcrunCommand(), - 'simctl', - 'boot', - deviceId, - ], - ); - } - Future takeScreenshot(String deviceId, String outputPath) async { try { await _processUtils.run( @@ -346,11 +296,7 @@ class SimDevice { final Map data; String get state => data['state']?.toString(); - - bool get isAvailable => - data['isAvailable'] == true || - data['availability']?.toString() == '(available)'; - + String get availability => data['availability']?.toString(); String get name => data['name']?.toString(); String get udid => data['udid']?.toString(); @@ -448,6 +394,7 @@ class IOSSimulator extends Device { @override bool isSupported() { if (!globals.platform.isMacOS) { + _supportMessage = 'iOS devices require a Mac host machine.'; return false; } @@ -455,25 +402,21 @@ class IOSSimulator extends Device { // We do not yet support WatchOS or tvOS devices. final RegExp blocklist = RegExp(r'Apple (TV|Watch)', caseSensitive: false); if (blocklist.hasMatch(name)) { + _supportMessage = 'Flutter does not support Apple TV or Apple Watch.'; return false; } return true; } - Future boot() async { - final RunResult result = await _simControl.boot(id); + String _supportMessage; - if (result.exitCode == 0) { - return true; - } - // 149 exit code means the device is already booted. Ignore this error. - if (result.exitCode == 149) { - globals.printTrace('Simulator "$id" already booted.'); - return true; + @override + String supportMessage() { + if (isSupported()) { + return 'Supported'; } - globals.logger.printError('$result'); - return false; + return _supportMessage ?? 'Unknown'; } @override diff --git a/packages/flutter_tools/test/general.shard/android/android_emulator_test.dart b/packages/flutter_tools/test/general.shard/android/android_emulator_test.dart index 0d46c518f1d9..82a1b108c461 100644 --- a/packages/flutter_tools/test/general.shard/android/android_emulator_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_emulator_test.dart @@ -71,7 +71,7 @@ void main() { expect(emulator.name, displayName); expect(emulator.manufacturer, manufacturer); expect(emulator.category, Category.mobile); - expect(emulator.platformDisplay, 'android'); + expect(emulator.platformType, PlatformType.android); }); testWithoutContext('prefers displayname for name', () { diff --git a/packages/flutter_tools/test/general.shard/emulator_test.dart b/packages/flutter_tools/test/general.shard/emulator_test.dart index e51a6499a0f7..89d62087b17c 100644 --- a/packages/flutter_tools/test/general.shard/emulator_test.dart +++ b/packages/flutter_tools/test/general.shard/emulator_test.dart @@ -2,13 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:convert'; + import 'package:file/memory.dart'; import 'package:flutter_tools/src/android/android_workflow.dart'; +import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/emulator.dart'; import 'package:flutter_tools/src/ios/ios_emulators.dart'; -import 'package:flutter_tools/src/ios/simulators.dart'; import 'package:flutter_tools/src/macos/xcode.dart'; import 'package:mockito/mockito.dart'; import 'package:process/process.dart'; @@ -43,10 +45,14 @@ const FakeCommand kListEmulatorsCommand = FakeCommand( ); void main() { + MockProcessManager mockProcessManager; MockAndroidSdk mockSdk; + MockXcode mockXcode; setUp(() { + mockProcessManager = MockProcessManager(); mockSdk = MockAndroidSdk(); + mockXcode = MockXcode(); when(mockSdk.avdManagerPath).thenReturn('avdmanager'); when(mockSdk.getAvdManagerPath()).thenReturn('avdmanager'); @@ -293,53 +299,24 @@ void main() { }); group('ios_emulators', () { - MockXcode mockXcode; - FakeProcessManager fakeProcessManager; - + bool didAttemptToRunSimulator = false; setUp(() { - fakeProcessManager = FakeProcessManager.list([]); - mockXcode = MockXcode(); - when(mockXcode.xcrunCommand()).thenReturn(['xcrun']); - when(mockXcode.getSimulatorPath()) - .thenAnswer((_) => '/fake/simulator.app'); + when(mockXcode.xcodeSelectPath).thenReturn('/fake/Xcode.app/Contents/Developer'); + when(mockXcode.getSimulatorPath()).thenAnswer((_) => '/fake/simulator.app'); + when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async { + final List args = invocation.positionalArguments[0] as List; + if (args.length >= 3 && args[0] == 'open' && args[1] == '-a' && args[2] == '/fake/simulator.app') { + didAttemptToRunSimulator = true; + } + return ProcessResult(101, 0, '', ''); + }); }); - testUsingContext('runs correct launch commands', () async { - fakeProcessManager.addCommands([ - const FakeCommand(command: [ - 'open', - '-a', - '/fake/simulator.app', - ]), - const FakeCommand(command: [ - 'xcrun', - 'simctl', - 'boot', - '1234', - ]), - ]); - final SimControl simControl = SimControl.test( - processManager: fakeProcessManager, - xcode: mockXcode, - ); - - final IOSSimulator simulator = IOSSimulator( - '1234', - name: 'iPhone 12', - simulatorCategory: 'com.apple.CoreSimulator.SimRuntime.iOS-14-3', - simControl: simControl, - xcode: mockXcode, - ); - final IOSEmulator emulator = IOSEmulator(simulator); - - expect(emulator.id, '1234'); - expect(emulator.name, 'iPhone 12'); - expect(emulator.category, Category.mobile); - expect(emulator.platformDisplay, 'iOS-14-3'); + const Emulator emulator = IOSEmulator('ios'); await emulator.launch(); - expect(fakeProcessManager.hasRemainingExpectations, false); + expect(didAttemptToRunSimulator, equals(true)); }, overrides: { - ProcessManager: () => fakeProcessManager, + ProcessManager: () => mockProcessManager, Xcode: () => mockXcode, }); }); @@ -370,11 +347,35 @@ class FakeEmulator extends Emulator { Category get category => Category.mobile; @override - String get platformDisplay => PlatformType.android.toString(); + PlatformType get platformType => PlatformType.android; @override Future launch() { throw UnimplementedError('Not implemented in Mock'); } } + +class MockProcessManager extends Mock implements ProcessManager { + + @override + ProcessResult runSync( + List command, { + String workingDirectory, + Map environment, + bool includeParentEnvironment = true, + bool runInShell = false, + Encoding stdoutEncoding = systemEncoding, + Encoding stderrEncoding = systemEncoding, + }) { + final String program = command[0] as String; + final List args = command.sublist(1) as List; + switch (program) { + case '/usr/bin/xcode-select': + throw ProcessException(program, args); + break; + } + throw StateError('Unexpected process call: $command'); + } +} + class MockXcode extends Mock implements Xcode {} 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 3048f2f1242c..6b921ec7b771 100644 --- a/packages/flutter_tools/test/general.shard/ios/simulators_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/simulators_test.dart @@ -11,7 +11,6 @@ import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; -import 'package:flutter_tools/src/base/process.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/devfs.dart'; @@ -786,19 +785,12 @@ Dec 20 17:04:32 md32-11-vm1 Another App[88374]: Ignore this text''' "availability" : "(available)", "name" : "iPhone 5s", "udid" : "TEST-PHONE-UDID" - }, - { - "state" : "Shutdown", - "isAvailable" : false, - "name" : "iPhone 11", - "udid" : "TEST-PHONE-UNAVAILABLE-UDID", - "availabilityError" : "runtime profile not found" } ], "tvOS 11.4" : [ { "state" : "Shutdown", - "isAvailable" : true, + "availability" : "(available)", "name" : "Apple TV", "udid" : "TEST-TV-UDID" } @@ -832,12 +824,11 @@ Dec 20 17:04:32 md32-11-vm1 Another App[88374]: Ignore this text''' testWithoutContext('getDevices succeeds', () async { final List devices = await simControl.getDevices(); - expect(devices.length, 4); final SimDevice watch = devices[0]; expect(watch.category, 'watchOS 4.3'); expect(watch.state, 'Shutdown'); - expect(watch.isAvailable, true); + expect(watch.availability, '(available)'); expect(watch.name, 'Apple Watch - 38mm'); expect(watch.udid, 'TEST-WATCH-UDID'); expect(watch.isBooted, isFalse); @@ -845,33 +836,20 @@ Dec 20 17:04:32 md32-11-vm1 Another App[88374]: Ignore this text''' final SimDevice phone = devices[1]; expect(phone.category, 'iOS 11.4'); expect(phone.state, 'Booted'); - expect(phone.isAvailable, true); + expect(phone.availability, '(available)'); expect(phone.name, 'iPhone 5s'); expect(phone.udid, 'TEST-PHONE-UDID'); expect(phone.isBooted, isTrue); - final SimDevice unavailablePhone = devices[2]; - expect(unavailablePhone.category, 'iOS 11.4'); - expect(unavailablePhone.state, 'Shutdown'); - expect(unavailablePhone.isAvailable, isFalse); - expect(unavailablePhone.name, 'iPhone 11'); - expect(unavailablePhone.udid, 'TEST-PHONE-UNAVAILABLE-UDID'); - expect(unavailablePhone.isBooted, isFalse); - - final SimDevice tv = devices[3]; + final SimDevice tv = devices[2]; expect(tv.category, 'tvOS 11.4'); expect(tv.state, 'Shutdown'); - expect(tv.isAvailable, true); + expect(tv.availability, '(available)'); expect(tv.name, 'Apple TV'); expect(tv.udid, 'TEST-TV-UDID'); expect(tv.isBooted, isFalse); }); - testWithoutContext('getAvailableDevices succeeds', () async { - final List devices = await simControl.getAvailableDevices(); - expect(devices.length, 3); - }); - testWithoutContext('getDevices handles bad simctl output', () async { when(mockProcessManager.run(any)) .thenAnswer((Invocation _) async => ProcessResult(mockPid, 0, 'Install Started', '')); @@ -927,78 +905,6 @@ Dec 20 17:04:32 md32-11-vm1 Another App[88374]: Ignore this text''' throwsToolExit(message: r'Unable to launch'), ); }); - - testWithoutContext('.boot() calls the right command', () async { - await simControl.boot(deviceId); - verify(mockProcessManager.run( - ['xcrun', 'simctl', 'boot', deviceId], - environment: anyNamed('environment'), - workingDirectory: anyNamed('workingDirectory'), - )); - }); - }); - - group('boot', () { - SimControl simControl; - MockXcode mockXcode; - - setUp(() { - simControl = MockSimControl(); - mockXcode = MockXcode(); - when(mockXcode.xcrunCommand()).thenReturn(['xcrun']); - }); - - testUsingContext('success', () async { - final IOSSimulator device = IOSSimulator( - 'x', - name: 'iPhone SE', - simulatorCategory: 'iOS 11.2', - simControl: simControl, - xcode: mockXcode, - ); - when(simControl.boot(any)).thenAnswer((_) async => - RunResult(ProcessResult(0, 0, '', ''), ['simctl'])); - - expect(await device.boot(), isTrue); - }, overrides: { - FileSystem: () => fileSystem, - ProcessManager: () => FakeProcessManager.any(), - }); - - testUsingContext('already booted', () async { - final IOSSimulator device = IOSSimulator( - 'x', - name: 'iPhone SE', - simulatorCategory: 'iOS 11.2', - simControl: simControl, - xcode: mockXcode, - ); - // 149 means the device is already booted. - when(simControl.boot(any)).thenAnswer((_) async => - RunResult(ProcessResult(0, 149, '', ''), ['simctl'])); - - expect(await device.boot(), isTrue); - }, overrides: { - FileSystem: () => fileSystem, - ProcessManager: () => FakeProcessManager.any(), - }); - - testUsingContext('failed', () async { - final IOSSimulator device = IOSSimulator( - 'x', - name: 'iPhone SE', - simulatorCategory: 'iOS 11.2', - simControl: simControl, - xcode: mockXcode, - ); - when(simControl.boot(any)).thenAnswer((_) async => - RunResult(ProcessResult(0, 1, '', ''), ['simctl'])); - - expect(await device.boot(), isFalse); - }, overrides: { - FileSystem: () => fileSystem, - ProcessManager: () => FakeProcessManager.any(), - }); }); group('startApp', () { diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart index 5e89ddf7a0aa..b1616fea2d5a 100644 --- a/packages/flutter_tools/test/src/context.dart +++ b/packages/flutter_tools/test/src/context.dart @@ -114,7 +114,6 @@ void testUsingContext( IOSSimulatorUtils: () { final MockIOSSimulatorUtils mock = MockIOSSimulatorUtils(); when(mock.getAttachedDevices()).thenAnswer((Invocation _) async => []); - when(mock.getAvailableDevices()).thenAnswer((Invocation _) async => []); return mock; }, OutputPreferences: () => OutputPreferences.test(), @@ -287,7 +286,7 @@ class FakeDoctor extends Doctor { class MockSimControl extends Mock implements SimControl { MockSimControl() { - when(getAvailableDevices()).thenAnswer((Invocation _) async => []); + when(getConnectedDevices()).thenAnswer((Invocation _) async => []); } }