Unverified Commit 2d9902d9 authored by Christopher Fujino's avatar Christopher Fujino Committed by GitHub

Cleanup ios devices (#52568)

parent dc8ffe04
......@@ -76,7 +76,11 @@ class DeviceManager {
androidWorkflow: androidWorkflow,
processManager: globals.processManager,
),
IOSDevices(),
IOSDevices(
platform: globals.platform,
xcdevice: globals.xcdevice,
iosWorkflow: globals.iosWorkflow,
),
IOSSimulators(iosSimulatorUtils: globals.iosSimulatorUtils),
FuchsiaDevices(),
FlutterTesterDevices(),
......
......@@ -27,25 +27,51 @@ import '../protocol_discovery.dart';
import '../vmservice.dart';
import 'fallback_discovery.dart';
import 'ios_deploy.dart';
import 'ios_workflow.dart';
import 'mac.dart';
class IOSDevices extends PollingDeviceDiscovery {
IOSDevices() : super('iOS devices');
// TODO(fujino): make these required and remove fallbacks once internal invocations migrated
IOSDevices({
Platform platform,
XCDevice xcdevice,
IOSWorkflow iosWorkflow,
}) : _platform = platform ?? globals.platform,
_xcdevice = xcdevice ?? globals.xcdevice,
_iosWorkflow = iosWorkflow ?? globals.iosWorkflow,
super('iOS devices');
final Platform _platform;
final XCDevice _xcdevice;
final IOSWorkflow _iosWorkflow;
@override
bool get supportsPlatform => globals.platform.isMacOS;
bool get supportsPlatform => _platform.isMacOS;
@override
bool get canListAnything => globals.iosWorkflow.canListDevices;
bool get canListAnything => _iosWorkflow.canListDevices;
@override
Future<List<Device>> pollingGetDevices({ Duration timeout }) {
return IOSDevice.getAttachedDevices(
globals.platform, globals.xcdevice, timeout: timeout);
Future<List<Device>> pollingGetDevices({ Duration timeout }) async {
if (!_platform.isMacOS) {
throw UnsupportedError(
'Control of iOS devices or simulators only supported on macOS.'
);
}
return await _xcdevice.getAvailableTetheredIOSDevices(timeout: timeout);
}
@override
Future<List<String>> getDiagnostics() => IOSDevice.getDiagnostics(globals.platform, globals.xcdevice);
Future<List<String>> getDiagnostics() async {
if (!_platform.isMacOS) {
return const <String>[
'Control of iOS devices or simulators only supported on macOS.'
];
}
return await _xcdevice.getDiagnostics();
}
}
class IOSDevice extends Device {
......@@ -57,10 +83,13 @@ class IOSDevice extends Device {
@required Platform platform,
@required Artifacts artifacts,
@required IOSDeploy iosDeploy,
@required Logger logger,
})
: _sdkVersion = sdkVersion,
_iosDeploy = iosDeploy,
_fileSystem = fileSystem,
_logger = logger,
_platform = platform,
super(
id,
category: Category.mobile,
......@@ -82,6 +111,8 @@ class IOSDevice extends Device {
final String _sdkVersion;
final IOSDeploy _iosDeploy;
final FileSystem _fileSystem;
final Logger _logger;
final Platform _platform;
/// May be 0 if version cannot be parsed.
int get majorSdkVersion {
......@@ -113,22 +144,6 @@ class IOSDevice extends Device {
@override
bool get supportsStartPaused => false;
static Future<List<IOSDevice>> getAttachedDevices(Platform platform, XCDevice xcdevice, { Duration timeout }) async {
if (!platform.isMacOS) {
throw UnsupportedError('Control of iOS devices or simulators only supported on macOS.');
}
return await xcdevice.getAvailableTetheredIOSDevices(timeout: timeout);
}
static Future<List<String>> getDiagnostics(Platform platform, XCDevice xcdevice) async {
if (!platform.isMacOS) {
return const <String>['Control of iOS devices or simulators only supported on macOS.'];
}
return await xcdevice.getDiagnostics();
}
@override
Future<bool> isAppInstalled(IOSApp app) async {
bool result;
......@@ -138,7 +153,7 @@ class IOSDevice extends Device {
deviceId: id,
);
} on ProcessException catch (e) {
globals.printError(e.message);
_logger.printError(e.message);
return false;
}
return result;
......@@ -151,7 +166,7 @@ class IOSDevice extends Device {
Future<bool> installApp(IOSApp app) async {
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"?');
_logger.printError('Could not find application bundle at ${bundle.path}; have you run "flutter build ios"?');
return false;
}
......@@ -163,14 +178,14 @@ class IOSDevice extends Device {
launchArguments: <String>[],
);
} on ProcessException catch (e) {
globals.printError(e.message);
_logger.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('');
_logger.printError('Could not install ${bundle.path} on $id.');
_logger.printError('Try launching Xcode and selecting "Product > Run" to fix the problem:');
_logger.printError(' open ios/Runner.xcworkspace');
_logger.printError('');
return false;
}
return true;
......@@ -185,11 +200,11 @@ class IOSDevice extends Device {
bundleId: app.id,
);
} on ProcessException catch (e) {
globals.printError(e.message);
_logger.printError(e.message);
return false;
}
if (uninstallationResult != 0) {
globals.printError('Could not uninstall ${app.id} on $id.');
_logger.printError('Could not uninstall ${app.id} on $id.');
return false;
}
return true;
......@@ -212,7 +227,7 @@ class IOSDevice extends Device {
if (!prebuiltApplication) {
// TODO(chinmaygarde): Use mainPath, route.
globals.printTrace('Building ${package.name} for $id');
_logger.printTrace('Building ${package.name} for $id');
// Step 1: Build the precompiled/DBC application if necessary.
final XcodeBuildResult buildResult = await buildXcodeProject(
......@@ -223,9 +238,9 @@ class IOSDevice extends Device {
activeArch: cpuArchitecture,
);
if (!buildResult.success) {
globals.printError('Could not build the precompiled application for the device.');
_logger.printError('Could not build the precompiled application for the device.');
await diagnoseXcodeBuildFailure(buildResult);
globals.printError('');
_logger.printError('');
return LaunchResult.failed();
}
packageId = buildResult.xcodeBuildExecution?.buildSettings['PRODUCT_BUNDLE_IDENTIFIER'];
......@@ -240,7 +255,7 @@ class IOSDevice extends Device {
// Step 2: Check that the application exists at the specified path.
final Directory bundle = _fileSystem.directory(package.deviceBundlePath);
if (!bundle.existsSync()) {
globals.printError('Could not find the built application bundle at ${bundle.path}.');
_logger.printError('Could not find the built application bundle at ${bundle.path}.');
return LaunchResult.failed();
}
......@@ -267,7 +282,7 @@ class IOSDevice extends Device {
// "system_debug_ios" integration test in the CI, which simulates a
// home-screen launch.
if (debuggingOptions.debuggingEnabled &&
globals.platform.environment['FLUTTER_TOOLS_DEBUG_WITHOUT_CHECKED_MODE'] != 'true') ...<String>[
_platform.environment['FLUTTER_TOOLS_DEBUG_WITHOUT_CHECKED_MODE'] != 'true') ...<String>[
'--enable-checked-mode',
'--verify-entry-points',
],
......@@ -282,13 +297,13 @@ class IOSDevice extends Device {
if (platformArgs['trace-startup'] as bool ?? false) '--trace-startup',
];
final Status installStatus = globals.logger.startProgress(
final Status installStatus = _logger.startProgress(
'Installing and launching...',
timeout: timeoutConfiguration.slowOperation);
try {
ProtocolDiscovery observatoryDiscovery;
if (debuggingOptions.debuggingEnabled) {
globals.printTrace('Debugging is enabled, connecting to observatory');
_logger.printTrace('Debugging is enabled, connecting to observatory');
observatoryDiscovery = ProtocolDiscovery.observatory(
getLogReader(app: package),
portForwarder: portForwarder,
......@@ -303,10 +318,10 @@ class IOSDevice extends Device {
launchArguments: launchArguments,
);
if (installationResult != 0) {
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('');
_logger.printError('Could not run ${bundle.path} on $id.');
_logger.printError('Try launching Xcode and selecting "Product > Run" to fix the problem:');
_logger.printError(' open ios/Runner.xcworkspace');
_logger.printError('');
return LaunchResult.failed();
}
......@@ -314,9 +329,9 @@ class IOSDevice extends Device {
return LaunchResult.succeeded();
}
globals.printTrace('Application launched on the device. Waiting for observatory port.');
_logger.printTrace('Application launched on the device. Waiting for observatory port.');
final FallbackDiscovery fallbackDiscovery = FallbackDiscovery(
logger: globals.logger,
logger: _logger,
mDnsObservatoryDiscovery: MDnsObservatoryDiscovery.instance,
portForwarder: portForwarder,
protocolDiscovery: observatoryDiscovery,
......@@ -334,7 +349,7 @@ class IOSDevice extends Device {
}
return LaunchResult.succeeded(observatoryUri: localUri);
} on ProcessException catch (e) {
globals.printError(e.message);
_logger.printError(e.message);
return LaunchResult.failed();
} finally {
installStatus.stop();
......
......@@ -2,13 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../base/context.dart';
import '../doctor.dart';
import '../globals.dart' as globals;
// TODO(fujino): remove once internal references replaced by `globals.iosWorkflow`
IOSWorkflow get iosWorkflow => context.get<IOSWorkflow>();
class IOSWorkflow implements Workflow {
const IOSWorkflow();
......
......@@ -354,6 +354,7 @@ class XCDevice {
sdkVersion: _sdkVersion(deviceProperties),
artifacts: globals.artifacts,
fileSystem: globals.fs,
logger: globals.logger,
iosDeploy: globals.iosDeploy,
platform: globals.platform,
));
......
......@@ -21,6 +21,7 @@ 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';
import 'package:flutter_tools/src/project.dart';
......@@ -74,6 +75,7 @@ void main() {
'device-123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
logger: logger,
platform: macPlatform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
......@@ -87,6 +89,7 @@ void main() {
'device-123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
logger: logger,
platform: macPlatform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
......@@ -97,6 +100,7 @@ void main() {
'device-123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
logger: logger,
platform: macPlatform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
......@@ -107,6 +111,7 @@ void main() {
'device-123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
logger: logger,
platform: macPlatform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
......@@ -117,6 +122,7 @@ void main() {
'device-123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
logger: logger,
platform: macPlatform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
......@@ -127,6 +133,7 @@ void main() {
'device-123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
logger: logger,
platform: macPlatform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
......@@ -143,6 +150,7 @@ void main() {
'device-123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
logger: logger,
platform: platform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
......@@ -157,46 +165,57 @@ void main() {
group('ios-deploy wrappers', () {
const String appId = '789';
const String deviceId = 'device-123';
IOSDevice device;
IOSDeploy iosDeploy;
FullMockProcessManager mockProcessManager;
FakeProcessManager fakeProcessManager;
const String iosDeployPath = '/path/to/ios-deploy';
setUp(() {
mockProcessManager = FullMockProcessManager();
when(mockArtifacts.getArtifactPath(Artifact.iosDeploy, platform: TargetPlatform.ios))
.thenReturn(iosDeployPath);
});
testWithoutContext('isAppInstalled() catches ProcessException from ios-deploy', () async {
final MockIOSApp mockApp = MockIOSApp();
when(mockApp.id).thenReturn(appId);
fakeProcessManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: const <String>[
iosDeployPath,
'--id',
deviceId,
'--exists',
'--bundle_id',
appId,
],
onRun: () => throw const ProcessException('ios-deploy', <String>[]),
)
]);
iosDeploy = IOSDeploy(
artifacts: mockArtifacts,
cache: mockCache,
logger: logger,
platform: macPlatform,
processManager: mockProcessManager,
processManager: fakeProcessManager,
);
device = IOSDevice(
'device-123',
deviceId,
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
logger: logger,
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 {
testWithoutContext('installApp() catches ProcessException from ios-deploy', () async {
const String bundlePath = '/path/to/bundle';
final MockIOSApp mockApp = MockIOSApp();
when(mockApp.id).thenReturn(appId);
......@@ -204,24 +223,77 @@ void main() {
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>[]));
when(mockDirectory.path).thenReturn(bundlePath);
fakeProcessManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: const <String>[
iosDeployPath,
'--id',
deviceId,
'--bundle',
bundlePath,
'--no-wifi',
],
onRun: () => throw const ProcessException('ios-deploy', <String>[]),
)
]);
iosDeploy = IOSDeploy(
artifacts: mockArtifacts,
cache: mockCache,
logger: logger,
platform: macPlatform,
processManager: fakeProcessManager,
);
device = IOSDevice(
deviceId,
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
logger: logger,
platform: macPlatform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
);
final bool result = await device.installApp(mockApp);
expect(result, false);
});
testUsingContext('uninstallApp() catches ProcessException from ios-deploy', () async {
testWithoutContext('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>[]));
fakeProcessManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: const <String>[
iosDeployPath,
'--id',
deviceId,
'--uninstall_only',
'--bundle_id',
appId,
],
onRun: () => throw const ProcessException('ios-deploy', <String>[]),
)
]);
iosDeploy = IOSDeploy(
artifacts: mockArtifacts,
cache: mockCache,
logger: logger,
platform: macPlatform,
processManager: fakeProcessManager,
);
device = IOSDevice(
deviceId,
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
logger: logger,
platform: macPlatform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
);
final bool result = await device.uninstallApp(mockApp);
expect(result, false);
......@@ -294,6 +366,7 @@ void main() {
'123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
logger: logger,
platform: macPlatform,
iosDeploy: iosDeploy,
name: 'iPhone 1',
......@@ -420,13 +493,14 @@ void main() {
Cache.enableLocking();
});
testUsingContext('disposing device disposes the portForwarder', () async {
testWithoutContext('disposing device disposes the portForwarder', () async {
final IOSDevice device = IOSDevice(
'123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
platform: macPlatform,
iosDeploy: iosDeploy,
logger: logger,
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
......@@ -444,6 +518,7 @@ void main() {
sdkVersion: '13.3',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
logger: logger,
platform: macPlatform,
iosDeploy: mockIosDeploy,
cpuArchitecture: DarwinArch.arm64,
......@@ -479,12 +554,7 @@ void main() {
expect(launchResult.hasObservatory, isTrue);
expect(await device.stopApp(mockApp), isFalse);
}, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts,
Cache: () => mockCache,
FileSystem: () => mockFileSystem,
MDnsObservatoryDiscovery: () => mockMDnsObservatoryDiscovery,
Platform: () => macPlatform,
ProcessManager: () => mockProcessManager,
Usage: () => mockUsage,
});
......@@ -493,6 +563,7 @@ void main() {
'123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
logger: logger,
platform: macPlatform,
iosDeploy: mockIosDeploy,
name: 'iPhone 1',
......@@ -528,11 +599,8 @@ void main() {
verify(mockUsage.sendEvent('ios-handshake', 'fallback-success')).called(1);
expect(await device.stopApp(mockApp), isFalse);
}, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts,
Cache: () => mockCache,
FileSystem: () => mockFileSystem,
MDnsObservatoryDiscovery: () => mockMDnsObservatoryDiscovery,
Platform: () => macPlatform,
ProcessManager: () => mockProcessManager,
Usage: () => mockUsage,
});
......@@ -542,6 +610,7 @@ void main() {
'123',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
logger: logger,
platform: macPlatform,
iosDeploy: mockIosDeploy,
name: 'iPhone 1',
......@@ -585,11 +654,8 @@ void main() {
verify(mockUsage.sendEvent('ios-handshake', 'mdns-failure')).called(1);
verify(mockUsage.sendEvent('ios-handshake', 'fallback-failure')).called(1);
}, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts,
Cache: () => mockCache,
FileSystem: () => mockFileSystem,
MDnsObservatoryDiscovery: () => mockMDnsObservatoryDiscovery,
Platform: () => macPlatform,
ProcessManager: () => mockProcessManager,
Usage: () => mockUsage,
});
......@@ -601,6 +667,7 @@ void main() {
fileSystem: mockFileSystem,
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
logger: logger,
platform: mockPlatform,
artifacts: mockArtifacts,
iosDeploy: mockIosDeploy,
......@@ -642,6 +709,7 @@ void main() {
fileSystem: mockFileSystem,
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
logger: logger,
platform: mockPlatform,
artifacts: mockArtifacts,
iosDeploy: mockIosDeploy,
......@@ -679,6 +747,7 @@ void main() {
sdkVersion: '13.3',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
logger: logger,
platform: macPlatform,
iosDeploy: mockIosDeploy,
cpuArchitecture: DarwinArch.arm64,
......@@ -737,6 +806,7 @@ void main() {
sdkVersion: '13.3',
artifacts: mockArtifacts,
fileSystem: mockFileSystem,
logger: logger,
platform: macPlatform,
iosDeploy: mockIosDeploy,
cpuArchitecture: DarwinArch.arm64,
......@@ -778,14 +848,10 @@ void main() {
expect(args, contains('--observatory-port=8181'));
expect(await device.stopApp(mockApp), isFalse);
}, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts,
Cache: () => mockCache,
FileSystem: () => mockFileSystem,
MDnsObservatoryDiscovery: () => mockMDnsObservatoryDiscovery,
Platform: () => macPlatform,
ProcessManager: () => mockProcessManager,
Usage: () => mockUsage,
IOSDeploy: () => mockIosDeploy,
});
void testNonPrebuilt(
......@@ -865,6 +931,7 @@ void main() {
sdkVersion: '13.3',
artifacts: mockArtifacts,
fileSystem: globals.fs,
logger: testLogger,
platform: macPlatform,
iosDeploy: mockIosDeploy,
cpuArchitecture: DarwinArch.arm64,
......@@ -951,11 +1018,12 @@ void main() {
MockArtifacts mockArtifacts;
MockCache mockCache;
MockFileSystem mockFileSystem;
MockProcessManager mockProcessManager;
Logger logger;
MockPlatform mockPlatform;
FullMockProcessManager mockProcessManager;
const String iosDeployPath = '/path/to/ios-deploy';
const String appId = '789';
const String deviceId = '123';
const MapEntry<String, String> libraryEntry = MapEntry<String, String>(
'DYLD_LIBRARY_PATH',
'/path/to/libraries',
......@@ -979,13 +1047,13 @@ void main() {
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);
mockProcessManager = MockProcessManager();
iosDeploy = IOSDeploy(
artifacts: mockArtifacts,
cache: mockCache,
......@@ -1001,82 +1069,83 @@ void main() {
});
testWithoutContext('installApp() calls ios-deploy', () async {
final FakeProcessManager fakeProcessManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(command: <String>[
iosDeployPath,
'--id',
deviceId,
'--bundle',
bundlePath,
'--no-wifi',
]),
]);
iosDeploy = IOSDeploy(
artifacts: mockArtifacts,
cache: mockCache,
logger: logger,
platform: mockPlatform,
processManager: fakeProcessManager,
);
when(mockFileSystem.directory(bundlePath)).thenReturn(directory);
final IOSDevice device = IOSDevice(
'123',
deviceId,
name: 'iPhone 1',
fileSystem: mockFileSystem,
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
logger: logger,
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);
final List<String> invocationArguments = verify(mockProcessManager.start(
captureAny,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).captured.first as List<String>;
expect(invocationArguments, args);
});
testWithoutContext('uninstallApp() calls ios-deploy', () async {
final FakeProcessManager fakeProcessManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(command: <String>[
iosDeployPath,
'--id',
deviceId,
'--uninstall_only',
'--bundle_id',
appId,
]),
]);
iosDeploy = IOSDeploy(
artifacts: mockArtifacts,
cache: mockCache,
logger: logger,
platform: mockPlatform,
processManager: fakeProcessManager,
);
final IOSDevice device = IOSDevice(
'123',
deviceId,
name: 'iPhone 1',
fileSystem: fs,
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
logger: logger,
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);
final List<String> invocationArguments = verify(mockProcessManager.start(
captureAny,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).captured.first as List<String>;
expect(invocationArguments, args);
});
});
});
group('getAttachedDevices', () {
group('pollingGetDevices', () {
MockXcdevice mockXcdevice;
MockArtifacts mockArtifacts;
MockCache mockCache;
MockFileSystem mockFileSystem;
FakeProcessManager fakeProcessManager;
Logger logger;
FullMockProcessManager mockProcessManager;
IOSDeploy iosDeploy;
IOSWorkflow mockIosWorkflow;
setUp(() {
mockXcdevice = MockXcdevice();
......@@ -1084,28 +1153,39 @@ void main() {
mockCache = MockCache();
logger = BufferLogger.test();
mockFileSystem = MockFileSystem();
mockProcessManager = FullMockProcessManager();
mockIosWorkflow = MockIOSWorkflow();
fakeProcessManager = FakeProcessManager.any();
iosDeploy = IOSDeploy(
artifacts: mockArtifacts,
cache: mockCache,
logger: logger,
platform: macPlatform,
processManager: mockProcessManager,
processManager: fakeProcessManager,
);
});
final List<Platform> unsupportedPlatforms = <Platform>[linuxPlatform, windowsPlatform];
for (final Platform unsupportedPlatform in unsupportedPlatforms) {
testWithoutContext('throws Unsupported Operation exception on ${unsupportedPlatform.operatingSystem}', () async {
final IOSDevices iosDevices = IOSDevices(
platform: unsupportedPlatform,
xcdevice: mockXcdevice,
iosWorkflow: mockIosWorkflow,
);
when(mockXcdevice.isInstalled).thenReturn(false);
expect(
() async { await IOSDevice.getAttachedDevices(unsupportedPlatform, mockXcdevice); },
() async { await iosDevices.pollingGetDevices(); },
throwsA(isA<UnsupportedError>()),
);
});
}
testWithoutContext('returns attached devices', () async {
final IOSDevices iosDevices = IOSDevices(
platform: macPlatform,
xcdevice: mockXcdevice,
iosWorkflow: mockIosWorkflow,
);
when(mockXcdevice.isInstalled).thenReturn(true);
final IOSDevice device = IOSDevice(
......@@ -1115,13 +1195,14 @@ void main() {
cpuArchitecture: DarwinArch.arm64,
artifacts: mockArtifacts,
iosDeploy: iosDeploy,
logger: logger,
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);
final List<Device> devices = await iosDevices.pollingGetDevices();
expect(devices, hasLength(1));
expect(identical(devices.first, device), isTrue);
});
......@@ -1129,39 +1210,49 @@ void main() {
group('getDiagnostics', () {
MockXcdevice mockXcdevice;
IOSWorkflow mockIosWorkflow;
setUp(() {
mockXcdevice = MockXcdevice();
mockIosWorkflow = MockIOSWorkflow();
});
final List<Platform> unsupportedPlatforms = <Platform>[linuxPlatform, windowsPlatform];
for (final Platform unsupportedPlatform in unsupportedPlatforms) {
testWithoutContext('throws returns platform diagnostic exception on ${unsupportedPlatform.operatingSystem}', () async {
final IOSDevices iosDevices = IOSDevices(
platform: unsupportedPlatform,
xcdevice: mockXcdevice,
iosWorkflow: mockIosWorkflow,
);
when(mockXcdevice.isInstalled).thenReturn(false);
expect((await IOSDevice.getDiagnostics(unsupportedPlatform, mockXcdevice)).first, 'Control of iOS devices or simulators only supported on macOS.');
expect((await iosDevices.getDiagnostics()).first, 'Control of iOS devices or simulators only supported on macOS.');
});
}
testUsingContext('returns diagnostics', () async {
testWithoutContext('returns diagnostics', () async {
final IOSDevices iosDevices = IOSDevices(
platform: macPlatform,
xcdevice: mockXcdevice,
iosWorkflow: mockIosWorkflow,
);
when(mockXcdevice.isInstalled).thenReturn(true);
when(mockXcdevice.getDiagnostics())
.thenAnswer((Invocation invocation) => Future<List<String>>.value(<String>['Generic pairing error']));
final List<String> diagnostics = await IOSDevice.getDiagnostics(macPlatform, mockXcdevice);
final List<String> diagnostics = await iosDevices.getDiagnostics();
expect(diagnostics, hasLength(1));
expect(diagnostics.first, 'Generic pairing error');
}, overrides: <Type, Generator>{
Platform: () => macPlatform,
});
});
group('decodeSyslog', () {
test('decodes a syslog-encoded line', () {
testWithoutContext('decodes a syslog-encoded line', () {
final String decoded = decodeSyslog(r'I \M-b\M^]\M-$\M-o\M-8\M^O syslog \M-B\M-/\134_(\M-c\M^C\M^D)_/\M-B\M-/ \M-l\M^F\240!');
expect(decoded, r'I ❤️ syslog ¯\_(ツ)_/¯ 솠!');
});
test('passes through un-decodeable lines as-is', () {
testWithoutContext('passes through un-decodeable lines as-is', () {
final String decoded = decodeSyslog(r'I \M-b\M^O syslog!');
expect(decoded, r'I \M-b\M^O syslog!');
});
......@@ -1173,8 +1264,8 @@ void main() {
MockArtifacts mockArtifacts;
MockCache mockCache;
MockFileSystem mockFileSystem;
FakeProcessManager fakeProcessManager;
Logger logger;
FullMockProcessManager mockProcessManager;
IOSDeploy iosDeploy;
setUp(() {
......@@ -1184,13 +1275,13 @@ void main() {
mockCache = MockCache();
logger = BufferLogger.test();
mockFileSystem = MockFileSystem();
mockProcessManager = FullMockProcessManager();
fakeProcessManager = FakeProcessManager.any();
iosDeploy = IOSDeploy(
artifacts: mockArtifacts,
cache: mockCache,
logger: logger,
platform: macPlatform,
processManager: mockProcessManager,
processManager: fakeProcessManager,
);
});
......@@ -1215,6 +1306,7 @@ Runner(UIKit)[297] <Notice>: E is for enpitsu"
cpuArchitecture: DarwinArch.arm64,
artifacts: mockArtifacts,
iosDeploy: iosDeploy,
logger: logger,
platform: macPlatform,
fileSystem: mockFileSystem,
);
......@@ -1226,7 +1318,6 @@ Runner(UIKit)[297] <Notice>: E is for enpitsu"
expect(lines, <String>['A is for ari', 'I is for ichigo']);
}, overrides: <Type, Generator>{
IMobileDevice: () => mockIMobileDevice,
Platform: () => macPlatform,
});
testUsingContext('includes multi-line Flutter logs in the output', () async {
......@@ -1250,6 +1341,7 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
cpuArchitecture: DarwinArch.arm64,
artifacts: mockArtifacts,
iosDeploy: iosDeploy,
logger: logger,
platform: macPlatform,
fileSystem: mockFileSystem,
);
......@@ -1267,7 +1359,6 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
expect(device.category, Category.mobile);
}, overrides: <Type, Generator>{
IMobileDevice: () => mockIMobileDevice,
Platform: () => macPlatform,
});
});
......@@ -1276,21 +1367,25 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
MockCache mockCache;
Logger logger;
IOSDeploy iosDeploy;
MemoryFileSystem fileSystem;
FakeProcessManager fakeProcessManager;
setUp(() {
fileSystem = MemoryFileSystem();
mockArtifacts = MockArtifacts();
mockCache = MockCache();
fakeProcessManager = FakeProcessManager.any();
iosDeploy = IOSDeploy(
artifacts: mockArtifacts,
cache: mockCache,
logger: logger,
platform: macPlatform,
processManager: FakeProcessManager.any(),
processManager: fakeProcessManager,
);
});
testUsingContext('is true on module project', () async {
globals.fs.file('pubspec.yaml')
fileSystem.file('pubspec.yaml')
..createSync()
..writeAsStringSync(r'''
name: example
......@@ -1298,59 +1393,60 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
flutter:
module: {}
''');
globals.fs.file('.packages').createSync();
fileSystem.file('.packages').createSync();
final FlutterProject flutterProject = FlutterProject.current();
final IOSDevice device = IOSDevice(
'test',
artifacts: mockArtifacts,
fileSystem: globals.fs,
fileSystem: fileSystem,
iosDeploy: iosDeploy,
platform: globals.platform,
logger: logger,
platform: macPlatform,
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64
);
expect(device.isSupportedForProject(flutterProject), true);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => macPlatform,
FileSystem: () => fileSystem,
ProcessManager: () => fakeProcessManager,
});
testUsingContext('is true with editable host app', () async {
globals.fs.file('pubspec.yaml').createSync();
globals.fs.file('.packages').createSync();
globals.fs.directory('ios').createSync();
fileSystem.file('pubspec.yaml').createSync();
fileSystem.file('.packages').createSync();
fileSystem.directory('ios').createSync();
final FlutterProject flutterProject = FlutterProject.current();
final IOSDevice device = IOSDevice(
'test',
artifacts: mockArtifacts,
fileSystem: globals.fs,
fileSystem: fileSystem,
iosDeploy: iosDeploy,
platform: globals.platform,
logger: logger,
platform: macPlatform,
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
);
expect(device.isSupportedForProject(flutterProject), true);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => macPlatform,
FileSystem: () => fileSystem,
ProcessManager: () => fakeProcessManager,
});
testUsingContext('is false with no host app and no module', () async {
globals.fs.file('pubspec.yaml').createSync();
globals.fs.file('.packages').createSync();
fileSystem.file('pubspec.yaml').createSync();
fileSystem.file('.packages').createSync();
final FlutterProject flutterProject = FlutterProject.current();
final IOSDevice device = IOSDevice(
'test',
artifacts: mockArtifacts,
fileSystem: globals.fs,
fileSystem: fileSystem,
iosDeploy: iosDeploy,
platform: globals.platform,
logger: logger,
platform: macPlatform,
name: 'iPhone 1',
sdkVersion: '13.3',
cpuArchitecture: DarwinArch.arm64,
......@@ -1358,8 +1454,7 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
expect(device.isSupportedForProject(flutterProject), false);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
Platform: () => macPlatform,
ProcessManager: () => fakeProcessManager,
});
});
}
......@@ -1397,6 +1492,7 @@ class FakeIosDoctorProvider implements DoctorValidatorsProvider {
}
}
class MockIOSApp extends Mock implements IOSApp {}
class MockApplicationPackage extends Mock implements ApplicationPackage {}
class MockArtifacts extends Mock implements Artifacts {}
......@@ -1408,12 +1504,12 @@ 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 MockIOSWorkflow extends Mock implements IOSWorkflow {}
class MockMDnsObservatoryDiscovery extends Mock implements MDnsObservatoryDiscovery {}
class MockMDnsObservatoryDiscoveryResult extends Mock implements MDnsObservatoryDiscoveryResult {}
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 MockStatus extends Mock implements Status {}
class MockUsage extends Mock implements Usage {}
class MockXcdevice extends Mock implements XCDevice {}
class MockXcode extends Mock implements Xcode {}
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