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