Throw exception if instantiating IOSDevice on non-mac os platform (#36288)
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index 4ed40de..904252c 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -125,14 +125,12 @@
ephemeral: true,
) {
if (!platform.isMacOS) {
- printError('Cannot control iOS devices or simulators. ideviceinstaller and iproxy are not available on your platform.');
- _installerPath = null;
- _iproxyPath = null;
+ assert(false, 'Control of iOS devices or simulators only supported on Mac OS.');
return;
}
_installerPath = artifacts.getArtifactPath(
Artifact.ideviceinstaller,
- platform: TargetPlatform.ios
+ platform: TargetPlatform.ios,
) ?? 'ideviceinstaller'; // TODO(fujino): remove fallback once g3 updated
_iproxyPath = artifacts.getArtifactPath(
Artifact.iproxy,
@@ -168,6 +166,9 @@
bool get supportsStartPaused => false;
static Future<List<IOSDevice>> getAttachedDevices() async {
+ if (!platform.isMacOS) {
+ throw UnsupportedError('Control of iOS devices or simulators only supported on Mac OS.');
+ }
if (!iMobileDevice.isInstalled)
return <IOSDevice>[];
diff --git a/packages/flutter_tools/test/general.shard/ios/devices_test.dart b/packages/flutter_tools/test/general.shard/ios/devices_test.dart
index f01894e..89075c6 100644
--- a/packages/flutter_tools/test/general.shard/ios/devices_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/devices_test.dart
@@ -38,92 +38,117 @@
void main() {
final FakePlatform macPlatform = FakePlatform.fromPlatform(const LocalPlatform());
macPlatform.operatingSystem = 'macos';
+ final FakePlatform linuxPlatform = FakePlatform.fromPlatform(const LocalPlatform());
+ linuxPlatform.operatingSystem = 'linux';
+ final FakePlatform windowsPlatform = FakePlatform.fromPlatform(const LocalPlatform());
+ windowsPlatform.operatingSystem = 'windows';
- group('Process calls', () {
- MockIOSApp mockApp;
- MockArtifacts mockArtifacts;
- MockCache mockCache;
- MockFileSystem mockFileSystem;
- MockProcessManager mockProcessManager;
- const String installerPath = '/path/to/ideviceinstaller';
- 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]
- );
+ group('IOSDevice', () {
+ final List<Platform> unsupportedPlatforms = <Platform>[linuxPlatform, windowsPlatform];
- setUp(() {
- mockApp = MockIOSApp();
- mockArtifacts = MockArtifacts();
- mockCache = MockCache();
- when(mockCache.dyLdLibEntry).thenReturn(libraryEntry);
- mockFileSystem = MockFileSystem();
- mockProcessManager = MockProcessManager();
- when(
- mockArtifacts.getArtifactPath(
- Artifact.ideviceinstaller,
- platform: anyNamed('platform'),
- )
- ).thenReturn(installerPath);
+ testUsingContext('successfully instantiates on Mac OS', () {
+ IOSDevice('device-123');
+ }, overrides: <Type, Generator>{
+ Platform: () => macPlatform,
});
- testUsingContext('installApp() invokes process with correct environment', () async {
- final IOSDevice device = IOSDevice('123');
- 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(directory.existsSync()).thenReturn(true);
- when(mockProcessManager.run(args, environment: env))
- .thenAnswer(
- (_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', ''))
+ for (Platform platform in unsupportedPlatforms) {
+ testUsingContext('throws UnsupportedError exception if instantiated on ${platform.operatingSystem}', () {
+ expect(
+ () { IOSDevice('device-123'); },
+ throwsA(isInstanceOf<AssertionError>())
);
- await device.installApp(mockApp);
- verify(mockProcessManager.run(args, environment: env));
- }, overrides: <Type, Generator>{
- Artifacts: () => mockArtifacts,
- Cache: () => mockCache,
- FileSystem: () => mockFileSystem,
- Platform: () => macPlatform,
- ProcessManager: () => mockProcessManager,
- });
+ }, overrides: <Type, Generator>{
+ Platform: () => platform,
+ });
+ }
- testUsingContext('isAppInstalled() invokes process with correct environment', () async {
- final IOSDevice device = IOSDevice('123');
- 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,
- });
+ group('Process calls', () {
+ MockIOSApp mockApp;
+ MockArtifacts mockArtifacts;
+ MockCache mockCache;
+ MockFileSystem mockFileSystem;
+ MockProcessManager mockProcessManager;
+ const String installerPath = '/path/to/ideviceinstaller';
+ 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]
+ );
- testUsingContext('uninstallApp() invokes process with correct environment', () async {
- final IOSDevice device = IOSDevice('123');
- 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, '', ''))
- );
- await device.uninstallApp(mockApp);
- verify(mockProcessManager.run(args, environment: env));
- }, overrides: <Type, Generator>{
- Artifacts: () => mockArtifacts,
- Cache: () => mockCache,
- Platform: () => macPlatform,
- ProcessManager: () => mockProcessManager,
+ setUp(() {
+ mockApp = MockIOSApp();
+ mockArtifacts = MockArtifacts();
+ mockCache = MockCache();
+ when(mockCache.dyLdLibEntry).thenReturn(libraryEntry);
+ mockFileSystem = MockFileSystem();
+ 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');
+ 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(directory.existsSync()).thenReturn(true);
+ when(mockProcessManager.run(args, environment: env))
+ .thenAnswer(
+ (_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', ''))
+ );
+ 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');
+ 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,
+ });
+
+ testUsingContext('uninstallApp() invokes process with correct environment', () async {
+ final IOSDevice device = IOSDevice('123');
+ 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, '', ''))
+ );
+ await device.uninstallApp(mockApp);
+ verify(mockProcessManager.run(args, environment: env));
+ }, overrides: <Type, Generator>{
+ Artifacts: () => mockArtifacts,
+ Cache: () => mockCache,
+ Platform: () => macPlatform,
+ ProcessManager: () => mockProcessManager,
+ });
});
});
@@ -139,6 +164,7 @@
expect(await IOSDevice.getAttachedDevices(), isEmpty);
}, overrides: <Type, Generator>{
IMobileDevice: () => mockIMobileDevice,
+ Platform: () => macPlatform,
});
testUsingContext('returns no devices if none are attached', () async {
@@ -149,8 +175,25 @@
expect(devices, isEmpty);
}, overrides: <Type, Generator>{
IMobileDevice: () => mockIMobileDevice,
+ Platform: () => macPlatform,
});
+ final List<Platform> unsupportedPlatforms = <Platform>[linuxPlatform, windowsPlatform];
+ for (Platform platform in unsupportedPlatforms) {
+ testUsingContext('throws Unsupported Operation exception on ${platform.operatingSystem}', () async {
+ when(iMobileDevice.isInstalled).thenReturn(false);
+ when(iMobileDevice.getAvailableDeviceIDs())
+ .thenAnswer((Invocation invocation) => Future<String>.value(''));
+ expect(
+ () async { await IOSDevice.getAttachedDevices(); },
+ throwsA(isInstanceOf<UnsupportedError>()),
+ );
+ }, overrides: <Type, Generator>{
+ IMobileDevice: () => mockIMobileDevice,
+ Platform: () => platform,
+ });
+ }
+
testUsingContext('returns attached devices', () async {
when(iMobileDevice.isInstalled).thenReturn(true);
when(iMobileDevice.getAvailableDeviceIDs())
@@ -174,6 +217,7 @@
expect(devices[1].name, 'Puits sans fond');
}, overrides: <Type, Generator>{
IMobileDevice: () => mockIMobileDevice,
+ Platform: () => macPlatform,
});
testUsingContext('returns attached devices and ignores devices that cannot be found by ideviceinfo', () async {
@@ -193,6 +237,7 @@
expect(devices[0].name, 'La tele me regarde');
}, overrides: <Type, Generator>{
IMobileDevice: () => mockIMobileDevice,
+ Platform: () => macPlatform,
});
});
@@ -244,8 +289,8 @@
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 {
when(mockIMobileDevice.startLogger('123456')).thenAnswer((Invocation invocation) {
final Process mockProcess = MockProcess();
@@ -280,9 +325,9 @@
expect(device.category, Category.mobile);
}, overrides: <Type, Generator>{
IMobileDevice: () => mockIMobileDevice,
+ Platform: () => macPlatform,
});
});
-
testUsingContext('IOSDevice.isSupportedForProject is true on module project', () async {
fs.file('pubspec.yaml')
..createSync()
@@ -298,8 +343,8 @@
expect(IOSDevice('test').isSupportedForProject(flutterProject), true);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
+ Platform: () => macPlatform,
});
-
testUsingContext('IOSDevice.isSupportedForProject is true with editable host app', () async {
fs.file('pubspec.yaml').createSync();
fs.file('.packages').createSync();
@@ -309,6 +354,7 @@
expect(IOSDevice('test').isSupportedForProject(flutterProject), true);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
+ Platform: () => macPlatform,
});
testUsingContext('IOSDevice.isSupportedForProject is false with no host app and no module', () async {
@@ -319,5 +365,6 @@
expect(IOSDevice('test').isSupportedForProject(flutterProject), false);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
+ Platform: () => macPlatform,
});
}