// 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 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:mockito/mockito.dart'; import 'package:platform/platform.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/device.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 '../../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 unsupportedPlatforms = [linuxPlatform, windowsPlatform]; Artifacts mockArtifacts; MockCache mockCache; Logger logger; IOSDeploy iosDeploy; IMobileDevice iMobileDevice; FileSystem mockFileSystem; setUp(() { mockArtifacts = MockArtifacts(); mockCache = MockCache(); const MapEntry dyLdLibEntry = MapEntry('DYLD_LIBRARY_PATH', '/path/to/libs'); when(mockCache.dyLdLibEntry).thenReturn(dyLdLibEntry); logger = BufferLogger.test(); iosDeploy = IOSDeploy( artifacts: mockArtifacts, cache: mockCache, logger: logger, platform: macPlatform, processManager: FakeProcessManager.any(), ); iMobileDevice = IMobileDevice( artifacts: mockArtifacts, cache: mockCache, logger: logger, processManager: FakeProcessManager.any(), ); }); testWithoutContext('successfully instantiates on Mac OS', () { IOSDevice( 'device-123', artifacts: mockArtifacts, fileSystem: mockFileSystem, logger: logger, platform: macPlatform, iosDeploy: iosDeploy, iMobileDevice: iMobileDevice, 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, iMobileDevice: iMobileDevice, 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, iMobileDevice: iMobileDevice, 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, iMobileDevice: iMobileDevice, name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '10' ).majorSdkVersion, 10); expect(IOSDevice( 'device-123', artifacts: mockArtifacts, fileSystem: mockFileSystem, logger: logger, platform: macPlatform, iosDeploy: iosDeploy, iMobileDevice: iMobileDevice, name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '0' ).majorSdkVersion, 0); expect(IOSDevice( 'device-123', artifacts: mockArtifacts, fileSystem: mockFileSystem, logger: logger, platform: macPlatform, iosDeploy: iosDeploy, iMobileDevice: iMobileDevice, 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, iMobileDevice: iMobileDevice, name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64, ); }, throwsAssertionError, ); }); } 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]); return portForwarder; } IOSDeviceLogReader createLogReader( IOSDevice device, IOSApp appPackage, Process process) { final IOSDeviceLogReader logReader = IOSDeviceLogReader.create( device: device, app: appPackage, iMobileDevice: null, // not used by this test. ); 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(); 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, iMobileDevice: iMobileDevice, 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('pollingGetDevices', () { MockXcdevice mockXcdevice; MockArtifacts mockArtifacts; MockCache mockCache; FakeProcessManager fakeProcessManager; Logger logger; IOSDeploy iosDeploy; IMobileDevice iMobileDevice; IOSWorkflow mockIosWorkflow; setUp(() { mockXcdevice = MockXcdevice(); mockArtifacts = MockArtifacts(); mockCache = MockCache(); logger = BufferLogger.test(); mockIosWorkflow = MockIOSWorkflow(); fakeProcessManager = FakeProcessManager.any(); iosDeploy = IOSDeploy( artifacts: mockArtifacts, cache: mockCache, logger: logger, platform: macPlatform, processManager: fakeProcessManager, ); iMobileDevice = IMobileDevice( artifacts: mockArtifacts, cache: mockCache, processManager: fakeProcessManager, logger: logger, ); }); final List unsupportedPlatforms = [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()), ); }); } 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, iMobileDevice: iMobileDevice, logger: logger, platform: macPlatform, fileSystem: MemoryFileSystem.test(), ); when(mockXcdevice.getAvailableTetheredIOSDevices()) .thenAnswer((Invocation invocation) => Future>.value([device])); final List 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 unsupportedPlatforms = [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>.value(['Generic pairing error'])); final List diagnostics = await iosDevices.getDiagnostics(); expect(diagnostics, hasLength(1)); expect(diagnostics.first, 'Generic pairing error'); }); }); } class MockIOSApp extends Mock implements IOSApp {} class MockArtifacts extends Mock implements Artifacts {} class MockCache extends Mock implements Cache {} class MockIMobileDevice extends Mock implements IMobileDevice {} class MockIOSDeploy extends Mock implements IOSDeploy {} class MockIOSWorkflow extends Mock implements IOSWorkflow {} class MockXcdevice extends Mock implements XCDevice {}