Revert "Replace ideviceinfo and idevice_id with xcdevice (#49854)" (#50243)

This reverts commit 3aa7a80053.
This commit is contained in:
Jenn Magder 2020-02-05 18:00:31 -08:00 committed by GitHub
parent eb4b6dc91b
commit ce6fbf6668
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 264 additions and 863 deletions

View file

@ -166,11 +166,6 @@ Future<T> runInContext<T>(
fileSystem: globals.fs,
xcodeProjectInterpreter: xcodeProjectInterpreter,
),
XCDevice: () => XCDevice(
processManager: globals.processManager,
logger: globals.logger,
xcode: globals.xcode,
),
XcodeProjectInterpreter: () => XcodeProjectInterpreter(
logger: globals.logger,
processManager: globals.processManager,

View file

@ -57,8 +57,6 @@ Xcode get xcode => context.get<Xcode>();
FlutterVersion get flutterVersion => context.get<FlutterVersion>();
IMobileDevice get iMobileDevice => context.get<IMobileDevice>();
XCDevice get xcdevice => context.get<XCDevice>();
/// Display an error level message to the user. Commands should use this if they
/// fail in some way.
///

View file

@ -5,7 +5,6 @@
import 'dart:async';
import 'package:meta/meta.dart';
import 'package:platform/platform.dart';
import '../application_package.dart';
import '../artifacts.dart';
@ -19,7 +18,6 @@ import '../build_info.dart';
import '../convert.dart';
import '../device.dart';
import '../globals.dart' as globals;
import '../macos/xcode.dart';
import '../mdns_discovery.dart';
import '../project.dart';
import '../protocol_discovery.dart';
@ -113,18 +111,11 @@ class IOSDevices extends PollingDeviceDiscovery {
bool get canListAnything => iosWorkflow.canListDevices;
@override
Future<List<Device>> pollingGetDevices() => IOSDevice.getAttachedDevices(globals.platform, globals.xcdevice);
@override
Future<List<String>> getDiagnostics() => IOSDevice.getDiagnostics(globals.platform, globals.xcdevice);
Future<List<Device>> pollingGetDevices() => IOSDevice.getAttachedDevices();
}
class IOSDevice extends Device {
IOSDevice(String id, {
@required this.name,
@required this.cpuArchitecture,
@required String sdkVersion,
})
IOSDevice(String id, { this.name, String sdkVersion })
: _sdkVersion = sdkVersion,
super(
id,
@ -166,8 +157,6 @@ class IOSDevice extends Device {
@override
final String name;
final DarwinArch cpuArchitecture;
Map<IOSApp, DeviceLogReader> _logReaders;
DevicePortForwarder _portForwarder;
@ -181,20 +170,34 @@ class IOSDevice extends Device {
@override
bool get supportsStartPaused => false;
static Future<List<IOSDevice>> getAttachedDevices(Platform platform, XCDevice xcdevice) async {
if (!platform.isMacOS) {
throw UnsupportedError('Control of iOS devices or simulators only supported on macOS.');
static Future<List<IOSDevice>> getAttachedDevices() async {
if (!globals.platform.isMacOS) {
throw UnsupportedError('Control of iOS devices or simulators only supported on Mac OS.');
}
if (!globals.iMobileDevice.isInstalled) {
return <IOSDevice>[];
}
return await xcdevice.getAvailableTetheredIOSDevices();
}
final List<IOSDevice> devices = <IOSDevice>[];
for (String id in (await globals.iMobileDevice.getAvailableDeviceIDs()).split('\n')) {
id = id.trim();
if (id.isEmpty) {
continue;
}
static Future<List<String>> getDiagnostics(Platform platform, XCDevice xcdevice) async {
if (!platform.isMacOS) {
return const <String>['Control of iOS devices or simulators only supported on macOS.'];
try {
final String deviceName = await globals.iMobileDevice.getInfoForDevice(id, 'DeviceName');
final String sdkVersion = await globals.iMobileDevice.getInfoForDevice(id, 'ProductVersion');
devices.add(IOSDevice(id, name: deviceName, sdkVersion: sdkVersion));
} on IOSDeviceNotFoundError catch (error) {
// Unable to find device with given udid. Possibly a network device.
globals.printTrace('Error getting attached iOS device: $error');
} on IOSDeviceNotTrustedError catch (error) {
globals.printTrace('Error getting attached iOS device information: $error');
UsageEvent('device', 'ios-trust-failure').send();
}
}
return await xcdevice.getDiagnostics();
return devices;
}
@override
@ -277,13 +280,24 @@ class IOSDevice extends Device {
// TODO(chinmaygarde): Use mainPath, route.
globals.printTrace('Building ${package.name} for $id');
String cpuArchitecture;
try {
cpuArchitecture = await globals.iMobileDevice.getInfoForDevice(id, 'CPUArchitecture');
} on IOSDeviceNotFoundError catch (e) {
globals.printError(e.message);
return LaunchResult.failed();
}
final DarwinArch iosArch = getIOSArchForName(cpuArchitecture);
// Step 1: Build the precompiled/DBC application if necessary.
final XcodeBuildResult buildResult = await buildXcodeProject(
app: package as BuildableIOSApp,
buildInfo: debuggingOptions.buildInfo,
targetOverride: mainPath,
buildForDevice: true,
activeArch: cpuArchitecture,
activeArch: iosArch,
);
if (!buildResult.success) {
globals.printError('Could not build the precompiled application for the device.');

View file

@ -13,11 +13,7 @@ import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
import '../base/process.dart';
import '../build_info.dart';
import '../convert.dart';
import '../ios/devices.dart';
import '../ios/xcodeproj.dart';
import '../reporting/reporting.dart';
const int kXcodeRequiredVersionMajor = 10;
const int kXcodeRequiredVersionMinor = 2;
@ -189,315 +185,3 @@ class Xcode {
);
}
}
/// A utility class for interacting with Xcode xcdevice command line tools.
class XCDevice {
XCDevice({
@required ProcessManager processManager,
@required Logger logger,
@required Xcode xcode,
}) : _processUtils = ProcessUtils(logger: logger, processManager: processManager),
_logger = logger,
_xcode = xcode;
final ProcessUtils _processUtils;
final Logger _logger;
final Xcode _xcode;
bool get isInstalled => _xcode.isInstalledAndMeetsVersionCheck && xcdevicePath != null;
String _xcdevicePath;
String get xcdevicePath {
if (_xcdevicePath == null) {
try {
_xcdevicePath = _processUtils.runSync(
<String>[
'xcrun',
'--find',
'xcdevice'
],
throwOnError: true,
).stdout.trim();
} on ProcessException catch (exception) {
_logger.printTrace('Process exception finding xcdevice:\n$exception');
} on ArgumentError catch (exception) {
_logger.printTrace('Argument exception finding xcdevice:\n$exception');
}
}
return _xcdevicePath;
}
Future<List<dynamic>> _getAllDevices({bool useCache = false}) async {
if (!isInstalled) {
_logger.printTrace('Xcode not found. Run \'flutter doctor\' for more information.');
return null;
}
if (useCache && _cachedListResults != null) {
return _cachedListResults;
}
try {
// USB-tethered devices should be found quickly. 1 second timeout is faster than the default.
final RunResult result = await _processUtils.run(
<String>[
'xcrun',
'xcdevice',
'list',
'--timeout',
'1',
],
throwOnError: true,
);
if (result.exitCode == 0) {
final List<dynamic> listResults = json.decode(result.stdout) as List<dynamic>;
_cachedListResults = listResults;
return listResults;
}
_logger.printTrace('xcdevice returned an error:\n${result.stderr}');
} on ProcessException catch (exception) {
_logger.printTrace('Process exception running xcdevice list:\n$exception');
} on ArgumentError catch (exception) {
_logger.printTrace('Argument exception running xcdevice list:\n$exception');
}
return null;
}
List<dynamic> _cachedListResults;
/// List of devices available over USB.
Future<List<IOSDevice>> getAvailableTetheredIOSDevices() async {
final List<dynamic> allAvailableDevices = await _getAllDevices();
if (allAvailableDevices == null) {
return const <IOSDevice>[];
}
// [
// {
// "simulator" : true,
// "operatingSystemVersion" : "13.3 (17K446)",
// "available" : true,
// "platform" : "com.apple.platform.appletvsimulator",
// "modelCode" : "AppleTV5,3",
// "identifier" : "CBB5E1ED-2172-446E-B4E7-F2B5823DBBA6",
// "architecture" : "x86_64",
// "modelName" : "Apple TV",
// "name" : "Apple TV"
// },
// {
// "simulator" : false,
// "operatingSystemVersion" : "13.3 (17C54)",
// "interface" : "usb",
// "available" : true,
// "platform" : "com.apple.platform.iphoneos",
// "modelCode" : "iPhone8,1",
// "identifier" : "d83d5bc53967baa0ee18626ba87b6254b2ab5418",
// "architecture" : "arm64",
// "modelName" : "iPhone 6s",
// "name" : "iPhone"
// },
// {
// "simulator" : true,
// "operatingSystemVersion" : "6.1.1 (17S445)",
// "available" : true,
// "platform" : "com.apple.platform.watchsimulator",
// "modelCode" : "Watch5,4",
// "identifier" : "2D74FB11-88A0-44D0-B81E-C0C142B1C94A",
// "architecture" : "i386",
// "modelName" : "Apple Watch Series 5 - 44mm",
// "name" : "Apple Watch Series 5 - 44mm"
// },
// ...
final List<IOSDevice> devices = <IOSDevice>[];
for (final dynamic device in allAvailableDevices) {
if (device is! Map) {
continue;
}
final Map<String, dynamic> deviceProperties = device as Map<String, dynamic>;
// Only include iPhone, iPad, iPod, or other iOS devices.
if (!_isIPhoneOSDevice(deviceProperties)) {
continue;
}
final String errorMessage = _parseErrorMessage(deviceProperties);
if (errorMessage != null) {
if (errorMessage.contains('not paired')) {
UsageEvent('device', 'ios-trust-failure').send();
}
_logger.printTrace(errorMessage);
continue;
}
// In case unavailable without an error (may not be possible...)
if (!_isAvailable(deviceProperties)) {
continue;
}
// Only support USB devices, skip "network" interface (Xcode > Window > Devices and Simulators > Connect via network).
if (!_isUSBTethered(deviceProperties)) {
continue;
}
devices.add(IOSDevice(
device['identifier'] as String,
name: device['name'] as String,
cpuArchitecture: _cpuArchitecture(deviceProperties),
sdkVersion: _sdkVersion(deviceProperties),
));
}
return devices;
}
/// Despite the name, com.apple.platform.iphoneos includes iPhone, iPads, and all iOS devices.
/// Excludes simulators.
static bool _isIPhoneOSDevice(Map<String, dynamic> deviceProperties) {
if (deviceProperties.containsKey('platform')) {
final String platform = deviceProperties['platform'] as String;
return platform == 'com.apple.platform.iphoneos';
}
return false;
}
static bool _isAvailable(Map<String, dynamic> deviceProperties) {
return deviceProperties.containsKey('available') && (deviceProperties['available'] as bool);
}
static bool _isUSBTethered(Map<String, dynamic> deviceProperties) {
// Interface can be "usb", "network", or not present for simulators.
return deviceProperties.containsKey('interface') &&
(deviceProperties['interface'] as String).toLowerCase() == 'usb';
}
static String _sdkVersion(Map<String, dynamic> deviceProperties) {
if (deviceProperties.containsKey('operatingSystemVersion')) {
// Parse out the OS version, ignore the build number in parentheses.
// "13.3 (17C54)"
final RegExp operatingSystemRegex = RegExp(r'(.*) \(.*\)$');
final String operatingSystemVersion = deviceProperties['operatingSystemVersion'] as String;
return operatingSystemRegex.firstMatch(operatingSystemVersion.trim())?.group(1);
}
return null;
}
DarwinArch _cpuArchitecture(Map<String, dynamic> deviceProperties) {
DarwinArch cpuArchitecture;
if (deviceProperties.containsKey('architecture')) {
final String architecture = deviceProperties['architecture'] as String;
try {
cpuArchitecture = getIOSArchForName(architecture);
} catch (error) {
// Fallback to default iOS architecture. Future-proof against a theoretical version
// of Xcode that changes this string to something slightly different like "ARM64".
cpuArchitecture ??= defaultIOSArchs.first;
_logger.printError('Unknown architecture $architecture, defaulting to ${getNameForDarwinArch(cpuArchitecture)}');
}
}
return cpuArchitecture;
}
/// Error message parsed from xcdevice. null if no error.
static String _parseErrorMessage(Map<String, dynamic> deviceProperties) {
// {
// "simulator" : false,
// "operatingSystemVersion" : "13.3 (17C54)",
// "interface" : "usb",
// "available" : false,
// "platform" : "com.apple.platform.iphoneos",
// "modelCode" : "iPhone8,1",
// "identifier" : "98206e7a4afd4aedaff06e687594e089dede3c44",
// "architecture" : "arm64",
// "modelName" : "iPhone 6s",
// "name" : "iPhone",
// "error" : {
// "code" : -9,
// "failureReason" : "",
// "underlyingErrors" : [
// {
// "code" : 5,
// "failureReason" : "allowsSecureServices: 1. isConnected: 0. Platform: <DVTPlatform:0x7f804ce32880:'com.apple.platform.iphoneos':<DVTFilePath:0x7f804ce32800:'\/Users\/magder\/Applications\/Xcode_11-3-1.app\/Contents\/Developer\/Platforms\/iPhoneOS.platform'>>. DTDKDeviceIdentifierIsIDID: 0",
// "description" : "📱<DVTiOSDevice (0x7f801f190450), iPhone, iPhone, 13.3 (17C54), d83d5bc53967baa0ee18626ba87b6254b2ab5418> -- Failed _shouldMakeReadyForDevelopment check even though device is not locked by passcode.",
// "recoverySuggestion" : "",
// "domain" : "com.apple.platform.iphoneos"
// }
// ],
// "description" : "iPhone is not paired with your computer.",
// "recoverySuggestion" : "To use iPhone with Xcode, unlock it and choose to trust this computer when prompted.",
// "domain" : "com.apple.platform.iphoneos"
// }
// },
// {
// "simulator" : false,
// "operatingSystemVersion" : "13.3 (17C54)",
// "interface" : "usb",
// "available" : false,
// "platform" : "com.apple.platform.iphoneos",
// "modelCode" : "iPhone8,1",
// "identifier" : "d83d5bc53967baa0ee18626ba87b6254b2ab5418",
// "architecture" : "arm64",
// "modelName" : "iPhone 6s",
// "name" : "iPhone",
// "error" : {
// "code" : -9,
// "failureReason" : "",
// "description" : "iPhone is not paired with your computer.",
// "domain" : "com.apple.platform.iphoneos"
// }
// }
// ...
if (!deviceProperties.containsKey('error')) {
return null;
}
final Map<String, dynamic> error = deviceProperties['error'] as Map<String, dynamic>;
final StringBuffer errorMessage = StringBuffer('Error: ');
if (error.containsKey('description')) {
final String description = error['description'] as String;
errorMessage.write(description);
if (!description.endsWith('.')) {
errorMessage.write('.');
}
} else {
errorMessage.write('Xcode pairing error.');
}
if (error.containsKey('recoverySuggestion')) {
final String recoverySuggestion = error['recoverySuggestion'] as String;
errorMessage.write(' $recoverySuggestion');
}
if (error.containsKey('code') && error['code'] is int) {
final int code = error['code'] as int;
errorMessage.write(' (code $code)');
}
return errorMessage.toString();
}
/// List of all devices reporting errors.
Future<List<String>> getDiagnostics() async {
final List<dynamic> allAvailableDevices = await _getAllDevices(useCache: true);
if (allAvailableDevices == null) {
return const <String>[];
}
final List<String> diagnostics = <String>[];
for (final dynamic device in allAvailableDevices) {
if (device is! Map) {
continue;
}
final Map<String, dynamic> deviceProperties = device as Map<String, dynamic>;
final String errorMessage = _parseErrorMessage(deviceProperties);
if (errorMessage != null) {
diagnostics.add(errorMessage);
}
}
return diagnostics;
}
}

View file

@ -52,7 +52,6 @@ class MockXcode extends Mock implements Xcode {}
class MockFile extends Mock implements File {}
class MockPortForwarder extends Mock implements DevicePortForwarder {}
class MockUsage extends Mock implements Usage {}
class MockXcdevice extends Mock implements XCDevice {}
void main() {
final FakePlatform macPlatform = FakePlatform.fromPlatform(const LocalPlatform());
@ -66,17 +65,17 @@ void main() {
final List<Platform> unsupportedPlatforms = <Platform>[linuxPlatform, windowsPlatform];
testUsingContext('successfully instantiates on Mac OS', () {
IOSDevice('device-123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
IOSDevice('device-123');
}, overrides: <Type, Generator>{
Platform: () => macPlatform,
});
testUsingContext('parses major version', () {
expect(IOSDevice('device-123', name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '1.0.0').majorSdkVersion, 1);
expect(IOSDevice('device-123', name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '13.1.1').majorSdkVersion, 13);
expect(IOSDevice('device-123', name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '10').majorSdkVersion, 10);
expect(IOSDevice('device-123', name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '0').majorSdkVersion, 0);
expect(IOSDevice('device-123', name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: 'bogus').majorSdkVersion, 0);
expect(IOSDevice('device-123', sdkVersion: '1.0.0').majorSdkVersion, 1);
expect(IOSDevice('device-123', sdkVersion: '13.1.1').majorSdkVersion, 13);
expect(IOSDevice('device-123', sdkVersion: '10').majorSdkVersion, 10);
expect(IOSDevice('device-123', sdkVersion: '0').majorSdkVersion, 0);
expect(IOSDevice('device-123', sdkVersion: 'bogus').majorSdkVersion, 0);
}, overrides: <Type, Generator>{
Platform: () => macPlatform,
});
@ -84,7 +83,7 @@ void main() {
for (final Platform platform in unsupportedPlatforms) {
testUsingContext('throws UnsupportedError exception if instantiated on ${platform.operatingSystem}', () {
expect(
() { IOSDevice('device-123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64); },
() { IOSDevice('device-123'); },
throwsAssertionError,
);
}, overrides: <Type, Generator>{
@ -133,7 +132,7 @@ void main() {
});
testUsingContext(' kills all log readers & port forwarders', () async {
device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
device = IOSDevice('123');
logReader1 = createLogReader(device, appPackage1, mockProcess1);
logReader2 = createLogReader(device, appPackage2, mockProcess2);
portForwarder = createPortForwarder(forwardedPort, device);
@ -240,6 +239,9 @@ void main() {
)).thenAnswer(
(_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', ''))
);
when(mockIMobileDevice.getInfoForDevice(any, 'CPUArchitecture'))
.thenAnswer((_) => Future<String>.value('arm64'));
});
tearDown(() {
@ -250,7 +252,7 @@ void main() {
});
testUsingContext('disposing device disposes the portForwarder', () async {
final IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice('123');
device.portForwarder = mockPortForwarder;
device.setLogReader(mockApp, mockLogReader);
await device.dispose();
@ -259,8 +261,24 @@ void main() {
Platform: () => macPlatform,
});
testUsingContext('returns failed if the IOSDevice is not found', () async {
final IOSDevice device = IOSDevice('123');
when(mockIMobileDevice.getInfoForDevice(any, 'CPUArchitecture')).thenThrow(
const IOSDeviceNotFoundError(
'ideviceinfo could not find device:\n'
'No device found with udid 123, is it plugged in?\n'
'Try unlocking attached devices.'
)
);
final LaunchResult result = await device.startApp(mockApp);
expect(result.started, false);
}, overrides: <Type, Generator>{
IMobileDevice: () => mockIMobileDevice,
Platform: () => macPlatform,
});
testUsingContext(' succeeds in debug mode via mDNS', () async {
final IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice('123');
device.portForwarder = mockPortForwarder;
device.setLogReader(mockApp, mockLogReader);
final Uri uri = Uri(
@ -297,7 +315,7 @@ void main() {
testUsingContext(' .forward() will kill iproxy processes before invoking a second', () async {
const String deviceId = '123';
const int devicePort = 456;
final IOSDevice device = IOSDevice(deviceId, name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice(deviceId);
final IOSDevicePortForwarder portForwarder = IOSDevicePortForwarder(device);
bool firstRun = true;
final MockProcess successProcess = MockProcess(
@ -331,7 +349,7 @@ void main() {
});
testUsingContext(' succeeds in debug mode when mDNS fails by falling back to manual protocol discovery', () async {
final IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice('123');
device.portForwarder = mockPortForwarder;
device.setLogReader(mockApp, mockLogReader);
// Now that the reader is used, start writing messages to it.
@ -363,7 +381,7 @@ void main() {
});
testUsingContext(' fails in debug mode when mDNS fails and when Observatory URI is malformed', () async {
final IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice('123');
device.portForwarder = mockPortForwarder;
device.setLogReader(mockApp, mockLogReader);
@ -395,7 +413,7 @@ void main() {
});
testUsingContext('succeeds in release mode', () async {
final IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice('123');
final LaunchResult launchResult = await device.startApp(mockApp,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
@ -413,7 +431,7 @@ void main() {
});
testUsingContext('succeeds with --cache-sksl', () async {
final IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice('123');
device.setLogReader(mockApp, mockLogReader);
final Uri uri = Uri(
scheme: 'http',
@ -457,7 +475,7 @@ void main() {
});
testUsingContext('succeeds with --device-vmservice-port', () async {
final IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice('123');
device.setLogReader(mockApp, mockLogReader);
final Uri uri = Uri(
scheme: 'http',
@ -571,7 +589,7 @@ void main() {
final IOSApp app = await AbsoluteBuildableIOSApp.fromProject(
FlutterProject.fromDirectory(projectDir).ios);
final IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice('123');
// Pre-create the expected build products.
targetBuildDir.createSync(recursive: true);
@ -679,7 +697,7 @@ void main() {
});
testUsingContext('installApp() invokes process with correct environment', () async {
final IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice('123');
const String bundlePath = '/path/to/bundle';
final List<String> args = <String>[installerPath, '-i', bundlePath];
when(mockApp.deviceBundlePath).thenReturn(bundlePath);
@ -701,7 +719,7 @@ void main() {
});
testUsingContext('isAppInstalled() invokes process with correct environment', () async {
final IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice('123');
final List<String> args = <String>[installerPath, '--list-apps'];
when(mockProcessManager.run(args, environment: env))
.thenAnswer(
@ -718,7 +736,7 @@ void main() {
});
testUsingContext('uninstallApp() invokes process with correct environment', () async {
final IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice('123');
final List<String> args = <String>[installerPath, '-U', appId];
when(mockApp.id).thenReturn(appId);
when(mockProcessManager.run(args, environment: env))
@ -737,61 +755,90 @@ void main() {
});
group('getAttachedDevices', () {
MockXcdevice mockXcdevice;
MockIMobileDevice mockIMobileDevice;
setUp(() {
mockXcdevice = MockXcdevice();
mockIMobileDevice = MockIMobileDevice();
});
testUsingContext('return no devices if Xcode is not installed', () async {
when(mockIMobileDevice.isInstalled).thenReturn(false);
expect(await IOSDevice.getAttachedDevices(), isEmpty);
}, overrides: <Type, Generator>{
IMobileDevice: () => mockIMobileDevice,
Platform: () => macPlatform,
});
testUsingContext('returns no devices if none are attached', () async {
when(globals.iMobileDevice.isInstalled).thenReturn(true);
when(globals.iMobileDevice.getAvailableDeviceIDs())
.thenAnswer((Invocation invocation) => Future<String>.value(''));
final List<IOSDevice> devices = await IOSDevice.getAttachedDevices();
expect(devices, isEmpty);
}, overrides: <Type, Generator>{
IMobileDevice: () => mockIMobileDevice,
Platform: () => macPlatform,
});
final List<Platform> unsupportedPlatforms = <Platform>[linuxPlatform, windowsPlatform];
for (final Platform unsupportedPlatform in unsupportedPlatforms) {
testWithoutContext('throws Unsupported Operation exception on ${unsupportedPlatform.operatingSystem}', () async {
when(mockXcdevice.isInstalled).thenReturn(false);
for (final Platform platform in unsupportedPlatforms) {
testUsingContext('throws Unsupported Operation exception on ${platform.operatingSystem}', () async {
when(globals.iMobileDevice.isInstalled).thenReturn(false);
when(globals.iMobileDevice.getAvailableDeviceIDs())
.thenAnswer((Invocation invocation) => Future<String>.value(''));
expect(
() async { await IOSDevice.getAttachedDevices(unsupportedPlatform, mockXcdevice); },
() async { await IOSDevice.getAttachedDevices(); },
throwsA(isA<UnsupportedError>()),
);
}, overrides: <Type, Generator>{
IMobileDevice: () => mockIMobileDevice,
Platform: () => platform,
});
}
testUsingContext('returns attached devices', () async {
when(mockXcdevice.isInstalled).thenReturn(true);
final IOSDevice device = IOSDevice('d83d5bc53967baa0ee18626ba87b6254b2ab5418', name: 'Paired iPhone', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
when(mockXcdevice.getAvailableTetheredIOSDevices())
.thenAnswer((Invocation invocation) => Future<List<IOSDevice>>.value(<IOSDevice>[device]));
final List<IOSDevice> devices = await IOSDevice.getAttachedDevices(macPlatform, mockXcdevice);
expect(devices, hasLength(1));
expect(identical(devices.first, device), isTrue);
when(globals.iMobileDevice.isInstalled).thenReturn(true);
when(globals.iMobileDevice.getAvailableDeviceIDs())
.thenAnswer((Invocation invocation) => Future<String>.value('''
98206e7a4afd4aedaff06e687594e089dede3c44
f577a7903cc54959be2e34bc4f7f80b7009efcf4
'''));
when(globals.iMobileDevice.getInfoForDevice('98206e7a4afd4aedaff06e687594e089dede3c44', 'DeviceName'))
.thenAnswer((_) => Future<String>.value('La tele me regarde'));
when(globals.iMobileDevice.getInfoForDevice('98206e7a4afd4aedaff06e687594e089dede3c44', 'ProductVersion'))
.thenAnswer((_) => Future<String>.value('10.3.2'));
when(globals.iMobileDevice.getInfoForDevice('f577a7903cc54959be2e34bc4f7f80b7009efcf4', 'DeviceName'))
.thenAnswer((_) => Future<String>.value('Puits sans fond'));
when(globals.iMobileDevice.getInfoForDevice('f577a7903cc54959be2e34bc4f7f80b7009efcf4', 'ProductVersion'))
.thenAnswer((_) => Future<String>.value('11.0'));
final List<IOSDevice> devices = await IOSDevice.getAttachedDevices();
expect(devices, hasLength(2));
expect(devices[0].id, '98206e7a4afd4aedaff06e687594e089dede3c44');
expect(devices[0].name, 'La tele me regarde');
expect(devices[1].id, 'f577a7903cc54959be2e34bc4f7f80b7009efcf4');
expect(devices[1].name, 'Puits sans fond');
}, overrides: <Type, Generator>{
IMobileDevice: () => mockIMobileDevice,
Platform: () => macPlatform,
});
});
group('getDiagnostics', () {
MockXcdevice mockXcdevice;
setUp(() {
mockXcdevice = MockXcdevice();
});
final List<Platform> unsupportedPlatforms = <Platform>[linuxPlatform, windowsPlatform];
for (final Platform unsupportedPlatform in unsupportedPlatforms) {
testWithoutContext('throws returns platform diagnostic exception on ${unsupportedPlatform.operatingSystem}', () async {
when(mockXcdevice.isInstalled).thenReturn(false);
expect((await IOSDevice.getDiagnostics(unsupportedPlatform, mockXcdevice)).first, 'Control of iOS devices or simulators only supported on macOS.');
});
}
testUsingContext('returns diagnostics', () async {
when(mockXcdevice.isInstalled).thenReturn(true);
when(mockXcdevice.getDiagnostics())
.thenAnswer((Invocation invocation) => Future<List<String>>.value(<String>['Generic pairing error']));
final List<String> diagnostics = await IOSDevice.getDiagnostics(macPlatform, mockXcdevice);
expect(diagnostics, hasLength(1));
expect(diagnostics.first, 'Generic pairing error');
testUsingContext('returns attached devices and ignores devices that cannot be found by ideviceinfo', () async {
when(globals.iMobileDevice.isInstalled).thenReturn(true);
when(globals.iMobileDevice.getAvailableDeviceIDs())
.thenAnswer((Invocation invocation) => Future<String>.value('''
98206e7a4afd4aedaff06e687594e089dede3c44
f577a7903cc54959be2e34bc4f7f80b7009efcf4
'''));
when(globals.iMobileDevice.getInfoForDevice('98206e7a4afd4aedaff06e687594e089dede3c44', 'DeviceName'))
.thenAnswer((_) => Future<String>.value('La tele me regarde'));
when(globals.iMobileDevice.getInfoForDevice('f577a7903cc54959be2e34bc4f7f80b7009efcf4', 'DeviceName'))
.thenThrow(const IOSDeviceNotFoundError('Device not found'));
final List<IOSDevice> devices = await IOSDevice.getAttachedDevices();
expect(devices, hasLength(1));
expect(devices[0].id, '98206e7a4afd4aedaff06e687594e089dede3c44');
expect(devices[0].name, 'La tele me regarde');
}, overrides: <Type, Generator>{
IMobileDevice: () => mockIMobileDevice,
Platform: () => macPlatform,
});
});
@ -830,7 +877,7 @@ Runner(UIKit)[297] <Notice>: E is for enpitsu"
return Future<Process>.value(mockProcess);
});
final IOSDevice device = IOSDevice('123456', name: 'iPhone 1', sdkVersion: '10.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice('123456');
final DeviceLogReader logReader = device.getLogReader(
app: await BuildableIOSApp.fromProject(mockIosProject),
);
@ -841,7 +888,6 @@ Runner(UIKit)[297] <Notice>: E is for enpitsu"
IMobileDevice: () => mockIMobileDevice,
Platform: () => macPlatform,
});
testUsingContext('includes multi-line Flutter logs in the output', () async {
when(mockIMobileDevice.startLogger('123456')).thenAnswer((Invocation invocation) {
final Process mockProcess = MockProcess(
@ -856,7 +902,7 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
return Future<Process>.value(mockProcess);
});
final IOSDevice device = IOSDevice('123456', name: 'iPhone 1', sdkVersion: '10.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice('123456');
final DeviceLogReader logReader = device.getLogReader(
app: await BuildableIOSApp.fromProject(mockIosProject),
);
@ -886,7 +932,7 @@ flutter:
globals.fs.file('.packages').createSync();
final FlutterProject flutterProject = FlutterProject.current();
expect(IOSDevice('test', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64).isSupportedForProject(flutterProject), true);
expect(IOSDevice('test').isSupportedForProject(flutterProject), true);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
@ -898,7 +944,7 @@ flutter:
globals.fs.directory('ios').createSync();
final FlutterProject flutterProject = FlutterProject.current();
expect(IOSDevice('test', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64).isSupportedForProject(flutterProject), true);
expect(IOSDevice('test').isSupportedForProject(flutterProject), true);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
@ -910,7 +956,7 @@ flutter:
globals.fs.file('.packages').createSync();
final FlutterProject flutterProject = FlutterProject.current();
expect(IOSDevice('test', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64).isSupportedForProject(flutterProject), false);
expect(IOSDevice('test').isSupportedForProject(flutterProject), false);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),

View file

@ -6,8 +6,6 @@ import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart' show ProcessException, ProcessResult;
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/ios/devices.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/macos/xcode.dart';
import 'package:mockito/mockito.dart';
@ -23,494 +21,160 @@ class MockPlatform extends Mock implements Platform {}
void main() {
ProcessManager processManager;
Xcode xcode;
MockXcodeProjectInterpreter mockXcodeProjectInterpreter;
MockPlatform platform;
Logger logger;
FileSystem fileSystem;
setUp(() {
logger = MockLogger();
fileSystem = MemoryFileSystem();
processManager = MockProcessManager();
mockXcodeProjectInterpreter = MockXcodeProjectInterpreter();
platform = MockPlatform();
xcode = Xcode(
logger: logger,
platform: platform,
fileSystem: fileSystem,
processManager: processManager,
xcodeProjectInterpreter: mockXcodeProjectInterpreter,
);
});
group('Xcode', () {
Xcode xcode;
MockXcodeProjectInterpreter mockXcodeProjectInterpreter;
MockPlatform platform;
FileSystem fileSystem;
testWithoutContext('xcodeSelectPath returns null when xcode-select is not installed', () {
when(processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
.thenThrow(const ProcessException('/usr/bin/xcode-select', <String>['--print-path']));
expect(xcode.xcodeSelectPath, isNull);
when(processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
.thenThrow(ArgumentError('Invalid argument(s): Cannot find executable for /usr/bin/xcode-select'));
setUp(() {
fileSystem = MemoryFileSystem();
mockXcodeProjectInterpreter = MockXcodeProjectInterpreter();
platform = MockPlatform();
xcode = Xcode(
logger: logger,
platform: platform,
fileSystem: fileSystem,
processManager: processManager,
xcodeProjectInterpreter: mockXcodeProjectInterpreter,
);
});
testWithoutContext('xcodeSelectPath returns null when xcode-select is not installed', () {
when(processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
.thenThrow(const ProcessException('/usr/bin/xcode-select', <String>['--print-path']));
expect(xcode.xcodeSelectPath, isNull);
when(processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
.thenThrow(ArgumentError('Invalid argument(s): Cannot find executable for /usr/bin/xcode-select'));
expect(xcode.xcodeSelectPath, isNull);
});
testWithoutContext('xcodeSelectPath returns path when xcode-select is installed', () {
const String xcodePath = '/Applications/Xcode8.0.app/Contents/Developer';
when(processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
.thenReturn(ProcessResult(1, 0, xcodePath, ''));
expect(xcode.xcodeSelectPath, xcodePath);
});
testWithoutContext('xcodeVersionSatisfactory is false when version is less than minimum', () {
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(9);
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(0);
expect(xcode.isVersionSatisfactory, isFalse);
});
testWithoutContext('xcodeVersionSatisfactory is false when xcodebuild tools are not installed', () {
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(false);
expect(xcode.isVersionSatisfactory, isFalse);
});
testWithoutContext('xcodeVersionSatisfactory is true when version meets minimum', () {
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(10);
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(2);
expect(xcode.isVersionSatisfactory, isTrue);
});
testWithoutContext('xcodeVersionSatisfactory is true when major version exceeds minimum', () {
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(11);
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(2);
expect(xcode.isVersionSatisfactory, isTrue);
});
testWithoutContext('xcodeVersionSatisfactory is true when minor version exceeds minimum', () {
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(10);
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(3);
expect(xcode.isVersionSatisfactory, isTrue);
});
testWithoutContext('isInstalledAndMeetsVersionCheck is false when not macOS', () {
when(platform.isMacOS).thenReturn(false);
expect(xcode.isInstalledAndMeetsVersionCheck, isFalse);
});
testWithoutContext('isInstalledAndMeetsVersionCheck is false when not installed', () {
when(platform.isMacOS).thenReturn(true);
const String xcodePath = '/Applications/Xcode8.0.app/Contents/Developer';
when(processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
.thenReturn(ProcessResult(1, 0, xcodePath, ''));
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(false);
expect(xcode.isInstalledAndMeetsVersionCheck, isFalse);
});
testWithoutContext('isInstalledAndMeetsVersionCheck is false when no xcode-select', () {
when(platform.isMacOS).thenReturn(true);
when(processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
.thenReturn(ProcessResult(1, 127, '', 'ERROR'));
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(10);
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(2);
expect(xcode.isInstalledAndMeetsVersionCheck, isFalse);
});
testWithoutContext('isInstalledAndMeetsVersionCheck is false when version not satisfied', () {
when(platform.isMacOS).thenReturn(true);
const String xcodePath = '/Applications/Xcode8.0.app/Contents/Developer';
when(processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
.thenReturn(ProcessResult(1, 0, xcodePath, ''));
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(9);
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(0);
expect(xcode.isInstalledAndMeetsVersionCheck, isFalse);
});
testWithoutContext('isInstalledAndMeetsVersionCheck is true when macOS and installed and version is satisfied', () {
when(platform.isMacOS).thenReturn(true);
const String xcodePath = '/Applications/Xcode8.0.app/Contents/Developer';
when(processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
.thenReturn(ProcessResult(1, 0, xcodePath, ''));
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(10);
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(2);
expect(xcode.isInstalledAndMeetsVersionCheck, isTrue);
});
testWithoutContext('eulaSigned is false when clang is not installed', () {
when(processManager.runSync(<String>['/usr/bin/xcrun', 'clang']))
.thenThrow(const ProcessException('/usr/bin/xcrun', <String>['clang']));
expect(xcode.eulaSigned, isFalse);
});
testWithoutContext('eulaSigned is false when clang output indicates EULA not yet accepted', () {
when(processManager.runSync(<String>['/usr/bin/xcrun', 'clang']))
.thenReturn(ProcessResult(1, 1, '', 'Xcode EULA has not been accepted.\nLaunch Xcode and accept the license.'));
expect(xcode.eulaSigned, isFalse);
});
testWithoutContext('eulaSigned is true when clang output indicates EULA has been accepted', () {
when(processManager.runSync(<String>['/usr/bin/xcrun', 'clang']))
.thenReturn(ProcessResult(1, 1, '', 'clang: error: no input files'));
expect(xcode.eulaSigned, isTrue);
});
testWithoutContext('SDK name', () {
expect(getNameForSdk(SdkType.iPhone), 'iphoneos');
expect(getNameForSdk(SdkType.iPhoneSimulator), 'iphonesimulator');
expect(getNameForSdk(SdkType.macOS), 'macosx');
});
expect(xcode.xcodeSelectPath, isNull);
});
group('xcdevice', () {
XCDevice xcdevice;
MockXcode mockXcode;
testWithoutContext('xcodeSelectPath returns path when xcode-select is installed', () {
const String xcodePath = '/Applications/Xcode8.0.app/Contents/Developer';
when(processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
.thenReturn(ProcessResult(1, 0, xcodePath, ''));
setUp(() {
mockXcode = MockXcode();
xcdevice = XCDevice(
processManager: processManager,
logger: logger,
xcode: mockXcode,
);
});
expect(xcode.xcodeSelectPath, xcodePath);
});
group('installed', () {
testWithoutContext('Xcode not installed', () {
when(mockXcode.isInstalledAndMeetsVersionCheck).thenReturn(false);
expect(xcdevice.isInstalled, false);
});
testWithoutContext('xcodeVersionSatisfactory is false when version is less than minimum', () {
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(9);
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(0);
testWithoutContext("xcrun can't find xcdevice", () {
when(mockXcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
expect(xcode.isVersionSatisfactory, isFalse);
});
when(processManager.runSync(<String>['xcrun', '--find', 'xcdevice']))
.thenThrow(const ProcessException('xcrun', <String>['--find', 'xcdevice']));
expect(xcdevice.isInstalled, false);
verify(processManager.runSync(any)).called(1);
});
testWithoutContext('xcodeVersionSatisfactory is false when xcodebuild tools are not installed', () {
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(false);
testWithoutContext('is installed', () {
when(mockXcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
expect(xcode.isVersionSatisfactory, isFalse);
});
when(processManager.runSync(<String>['xcrun', '--find', 'xcdevice']))
.thenReturn(ProcessResult(1, 0, '/path/to/xcdevice', ''));
expect(xcdevice.isInstalled, true);
});
});
testWithoutContext('xcodeVersionSatisfactory is true when version meets minimum', () {
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(10);
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(2);
group('available devices', () {
final FakePlatform macPlatform = FakePlatform.fromPlatform(const LocalPlatform());
macPlatform.operatingSystem = 'macos';
expect(xcode.isVersionSatisfactory, isTrue);
});
testWithoutContext('Xcode not installed', () async {
when(mockXcode.isInstalledAndMeetsVersionCheck).thenReturn(false);
testWithoutContext('xcodeVersionSatisfactory is true when major version exceeds minimum', () {
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(11);
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(2);
expect(await xcdevice.getAvailableTetheredIOSDevices(), isEmpty);
verifyNever(processManager.run(any));
});
expect(xcode.isVersionSatisfactory, isTrue);
});
testWithoutContext('xcdevice fails', () async {
when(mockXcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
testWithoutContext('xcodeVersionSatisfactory is true when minor version exceeds minimum', () {
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(10);
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(3);
when(processManager.runSync(<String>['xcrun', '--find', 'xcdevice']))
.thenReturn(ProcessResult(1, 0, '/path/to/xcdevice', ''));
expect(xcode.isVersionSatisfactory, isTrue);
});
when(processManager.run(<String>['xcrun', 'xcdevice', 'list', '--timeout', '1']))
.thenThrow(const ProcessException('xcrun', <String>['xcdevice', 'list', '--timeout', '1']));
testWithoutContext('isInstalledAndMeetsVersionCheck is false when not macOS', () {
when(platform.isMacOS).thenReturn(false);
expect(await xcdevice.getAvailableTetheredIOSDevices(), isEmpty);
});
expect(xcode.isInstalledAndMeetsVersionCheck, isFalse);
});
testUsingContext('returns devices', () async {
when(mockXcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
testWithoutContext('isInstalledAndMeetsVersionCheck is false when not installed', () {
when(platform.isMacOS).thenReturn(true);
const String xcodePath = '/Applications/Xcode8.0.app/Contents/Developer';
when(processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
.thenReturn(ProcessResult(1, 0, xcodePath, ''));
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(false);
when(processManager.runSync(<String>['xcrun', '--find', 'xcdevice']))
.thenReturn(ProcessResult(1, 0, '/path/to/xcdevice', ''));
expect(xcode.isInstalledAndMeetsVersionCheck, isFalse);
});
const String devicesOutput = '''
[
{
"simulator" : true,
"operatingSystemVersion" : "13.3 (17K446)",
"available" : true,
"platform" : "com.apple.platform.appletvsimulator",
"modelCode" : "AppleTV5,3",
"identifier" : "CBB5E1ED-2172-446E-B4E7-F2B5823DBBA6",
"architecture" : "x86_64",
"modelName" : "Apple TV",
"name" : "Apple TV"
},
{
"simulator" : false,
"operatingSystemVersion" : "13.3 (17C54)",
"interface" : "usb",
"available" : true,
"platform" : "com.apple.platform.iphoneos",
"modelCode" : "iPhone8,1",
"identifier" : "d83d5bc53967baa0ee18626ba87b6254b2ab5418",
"architecture" : "arm64",
"modelName" : "iPhone 6s",
"name" : "An iPhone (Space Gray)"
},
{
"simulator" : false,
"operatingSystemVersion" : "10.1 (14C54)",
"interface" : "usb",
"available" : true,
"platform" : "com.apple.platform.iphoneos",
"modelCode" : "iPad11,4",
"identifier" : "98206e7a4afd4aedaff06e687594e089dede3c44",
"architecture" : "armv7",
"modelName" : "iPad Air 3rd Gen",
"name" : "iPad 1"
},
{
"simulator" : false,
"operatingSystemVersion" : "10.1 (14C54)",
"interface" : "network",
"available" : true,
"platform" : "com.apple.platform.iphoneos",
"modelCode" : "iPad11,4",
"identifier" : "234234234234234234345445687594e089dede3c44",
"architecture" : "arm64",
"modelName" : "iPad Air 3rd Gen",
"name" : "A networked iPad"
},
{
"simulator" : false,
"operatingSystemVersion" : "10.1 (14C54)",
"interface" : "usb",
"available" : true,
"platform" : "com.apple.platform.iphoneos",
"modelCode" : "iPad11,4",
"identifier" : "f577a7903cc54959be2e34bc4f7f80b7009efcf4",
"architecture" : "BOGUS",
"modelName" : "iPad Air 3rd Gen",
"name" : "iPad 2"
},
{
"simulator" : true,
"operatingSystemVersion" : "6.1.1 (17S445)",
"available" : true,
"platform" : "com.apple.platform.watchsimulator",
"modelCode" : "Watch5,4",
"identifier" : "2D74FB11-88A0-44D0-B81E-C0C142B1C94A",
"architecture" : "i386",
"modelName" : "Apple Watch Series 5 - 44mm",
"name" : "Apple Watch Series 5 - 44mm"
},
{
"simulator" : false,
"operatingSystemVersion" : "13.3 (17C54)",
"interface" : "usb",
"available" : false,
"platform" : "com.apple.platform.iphoneos",
"modelCode" : "iPhone8,1",
"identifier" : "c4ca6f7a53027d1b7e4972e28478e7a28e2faee2",
"architecture" : "arm64",
"modelName" : "iPhone 6s",
"name" : "iPhone",
"error" : {
"code" : -9,
"failureReason" : "",
"description" : "iPhone is not paired with your computer.",
"domain" : "com.apple.platform.iphoneos"
}
}
]
''';
testWithoutContext('isInstalledAndMeetsVersionCheck is false when no xcode-select', () {
when(platform.isMacOS).thenReturn(true);
when(processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
.thenReturn(ProcessResult(1, 127, '', 'ERROR'));
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(10);
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(2);
when(processManager.run(<String>['xcrun', 'xcdevice', 'list', '--timeout', '1']))
.thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(1, 0, devicesOutput, '')));
final List<IOSDevice> devices = await xcdevice.getAvailableTetheredIOSDevices();
expect(devices, hasLength(3));
expect(devices[0].id, 'd83d5bc53967baa0ee18626ba87b6254b2ab5418');
expect(devices[0].name, 'An iPhone (Space Gray)');
expect(await devices[0].sdkNameAndVersion, 'iOS 13.3');
expect(devices[0].cpuArchitecture, DarwinArch.arm64);
expect(devices[1].id, '98206e7a4afd4aedaff06e687594e089dede3c44');
expect(devices[1].name, 'iPad 1');
expect(await devices[1].sdkNameAndVersion, 'iOS 10.1');
expect(devices[1].cpuArchitecture, DarwinArch.armv7);
expect(devices[2].id, 'f577a7903cc54959be2e34bc4f7f80b7009efcf4');
expect(devices[2].name, 'iPad 2');
expect(await devices[2].sdkNameAndVersion, 'iOS 10.1');
expect(devices[2].cpuArchitecture, DarwinArch.arm64); // Defaults to arm64 for unknown architecture.
}, overrides: <Type, Generator>{
Platform: () => macPlatform,
});
});
expect(xcode.isInstalledAndMeetsVersionCheck, isFalse);
});
group('diagnostics', () {
final FakePlatform macPlatform = FakePlatform.fromPlatform(const LocalPlatform());
macPlatform.operatingSystem = 'macos';
testWithoutContext('isInstalledAndMeetsVersionCheck is false when version not satisfied', () {
when(platform.isMacOS).thenReturn(true);
const String xcodePath = '/Applications/Xcode8.0.app/Contents/Developer';
when(processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
.thenReturn(ProcessResult(1, 0, xcodePath, ''));
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(9);
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(0);
testWithoutContext('Xcode not installed', () async {
when(mockXcode.isInstalledAndMeetsVersionCheck).thenReturn(false);
expect(xcode.isInstalledAndMeetsVersionCheck, isFalse);
});
expect(await xcdevice.getDiagnostics(), isEmpty);
verifyNever(processManager.run(any));
});
testWithoutContext('isInstalledAndMeetsVersionCheck is true when macOS and installed and version is satisfied', () {
when(platform.isMacOS).thenReturn(true);
const String xcodePath = '/Applications/Xcode8.0.app/Contents/Developer';
when(processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
.thenReturn(ProcessResult(1, 0, xcodePath, ''));
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(10);
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(2);
testWithoutContext('xcdevice fails', () async {
when(mockXcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
expect(xcode.isInstalledAndMeetsVersionCheck, isTrue);
});
when(processManager.runSync(<String>['xcrun', '--find', 'xcdevice']))
.thenReturn(ProcessResult(1, 0, '/path/to/xcdevice', ''));
testWithoutContext('eulaSigned is false when clang is not installed', () {
when(processManager.runSync(<String>['/usr/bin/xcrun', 'clang']))
.thenThrow(const ProcessException('/usr/bin/xcrun', <String>['clang']));
when(processManager.run(<String>['xcrun', 'xcdevice', 'list', '--timeout', '1']))
.thenThrow(const ProcessException('xcrun', <String>['xcdevice', 'list', '--timeout', '1']));
expect(xcode.eulaSigned, isFalse);
});
expect(await xcdevice.getDiagnostics(), isEmpty);
});
testWithoutContext('eulaSigned is false when clang output indicates EULA not yet accepted', () {
when(processManager.runSync(<String>['/usr/bin/xcrun', 'clang']))
.thenReturn(ProcessResult(1, 1, '', 'Xcode EULA has not been accepted.\nLaunch Xcode and accept the license.'));
testUsingContext('uses cache', () async {
when(mockXcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
expect(xcode.eulaSigned, isFalse);
});
when(processManager.runSync(<String>['xcrun', '--find', 'xcdevice']))
.thenReturn(ProcessResult(1, 0, '/path/to/xcdevice', ''));
testWithoutContext('eulaSigned is true when clang output indicates EULA has been accepted', () {
when(processManager.runSync(<String>['/usr/bin/xcrun', 'clang']))
.thenReturn(ProcessResult(1, 1, '', 'clang: error: no input files'));
const String devicesOutput = '''
[
{
"simulator" : false,
"operatingSystemVersion" : "13.3 (17C54)",
"interface" : "network",
"available" : false,
"platform" : "com.apple.platform.iphoneos",
"modelCode" : "iPhone8,1",
"identifier" : "d83d5bc53967baa0ee18626ba87b6254b2ab5418",
"architecture" : "arm64",
"modelName" : "iPhone 6s",
"error" : {
"code" : -13,
"failureReason" : "",
"domain" : "com.apple.platform.iphoneos"
}
}
]
''';
expect(xcode.eulaSigned, isTrue);
});
when(processManager.run(<String>['xcrun', 'xcdevice', 'list', '--timeout', '1']))
.thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(1, 0, devicesOutput, '')));
await xcdevice.getAvailableTetheredIOSDevices();
final List<String> errors = await xcdevice.getDiagnostics();
expect(errors, hasLength(1));
verify(processManager.run(any)).called(1);
}, overrides: <Type, Generator>{
Platform: () => macPlatform,
});
testUsingContext('returns error message', () async {
when(mockXcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
when(processManager.runSync(<String>['xcrun', '--find', 'xcdevice']))
.thenReturn(ProcessResult(1, 0, '/path/to/xcdevice', ''));
const String devicesOutput = '''
[
{
"simulator" : false,
"operatingSystemVersion" : "13.3 (17C54)",
"interface" : "usb",
"available" : false,
"platform" : "com.apple.platform.iphoneos",
"modelCode" : "iPhone8,1",
"identifier" : "98206e7a4afd4aedaff06e687594e089dede3c44",
"architecture" : "arm64",
"modelName" : "iPhone 6s",
"name" : "An iPhone (Space Gray)",
"error" : {
"code" : -9,
"failureReason" : "",
"underlyingErrors" : [
{
"code" : 5,
"failureReason" : "allowsSecureServices: 1. isConnected: 0. Platform: <DVTPlatform:0x7f804ce32880:'com.apple.platform.iphoneos':<DVTFilePath:0x7f804ce32800:'\/Users\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/iPhoneOS.platform'>>. DTDKDeviceIdentifierIsIDID: 0",
"description" : "📱<DVTiOSDevice (0x7f801f190450), iPhone, iPhone, 13.3 (17C54), d83d5bc53967baa0ee18626ba87b6254b2ab5418> -- Failed _shouldMakeReadyForDevelopment check even though device is not locked by passcode.",
"recoverySuggestion" : "",
"domain" : "com.apple.platform.iphoneos"
}
],
"description" : "iPhone is not paired with your computer.",
"recoverySuggestion" : "To use iPhone with Xcode, unlock it and choose to trust this computer when prompted.",
"domain" : "com.apple.platform.iphoneos"
}
},
{
"simulator" : false,
"operatingSystemVersion" : "13.3 (17C54)",
"interface" : "usb",
"available" : false,
"platform" : "com.apple.platform.iphoneos",
"modelCode" : "iPhone8,1",
"identifier" : "d83d5bc53967baa0ee18626ba87b6254b2ab5418",
"architecture" : "arm64",
"modelName" : "iPhone 6s",
"name" : "iPhone",
"error" : {
"code" : -9,
"failureReason" : "",
"description" : "iPhone is not paired with your computer",
"domain" : "com.apple.platform.iphoneos"
}
},
{
"simulator" : false,
"operatingSystemVersion" : "13.3 (17C54)",
"interface" : "network",
"available" : false,
"platform" : "com.apple.platform.iphoneos",
"modelCode" : "iPhone8,1",
"identifier" : "d83d5bc53967baa0ee18626ba87b6254b2ab5418",
"architecture" : "arm64",
"modelName" : "iPhone 6s",
"error" : {
"code" : -13,
"failureReason" : "",
"domain" : "com.apple.platform.iphoneos"
}
}
]
''';
when(processManager.run(<String>['xcrun', 'xcdevice', 'list', '--timeout', '1']))
.thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(1, 0, devicesOutput, '')));
final List<String> errors = await xcdevice.getDiagnostics();
expect(errors, hasLength(3));
expect(errors[0], 'Error: iPhone is not paired with your computer. To use iPhone with Xcode, unlock it and choose to trust this computer when prompted. (code -9)');
expect(errors[1], 'Error: iPhone is not paired with your computer. (code -9)');
expect(errors[2], 'Error: Xcode pairing error. (code -13)');
}, overrides: <Type, Generator>{
Platform: () => macPlatform,
});
});
testWithoutContext('SDK name', () {
expect(getNameForSdk(SdkType.iPhone), 'iphoneos');
expect(getNameForSdk(SdkType.iPhoneSimulator), 'iphonesimulator');
expect(getNameForSdk(SdkType.macOS), 'macosx');
});
}
class MockLogger extends Mock implements Logger {}
class MockXcode extends Mock implements Xcode {}