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 {
'usbmuxd',
'libplist',
'openssl',
'ideviceinstaller',
'ios-deploy',
'libzip',
].map((String packageName) => path.join(getArtifactPath(), packageName)).join(':');
return <String, String>{'DYLD_LIBRARY_PATH': libPath};
}
......
......@@ -1261,9 +1261,7 @@ class IosUsbArtifacts extends CachedArtifact {
'usbmuxd',
'libplist',
'openssl',
'ideviceinstaller',
'ios-deploy',
'libzip',
];
// For unknown reasons, users are getting into bad states where libimobiledevice is
......
......@@ -6,6 +6,7 @@ import 'dart:async';
import '../application_package.dart';
import '../base/common.dart';
import '../base/io.dart';
import '../cache.dart';
import '../device.dart';
import '../globals.dart' as globals;
......@@ -54,11 +55,15 @@ Future<bool> installApp(Device device, ApplicationPackage package, { bool uninst
return false;
}
if (uninstall && await device.isAppInstalled(package)) {
globals.printStatus('Uninstalling old version...');
if (!await device.uninstallApp(package)) {
globals.printError('Warning: uninstalling old version failed');
try {
if (uninstall && await device.isAppInstalled(package)) {
globals.printStatus('Uninstalling old version...');
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);
......
......@@ -35,7 +35,7 @@ import 'fuchsia/fuchsia_device.dart' show FuchsiaDeviceTools;
import 'fuchsia/fuchsia_sdk.dart' show FuchsiaSdk, FuchsiaArtifacts;
import 'fuchsia/fuchsia_workflow.dart' show FuchsiaWorkflow;
import 'globals.dart' as globals;
import 'ios/devices.dart' show IOSDeploy;
import 'ios/ios_deploy.dart';
import 'ios/ios_workflow.dart';
import 'ios/mac.dart';
import 'ios/simulators.dart';
......@@ -125,7 +125,13 @@ Future<T> runInContext<T>(
GradleUtils: () => GradleUtils(),
HotRunnerConfig: () => HotRunnerConfig(),
IMobileDevice: () => IMobileDevice(),
IOSDeploy: () => const IOSDeploy(),
IOSDeploy: () => IOSDeploy(
artifacts: globals.artifacts,
cache: globals.cache,
logger: globals.logger,
platform: globals.platform,
processManager: globals.processManager,
),
IOSSimulatorUtils: () => IOSSimulatorUtils(),
IOSWorkflow: () => const IOSWorkflow(),
KernelCompilerFactory: () => const KernelCompilerFactory(),
......
......@@ -20,6 +20,7 @@ import 'base/os.dart';
import 'base/terminal.dart';
import 'base/user_messages.dart';
import 'cache.dart';
import 'ios/ios_deploy.dart';
import 'ios/mac.dart';
import 'macos/xcode.dart';
import 'persistent_tool_state.dart';
......@@ -62,6 +63,7 @@ AndroidStudio get androidStudio => context.get<AndroidStudio>();
AndroidSdk get androidSdk => context.get<AndroidSdk>();
FlutterVersion get flutterVersion => context.get<FlutterVersion>();
IMobileDevice get iMobileDevice => context.get<IMobileDevice>();
IOSDeploy get iosDeploy => context.get<IOSDeploy>();
UserMessages get userMessages => context.get<UserMessages>();
Xcode get xcode => context.get<Xcode>();
......
......@@ -11,7 +11,6 @@ import 'package:platform/platform.dart';
import '../application_package.dart';
import '../artifacts.dart';
import '../base/common.dart';
import '../base/context.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
......@@ -25,85 +24,11 @@ import '../mdns_discovery.dart';
import '../project.dart';
import '../protocol_discovery.dart';
import '../vmservice.dart';
import 'code_signing.dart';
import 'fallback_discovery.dart';
import 'ios_deploy.dart';
import 'ios_workflow.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 {
IOSDevices() : super('iOS devices');
......@@ -122,35 +47,38 @@ class IOSDevices extends PollingDeviceDiscovery {
class IOSDevice extends Device {
IOSDevice(String id, {
@required FileSystem fileSystem,
@required this.name,
@required this.cpuArchitecture,
@required String sdkVersion,
@required Platform platform,
@required Artifacts artifacts,
@required IOSDeploy iosDeploy,
})
: _sdkVersion = sdkVersion,
_iosDeploy = iosDeploy,
_fileSystem = fileSystem,
super(
id,
category: Category.mobile,
platformType: PlatformType.ios,
ephemeral: true,
) {
if (!globals.platform.isMacOS) {
if (!platform.isMacOS) {
assert(false, 'Control of iOS devices or simulators only supported on Mac OS.');
return;
}
_installerPath = globals.artifacts.getArtifactPath(
Artifact.ideviceinstaller,
platform: TargetPlatform.ios,
);
_iproxyPath = globals.artifacts.getArtifactPath(
_iproxyPath = artifacts.getArtifactPath(
Artifact.iproxy,
platform: TargetPlatform.ios,
);
}
String _installerPath;
String _iproxyPath;
final String _sdkVersion;
final IOSDeploy _iosDeploy;
final FileSystem _fileSystem;
/// May be 0 if version cannot be parsed.
int get majorSdkVersion {
......@@ -200,19 +128,17 @@ class IOSDevice extends Device {
@override
Future<bool> isAppInstalled(IOSApp app) async {
RunResult apps;
bool result;
try {
apps = await processUtils.run(
<String>[_installerPath, '--list-apps'],
throwOnError: true,
environment: Map<String, String>.fromEntries(
<MapEntry<String, String>>[globals.cache.dyLdLibEntry],
),
result = await _iosDeploy.isAppInstalled(
bundleId: app.id,
deviceId: id,
);
} on ProcessException {
} on ProcessException catch (e) {
globals.printError(e.message);
return false;
}
return RegExp(app.id, multiLine: true).hasMatch(apps.stdout);
return result;
}
@override
......@@ -220,42 +146,50 @@ class IOSDevice extends Device {
@override
Future<bool> installApp(IOSApp app) async {
final Directory bundle = globals.fs.directory(app.deviceBundlePath);
final Directory bundle = _fileSystem.directory(app.deviceBundlePath);
if (!bundle.existsSync()) {
globals.printError('Could not find application bundle at ${bundle.path}; have you run "flutter build ios"?');
return false;
}
int installationResult;
try {
await processUtils.run(
<String>[_installerPath, '-i', app.deviceBundlePath],
throwOnError: true,
environment: Map<String, String>.fromEntries(
<MapEntry<String, String>>[globals.cache.dyLdLibEntry],
),
installationResult = await _iosDeploy.installApp(
deviceId: id,
bundlePath: bundle.path,
launchArguments: <String>[],
);
return true;
} on ProcessException catch (error) {
globals.printError(error.message);
} on ProcessException catch (e) {
globals.printError(e.message);
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
Future<bool> uninstallApp(IOSApp app) async {
int uninstallationResult;
try {
await processUtils.run(
<String>[_installerPath, '-U', app.id],
throwOnError: true,
environment: Map<String, String>.fromEntries(
<MapEntry<String, String>>[globals.cache.dyLdLibEntry],
),
uninstallationResult = await _iosDeploy.uninstallApp(
deviceId: id,
bundleId: app.id,
);
return true;
} on ProcessException catch (error) {
globals.printError(error.message);
} on ProcessException catch (e) {
globals.printError(e.message);
return false;
}
if (uninstallationResult != 0) {
globals.printError('Could not uninstall ${app.id} on $id.');
return false;
}
return true;
}
@override
......@@ -301,7 +235,7 @@ class IOSDevice extends Device {
packageId ??= package.id;
// 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()) {
globals.printError('Could not find the built application bundle at ${bundle.path}.');
return LaunchResult.failed();
......@@ -359,13 +293,13 @@ class IOSDevice extends Device {
ipv6: ipv6,
);
}
final int installationResult = await IOSDeploy.instance.runApp(
final int installationResult = await _iosDeploy.runApp(
deviceId: id,
bundlePath: bundle.path,
launchArguments: launchArguments,
);
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(' open ios/Runner.xcworkspace');
globals.printError('');
......@@ -395,6 +329,9 @@ class IOSDevice extends Device {
return LaunchResult.failed();
}
return LaunchResult.succeeded(observatoryUri: localUri);
} on ProcessException catch (e) {
globals.printError(e.message);
return LaunchResult.failed();
} finally {
installStatus.stop();
}
......@@ -450,7 +387,7 @@ class IOSDevice extends Device {
@override
Future<void> dispose() async {
_logReaders.forEach((IOSApp application, DeviceLogReader logReader) {
_logReaders?.forEach((IOSApp application, DeviceLogReader logReader) {
logReader.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({
return XcodeBuildResult(success: false);
}
final XcodeProjectInfo projectInfo = await xcodeProjectInterpreter.getInfo(app.project.hostAppRoot.path);
if (!projectInfo.targets.contains('Runner')) {
globals.printError('The Xcode project does not define target "Runner" which is needed by Flutter tooling.');
......
......@@ -15,6 +15,7 @@ import '../base/logger.dart';
import '../base/process.dart';
import '../build_info.dart';
import '../convert.dart';
import '../globals.dart' as globals;
import '../ios/devices.dart';
import '../ios/xcodeproj.dart';
import '../reporting/reporting.dart';
......@@ -349,6 +350,10 @@ class XCDevice {
name: device['name'] as String,
cpuArchitecture: _cpuArchitecture(deviceProperties),
sdkVersion: _sdkVersion(deviceProperties),
artifacts: globals.artifacts,
fileSystem: globals.fs,
iosDeploy: globals.iosDeploy,
platform: globals.platform,
));
}
return devices;
......
......@@ -12,6 +12,7 @@ import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/create.dart';
......@@ -19,6 +20,7 @@ import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/ios/devices.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/macos/xcode.dart';
import 'package:flutter_tools/src/mdns_discovery.dart';
......@@ -40,19 +42,23 @@ class MockIOSApp extends Mock implements IOSApp {}
class MockApplicationPackage extends Mock implements ApplicationPackage {}
class MockArtifacts extends Mock implements Artifacts {}
class MockCache extends Mock implements Cache {}
class MockDevicePortForwarder extends Mock implements DevicePortForwarder {}
class MockDirectory extends Mock implements Directory {}
class MockFile extends Mock implements File {}
class MockFileSystem extends Mock implements FileSystem {}
class MockForwardedPort extends Mock implements ForwardedPort {}
class MockIMobileDevice extends Mock implements IMobileDevice {}
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 MockMDnsObservatoryDiscoveryResult extends Mock implements MDnsObservatoryDiscoveryResult {}
class MockXcode extends Mock implements Xcode {}
class MockFile extends Mock implements File {}
class MockPlatform extends Mock implements Platform {}
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 MockXcdevice extends Mock implements XCDevice {}
class MockXcode extends Mock implements Xcode {}
void main() {
final FakePlatform macPlatform = FakePlatform.fromPlatform(const LocalPlatform());
......@@ -64,34 +70,187 @@ void main() {
group('IOSDevice', () {
final List<Platform> unsupportedPlatforms = <Platform>[linuxPlatform, windowsPlatform];
Artifacts mockArtifacts;
MockCache mockCache;
MockLogger mockLogger;
IOSDeploy iosDeploy;
FileSystem mockFileSystem;
testUsingContext('successfully instantiates on Mac OS', () {
IOSDevice('device-123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
}, overrides: <Type, Generator>{
Platform: () => macPlatform,
setUp(() {
mockArtifacts = MockArtifacts();
mockCache = MockCache();
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', () {
expect(IOSDevice('device-123', name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '1.0.0').majorSdkVersion, 1);
expect(IOSDevice('device-123', name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '13.1.1').majorSdkVersion, 13);
expect(IOSDevice('device-123', name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '10').majorSdkVersion, 10);
expect(IOSDevice('device-123', name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '0').majorSdkVersion, 0);
expect(IOSDevice('device-123', name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: 'bogus').majorSdkVersion, 0);
}, overrides: <Type, Generator>{
Platform: () => macPlatform,
testWithoutContext('successfully instantiates on Mac OS', () {
IOSDevice(
'device-123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
platform: macPlatform,
iosDeploy: iosDeploy,
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) {
testUsingContext('throws UnsupportedError exception if instantiated on ${platform.operatingSystem}', () {
testWithoutContext('throws UnsupportedError exception if instantiated on ${platform.operatingSystem}', () {
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,
);
}, 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()', () {
IOSDevice device;
MockIOSApp appPackage1;
......@@ -103,6 +262,11 @@ void main() {
MockProcess mockProcess3;
IOSDevicePortForwarder portForwarder;
ForwardedPort forwardedPort;
Artifacts mockArtifacts;
MockCache mockCache;
MockLogger mockLogger;
IOSDeploy iosDeploy;
FileSystem mockFileSystem;
IOSDevicePortForwarder createPortForwarder(
ForwardedPort forwardedPort,
......@@ -130,10 +294,29 @@ void main() {
mockProcess2 = MockProcess();
mockProcess3 = MockProcess();
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 {
device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
testWithoutContext('kills all log readers & port forwarders', () async {
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);
logReader2 = createLogReader(device, appPackage2, mockProcess2);
portForwarder = createPortForwarder(forwardedPort, device);
......@@ -146,8 +329,6 @@ void main() {
verify(mockProcess1.kill());
verify(mockProcess2.kill());
verify(mockProcess3.kill());
}, overrides: <Type, Generator>{
Platform: () => macPlatform,
});
});
......@@ -156,6 +337,7 @@ void main() {
MockArtifacts mockArtifacts;
MockCache mockCache;
MockFileSystem mockFileSystem;
MockPlatform mockPlatform;
MockProcessManager mockProcessManager;
MockDeviceLogReader mockLogReader;
MockMDnsObservatoryDiscovery mockMDnsObservatoryDiscovery;
......@@ -188,6 +370,8 @@ void main() {
mockCache = MockCache();
when(mockCache.dyLdLibEntry).thenReturn(libraryEntry);
mockFileSystem = MockFileSystem();
mockPlatform = MockPlatform();
when(mockPlatform.isMacOS).thenReturn(true);
mockMDnsObservatoryDiscovery = MockMDnsObservatoryDiscovery();
mockProcessManager = MockProcessManager();
mockLogReader = MockDeviceLogReader();
......@@ -254,17 +438,43 @@ void main() {
});
testUsingContext('disposing device disposes the portForwarder', () async {
final IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice(
'123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
platform: macPlatform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
);
device.portForwarder = mockPortForwarder;
device.setLogReader(mockApp, mockLogReader);
await device.dispose();
verify(mockPortForwarder.dispose()).called(1);
}, overrides: <Type, Generator>{
Platform: () => macPlatform,
});
testUsingContext(' succeeds in debug mode via mDNS', () async {
final IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
testUsingContext('succeeds in debug mode via mDNS', () async {
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.setLogReader(mockApp, mockLogReader);
final Uri uri = Uri(
......@@ -298,10 +508,19 @@ void main() {
// By default, the .forward() method will try every port between 1024
// and 65535; this test verifies we are killing iproxy processes when
// 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 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);
bool firstRun = true;
final MockProcess successProcess = MockProcess(
......@@ -334,8 +553,25 @@ void main() {
Usage: () => mockUsage,
});
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);
testUsingContext('succeeds in debug mode when mDNS fails by falling back to manual protocol discovery', () async {
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.setLogReader(mockApp, mockLogReader);
// Now that the reader is used, start writing messages to it.
......@@ -351,10 +587,10 @@ void main() {
debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)),
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.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);
}, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts,
......@@ -366,8 +602,27 @@ void main() {
Usage: () => mockUsage,
});
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);
testUsingContext('fails in debug mode when mDNS fails and when Observatory URI is malformed', () async {
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.setLogReader(mockApp, mockLogReader);
......@@ -384,6 +639,8 @@ void main() {
debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)),
platformArgs: <String, dynamic>{},
);
expect(launchResult.started, isFalse);
expect(launchResult.hasObservatory, isFalse);
verify(mockUsage.sendEvent(
'ios-handshake',
'failure',
......@@ -392,8 +649,6 @@ void main() {
)).called(1);
verify(mockUsage.sendEvent('ios-handshake', 'mdns-failure')).called(1);
verify(mockUsage.sendEvent('ios-handshake', 'fallback-failure')).called(1);
expect(launchResult.started, isFalse);
expect(launchResult.hasObservatory, isFalse);
}, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts,
Cache: () => mockCache,
......@@ -405,25 +660,57 @@ void main() {
});
testUsingContext('succeeds in release mode', () async {
final IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice(
'123',
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,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
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.hasObservatory, 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 {
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);
final Uri uri = Uri(
scheme: 'http',
......@@ -443,6 +730,11 @@ void main() {
args = inv.namedArguments[const Symbol('launchArguments')] as List<String>;
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,
prebuiltApplication: true,
......@@ -467,7 +759,16 @@ void main() {
});
testUsingContext('succeeds with --device-vmservice-port', () async {
final IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice(
'123',
name: 'iPhone 1',
sdkVersion: '13.3',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
platform: macPlatform,
iosDeploy: mockIosDeploy,
cpuArchitecture: DarwinArch.arm64,
);
device.setLogReader(mockApp, mockLogReader);
final Uri uri = Uri(
scheme: 'http',
......@@ -488,6 +789,11 @@ void main() {
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,
prebuiltApplication: true,
debuggingOptions: DebuggingOptions.enabled(
......@@ -581,7 +887,16 @@ void main() {
final IOSApp app = await AbsoluteBuildableIOSApp.fromProject(
FlutterProject.fromDirectory(projectDir).ios);
final IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice(
'123',
name: 'iPhone 1',
sdkVersion: '13.3',
artifacts: mockArtifacts,
fileSystem: globals.fs,
platform: macPlatform,
iosDeploy: mockIosDeploy,
cpuArchitecture: DarwinArch.arm64,
);
// Pre-create the expected build products.
targetBuildDir.createSync(recursive: true);
......@@ -611,7 +926,6 @@ void main() {
}, overrides: <Type, Generator>{
DoctorValidatorsProvider: () => FakeIosDoctorProvider(),
IMobileDevice: () => mockIMobileDevice,
IOSDeploy: () => mockIosDeploy,
Platform: () => macPlatform,
ProcessManager: () => mockProcessManager,
});
......@@ -658,102 +972,154 @@ void main() {
});
group('Process calls', () {
const String bundlePath = '/path/to/bundle';
FileSystem fs;
MockDirectory directory;
MockIOSApp mockApp;
MockArtifacts mockArtifacts;
MockCache mockCache;
MockFileSystem mockFileSystem;
MockProcessManager mockProcessManager;
const String installerPath = '/path/to/ideviceinstaller';
MockLogger mockLogger;
MockPlatform mockPlatform;
FullMockProcessManager mockProcessManager;
const String iosDeployPath = '/path/to/ios-deploy';
const String appId = '789';
const MapEntry<String, String> libraryEntry = MapEntry<String, String>(
'DYLD_LIBRARY_PATH',
'/path/to/libraries',
);
final Map<String, String> env = Map<String, String>.fromEntries(
<MapEntry<String, String>>[libraryEntry]
);
IOSDeploy iosDeploy;
setUp(() {
mockFileSystem = MockFileSystem();
directory = MockDirectory();
when(mockFileSystem.directory(bundlePath)).thenReturn(directory);
mockApp = MockIOSApp();
when(mockApp.id).thenReturn(appId);
when(mockApp.deviceBundlePath).thenReturn(bundlePath);
when(directory.existsSync()).thenReturn(true);
when(directory.path).thenReturn(bundlePath);
mockArtifacts = MockArtifacts();
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);
mockFileSystem = MockFileSystem();
final MemoryFileSystem memoryFileSystem = MemoryFileSystem();
when(mockFileSystem.currentDirectory)
.thenReturn(memoryFileSystem.currentDirectory);
mockProcessManager = MockProcessManager();
when(
mockArtifacts.getArtifactPath(
Artifact.ideviceinstaller,
platform: anyNamed('platform'),
),
).thenReturn(installerPath);
});
testUsingContext('installApp() invokes process with correct environment', () 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();
testWithoutContext('installApp() calls ios-deploy', () async {
when(mockFileSystem.directory(bundlePath)).thenReturn(directory);
when(directory.existsSync()).thenReturn(true);
when(mockProcessManager.run(args, environment: env))
.thenAnswer(
(_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', ''))
);
final IOSDevice device = IOSDevice(
'123',
name: 'iPhone 1',
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);
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 IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
final List<String> args = <String>[installerPath, '--list-apps'];
when(mockProcessManager.run(args, environment: env))
.thenAnswer(
(_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', ''))
);
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,
final List<String> invocationArguments = verify(mockProcessManager.start(
captureAny,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).captured.first as List<String>;
expect(invocationArguments, args);
});
testUsingContext('uninstallApp() invokes process with correct environment', () async {
final IOSDevice device = IOSDevice('123', name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64);
final List<String> args = <String>[installerPath, '-U', appId];
when(mockApp.id).thenReturn(appId);
when(mockProcessManager.run(args, environment: env))
.thenAnswer(
(_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', ''))
);
testWithoutContext('uninstallApp() calls ios-deploy', () async {
final IOSDevice device = IOSDevice(
'123',
name: 'iPhone 1',
fileSystem: fs,
sdkVersion: '13.3',
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);
verify(mockProcessManager.run(args, environment: env));
}, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts,
Cache: () => mockCache,
Platform: () => macPlatform,
ProcessManager: () => mockProcessManager,
final List<String> invocationArguments = verify(mockProcessManager.start(
captureAny,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).captured.first as List<String>;
expect(invocationArguments, args);
});
});
});
group('getAttachedDevices', () {
MockXcdevice mockXcdevice;
MockArtifacts mockArtifacts;
MockCache mockCache;
MockFileSystem mockFileSystem;
MockLogger mockLogger;
FullMockProcessManager mockProcessManager;
IOSDeploy iosDeploy;
setUp(() {
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];
......@@ -767,17 +1133,25 @@ void main() {
});
}
testUsingContext('returns attached devices', () async {
testWithoutContext('returns attached devices', () async {
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())
.thenAnswer((Invocation invocation) => Future<List<IOSDevice>>.value(<IOSDevice>[device]));
final List<IOSDevice> devices = await IOSDevice.getAttachedDevices(macPlatform, mockXcdevice);
expect(devices, hasLength(1));
expect(identical(devices.first, device), isTrue);
}, overrides: <Type, Generator>{
Platform: () => macPlatform,
});
});
......@@ -820,13 +1194,32 @@ void main() {
expect(decoded, r'I \M-b\M^O syslog!');
});
});
group('logging', () {
MockIMobileDevice mockIMobileDevice;
MockIosProject mockIosProject;
MockArtifacts mockArtifacts;
MockCache mockCache;
MockFileSystem mockFileSystem;
MockLogger mockLogger;
FullMockProcessManager mockProcessManager;
IOSDeploy iosDeploy;
setUp(() {
mockIMobileDevice = MockIMobileDevice();
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 {
......@@ -843,7 +1236,16 @@ Runner(UIKit)[297] <Notice>: E is for enpitsu"
return Future<Process>.value(mockProcess);
});
final IOSDevice device = IOSDevice('123456', name: 'iPhone 1', sdkVersion: '10.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice(
'123456',
name: 'iPhone 1',
sdkVersion: '10.3',
cpuArchitecture: DarwinArch.arm64,
artifacts: mockArtifacts,
iosDeploy: iosDeploy,
platform: macPlatform,
fileSystem: mockFileSystem,
);
final DeviceLogReader logReader = device.getLogReader(
app: await BuildableIOSApp.fromProject(mockIosProject),
);
......@@ -869,7 +1271,16 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
return Future<Process>.value(mockProcess);
});
final IOSDevice device = IOSDevice('123456', name: 'iPhone 1', sdkVersion: '10.3', cpuArchitecture: DarwinArch.arm64);
final IOSDevice device = IOSDevice(
'123456',
name: 'iPhone 1',
sdkVersion: '10.3',
cpuArchitecture: DarwinArch.arm64,
artifacts: mockArtifacts,
iosDeploy: iosDeploy,
platform: macPlatform,
fileSystem: mockFileSystem,
);
final DeviceLogReader logReader = device.getLogReader(
app: await BuildableIOSApp.fromProject(mockIosProject),
);
......@@ -887,47 +1298,97 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
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 {
globals.fs.file('pubspec.yaml').createSync();
globals.fs.file('.packages').createSync();
final FlutterProject flutterProject = FlutterProject.current();
group('isSupportedForProject', () {
Artifacts mockArtifacts;
MockCache mockCache;
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);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => macPlatform,
testUsingContext('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();
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