mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
This reverts commit 3aa7a80053
.
This commit is contained in:
parent
eb4b6dc91b
commit
ce6fbf6668
@ -166,11 +166,6 @@ Future<T> runInContext<T>(
|
|||||||
fileSystem: globals.fs,
|
fileSystem: globals.fs,
|
||||||
xcodeProjectInterpreter: xcodeProjectInterpreter,
|
xcodeProjectInterpreter: xcodeProjectInterpreter,
|
||||||
),
|
),
|
||||||
XCDevice: () => XCDevice(
|
|
||||||
processManager: globals.processManager,
|
|
||||||
logger: globals.logger,
|
|
||||||
xcode: globals.xcode,
|
|
||||||
),
|
|
||||||
XcodeProjectInterpreter: () => XcodeProjectInterpreter(
|
XcodeProjectInterpreter: () => XcodeProjectInterpreter(
|
||||||
logger: globals.logger,
|
logger: globals.logger,
|
||||||
processManager: globals.processManager,
|
processManager: globals.processManager,
|
||||||
|
@ -57,8 +57,6 @@ Xcode get xcode => context.get<Xcode>();
|
|||||||
FlutterVersion get flutterVersion => context.get<FlutterVersion>();
|
FlutterVersion get flutterVersion => context.get<FlutterVersion>();
|
||||||
IMobileDevice get iMobileDevice => context.get<IMobileDevice>();
|
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
|
/// Display an error level message to the user. Commands should use this if they
|
||||||
/// fail in some way.
|
/// fail in some way.
|
||||||
///
|
///
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:platform/platform.dart';
|
|
||||||
|
|
||||||
import '../application_package.dart';
|
import '../application_package.dart';
|
||||||
import '../artifacts.dart';
|
import '../artifacts.dart';
|
||||||
@ -19,7 +18,6 @@ import '../build_info.dart';
|
|||||||
import '../convert.dart';
|
import '../convert.dart';
|
||||||
import '../device.dart';
|
import '../device.dart';
|
||||||
import '../globals.dart' as globals;
|
import '../globals.dart' as globals;
|
||||||
import '../macos/xcode.dart';
|
|
||||||
import '../mdns_discovery.dart';
|
import '../mdns_discovery.dart';
|
||||||
import '../project.dart';
|
import '../project.dart';
|
||||||
import '../protocol_discovery.dart';
|
import '../protocol_discovery.dart';
|
||||||
@ -113,18 +111,11 @@ class IOSDevices extends PollingDeviceDiscovery {
|
|||||||
bool get canListAnything => iosWorkflow.canListDevices;
|
bool get canListAnything => iosWorkflow.canListDevices;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Device>> pollingGetDevices() => IOSDevice.getAttachedDevices(globals.platform, globals.xcdevice);
|
Future<List<Device>> pollingGetDevices() => IOSDevice.getAttachedDevices();
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<String>> getDiagnostics() => IOSDevice.getDiagnostics(globals.platform, globals.xcdevice);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class IOSDevice extends Device {
|
class IOSDevice extends Device {
|
||||||
IOSDevice(String id, {
|
IOSDevice(String id, { this.name, String sdkVersion })
|
||||||
@required this.name,
|
|
||||||
@required this.cpuArchitecture,
|
|
||||||
@required String sdkVersion,
|
|
||||||
})
|
|
||||||
: _sdkVersion = sdkVersion,
|
: _sdkVersion = sdkVersion,
|
||||||
super(
|
super(
|
||||||
id,
|
id,
|
||||||
@ -166,8 +157,6 @@ class IOSDevice extends Device {
|
|||||||
@override
|
@override
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
final DarwinArch cpuArchitecture;
|
|
||||||
|
|
||||||
Map<IOSApp, DeviceLogReader> _logReaders;
|
Map<IOSApp, DeviceLogReader> _logReaders;
|
||||||
|
|
||||||
DevicePortForwarder _portForwarder;
|
DevicePortForwarder _portForwarder;
|
||||||
@ -181,20 +170,34 @@ class IOSDevice extends Device {
|
|||||||
@override
|
@override
|
||||||
bool get supportsStartPaused => false;
|
bool get supportsStartPaused => false;
|
||||||
|
|
||||||
static Future<List<IOSDevice>> getAttachedDevices(Platform platform, XCDevice xcdevice) async {
|
static Future<List<IOSDevice>> getAttachedDevices() async {
|
||||||
if (!platform.isMacOS) {
|
if (!globals.platform.isMacOS) {
|
||||||
throw UnsupportedError('Control of iOS devices or simulators only supported on macOS.');
|
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 {
|
try {
|
||||||
if (!platform.isMacOS) {
|
final String deviceName = await globals.iMobileDevice.getInfoForDevice(id, 'DeviceName');
|
||||||
return const <String>['Control of iOS devices or simulators only supported on macOS.'];
|
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 devices;
|
||||||
return await xcdevice.getDiagnostics();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -277,13 +280,24 @@ class IOSDevice extends Device {
|
|||||||
// TODO(chinmaygarde): Use mainPath, route.
|
// TODO(chinmaygarde): Use mainPath, route.
|
||||||
globals.printTrace('Building ${package.name} for $id');
|
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.
|
// Step 1: Build the precompiled/DBC application if necessary.
|
||||||
final XcodeBuildResult buildResult = await buildXcodeProject(
|
final XcodeBuildResult buildResult = await buildXcodeProject(
|
||||||
app: package as BuildableIOSApp,
|
app: package as BuildableIOSApp,
|
||||||
buildInfo: debuggingOptions.buildInfo,
|
buildInfo: debuggingOptions.buildInfo,
|
||||||
targetOverride: mainPath,
|
targetOverride: mainPath,
|
||||||
buildForDevice: true,
|
buildForDevice: true,
|
||||||
activeArch: cpuArchitecture,
|
activeArch: iosArch,
|
||||||
);
|
);
|
||||||
if (!buildResult.success) {
|
if (!buildResult.success) {
|
||||||
globals.printError('Could not build the precompiled application for the device.');
|
globals.printError('Could not build the precompiled application for the device.');
|
||||||
|
@ -13,11 +13,7 @@ import '../base/file_system.dart';
|
|||||||
import '../base/io.dart';
|
import '../base/io.dart';
|
||||||
import '../base/logger.dart';
|
import '../base/logger.dart';
|
||||||
import '../base/process.dart';
|
import '../base/process.dart';
|
||||||
import '../build_info.dart';
|
|
||||||
import '../convert.dart';
|
|
||||||
import '../ios/devices.dart';
|
|
||||||
import '../ios/xcodeproj.dart';
|
import '../ios/xcodeproj.dart';
|
||||||
import '../reporting/reporting.dart';
|
|
||||||
|
|
||||||
const int kXcodeRequiredVersionMajor = 10;
|
const int kXcodeRequiredVersionMajor = 10;
|
||||||
const int kXcodeRequiredVersionMinor = 2;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -52,7 +52,6 @@ class MockXcode extends Mock implements Xcode {}
|
|||||||
class MockFile extends Mock implements File {}
|
class MockFile extends Mock implements File {}
|
||||||
class MockPortForwarder extends Mock implements DevicePortForwarder {}
|
class MockPortForwarder extends Mock implements DevicePortForwarder {}
|
||||||
class MockUsage extends Mock implements Usage {}
|
class MockUsage extends Mock implements Usage {}
|
||||||
class MockXcdevice extends Mock implements XCDevice {}
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
final FakePlatform macPlatform = FakePlatform.fromPlatform(const LocalPlatform());
|
final FakePlatform macPlatform = FakePlatform.fromPlatform(const LocalPlatform());
|
||||||
@ -66,17 +65,17 @@ void main() {
|
|||||||
final List<Platform> unsupportedPlatforms = <Platform>[linuxPlatform, windowsPlatform];
|
final List<Platform> unsupportedPlatforms = <Platform>[linuxPlatform, windowsPlatform];
|
||||||
|
|
||||||
testUsingContext('successfully instantiates on Mac OS', () {
|
testUsingContext('successfully instantiates on Mac OS', () {
|
||||||
IOSDevice('device-123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
|
IOSDevice('device-123');
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
Platform: () => macPlatform,
|
Platform: () => macPlatform,
|
||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('parses major version', () {
|
testUsingContext('parses major version', () {
|
||||||
expect(IOSDevice('device-123', name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '1.0.0').majorSdkVersion, 1);
|
expect(IOSDevice('device-123', 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', sdkVersion: '13.1.1').majorSdkVersion, 13);
|
||||||
expect(IOSDevice('device-123', name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '10').majorSdkVersion, 10);
|
expect(IOSDevice('device-123', sdkVersion: '10').majorSdkVersion, 10);
|
||||||
expect(IOSDevice('device-123', name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '0').majorSdkVersion, 0);
|
expect(IOSDevice('device-123', sdkVersion: '0').majorSdkVersion, 0);
|
||||||
expect(IOSDevice('device-123', name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: 'bogus').majorSdkVersion, 0);
|
expect(IOSDevice('device-123', sdkVersion: 'bogus').majorSdkVersion, 0);
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
Platform: () => macPlatform,
|
Platform: () => macPlatform,
|
||||||
});
|
});
|
||||||
@ -84,7 +83,7 @@ void main() {
|
|||||||
for (final Platform platform in unsupportedPlatforms) {
|
for (final Platform platform in unsupportedPlatforms) {
|
||||||
testUsingContext('throws UnsupportedError exception if instantiated on ${platform.operatingSystem}', () {
|
testUsingContext('throws UnsupportedError exception if instantiated on ${platform.operatingSystem}', () {
|
||||||
expect(
|
expect(
|
||||||
() { IOSDevice('device-123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64); },
|
() { IOSDevice('device-123'); },
|
||||||
throwsAssertionError,
|
throwsAssertionError,
|
||||||
);
|
);
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
@ -133,7 +132,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext(' kills all log readers & port forwarders', () async {
|
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);
|
logReader1 = createLogReader(device, appPackage1, mockProcess1);
|
||||||
logReader2 = createLogReader(device, appPackage2, mockProcess2);
|
logReader2 = createLogReader(device, appPackage2, mockProcess2);
|
||||||
portForwarder = createPortForwarder(forwardedPort, device);
|
portForwarder = createPortForwarder(forwardedPort, device);
|
||||||
@ -240,6 +239,9 @@ void main() {
|
|||||||
)).thenAnswer(
|
)).thenAnswer(
|
||||||
(_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', ''))
|
(_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', ''))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
when(mockIMobileDevice.getInfoForDevice(any, 'CPUArchitecture'))
|
||||||
|
.thenAnswer((_) => Future<String>.value('arm64'));
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() {
|
tearDown(() {
|
||||||
@ -250,7 +252,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('disposing device disposes the portForwarder', () async {
|
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.portForwarder = mockPortForwarder;
|
||||||
device.setLogReader(mockApp, mockLogReader);
|
device.setLogReader(mockApp, mockLogReader);
|
||||||
await device.dispose();
|
await device.dispose();
|
||||||
@ -259,8 +261,24 @@ void main() {
|
|||||||
Platform: () => macPlatform,
|
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 {
|
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.portForwarder = mockPortForwarder;
|
||||||
device.setLogReader(mockApp, mockLogReader);
|
device.setLogReader(mockApp, mockLogReader);
|
||||||
final Uri uri = Uri(
|
final Uri uri = Uri(
|
||||||
@ -297,7 +315,7 @@ void main() {
|
|||||||
testUsingContext(' .forward() will kill iproxy processes before invoking a second', () async {
|
testUsingContext(' .forward() will kill iproxy processes before invoking a second', () async {
|
||||||
const String deviceId = '123';
|
const String deviceId = '123';
|
||||||
const int devicePort = 456;
|
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);
|
final IOSDevicePortForwarder portForwarder = IOSDevicePortForwarder(device);
|
||||||
bool firstRun = true;
|
bool firstRun = true;
|
||||||
final MockProcess successProcess = MockProcess(
|
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 {
|
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.portForwarder = mockPortForwarder;
|
||||||
device.setLogReader(mockApp, mockLogReader);
|
device.setLogReader(mockApp, mockLogReader);
|
||||||
// Now that the reader is used, start writing messages to it.
|
// 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 {
|
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.portForwarder = mockPortForwarder;
|
||||||
device.setLogReader(mockApp, mockLogReader);
|
device.setLogReader(mockApp, mockLogReader);
|
||||||
|
|
||||||
@ -395,7 +413,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('succeeds in release mode', () async {
|
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,
|
final LaunchResult launchResult = await device.startApp(mockApp,
|
||||||
prebuiltApplication: true,
|
prebuiltApplication: true,
|
||||||
debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
|
debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
|
||||||
@ -413,7 +431,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('succeeds with --cache-sksl', () async {
|
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);
|
device.setLogReader(mockApp, mockLogReader);
|
||||||
final Uri uri = Uri(
|
final Uri uri = Uri(
|
||||||
scheme: 'http',
|
scheme: 'http',
|
||||||
@ -457,7 +475,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('succeeds with --device-vmservice-port', () async {
|
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);
|
device.setLogReader(mockApp, mockLogReader);
|
||||||
final Uri uri = Uri(
|
final Uri uri = Uri(
|
||||||
scheme: 'http',
|
scheme: 'http',
|
||||||
@ -571,7 +589,7 @@ void main() {
|
|||||||
|
|
||||||
final IOSApp app = await AbsoluteBuildableIOSApp.fromProject(
|
final IOSApp app = await AbsoluteBuildableIOSApp.fromProject(
|
||||||
FlutterProject.fromDirectory(projectDir).ios);
|
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.
|
// Pre-create the expected build products.
|
||||||
targetBuildDir.createSync(recursive: true);
|
targetBuildDir.createSync(recursive: true);
|
||||||
@ -679,7 +697,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('installApp() invokes process with correct environment', () async {
|
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';
|
const String bundlePath = '/path/to/bundle';
|
||||||
final List<String> args = <String>[installerPath, '-i', bundlePath];
|
final List<String> args = <String>[installerPath, '-i', bundlePath];
|
||||||
when(mockApp.deviceBundlePath).thenReturn(bundlePath);
|
when(mockApp.deviceBundlePath).thenReturn(bundlePath);
|
||||||
@ -701,7 +719,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('isAppInstalled() invokes process with correct environment', () async {
|
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'];
|
final List<String> args = <String>[installerPath, '--list-apps'];
|
||||||
when(mockProcessManager.run(args, environment: env))
|
when(mockProcessManager.run(args, environment: env))
|
||||||
.thenAnswer(
|
.thenAnswer(
|
||||||
@ -718,7 +736,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('uninstallApp() invokes process with correct environment', () async {
|
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];
|
final List<String> args = <String>[installerPath, '-U', appId];
|
||||||
when(mockApp.id).thenReturn(appId);
|
when(mockApp.id).thenReturn(appId);
|
||||||
when(mockProcessManager.run(args, environment: env))
|
when(mockProcessManager.run(args, environment: env))
|
||||||
@ -737,61 +755,90 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
group('getAttachedDevices', () {
|
group('getAttachedDevices', () {
|
||||||
MockXcdevice mockXcdevice;
|
MockIMobileDevice mockIMobileDevice;
|
||||||
|
|
||||||
setUp(() {
|
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];
|
final List<Platform> unsupportedPlatforms = <Platform>[linuxPlatform, windowsPlatform];
|
||||||
for (final Platform unsupportedPlatform in unsupportedPlatforms) {
|
for (final Platform platform in unsupportedPlatforms) {
|
||||||
testWithoutContext('throws Unsupported Operation exception on ${unsupportedPlatform.operatingSystem}', () async {
|
testUsingContext('throws Unsupported Operation exception on ${platform.operatingSystem}', () async {
|
||||||
when(mockXcdevice.isInstalled).thenReturn(false);
|
when(globals.iMobileDevice.isInstalled).thenReturn(false);
|
||||||
|
when(globals.iMobileDevice.getAvailableDeviceIDs())
|
||||||
|
.thenAnswer((Invocation invocation) => Future<String>.value(''));
|
||||||
expect(
|
expect(
|
||||||
() async { await IOSDevice.getAttachedDevices(unsupportedPlatform, mockXcdevice); },
|
() async { await IOSDevice.getAttachedDevices(); },
|
||||||
throwsA(isA<UnsupportedError>()),
|
throwsA(isA<UnsupportedError>()),
|
||||||
);
|
);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
IMobileDevice: () => mockIMobileDevice,
|
||||||
|
Platform: () => platform,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
testUsingContext('returns attached devices', () async {
|
testUsingContext('returns attached devices', () async {
|
||||||
when(mockXcdevice.isInstalled).thenReturn(true);
|
when(globals.iMobileDevice.isInstalled).thenReturn(true);
|
||||||
final IOSDevice device = IOSDevice('d83d5bc53967baa0ee18626ba87b6254b2ab5418', name: 'Paired iPhone', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
|
when(globals.iMobileDevice.getAvailableDeviceIDs())
|
||||||
when(mockXcdevice.getAvailableTetheredIOSDevices())
|
.thenAnswer((Invocation invocation) => Future<String>.value('''
|
||||||
.thenAnswer((Invocation invocation) => Future<List<IOSDevice>>.value(<IOSDevice>[device]));
|
98206e7a4afd4aedaff06e687594e089dede3c44
|
||||||
|
f577a7903cc54959be2e34bc4f7f80b7009efcf4
|
||||||
final List<IOSDevice> devices = await IOSDevice.getAttachedDevices(macPlatform, mockXcdevice);
|
'''));
|
||||||
expect(devices, hasLength(1));
|
when(globals.iMobileDevice.getInfoForDevice('98206e7a4afd4aedaff06e687594e089dede3c44', 'DeviceName'))
|
||||||
expect(identical(devices.first, device), isTrue);
|
.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>{
|
}, overrides: <Type, Generator>{
|
||||||
|
IMobileDevice: () => mockIMobileDevice,
|
||||||
Platform: () => macPlatform,
|
Platform: () => macPlatform,
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
group('getDiagnostics', () {
|
testUsingContext('returns attached devices and ignores devices that cannot be found by ideviceinfo', () async {
|
||||||
MockXcdevice mockXcdevice;
|
when(globals.iMobileDevice.isInstalled).thenReturn(true);
|
||||||
|
when(globals.iMobileDevice.getAvailableDeviceIDs())
|
||||||
setUp(() {
|
.thenAnswer((Invocation invocation) => Future<String>.value('''
|
||||||
mockXcdevice = MockXcdevice();
|
98206e7a4afd4aedaff06e687594e089dede3c44
|
||||||
});
|
f577a7903cc54959be2e34bc4f7f80b7009efcf4
|
||||||
|
'''));
|
||||||
final List<Platform> unsupportedPlatforms = <Platform>[linuxPlatform, windowsPlatform];
|
when(globals.iMobileDevice.getInfoForDevice('98206e7a4afd4aedaff06e687594e089dede3c44', 'DeviceName'))
|
||||||
for (final Platform unsupportedPlatform in unsupportedPlatforms) {
|
.thenAnswer((_) => Future<String>.value('La tele me regarde'));
|
||||||
testWithoutContext('throws returns platform diagnostic exception on ${unsupportedPlatform.operatingSystem}', () async {
|
when(globals.iMobileDevice.getInfoForDevice('f577a7903cc54959be2e34bc4f7f80b7009efcf4', 'DeviceName'))
|
||||||
when(mockXcdevice.isInstalled).thenReturn(false);
|
.thenThrow(const IOSDeviceNotFoundError('Device not found'));
|
||||||
expect((await IOSDevice.getDiagnostics(unsupportedPlatform, mockXcdevice)).first, 'Control of iOS devices or simulators only supported on macOS.');
|
final List<IOSDevice> devices = await IOSDevice.getAttachedDevices();
|
||||||
});
|
expect(devices, hasLength(1));
|
||||||
}
|
expect(devices[0].id, '98206e7a4afd4aedaff06e687594e089dede3c44');
|
||||||
|
expect(devices[0].name, 'La tele me regarde');
|
||||||
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');
|
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
|
IMobileDevice: () => mockIMobileDevice,
|
||||||
Platform: () => macPlatform,
|
Platform: () => macPlatform,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -830,7 +877,7 @@ Runner(UIKit)[297] <Notice>: E is for enpitsu"
|
|||||||
return Future<Process>.value(mockProcess);
|
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(
|
final DeviceLogReader logReader = device.getLogReader(
|
||||||
app: await BuildableIOSApp.fromProject(mockIosProject),
|
app: await BuildableIOSApp.fromProject(mockIosProject),
|
||||||
);
|
);
|
||||||
@ -841,7 +888,6 @@ Runner(UIKit)[297] <Notice>: E is for enpitsu"
|
|||||||
IMobileDevice: () => mockIMobileDevice,
|
IMobileDevice: () => mockIMobileDevice,
|
||||||
Platform: () => macPlatform,
|
Platform: () => macPlatform,
|
||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('includes multi-line Flutter logs in the output', () async {
|
testUsingContext('includes multi-line Flutter logs in the output', () async {
|
||||||
when(mockIMobileDevice.startLogger('123456')).thenAnswer((Invocation invocation) {
|
when(mockIMobileDevice.startLogger('123456')).thenAnswer((Invocation invocation) {
|
||||||
final Process mockProcess = MockProcess(
|
final Process mockProcess = MockProcess(
|
||||||
@ -856,7 +902,7 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
|
|||||||
return Future<Process>.value(mockProcess);
|
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(
|
final DeviceLogReader logReader = device.getLogReader(
|
||||||
app: await BuildableIOSApp.fromProject(mockIosProject),
|
app: await BuildableIOSApp.fromProject(mockIosProject),
|
||||||
);
|
);
|
||||||
@ -886,7 +932,7 @@ flutter:
|
|||||||
globals.fs.file('.packages').createSync();
|
globals.fs.file('.packages').createSync();
|
||||||
final FlutterProject flutterProject = FlutterProject.current();
|
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>{
|
}, overrides: <Type, Generator>{
|
||||||
FileSystem: () => MemoryFileSystem(),
|
FileSystem: () => MemoryFileSystem(),
|
||||||
ProcessManager: () => FakeProcessManager.any(),
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
@ -898,7 +944,7 @@ flutter:
|
|||||||
globals.fs.directory('ios').createSync();
|
globals.fs.directory('ios').createSync();
|
||||||
final FlutterProject flutterProject = FlutterProject.current();
|
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>{
|
}, overrides: <Type, Generator>{
|
||||||
FileSystem: () => MemoryFileSystem(),
|
FileSystem: () => MemoryFileSystem(),
|
||||||
ProcessManager: () => FakeProcessManager.any(),
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
@ -910,7 +956,7 @@ flutter:
|
|||||||
globals.fs.file('.packages').createSync();
|
globals.fs.file('.packages').createSync();
|
||||||
final FlutterProject flutterProject = FlutterProject.current();
|
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>{
|
}, overrides: <Type, Generator>{
|
||||||
FileSystem: () => MemoryFileSystem(),
|
FileSystem: () => MemoryFileSystem(),
|
||||||
ProcessManager: () => FakeProcessManager.any(),
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
|
@ -6,8 +6,6 @@ import 'package:file/memory.dart';
|
|||||||
import 'package:flutter_tools/src/base/file_system.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/io.dart' show ProcessException, ProcessResult;
|
||||||
import 'package:flutter_tools/src/base/logger.dart';
|
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/ios/xcodeproj.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';
|
||||||
@ -23,494 +21,160 @@ class MockPlatform extends Mock implements Platform {}
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
ProcessManager processManager;
|
ProcessManager processManager;
|
||||||
|
Xcode xcode;
|
||||||
|
MockXcodeProjectInterpreter mockXcodeProjectInterpreter;
|
||||||
|
MockPlatform platform;
|
||||||
Logger logger;
|
Logger logger;
|
||||||
|
FileSystem fileSystem;
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
logger = MockLogger();
|
logger = MockLogger();
|
||||||
|
fileSystem = MemoryFileSystem();
|
||||||
processManager = MockProcessManager();
|
processManager = MockProcessManager();
|
||||||
|
mockXcodeProjectInterpreter = MockXcodeProjectInterpreter();
|
||||||
|
platform = MockPlatform();
|
||||||
|
xcode = Xcode(
|
||||||
|
logger: logger,
|
||||||
|
platform: platform,
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
processManager: processManager,
|
||||||
|
xcodeProjectInterpreter: mockXcodeProjectInterpreter,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('Xcode', () {
|
testWithoutContext('xcodeSelectPath returns null when xcode-select is not installed', () {
|
||||||
Xcode xcode;
|
when(processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
|
||||||
MockXcodeProjectInterpreter mockXcodeProjectInterpreter;
|
.thenThrow(const ProcessException('/usr/bin/xcode-select', <String>['--print-path']));
|
||||||
MockPlatform platform;
|
expect(xcode.xcodeSelectPath, isNull);
|
||||||
FileSystem fileSystem;
|
when(processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
|
||||||
|
.thenThrow(ArgumentError('Invalid argument(s): Cannot find executable for /usr/bin/xcode-select'));
|
||||||
|
|
||||||
setUp(() {
|
expect(xcode.xcodeSelectPath, isNull);
|
||||||
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');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
group('xcdevice', () {
|
testWithoutContext('xcodeSelectPath returns path when xcode-select is installed', () {
|
||||||
XCDevice xcdevice;
|
const String xcodePath = '/Applications/Xcode8.0.app/Contents/Developer';
|
||||||
MockXcode mockXcode;
|
when(processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
|
||||||
|
.thenReturn(ProcessResult(1, 0, xcodePath, ''));
|
||||||
|
|
||||||
setUp(() {
|
expect(xcode.xcodeSelectPath, xcodePath);
|
||||||
mockXcode = MockXcode();
|
});
|
||||||
xcdevice = XCDevice(
|
|
||||||
processManager: processManager,
|
|
||||||
logger: logger,
|
|
||||||
xcode: mockXcode,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
group('installed', () {
|
testWithoutContext('xcodeVersionSatisfactory is false when version is less than minimum', () {
|
||||||
testWithoutContext('Xcode not installed', () {
|
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
|
||||||
when(mockXcode.isInstalledAndMeetsVersionCheck).thenReturn(false);
|
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(9);
|
||||||
expect(xcdevice.isInstalled, false);
|
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(0);
|
||||||
});
|
|
||||||
|
|
||||||
testWithoutContext("xcrun can't find xcdevice", () {
|
expect(xcode.isVersionSatisfactory, isFalse);
|
||||||
when(mockXcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
|
});
|
||||||
|
|
||||||
when(processManager.runSync(<String>['xcrun', '--find', 'xcdevice']))
|
testWithoutContext('xcodeVersionSatisfactory is false when xcodebuild tools are not installed', () {
|
||||||
.thenThrow(const ProcessException('xcrun', <String>['--find', 'xcdevice']));
|
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(false);
|
||||||
expect(xcdevice.isInstalled, false);
|
|
||||||
verify(processManager.runSync(any)).called(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWithoutContext('is installed', () {
|
expect(xcode.isVersionSatisfactory, isFalse);
|
||||||
when(mockXcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
|
});
|
||||||
|
|
||||||
when(processManager.runSync(<String>['xcrun', '--find', 'xcdevice']))
|
testWithoutContext('xcodeVersionSatisfactory is true when version meets minimum', () {
|
||||||
.thenReturn(ProcessResult(1, 0, '/path/to/xcdevice', ''));
|
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
|
||||||
expect(xcdevice.isInstalled, true);
|
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(10);
|
||||||
});
|
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(2);
|
||||||
});
|
|
||||||
|
|
||||||
group('available devices', () {
|
expect(xcode.isVersionSatisfactory, isTrue);
|
||||||
final FakePlatform macPlatform = FakePlatform.fromPlatform(const LocalPlatform());
|
});
|
||||||
macPlatform.operatingSystem = 'macos';
|
|
||||||
|
|
||||||
testWithoutContext('Xcode not installed', () async {
|
testWithoutContext('xcodeVersionSatisfactory is true when major version exceeds minimum', () {
|
||||||
when(mockXcode.isInstalledAndMeetsVersionCheck).thenReturn(false);
|
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
|
||||||
|
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(11);
|
||||||
|
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(2);
|
||||||
|
|
||||||
expect(await xcdevice.getAvailableTetheredIOSDevices(), isEmpty);
|
expect(xcode.isVersionSatisfactory, isTrue);
|
||||||
verifyNever(processManager.run(any));
|
});
|
||||||
});
|
|
||||||
|
|
||||||
testWithoutContext('xcdevice fails', () async {
|
testWithoutContext('xcodeVersionSatisfactory is true when minor version exceeds minimum', () {
|
||||||
when(mockXcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
|
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
|
||||||
|
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(10);
|
||||||
|
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(3);
|
||||||
|
|
||||||
when(processManager.runSync(<String>['xcrun', '--find', 'xcdevice']))
|
expect(xcode.isVersionSatisfactory, isTrue);
|
||||||
.thenReturn(ProcessResult(1, 0, '/path/to/xcdevice', ''));
|
});
|
||||||
|
|
||||||
when(processManager.run(<String>['xcrun', 'xcdevice', 'list', '--timeout', '1']))
|
testWithoutContext('isInstalledAndMeetsVersionCheck is false when not macOS', () {
|
||||||
.thenThrow(const ProcessException('xcrun', <String>['xcdevice', 'list', '--timeout', '1']));
|
when(platform.isMacOS).thenReturn(false);
|
||||||
|
|
||||||
expect(await xcdevice.getAvailableTetheredIOSDevices(), isEmpty);
|
expect(xcode.isInstalledAndMeetsVersionCheck, isFalse);
|
||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('returns devices', () async {
|
testWithoutContext('isInstalledAndMeetsVersionCheck is false when not installed', () {
|
||||||
when(mockXcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
|
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']))
|
expect(xcode.isInstalledAndMeetsVersionCheck, isFalse);
|
||||||
.thenReturn(ProcessResult(1, 0, '/path/to/xcdevice', ''));
|
});
|
||||||
|
|
||||||
const String devicesOutput = '''
|
testWithoutContext('isInstalledAndMeetsVersionCheck is false when no xcode-select', () {
|
||||||
[
|
when(platform.isMacOS).thenReturn(true);
|
||||||
{
|
when(processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
|
||||||
"simulator" : true,
|
.thenReturn(ProcessResult(1, 127, '', 'ERROR'));
|
||||||
"operatingSystemVersion" : "13.3 (17K446)",
|
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
|
||||||
"available" : true,
|
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(10);
|
||||||
"platform" : "com.apple.platform.appletvsimulator",
|
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(2);
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
''';
|
|
||||||
|
|
||||||
when(processManager.run(<String>['xcrun', 'xcdevice', 'list', '--timeout', '1']))
|
expect(xcode.isInstalledAndMeetsVersionCheck, isFalse);
|
||||||
.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,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group('diagnostics', () {
|
testWithoutContext('isInstalledAndMeetsVersionCheck is false when version not satisfied', () {
|
||||||
final FakePlatform macPlatform = FakePlatform.fromPlatform(const LocalPlatform());
|
when(platform.isMacOS).thenReturn(true);
|
||||||
macPlatform.operatingSystem = 'macos';
|
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 {
|
expect(xcode.isInstalledAndMeetsVersionCheck, isFalse);
|
||||||
when(mockXcode.isInstalledAndMeetsVersionCheck).thenReturn(false);
|
});
|
||||||
|
|
||||||
expect(await xcdevice.getDiagnostics(), isEmpty);
|
testWithoutContext('isInstalledAndMeetsVersionCheck is true when macOS and installed and version is satisfied', () {
|
||||||
verifyNever(processManager.run(any));
|
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 {
|
expect(xcode.isInstalledAndMeetsVersionCheck, isTrue);
|
||||||
when(mockXcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
|
});
|
||||||
|
|
||||||
when(processManager.runSync(<String>['xcrun', '--find', 'xcdevice']))
|
testWithoutContext('eulaSigned is false when clang is not installed', () {
|
||||||
.thenReturn(ProcessResult(1, 0, '/path/to/xcdevice', ''));
|
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']))
|
expect(xcode.eulaSigned, isFalse);
|
||||||
.thenThrow(const ProcessException('xcrun', <String>['xcdevice', 'list', '--timeout', '1']));
|
});
|
||||||
|
|
||||||
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 {
|
expect(xcode.eulaSigned, isFalse);
|
||||||
when(mockXcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
|
});
|
||||||
|
|
||||||
when(processManager.runSync(<String>['xcrun', '--find', 'xcdevice']))
|
testWithoutContext('eulaSigned is true when clang output indicates EULA has been accepted', () {
|
||||||
.thenReturn(ProcessResult(1, 0, '/path/to/xcdevice', ''));
|
when(processManager.runSync(<String>['/usr/bin/xcrun', 'clang']))
|
||||||
|
.thenReturn(ProcessResult(1, 1, '', 'clang: error: no input files'));
|
||||||
|
|
||||||
const String devicesOutput = '''
|
expect(xcode.eulaSigned, isTrue);
|
||||||
[
|
});
|
||||||
{
|
|
||||||
"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']))
|
testWithoutContext('SDK name', () {
|
||||||
.thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(1, 0, devicesOutput, '')));
|
expect(getNameForSdk(SdkType.iPhone), 'iphoneos');
|
||||||
await xcdevice.getAvailableTetheredIOSDevices();
|
expect(getNameForSdk(SdkType.iPhoneSimulator), 'iphonesimulator');
|
||||||
final List<String> errors = await xcdevice.getDiagnostics();
|
expect(getNameForSdk(SdkType.macOS), 'macosx');
|
||||||
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,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockLogger extends Mock implements Logger {}
|
class MockLogger extends Mock implements Logger {}
|
||||||
class MockXcode extends Mock implements Xcode {}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user