diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index 6c4613d4edd..88015c116d7 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -383,7 +383,7 @@ class IOSDevice extends Device { @override DevicePortForwarder get portForwarder => _portForwarder ??= IOSDevicePortForwarder( processManager: globals.processManager, - logger: globals.logger, + logger: _logger, dyLdLibEntry: globals.cache.dyLdLibEntry, id: id, iproxyPath: _iproxyPath, diff --git a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart index 4f407158717..9d271893a5d 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart @@ -30,6 +30,7 @@ import 'package:flutter_tools/src/globals.dart' as globals; import '../../src/common.dart'; import '../../src/context.dart'; +import '../../src/fakes.dart'; import '../../src/mocks.dart'; @@ -54,7 +55,7 @@ void main() { const int devicePort = 499; const int hostPort = 42; - MockDeviceLogReader mockLogReader; + FakeDeviceLogReader mockLogReader; MockPortForwarder portForwarder; MockAndroidDevice device; MockProcessManager mockProcessManager; @@ -63,7 +64,7 @@ void main() { setUp(() { mockProcessManager = MockProcessManager(); - mockLogReader = MockDeviceLogReader(); + mockLogReader = FakeDeviceLogReader(); portForwarder = MockPortForwarder(); device = MockAndroidDevice(); vmServiceDoneCompleter = Completer(); @@ -374,7 +375,7 @@ void main() { testUsingContext('selects specified target', () async { const int devicePort = 499; const int hostPort = 42; - final MockDeviceLogReader mockLogReader = MockDeviceLogReader(); + final FakeDeviceLogReader mockLogReader = FakeDeviceLogReader(); final MockPortForwarder portForwarder = MockPortForwarder(); final MockAndroidDevice device = MockAndroidDevice(); final MockHotRunner mockHotRunner = MockHotRunner(); @@ -434,7 +435,7 @@ void main() { testUsingContext('fallbacks to protocol observatory if MDNS failed on iOS', () async { const int devicePort = 499; const int hostPort = 42; - final MockDeviceLogReader mockLogReader = MockDeviceLogReader(); + final FakeDeviceLogReader mockLogReader = FakeDeviceLogReader(); final MockPortForwarder portForwarder = MockPortForwarder(); final MockIOSDevice device = MockIOSDevice(); final MockHotRunner mockHotRunner = MockHotRunner(); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart index 2e04041488f..d694b5fbc7e 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart @@ -21,6 +21,7 @@ import 'package:webdriver/sync_io.dart' as sync_io; import '../../src/common.dart'; import '../../src/context.dart'; +import '../../src/fakes.dart'; import '../../src/mocks.dart'; void main() { @@ -351,7 +352,7 @@ void main() { final Device mockDevice = MockDevice(); testDeviceManager.addDevice(mockDevice); - final MockDeviceLogReader mockDeviceLogReader = MockDeviceLogReader(); + final FakeDeviceLogReader mockDeviceLogReader = FakeDeviceLogReader(); when(mockDevice.getLogReader()).thenReturn(mockDeviceLogReader); final MockLaunchResult mockLaunchResult = MockLaunchResult(); when(mockLaunchResult.started).thenReturn(true); @@ -481,7 +482,7 @@ void main() { final Device mockDevice = MockDevice(); testDeviceManager.addDevice(mockDevice); - final MockDeviceLogReader mockDeviceLogReader = MockDeviceLogReader(); + final FakeDeviceLogReader mockDeviceLogReader = FakeDeviceLogReader(); when(mockDevice.getLogReader()).thenReturn(mockDeviceLogReader); final MockLaunchResult mockLaunchResult = MockLaunchResult(); when(mockLaunchResult.started).thenReturn(true); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart index aab1f3b55ea..ded81725915 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart @@ -32,6 +32,7 @@ import 'package:mockito/mockito.dart'; import '../../src/common.dart'; import '../../src/context.dart'; +import '../../src/fakes.dart'; import '../../src/mocks.dart'; import '../../src/testbed.dart'; @@ -279,7 +280,7 @@ void main() { applyMocksToCommand(command); final MockDevice mockDevice = MockDevice(TargetPlatform.ios); when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) => Future.value(false)); - when(mockDevice.getLogReader(app: anyNamed('app'))).thenReturn(MockDeviceLogReader()); + when(mockDevice.getLogReader(app: anyNamed('app'))).thenReturn(FakeDeviceLogReader()); when(mockDevice.supportsFastStart).thenReturn(true); when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) => Future.value('iOS 13')); // App fails to start because we're only interested in usage @@ -633,7 +634,7 @@ class FakeDevice extends Fake implements Device { @override DeviceLogReader getLogReader({ ApplicationPackage app }) { - return MockDeviceLogReader(); + return FakeDeviceLogReader(); } @override diff --git a/packages/flutter_tools/test/general.shard/ios/devices_test.dart b/packages/flutter_tools/test/general.shard/ios/devices_test.dart index cf8f386171d..e454554b4aa 100644 --- a/packages/flutter_tools/test/general.shard/ios/devices_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/devices_test.dart @@ -36,6 +36,7 @@ import 'package:quiver/testing/async.dart'; import '../../src/common.dart'; import '../../src/context.dart'; +import '../../src/fakes.dart'; import '../../src/mocks.dart'; void main() { @@ -395,12 +396,10 @@ void main() { MockFileSystem mockFileSystem; MockPlatform mockPlatform; MockProcessManager mockProcessManager; - MockDeviceLogReader mockLogReader; - MockMDnsObservatoryDiscovery mockMDnsObservatoryDiscovery; + FakeDeviceLogReader mockLogReader; MockPortForwarder mockPortForwarder; MockIMobileDevice mockIMobileDevice; MockIOSDeploy mockIosDeploy; - MockUsage mockUsage; Directory tempDir; Directory projectDir; @@ -428,13 +427,11 @@ void main() { mockFileSystem = MockFileSystem(); mockPlatform = MockPlatform(); when(mockPlatform.isMacOS).thenReturn(true); - mockMDnsObservatoryDiscovery = MockMDnsObservatoryDiscovery(); mockProcessManager = MockProcessManager(); - mockLogReader = MockDeviceLogReader(); + mockLogReader = FakeDeviceLogReader(); mockPortForwarder = MockPortForwarder(); mockIMobileDevice = MockIMobileDevice(); mockIosDeploy = MockIOSDeploy(); - mockUsage = MockUsage(); tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_create_test.'); projectDir = tempDir.childDirectory('flutter_project'); @@ -493,367 +490,6 @@ void main() { 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: [], - )).thenAnswer((Invocation invocation) => Future.value(0)); - when(mockIosDeploy.runApp( - deviceId: device.id, - bundlePath: anyNamed('bundlePath'), - launchArguments: anyNamed('launchArguments'), - )).thenAnswer((Invocation invocation) => Future.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.value(uri)); - - final LaunchResult launchResult = await device.startApp(mockApp, - prebuiltApplication: true, - debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)), - platformArgs: {}, - ); - verify(mockUsage.sendEvent('ios-handshake', 'mdns-success')).called(1); - expect(launchResult.started, isTrue); - expect(launchResult.hasObservatory, isTrue); - expect(await device.stopApp(mockApp), isFalse); - }, overrides: { - 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: []) - ).thenAnswer((Invocation invocation) => Future.value(0)); - when(mockIosDeploy.runApp( - deviceId: device.id, - bundlePath: anyNamed('bundlePath'), - launchArguments: anyNamed('launchArguments'), - )).thenAnswer((Invocation invocation) => Future.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.value(null)); - - final LaunchResult launchResult = await device.startApp(mockApp, - prebuiltApplication: true, - debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)), - platformArgs: {}, - ); - 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: { - 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: [], - )).thenAnswer((Invocation invocation) => Future.value(0)); - when(mockIosDeploy.runApp( - deviceId: device.id, - bundlePath: anyNamed('bundlePath'), - launchArguments: anyNamed('launchArguments'), - )).thenAnswer((Invocation invocation) => Future.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.value(null)); - - final LaunchResult launchResult = await device.startApp(mockApp, - prebuiltApplication: true, - debuggingOptions: DebuggingOptions.enabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)), - platformArgs: {}, - ); - 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: { - 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: [], - )).thenAnswer((Invocation invocation) => Future.value(0)); - when(mockIosDeploy.runApp( - deviceId: device.id, - bundlePath: anyNamed('bundlePath'), - launchArguments: anyNamed('launchArguments'), - )).thenAnswer((Invocation invocation) => Future.value(0)); - final LaunchResult launchResult = await device.startApp(mockApp, - prebuiltApplication: true, - debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.release, null, treeShakeIcons: false)), - platformArgs: {}, - ); - verify(mockIosDeploy.installApp( - deviceId: device.id, - bundlePath: anyNamed('bundlePath'), - launchArguments: [], - )); - 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: [], - )).thenAnswer((Invocation invocation) => Future.value(0)); - when(mockIosDeploy.runApp( - deviceId: device.id, - bundlePath: anyNamed('bundlePath'), - launchArguments: anyNamed('launchArguments'), - )).thenAnswer((Invocation invocation) => Future.value(0)); - await device.startApp(mockApp, - prebuiltApplication: true, - debuggingOptions: DebuggingOptions.disabled( - const BuildInfo(BuildMode.release, null, treeShakeIcons: false), - traceWhitelist: 'foo'), - platformArgs: {}, - ); - 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.value(uri)); - - List args; - when(mockIosDeploy.runApp( - deviceId: anyNamed('deviceId'), - bundlePath: anyNamed('bundlePath'), - launchArguments: anyNamed('launchArguments'), - )).thenAnswer((Invocation inv) { - args = inv.namedArguments[const Symbol('launchArguments')] as List; - return Future.value(0); - }); - when(mockIosDeploy.installApp( - deviceId: device.id, - bundlePath: anyNamed('bundlePath'), - launchArguments: anyNamed('launchArguments'), - )).thenAnswer((Invocation invocation) => Future.value(0)); - - final LaunchResult launchResult = await device.startApp(mockApp, - prebuiltApplication: true, - debuggingOptions: DebuggingOptions.enabled( - const BuildInfo(BuildMode.debug, null, treeShakeIcons: false), - cacheSkSL: true, - ), - platformArgs: {}, - ); - expect(launchResult.started, isTrue); - expect(args, contains('--cache-sksl')); - expect(await device.stopApp(mockApp), isFalse); - }, overrides: { - 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.value(uri)); - - List args; - when(mockIosDeploy.runApp( - deviceId: anyNamed('deviceId'), - bundlePath: anyNamed('bundlePath'), - launchArguments: anyNamed('launchArguments'), - )).thenAnswer((Invocation inv) { - args = inv.namedArguments[const Symbol('launchArguments')] as List; - return Future.value(0); - }); - - when(mockIosDeploy.installApp( - deviceId: device.id, - bundlePath: anyNamed('bundlePath'), - launchArguments: anyNamed('launchArguments'), - )).thenAnswer((Invocation invocation) => Future.value(0)); - final LaunchResult launchResult = await device.startApp(mockApp, - prebuiltApplication: true, - debuggingOptions: DebuggingOptions.enabled( - const BuildInfo(BuildMode.debug, null, treeShakeIcons: false), - deviceVmServicePort: 8181, - ), - platformArgs: {}, - ); - expect(launchResult.started, isTrue); - expect(args, contains('--observatory-port=8181')); - expect(await device.stopApp(mockApp), isFalse); - }, overrides: { - Cache: () => mockCache, - MDnsObservatoryDiscovery: () => mockMDnsObservatoryDiscovery, - ProcessManager: () => mockProcessManager, - Usage: () => mockUsage, - }); - void testNonPrebuilt( String name, { @required bool showBuildSettingsFlakes, diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart new file mode 100644 index 00000000000..826e0a92009 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart @@ -0,0 +1,402 @@ +// 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/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/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/ios_deploy.dart'; +import 'package:flutter_tools/src/mdns_discovery.dart'; +import 'package:flutter_tools/src/reporting/reporting.dart'; +import 'package:mockito/mockito.dart'; +import 'package:platform/platform.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; + +import '../../src/common.dart'; +import '../../src/context.dart'; +import '../../src/fakes.dart'; + +const FakeCommand kDeployCommand = FakeCommand( + command: [ + 'ios-deploy', + '--id', + '123', + '--bundle', + '/', + '--no-wifi', + ], + environment: { + 'PATH': '/usr/bin:null', + 'DYLD_LIBRARY_PATH': '/path/to/libraries', + } +); + +// The command used to actually launch the app with args in release/profile. +const FakeCommand kLaunchReleaseCommand = FakeCommand( + command: [ + 'ios-deploy', + '--id', + '123', + '--bundle', + '/', + '--no-wifi', + '--justlaunch', + // These args are the default on DebuggingOptions. + '--args', + '--enable-dart-profiling --enable-service-port-fallback --disable-service-auth-codes --observatory-port=60700', + ], + environment: { + 'PATH': '/usr/bin:null', + 'DYLD_LIBRARY_PATH': '/path/to/libraries', + } +); + +// The command used to actually launch the app with args in debug. +const FakeCommand kLaunchDebugCommand = FakeCommand(command: [ + 'ios-deploy', + '--id', + '123', + '--bundle', + '/', + '--no-wifi', + '--justlaunch', + '--args', + '--enable-dart-profiling --enable-service-port-fallback --disable-service-auth-codes --observatory-port=60700 --enable-checked-mode --verify-entry-points' +], environment: { + 'PATH': '/usr/bin:null', + 'DYLD_LIBRARY_PATH': '/path/to/libraries', +}); + +void main() { + // TODO(jonahwilliams): This test doesn't really belong here but + // I don't have a better place for it for now. + testWithoutContext('disposing device disposes the portForwarder and logReader', () async { + final IOSDevice device = setUpIOSDevice(); + final DevicePortForwarder devicePortForwarder = MockDevicePortForwarder(); + final DeviceLogReader deviceLogReader = MockDeviceLogReader(); + final IOSApp iosApp = PrebuiltIOSApp( + projectBundleId: 'app', + bundleName: 'Runner', + ); + + device.portForwarder = devicePortForwarder; + device.setLogReader(iosApp, deviceLogReader); + await device.dispose(); + + verify(deviceLogReader.dispose()).called(1); + verify(devicePortForwarder.dispose()).called(1); + }); + + // Still uses context for analytics and mDNS. + testUsingContext('IOSDevice.startApp succeeds in debug mode via mDNS discovery', () async { + final FileSystem fileSystem = MemoryFileSystem.test(); + final FakeProcessManager processManager = FakeProcessManager.list([ + kDeployCommand, + kLaunchDebugCommand, + ]); + final IOSDevice device = setUpIOSDevice( + processManager: processManager, + fileSystem: fileSystem, + ); + final IOSApp iosApp = PrebuiltIOSApp( + projectBundleId: 'app', + bundleName: 'Runner', + bundleDir: fileSystem.currentDirectory, + ); + final Uri uri = Uri( + scheme: 'http', + host: '127.0.0.1', + port: 1234, + path: 'observatory', + ); + + device.portForwarder = const NoOpDevicePortForwarder(); + device.setLogReader(iosApp, FakeDeviceLogReader()); + + when(MDnsObservatoryDiscovery.instance.getObservatoryUri(any, any, usesIpv6: anyNamed('usesIpv6'))) + .thenAnswer((Invocation invocation) async => uri); + + final LaunchResult launchResult = await device.startApp(iosApp, + prebuiltApplication: true, + debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), + platformArgs: {}, + ); + + verify(globals.flutterUsage.sendEvent('ios-handshake', 'mdns-success')).called(1); + expect(launchResult.started, true); + expect(launchResult.hasObservatory, true); + expect(await device.stopApp(iosApp), false); + }, overrides: { + MDnsObservatoryDiscovery: () => MockMDnsObservatoryDiscovery(), + Usage: () => MockUsage(), + }); + + // Still uses context for analytics and mDNS. + testUsingContext('IOSDevice.startApp succeeds in debug mode when mDNS fails', () async { + final FileSystem fileSystem = MemoryFileSystem.test(); + final FakeProcessManager processManager = FakeProcessManager.list([ + kDeployCommand, + kLaunchDebugCommand, + ]); + final IOSDevice device = setUpIOSDevice( + processManager: processManager, + fileSystem: fileSystem, + ); + final IOSApp iosApp = PrebuiltIOSApp( + projectBundleId: 'app', + bundleName: 'Runner', + bundleDir: fileSystem.currentDirectory, + ); + final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader(); + + device.portForwarder = const NoOpDevicePortForwarder(); + device.setLogReader(iosApp, deviceLogReader); + + // Now that the reader is used, start writing messages to it. + Timer.run(() { + deviceLogReader.addLine('Foo'); + deviceLogReader.addLine('Observatory listening on http://127.0.0.1:456'); + }); + when(MDnsObservatoryDiscovery.instance.getObservatoryUri(any, any, usesIpv6: anyNamed('usesIpv6'))) + .thenAnswer((Invocation invocation) async => null); + + final LaunchResult launchResult = await device.startApp(iosApp, + prebuiltApplication: true, + debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), + platformArgs: {}, + ); + + expect(launchResult.started, true); + expect(launchResult.hasObservatory, true); + verify(globals.flutterUsage.sendEvent('ios-handshake', 'mdns-failure')).called(1); + verify(globals.flutterUsage.sendEvent('ios-handshake', 'fallback-success')).called(1); + expect(await device.stopApp(iosApp), false); + }, overrides: { + Usage: () => MockUsage(), + MDnsObservatoryDiscovery: () => MockMDnsObservatoryDiscovery(), + }); + + // Still uses context for analytics and mDNS. + testUsingContext('IOSDevice.startApp fails in debug mode when mDNS fails and ' + 'when Observatory URI is malformed', () async { + final FileSystem fileSystem = MemoryFileSystem.test(); + final FakeProcessManager processManager = FakeProcessManager.list([ + kDeployCommand, + kLaunchDebugCommand, + ]); + final IOSDevice device = setUpIOSDevice( + processManager: processManager, + fileSystem: fileSystem, + ); + final IOSApp iosApp = PrebuiltIOSApp( + projectBundleId: 'app', + bundleName: 'Runner', + bundleDir: fileSystem.currentDirectory, + ); + final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader(); + + device.portForwarder = const NoOpDevicePortForwarder(); + device.setLogReader(iosApp, deviceLogReader); + + // Now that the reader is used, start writing messages to it. + Timer.run(() { + deviceLogReader.addLine('Foo'); + deviceLogReader.addLine('Observatory listening on http:/:/127.0.0.1:456'); + }); + when(MDnsObservatoryDiscovery.instance.getObservatoryUri(any, any, usesIpv6: anyNamed('usesIpv6'))) + .thenAnswer((Invocation invocation) async => null); + + final LaunchResult launchResult = await device.startApp(iosApp, + prebuiltApplication: true, + debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), + platformArgs: {}, + ); + + expect(launchResult.started, false); + expect(launchResult.hasObservatory, false); + verify(globals.flutterUsage.sendEvent( + 'ios-handshake', + 'failure-other', + label: anyNamed('label'), + value: anyNamed('value'), + )).called(1); + verify(globals.flutterUsage.sendEvent('ios-handshake', 'mdns-failure')).called(1); + verify(globals.flutterUsage.sendEvent('ios-handshake', 'fallback-failure')).called(1); + }, overrides: { + MDnsObservatoryDiscovery: () => MockMDnsObservatoryDiscovery(), + Usage: () => MockUsage(), + }); + + // Still uses context for TimeoutConfiguration and usage + testUsingContext('IOSDevice.startApp succeeds in release mode', () async { + final FileSystem fileSystem = MemoryFileSystem.test(); + final FakeProcessManager processManager = FakeProcessManager.list([ + kDeployCommand, + kLaunchReleaseCommand, + ]); + final IOSDevice device = setUpIOSDevice( + processManager: processManager, + fileSystem: fileSystem, + ); + final IOSApp iosApp = PrebuiltIOSApp( + projectBundleId: 'app', + bundleName: 'Runner', + bundleDir: fileSystem.currentDirectory, + ); + + final LaunchResult launchResult = await device.startApp(iosApp, + prebuiltApplication: true, + debuggingOptions: DebuggingOptions.disabled(BuildInfo.release), + platformArgs: {}, + ); + + expect(launchResult.started, true); + expect(launchResult.hasObservatory, false); + expect(await device.stopApp(iosApp), false); + expect(processManager.hasRemainingExpectations, false); + }, overrides: { + Usage: () => MockUsage(), + }); + + // Still uses context for analytics and mDNS. + testUsingContext('IOSDevice.startApp forwards all supported debugging options', () async { + final FileSystem fileSystem = MemoryFileSystem.test(); + final FakeProcessManager processManager = FakeProcessManager.list([ + kDeployCommand, + FakeCommand( + command: [ + 'ios-deploy', + '--id', + '123', + '--bundle', + '/', + '--no-wifi', + '--justlaunch', + // The arguments below are determined by what is passed into + // the debugging options argument to startApp. + '--args', + [ + '--enable-dart-profiling', + '--enable-service-port-fallback', + '--disable-service-auth-codes', + '--observatory-port=60700', + '--start-paused', + '--dart-flags="--foo"', + '--enable-checked-mode', + '--verify-entry-points', + '--enable-software-rendering', + '--skia-deterministic-rendering', + '--trace-skia', + '--endless-trace-buffer', + '--dump-skp-on-shader-compilation', + '--verbose-logging', + '--cache-sksl', + ].join(' '), + ], environment: const { + 'PATH': '/usr/bin:null', + 'DYLD_LIBRARY_PATH': '/path/to/libraries', + } + ) + ]); + final IOSDevice device = setUpIOSDevice( + sdkVersion: '13.3', + processManager: processManager, + fileSystem: fileSystem, + ); + final IOSApp iosApp = PrebuiltIOSApp( + projectBundleId: 'app', + bundleName: 'Runner', + bundleDir: fileSystem.currentDirectory, + ); + final Uri uri = Uri( + scheme: 'http', + host: '127.0.0.1', + port: 1234, + path: 'observatory', + ); + + device.setLogReader(iosApp, FakeDeviceLogReader()); + device.portForwarder = const NoOpDevicePortForwarder(); + + when(MDnsObservatoryDiscovery.instance.getObservatoryUri(any, any, usesIpv6: anyNamed('usesIpv6'))) + .thenAnswer((Invocation invocation) async => uri); + + final LaunchResult launchResult = await device.startApp(iosApp, + prebuiltApplication: true, + debuggingOptions: DebuggingOptions.enabled( + BuildInfo.debug, + startPaused: true, + disableServiceAuthCodes: true, + dartFlags: '--foo', + enableSoftwareRendering: true, + skiaDeterministicRendering: true, + traceSkia: true, + traceSystrace: true, + endlessTraceBuffer: true, + dumpSkpOnShaderCompilation: true, + cacheSkSL: true, + verboseSystemLogs: true, + ), + platformArgs: {}, + ); + + expect(launchResult.started, true); + expect(await device.stopApp(iosApp), false); + expect(processManager.hasRemainingExpectations, false); + }, overrides: { + MDnsObservatoryDiscovery: () => MockMDnsObservatoryDiscovery(), + Usage: () => MockUsage(), + }); +} + +IOSDevice setUpIOSDevice({ + String sdkVersion = '13.0.1', + FileSystem fileSystem, + Logger logger, + ProcessManager processManager, +}) { + const MapEntry dyldLibraryEntry = MapEntry( + 'DYLD_LIBRARY_PATH', + '/path/to/libraries', + ); + final MockCache cache = MockCache(); + final MockArtifacts artifacts = MockArtifacts(); + final FakePlatform macPlatform = FakePlatform( + operatingSystem: 'macos', + environment: {}, + ); + when(cache.dyLdLibEntry).thenReturn(dyldLibraryEntry); + when(artifacts.getArtifactPath(Artifact.iosDeploy, platform: anyNamed('platform'))) + .thenReturn('ios-deploy'); + return IOSDevice('123', + name: 'iPhone 1', + sdkVersion: sdkVersion, + fileSystem: fileSystem ?? MemoryFileSystem.test(), + platform: macPlatform, + artifacts: artifacts, + logger: BufferLogger.test(), + iosDeploy: IOSDeploy( + logger: logger ?? BufferLogger.test(), + platform: macPlatform, + processManager: processManager ?? FakeProcessManager.any(), + artifacts: artifacts, + cache: cache, + ), + cpuArchitecture: DarwinArch.arm64, + ); +} + +class MockDevicePortForwarder extends Mock implements DevicePortForwarder {} +class MockDeviceLogReader extends Mock implements DeviceLogReader {} +class MockUsage extends Mock implements Usage {} +class MockMDnsObservatoryDiscovery extends Mock implements MDnsObservatoryDiscovery {} +class MockArtifacts extends Mock implements Artifacts {} +class MockCache extends Mock implements Cache {} diff --git a/packages/flutter_tools/test/general.shard/protocol_discovery_test.dart b/packages/flutter_tools/test/general.shard/protocol_discovery_test.dart index 983af779146..70811d1b391 100644 --- a/packages/flutter_tools/test/general.shard/protocol_discovery_test.dart +++ b/packages/flutter_tools/test/general.shard/protocol_discovery_test.dart @@ -10,11 +10,11 @@ import 'package:quiver/testing/async.dart'; import '../src/common.dart'; import '../src/context.dart'; -import '../src/mocks.dart'; +import '../src/fakes.dart'; void main() { group('service_protocol discovery', () { - MockDeviceLogReader logReader; + FakeDeviceLogReader logReader; ProtocolDiscovery discoverer; /// Performs test set-up functionality that must be performed as part of @@ -37,7 +37,7 @@ void main() { int devicePort, Duration throttleDuration = const Duration(milliseconds: 200), }) { - logReader = MockDeviceLogReader(); + logReader = FakeDeviceLogReader(); discoverer = ProtocolDiscovery.observatory( logReader, ipv6: false, @@ -261,7 +261,7 @@ void main() { group('port forwarding', () { testUsingContext('default port', () async { - final MockDeviceLogReader logReader = MockDeviceLogReader(); + final FakeDeviceLogReader logReader = FakeDeviceLogReader(); final ProtocolDiscovery discoverer = ProtocolDiscovery.observatory( logReader, portForwarder: MockPortForwarder(99), @@ -282,7 +282,7 @@ void main() { }); testUsingContext('specified port', () async { - final MockDeviceLogReader logReader = MockDeviceLogReader(); + final FakeDeviceLogReader logReader = FakeDeviceLogReader(); final ProtocolDiscovery discoverer = ProtocolDiscovery.observatory( logReader, portForwarder: MockPortForwarder(99), @@ -303,7 +303,7 @@ void main() { }); testUsingContext('specified port zero', () async { - final MockDeviceLogReader logReader = MockDeviceLogReader(); + final FakeDeviceLogReader logReader = FakeDeviceLogReader(); final ProtocolDiscovery discoverer = ProtocolDiscovery.observatory( logReader, portForwarder: MockPortForwarder(99), @@ -324,7 +324,7 @@ void main() { }); testUsingContext('ipv6', () async { - final MockDeviceLogReader logReader = MockDeviceLogReader(); + final FakeDeviceLogReader logReader = FakeDeviceLogReader(); final ProtocolDiscovery discoverer = ProtocolDiscovery.observatory( logReader, portForwarder: MockPortForwarder(99), @@ -345,7 +345,7 @@ void main() { }); testUsingContext('ipv6 with Ascii Escape code', () async { - final MockDeviceLogReader logReader = MockDeviceLogReader(); + final FakeDeviceLogReader logReader = FakeDeviceLogReader(); final ProtocolDiscovery discoverer = ProtocolDiscovery.observatory( logReader, portForwarder: MockPortForwarder(99), diff --git a/packages/flutter_tools/test/src/fake_process_manager.dart b/packages/flutter_tools/test/src/fake_process_manager.dart index a0cebc440c7..a911c8ee688 100644 --- a/packages/flutter_tools/test/src/fake_process_manager.dart +++ b/packages/flutter_tools/test/src/fake_process_manager.dart @@ -82,39 +82,14 @@ class FakeCommand { /// resolves. final Completer completer; - static bool _listEquals(List a, List b) { - if (a == null) { - return b == null; - } - if (b == null || a.length != b.length) { - return false; - } - for (int index = 0; index < a.length; index += 1) { - if (a[index] != b[index]) { - return false; - } - } - return true; - } - - bool _matches(List command, String workingDirectory, Map environment) { - if (!_listEquals(command, this.command)) { - return false; - } - if (this.workingDirectory != null && workingDirectory != this.workingDirectory) { - return false; + void _matches(List command, String workingDirectory, Map environment) { + expect(command, equals(this.command)); + if (this.workingDirectory != null) { + expect(this.workingDirectory, workingDirectory); } if (this.environment != null) { - if (environment == null) { - return false; - } - for (final String key in environment.keys) { - if (environment[key] != this.environment[key]) { - return false; - } - } + expect(this.environment, environment); } - return true; } } @@ -322,12 +297,7 @@ class _SequenceProcessManager extends FakeProcessManager { reason: 'ProcessManager was told to execute $command (in $workingDirectory) ' 'but the FakeProcessManager.list expected no more processes.' ); - expect(_commands.first._matches(command, workingDirectory, environment), isTrue, - reason: 'ProcessManager was told to execute $command ' - '(in $workingDirectory, with environment $environment) ' - 'but the next process that was expected was ${_commands.first.command} ' - '(in ${_commands.first.workingDirectory}, with environment ${_commands.first.environment})}.' - ); + _commands.first._matches(command, workingDirectory, environment); return _commands.removeAt(0); } diff --git a/packages/flutter_tools/test/src/fakes.dart b/packages/flutter_tools/test/src/fakes.dart new file mode 100644 index 00000000000..b2cc65ef6df --- /dev/null +++ b/packages/flutter_tools/test/src/fakes.dart @@ -0,0 +1,42 @@ +// 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:flutter_tools/src/device.dart'; + +/// A fake implementation of the [DeviceLogReader]. +class FakeDeviceLogReader extends DeviceLogReader { + @override + String get name => 'FakeLogReader'; + + StreamController _cachedLinesController; + + final List _lineQueue = []; + StreamController get _linesController { + _cachedLinesController ??= StreamController + .broadcast(onListen: () { + _lineQueue.forEach(_linesController.add); + _lineQueue.clear(); + }); + return _cachedLinesController; + } + + @override + Stream get logLines => _linesController.stream; + + void addLine(String line) { + if (_linesController.hasListener) { + _linesController.add(line); + } else { + _lineQueue.add(line); + } + } + + @override + Future dispose() async { + _lineQueue.clear(); + await _linesController.close(); + } +} diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart index 47808de9516..006b6b92f90 100644 --- a/packages/flutter_tools/test/src/mocks.dart +++ b/packages/flutter_tools/test/src/mocks.dart @@ -612,40 +612,6 @@ class MockIOSSimulator extends Mock implements IOSSimulator { bool isSupportedForProject(FlutterProject flutterProject) => true; } -class MockDeviceLogReader extends DeviceLogReader { - @override - String get name => 'MockLogReader'; - - StreamController _cachedLinesController; - - final List _lineQueue = []; - StreamController get _linesController { - _cachedLinesController ??= StreamController - .broadcast(onListen: () { - _lineQueue.forEach(_linesController.add); - _lineQueue.clear(); - }); - return _cachedLinesController; - } - - @override - Stream get logLines => _linesController.stream; - - void addLine(String line) { - if (_linesController.hasListener) { - _linesController.add(line); - } else { - _lineQueue.add(line); - } - } - - @override - Future dispose() async { - _lineQueue.clear(); - await _linesController.close(); - } -} - void applyMocksToCommand(FlutterCommand command) { command.applicationPackages = MockApplicationPackageStore(); }