| // 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 'package:file/memory.dart'; |
| import 'package:file_testing/file_testing.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/base/version.dart'; |
| import 'package:flutter_tools/src/ios/core_devices.dart'; |
| import 'package:flutter_tools/src/ios/xcodeproj.dart'; |
| import 'package:flutter_tools/src/macos/xcode.dart'; |
| |
| import '../../src/common.dart'; |
| import '../../src/fake_process_manager.dart'; |
| |
| void main() { |
| late MemoryFileSystem fileSystem; |
| |
| setUp(() { |
| fileSystem = MemoryFileSystem.test(); |
| }); |
| |
| group('Xcode prior to Core Device Control/Xcode 15', () { |
| late BufferLogger logger; |
| late FakeProcessManager fakeProcessManager; |
| late Xcode xcode; |
| late IOSCoreDeviceControl deviceControl; |
| |
| setUp(() { |
| logger = BufferLogger.test(); |
| fakeProcessManager = FakeProcessManager.empty(); |
| final XcodeProjectInterpreter xcodeProjectInterpreter = XcodeProjectInterpreter.test( |
| processManager: fakeProcessManager, |
| version: Version(14, 0, 0), |
| ); |
| xcode = Xcode.test( |
| processManager: fakeProcessManager, |
| xcodeProjectInterpreter: xcodeProjectInterpreter, |
| ); |
| deviceControl = IOSCoreDeviceControl( |
| logger: logger, |
| processManager: fakeProcessManager, |
| xcode: xcode, |
| fileSystem: fileSystem, |
| ); |
| }); |
| |
| group('devicectl is not installed', () { |
| testWithoutContext('fails to get device list', () async { |
| final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices(); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, contains('devicectl is not installed.')); |
| expect(devices.isEmpty, isTrue); |
| }); |
| |
| testWithoutContext('fails to install app', () async { |
| final bool status = await deviceControl.installApp(deviceId: 'device-id', bundlePath: '/path/to/bundle'); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, contains('devicectl is not installed.')); |
| expect(status, isFalse); |
| }); |
| |
| testWithoutContext('fails to launch app', () async { |
| final bool status = await deviceControl.launchApp(deviceId: 'device-id', bundleId: 'com.example.flutterApp'); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, contains('devicectl is not installed.')); |
| expect(status, isFalse); |
| }); |
| |
| testWithoutContext('fails to check if app is installed', () async { |
| final bool status = await deviceControl.isAppInstalled(deviceId: 'device-id', bundleId: 'com.example.flutterApp'); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, contains('devicectl is not installed.')); |
| expect(status, isFalse); |
| }); |
| }); |
| }); |
| |
| group('Core Device Control', () { |
| late BufferLogger logger; |
| late FakeProcessManager fakeProcessManager; |
| late Xcode xcode; |
| late IOSCoreDeviceControl deviceControl; |
| |
| setUp(() { |
| logger = BufferLogger.test(); |
| fakeProcessManager = FakeProcessManager.empty(); |
| // TODO(fujino): re-use fakeProcessManager |
| xcode = Xcode.test(processManager: FakeProcessManager.any()); |
| deviceControl = IOSCoreDeviceControl( |
| logger: logger, |
| processManager: fakeProcessManager, |
| xcode: xcode, |
| fileSystem: fileSystem, |
| ); |
| }); |
| |
| group('install app', () { |
| const String deviceId = 'device-id'; |
| const String bundlePath = '/path/to/com.example.flutterApp'; |
| |
| testWithoutContext('Successful install', () async { |
| const String deviceControlOutput = ''' |
| { |
| "info" : { |
| "arguments" : [ |
| "devicectl", |
| "device", |
| "install", |
| "app", |
| "--device", |
| "00001234-0001234A3C03401E", |
| "build/ios/iphoneos/Runner.app", |
| "--json-output", |
| "/var/folders/wq/randompath/T/flutter_tools.rand0/core_devices.rand0/install_results.json" |
| ], |
| "commandType" : "devicectl.device.install.app", |
| "environment" : { |
| |
| }, |
| "outcome" : "success", |
| "version" : "341" |
| }, |
| "result" : { |
| "deviceIdentifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", |
| "installedApplications" : [ |
| { |
| "bundleID" : "com.example.bundle", |
| "databaseSequenceNumber" : 1230, |
| "databaseUUID" : "1234A567-D890-1B23-BCF4-D5D67A8D901E", |
| "installationURL" : "file:///private/var/containers/Bundle/Application/12345E6A-7F89-0C12-345E-F6A7E890CFF1/Runner.app/", |
| "launchServicesIdentifier" : "unknown", |
| "options" : { |
| |
| } |
| } |
| ] |
| } |
| } |
| '''; |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('install_results.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'device', |
| 'install', |
| 'app', |
| '--device', |
| deviceId, |
| bundlePath, |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final bool status = await deviceControl.installApp( |
| deviceId: deviceId, |
| bundlePath: bundlePath, |
| ); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, isEmpty); |
| expect(tempFile, isNot(exists)); |
| expect(status, true); |
| }); |
| |
| testWithoutContext('devicectl fails install', () async { |
| const String deviceControlOutput = ''' |
| { |
| "error" : { |
| "code" : 1005, |
| "domain" : "com.apple.dt.CoreDeviceError", |
| "userInfo" : { |
| "NSLocalizedDescription" : { |
| "string" : "Could not obtain access to one or more requested file system resources because CoreDevice was unable to create bookmark data." |
| }, |
| "NSUnderlyingError" : { |
| "error" : { |
| "code" : 260, |
| "domain" : "NSCocoaErrorDomain", |
| "userInfo" : { |
| |
| } |
| } |
| } |
| } |
| }, |
| "info" : { |
| "arguments" : [ |
| "devicectl", |
| "device", |
| "install", |
| "app", |
| "--device", |
| "00001234-0001234A3C03401E", |
| "/path/to/app", |
| "--json-output", |
| "/var/folders/wq/randompath/T/flutter_tools.rand0/core_devices.rand0/install_results.json" |
| ], |
| "commandType" : "devicectl.device.install.app", |
| "environment" : { |
| "TERM" : "xterm-256color" |
| }, |
| "outcome" : "failed", |
| "version" : "341" |
| } |
| } |
| '''; |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('install_results.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'device', |
| 'install', |
| 'app', |
| '--device', |
| deviceId, |
| bundlePath, |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| exitCode: 1, |
| stderr: ''' |
| ERROR: Could not obtain access to one or more requested file system resources because CoreDevice was unable to create bookmark data. (com.apple.dt.CoreDeviceError error 1005.) |
| NSURL = file:///path/to/app |
| -------------------------------------------------------------------------------- |
| ERROR: The file couldn’t be opened because it doesn’t exist. (NSCocoaErrorDomain error 260.) |
| ''' |
| )); |
| |
| final bool status = await deviceControl.installApp( |
| deviceId: deviceId, |
| bundlePath: bundlePath, |
| ); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, contains('ERROR: Could not obtain access to one or more requested file system')); |
| expect(tempFile, isNot(exists)); |
| expect(status, false); |
| }); |
| |
| testWithoutContext('fails install because of unexpected JSON', () async { |
| const String deviceControlOutput = ''' |
| { |
| "valid_unexpected_json": true |
| } |
| '''; |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('install_results.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'device', |
| 'install', |
| 'app', |
| '--device', |
| deviceId, |
| bundlePath, |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final bool status = await deviceControl.installApp( |
| deviceId: deviceId, |
| bundlePath: bundlePath, |
| ); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, contains('devicectl returned unexpected JSON response')); |
| expect(tempFile, isNot(exists)); |
| expect(status, false); |
| }); |
| |
| testWithoutContext('fails install because of invalid JSON', () async { |
| const String deviceControlOutput = ''' |
| invalid JSON |
| '''; |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('install_results.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'device', |
| 'install', |
| 'app', |
| '--device', |
| deviceId, |
| bundlePath, |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final bool status = await deviceControl.installApp( |
| deviceId: deviceId, |
| bundlePath: bundlePath, |
| ); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, contains('devicectl returned non-JSON response')); |
| expect(tempFile, isNot(exists)); |
| expect(status, false); |
| }); |
| }); |
| |
| group('uninstall app', () { |
| const String deviceId = 'device-id'; |
| const String bundleId = 'com.example.flutterApp'; |
| |
| testWithoutContext('Successful uninstall', () async { |
| const String deviceControlOutput = ''' |
| { |
| "info" : { |
| "arguments" : [ |
| "devicectl", |
| "device", |
| "uninstall", |
| "app", |
| "--device", |
| "00001234-0001234A3C03401E", |
| "build/ios/iphoneos/Runner.app", |
| "--json-output", |
| "/var/folders/wq/randompath/T/flutter_tools.rand0/core_devices.rand0/uninstall_results.json" |
| ], |
| "commandType" : "devicectl.device.uninstall.app", |
| "environment" : { |
| |
| }, |
| "outcome" : "success", |
| "version" : "341" |
| }, |
| "result" : { |
| "deviceIdentifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", |
| "uninstalledApplications" : [ |
| { |
| "bundleID" : "com.example.bundle" |
| } |
| ] |
| } |
| } |
| '''; |
| |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('uninstall_results.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'device', |
| 'uninstall', |
| 'app', |
| '--device', |
| deviceId, |
| bundleId, |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final bool status = await deviceControl.uninstallApp( |
| deviceId: deviceId, |
| bundleId: bundleId, |
| ); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, isEmpty); |
| expect(tempFile, isNot(exists)); |
| expect(status, true); |
| }); |
| |
| testWithoutContext('devicectl fails uninstall', () async { |
| const String deviceControlOutput = ''' |
| { |
| "error" : { |
| "code" : 1005, |
| "domain" : "com.apple.dt.CoreDeviceError", |
| "userInfo" : { |
| "NSLocalizedDescription" : { |
| "string" : "Could not obtain access to one or more requested file system resources because CoreDevice was unable to create bookmark data." |
| }, |
| "NSUnderlyingError" : { |
| "error" : { |
| "code" : 260, |
| "domain" : "NSCocoaErrorDomain", |
| "userInfo" : { |
| |
| } |
| } |
| } |
| } |
| }, |
| "info" : { |
| "arguments" : [ |
| "devicectl", |
| "device", |
| "uninstall", |
| "app", |
| "--device", |
| "00001234-0001234A3C03401E", |
| "com.example.flutterApp", |
| "--json-output", |
| "/var/folders/wq/randompath/T/flutter_tools.rand0/core_devices.rand0/uninstall_results.json" |
| ], |
| "commandType" : "devicectl.device.uninstall.app", |
| "environment" : { |
| "TERM" : "xterm-256color" |
| }, |
| "outcome" : "failed", |
| "version" : "341" |
| } |
| } |
| '''; |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('uninstall_results.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'device', |
| 'uninstall', |
| 'app', |
| '--device', |
| deviceId, |
| bundleId, |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| exitCode: 1, |
| stderr: ''' |
| ERROR: Could not obtain access to one or more requested file system resources because CoreDevice was unable to create bookmark data. (com.apple.dt.CoreDeviceError error 1005.) |
| NSURL = file:///path/to/app |
| -------------------------------------------------------------------------------- |
| ERROR: The file couldn’t be opened because it doesn’t exist. (NSCocoaErrorDomain error 260.) |
| ''' |
| )); |
| |
| final bool status = await deviceControl.uninstallApp( |
| deviceId: deviceId, |
| bundleId: bundleId, |
| ); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, contains('ERROR: Could not obtain access to one or more requested file system')); |
| expect(tempFile, isNot(exists)); |
| expect(status, false); |
| }); |
| |
| testWithoutContext('fails uninstall because of unexpected JSON', () async { |
| const String deviceControlOutput = ''' |
| { |
| "valid_unexpected_json": true |
| } |
| '''; |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('uninstall_results.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'device', |
| 'uninstall', |
| 'app', |
| '--device', |
| deviceId, |
| bundleId, |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final bool status = await deviceControl.uninstallApp( |
| deviceId: deviceId, |
| bundleId: bundleId, |
| ); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, contains('devicectl returned unexpected JSON response')); |
| expect(tempFile, isNot(exists)); |
| expect(status, false); |
| }); |
| |
| testWithoutContext('fails uninstall because of invalid JSON', () async { |
| const String deviceControlOutput = ''' |
| invalid JSON |
| '''; |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('uninstall_results.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'device', |
| 'uninstall', |
| 'app', |
| '--device', |
| deviceId, |
| bundleId, |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final bool status = await deviceControl.uninstallApp( |
| deviceId: deviceId, |
| bundleId: bundleId, |
| ); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, contains('devicectl returned non-JSON response')); |
| expect(tempFile, isNot(exists)); |
| expect(status, false); |
| }); |
| }); |
| |
| group('launch app', () { |
| const String deviceId = 'device-id'; |
| const String bundleId = 'com.example.flutterApp'; |
| |
| testWithoutContext('Successful launch without launch args', () async { |
| const String deviceControlOutput = ''' |
| { |
| "info" : { |
| "arguments" : [ |
| "devicectl", |
| "device", |
| "process", |
| "launch", |
| "--device", |
| "00001234-0001234A3C03401E", |
| "com.example.flutterApp", |
| "--json-output", |
| "/var/folders/wq/randompath/T/flutter_tools.rand0/core_devices.rand0/install_results.json" |
| ], |
| "commandType" : "devicectl.device.process.launch", |
| "environment" : { |
| |
| }, |
| "outcome" : "success", |
| "version" : "341" |
| }, |
| "result" : { |
| "deviceIdentifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", |
| "launchOptions" : { |
| "activatedWhenStarted" : true, |
| "arguments" : [ |
| |
| ], |
| "environmentVariables" : { |
| "TERM" : "vt100" |
| }, |
| "platformSpecificOptions" : { |
| |
| }, |
| "startStopped" : false, |
| "terminateExistingInstances" : false, |
| "user" : { |
| "active" : true |
| } |
| }, |
| "process" : { |
| "auditToken" : [ |
| 12345, |
| 678 |
| ], |
| "executable" : "file:///private/var/containers/Bundle/Application/12345E6A-7F89-0C12-345E-F6A7E890CFF1/Runner.app/Runner", |
| "processIdentifier" : 1234 |
| } |
| } |
| } |
| '''; |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('launch_results.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'device', |
| 'process', |
| 'launch', |
| '--device', |
| deviceId, |
| bundleId, |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final bool status = await deviceControl.launchApp( |
| deviceId: deviceId, |
| bundleId: bundleId, |
| ); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, isEmpty); |
| expect(tempFile, isNot(exists)); |
| expect(status, true); |
| }); |
| |
| testWithoutContext('Successful launch with launch args', () async { |
| const String deviceControlOutput = ''' |
| { |
| "info" : { |
| "arguments" : [ |
| "devicectl", |
| "device", |
| "process", |
| "launch", |
| "--device", |
| "00001234-0001234A3C03401E", |
| "com.example.flutterApp", |
| "--arg1", |
| "--arg2", |
| "--json-output", |
| "/var/folders/wq/randompath/T/flutter_tools.rand0/core_devices.rand0/install_results.json" |
| ], |
| "commandType" : "devicectl.device.process.launch", |
| "environment" : { |
| |
| }, |
| "outcome" : "success", |
| "version" : "341" |
| }, |
| "result" : { |
| "deviceIdentifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", |
| "launchOptions" : { |
| "activatedWhenStarted" : true, |
| "arguments" : [ |
| |
| ], |
| "environmentVariables" : { |
| "TERM" : "vt100" |
| }, |
| "platformSpecificOptions" : { |
| |
| }, |
| "startStopped" : false, |
| "terminateExistingInstances" : false, |
| "user" : { |
| "active" : true |
| } |
| }, |
| "process" : { |
| "auditToken" : [ |
| 12345, |
| 678 |
| ], |
| "executable" : "file:///private/var/containers/Bundle/Application/12345E6A-7F89-0C12-345E-F6A7E890CFF1/Runner.app/Runner", |
| "processIdentifier" : 1234 |
| } |
| } |
| } |
| '''; |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('launch_results.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'device', |
| 'process', |
| 'launch', |
| '--device', |
| deviceId, |
| bundleId, |
| '--arg1', |
| '--arg2', |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final bool status = await deviceControl.launchApp( |
| deviceId: deviceId, |
| bundleId: bundleId, |
| launchArguments: <String>['--arg1', '--arg2'], |
| ); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, isEmpty); |
| expect(tempFile, isNot(exists)); |
| expect(status, true); |
| }); |
| |
| testWithoutContext('devicectl fails install', () async { |
| const String deviceControlOutput = ''' |
| { |
| "error" : { |
| "code" : -10814, |
| "domain" : "NSOSStatusErrorDomain", |
| "userInfo" : { |
| "_LSFunction" : { |
| "string" : "runEvaluator" |
| }, |
| "_LSLine" : { |
| "int" : 1608 |
| } |
| } |
| }, |
| "info" : { |
| "arguments" : [ |
| "devicectl", |
| "device", |
| "process", |
| "launch", |
| "--device", |
| "00001234-0001234A3C03401E", |
| "com.example.flutterApp", |
| "--json-output", |
| "/var/folders/wq/randompath/T/flutter_tools.rand0/core_devices.rand0/install_results.json" |
| ], |
| "commandType" : "devicectl.device.process.launch", |
| "environment" : { |
| |
| }, |
| "outcome" : "failed", |
| "version" : "341" |
| } |
| } |
| '''; |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('launch_results.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'device', |
| 'process', |
| 'launch', |
| '--device', |
| deviceId, |
| bundleId, |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| exitCode: 1, |
| stderr: ''' |
| ERROR: The operation couldn?t be completed. (OSStatus error -10814.) (NSOSStatusErrorDomain error -10814.) |
| _LSFunction = runEvaluator |
| _LSLine = 1608 |
| ''' |
| )); |
| |
| final bool status = await deviceControl.launchApp( |
| deviceId: deviceId, |
| bundleId: bundleId, |
| ); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, contains('ERROR: The operation couldn?t be completed.')); |
| expect(tempFile, isNot(exists)); |
| expect(status, false); |
| }); |
| |
| testWithoutContext('fails launch because of unexpected JSON', () async { |
| const String deviceControlOutput = ''' |
| { |
| "valid_unexpected_json": true |
| } |
| '''; |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('launch_results.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'device', |
| 'process', |
| 'launch', |
| '--device', |
| deviceId, |
| bundleId, |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| |
| final bool status = await deviceControl.launchApp( |
| deviceId: deviceId, |
| bundleId: bundleId, |
| ); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, contains('devicectl returned unexpected JSON response')); |
| expect(tempFile, isNot(exists)); |
| expect(status, false); |
| }); |
| |
| testWithoutContext('fails launch because of invalid JSON', () async { |
| const String deviceControlOutput = ''' |
| invalid JSON |
| '''; |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('launch_results.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'device', |
| 'process', |
| 'launch', |
| '--device', |
| deviceId, |
| bundleId, |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final bool status = await deviceControl.launchApp( |
| deviceId: deviceId, |
| bundleId: bundleId, |
| ); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, contains('devicectl returned non-JSON response')); |
| expect(tempFile, isNot(exists)); |
| expect(status, false); |
| }); |
| }); |
| |
| group('list apps', () { |
| const String deviceId = 'device-id'; |
| const String bundleId = 'com.example.flutterApp'; |
| |
| testWithoutContext('Successfully parses apps', () async { |
| const String deviceControlOutput = ''' |
| { |
| "info" : { |
| "arguments" : [ |
| "devicectl", |
| "device", |
| "info", |
| "apps", |
| "--device", |
| "00001234-0001234A3C03401E", |
| "--bundle-id", |
| "com.example.flutterApp", |
| "--json-output", |
| "apps.txt" |
| ], |
| "commandType" : "devicectl.device.info.apps", |
| "environment" : { |
| "TERM" : "xterm-256color" |
| }, |
| "outcome" : "success", |
| "version" : "341" |
| }, |
| "result" : { |
| "apps" : [ |
| { |
| "appClip" : false, |
| "builtByDeveloper" : true, |
| "bundleIdentifier" : "com.example.flutterApp", |
| "bundleVersion" : "1", |
| "defaultApp" : false, |
| "hidden" : false, |
| "internalApp" : false, |
| "name" : "Bundle", |
| "removable" : true, |
| "url" : "file:///private/var/containers/Bundle/Application/12345E6A-7F89-0C12-345E-F6A7E890CFF1/Runner.app/", |
| "version" : "1.0.0" |
| }, |
| { |
| "appClip" : true, |
| "builtByDeveloper" : false, |
| "bundleIdentifier" : "com.example.flutterApp2", |
| "bundleVersion" : "2", |
| "defaultApp" : true, |
| "hidden" : true, |
| "internalApp" : true, |
| "name" : "Bundle 2", |
| "removable" : false, |
| "url" : "file:///private/var/containers/Bundle/Application/12345E6A-7F89-0C12-345E-F6A7E890CFF1/Runner.app/", |
| "version" : "1.0.0" |
| } |
| ], |
| "defaultAppsIncluded" : false, |
| "deviceIdentifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", |
| "hiddenAppsIncluded" : false, |
| "internalAppsIncluded" : false, |
| "matchingBundleIdentifier" : "com.example.flutterApp", |
| "removableAppsIncluded" : true |
| } |
| } |
| '''; |
| |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('core_device_app_list.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'device', |
| 'info', |
| 'apps', |
| '--device', |
| deviceId, |
| '--bundle-id', |
| bundleId, |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final List<IOSCoreDeviceInstalledApp> apps = await deviceControl.getInstalledApps( |
| deviceId: deviceId, |
| bundleId: bundleId, |
| ); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, isEmpty); |
| expect(tempFile, isNot(exists)); |
| expect(apps.length, 2); |
| |
| expect(apps[0].appClip, isFalse); |
| expect(apps[0].builtByDeveloper, isTrue); |
| expect(apps[0].bundleIdentifier, 'com.example.flutterApp'); |
| expect(apps[0].bundleVersion, '1'); |
| expect(apps[0].defaultApp, isFalse); |
| expect(apps[0].hidden, isFalse); |
| expect(apps[0].internalApp, isFalse); |
| expect(apps[0].name, 'Bundle'); |
| expect(apps[0].removable, isTrue); |
| expect(apps[0].url, 'file:///private/var/containers/Bundle/Application/12345E6A-7F89-0C12-345E-F6A7E890CFF1/Runner.app/'); |
| expect(apps[0].version, '1.0.0'); |
| |
| expect(apps[1].appClip, isTrue); |
| expect(apps[1].builtByDeveloper, isFalse); |
| expect(apps[1].bundleIdentifier, 'com.example.flutterApp2'); |
| expect(apps[1].bundleVersion, '2'); |
| expect(apps[1].defaultApp, isTrue); |
| expect(apps[1].hidden, isTrue); |
| expect(apps[1].internalApp, isTrue); |
| expect(apps[1].name, 'Bundle 2'); |
| expect(apps[1].removable, isFalse); |
| expect(apps[1].url, 'file:///private/var/containers/Bundle/Application/12345E6A-7F89-0C12-345E-F6A7E890CFF1/Runner.app/'); |
| expect(apps[1].version, '1.0.0'); |
| }); |
| |
| |
| testWithoutContext('Successfully find installed app', () async { |
| const String deviceControlOutput = ''' |
| { |
| "info" : { |
| "arguments" : [ |
| "devicectl", |
| "device", |
| "info", |
| "apps", |
| "--device", |
| "00001234-0001234A3C03401E", |
| "--bundle-id", |
| "com.example.flutterApp", |
| "--json-output", |
| "apps.txt" |
| ], |
| "commandType" : "devicectl.device.info.apps", |
| "environment" : { |
| "TERM" : "xterm-256color" |
| }, |
| "outcome" : "success", |
| "version" : "341" |
| }, |
| "result" : { |
| "apps" : [ |
| { |
| "appClip" : false, |
| "builtByDeveloper" : true, |
| "bundleIdentifier" : "com.example.flutterApp", |
| "bundleVersion" : "1", |
| "defaultApp" : false, |
| "hidden" : false, |
| "internalApp" : false, |
| "name" : "Bundle", |
| "removable" : true, |
| "url" : "file:///private/var/containers/Bundle/Application/12345E6A-7F89-0C12-345E-F6A7E890CFF1/Runner.app/", |
| "version" : "1.0.0" |
| } |
| ], |
| "defaultAppsIncluded" : false, |
| "deviceIdentifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", |
| "hiddenAppsIncluded" : false, |
| "internalAppsIncluded" : false, |
| "matchingBundleIdentifier" : "com.example.flutterApp", |
| "removableAppsIncluded" : true |
| } |
| } |
| '''; |
| |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('core_device_app_list.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'device', |
| 'info', |
| 'apps', |
| '--device', |
| deviceId, |
| '--bundle-id', |
| bundleId, |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final bool status = await deviceControl.isAppInstalled( |
| deviceId: deviceId, |
| bundleId: bundleId, |
| ); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, isEmpty); |
| expect(tempFile, isNot(exists)); |
| expect(status, true); |
| }); |
| |
| testWithoutContext('Succeeds but does not find app', () async { |
| const String deviceControlOutput = ''' |
| { |
| "info" : { |
| "arguments" : [ |
| "devicectl", |
| "device", |
| "info", |
| "apps", |
| "--device", |
| "00001234-0001234A3C03401E", |
| "--bundle-id", |
| "com.example.flutterApp", |
| "--json-output", |
| "apps.txt" |
| ], |
| "commandType" : "devicectl.device.info.apps", |
| "environment" : { |
| "TERM" : "xterm-256color" |
| }, |
| "outcome" : "success", |
| "version" : "341" |
| }, |
| "result" : { |
| "apps" : [ |
| ], |
| "defaultAppsIncluded" : false, |
| "deviceIdentifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", |
| "hiddenAppsIncluded" : false, |
| "internalAppsIncluded" : false, |
| "matchingBundleIdentifier" : "com.example.flutterApp", |
| "removableAppsIncluded" : true |
| } |
| } |
| '''; |
| |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('core_device_app_list.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'device', |
| 'info', |
| 'apps', |
| '--device', |
| deviceId, |
| '--bundle-id', |
| bundleId, |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final bool status = await deviceControl.isAppInstalled( |
| deviceId: deviceId, |
| bundleId: bundleId, |
| ); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, isEmpty); |
| expect(tempFile, isNot(exists)); |
| expect(status, false); |
| }); |
| |
| testWithoutContext('devicectl fails to get apps', () async { |
| const String deviceControlOutput = ''' |
| { |
| "error" : { |
| "code" : 1000, |
| "domain" : "com.apple.dt.CoreDeviceError", |
| "userInfo" : { |
| "NSLocalizedDescription" : { |
| "string" : "The specified device was not found." |
| } |
| } |
| }, |
| "info" : { |
| "arguments" : [ |
| "devicectl", |
| "device", |
| "info", |
| "apps", |
| "--device", |
| "00001234-0001234A3C03401E", |
| "--bundle-id", |
| "com.example.flutterApp", |
| "--json-output", |
| "apps.txt" |
| ], |
| "commandType" : "devicectl.device.info.apps", |
| "environment" : { |
| "TERM" : "xterm-256color" |
| }, |
| "outcome" : "failed", |
| "version" : "341" |
| } |
| } |
| '''; |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('core_device_app_list.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'device', |
| 'info', |
| 'apps', |
| '--device', |
| deviceId, |
| '--bundle-id', |
| bundleId, |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| exitCode: 1, |
| stderr: ''' |
| ERROR: The specified device was not found. (com.apple.dt.CoreDeviceError error 1000.) |
| ''' |
| )); |
| |
| final bool status = await deviceControl.isAppInstalled( |
| deviceId: deviceId, |
| bundleId: bundleId, |
| ); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, contains('ERROR: The specified device was not found.')); |
| expect(tempFile, isNot(exists)); |
| expect(status, false); |
| }); |
| |
| testWithoutContext('fails launch because of unexpected JSON', () async { |
| const String deviceControlOutput = ''' |
| { |
| "valid_unexpected_json": true |
| } |
| '''; |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('core_device_app_list.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'device', |
| 'info', |
| 'apps', |
| '--device', |
| deviceId, |
| '--bundle-id', |
| bundleId, |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| |
| final bool status = await deviceControl.isAppInstalled( |
| deviceId: deviceId, |
| bundleId: bundleId, |
| ); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, contains('devicectl returned unexpected JSON response')); |
| expect(tempFile, isNot(exists)); |
| expect(status, false); |
| }); |
| |
| testWithoutContext('fails launch because of invalid JSON', () async { |
| const String deviceControlOutput = ''' |
| invalid JSON |
| '''; |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('core_device_app_list.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'device', |
| 'info', |
| 'apps', |
| '--device', |
| deviceId, |
| '--bundle-id', |
| bundleId, |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final bool status = await deviceControl.isAppInstalled( |
| deviceId: deviceId, |
| bundleId: bundleId, |
| ); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, contains('devicectl returned non-JSON response')); |
| expect(tempFile, isNot(exists)); |
| expect(status, false); |
| }); |
| }); |
| |
| group('list devices', () { |
| testWithoutContext('Handles FileSystemException deleting temp directory', () async { |
| final Directory tempDir = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0'); |
| final File tempFile = tempDir.childFile('core_device_list.json'); |
| final List<String> args = <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'list', |
| 'devices', |
| '--timeout', |
| '5', |
| '--json-output', |
| tempFile.path, |
| ]; |
| fakeProcessManager.addCommand(FakeCommand( |
| command: args, |
| onRun: (_) { |
| // Simulate that this command threw and simultaneously the OS |
| // deleted the temp directory |
| expect(tempFile, exists); |
| tempDir.deleteSync(recursive: true); |
| expect(tempFile, isNot(exists)); |
| throw ProcessException(args.first, args.sublist(1)); |
| }, |
| )); |
| |
| await deviceControl.getCoreDevices(); |
| expect(logger.errorText, contains('Error executing devicectl: ProcessException')); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| }); |
| |
| testWithoutContext('Handles json file mysteriously disappearing', () async { |
| final Directory tempDir = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0'); |
| final File tempFile = tempDir.childFile('core_device_list.json'); |
| final List<String> args = <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'list', |
| 'devices', |
| '--timeout', |
| '5', |
| '--json-output', |
| tempFile.path, |
| ]; |
| fakeProcessManager.addCommand(FakeCommand( |
| command: args, |
| onRun: (_) { |
| // Simulate that this command deleted tempFile, did not create a |
| // new one, and exited successfully |
| expect(tempFile, exists); |
| tempFile.deleteSync(); |
| expect(tempFile, isNot(exists)); |
| }, |
| )); |
| |
| await expectLater( |
| () => deviceControl.getCoreDevices(), |
| throwsA( |
| isA<StateError>().having( |
| (StateError e) => e.message, |
| 'message', |
| contains('Expected the file ${tempFile.path} to exist but it did not'), |
| ), |
| ), |
| ); |
| expect( |
| logger.errorText, |
| contains('After running the command xcrun devicectl list devices ' |
| '--timeout 5 --json-output ${tempFile.path} the file\n' |
| '${tempFile.path} was expected to exist, but it did not', |
| ), |
| ); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| }); |
| |
| testWithoutContext('No devices', () async { |
| const String deviceControlOutput = ''' |
| { |
| "info" : { |
| "arguments" : [ |
| "devicectl", |
| "list", |
| "devices", |
| "--json-output", |
| "core_device_list.json" |
| ], |
| "commandType" : "devicectl.list.devices", |
| "environment" : { |
| "TERM" : "xterm-256color" |
| }, |
| "outcome" : "success", |
| "version" : "325.3" |
| }, |
| "result" : { |
| "devices" : [ |
| |
| ] |
| } |
| } |
| '''; |
| |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('core_device_list.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'list', |
| 'devices', |
| '--timeout', |
| '5', |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices(); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(devices.isEmpty, isTrue); |
| }); |
| |
| testWithoutContext('All sections parsed', () async { |
| const String deviceControlOutput = ''' |
| { |
| "info" : { |
| "arguments" : [ |
| "devicectl", |
| "list", |
| "devices", |
| "--json-output", |
| "core_device_list.json" |
| ], |
| "commandType" : "devicectl.list.devices", |
| "environment" : { |
| "TERM" : "xterm-256color" |
| }, |
| "outcome" : "success", |
| "version" : "325.3" |
| }, |
| "result" : { |
| "devices" : [ |
| { |
| "capabilities" : [ |
| ], |
| "connectionProperties" : { |
| }, |
| "deviceProperties" : { |
| }, |
| "hardwareProperties" : { |
| }, |
| "identifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", |
| "visibilityClass" : "default" |
| } |
| ] |
| } |
| } |
| '''; |
| |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('core_device_list.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'list', |
| 'devices', |
| '--timeout', |
| '5', |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices(); |
| expect(devices.length, 1); |
| |
| expect(devices[0].capabilities, isNotNull); |
| expect(devices[0].connectionProperties, isNotNull); |
| expect(devices[0].deviceProperties, isNotNull); |
| expect(devices[0].hardwareProperties, isNotNull); |
| expect(devices[0].coreDeviceIdentifier, '123456BB5-AEDE-7A22-B890-1234567890DD'); |
| expect(devices[0].visibilityClass, 'default'); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(tempFile, isNot(exists)); |
| }); |
| |
| testWithoutContext('All sections parsed, device missing sections', () async { |
| const String deviceControlOutput = ''' |
| { |
| "info" : { |
| "arguments" : [ |
| "devicectl", |
| "list", |
| "devices", |
| "--json-output", |
| "core_device_list.json" |
| ], |
| "commandType" : "devicectl.list.devices", |
| "environment" : { |
| "TERM" : "xterm-256color" |
| }, |
| "outcome" : "success", |
| "version" : "325.3" |
| }, |
| "result" : { |
| "devices" : [ |
| { |
| "identifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", |
| "visibilityClass" : "default" |
| } |
| ] |
| } |
| } |
| '''; |
| |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('core_device_list.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'list', |
| 'devices', |
| '--timeout', |
| '5', |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices(); |
| expect(devices.length, 1); |
| |
| expect(devices[0].capabilities, isEmpty); |
| expect(devices[0].connectionProperties, isNull); |
| expect(devices[0].deviceProperties, isNull); |
| expect(devices[0].hardwareProperties, isNull); |
| expect(devices[0].coreDeviceIdentifier, '123456BB5-AEDE-7A22-B890-1234567890DD'); |
| expect(devices[0].visibilityClass, 'default'); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(tempFile, isNot(exists)); |
| }); |
| |
| testWithoutContext('capabilities parsed', () async { |
| const String deviceControlOutput = ''' |
| { |
| "result" : { |
| "devices" : [ |
| { |
| "capabilities" : [ |
| { |
| "featureIdentifier" : "com.apple.coredevice.feature.spawnexecutable", |
| "name" : "Spawn Executable" |
| }, |
| { |
| "featureIdentifier" : "com.apple.coredevice.feature.launchapplication", |
| "name" : "Launch Application" |
| } |
| ] |
| } |
| ] |
| } |
| } |
| '''; |
| |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('core_device_list.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'list', |
| 'devices', |
| '--timeout', |
| '5', |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices(); |
| expect(devices.length, 1); |
| |
| expect(devices[0].capabilities.length, 2); |
| expect(devices[0].capabilities[0].featureIdentifier, 'com.apple.coredevice.feature.spawnexecutable'); |
| expect(devices[0].capabilities[0].name, 'Spawn Executable'); |
| expect(devices[0].capabilities[1].featureIdentifier, 'com.apple.coredevice.feature.launchapplication'); |
| expect(devices[0].capabilities[1].name, 'Launch Application'); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(tempFile, isNot(exists)); |
| }); |
| |
| testWithoutContext('connectionProperties parsed', () async { |
| const String deviceControlOutput = ''' |
| { |
| "result" : { |
| "devices" : [ |
| { |
| "connectionProperties" : { |
| "authenticationType" : "manualPairing", |
| "isMobileDeviceOnly" : false, |
| "lastConnectionDate" : "2023-06-15T15:29:00.082Z", |
| "localHostnames" : [ |
| "Victorias-iPad.coredevice.local", |
| "00001234-0001234A3C03401E.coredevice.local", |
| "123456BB5-AEDE-7A22-B890-1234567890DD.coredevice.local" |
| ], |
| "pairingState" : "paired", |
| "potentialHostnames" : [ |
| "00001234-0001234A3C03401E.coredevice.local", |
| "123456BB5-AEDE-7A22-B890-1234567890DD.coredevice.local" |
| ], |
| "transportType" : "wired", |
| "tunnelIPAddress" : "fdf1:23c4:cd56::1", |
| "tunnelState" : "connected", |
| "tunnelTransportProtocol" : "tcp" |
| } |
| } |
| ] |
| } |
| } |
| '''; |
| |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('core_device_list.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'list', |
| 'devices', |
| '--timeout', |
| '5', |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices(); |
| expect(devices.length, 1); |
| |
| expect(devices[0].connectionProperties?.authenticationType, 'manualPairing'); |
| expect(devices[0].connectionProperties?.isMobileDeviceOnly, false); |
| expect(devices[0].connectionProperties?.lastConnectionDate, '2023-06-15T15:29:00.082Z'); |
| expect( |
| devices[0].connectionProperties?.localHostnames, |
| <String>[ |
| 'Victorias-iPad.coredevice.local', |
| '00001234-0001234A3C03401E.coredevice.local', |
| '123456BB5-AEDE-7A22-B890-1234567890DD.coredevice.local', |
| ], |
| ); |
| expect(devices[0].connectionProperties?.pairingState, 'paired'); |
| expect(devices[0].connectionProperties?.potentialHostnames, <String>[ |
| '00001234-0001234A3C03401E.coredevice.local', |
| '123456BB5-AEDE-7A22-B890-1234567890DD.coredevice.local', |
| ]); |
| expect(devices[0].connectionProperties?.transportType, 'wired'); |
| expect(devices[0].connectionProperties?.tunnelIPAddress, 'fdf1:23c4:cd56::1'); |
| expect(devices[0].connectionProperties?.tunnelState, 'connected'); |
| expect(devices[0].connectionProperties?.tunnelTransportProtocol, 'tcp'); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(tempFile, isNot(exists)); |
| }); |
| |
| testWithoutContext('deviceProperties parsed', () async { |
| const String deviceControlOutput = ''' |
| { |
| "result" : { |
| "devices" : [ |
| { |
| "deviceProperties" : { |
| "bootedFromSnapshot" : true, |
| "bootedSnapshotName" : "com.apple.os.update-123456", |
| "bootState" : "booted", |
| "ddiServicesAvailable" : true, |
| "developerModeStatus" : "enabled", |
| "hasInternalOSBuild" : false, |
| "name" : "iPadName", |
| "osBuildUpdate" : "21A5248v", |
| "osVersionNumber" : "17.0", |
| "rootFileSystemIsWritable" : false, |
| "screenViewingURL" : "coredevice-devices:/viewDeviceByUUID?uuid=123456BB5-AEDE-7A22-B890-1234567890DD" |
| } |
| } |
| ] |
| } |
| } |
| '''; |
| |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('core_device_list.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'list', |
| 'devices', |
| '--timeout', |
| '5', |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices(); |
| expect(devices.length, 1); |
| |
| expect(devices[0].deviceProperties?.bootedFromSnapshot, true); |
| expect(devices[0].deviceProperties?.bootedSnapshotName, 'com.apple.os.update-123456'); |
| expect(devices[0].deviceProperties?.bootState, 'booted'); |
| expect(devices[0].deviceProperties?.ddiServicesAvailable, true); |
| expect(devices[0].deviceProperties?.developerModeStatus, 'enabled'); |
| expect(devices[0].deviceProperties?.hasInternalOSBuild, false); |
| expect(devices[0].deviceProperties?.name, 'iPadName'); |
| expect(devices[0].deviceProperties?.osBuildUpdate, '21A5248v'); |
| expect(devices[0].deviceProperties?.osVersionNumber, '17.0'); |
| expect(devices[0].deviceProperties?.rootFileSystemIsWritable, false); |
| expect(devices[0].deviceProperties?.screenViewingURL, 'coredevice-devices:/viewDeviceByUUID?uuid=123456BB5-AEDE-7A22-B890-1234567890DD'); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(tempFile, isNot(exists)); |
| }); |
| |
| testWithoutContext('hardwareProperties parsed', () async { |
| const String deviceControlOutput = r''' |
| { |
| "result" : { |
| "devices" : [ |
| { |
| "hardwareProperties" : { |
| "cpuType" : { |
| "name" : "arm64e", |
| "subType" : 2, |
| "type" : 16777228 |
| }, |
| "deviceType" : "iPad", |
| "ecid" : 12345678903408542, |
| "hardwareModel" : "J617AP", |
| "internalStorageCapacity" : 128000000000, |
| "marketingName" : "iPad Pro (11-inch) (4th generation)\"", |
| "platform" : "iOS", |
| "productType" : "iPad14,3", |
| "serialNumber" : "HC123DHCQV", |
| "supportedCPUTypes" : [ |
| { |
| "name" : "arm64e", |
| "subType" : 2, |
| "type" : 16777228 |
| }, |
| { |
| "name" : "arm64", |
| "subType" : 0, |
| "type" : 16777228 |
| } |
| ], |
| "supportedDeviceFamilies" : [ |
| 1, |
| 2 |
| ], |
| "thinningProductType" : "iPad14,3-A", |
| "udid" : "00001234-0001234A3C03401E" |
| } |
| } |
| ] |
| } |
| } |
| '''; |
| |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('core_device_list.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'list', |
| 'devices', |
| '--timeout', |
| '5', |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices(); |
| expect(devices.length, 1); |
| |
| expect(devices[0].hardwareProperties?.cpuType, isNotNull); |
| expect(devices[0].hardwareProperties?.cpuType?.name, 'arm64e'); |
| expect(devices[0].hardwareProperties?.cpuType?.subType, 2); |
| expect(devices[0].hardwareProperties?.cpuType?.cpuType, 16777228); |
| expect(devices[0].hardwareProperties?.deviceType, 'iPad'); |
| expect(devices[0].hardwareProperties?.ecid, 12345678903408542); |
| expect(devices[0].hardwareProperties?.hardwareModel, 'J617AP'); |
| expect(devices[0].hardwareProperties?.internalStorageCapacity, 128000000000); |
| expect(devices[0].hardwareProperties?.marketingName, 'iPad Pro (11-inch) (4th generation)"'); |
| expect(devices[0].hardwareProperties?.platform, 'iOS'); |
| expect(devices[0].hardwareProperties?.productType, 'iPad14,3'); |
| expect(devices[0].hardwareProperties?.serialNumber, 'HC123DHCQV'); |
| expect(devices[0].hardwareProperties?.supportedCPUTypes, isNotNull); |
| expect(devices[0].hardwareProperties?.supportedCPUTypes?[0].name, 'arm64e'); |
| expect(devices[0].hardwareProperties?.supportedCPUTypes?[0].subType, 2); |
| expect(devices[0].hardwareProperties?.supportedCPUTypes?[0].cpuType, 16777228); |
| expect(devices[0].hardwareProperties?.supportedCPUTypes?[1].name, 'arm64'); |
| expect(devices[0].hardwareProperties?.supportedCPUTypes?[1].subType, 0); |
| expect(devices[0].hardwareProperties?.supportedCPUTypes?[1].cpuType, 16777228); |
| expect(devices[0].hardwareProperties?.supportedDeviceFamilies, <int>[1, 2]); |
| expect(devices[0].hardwareProperties?.thinningProductType, 'iPad14,3-A'); |
| |
| expect(devices[0].hardwareProperties?.udid, '00001234-0001234A3C03401E'); |
| |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(tempFile, isNot(exists)); |
| }); |
| |
| group('Handles errors', () { |
| testWithoutContext('invalid json', () async { |
| const String deviceControlOutput = '''Invalid JSON'''; |
| |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('core_device_list.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'list', |
| 'devices', |
| '--timeout', |
| '5', |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices(); |
| expect(devices.isEmpty, isTrue); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, contains('devicectl returned non-JSON response: Invalid JSON')); |
| }); |
| |
| testWithoutContext('unexpected json', () async { |
| const String deviceControlOutput = ''' |
| { |
| "info" : { |
| "arguments" : [ |
| "devicectl", |
| "list", |
| "devices", |
| "--json-output", |
| "device_list.json" |
| ], |
| "commandType" : "devicectl.list.devices", |
| "environment" : { |
| "TERM" : "xterm-256color" |
| }, |
| "outcome" : "success", |
| "version" : "325.3" |
| }, |
| "result" : [ |
| |
| ] |
| } |
| '''; |
| |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('core_device_list.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'list', |
| 'devices', |
| '--timeout', |
| '5', |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices(); |
| expect(devices.isEmpty, isTrue); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect(logger.errorText, contains('devicectl returned unexpected JSON response:')); |
| }); |
| |
| testWithoutContext('When timeout is below minimum, default to minimum', () async { |
| const String deviceControlOutput = ''' |
| { |
| "info" : { |
| "arguments" : [ |
| "devicectl", |
| "list", |
| "devices", |
| "--json-output", |
| "core_device_list.json" |
| ], |
| "commandType" : "devicectl.list.devices", |
| "environment" : { |
| "TERM" : "xterm-256color" |
| }, |
| "outcome" : "success", |
| "version" : "325.3" |
| }, |
| "result" : { |
| "devices" : [ |
| { |
| "identifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", |
| "visibilityClass" : "default" |
| } |
| ] |
| } |
| } |
| '''; |
| |
| final File tempFile = fileSystem.systemTempDirectory |
| .childDirectory('core_devices.rand0') |
| .childFile('core_device_list.json'); |
| fakeProcessManager.addCommand(FakeCommand( |
| command: <String>[ |
| 'xcrun', |
| 'devicectl', |
| 'list', |
| 'devices', |
| '--timeout', |
| '5', |
| '--json-output', |
| tempFile.path, |
| ], |
| onRun: (_) { |
| expect(tempFile, exists); |
| tempFile.writeAsStringSync(deviceControlOutput); |
| }, |
| )); |
| |
| final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices( |
| timeout: const Duration(seconds: 2), |
| ); |
| expect(devices.isNotEmpty, isTrue); |
| expect(fakeProcessManager, hasNoRemainingExpectations); |
| expect( |
| logger.errorText, |
| contains('Timeout of 2 seconds is below the minimum timeout value ' |
| 'for devicectl. Changing the timeout to the minimum value of 5.'), |
| ); |
| }); |
| }); |
| }); |
| |
| |
| }); |
| } |