// 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: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/base/platform.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/ios_deploy.dart'; import 'package:flutter_tools/src/ios/iproxy.dart'; import 'package:flutter_tools/src/ios/mac.dart'; import 'package:meta/meta.dart'; import 'package:mockito/mockito.dart'; import 'package:vm_service/vm_service.dart'; import '../../src/common.dart'; import '../../src/context.dart'; import '../../src/fakes.dart'; const Map<String, String> kDyLdLibEntry = <String, String>{ 'DYLD_LIBRARY_PATH': '/path/to/libraries', }; void main() { Artifacts artifacts; String iosDeployPath; setUp(() { artifacts = Artifacts.test(); iosDeployPath = artifacts.getArtifactPath(Artifact.iosDeploy, platform: TargetPlatform.ios); }); testWithoutContext('IOSDevice.installApp calls ios-deploy correctly with USB', () async { final FileSystem fileSystem = MemoryFileSystem.test(); final IOSApp iosApp = PrebuiltIOSApp( projectBundleId: 'app', bundleDir: fileSystem.currentDirectory, ); final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ FakeCommand(command: <String>[ iosDeployPath, '--id', '1234', '--bundle', '/', '--no-wifi', ], environment: const <String, String>{ 'PATH': '/usr/bin:null', ...kDyLdLibEntry, }) ]); final IOSDevice device = setUpIOSDevice( processManager: processManager, fileSystem: fileSystem, interfaceType: IOSDeviceInterface.usb, artifacts: artifacts, ); final bool wasInstalled = await device.installApp(iosApp); expect(wasInstalled, true); expect(processManager.hasRemainingExpectations, false); }); testWithoutContext('IOSDevice.installApp calls ios-deploy correctly with network', () async { final FileSystem fileSystem = MemoryFileSystem.test(); final IOSApp iosApp = PrebuiltIOSApp( projectBundleId: 'app', bundleDir: fileSystem.currentDirectory, ); final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ FakeCommand(command: <String>[ iosDeployPath, '--id', '1234', '--bundle', '/', ], environment: const <String, String>{ 'PATH': '/usr/bin:null', ...kDyLdLibEntry, }) ]); final IOSDevice device = setUpIOSDevice( processManager: processManager, fileSystem: fileSystem, interfaceType: IOSDeviceInterface.network, artifacts: artifacts, ); final bool wasInstalled = await device.installApp(iosApp); expect(wasInstalled, true); expect(processManager.hasRemainingExpectations, false); }); testWithoutContext('IOSDevice.uninstallApp calls ios-deploy correctly', () async { final IOSApp iosApp = PrebuiltIOSApp(projectBundleId: 'app'); final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ FakeCommand(command: <String>[ iosDeployPath, '--id', '1234', '--uninstall_only', '--bundle_id', 'app', ], environment: const <String, String>{ 'PATH': '/usr/bin:null', ...kDyLdLibEntry, }) ]); final IOSDevice device = setUpIOSDevice(processManager: processManager, artifacts: artifacts); final bool wasUninstalled = await device.uninstallApp(iosApp); expect(wasUninstalled, true); expect(processManager.hasRemainingExpectations, false); }); group('isAppInstalled', () { testWithoutContext('catches ProcessException from ios-deploy', () async { final IOSApp iosApp = PrebuiltIOSApp(projectBundleId: 'app'); final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ FakeCommand(command: <String>[ iosDeployPath, '--id', '1234', '--exists', '--timeout', '10', '--bundle_id', 'app', ], environment: const <String, String>{ 'PATH': '/usr/bin:null', ...kDyLdLibEntry, }, onRun: () { throw const ProcessException('ios-deploy', <String>[]); }) ]); final IOSDevice device = setUpIOSDevice(processManager: processManager, artifacts: artifacts); final bool isAppInstalled = await device.isAppInstalled(iosApp); expect(isAppInstalled, false); expect(processManager.hasRemainingExpectations, false); }); testWithoutContext('returns true when app is installed', () async { final IOSApp iosApp = PrebuiltIOSApp(projectBundleId: 'app'); final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ FakeCommand(command: <String>[ iosDeployPath, '--id', '1234', '--exists', '--timeout', '10', '--bundle_id', 'app', ], environment: const <String, String>{ 'PATH': '/usr/bin:null', ...kDyLdLibEntry, }, exitCode: 0) ]); final IOSDevice device = setUpIOSDevice(processManager: processManager, artifacts: artifacts); final bool isAppInstalled = await device.isAppInstalled(iosApp); expect(isAppInstalled, isTrue); expect(processManager.hasRemainingExpectations, false); }); testWithoutContext('returns false when app is not installed', () async { final IOSApp iosApp = PrebuiltIOSApp(projectBundleId: 'app'); final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ FakeCommand(command: <String>[ iosDeployPath, '--id', '1234', '--exists', '--timeout', '10', '--bundle_id', 'app', ], environment: const <String, String>{ 'PATH': '/usr/bin:null', ...kDyLdLibEntry, }, exitCode: 255) ]); final BufferLogger logger = BufferLogger.test(); final IOSDevice device = setUpIOSDevice(processManager: processManager, logger: logger, artifacts: artifacts); final bool isAppInstalled = await device.isAppInstalled(iosApp); expect(isAppInstalled, isFalse); expect(processManager.hasRemainingExpectations, false); expect(logger.traceText, contains('${iosApp.id} not installed on ${device.id}')); }); testWithoutContext('returns false on command timeout or other error', () async { final IOSApp iosApp = PrebuiltIOSApp(projectBundleId: 'app'); const String stderr = '2020-03-26 17:48:43.484 ios-deploy[21518:5501783] [ !! ] Timed out waiting for device'; final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ FakeCommand(command: <String>[ iosDeployPath, '--id', '1234', '--exists', '--timeout', '10', '--bundle_id', 'app', ], environment: const <String, String>{ 'PATH': '/usr/bin:null', ...kDyLdLibEntry, }, stderr: stderr, exitCode: 253) ]); final BufferLogger logger = BufferLogger.test(); final IOSDevice device = setUpIOSDevice(processManager: processManager, logger: logger, artifacts: artifacts); final bool isAppInstalled = await device.isAppInstalled(iosApp); expect(isAppInstalled, isFalse); expect(processManager.hasRemainingExpectations, false); expect(logger.traceText, contains(stderr)); }); }); testWithoutContext('IOSDevice.installApp catches ProcessException from ios-deploy', () async { final FileSystem fileSystem = MemoryFileSystem.test(); final IOSApp iosApp = PrebuiltIOSApp( projectBundleId: 'app', bundleDir: fileSystem.currentDirectory, ); final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ FakeCommand(command: <String>[ iosDeployPath, '--id', '1234', '--bundle', '/', '--no-wifi', ], environment: const <String, String>{ 'PATH': '/usr/bin:null', ...kDyLdLibEntry, }, onRun: () { throw const ProcessException('ios-deploy', <String>[]); }) ]); final IOSDevice device = setUpIOSDevice(processManager: processManager, artifacts: artifacts); final bool wasAppInstalled = await device.installApp(iosApp); expect(wasAppInstalled, false); }); testWithoutContext('IOSDevice.uninstallApp catches ProcessException from ios-deploy', () async { final IOSApp iosApp = PrebuiltIOSApp(projectBundleId: 'app'); final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ FakeCommand(command: <String>[ iosDeployPath, '--id', '1234', '--uninstall_only', '--bundle_id', 'app', ], environment: const <String, String>{ 'PATH': '/usr/bin:null', ...kDyLdLibEntry, }, onRun: () { throw const ProcessException('ios-deploy', <String>[]); }) ]); final IOSDevice device = setUpIOSDevice(processManager: processManager, artifacts: artifacts); final bool wasAppUninstalled = await device.uninstallApp(iosApp); expect(wasAppUninstalled, false); }); } IOSDevice setUpIOSDevice({ @required ProcessManager processManager, FileSystem fileSystem, Logger logger, IOSDeviceInterface interfaceType, Artifacts artifacts, }) { logger ??= BufferLogger.test(); final FakePlatform platform = FakePlatform( operatingSystem: 'macos', environment: <String, String>{}, ); artifacts ??= Artifacts.test(); final Cache cache = Cache.test( platform: platform, artifacts: <ArtifactSet>[ FakeDyldEnvironmentArtifact(), ], ); return IOSDevice( '1234', name: 'iPhone 1', logger: logger, fileSystem: fileSystem ?? MemoryFileSystem.test(), sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64, platform: platform, iMobileDevice: IMobileDevice( logger: logger, processManager: processManager, artifacts: artifacts, cache: cache, ), iosDeploy: IOSDeploy( logger: logger, platform: platform, processManager: processManager, artifacts: artifacts, cache: cache, ), iProxy: IProxy.test(logger: logger, processManager: processManager), interfaceType: interfaceType, vmServiceConnectUri: (String string, {Log log}) async => MockVmService(), ); } class MockVmService extends Mock implements VmService {}