Unverified Commit 704fb4cb authored by Christopher Fujino's avatar Christopher Fujino Committed by GitHub

Remove usage of ideviceinstaller in favor of ios-deploy (#50772)

parent 2078cc4d
...@@ -498,9 +498,7 @@ class IosDeviceDiscovery implements DeviceDiscovery { ...@@ -498,9 +498,7 @@ class IosDeviceDiscovery implements DeviceDiscovery {
'usbmuxd', 'usbmuxd',
'libplist', 'libplist',
'openssl', 'openssl',
'ideviceinstaller',
'ios-deploy', 'ios-deploy',
'libzip',
].map((String packageName) => path.join(getArtifactPath(), packageName)).join(':'); ].map((String packageName) => path.join(getArtifactPath(), packageName)).join(':');
return <String, String>{'DYLD_LIBRARY_PATH': libPath}; return <String, String>{'DYLD_LIBRARY_PATH': libPath};
} }
......
...@@ -1261,9 +1261,7 @@ class IosUsbArtifacts extends CachedArtifact { ...@@ -1261,9 +1261,7 @@ class IosUsbArtifacts extends CachedArtifact {
'usbmuxd', 'usbmuxd',
'libplist', 'libplist',
'openssl', 'openssl',
'ideviceinstaller',
'ios-deploy', 'ios-deploy',
'libzip',
]; ];
// For unknown reasons, users are getting into bad states where libimobiledevice is // For unknown reasons, users are getting into bad states where libimobiledevice is
......
...@@ -6,6 +6,7 @@ import 'dart:async'; ...@@ -6,6 +6,7 @@ import 'dart:async';
import '../application_package.dart'; import '../application_package.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/io.dart';
import '../cache.dart'; import '../cache.dart';
import '../device.dart'; import '../device.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
...@@ -54,11 +55,15 @@ Future<bool> installApp(Device device, ApplicationPackage package, { bool uninst ...@@ -54,11 +55,15 @@ Future<bool> installApp(Device device, ApplicationPackage package, { bool uninst
return false; return false;
} }
if (uninstall && await device.isAppInstalled(package)) { try {
globals.printStatus('Uninstalling old version...'); if (uninstall && await device.isAppInstalled(package)) {
if (!await device.uninstallApp(package)) { globals.printStatus('Uninstalling old version...');
globals.printError('Warning: uninstalling old version failed'); if (!await device.uninstallApp(package)) {
globals.printError('Warning: uninstalling old version failed');
}
} }
} on ProcessException catch (e) {
globals.printError('Error accessing device ${device.id}:\n${e.message}');
} }
return device.installApp(package); return device.installApp(package);
......
...@@ -35,7 +35,7 @@ import 'fuchsia/fuchsia_device.dart' show FuchsiaDeviceTools; ...@@ -35,7 +35,7 @@ import 'fuchsia/fuchsia_device.dart' show FuchsiaDeviceTools;
import 'fuchsia/fuchsia_sdk.dart' show FuchsiaSdk, FuchsiaArtifacts; import 'fuchsia/fuchsia_sdk.dart' show FuchsiaSdk, FuchsiaArtifacts;
import 'fuchsia/fuchsia_workflow.dart' show FuchsiaWorkflow; import 'fuchsia/fuchsia_workflow.dart' show FuchsiaWorkflow;
import 'globals.dart' as globals; import 'globals.dart' as globals;
import 'ios/devices.dart' show IOSDeploy; import 'ios/ios_deploy.dart';
import 'ios/ios_workflow.dart'; import 'ios/ios_workflow.dart';
import 'ios/mac.dart'; import 'ios/mac.dart';
import 'ios/simulators.dart'; import 'ios/simulators.dart';
...@@ -125,7 +125,13 @@ Future<T> runInContext<T>( ...@@ -125,7 +125,13 @@ Future<T> runInContext<T>(
GradleUtils: () => GradleUtils(), GradleUtils: () => GradleUtils(),
HotRunnerConfig: () => HotRunnerConfig(), HotRunnerConfig: () => HotRunnerConfig(),
IMobileDevice: () => IMobileDevice(), IMobileDevice: () => IMobileDevice(),
IOSDeploy: () => const IOSDeploy(), IOSDeploy: () => IOSDeploy(
artifacts: globals.artifacts,
cache: globals.cache,
logger: globals.logger,
platform: globals.platform,
processManager: globals.processManager,
),
IOSSimulatorUtils: () => IOSSimulatorUtils(), IOSSimulatorUtils: () => IOSSimulatorUtils(),
IOSWorkflow: () => const IOSWorkflow(), IOSWorkflow: () => const IOSWorkflow(),
KernelCompilerFactory: () => const KernelCompilerFactory(), KernelCompilerFactory: () => const KernelCompilerFactory(),
......
...@@ -20,6 +20,7 @@ import 'base/os.dart'; ...@@ -20,6 +20,7 @@ import 'base/os.dart';
import 'base/terminal.dart'; import 'base/terminal.dart';
import 'base/user_messages.dart'; import 'base/user_messages.dart';
import 'cache.dart'; import 'cache.dart';
import 'ios/ios_deploy.dart';
import 'ios/mac.dart'; import 'ios/mac.dart';
import 'macos/xcode.dart'; import 'macos/xcode.dart';
import 'persistent_tool_state.dart'; import 'persistent_tool_state.dart';
...@@ -62,6 +63,7 @@ AndroidStudio get androidStudio => context.get<AndroidStudio>(); ...@@ -62,6 +63,7 @@ AndroidStudio get androidStudio => context.get<AndroidStudio>();
AndroidSdk get androidSdk => context.get<AndroidSdk>(); AndroidSdk get androidSdk => context.get<AndroidSdk>();
FlutterVersion get flutterVersion => context.get<FlutterVersion>(); FlutterVersion get flutterVersion => context.get<FlutterVersion>();
IMobileDevice get iMobileDevice => context.get<IMobileDevice>(); IMobileDevice get iMobileDevice => context.get<IMobileDevice>();
IOSDeploy get iosDeploy => context.get<IOSDeploy>();
UserMessages get userMessages => context.get<UserMessages>(); UserMessages get userMessages => context.get<UserMessages>();
Xcode get xcode => context.get<Xcode>(); Xcode get xcode => context.get<Xcode>();
......
...@@ -11,7 +11,6 @@ import 'package:platform/platform.dart'; ...@@ -11,7 +11,6 @@ import 'package:platform/platform.dart';
import '../application_package.dart'; import '../application_package.dart';
import '../artifacts.dart'; import '../artifacts.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/context.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/logger.dart'; import '../base/logger.dart';
...@@ -25,85 +24,11 @@ import '../mdns_discovery.dart'; ...@@ -25,85 +24,11 @@ import '../mdns_discovery.dart';
import '../project.dart'; import '../project.dart';
import '../protocol_discovery.dart'; import '../protocol_discovery.dart';
import '../vmservice.dart'; import '../vmservice.dart';
import 'code_signing.dart';
import 'fallback_discovery.dart'; import 'fallback_discovery.dart';
import 'ios_deploy.dart';
import 'ios_workflow.dart'; import 'ios_workflow.dart';
import 'mac.dart'; import 'mac.dart';
class IOSDeploy {
const IOSDeploy();
static IOSDeploy get instance => context.get<IOSDeploy>();
/// Installs and runs the specified app bundle using ios-deploy, then returns
/// the exit code.
Future<int> runApp({
@required String deviceId,
@required String bundlePath,
@required List<String> launchArguments,
}) async {
final String iosDeployPath = globals.artifacts.getArtifactPath(Artifact.iosDeploy, platform: TargetPlatform.ios);
final List<String> launchCommand = <String>[
iosDeployPath,
'--id',
deviceId,
'--bundle',
bundlePath,
'--no-wifi',
'--justlaunch',
if (launchArguments.isNotEmpty) ...<String>[
'--args',
launchArguments.join(' '),
],
];
// Push /usr/bin to the front of PATH to pick up default system python, package 'six'.
//
// ios-deploy transitively depends on LLDB.framework, which invokes a
// Python script that uses package 'six'. LLDB.framework relies on the
// python at the front of the path, which may not include package 'six'.
// Ensure that we pick up the system install of python, which does include
// it.
final Map<String, String> iosDeployEnv = Map<String, String>.from(globals.platform.environment);
iosDeployEnv['PATH'] = '/usr/bin:${iosDeployEnv['PATH']}';
iosDeployEnv.addEntries(<MapEntry<String, String>>[globals.cache.dyLdLibEntry]);
return await processUtils.stream(
launchCommand,
mapFunction: _monitorInstallationFailure,
trace: true,
environment: iosDeployEnv,
);
}
// Maps stdout line stream. Must return original line.
String _monitorInstallationFailure(String stdout) {
// Installation issues.
if (stdout.contains('Error 0xe8008015') || stdout.contains('Error 0xe8000067')) {
globals.printError(noProvisioningProfileInstruction, emphasis: true);
// Launch issues.
} else if (stdout.contains('e80000e2')) {
globals.printError('''
═══════════════════════════════════════════════════════════════════════════════════
Your device is locked. Unlock your device first before running.
═══════════════════════════════════════════════════════════════════════════════════''',
emphasis: true);
} else if (stdout.contains('Error 0xe8000022')) {
globals.printError('''
═══════════════════════════════════════════════════════════════════════════════════
Error launching app. Try launching from within Xcode via:
open ios/Runner.xcworkspace
Your Xcode version may be too old for your iOS version.
═══════════════════════════════════════════════════════════════════════════════════''',
emphasis: true);
}
return stdout;
}
}
class IOSDevices extends PollingDeviceDiscovery { class IOSDevices extends PollingDeviceDiscovery {
IOSDevices() : super('iOS devices'); IOSDevices() : super('iOS devices');
...@@ -122,35 +47,38 @@ class IOSDevices extends PollingDeviceDiscovery { ...@@ -122,35 +47,38 @@ class IOSDevices extends PollingDeviceDiscovery {
class IOSDevice extends Device { class IOSDevice extends Device {
IOSDevice(String id, { IOSDevice(String id, {
@required FileSystem fileSystem,
@required this.name, @required this.name,
@required this.cpuArchitecture, @required this.cpuArchitecture,
@required String sdkVersion, @required String sdkVersion,
@required Platform platform,
@required Artifacts artifacts,
@required IOSDeploy iosDeploy,
}) })
: _sdkVersion = sdkVersion, : _sdkVersion = sdkVersion,
_iosDeploy = iosDeploy,
_fileSystem = fileSystem,
super( super(
id, id,
category: Category.mobile, category: Category.mobile,
platformType: PlatformType.ios, platformType: PlatformType.ios,
ephemeral: true, ephemeral: true,
) { ) {
if (!globals.platform.isMacOS) { if (!platform.isMacOS) {
assert(false, 'Control of iOS devices or simulators only supported on Mac OS.'); assert(false, 'Control of iOS devices or simulators only supported on Mac OS.');
return; return;
} }
_installerPath = globals.artifacts.getArtifactPath( _iproxyPath = artifacts.getArtifactPath(
Artifact.ideviceinstaller,
platform: TargetPlatform.ios,
);
_iproxyPath = globals.artifacts.getArtifactPath(
Artifact.iproxy, Artifact.iproxy,
platform: TargetPlatform.ios, platform: TargetPlatform.ios,
); );
} }
String _installerPath;
String _iproxyPath; String _iproxyPath;
final String _sdkVersion; final String _sdkVersion;
final IOSDeploy _iosDeploy;
final FileSystem _fileSystem;
/// May be 0 if version cannot be parsed. /// May be 0 if version cannot be parsed.
int get majorSdkVersion { int get majorSdkVersion {
...@@ -200,19 +128,17 @@ class IOSDevice extends Device { ...@@ -200,19 +128,17 @@ class IOSDevice extends Device {
@override @override
Future<bool> isAppInstalled(IOSApp app) async { Future<bool> isAppInstalled(IOSApp app) async {
RunResult apps; bool result;
try { try {
apps = await processUtils.run( result = await _iosDeploy.isAppInstalled(
<String>[_installerPath, '--list-apps'], bundleId: app.id,
throwOnError: true, deviceId: id,
environment: Map<String, String>.fromEntries(
<MapEntry<String, String>>[globals.cache.dyLdLibEntry],
),
); );
} on ProcessException { } on ProcessException catch (e) {
globals.printError(e.message);
return false; return false;
} }
return RegExp(app.id, multiLine: true).hasMatch(apps.stdout); return result;
} }
@override @override
...@@ -220,42 +146,50 @@ class IOSDevice extends Device { ...@@ -220,42 +146,50 @@ class IOSDevice extends Device {
@override @override
Future<bool> installApp(IOSApp app) async { Future<bool> installApp(IOSApp app) async {
final Directory bundle = globals.fs.directory(app.deviceBundlePath); final Directory bundle = _fileSystem.directory(app.deviceBundlePath);
if (!bundle.existsSync()) { if (!bundle.existsSync()) {
globals.printError('Could not find application bundle at ${bundle.path}; have you run "flutter build ios"?'); globals.printError('Could not find application bundle at ${bundle.path}; have you run "flutter build ios"?');
return false; return false;
} }
int installationResult;
try { try {
await processUtils.run( installationResult = await _iosDeploy.installApp(
<String>[_installerPath, '-i', app.deviceBundlePath], deviceId: id,
throwOnError: true, bundlePath: bundle.path,
environment: Map<String, String>.fromEntries( launchArguments: <String>[],
<MapEntry<String, String>>[globals.cache.dyLdLibEntry],
),
); );
return true; } on ProcessException catch (e) {
} on ProcessException catch (error) { globals.printError(e.message);
globals.printError(error.message);
return false; return false;
} }
if (installationResult != 0) {
globals.printError('Could not install ${bundle.path} on $id.');
globals.printError('Try launching Xcode and selecting "Product > Run" to fix the problem:');
globals.printError(' open ios/Runner.xcworkspace');
globals.printError('');
return false;
}
return true;
} }
@override @override
Future<bool> uninstallApp(IOSApp app) async { Future<bool> uninstallApp(IOSApp app) async {
int uninstallationResult;
try { try {
await processUtils.run( uninstallationResult = await _iosDeploy.uninstallApp(
<String>[_installerPath, '-U', app.id], deviceId: id,
throwOnError: true, bundleId: app.id,
environment: Map<String, String>.fromEntries(
<MapEntry<String, String>>[globals.cache.dyLdLibEntry],
),
); );
return true; } on ProcessException catch (e) {
} on ProcessException catch (error) { globals.printError(e.message);
globals.printError(error.message); return false;
}
if (uninstallationResult != 0) {
globals.printError('Could not uninstall ${app.id} on $id.');
return false; return false;
} }
return true;
} }
@override @override
...@@ -301,7 +235,7 @@ class IOSDevice extends Device { ...@@ -301,7 +235,7 @@ class IOSDevice extends Device {
packageId ??= package.id; packageId ??= package.id;
// Step 2: Check that the application exists at the specified path. // Step 2: Check that the application exists at the specified path.
final Directory bundle = globals.fs.directory(package.deviceBundlePath); final Directory bundle = _fileSystem.directory(package.deviceBundlePath);
if (!bundle.existsSync()) { if (!bundle.existsSync()) {
globals.printError('Could not find the built application bundle at ${bundle.path}.'); globals.printError('Could not find the built application bundle at ${bundle.path}.');
return LaunchResult.failed(); return LaunchResult.failed();
...@@ -359,13 +293,13 @@ class IOSDevice extends Device { ...@@ -359,13 +293,13 @@ class IOSDevice extends Device {
ipv6: ipv6, ipv6: ipv6,
); );
} }
final int installationResult = await IOSDeploy.instance.runApp( final int installationResult = await _iosDeploy.runApp(
deviceId: id, deviceId: id,
bundlePath: bundle.path, bundlePath: bundle.path,
launchArguments: launchArguments, launchArguments: launchArguments,
); );
if (installationResult != 0) { if (installationResult != 0) {
globals.printError('Could not install ${bundle.path} on $id.'); globals.printError('Could not run ${bundle.path} on $id.');
globals.printError('Try launching Xcode and selecting "Product > Run" to fix the problem:'); globals.printError('Try launching Xcode and selecting "Product > Run" to fix the problem:');
globals.printError(' open ios/Runner.xcworkspace'); globals.printError(' open ios/Runner.xcworkspace');
globals.printError(''); globals.printError('');
...@@ -395,6 +329,9 @@ class IOSDevice extends Device { ...@@ -395,6 +329,9 @@ class IOSDevice extends Device {
return LaunchResult.failed(); return LaunchResult.failed();
} }
return LaunchResult.succeeded(observatoryUri: localUri); return LaunchResult.succeeded(observatoryUri: localUri);
} on ProcessException catch (e) {
globals.printError(e.message);
return LaunchResult.failed();
} finally { } finally {
installStatus.stop(); installStatus.stop();
} }
...@@ -450,7 +387,7 @@ class IOSDevice extends Device { ...@@ -450,7 +387,7 @@ class IOSDevice extends Device {
@override @override
Future<void> dispose() async { Future<void> dispose() async {
_logReaders.forEach((IOSApp application, DeviceLogReader logReader) { _logReaders?.forEach((IOSApp application, DeviceLogReader logReader) {
logReader.dispose(); logReader.dispose();
}); });
await _portForwarder?.dispose(); await _portForwarder?.dispose();
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:meta/meta.dart';
import 'package:platform/platform.dart';
import 'package:process/process.dart';
import '../artifacts.dart';
import '../base/logger.dart';
import '../base/process.dart';
import '../build_info.dart';
import '../cache.dart';
import 'code_signing.dart';
// Error message patterns from ios-deploy output
const String noProvisioningProfileErrorOne = 'Error 0xe8008015';
const String noProvisioningProfileErrorTwo = 'Error 0xe8000067';
const String deviceLockedError = 'e80000e2';
const String unknownAppLaunchError = 'Error 0xe8000022';
class IOSDeploy {
IOSDeploy({
@required Artifacts artifacts,
@required Cache cache,
@required Logger logger,
@required Platform platform,
@required ProcessManager processManager,
}) : _platform = platform,
_cache = cache,
_processUtils = ProcessUtils(processManager: processManager, logger: logger),
_logger = logger,
_binaryPath = artifacts.getArtifactPath(Artifact.iosDeploy, platform: TargetPlatform.ios);
final Cache _cache;
final String _binaryPath;
final Logger _logger;
final Platform _platform;
final ProcessUtils _processUtils;
Map<String, String> get iosDeployEnv {
// Push /usr/bin to the front of PATH to pick up default system python, package 'six'.
//
// ios-deploy transitively depends on LLDB.framework, which invokes a
// Python script that uses package 'six'. LLDB.framework relies on the
// python at the front of the path, which may not include package 'six'.
// Ensure that we pick up the system install of python, which includes it.
final Map<String, String> environment = Map<String, String>.from(_platform.environment);
environment['PATH'] = '/usr/bin:${environment['PATH']}';
environment.addEntries(<MapEntry<String, String>>[_cache.dyLdLibEntry]);
return environment;
}
/// Uninstalls the specified app bundle.
///
/// Uses ios-deploy and returns the exit code.
Future<int> uninstallApp({
@required String deviceId,
@required String bundleId,
}) async {
final List<String> launchCommand = <String>[
_binaryPath,
'--id',
deviceId,
'--uninstall_only',
'--bundle_id',
bundleId,
];
return _processUtils.stream(
launchCommand,
mapFunction: _monitorFailure,
trace: true,
environment: iosDeployEnv,
);
}
/// Installs the specified app bundle.
///
/// Uses ios-deploy and returns the exit code.
Future<int> installApp({
@required String deviceId,
@required String bundlePath,
@required List<String>launchArguments,
}) async {
final List<String> launchCommand = <String>[
_binaryPath,
'--id',
deviceId,
'--bundle',
bundlePath,
'--no-wifi',
if (launchArguments.isNotEmpty) ...<String>[
'--args',
launchArguments.join(' '),
],
];
return _processUtils.stream(
launchCommand,
mapFunction: _monitorFailure,
trace: true,
environment: iosDeployEnv,
);
}
/// Installs and then runs the specified app bundle.
///
/// Uses ios-deploy and returns the exit code.
Future<int> runApp({
@required String deviceId,
@required String bundlePath,
@required List<String> launchArguments,
}) async {
final List<String> launchCommand = <String>[
_binaryPath,
'--id',
deviceId,
'--bundle',
bundlePath,
'--no-wifi',
'--justlaunch',
if (launchArguments.isNotEmpty) ...<String>[
'--args',
launchArguments.join(' '),
],
];
return _processUtils.stream(
launchCommand,
mapFunction: _monitorFailure,
trace: true,
environment: iosDeployEnv,
);
}
Future<bool> isAppInstalled({
@required String bundleId,
@required String deviceId,
}) async {
final List<String> launchCommand = <String>[
_binaryPath,
'--id',
deviceId,
'--exists',
'--bundle_id',
bundleId,
];
final RunResult result = await _processUtils.run(
launchCommand,
environment: iosDeployEnv,
);
if (result.exitCode != 0) {
return false;
}
return result.stdout.contains(bundleId);
}
// Maps stdout line stream. Must return original line.
String _monitorFailure(String stdout) {
// Installation issues.
if (stdout.contains(noProvisioningProfileErrorOne) || stdout.contains(noProvisioningProfileErrorTwo)) {
_logger.printError(noProvisioningProfileInstruction, emphasis: true);
// Launch issues.
} else if (stdout.contains(deviceLockedError)) {
_logger.printError('''
═══════════════════════════════════════════════════════════════════════════════════
Your device is locked. Unlock your device first before running.
═══════════════════════════════════════════════════════════════════════════════════''',
emphasis: true);
} else if (stdout.contains(unknownAppLaunchError)) {
_logger.printError('''
═══════════════════════════════════════════════════════════════════════════════════
Error launching app. Try launching from within Xcode via:
open ios/Runner.xcworkspace
Your Xcode version may be too old for your iOS version.
═══════════════════════════════════════════════════════════════════════════════════''',
emphasis: true);
}
return stdout;
}
}
...@@ -91,7 +91,6 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -91,7 +91,6 @@ Future<XcodeBuildResult> buildXcodeProject({
return XcodeBuildResult(success: false); return XcodeBuildResult(success: false);
} }
final XcodeProjectInfo projectInfo = await xcodeProjectInterpreter.getInfo(app.project.hostAppRoot.path); final XcodeProjectInfo projectInfo = await xcodeProjectInterpreter.getInfo(app.project.hostAppRoot.path);
if (!projectInfo.targets.contains('Runner')) { if (!projectInfo.targets.contains('Runner')) {
globals.printError('The Xcode project does not define target "Runner" which is needed by Flutter tooling.'); globals.printError('The Xcode project does not define target "Runner" which is needed by Flutter tooling.');
......
...@@ -15,6 +15,7 @@ import '../base/logger.dart'; ...@@ -15,6 +15,7 @@ import '../base/logger.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../convert.dart'; import '../convert.dart';
import '../globals.dart' as globals;
import '../ios/devices.dart'; import '../ios/devices.dart';
import '../ios/xcodeproj.dart'; import '../ios/xcodeproj.dart';
import '../reporting/reporting.dart'; import '../reporting/reporting.dart';
...@@ -349,6 +350,10 @@ class XCDevice { ...@@ -349,6 +350,10 @@ class XCDevice {
name: device['name'] as String, name: device['name'] as String,
cpuArchitecture: _cpuArchitecture(deviceProperties), cpuArchitecture: _cpuArchitecture(deviceProperties),
sdkVersion: _sdkVersion(deviceProperties), sdkVersion: _sdkVersion(deviceProperties),
artifacts: globals.artifacts,
fileSystem: globals.fs,
iosDeploy: globals.iosDeploy,
platform: globals.platform,
)); ));
} }
return devices; return devices;
......
...@@ -12,6 +12,7 @@ import 'package:flutter_tools/src/application_package.dart'; ...@@ -12,6 +12,7 @@ import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/artifacts.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'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/create.dart'; import 'package:flutter_tools/src/commands/create.dart';
...@@ -19,6 +20,7 @@ import 'package:flutter_tools/src/device.dart'; ...@@ -19,6 +20,7 @@ import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/devices.dart';
import 'package:flutter_tools/src/ios/mac.dart'; import 'package:flutter_tools/src/ios/mac.dart';
import 'package:flutter_tools/src/ios/ios_deploy.dart';
import 'package:flutter_tools/src/ios/ios_workflow.dart'; import 'package:flutter_tools/src/ios/ios_workflow.dart';
import 'package:flutter_tools/src/macos/xcode.dart'; import 'package:flutter_tools/src/macos/xcode.dart';
import 'package:flutter_tools/src/mdns_discovery.dart'; import 'package:flutter_tools/src/mdns_discovery.dart';
...@@ -40,19 +42,23 @@ class MockIOSApp extends Mock implements IOSApp {} ...@@ -40,19 +42,23 @@ class MockIOSApp extends Mock implements IOSApp {}
class MockApplicationPackage extends Mock implements ApplicationPackage {} class MockApplicationPackage extends Mock implements ApplicationPackage {}
class MockArtifacts extends Mock implements Artifacts {} class MockArtifacts extends Mock implements Artifacts {}
class MockCache extends Mock implements Cache {} class MockCache extends Mock implements Cache {}
class MockDevicePortForwarder extends Mock implements DevicePortForwarder {}
class MockDirectory extends Mock implements Directory {} class MockDirectory extends Mock implements Directory {}
class MockFile extends Mock implements File {}
class MockFileSystem extends Mock implements FileSystem {} class MockFileSystem extends Mock implements FileSystem {}
class MockForwardedPort extends Mock implements ForwardedPort {} class MockForwardedPort extends Mock implements ForwardedPort {}
class MockIMobileDevice extends Mock implements IMobileDevice {} class MockIMobileDevice extends Mock implements IMobileDevice {}
class MockIOSDeploy extends Mock implements IOSDeploy {} class MockIOSDeploy extends Mock implements IOSDeploy {}
class MockDevicePortForwarder extends Mock implements DevicePortForwarder {} class MockLogger extends Mock implements Logger {}
class MockMDnsObservatoryDiscovery extends Mock implements MDnsObservatoryDiscovery {} class MockMDnsObservatoryDiscovery extends Mock implements MDnsObservatoryDiscovery {}
class MockMDnsObservatoryDiscoveryResult extends Mock implements MDnsObservatoryDiscoveryResult {} class MockMDnsObservatoryDiscoveryResult extends Mock implements MDnsObservatoryDiscoveryResult {}
class MockXcode extends Mock implements Xcode {} class MockPlatform extends Mock implements Platform {}
class MockFile extends Mock implements File {}
class MockPortForwarder extends Mock implements DevicePortForwarder {} class MockPortForwarder extends Mock implements DevicePortForwarder {}
// src/mocks.dart imports `MockProcessManager` which implements some methods, this is a full mock
class FullMockProcessManager extends Mock implements ProcessManager {}
class MockUsage extends Mock implements Usage {} class MockUsage extends Mock implements Usage {}
class MockXcdevice extends Mock implements XCDevice {} class MockXcdevice extends Mock implements XCDevice {}
class MockXcode extends Mock implements Xcode {}
void main() { void main() {
final FakePlatform macPlatform = FakePlatform.fromPlatform(const LocalPlatform()); final FakePlatform macPlatform = FakePlatform.fromPlatform(const LocalPlatform());
...@@ -64,34 +70,187 @@ void main() { ...@@ -64,34 +70,187 @@ void main() {
group('IOSDevice', () { group('IOSDevice', () {
final List<Platform> unsupportedPlatforms = <Platform>[linuxPlatform, windowsPlatform]; final List<Platform> unsupportedPlatforms = <Platform>[linuxPlatform, windowsPlatform];
Artifacts mockArtifacts;
MockCache mockCache;
MockLogger mockLogger;
IOSDeploy iosDeploy;
FileSystem mockFileSystem;
testUsingContext('successfully instantiates on Mac OS', () { setUp(() {
IOSDevice('device-123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64); mockArtifacts = MockArtifacts();
}, overrides: <Type, Generator>{ mockCache = MockCache();
Platform: () => macPlatform, const MapEntry<String, String> dyLdLibEntry = MapEntry<String, String>('DYLD_LIBRARY_PATH', '/path/to/libs');
when(mockCache.dyLdLibEntry).thenReturn(dyLdLibEntry);
mockFileSystem = MockFileSystem();
mockLogger = MockLogger();
iosDeploy = IOSDeploy(
artifacts: mockArtifacts,
cache: mockCache,
logger: mockLogger,
platform: macPlatform,
processManager: FakeProcessManager.any(),
);
}); });
testUsingContext('parses major version', () { testWithoutContext('successfully instantiates on Mac OS', () {
expect(IOSDevice('device-123', name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '1.0.0').majorSdkVersion, 1); IOSDevice(
expect(IOSDevice('device-123', name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '13.1.1').majorSdkVersion, 13); 'device-123',
expect(IOSDevice('device-123', name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '10').majorSdkVersion, 10); artifacts: mockArtifacts,
expect(IOSDevice('device-123', name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '0').majorSdkVersion, 0); fileSystem: mockFileSystem,
expect(IOSDevice('device-123', name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: 'bogus').majorSdkVersion, 0); platform: macPlatform,
}, overrides: <Type, Generator>{ iosDeploy: iosDeploy,
Platform: () => macPlatform, name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64
);
});
testWithoutContext('parses major version', () {
expect(IOSDevice(
'device-123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
platform: macPlatform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
cpuArchitecture: DarwinArch.arm64,
sdkVersion: '1.0.0'
).majorSdkVersion, 1);
expect(IOSDevice(
'device-123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
platform: macPlatform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
cpuArchitecture: DarwinArch.arm64,
sdkVersion: '13.1.1'
).majorSdkVersion, 13);
expect(IOSDevice(
'device-123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
platform: macPlatform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
cpuArchitecture: DarwinArch.arm64,
sdkVersion: '10'
).majorSdkVersion, 10);
expect(IOSDevice(
'device-123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
platform: macPlatform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
cpuArchitecture: DarwinArch.arm64,
sdkVersion: '0'
).majorSdkVersion, 0);
expect(IOSDevice(
'device-123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
platform: macPlatform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
cpuArchitecture: DarwinArch.arm64,
sdkVersion: 'bogus'
).majorSdkVersion, 0);
}); });
for (final Platform platform in unsupportedPlatforms) { for (final Platform platform in unsupportedPlatforms) {
testUsingContext('throws UnsupportedError exception if instantiated on ${platform.operatingSystem}', () { testWithoutContext('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',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
platform: platform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
);
},
throwsAssertionError, throwsAssertionError,
); );
}, overrides: <Type, Generator>{
Platform: () => platform,
}); });
} }
group('ios-deploy wrappers', () {
const String appId = '789';
IOSDevice device;
IOSDeploy iosDeploy;
FullMockProcessManager mockProcessManager;
setUp(() {
mockProcessManager = FullMockProcessManager();
iosDeploy = IOSDeploy(
artifacts: mockArtifacts,
cache: mockCache,
logger: mockLogger,
platform: macPlatform,
processManager: mockProcessManager,
);
device = IOSDevice(
'device-123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
platform: macPlatform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
);
});
testUsingContext('isAppInstalled() catches ProcessException from ios-deploy', () async {
final MockIOSApp mockApp = MockIOSApp();
when(mockApp.id).thenReturn(appId);
when(mockProcessManager.run(
any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenThrow(const ProcessException('ios-deploy', <String>[]));
final bool result = await device.isAppInstalled(mockApp);
expect(result, false);
});
testUsingContext('installApp() catches ProcessException from ios-deploy', () async {
const String bundlePath = '/path/to/bundle';
final MockIOSApp mockApp = MockIOSApp();
when(mockApp.id).thenReturn(appId);
when(mockApp.deviceBundlePath).thenReturn(bundlePath);
final MockDirectory mockDirectory = MockDirectory();
when(mockFileSystem.directory(bundlePath)).thenReturn(mockDirectory);
when(mockDirectory.existsSync()).thenReturn(true);
when(mockProcessManager.start(
any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenThrow(const ProcessException('ios-deploy', <String>[]));
final bool result = await device.installApp(mockApp);
expect(result, false);
});
testUsingContext('uninstallApp() catches ProcessException from ios-deploy', () async {
final MockIOSApp mockApp = MockIOSApp();
when(mockApp.id).thenReturn(appId);
when(mockProcessManager.start(
any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenThrow(const ProcessException('ios-deploy', <String>[]));
final bool result = await device.uninstallApp(mockApp);
expect(result, false);
});
});
group('.dispose()', () { group('.dispose()', () {
IOSDevice device; IOSDevice device;
MockIOSApp appPackage1; MockIOSApp appPackage1;
...@@ -103,6 +262,11 @@ void main() { ...@@ -103,6 +262,11 @@ void main() {
MockProcess mockProcess3; MockProcess mockProcess3;
IOSDevicePortForwarder portForwarder; IOSDevicePortForwarder portForwarder;
ForwardedPort forwardedPort; ForwardedPort forwardedPort;
Artifacts mockArtifacts;
MockCache mockCache;
MockLogger mockLogger;
IOSDeploy iosDeploy;
FileSystem mockFileSystem;
IOSDevicePortForwarder createPortForwarder( IOSDevicePortForwarder createPortForwarder(
ForwardedPort forwardedPort, ForwardedPort forwardedPort,
...@@ -130,10 +294,29 @@ void main() { ...@@ -130,10 +294,29 @@ void main() {
mockProcess2 = MockProcess(); mockProcess2 = MockProcess();
mockProcess3 = MockProcess(); mockProcess3 = MockProcess();
forwardedPort = ForwardedPort.withContext(123, 456, mockProcess3); forwardedPort = ForwardedPort.withContext(123, 456, mockProcess3);
mockArtifacts = MockArtifacts();
mockCache = MockCache();
mockFileSystem = MockFileSystem();
iosDeploy = IOSDeploy(
artifacts: mockArtifacts,
cache: mockCache,
logger: mockLogger,
platform: macPlatform,
processManager: FakeProcessManager.any(),
);
}); });
testUsingContext(' kills all log readers & port forwarders', () async { testWithoutContext('kills all log readers & port forwarders', () async {
device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64); device = IOSDevice(
'123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
platform: macPlatform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
);
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);
...@@ -146,8 +329,6 @@ void main() { ...@@ -146,8 +329,6 @@ void main() {
verify(mockProcess1.kill()); verify(mockProcess1.kill());
verify(mockProcess2.kill()); verify(mockProcess2.kill());
verify(mockProcess3.kill()); verify(mockProcess3.kill());
}, overrides: <Type, Generator>{
Platform: () => macPlatform,
}); });
}); });
...@@ -156,6 +337,7 @@ void main() { ...@@ -156,6 +337,7 @@ void main() {
MockArtifacts mockArtifacts; MockArtifacts mockArtifacts;
MockCache mockCache; MockCache mockCache;
MockFileSystem mockFileSystem; MockFileSystem mockFileSystem;
MockPlatform mockPlatform;
MockProcessManager mockProcessManager; MockProcessManager mockProcessManager;
MockDeviceLogReader mockLogReader; MockDeviceLogReader mockLogReader;
MockMDnsObservatoryDiscovery mockMDnsObservatoryDiscovery; MockMDnsObservatoryDiscovery mockMDnsObservatoryDiscovery;
...@@ -188,6 +370,8 @@ void main() { ...@@ -188,6 +370,8 @@ void main() {
mockCache = MockCache(); mockCache = MockCache();
when(mockCache.dyLdLibEntry).thenReturn(libraryEntry); when(mockCache.dyLdLibEntry).thenReturn(libraryEntry);
mockFileSystem = MockFileSystem(); mockFileSystem = MockFileSystem();
mockPlatform = MockPlatform();
when(mockPlatform.isMacOS).thenReturn(true);
mockMDnsObservatoryDiscovery = MockMDnsObservatoryDiscovery(); mockMDnsObservatoryDiscovery = MockMDnsObservatoryDiscovery();
mockProcessManager = MockProcessManager(); mockProcessManager = MockProcessManager();
mockLogReader = MockDeviceLogReader(); mockLogReader = MockDeviceLogReader();
...@@ -254,17 +438,43 @@ void main() { ...@@ -254,17 +438,43 @@ 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',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
platform: macPlatform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
);
device.portForwarder = mockPortForwarder; device.portForwarder = mockPortForwarder;
device.setLogReader(mockApp, mockLogReader); device.setLogReader(mockApp, mockLogReader);
await device.dispose(); await device.dispose();
verify(mockPortForwarder.dispose()).called(1); verify(mockPortForwarder.dispose()).called(1);
}, overrides: <Type, Generator>{
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',
name: 'iPhone 1',
sdkVersion: '13.3',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
platform: macPlatform,
iosDeploy: mockIosDeploy,
cpuArchitecture: DarwinArch.arm64,
);
when(mockIosDeploy.installApp(
deviceId: device.id,
bundlePath: anyNamed('bundlePath'),
launchArguments: <String>[],
)).thenAnswer((Invocation invocation) => Future<int>.value(0));
when(mockIosDeploy.runApp(
deviceId: device.id,
bundlePath: anyNamed('bundlePath'),
launchArguments: anyNamed('launchArguments'),
)).thenAnswer((Invocation invocation) => Future<int>.value(0));
device.portForwarder = mockPortForwarder; device.portForwarder = mockPortForwarder;
device.setLogReader(mockApp, mockLogReader); device.setLogReader(mockApp, mockLogReader);
final Uri uri = Uri( final Uri uri = Uri(
...@@ -298,10 +508,19 @@ void main() { ...@@ -298,10 +508,19 @@ void main() {
// By default, the .forward() method will try every port between 1024 // By default, the .forward() method will try every port between 1024
// and 65535; this test verifies we are killing iproxy processes when // and 65535; this test verifies we are killing iproxy processes when
// we timeout on a port // we timeout on a port
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,
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
platform: macPlatform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
);
final IOSDevicePortForwarder portForwarder = IOSDevicePortForwarder(device); final IOSDevicePortForwarder portForwarder = IOSDevicePortForwarder(device);
bool firstRun = true; bool firstRun = true;
final MockProcess successProcess = MockProcess( final MockProcess successProcess = MockProcess(
...@@ -334,8 +553,25 @@ void main() { ...@@ -334,8 +553,25 @@ void main() {
Usage: () => mockUsage, Usage: () => mockUsage,
}); });
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',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
platform: macPlatform,
iosDeploy: mockIosDeploy,
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
);
when(
mockIosDeploy.installApp(deviceId: device.id, bundlePath: anyNamed('bundlePath'), launchArguments: <String>[])
).thenAnswer((Invocation invocation) => Future<int>.value(0));
when(mockIosDeploy.runApp(
deviceId: device.id,
bundlePath: anyNamed('bundlePath'),
launchArguments: anyNamed('launchArguments'),
)).thenAnswer((Invocation invocation) => Future<int>.value(0));
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.
...@@ -351,10 +587,10 @@ void main() { ...@@ -351,10 +587,10 @@ void main() {
debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)), debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)),
platformArgs: <String, dynamic>{}, platformArgs: <String, dynamic>{},
); );
verify(mockUsage.sendEvent('ios-handshake', 'mdns-failure')).called(1);
verify(mockUsage.sendEvent('ios-handshake', 'fallback-success')).called(1);
expect(launchResult.started, isTrue); expect(launchResult.started, isTrue);
expect(launchResult.hasObservatory, isTrue); expect(launchResult.hasObservatory, isTrue);
verify(mockUsage.sendEvent('ios-handshake', 'mdns-failure')).called(1);
verify(mockUsage.sendEvent('ios-handshake', 'fallback-success')).called(1);
expect(await device.stopApp(mockApp), isFalse); expect(await device.stopApp(mockApp), isFalse);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts, Artifacts: () => mockArtifacts,
...@@ -366,8 +602,27 @@ void main() { ...@@ -366,8 +602,27 @@ void main() {
Usage: () => mockUsage, Usage: () => mockUsage,
}); });
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',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
platform: macPlatform,
iosDeploy: mockIosDeploy,
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
);
when(mockIosDeploy.installApp(
deviceId: device.id,
bundlePath: anyNamed('bundlePath'),
launchArguments: <String>[],
)).thenAnswer((Invocation invocation) => Future<int>.value(0));
when(mockIosDeploy.runApp(
deviceId: device.id,
bundlePath: anyNamed('bundlePath'),
launchArguments: anyNamed('launchArguments'),
)).thenAnswer((Invocation invocation) => Future<int>.value(0));
device.portForwarder = mockPortForwarder; device.portForwarder = mockPortForwarder;
device.setLogReader(mockApp, mockLogReader); device.setLogReader(mockApp, mockLogReader);
...@@ -384,6 +639,8 @@ void main() { ...@@ -384,6 +639,8 @@ void main() {
debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)), debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)),
platformArgs: <String, dynamic>{}, platformArgs: <String, dynamic>{},
); );
expect(launchResult.started, isFalse);
expect(launchResult.hasObservatory, isFalse);
verify(mockUsage.sendEvent( verify(mockUsage.sendEvent(
'ios-handshake', 'ios-handshake',
'failure', 'failure',
...@@ -392,8 +649,6 @@ void main() { ...@@ -392,8 +649,6 @@ void main() {
)).called(1); )).called(1);
verify(mockUsage.sendEvent('ios-handshake', 'mdns-failure')).called(1); verify(mockUsage.sendEvent('ios-handshake', 'mdns-failure')).called(1);
verify(mockUsage.sendEvent('ios-handshake', 'fallback-failure')).called(1); verify(mockUsage.sendEvent('ios-handshake', 'fallback-failure')).called(1);
expect(launchResult.started, isFalse);
expect(launchResult.hasObservatory, isFalse);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts, Artifacts: () => mockArtifacts,
Cache: () => mockCache, Cache: () => mockCache,
...@@ -405,25 +660,57 @@ void main() { ...@@ -405,25 +660,57 @@ 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',
name: 'iPhone 1',
fileSystem: mockFileSystem,
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
platform: mockPlatform,
artifacts: mockArtifacts,
iosDeploy: mockIosDeploy,
);
when(mockIosDeploy.installApp(
deviceId: device.id,
bundlePath: anyNamed('bundlePath'),
launchArguments: <String>[],
)).thenAnswer((Invocation invocation) => Future<int>.value(0));
when(mockIosDeploy.runApp(
deviceId: device.id,
bundlePath: anyNamed('bundlePath'),
launchArguments: anyNamed('launchArguments'),
)).thenAnswer((Invocation invocation) => Future<int>.value(0));
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)),
platformArgs: <String, dynamic>{}, platformArgs: <String, dynamic>{},
); );
verify(mockIosDeploy.installApp(
deviceId: device.id,
bundlePath: anyNamed('bundlePath'),
launchArguments: <String>[],
));
verify(mockIosDeploy.runApp(
deviceId: device.id,
bundlePath: anyNamed('bundlePath'),
launchArguments: anyNamed('launchArguments'),
));
expect(launchResult.started, isTrue); expect(launchResult.started, isTrue);
expect(launchResult.hasObservatory, isFalse); expect(launchResult.hasObservatory, isFalse);
expect(await device.stopApp(mockApp), isFalse); expect(await device.stopApp(mockApp), isFalse);
}, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts,
Cache: () => mockCache,
FileSystem: () => mockFileSystem,
Platform: () => macPlatform,
ProcessManager: () => mockProcessManager,
}); });
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',
name: 'iPhone 1',
sdkVersion: '13.3',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
platform: macPlatform,
iosDeploy: mockIosDeploy,
cpuArchitecture: DarwinArch.arm64,
);
device.setLogReader(mockApp, mockLogReader); device.setLogReader(mockApp, mockLogReader);
final Uri uri = Uri( final Uri uri = Uri(
scheme: 'http', scheme: 'http',
...@@ -443,6 +730,11 @@ void main() { ...@@ -443,6 +730,11 @@ void main() {
args = inv.namedArguments[const Symbol('launchArguments')] as List<String>; args = inv.namedArguments[const Symbol('launchArguments')] as List<String>;
return Future<int>.value(0); return Future<int>.value(0);
}); });
when(mockIosDeploy.installApp(
deviceId: device.id,
bundlePath: anyNamed('bundlePath'),
launchArguments: anyNamed('launchArguments'),
)).thenAnswer((Invocation invocation) => Future<int>.value(0));
final LaunchResult launchResult = await device.startApp(mockApp, final LaunchResult launchResult = await device.startApp(mockApp,
prebuiltApplication: true, prebuiltApplication: true,
...@@ -467,7 +759,16 @@ void main() { ...@@ -467,7 +759,16 @@ 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',
name: 'iPhone 1',
sdkVersion: '13.3',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
platform: macPlatform,
iosDeploy: mockIosDeploy,
cpuArchitecture: DarwinArch.arm64,
);
device.setLogReader(mockApp, mockLogReader); device.setLogReader(mockApp, mockLogReader);
final Uri uri = Uri( final Uri uri = Uri(
scheme: 'http', scheme: 'http',
...@@ -488,6 +789,11 @@ void main() { ...@@ -488,6 +789,11 @@ void main() {
return Future<int>.value(0); return Future<int>.value(0);
}); });
when(mockIosDeploy.installApp(
deviceId: device.id,
bundlePath: anyNamed('bundlePath'),
launchArguments: anyNamed('launchArguments'),
)).thenAnswer((Invocation invocation) => Future<int>.value(0));
final LaunchResult launchResult = await device.startApp(mockApp, final LaunchResult launchResult = await device.startApp(mockApp,
prebuiltApplication: true, prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled( debuggingOptions: DebuggingOptions.enabled(
...@@ -581,7 +887,16 @@ void main() { ...@@ -581,7 +887,16 @@ 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',
name: 'iPhone 1',
sdkVersion: '13.3',
artifacts: mockArtifacts,
fileSystem: globals.fs,
platform: macPlatform,
iosDeploy: mockIosDeploy,
cpuArchitecture: DarwinArch.arm64,
);
// Pre-create the expected build products. // Pre-create the expected build products.
targetBuildDir.createSync(recursive: true); targetBuildDir.createSync(recursive: true);
...@@ -611,7 +926,6 @@ void main() { ...@@ -611,7 +926,6 @@ void main() {
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
DoctorValidatorsProvider: () => FakeIosDoctorProvider(), DoctorValidatorsProvider: () => FakeIosDoctorProvider(),
IMobileDevice: () => mockIMobileDevice, IMobileDevice: () => mockIMobileDevice,
IOSDeploy: () => mockIosDeploy,
Platform: () => macPlatform, Platform: () => macPlatform,
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
}); });
...@@ -658,102 +972,154 @@ void main() { ...@@ -658,102 +972,154 @@ void main() {
}); });
group('Process calls', () { group('Process calls', () {
const String bundlePath = '/path/to/bundle';
FileSystem fs;
MockDirectory directory;
MockIOSApp mockApp; MockIOSApp mockApp;
MockArtifacts mockArtifacts; MockArtifacts mockArtifacts;
MockCache mockCache; MockCache mockCache;
MockFileSystem mockFileSystem; MockFileSystem mockFileSystem;
MockProcessManager mockProcessManager; MockLogger mockLogger;
const String installerPath = '/path/to/ideviceinstaller'; MockPlatform mockPlatform;
FullMockProcessManager mockProcessManager;
const String iosDeployPath = '/path/to/ios-deploy';
const String appId = '789'; const String appId = '789';
const MapEntry<String, String> libraryEntry = MapEntry<String, String>( const MapEntry<String, String> libraryEntry = MapEntry<String, String>(
'DYLD_LIBRARY_PATH', 'DYLD_LIBRARY_PATH',
'/path/to/libraries', '/path/to/libraries',
); );
final Map<String, String> env = Map<String, String>.fromEntries( IOSDeploy iosDeploy;
<MapEntry<String, String>>[libraryEntry]
);
setUp(() { setUp(() {
mockFileSystem = MockFileSystem();
directory = MockDirectory();
when(mockFileSystem.directory(bundlePath)).thenReturn(directory);
mockApp = MockIOSApp(); mockApp = MockIOSApp();
when(mockApp.id).thenReturn(appId);
when(mockApp.deviceBundlePath).thenReturn(bundlePath);
when(directory.existsSync()).thenReturn(true);
when(directory.path).thenReturn(bundlePath);
mockArtifacts = MockArtifacts(); mockArtifacts = MockArtifacts();
mockCache = MockCache(); mockCache = MockCache();
mockLogger = MockLogger();
mockPlatform = MockPlatform();
when(mockPlatform.environment).thenReturn(<String, String>{});
when(mockPlatform.isMacOS).thenReturn(true);
mockProcessManager = FullMockProcessManager();
when(
mockArtifacts.getArtifactPath(
Artifact.iosDeploy,
platform: anyNamed('platform'),
),
).thenReturn(iosDeployPath);
iosDeploy = IOSDeploy(
artifacts: mockArtifacts,
cache: mockCache,
logger: mockLogger,
platform: mockPlatform,
processManager: mockProcessManager,
);
when(mockCache.dyLdLibEntry).thenReturn(libraryEntry); when(mockCache.dyLdLibEntry).thenReturn(libraryEntry);
mockFileSystem = MockFileSystem(); mockFileSystem = MockFileSystem();
final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); final MemoryFileSystem memoryFileSystem = MemoryFileSystem();
when(mockFileSystem.currentDirectory) when(mockFileSystem.currentDirectory)
.thenReturn(memoryFileSystem.currentDirectory); .thenReturn(memoryFileSystem.currentDirectory);
mockProcessManager = MockProcessManager();
when(
mockArtifacts.getArtifactPath(
Artifact.ideviceinstaller,
platform: anyNamed('platform'),
),
).thenReturn(installerPath);
}); });
testUsingContext('installApp() invokes process with correct environment', () async { testWithoutContext('installApp() calls ios-deploy', () async {
final IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
const String bundlePath = '/path/to/bundle';
final List<String> args = <String>[installerPath, '-i', bundlePath];
when(mockApp.deviceBundlePath).thenReturn(bundlePath);
final MockDirectory directory = MockDirectory();
when(mockFileSystem.directory(bundlePath)).thenReturn(directory); when(mockFileSystem.directory(bundlePath)).thenReturn(directory);
when(directory.existsSync()).thenReturn(true); final IOSDevice device = IOSDevice(
when(mockProcessManager.run(args, environment: env)) '123',
.thenAnswer( name: 'iPhone 1',
(_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', '')) fileSystem: mockFileSystem,
); sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
platform: mockPlatform,
artifacts: mockArtifacts,
iosDeploy: iosDeploy,
);
final List<String> args = <String>[
iosDeployPath,
'--id',
device.id,
'--bundle',
bundlePath,
'--no-wifi',
];
when(mockProcessManager.start(any, workingDirectory: anyNamed('workingDirectory'), environment: anyNamed('environment'))).
thenAnswer((Invocation invocation) {
return Future<Process>.value(createMockProcess());
});
await device.installApp(mockApp); await device.installApp(mockApp);
verify(mockProcessManager.run(args, environment: env));
}, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts,
Cache: () => mockCache,
FileSystem: () => mockFileSystem,
Platform: () => macPlatform,
ProcessManager: () => mockProcessManager,
});
testUsingContext('isAppInstalled() invokes process with correct environment', () async { final List<String> invocationArguments = verify(mockProcessManager.start(
final IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64); captureAny,
final List<String> args = <String>[installerPath, '--list-apps']; workingDirectory: anyNamed('workingDirectory'),
when(mockProcessManager.run(args, environment: env)) environment: anyNamed('environment'),
.thenAnswer( )).captured.first as List<String>;
(_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', '')) expect(invocationArguments, args);
);
when(mockApp.id).thenReturn(appId);
await device.isAppInstalled(mockApp);
verify(mockProcessManager.run(args, environment: env));
}, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts,
Cache: () => mockCache,
Platform: () => macPlatform,
ProcessManager: () => mockProcessManager,
}); });
testUsingContext('uninstallApp() invokes process with correct environment', () async { testWithoutContext('uninstallApp() calls ios-deploy', () async {
final IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64); final IOSDevice device = IOSDevice(
final List<String> args = <String>[installerPath, '-U', appId]; '123',
when(mockApp.id).thenReturn(appId); name: 'iPhone 1',
when(mockProcessManager.run(args, environment: env)) fileSystem: fs,
.thenAnswer( sdkVersion: '13.3',
(_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', '')) cpuArchitecture: DarwinArch.arm64,
); platform: mockPlatform,
artifacts: mockArtifacts,
iosDeploy: iosDeploy,
);
final List<String> args = <String>[
iosDeployPath,
'--id',
device.id,
'--uninstall_only',
'--bundle_id',
appId,
];
when(mockProcessManager.start(args, workingDirectory: anyNamed('workingDirectory'), environment: anyNamed('environment'))).
thenAnswer((Invocation invocation) {
return Future<Process>.value(createMockProcess());
});
await device.uninstallApp(mockApp); await device.uninstallApp(mockApp);
verify(mockProcessManager.run(args, environment: env)); final List<String> invocationArguments = verify(mockProcessManager.start(
}, overrides: <Type, Generator>{ captureAny,
Artifacts: () => mockArtifacts, workingDirectory: anyNamed('workingDirectory'),
Cache: () => mockCache, environment: anyNamed('environment'),
Platform: () => macPlatform, )).captured.first as List<String>;
ProcessManager: () => mockProcessManager, expect(invocationArguments, args);
}); });
}); });
}); });
group('getAttachedDevices', () { group('getAttachedDevices', () {
MockXcdevice mockXcdevice; MockXcdevice mockXcdevice;
MockArtifacts mockArtifacts;
MockCache mockCache;
MockFileSystem mockFileSystem;
MockLogger mockLogger;
FullMockProcessManager mockProcessManager;
IOSDeploy iosDeploy;
setUp(() { setUp(() {
mockXcdevice = MockXcdevice(); mockXcdevice = MockXcdevice();
mockArtifacts = MockArtifacts();
mockCache = MockCache();
mockLogger = MockLogger();
mockFileSystem = MockFileSystem();
mockProcessManager = FullMockProcessManager();
iosDeploy = IOSDeploy(
artifacts: mockArtifacts,
cache: mockCache,
logger: mockLogger,
platform: macPlatform,
processManager: mockProcessManager,
);
}); });
final List<Platform> unsupportedPlatforms = <Platform>[linuxPlatform, windowsPlatform]; final List<Platform> unsupportedPlatforms = <Platform>[linuxPlatform, windowsPlatform];
...@@ -767,17 +1133,25 @@ void main() { ...@@ -767,17 +1133,25 @@ void main() {
}); });
} }
testUsingContext('returns attached devices', () async { testWithoutContext('returns attached devices', () async {
when(mockXcdevice.isInstalled).thenReturn(true); when(mockXcdevice.isInstalled).thenReturn(true);
final IOSDevice device = IOSDevice('d83d5bc53967baa0ee18626ba87b6254b2ab5418', name: 'Paired iPhone', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice(
'd83d5bc53967baa0ee18626ba87b6254b2ab5418',
name: 'Paired iPhone',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
artifacts: mockArtifacts,
iosDeploy: iosDeploy,
platform: macPlatform,
fileSystem: mockFileSystem,
);
when(mockXcdevice.getAvailableTetheredIOSDevices()) when(mockXcdevice.getAvailableTetheredIOSDevices())
.thenAnswer((Invocation invocation) => Future<List<IOSDevice>>.value(<IOSDevice>[device])); .thenAnswer((Invocation invocation) => Future<List<IOSDevice>>.value(<IOSDevice>[device]));
final List<IOSDevice> devices = await IOSDevice.getAttachedDevices(macPlatform, mockXcdevice); final List<IOSDevice> devices = await IOSDevice.getAttachedDevices(macPlatform, mockXcdevice);
expect(devices, hasLength(1)); expect(devices, hasLength(1));
expect(identical(devices.first, device), isTrue); expect(identical(devices.first, device), isTrue);
}, overrides: <Type, Generator>{
Platform: () => macPlatform,
}); });
}); });
...@@ -820,13 +1194,32 @@ void main() { ...@@ -820,13 +1194,32 @@ void main() {
expect(decoded, r'I \M-b\M^O syslog!'); expect(decoded, r'I \M-b\M^O syslog!');
}); });
}); });
group('logging', () { group('logging', () {
MockIMobileDevice mockIMobileDevice; MockIMobileDevice mockIMobileDevice;
MockIosProject mockIosProject; MockIosProject mockIosProject;
MockArtifacts mockArtifacts;
MockCache mockCache;
MockFileSystem mockFileSystem;
MockLogger mockLogger;
FullMockProcessManager mockProcessManager;
IOSDeploy iosDeploy;
setUp(() { setUp(() {
mockIMobileDevice = MockIMobileDevice(); mockIMobileDevice = MockIMobileDevice();
mockIosProject = MockIosProject(); mockIosProject = MockIosProject();
mockArtifacts = MockArtifacts();
mockCache = MockCache();
mockLogger = MockLogger();
mockFileSystem = MockFileSystem();
mockProcessManager = FullMockProcessManager();
iosDeploy = IOSDeploy(
artifacts: mockArtifacts,
cache: mockCache,
logger: mockLogger,
platform: macPlatform,
processManager: mockProcessManager,
);
}); });
testUsingContext('suppresses non-Flutter lines from output', () async { testUsingContext('suppresses non-Flutter lines from output', () async {
...@@ -843,7 +1236,16 @@ Runner(UIKit)[297] <Notice>: E is for enpitsu" ...@@ -843,7 +1236,16 @@ 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',
name: 'iPhone 1',
sdkVersion: '10.3',
cpuArchitecture: DarwinArch.arm64,
artifacts: mockArtifacts,
iosDeploy: iosDeploy,
platform: macPlatform,
fileSystem: mockFileSystem,
);
final DeviceLogReader logReader = device.getLogReader( final DeviceLogReader logReader = device.getLogReader(
app: await BuildableIOSApp.fromProject(mockIosProject), app: await BuildableIOSApp.fromProject(mockIosProject),
); );
...@@ -869,7 +1271,16 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt ...@@ -869,7 +1271,16 @@ 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',
name: 'iPhone 1',
sdkVersion: '10.3',
cpuArchitecture: DarwinArch.arm64,
artifacts: mockArtifacts,
iosDeploy: iosDeploy,
platform: macPlatform,
fileSystem: mockFileSystem,
);
final DeviceLogReader logReader = device.getLogReader( final DeviceLogReader logReader = device.getLogReader(
app: await BuildableIOSApp.fromProject(mockIosProject), app: await BuildableIOSApp.fromProject(mockIosProject),
); );
...@@ -887,47 +1298,97 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt ...@@ -887,47 +1298,97 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
Platform: () => macPlatform, Platform: () => macPlatform,
}); });
}); });
testUsingContext('IOSDevice.isSupportedForProject is true on module project', () async {
globals.fs.file('pubspec.yaml')
..createSync()
..writeAsStringSync(r'''
name: example
flutter:
module: {}
''');
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);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => macPlatform,
});
testUsingContext('IOSDevice.isSupportedForProject is true with editable host app', () async {
globals.fs.file('pubspec.yaml').createSync();
globals.fs.file('.packages').createSync();
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);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => macPlatform,
});
testUsingContext('IOSDevice.isSupportedForProject is false with no host app and no module', () async { group('isSupportedForProject', () {
globals.fs.file('pubspec.yaml').createSync(); Artifacts mockArtifacts;
globals.fs.file('.packages').createSync(); MockCache mockCache;
final FlutterProject flutterProject = FlutterProject.current(); MockLogger mockLogger;
IOSDeploy iosDeploy;
setUp(() {
mockArtifacts = MockArtifacts();
mockCache = MockCache();
iosDeploy = IOSDeploy(
artifacts: mockArtifacts,
cache: mockCache,
logger: mockLogger,
platform: macPlatform,
processManager: FakeProcessManager.any(),
);
});
expect(IOSDevice('test', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64).isSupportedForProject(flutterProject), false); testUsingContext('is true on module project', () async {
}, overrides: <Type, Generator>{ globals.fs.file('pubspec.yaml')
FileSystem: () => MemoryFileSystem(), ..createSync()
ProcessManager: () => FakeProcessManager.any(), ..writeAsStringSync(r'''
Platform: () => macPlatform, name: example
flutter:
module: {}
''');
globals.fs.file('.packages').createSync();
final FlutterProject flutterProject = FlutterProject.current();
final IOSDevice device = IOSDevice(
'test',
artifacts: mockArtifacts,
fileSystem: globals.fs,
iosDeploy: iosDeploy,
platform: globals.platform,
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64
);
expect(device.isSupportedForProject(flutterProject), true);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => macPlatform,
});
testUsingContext('is true with editable host app', () async {
globals.fs.file('pubspec.yaml').createSync();
globals.fs.file('.packages').createSync();
globals.fs.directory('ios').createSync();
final FlutterProject flutterProject = FlutterProject.current();
final IOSDevice device = IOSDevice(
'test',
artifacts: mockArtifacts,
fileSystem: globals.fs,
iosDeploy: iosDeploy,
platform: globals.platform,
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
);
expect(device.isSupportedForProject(flutterProject), true);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => macPlatform,
});
testUsingContext('is false with no host app and no module', () async {
globals.fs.file('pubspec.yaml').createSync();
globals.fs.file('.packages').createSync();
final FlutterProject flutterProject = FlutterProject.current();
final IOSDevice device = IOSDevice(
'test',
artifacts: mockArtifacts,
fileSystem: globals.fs,
iosDeploy: iosDeploy,
platform: globals.platform,
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
);
expect(device.isSupportedForProject(flutterProject), false);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => macPlatform,
});
}); });
} }
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io' show Process;
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/ios/ios_deploy.dart';
import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart';
import 'package:process/process.dart';
import '../../src/common.dart';
import '../../src/mocks.dart';
class MockArtifacts extends Mock implements Artifacts {}
class MockCache extends Mock implements Cache {}
class MockLogger extends Mock implements Logger {}
class MockPlatform extends Mock implements Platform {}
class MockProcess extends Mock implements Process {}
class MockProcessManager extends Mock implements ProcessManager {}
void main () {
group('IOSDeploy()', () {
Artifacts mockArtifacts;
Cache mockCache;
IOSDeploy iosDeploy;
Logger mockLogger;
Platform mockPlatform;
ProcessManager mockProcessManager;
const String iosDeployPath = '/path/to/ios-deploy';
const String deviceId = '123';
const String bundleId = 'com.example.app';
setUp(() {
mockArtifacts = MockArtifacts();
when(mockArtifacts.getArtifactPath(Artifact.iosDeploy, platform: TargetPlatform.ios))
.thenReturn(iosDeployPath);
mockCache = MockCache();
const MapEntry<String, String> mapEntry = MapEntry<String, String>('DYLD_LIBRARY_PATH', '/path/to/libs');
when(mockCache.dyLdLibEntry).thenReturn(mapEntry);
mockLogger = MockLogger();
mockPlatform = MockPlatform();
when(mockPlatform.environment).thenReturn(<String, String>{
'PATH': '/usr/local/bin:/usr/bin',
});
mockProcessManager = MockProcessManager();
iosDeploy = IOSDeploy(
artifacts: mockArtifacts,
cache: mockCache,
logger: mockLogger,
platform: mockPlatform,
processManager: mockProcessManager,
);
});
testWithoutContext('iosDeployEnv returns path with /usr/bin first', () {
final Map<String, String> env = iosDeploy.iosDeployEnv;
expect(env['PATH'].startsWith('/usr/bin'), true);
});
testWithoutContext('uninstallApp() calls ios-deploy with correct arguments and returns 0 on success', () async {
final List<String> args = <String>[
iosDeployPath,
'--id',
deviceId,
'--uninstall_only',
'--bundle_id',
bundleId,
];
when(mockProcessManager.start(
args,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenAnswer((Invocation invocation) => Future<Process>.value(createMockProcess(exitCode: 0)));
final int exitCode = await iosDeploy.uninstallApp(
deviceId: deviceId,
bundleId: bundleId,
);
verify(mockProcessManager.start(
args,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
));
expect(exitCode, 0);
});
testWithoutContext('uninstallApp() returns non-zero exit code when ios-deploy does the same', () async {
final List<String> args = <String>[
iosDeployPath,
'--id',
deviceId,
'--uninstall_only',
'--bundle_id',
bundleId,
];
when(mockProcessManager.start(
args,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenAnswer((Invocation invocation) => Future<Process>.value(createMockProcess(exitCode: 1)));
final int exitCode = await iosDeploy.uninstallApp(
deviceId: deviceId,
bundleId: bundleId,
);
verify(mockProcessManager.start(
args,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
));
expect(exitCode, 1);
});
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment