| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| import 'dart:convert'; |
| |
| import 'package:args/command_runner.dart'; |
| import 'package:file/file.dart'; |
| import 'package:file/memory.dart'; |
| import 'package:flutter_tools/src/application_package.dart'; |
| import 'package:flutter_tools/src/artifacts.dart'; |
| import 'package:flutter_tools/src/base/file_system.dart'; |
| import 'package:flutter_tools/src/base/io.dart'; |
| import 'package:flutter_tools/src/base/logger.dart'; |
| import 'package:flutter_tools/src/build_info.dart'; |
| import 'package:flutter_tools/src/cache.dart'; |
| import 'package:flutter_tools/src/commands/create.dart'; |
| import 'package:flutter_tools/src/device.dart'; |
| import 'package:flutter_tools/src/doctor.dart'; |
| import 'package:flutter_tools/src/ios/devices.dart'; |
| import 'package:flutter_tools/src/ios/mac.dart'; |
| import 'package:flutter_tools/src/ios/ios_deploy.dart'; |
| import 'package:flutter_tools/src/ios/ios_workflow.dart'; |
| import 'package:flutter_tools/src/macos/xcode.dart'; |
| import 'package:flutter_tools/src/mdns_discovery.dart'; |
| import 'package:flutter_tools/src/project.dart'; |
| import 'package:flutter_tools/src/reporting/reporting.dart'; |
| import 'package:flutter_tools/src/globals.dart' as globals; |
| |
| import 'package:meta/meta.dart'; |
| import 'package:mockito/mockito.dart'; |
| import 'package:platform/platform.dart'; |
| import 'package:process/process.dart'; |
| import 'package:quiver/testing/async.dart'; |
| |
| import '../../src/common.dart'; |
| import '../../src/context.dart'; |
| import '../../src/mocks.dart'; |
| |
| 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('IOSDevice', () { |
| final List<Platform> unsupportedPlatforms = <Platform>[linuxPlatform, windowsPlatform]; |
| Artifacts mockArtifacts; |
| MockCache mockCache; |
| Logger logger; |
| IOSDeploy iosDeploy; |
| FileSystem mockFileSystem; |
| |
| setUp(() { |
| mockArtifacts = MockArtifacts(); |
| mockCache = MockCache(); |
| const MapEntry<String, String> dyLdLibEntry = MapEntry<String, String>('DYLD_LIBRARY_PATH', '/path/to/libs'); |
| when(mockCache.dyLdLibEntry).thenReturn(dyLdLibEntry); |
| mockFileSystem = MockFileSystem(); |
| logger = BufferLogger.test(); |
| iosDeploy = IOSDeploy( |
| artifacts: mockArtifacts, |
| cache: mockCache, |
| logger: logger, |
| platform: macPlatform, |
| processManager: FakeProcessManager.any(), |
| ); |
| }); |
| |
| testWithoutContext('successfully instantiates on Mac OS', () { |
| IOSDevice( |
| 'device-123', |
| artifacts: mockArtifacts, |
| fileSystem: mockFileSystem, |
| logger: logger, |
| platform: macPlatform, |
| iosDeploy: iosDeploy, |
| name: 'iPhone 1', |
| sdkVersion: '13.3', |
| cpuArchitecture: DarwinArch.arm64 |
| ); |
| }); |
| |
| testWithoutContext('parses major version', () { |
| expect(IOSDevice( |
| 'device-123', |
| artifacts: mockArtifacts, |
| fileSystem: mockFileSystem, |
| logger: logger, |
| platform: macPlatform, |
| iosDeploy: iosDeploy, |
| name: 'iPhone 1', |
| cpuArchitecture: DarwinArch.arm64, |
| sdkVersion: '1.0.0' |
| ).majorSdkVersion, 1); |
| expect(IOSDevice( |
| 'device-123', |
| artifacts: mockArtifacts, |
| fileSystem: mockFileSystem, |
| logger: logger, |
| platform: macPlatform, |
| iosDeploy: iosDeploy, |
| name: 'iPhone 1', |
| cpuArchitecture: DarwinArch.arm64, |
| sdkVersion: '13.1.1' |
| ).majorSdkVersion, 13); |
| expect(IOSDevice( |
| 'device-123', |
| artifacts: mockArtifacts, |
| fileSystem: mockFileSystem, |
| logger: logger, |
| platform: macPlatform, |
| iosDeploy: iosDeploy, |
| name: 'iPhone 1', |
| cpuArchitecture: DarwinArch.arm64, |
| sdkVersion: '10' |
| ).majorSdkVersion, 10); |
| expect(IOSDevice( |
| 'device-123', |
| artifacts: mockArtifacts, |
| fileSystem: mockFileSystem, |
| logger: logger, |
| platform: macPlatform, |
| iosDeploy: iosDeploy, |
| name: 'iPhone 1', |
| cpuArchitecture: DarwinArch.arm64, |
| sdkVersion: '0' |
| ).majorSdkVersion, 0); |
| expect(IOSDevice( |
| 'device-123', |
| artifacts: mockArtifacts, |
| fileSystem: mockFileSystem, |
| logger: logger, |
| platform: macPlatform, |
| iosDeploy: iosDeploy, |
| name: 'iPhone 1', |
| cpuArchitecture: DarwinArch.arm64, |
| sdkVersion: 'bogus' |
| ).majorSdkVersion, 0); |
| }); |
| |
| for (final Platform platform in unsupportedPlatforms) { |
| testWithoutContext('throws UnsupportedError exception if instantiated on ${platform.operatingSystem}', () { |
| expect( |
| () { |
| IOSDevice( |
| 'device-123', |
| artifacts: mockArtifacts, |
| fileSystem: mockFileSystem, |
| logger: logger, |
| platform: platform, |
| iosDeploy: iosDeploy, |
| name: 'iPhone 1', |
| sdkVersion: '13.3', |
| cpuArchitecture: DarwinArch.arm64, |
| ); |
| }, |
| throwsAssertionError, |
| ); |
| }); |
| } |
| |
| group('ios-deploy wrappers', () { |
| const String appId = '789'; |
| const String deviceId = 'device-123'; |
| IOSDevice device; |
| IOSDeploy iosDeploy; |
| FakeProcessManager fakeProcessManager; |
| const String iosDeployPath = '/path/to/ios-deploy'; |
| |
| setUp(() { |
| 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: 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.isAppInstalled(mockApp); |
| expect(result, false); |
| }); |
| |
| testWithoutContext('installApp() catches ProcessException from ios-deploy', () async { |
| const String bundlePath = '/path/to/bundle'; |
| final MockIOSApp mockApp = MockIOSApp(); |
| when(mockApp.id).thenReturn(appId); |
| when(mockApp.deviceBundlePath).thenReturn(bundlePath); |
| final MockDirectory mockDirectory = MockDirectory(); |
| when(mockFileSystem.directory(bundlePath)).thenReturn(mockDirectory); |
| when(mockDirectory.existsSync()).thenReturn(true); |
| when(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); |
| }); |
| |
| testWithoutContext('uninstallApp() 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, |
| '--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); |
| }); |
| }); |
| |
| group('.dispose()', () { |
| IOSDevice device; |
| MockIOSApp appPackage1; |
| MockIOSApp appPackage2; |
| IOSDeviceLogReader logReader1; |
| IOSDeviceLogReader logReader2; |
| MockProcess mockProcess1; |
| MockProcess mockProcess2; |
| MockProcess mockProcess3; |
| IOSDevicePortForwarder portForwarder; |
| ForwardedPort forwardedPort; |
| Artifacts mockArtifacts; |
| MockCache mockCache; |
| Logger logger; |
| IOSDeploy iosDeploy; |
| FileSystem mockFileSystem; |
| |
| IOSDevicePortForwarder createPortForwarder( |
| ForwardedPort forwardedPort, |
| IOSDevice device) { |
| final IOSDevicePortForwarder portForwarder = IOSDevicePortForwarder( |
| dyLdLibEntry: mockCache.dyLdLibEntry, |
| id: device.id, |
| iproxyPath: mockArtifacts.getArtifactPath(Artifact.iproxy, platform: TargetPlatform.ios), |
| logger: logger, |
| processManager: FakeProcessManager.any(), |
| ); |
| portForwarder.addForwardedPorts(<ForwardedPort>[forwardedPort]); |
| return portForwarder; |
| } |
| |
| IOSDeviceLogReader createLogReader( |
| IOSDevice device, |
| IOSApp appPackage, |
| Process process) { |
| final IOSDeviceLogReader logReader = IOSDeviceLogReader(device, appPackage); |
| logReader.idevicesyslogProcess = process; |
| return logReader; |
| } |
| |
| setUp(() { |
| appPackage1 = MockIOSApp(); |
| appPackage2 = MockIOSApp(); |
| when(appPackage1.name).thenReturn('flutterApp1'); |
| when(appPackage2.name).thenReturn('flutterApp2'); |
| mockProcess1 = MockProcess(); |
| mockProcess2 = MockProcess(); |
| mockProcess3 = MockProcess(); |
| forwardedPort = ForwardedPort.withContext(123, 456, mockProcess3); |
| mockArtifacts = MockArtifacts(); |
| mockCache = MockCache(); |
| mockFileSystem = MockFileSystem(); |
| iosDeploy = IOSDeploy( |
| artifacts: mockArtifacts, |
| cache: mockCache, |
| logger: logger, |
| platform: macPlatform, |
| processManager: FakeProcessManager.any(), |
| ); |
| }); |
| |
| testWithoutContext('kills all log readers & port forwarders', () async { |
| device = IOSDevice( |
| '123', |
| artifacts: mockArtifacts, |
| fileSystem: mockFileSystem, |
| logger: logger, |
| platform: macPlatform, |
| iosDeploy: iosDeploy, |
| name: 'iPhone 1', |
| sdkVersion: '13.3', |
| cpuArchitecture: DarwinArch.arm64, |
| ); |
| logReader1 = createLogReader(device, appPackage1, mockProcess1); |
| logReader2 = createLogReader(device, appPackage2, mockProcess2); |
| portForwarder = createPortForwarder(forwardedPort, device); |
| device.setLogReader(appPackage1, logReader1); |
| device.setLogReader(appPackage2, logReader2); |
| device.portForwarder = portForwarder; |
| |
| await device.dispose(); |
| |
| verify(mockProcess1.kill()); |
| verify(mockProcess2.kill()); |
| verify(mockProcess3.kill()); |
| }); |
| }); |
| |
| group('startApp', () { |
| MockIOSApp mockApp; |
| MockArtifacts mockArtifacts; |
| MockCache mockCache; |
| MockFileSystem mockFileSystem; |
| MockPlatform mockPlatform; |
| MockProcessManager mockProcessManager; |
| MockDeviceLogReader mockLogReader; |
| MockMDnsObservatoryDiscovery mockMDnsObservatoryDiscovery; |
| MockPortForwarder mockPortForwarder; |
| MockIMobileDevice mockIMobileDevice; |
| MockIOSDeploy mockIosDeploy; |
| MockUsage mockUsage; |
| |
| Directory tempDir; |
| Directory projectDir; |
| |
| const int devicePort = 499; |
| const int hostPort = 42; |
| const String installerPath = '/path/to/ideviceinstaller'; |
| const String iosDeployPath = '/path/to/iosdeploy'; |
| const String iproxyPath = '/path/to/iproxy'; |
| 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] |
| ); |
| |
| setUp(() { |
| Cache.disableLocking(); |
| |
| mockApp = MockIOSApp(); |
| mockArtifacts = MockArtifacts(); |
| mockCache = MockCache(); |
| when(mockCache.dyLdLibEntry).thenReturn(libraryEntry); |
| mockFileSystem = MockFileSystem(); |
| mockPlatform = MockPlatform(); |
| when(mockPlatform.isMacOS).thenReturn(true); |
| mockMDnsObservatoryDiscovery = MockMDnsObservatoryDiscovery(); |
| mockProcessManager = MockProcessManager(); |
| mockLogReader = MockDeviceLogReader(); |
| mockPortForwarder = MockPortForwarder(); |
| mockIMobileDevice = MockIMobileDevice(); |
| mockIosDeploy = MockIOSDeploy(); |
| mockUsage = MockUsage(); |
| |
| tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_create_test.'); |
| projectDir = tempDir.childDirectory('flutter_project'); |
| |
| when( |
| mockArtifacts.getArtifactPath( |
| Artifact.ideviceinstaller, |
| platform: anyNamed('platform'), |
| ), |
| ).thenReturn(installerPath); |
| |
| when( |
| mockArtifacts.getArtifactPath( |
| Artifact.iosDeploy, |
| platform: anyNamed('platform'), |
| ), |
| ).thenReturn(iosDeployPath); |
| |
| when( |
| mockArtifacts.getArtifactPath( |
| Artifact.iproxy, |
| platform: anyNamed('platform'), |
| ), |
| ).thenReturn(iproxyPath); |
| |
| when(mockPortForwarder.forward(devicePort, hostPort: anyNamed('hostPort'))) |
| .thenAnswer((_) async => hostPort); |
| when(mockPortForwarder.forwardedPorts) |
| .thenReturn(<ForwardedPort>[ForwardedPort(hostPort, devicePort)]); |
| when(mockPortForwarder.unforward(any)) |
| .thenAnswer((_) async => null); |
| |
| final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); |
| when(mockFileSystem.currentDirectory) |
| .thenReturn(memoryFileSystem.currentDirectory); |
| |
| const String bundlePath = '/path/to/bundle'; |
| final List<String> installArgs = <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( |
| installArgs, |
| workingDirectory: anyNamed('workingDirectory'), |
| environment: env, |
| )).thenAnswer( |
| (_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', '')) |
| ); |
| }); |
| |
| tearDown(() { |
| mockLogReader.dispose(); |
| tryToDelete(tempDir); |
| |
| Cache.enableLocking(); |
| }); |
| |
| 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, |
| ); |
| device.portForwarder = mockPortForwarder; |
| device.setLogReader(mockApp, mockLogReader); |
| await device.dispose(); |
| verify(mockPortForwarder.dispose()).called(1); |
| }); |
| |
| testUsingContext('succeeds in debug mode via mDNS', () async { |
| final IOSDevice device = IOSDevice( |
| '123', |
| name: 'iPhone 1', |
| sdkVersion: '13.3', |
| artifacts: mockArtifacts, |
| fileSystem: mockFileSystem, |
| logger: logger, |
| platform: macPlatform, |
| iosDeploy: mockIosDeploy, |
| cpuArchitecture: DarwinArch.arm64, |
| ); |
| when(mockIosDeploy.installApp( |
| deviceId: device.id, |
| bundlePath: anyNamed('bundlePath'), |
| launchArguments: <String>[], |
| )).thenAnswer((Invocation invocation) => Future<int>.value(0)); |
| when(mockIosDeploy.runApp( |
| deviceId: device.id, |
| bundlePath: anyNamed('bundlePath'), |
| launchArguments: anyNamed('launchArguments'), |
| )).thenAnswer((Invocation invocation) => Future<int>.value(0)); |
| device.portForwarder = mockPortForwarder; |
| device.setLogReader(mockApp, mockLogReader); |
| final Uri uri = Uri( |
| scheme: 'http', |
| host: '127.0.0.1', |
| port: 1234, |
| path: 'observatory', |
| ); |
| when(mockMDnsObservatoryDiscovery.getObservatoryUri(any, any, usesIpv6: anyNamed('usesIpv6'))) |
| .thenAnswer((Invocation invocation) => Future<Uri>.value(uri)); |
| |
| final LaunchResult launchResult = await device.startApp(mockApp, |
| prebuiltApplication: true, |
| debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)), |
| platformArgs: <String, dynamic>{}, |
| ); |
| verify(mockUsage.sendEvent('ios-handshake', 'mdns-success')).called(1); |
| expect(launchResult.started, isTrue); |
| expect(launchResult.hasObservatory, isTrue); |
| expect(await device.stopApp(mockApp), isFalse); |
| }, overrides: <Type, Generator>{ |
| MDnsObservatoryDiscovery: () => mockMDnsObservatoryDiscovery, |
| Usage: () => mockUsage, |
| }); |
| |
| testUsingContext('succeeds in debug mode when mDNS fails by falling back to manual protocol discovery', () async { |
| final IOSDevice device = IOSDevice( |
| '123', |
| artifacts: mockArtifacts, |
| fileSystem: mockFileSystem, |
| logger: logger, |
| platform: macPlatform, |
| iosDeploy: mockIosDeploy, |
| name: 'iPhone 1', |
| sdkVersion: '13.3', |
| cpuArchitecture: DarwinArch.arm64, |
| ); |
| when( |
| mockIosDeploy.installApp(deviceId: device.id, bundlePath: anyNamed('bundlePath'), launchArguments: <String>[]) |
| ).thenAnswer((Invocation invocation) => Future<int>.value(0)); |
| when(mockIosDeploy.runApp( |
| deviceId: device.id, |
| bundlePath: anyNamed('bundlePath'), |
| launchArguments: anyNamed('launchArguments'), |
| )).thenAnswer((Invocation invocation) => Future<int>.value(0)); |
| device.portForwarder = mockPortForwarder; |
| device.setLogReader(mockApp, mockLogReader); |
| // Now that the reader is used, start writing messages to it. |
| Timer.run(() { |
| mockLogReader.addLine('Foo'); |
| mockLogReader.addLine('Observatory listening on http://127.0.0.1:$devicePort'); |
| }); |
| when(mockMDnsObservatoryDiscovery.getObservatoryUri(any, any, usesIpv6: anyNamed('usesIpv6'))) |
| .thenAnswer((Invocation invocation) => Future<Uri>.value(null)); |
| |
| final LaunchResult launchResult = await device.startApp(mockApp, |
| prebuiltApplication: true, |
| debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)), |
| platformArgs: <String, dynamic>{}, |
| ); |
| expect(launchResult.started, isTrue); |
| expect(launchResult.hasObservatory, isTrue); |
| verify(mockUsage.sendEvent('ios-handshake', 'mdns-failure')).called(1); |
| verify(mockUsage.sendEvent('ios-handshake', 'fallback-success')).called(1); |
| expect(await device.stopApp(mockApp), isFalse); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => mockFileSystem, |
| MDnsObservatoryDiscovery: () => mockMDnsObservatoryDiscovery, |
| ProcessManager: () => mockProcessManager, |
| Usage: () => mockUsage, |
| }); |
| |
| testUsingContext('fails in debug mode when mDNS fails and when Observatory URI is malformed', () async { |
| final IOSDevice device = IOSDevice( |
| '123', |
| artifacts: mockArtifacts, |
| fileSystem: mockFileSystem, |
| logger: logger, |
| platform: macPlatform, |
| iosDeploy: mockIosDeploy, |
| name: 'iPhone 1', |
| sdkVersion: '13.3', |
| cpuArchitecture: DarwinArch.arm64, |
| ); |
| when(mockIosDeploy.installApp( |
| deviceId: device.id, |
| bundlePath: anyNamed('bundlePath'), |
| launchArguments: <String>[], |
| )).thenAnswer((Invocation invocation) => Future<int>.value(0)); |
| when(mockIosDeploy.runApp( |
| deviceId: device.id, |
| bundlePath: anyNamed('bundlePath'), |
| launchArguments: anyNamed('launchArguments'), |
| )).thenAnswer((Invocation invocation) => Future<int>.value(0)); |
| device.portForwarder = mockPortForwarder; |
| device.setLogReader(mockApp, mockLogReader); |
| |
| // Now that the reader is used, start writing messages to it. |
| Timer.run(() { |
| mockLogReader.addLine('Foo'); |
| mockLogReader.addLine('Observatory listening on http:/:/127.0.0.1:$devicePort'); |
| }); |
| when(mockMDnsObservatoryDiscovery.getObservatoryUri(any, any, usesIpv6: anyNamed('usesIpv6'))) |
| .thenAnswer((Invocation invocation) => Future<Uri>.value(null)); |
| |
| final LaunchResult launchResult = await device.startApp(mockApp, |
| prebuiltApplication: true, |
| debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)), |
| platformArgs: <String, dynamic>{}, |
| ); |
| expect(launchResult.started, isFalse); |
| expect(launchResult.hasObservatory, isFalse); |
| verify(mockUsage.sendEvent( |
| 'ios-handshake', |
| 'failure-other', |
| label: anyNamed('label'), |
| value: anyNamed('value'), |
| )).called(1); |
| verify(mockUsage.sendEvent('ios-handshake', 'mdns-failure')).called(1); |
| verify(mockUsage.sendEvent('ios-handshake', 'fallback-failure')).called(1); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => mockFileSystem, |
| MDnsObservatoryDiscovery: () => mockMDnsObservatoryDiscovery, |
| ProcessManager: () => mockProcessManager, |
| Usage: () => mockUsage, |
| }); |
| |
| testUsingContext('succeeds in release mode', () async { |
| final IOSDevice device = IOSDevice( |
| '123', |
| name: 'iPhone 1', |
| fileSystem: mockFileSystem, |
| sdkVersion: '13.3', |
| cpuArchitecture: DarwinArch.arm64, |
| logger: logger, |
| platform: mockPlatform, |
| artifacts: mockArtifacts, |
| iosDeploy: mockIosDeploy, |
| ); |
| when(mockIosDeploy.installApp( |
| deviceId: device.id, |
| bundlePath: anyNamed('bundlePath'), |
| launchArguments: <String>[], |
| )).thenAnswer((Invocation invocation) => Future<int>.value(0)); |
| when(mockIosDeploy.runApp( |
| deviceId: device.id, |
| bundlePath: anyNamed('bundlePath'), |
| launchArguments: anyNamed('launchArguments'), |
| )).thenAnswer((Invocation invocation) => Future<int>.value(0)); |
| final LaunchResult launchResult = await device.startApp(mockApp, |
| prebuiltApplication: true, |
| debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null, treeShakeIcons: false)), |
| platformArgs: <String, dynamic>{}, |
| ); |
| verify(mockIosDeploy.installApp( |
| deviceId: device.id, |
| bundlePath: anyNamed('bundlePath'), |
| launchArguments: <String>[], |
| )); |
| verify(mockIosDeploy.runApp( |
| deviceId: device.id, |
| bundlePath: anyNamed('bundlePath'), |
| launchArguments: anyNamed('launchArguments'), |
| )); |
| expect(launchResult.started, isTrue); |
| expect(launchResult.hasObservatory, isFalse); |
| expect(await device.stopApp(mockApp), isFalse); |
| }); |
| |
| testUsingContext('trace whitelist flags', () async { |
| final IOSDevice device = IOSDevice( |
| '123', |
| name: 'iPhone 1', |
| fileSystem: mockFileSystem, |
| sdkVersion: '13.3', |
| cpuArchitecture: DarwinArch.arm64, |
| logger: logger, |
| platform: mockPlatform, |
| artifacts: mockArtifacts, |
| iosDeploy: mockIosDeploy, |
| ); |
| when(mockIosDeploy.installApp( |
| deviceId: device.id, |
| bundlePath: anyNamed('bundlePath'), |
| launchArguments: <String>[], |
| )).thenAnswer((Invocation invocation) => Future<int>.value(0)); |
| when(mockIosDeploy.runApp( |
| deviceId: device.id, |
| bundlePath: anyNamed('bundlePath'), |
| launchArguments: anyNamed('launchArguments'), |
| )).thenAnswer((Invocation invocation) => Future<int>.value(0)); |
| await device.startApp(mockApp, |
| prebuiltApplication: true, |
| debuggingOptions: DebuggingOptions.disabled( |
| const BuildInfo(BuildMode.release, null, treeShakeIcons: false), |
| traceWhitelist: 'foo'), |
| platformArgs: <String, dynamic>{}, |
| ); |
| final VerificationResult toVerify = verify(mockIosDeploy.runApp( |
| deviceId: device.id, |
| bundlePath: anyNamed('bundlePath'), |
| launchArguments: captureAnyNamed('launchArguments'), |
| )); |
| expect(toVerify.captured[0], contains('--trace-whitelist="foo"')); |
| await device.stopApp(mockApp); |
| }); |
| |
| testUsingContext('succeeds with --cache-sksl', () async { |
| final IOSDevice device = IOSDevice( |
| '123', |
| name: 'iPhone 1', |
| sdkVersion: '13.3', |
| artifacts: mockArtifacts, |
| fileSystem: mockFileSystem, |
| logger: logger, |
| platform: macPlatform, |
| iosDeploy: mockIosDeploy, |
| cpuArchitecture: DarwinArch.arm64, |
| ); |
| device.setLogReader(mockApp, mockLogReader); |
| final Uri uri = Uri( |
| scheme: 'http', |
| host: '127.0.0.1', |
| port: 1234, |
| path: 'observatory', |
| ); |
| when(mockMDnsObservatoryDiscovery.getObservatoryUri(any, any, usesIpv6: anyNamed('usesIpv6'))) |
| .thenAnswer((Invocation invocation) => Future<Uri>.value(uri)); |
| |
| List<String> args; |
| when(mockIosDeploy.runApp( |
| deviceId: anyNamed('deviceId'), |
| bundlePath: anyNamed('bundlePath'), |
| launchArguments: anyNamed('launchArguments'), |
| )).thenAnswer((Invocation inv) { |
| args = inv.namedArguments[const Symbol('launchArguments')] as List<String>; |
| return Future<int>.value(0); |
| }); |
| when(mockIosDeploy.installApp( |
| deviceId: device.id, |
| bundlePath: anyNamed('bundlePath'), |
| launchArguments: anyNamed('launchArguments'), |
| )).thenAnswer((Invocation invocation) => Future<int>.value(0)); |
| |
| final LaunchResult launchResult = await device.startApp(mockApp, |
| prebuiltApplication: true, |
| debuggingOptions: DebuggingOptions.enabled( |
| const BuildInfo(BuildMode.debug, null, treeShakeIcons: false), |
| cacheSkSL: true, |
| ), |
| platformArgs: <String, dynamic>{}, |
| ); |
| expect(launchResult.started, isTrue); |
| expect(args, contains('--cache-sksl')); |
| 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, |
| }); |
| |
| testUsingContext('succeeds with --device-vmservice-port', () async { |
| final IOSDevice device = IOSDevice( |
| '123', |
| name: 'iPhone 1', |
| sdkVersion: '13.3', |
| artifacts: mockArtifacts, |
| fileSystem: mockFileSystem, |
| logger: logger, |
| platform: macPlatform, |
| iosDeploy: mockIosDeploy, |
| cpuArchitecture: DarwinArch.arm64, |
| ); |
| device.setLogReader(mockApp, mockLogReader); |
| final Uri uri = Uri( |
| scheme: 'http', |
| host: '127.0.0.1', |
| port: 1234, |
| path: 'observatory', |
| ); |
| when(mockMDnsObservatoryDiscovery.getObservatoryUri(any, any, usesIpv6: anyNamed('usesIpv6'))) |
| .thenAnswer((Invocation invocation) => Future<Uri>.value(uri)); |
| |
| List<String> args; |
| when(mockIosDeploy.runApp( |
| deviceId: anyNamed('deviceId'), |
| bundlePath: anyNamed('bundlePath'), |
| launchArguments: anyNamed('launchArguments'), |
| )).thenAnswer((Invocation inv) { |
| args = inv.namedArguments[const Symbol('launchArguments')] as List<String>; |
| return Future<int>.value(0); |
| }); |
| |
| when(mockIosDeploy.installApp( |
| deviceId: device.id, |
| bundlePath: anyNamed('bundlePath'), |
| launchArguments: anyNamed('launchArguments'), |
| )).thenAnswer((Invocation invocation) => Future<int>.value(0)); |
| final LaunchResult launchResult = await device.startApp(mockApp, |
| prebuiltApplication: true, |
| debuggingOptions: DebuggingOptions.enabled( |
| const BuildInfo(BuildMode.debug, null, treeShakeIcons: false), |
| deviceVmServicePort: 8181, |
| ), |
| platformArgs: <String, dynamic>{}, |
| ); |
| expect(launchResult.started, isTrue); |
| expect(args, contains('--observatory-port=8181')); |
| expect(await device.stopApp(mockApp), isFalse); |
| }, overrides: <Type, Generator>{ |
| Cache: () => mockCache, |
| MDnsObservatoryDiscovery: () => mockMDnsObservatoryDiscovery, |
| ProcessManager: () => mockProcessManager, |
| Usage: () => mockUsage, |
| }); |
| |
| void testNonPrebuilt( |
| String name, { |
| @required bool showBuildSettingsFlakes, |
| void Function() additionalSetup, |
| void Function() additionalExpectations, |
| }) { |
| testUsingContext('non-prebuilt succeeds in debug mode $name', () async { |
| final Directory targetBuildDir = |
| projectDir.childDirectory('build/ios/iphoneos/Debug-arm64'); |
| |
| // The -showBuildSettings calls have a timeout and so go through |
| // globals.processManager.start(). |
| mockProcessManager.processFactory = flakyProcessFactory( |
| flakes: showBuildSettingsFlakes ? 1 : 0, |
| delay: const Duration(seconds: 62), |
| filter: (List<String> args) => args.contains('-showBuildSettings'), |
| stdout: |
| () => Stream<String> |
| .fromIterable( |
| <String>['TARGET_BUILD_DIR = ${targetBuildDir.path}\n']) |
| .transform(utf8.encoder), |
| ); |
| |
| // Make all other subcommands succeed. |
| when(mockProcessManager.run( |
| any, |
| workingDirectory: anyNamed('workingDirectory'), |
| environment: anyNamed('environment'), |
| )).thenAnswer((Invocation inv) { |
| return Future<ProcessResult>.value(ProcessResult(0, 0, '', '')); |
| }); |
| |
| when(mockProcessManager.run( |
| argThat(contains('find-identity')), |
| environment: anyNamed('environment'), |
| workingDirectory: anyNamed('workingDirectory'), |
| )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult( |
| 1, // pid |
| 0, // exitCode |
| ''' |
| 1) 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 "iPhone Developer: Profile 1 (1111AAAA11)" |
| 2) da4b9237bacccdf19c0760cab7aec4a8359010b0 "iPhone Developer: Profile 2 (2222BBBB22)" |
| 3) 5bf1fd927dfb8679496a2e6cf00cbe50c1c87145 "iPhone Developer: Profile 3 (3333CCCC33)" |
| 3 valid identities found''', |
| '', |
| ))); |
| |
| // Deploy works. |
| when(mockIosDeploy.runApp( |
| deviceId: anyNamed('deviceId'), |
| bundlePath: anyNamed('bundlePath'), |
| launchArguments: anyNamed('launchArguments'), |
| )).thenAnswer((_) => Future<int>.value(0)); |
| |
| // Create a dummy project to avoid mocking out the whole directory |
| // structure expected by device.startApp(). |
| Cache.flutterRoot = '../..'; |
| final CreateCommand command = CreateCommand(); |
| final CommandRunner<void> runner = createTestCommandRunner(command); |
| await runner.run(<String>[ |
| 'create', |
| '--no-pub', |
| projectDir.path, |
| ]); |
| |
| if (additionalSetup != null) { |
| additionalSetup(); |
| } |
| |
| final IOSApp app = await AbsoluteBuildableIOSApp.fromProject( |
| FlutterProject.fromDirectory(projectDir).ios); |
| final IOSDevice device = IOSDevice( |
| '123', |
| name: 'iPhone 1', |
| sdkVersion: '13.3', |
| artifacts: mockArtifacts, |
| fileSystem: globals.fs, |
| logger: testLogger, |
| platform: macPlatform, |
| iosDeploy: mockIosDeploy, |
| cpuArchitecture: DarwinArch.arm64, |
| ); |
| |
| // Pre-create the expected build products. |
| targetBuildDir.createSync(recursive: true); |
| projectDir.childDirectory('build/ios/iphoneos/Runner.app').createSync(recursive: true); |
| |
| final Completer<LaunchResult> completer = Completer<LaunchResult>(); |
| FakeAsync().run((FakeAsync time) { |
| device.startApp( |
| app, |
| prebuiltApplication: false, |
| debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)), |
| platformArgs: <String, dynamic>{}, |
| ).then((LaunchResult result) { |
| completer.complete(result); |
| }); |
| time.flushMicrotasks(); |
| time.elapse(const Duration(seconds: 65)); |
| }); |
| final LaunchResult launchResult = await completer.future; |
| expect(launchResult.started, isTrue); |
| expect(launchResult.hasObservatory, isFalse); |
| expect(await device.stopApp(mockApp), isFalse); |
| |
| if (additionalExpectations != null) { |
| additionalExpectations(); |
| } |
| }, overrides: <Type, Generator>{ |
| DoctorValidatorsProvider: () => FakeIosDoctorProvider(), |
| IMobileDevice: () => mockIMobileDevice, |
| Platform: () => macPlatform, |
| ProcessManager: () => mockProcessManager, |
| }); |
| } |
| |
| testNonPrebuilt('flaky: false', showBuildSettingsFlakes: false); |
| testNonPrebuilt('flaky: true', showBuildSettingsFlakes: true); |
| testNonPrebuilt('with concurrent build failiure', |
| showBuildSettingsFlakes: false, |
| additionalSetup: () { |
| int callCount = 0; |
| when(mockProcessManager.run( |
| argThat(allOf( |
| contains('xcodebuild'), |
| contains('-configuration'), |
| contains('Debug'), |
| )), |
| workingDirectory: anyNamed('workingDirectory'), |
| environment: anyNamed('environment'), |
| )).thenAnswer((Invocation inv) { |
| // Succeed after 2 calls. |
| if (++callCount > 2) { |
| return Future<ProcessResult>.value(ProcessResult(0, 0, '', '')); |
| } |
| // Otherwise fail with the Xcode concurrent error. |
| return Future<ProcessResult>.value(ProcessResult( |
| 0, |
| 1, |
| ''' |
| "/Developer/Xcode/DerivedData/foo/XCBuildData/build.db": |
| database is locked |
| Possibly there are two concurrent builds running in the same filesystem location. |
| ''', |
| '', |
| )); |
| }); |
| }, |
| additionalExpectations: () { |
| expect(testLogger.statusText, contains('will retry in 2 seconds')); |
| expect(testLogger.statusText, contains('will retry in 4 seconds')); |
| expect(testLogger.statusText, contains('Xcode build done.')); |
| }, |
| ); |
| }); |
| |
| group('Process calls', () { |
| const String bundlePath = '/path/to/bundle'; |
| FileSystem fs; |
| MockDirectory directory; |
| MockIOSApp mockApp; |
| MockArtifacts mockArtifacts; |
| MockCache mockCache; |
| MockFileSystem mockFileSystem; |
| MockProcessManager mockProcessManager; |
| Logger logger; |
| MockPlatform mockPlatform; |
| 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', |
| ); |
| IOSDeploy iosDeploy; |
| |
| setUp(() { |
| mockFileSystem = MockFileSystem(); |
| directory = MockDirectory(); |
| when(mockFileSystem.directory(bundlePath)).thenReturn(directory); |
| |
| mockApp = MockIOSApp(); |
| when(mockApp.id).thenReturn(appId); |
| when(mockApp.deviceBundlePath).thenReturn(bundlePath); |
| when(directory.existsSync()).thenReturn(true); |
| when(directory.path).thenReturn(bundlePath); |
| |
| mockArtifacts = MockArtifacts(); |
| mockCache = MockCache(); |
| logger = BufferLogger.test(); |
| mockPlatform = MockPlatform(); |
| when(mockPlatform.environment).thenReturn(<String, String>{}); |
| when(mockPlatform.isMacOS).thenReturn(true); |
| when( |
| mockArtifacts.getArtifactPath( |
| Artifact.iosDeploy, |
| platform: anyNamed('platform'), |
| ), |
| ).thenReturn(iosDeployPath); |
| mockProcessManager = MockProcessManager(); |
| iosDeploy = IOSDeploy( |
| artifacts: mockArtifacts, |
| cache: mockCache, |
| logger: logger, |
| platform: mockPlatform, |
| processManager: mockProcessManager, |
| ); |
| when(mockCache.dyLdLibEntry).thenReturn(libraryEntry); |
| mockFileSystem = MockFileSystem(); |
| final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); |
| when(mockFileSystem.currentDirectory) |
| .thenReturn(memoryFileSystem.currentDirectory); |
| }); |
| |
| 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( |
| deviceId, |
| name: 'iPhone 1', |
| fileSystem: mockFileSystem, |
| sdkVersion: '13.3', |
| cpuArchitecture: DarwinArch.arm64, |
| logger: logger, |
| platform: mockPlatform, |
| artifacts: mockArtifacts, |
| iosDeploy: iosDeploy, |
| ); |
| |
| await device.installApp(mockApp); |
| }); |
| |
| 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( |
| deviceId, |
| name: 'iPhone 1', |
| fileSystem: fs, |
| sdkVersion: '13.3', |
| cpuArchitecture: DarwinArch.arm64, |
| logger: logger, |
| platform: mockPlatform, |
| artifacts: mockArtifacts, |
| iosDeploy: iosDeploy, |
| ); |
| |
| await device.uninstallApp(mockApp); |
| }); |
| }); |
| }); |
| |
| group('pollingGetDevices', () { |
| MockXcdevice mockXcdevice; |
| MockArtifacts mockArtifacts; |
| MockCache mockCache; |
| MockFileSystem mockFileSystem; |
| FakeProcessManager fakeProcessManager; |
| Logger logger; |
| IOSDeploy iosDeploy; |
| IOSWorkflow mockIosWorkflow; |
| |
| setUp(() { |
| mockXcdevice = MockXcdevice(); |
| mockArtifacts = MockArtifacts(); |
| mockCache = MockCache(); |
| logger = BufferLogger.test(); |
| mockFileSystem = MockFileSystem(); |
| mockIosWorkflow = MockIOSWorkflow(); |
| fakeProcessManager = FakeProcessManager.any(); |
| iosDeploy = IOSDeploy( |
| artifacts: mockArtifacts, |
| cache: mockCache, |
| logger: logger, |
| platform: macPlatform, |
| 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 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( |
| 'd83d5bc53967baa0ee18626ba87b6254b2ab5418', |
| name: 'Paired iPhone', |
| sdkVersion: '13.3', |
| 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<Device> devices = await iosDevices.pollingGetDevices(); |
| expect(devices, hasLength(1)); |
| expect(identical(devices.first, device), isTrue); |
| }); |
| }); |
| |
| 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 iosDevices.getDiagnostics()).first, 'Control of iOS devices or simulators only supported on macOS.'); |
| }); |
| } |
| |
| 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 iosDevices.getDiagnostics(); |
| expect(diagnostics, hasLength(1)); |
| expect(diagnostics.first, 'Generic pairing error'); |
| }); |
| }); |
| |
| group('decodeSyslog', () { |
| 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 ¯\_(ツ)_/¯ 솠!'); |
| }); |
| |
| 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!'); |
| }); |
| }); |
| |
| group('logging', () { |
| MockIMobileDevice mockIMobileDevice; |
| MockIosProject mockIosProject; |
| MockArtifacts mockArtifacts; |
| MockCache mockCache; |
| MockFileSystem mockFileSystem; |
| FakeProcessManager fakeProcessManager; |
| Logger logger; |
| IOSDeploy iosDeploy; |
| |
| setUp(() { |
| mockIMobileDevice = MockIMobileDevice(); |
| mockIosProject = MockIosProject(); |
| mockArtifacts = MockArtifacts(); |
| mockCache = MockCache(); |
| logger = BufferLogger.test(); |
| mockFileSystem = MockFileSystem(); |
| fakeProcessManager = FakeProcessManager.any(); |
| iosDeploy = IOSDeploy( |
| artifacts: mockArtifacts, |
| cache: mockCache, |
| logger: logger, |
| platform: macPlatform, |
| processManager: fakeProcessManager, |
| ); |
| }); |
| |
| testUsingContext('suppresses non-Flutter lines from output', () async { |
| when(mockIMobileDevice.startLogger('123456')).thenAnswer((Invocation invocation) { |
| final Process mockProcess = MockProcess( |
| stdout: Stream<List<int>>.fromIterable(<List<int>>[''' |
| Runner(Flutter)[297] <Notice>: A is for ari |
| Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt MobileGestaltSupport.m:153: pid 123 (Runner) does not have sandbox access for frZQaeyWLUvLjeuEK43hmg and IS NOT appropriately entitled |
| Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt MobileGestalt.c:550: no access to InverseDeviceID (see <rdar://problem/11744455>) |
| Runner(Flutter)[297] <Notice>: I is for ichigo |
| Runner(UIKit)[297] <Notice>: E is for enpitsu" |
| '''.codeUnits]) |
| ); |
| return Future<Process>.value(mockProcess); |
| }); |
| |
| final IOSDevice device = IOSDevice( |
| '123456', |
| name: 'iPhone 1', |
| sdkVersion: '10.3', |
| cpuArchitecture: DarwinArch.arm64, |
| artifacts: mockArtifacts, |
| iosDeploy: iosDeploy, |
| logger: logger, |
| platform: macPlatform, |
| fileSystem: mockFileSystem, |
| ); |
| final DeviceLogReader logReader = device.getLogReader( |
| app: await BuildableIOSApp.fromProject(mockIosProject), |
| ); |
| |
| final List<String> lines = await logReader.logLines.toList(); |
| expect(lines, <String>['A is for ari', 'I is for ichigo']); |
| }, overrides: <Type, Generator>{ |
| IMobileDevice: () => mockIMobileDevice, |
| }); |
| |
| testUsingContext('includes multi-line Flutter logs in the output', () async { |
| when(mockIMobileDevice.startLogger('123456')).thenAnswer((Invocation invocation) { |
| final Process mockProcess = MockProcess( |
| stdout: Stream<List<int>>.fromIterable(<List<int>>[''' |
| Runner(Flutter)[297] <Notice>: This is a multi-line message, |
| with another Flutter message following it. |
| Runner(Flutter)[297] <Notice>: This is a multi-line message, |
| with a non-Flutter log message following it. |
| Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt |
| '''.codeUnits]), |
| ); |
| return Future<Process>.value(mockProcess); |
| }); |
| |
| final IOSDevice device = IOSDevice( |
| '123456', |
| name: 'iPhone 1', |
| sdkVersion: '10.3', |
| cpuArchitecture: DarwinArch.arm64, |
| artifacts: mockArtifacts, |
| iosDeploy: iosDeploy, |
| logger: logger, |
| platform: macPlatform, |
| fileSystem: mockFileSystem, |
| ); |
| final DeviceLogReader logReader = device.getLogReader( |
| app: await BuildableIOSApp.fromProject(mockIosProject), |
| ); |
| |
| final List<String> lines = await logReader.logLines.toList(); |
| expect(lines, <String>[ |
| 'This is a multi-line message,', |
| ' with another Flutter message following it.', |
| 'This is a multi-line message,', |
| ' with a non-Flutter log message following it.', |
| ]); |
| expect(device.category, Category.mobile); |
| }, overrides: <Type, Generator>{ |
| IMobileDevice: () => mockIMobileDevice, |
| }); |
| }); |
| |
| group('isSupportedForProject', () { |
| Artifacts mockArtifacts; |
| 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, |
| ); |
| }); |
| |
| testUsingContext('is true on module project', () async { |
| fileSystem.file('pubspec.yaml') |
| ..createSync() |
| ..writeAsStringSync(r''' |
| name: example |
| |
| flutter: |
| module: {} |
| '''); |
| fileSystem.file('.packages').createSync(); |
| final FlutterProject flutterProject = FlutterProject.current(); |
| |
| final IOSDevice device = IOSDevice( |
| 'test', |
| artifacts: mockArtifacts, |
| fileSystem: fileSystem, |
| iosDeploy: iosDeploy, |
| logger: logger, |
| platform: macPlatform, |
| name: 'iPhone 1', |
| sdkVersion: '13.3', |
| cpuArchitecture: DarwinArch.arm64 |
| ); |
| expect(device.isSupportedForProject(flutterProject), true); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => fakeProcessManager, |
| }); |
| |
| testUsingContext('is true with editable host app', () async { |
| 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: fileSystem, |
| iosDeploy: iosDeploy, |
| logger: logger, |
| platform: macPlatform, |
| name: 'iPhone 1', |
| sdkVersion: '13.3', |
| cpuArchitecture: DarwinArch.arm64, |
| ); |
| expect(device.isSupportedForProject(flutterProject), true); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => fileSystem, |
| ProcessManager: () => fakeProcessManager, |
| }); |
| |
| testUsingContext('is false with no host app and no module', () async { |
| fileSystem.file('pubspec.yaml').createSync(); |
| fileSystem.file('.packages').createSync(); |
| final FlutterProject flutterProject = FlutterProject.current(); |
| |
| final IOSDevice device = IOSDevice( |
| 'test', |
| artifacts: mockArtifacts, |
| fileSystem: fileSystem, |
| iosDeploy: iosDeploy, |
| logger: logger, |
| platform: macPlatform, |
| name: 'iPhone 1', |
| sdkVersion: '13.3', |
| cpuArchitecture: DarwinArch.arm64, |
| ); |
| expect(device.isSupportedForProject(flutterProject), false); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => MemoryFileSystem(), |
| ProcessManager: () => fakeProcessManager, |
| }); |
| }); |
| } |
| |
| class AbsoluteBuildableIOSApp extends BuildableIOSApp { |
| AbsoluteBuildableIOSApp(IosProject project, String projectBundleId) : |
| super(project, projectBundleId); |
| |
| static Future<AbsoluteBuildableIOSApp> fromProject(IosProject project) async { |
| final String projectBundleId = await project.productBundleIdentifier; |
| return AbsoluteBuildableIOSApp(project, projectBundleId); |
| } |
| |
| @override |
| String get deviceBundlePath => |
| globals.fs.path.join(project.parent.directory.path, 'build', 'ios', 'iphoneos', name); |
| |
| } |
| |
| class FakeIosDoctorProvider implements DoctorValidatorsProvider { |
| List<Workflow> _workflows; |
| |
| @override |
| List<DoctorValidator> get validators => <DoctorValidator>[]; |
| |
| @override |
| List<Workflow> get workflows { |
| if (_workflows == null) { |
| _workflows = <Workflow>[]; |
| if (globals.iosWorkflow.appliesToHostPlatform) { |
| _workflows.add(globals.iosWorkflow); |
| } |
| } |
| return _workflows; |
| } |
| } |
| |
| |
| class MockIOSApp extends Mock implements IOSApp {} |
| class MockApplicationPackage extends Mock implements ApplicationPackage {} |
| class MockArtifacts extends Mock implements Artifacts {} |
| class MockCache extends Mock implements Cache {} |
| class MockDevicePortForwarder extends Mock implements DevicePortForwarder {} |
| class MockDirectory extends Mock implements Directory {} |
| class MockFile extends Mock implements File {} |
| class MockFileSystem extends Mock implements FileSystem {} |
| class MockForwardedPort extends Mock implements ForwardedPort {} |
| class MockIMobileDevice extends Mock implements IMobileDevice {} |
| class MockIOSDeploy extends Mock implements IOSDeploy {} |
| class 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 {} |
| class MockStatus extends Mock implements Status {} |
| class MockUsage extends Mock implements Usage {} |
| class MockXcdevice extends Mock implements XCDevice {} |
| class MockXcode extends Mock implements Xcode {} |