| // 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/android/android_device.dart'; |
| import 'package:flutter_tools/src/android/application_package.dart'; |
| import 'package:flutter_tools/src/artifacts.dart'; |
| import 'package:flutter_tools/src/base/common.dart'; |
| import 'package:flutter_tools/src/base/dds.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/terminal.dart'; |
| import 'package:flutter_tools/src/build_info.dart'; |
| import 'package:flutter_tools/src/cache.dart'; |
| import 'package:flutter_tools/src/commands/attach.dart'; |
| import 'package:flutter_tools/src/device.dart'; |
| import 'package:flutter_tools/src/device_port_forwarder.dart'; |
| import 'package:flutter_tools/src/globals.dart' as globals; |
| import 'package:flutter_tools/src/ios/application_package.dart'; |
| import 'package:flutter_tools/src/ios/devices.dart'; |
| import 'package:flutter_tools/src/macos/macos_ipad_device.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/resident_runner.dart'; |
| import 'package:flutter_tools/src/run_hot.dart'; |
| import 'package:flutter_tools/src/vmservice.dart'; |
| import 'package:multicast_dns/multicast_dns.dart'; |
| import 'package:test/fake.dart'; |
| import 'package:vm_service/vm_service.dart' as vm_service; |
| |
| import '../../src/common.dart'; |
| import '../../src/context.dart'; |
| import '../../src/fake_devices.dart'; |
| import '../../src/test_flutter_command_runner.dart'; |
| |
| void main() { |
| tearDown(() { |
| MacOSDesignedForIPadDevices.allowDiscovery = false; |
| }); |
| |
| group('attach', () { |
| late StreamLogger logger; |
| late FileSystem testFileSystem; |
| late TestDeviceManager testDeviceManager; |
| |
| setUp(() { |
| Cache.disableLocking(); |
| logger = StreamLogger(); |
| testFileSystem = MemoryFileSystem( |
| style: globals.platform.isWindows |
| ? FileSystemStyle.windows |
| : FileSystemStyle.posix, |
| ); |
| testFileSystem.directory('lib').createSync(); |
| testFileSystem.file(testFileSystem.path.join('lib', 'main.dart')).createSync(); |
| testDeviceManager = TestDeviceManager(logger: BufferLogger.test()); |
| }); |
| |
| group('with one device and no specified target file', () { |
| const int devicePort = 499; |
| const int hostPort = 42; |
| |
| late FakeDeviceLogReader fakeLogReader; |
| late RecordingPortForwarder portForwarder; |
| late FakeDartDevelopmentService fakeDds; |
| late FakeAndroidDevice device; |
| |
| setUp(() { |
| fakeLogReader = FakeDeviceLogReader(); |
| portForwarder = RecordingPortForwarder(hostPort); |
| fakeDds = FakeDartDevelopmentService(); |
| device = FakeAndroidDevice(id: '1') |
| ..portForwarder = portForwarder |
| ..dds = fakeDds; |
| }); |
| |
| tearDown(() { |
| fakeLogReader.dispose(); |
| }); |
| |
| testUsingContext('succeeds with iOS device', () async { |
| final FakeIOSDevice device = FakeIOSDevice( |
| logReader: fakeLogReader, |
| portForwarder: portForwarder, |
| onGetLogReader: () { |
| fakeLogReader.addLine('Foo'); |
| fakeLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:$devicePort'); |
| return fakeLogReader; |
| }, |
| ); |
| |
| testDeviceManager.devices = <Device>[device]; |
| final Completer<void> completer = Completer<void>(); |
| final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) { |
| if (message == '[verbose] Observatory URL on device: http://127.0.0.1:$devicePort') { |
| // The "Observatory URL on device" message is output by the ProtocolDiscovery when it found the observatory. |
| completer.complete(); |
| } |
| }); |
| final Future<void> task = createTestCommandRunner(AttachCommand()).run(<String>['attach']); |
| await completer.future; |
| |
| expect(portForwarder.devicePort, devicePort); |
| expect(portForwarder.hostPort, hostPort); |
| |
| await fakeLogReader.dispose(); |
| await expectLoggerInterruptEndsTask(task, logger); |
| await loggerSubscription.cancel(); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Logger: () => logger, |
| DeviceManager: () => testDeviceManager, |
| MDnsObservatoryDiscovery: () => MDnsObservatoryDiscovery( |
| mdnsClient: FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{}), |
| logger: logger, |
| flutterUsage: TestUsage(), |
| ), |
| }); |
| |
| testUsingContext('finds observatory port and forwards', () async { |
| device.onGetLogReader = () { |
| fakeLogReader.addLine('Foo'); |
| fakeLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:$devicePort'); |
| return fakeLogReader; |
| }; |
| testDeviceManager.devices = <Device>[device]; |
| final Completer<void> completer = Completer<void>(); |
| final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) { |
| if (message == '[verbose] Observatory URL on device: http://127.0.0.1:$devicePort') { |
| // The "Observatory URL on device" message is output by the ProtocolDiscovery when it found the observatory. |
| completer.complete(); |
| } |
| }); |
| final Future<void> task = createTestCommandRunner(AttachCommand()).run(<String>['attach']); |
| await completer.future; |
| |
| expect(portForwarder.devicePort, devicePort); |
| expect(portForwarder.hostPort, hostPort); |
| |
| await fakeLogReader.dispose(); |
| await expectLoggerInterruptEndsTask(task, logger); |
| await loggerSubscription.cancel(); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Logger: () => logger, |
| DeviceManager: () => testDeviceManager, |
| }); |
| |
| testUsingContext('Fails with tool exit on bad Observatory uri', () async { |
| device.onGetLogReader = () { |
| fakeLogReader.addLine('Foo'); |
| fakeLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:$devicePort'); |
| fakeLogReader.dispose(); |
| return fakeLogReader; |
| }; |
| testDeviceManager.devices = <Device>[device]; |
| expect(() => createTestCommandRunner(AttachCommand()).run(<String>['attach']), throwsToolExit()); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Logger: () => logger, |
| DeviceManager: () => testDeviceManager, |
| }); |
| |
| testUsingContext('accepts filesystem parameters', () async { |
| device.onGetLogReader = () { |
| fakeLogReader.addLine('Foo'); |
| fakeLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:$devicePort'); |
| return fakeLogReader; |
| }; |
| testDeviceManager.devices = <Device>[device]; |
| |
| const String filesystemScheme = 'foo'; |
| const String filesystemRoot = '/build-output/'; |
| const String projectRoot = '/build-output/project-root'; |
| const String outputDill = '/tmp/output.dill'; |
| |
| final FakeHotRunner hotRunner = FakeHotRunner(); |
| hotRunner.onAttach = ( |
| Completer<DebugConnectionInfo>? connectionInfoCompleter, |
| Completer<void>? appStartedCompleter, |
| bool allowExistingDdsInstance, |
| bool enableDevTools, |
| ) async => 0; |
| hotRunner.exited = false; |
| hotRunner.isWaitingForObservatory = false; |
| |
| final FakeHotRunnerFactory hotRunnerFactory = FakeHotRunnerFactory() |
| ..hotRunner = hotRunner; |
| |
| final AttachCommand command = AttachCommand( |
| hotRunnerFactory: hotRunnerFactory, |
| ); |
| await createTestCommandRunner(command).run(<String>[ |
| 'attach', |
| '--filesystem-scheme', |
| filesystemScheme, |
| '--filesystem-root', |
| filesystemRoot, |
| '--project-root', |
| projectRoot, |
| '--output-dill', |
| outputDill, |
| '-v', // enables verbose logging |
| ]); |
| |
| // Validate the attach call built a fake runner with the right |
| // project root and output dill. |
| expect(hotRunnerFactory.projectRootPath, projectRoot); |
| expect(hotRunnerFactory.dillOutputPath, outputDill); |
| expect(hotRunnerFactory.devices, hasLength(1)); |
| |
| // Validate that the attach call built a flutter device with the right |
| // output dill, filesystem scheme, and filesystem root. |
| final FlutterDevice flutterDevice = hotRunnerFactory.devices.first; |
| |
| expect(flutterDevice.buildInfo.fileSystemScheme, filesystemScheme); |
| expect(flutterDevice.buildInfo.fileSystemRoots, const <String>[filesystemRoot]); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| DeviceManager: () => testDeviceManager, |
| }); |
| |
| testUsingContext('exits when ipv6 is specified and debug-port is not', () async { |
| testDeviceManager.devices = <Device>[device]; |
| |
| final AttachCommand command = AttachCommand(); |
| await expectLater( |
| createTestCommandRunner(command).run(<String>['attach', '--ipv6']), |
| throwsToolExit( |
| message: 'When the --debug-port or --debug-url is unknown, this command determines ' |
| 'the value of --ipv6 on its own.', |
| ), |
| ); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| DeviceManager: () => testDeviceManager, |
| },); |
| |
| testUsingContext('exits when observatory-port is specified and debug-port is not', () async { |
| device.onGetLogReader = () { |
| fakeLogReader.addLine('Foo'); |
| fakeLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:$devicePort'); |
| return fakeLogReader; |
| }; |
| testDeviceManager.devices = <Device>[device]; |
| |
| final AttachCommand command = AttachCommand(); |
| await expectLater( |
| createTestCommandRunner(command).run(<String>['attach', '--observatory-port', '100']), |
| throwsToolExit( |
| message: 'When the --debug-port or --debug-url is unknown, this command does not use ' |
| 'the value of --observatory-port.', |
| ), |
| ); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| DeviceManager: () => testDeviceManager, |
| },); |
| }); |
| |
| group('forwarding to given port', () { |
| const int devicePort = 499; |
| const int hostPort = 42; |
| late RecordingPortForwarder portForwarder; |
| late FakeAndroidDevice device; |
| |
| setUp(() { |
| final FakeDartDevelopmentService fakeDds = FakeDartDevelopmentService(); |
| portForwarder = RecordingPortForwarder(hostPort); |
| device = FakeAndroidDevice(id: '1') |
| ..portForwarder = portForwarder |
| ..dds = fakeDds; |
| }); |
| |
| testUsingContext('succeeds in ipv4 mode', () async { |
| testDeviceManager.devices = <Device>[device]; |
| |
| final Completer<void> completer = Completer<void>(); |
| final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) { |
| if (message == '[verbose] Connecting to service protocol: http://127.0.0.1:42/') { |
| // Wait until resident_runner.dart tries to connect. |
| // There's nothing to connect _to_, so that's as far as we care to go. |
| completer.complete(); |
| } |
| }); |
| final Future<void> task = createTestCommandRunner(AttachCommand()) |
| .run(<String>['attach', '--debug-port', '$devicePort']); |
| await completer.future; |
| expect(portForwarder.devicePort, devicePort); |
| expect(portForwarder.hostPort, hostPort); |
| |
| await expectLoggerInterruptEndsTask(task, logger); |
| await loggerSubscription.cancel(); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Logger: () => logger, |
| DeviceManager: () => testDeviceManager, |
| }); |
| |
| testUsingContext('succeeds in ipv6 mode', () async { |
| testDeviceManager.devices = <Device>[device]; |
| |
| final Completer<void> completer = Completer<void>(); |
| final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) { |
| if (message == '[verbose] Connecting to service protocol: http://[::1]:42/') { |
| // Wait until resident_runner.dart tries to connect. |
| // There's nothing to connect _to_, so that's as far as we care to go. |
| completer.complete(); |
| } |
| }); |
| final Future<void> task = createTestCommandRunner(AttachCommand()) |
| .run(<String>['attach', '--debug-port', '$devicePort', '--ipv6']); |
| await completer.future; |
| |
| expect(portForwarder.devicePort, devicePort); |
| expect(portForwarder.hostPort, hostPort); |
| |
| await expectLoggerInterruptEndsTask(task, logger); |
| await loggerSubscription.cancel(); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Logger: () => logger, |
| DeviceManager: () => testDeviceManager, |
| }); |
| |
| testUsingContext('skips in ipv4 mode with a provided observatory port', () async { |
| testDeviceManager.devices = <Device>[device]; |
| |
| final Completer<void> completer = Completer<void>(); |
| final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) { |
| if (message == '[verbose] Connecting to service protocol: http://127.0.0.1:42/') { |
| // Wait until resident_runner.dart tries to connect. |
| // There's nothing to connect _to_, so that's as far as we care to go. |
| completer.complete(); |
| } |
| }); |
| final Future<void> task = createTestCommandRunner(AttachCommand()).run( |
| <String>[ |
| 'attach', |
| '--debug-port', |
| '$devicePort', |
| '--observatory-port', |
| '$hostPort', |
| // Ensure DDS doesn't use hostPort by binding to a random port. |
| '--dds-port', |
| '0', |
| ], |
| ); |
| await completer.future; |
| expect(portForwarder.devicePort, null); |
| expect(portForwarder.hostPort, 42); |
| |
| await expectLoggerInterruptEndsTask(task, logger); |
| await loggerSubscription.cancel(); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Logger: () => logger, |
| DeviceManager: () => testDeviceManager, |
| }); |
| |
| testUsingContext('skips in ipv6 mode with a provided observatory port', () async { |
| testDeviceManager.devices = <Device>[device]; |
| |
| final Completer<void> completer = Completer<void>(); |
| final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) { |
| if (message == '[verbose] Connecting to service protocol: http://[::1]:42/') { |
| // Wait until resident_runner.dart tries to connect. |
| // There's nothing to connect _to_, so that's as far as we care to go. |
| completer.complete(); |
| } |
| }); |
| final Future<void> task = createTestCommandRunner(AttachCommand()).run( |
| <String>[ |
| 'attach', |
| '--debug-port', |
| '$devicePort', |
| '--observatory-port', |
| '$hostPort', |
| '--ipv6', |
| // Ensure DDS doesn't use hostPort by binding to a random port. |
| '--dds-port', |
| '0', |
| ], |
| ); |
| await completer.future; |
| expect(portForwarder.devicePort, null); |
| expect(portForwarder.hostPort, 42); |
| |
| await expectLoggerInterruptEndsTask(task, logger); |
| await loggerSubscription.cancel(); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| Logger: () => logger, |
| DeviceManager: () => testDeviceManager, |
| }); |
| }); |
| |
| testUsingContext('exits when no device connected', () async { |
| final AttachCommand command = AttachCommand(); |
| await expectLater( |
| createTestCommandRunner(command).run(<String>['attach']), |
| throwsToolExit(), |
| ); |
| expect(testLogger.statusText, containsIgnoringWhitespace('No supported devices connected')); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| DeviceManager: () => testDeviceManager, |
| }); |
| |
| testUsingContext('fails when targeted device is not Android with --device-user', () async { |
| final FakeIOSDevice device = FakeIOSDevice(); |
| testDeviceManager.devices = <Device>[device]; |
| expect(createTestCommandRunner(AttachCommand()).run(<String>[ |
| 'attach', |
| '--device-user', |
| '10', |
| ]), throwsToolExit(message: '--device-user is only supported for Android')); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| DeviceManager: () => testDeviceManager, |
| }); |
| |
| testUsingContext('exits when multiple devices connected', () async { |
| final AttachCommand command = AttachCommand(); |
| testDeviceManager.devices = <Device>[ |
| FakeAndroidDevice(id: 'xx1'), |
| FakeAndroidDevice(id: 'yy2'), |
| ]; |
| await expectLater( |
| createTestCommandRunner(command).run(<String>['attach']), |
| throwsToolExit(), |
| ); |
| expect(testLogger.statusText, containsIgnoringWhitespace('More than one device')); |
| expect(testLogger.statusText, contains('xx1')); |
| expect(testLogger.statusText, contains('yy2')); |
| expect(MacOSDesignedForIPadDevices.allowDiscovery, isTrue); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| DeviceManager: () => testDeviceManager, |
| AnsiTerminal: () => FakeTerminal(stdinHasTerminal: false), |
| }); |
| |
| testUsingContext('Catches service disappeared error', () async { |
| final FakeAndroidDevice device = FakeAndroidDevice(id: '1') |
| ..portForwarder = const NoOpDevicePortForwarder() |
| ..onGetLogReader = () => NoOpDeviceLogReader('test'); |
| final FakeHotRunner hotRunner = FakeHotRunner(); |
| final FakeHotRunnerFactory hotRunnerFactory = FakeHotRunnerFactory() |
| ..hotRunner = hotRunner; |
| hotRunner.onAttach = ( |
| Completer<DebugConnectionInfo>? connectionInfoCompleter, |
| Completer<void>? appStartedCompleter, |
| bool allowExistingDdsInstance, |
| bool enableDevTools, |
| ) async { |
| await null; |
| throw vm_service.RPCError('flutter._listViews', RPCErrorCodes.kServiceDisappeared, ''); |
| }; |
| |
| testDeviceManager.devices = <Device>[device]; |
| testFileSystem.file('lib/main.dart').createSync(); |
| |
| final AttachCommand command = AttachCommand(hotRunnerFactory: hotRunnerFactory); |
| await expectLater(createTestCommandRunner(command).run(<String>[ |
| 'attach', |
| ]), throwsToolExit(message: 'Lost connection to device.')); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| DeviceManager: () => testDeviceManager, |
| }); |
| |
| testUsingContext('Does not catch generic RPC error', () async { |
| final FakeAndroidDevice device = FakeAndroidDevice(id: '1') |
| ..portForwarder = const NoOpDevicePortForwarder() |
| ..onGetLogReader = () => NoOpDeviceLogReader('test'); |
| final FakeHotRunner hotRunner = FakeHotRunner(); |
| final FakeHotRunnerFactory hotRunnerFactory = FakeHotRunnerFactory() |
| ..hotRunner = hotRunner; |
| |
| hotRunner.onAttach = ( |
| Completer<DebugConnectionInfo>? connectionInfoCompleter, |
| Completer<void>? appStartedCompleter, |
| bool allowExistingDdsInstance, |
| bool enableDevTools, |
| ) async { |
| await null; |
| throw vm_service.RPCError('flutter._listViews', RPCErrorCodes.kInvalidParams, ''); |
| }; |
| |
| testDeviceManager.devices = <Device>[device]; |
| testFileSystem.file('lib/main.dart').createSync(); |
| |
| final AttachCommand command = AttachCommand(hotRunnerFactory: hotRunnerFactory); |
| await expectLater(createTestCommandRunner(command).run(<String>[ |
| 'attach', |
| ]), throwsA(isA<vm_service.RPCError>())); |
| }, overrides: <Type, Generator>{ |
| FileSystem: () => testFileSystem, |
| ProcessManager: () => FakeProcessManager.any(), |
| DeviceManager: () => testDeviceManager, |
| }); |
| }); |
| } |
| |
| class FakeHotRunner extends Fake implements HotRunner { |
| late Future<int> Function(Completer<DebugConnectionInfo>?, Completer<void>?, bool, bool) onAttach; |
| |
| @override |
| bool exited = false; |
| |
| @override |
| bool isWaitingForObservatory = true; |
| |
| @override |
| Future<int> attach({ |
| Completer<DebugConnectionInfo>? connectionInfoCompleter, |
| Completer<void>? appStartedCompleter, |
| bool allowExistingDdsInstance = false, |
| bool enableDevTools = false, |
| bool needsFullRestart = true, |
| }) { |
| return onAttach(connectionInfoCompleter, appStartedCompleter, allowExistingDdsInstance, enableDevTools); |
| } |
| } |
| |
| class FakeHotRunnerFactory extends Fake implements HotRunnerFactory { |
| late HotRunner hotRunner; |
| String? dillOutputPath; |
| String? projectRootPath; |
| late List<FlutterDevice> devices; |
| |
| @override |
| HotRunner build( |
| List<FlutterDevice> devices, { |
| required String target, |
| required DebuggingOptions debuggingOptions, |
| bool benchmarkMode = false, |
| File? applicationBinary, |
| bool hostIsIde = false, |
| String? projectRootPath, |
| String? packagesFilePath, |
| String? dillOutputPath, |
| bool stayResident = true, |
| bool ipv6 = false, |
| FlutterProject? flutterProject, |
| }) { |
| this.devices = devices; |
| this.dillOutputPath = dillOutputPath; |
| this.projectRootPath = projectRootPath; |
| return hotRunner; |
| } |
| } |
| |
| class RecordingPortForwarder implements DevicePortForwarder { |
| RecordingPortForwarder([this.hostPort]); |
| |
| int? devicePort; |
| int? hostPort; |
| |
| @override |
| Future<void> dispose() async { } |
| |
| @override |
| Future<int> forward(int devicePort, {int? hostPort}) async { |
| this.devicePort = devicePort; |
| this.hostPort ??= hostPort; |
| return this.hostPort!; |
| } |
| |
| @override |
| List<ForwardedPort> get forwardedPorts => <ForwardedPort>[]; |
| |
| @override |
| Future<void> unforward(ForwardedPort forwardedPort) async { } |
| } |
| |
| class StreamLogger extends Logger { |
| @override |
| bool get isVerbose => true; |
| |
| @override |
| void printError( |
| String message, { |
| StackTrace? stackTrace, |
| bool? emphasis, |
| TerminalColor? color, |
| int? indent, |
| int? hangingIndent, |
| bool? wrap, |
| }) { |
| hadErrorOutput = true; |
| _log('[stderr] $message'); |
| } |
| |
| @override |
| void printWarning( |
| String message, { |
| bool? emphasis, |
| TerminalColor? color, |
| int? indent, |
| int? hangingIndent, |
| bool? wrap, |
| }) { |
| hadWarningOutput = true; |
| _log('[stderr] $message'); |
| } |
| |
| @override |
| void printStatus( |
| String message, { |
| bool? emphasis, |
| TerminalColor? color, |
| bool? newline, |
| int? indent, |
| int? hangingIndent, |
| bool? wrap, |
| }) { |
| _log('[stdout] $message'); |
| } |
| |
| @override |
| void printBox( |
| String message, { |
| String? title, |
| }) { |
| if (title == null) { |
| _log('[stdout] $message'); |
| } else { |
| _log('[stdout] $title: $message'); |
| } |
| } |
| |
| @override |
| void printTrace(String message) { |
| _log('[verbose] $message'); |
| } |
| |
| @override |
| Status startProgress( |
| String message, { |
| Duration? timeout, |
| String? progressId, |
| bool multilineOutput = false, |
| bool includeTiming = true, |
| int progressIndicatorPadding = kDefaultStatusPadding, |
| }) { |
| _log('[progress] $message'); |
| return SilentStatus( |
| stopwatch: Stopwatch(), |
| )..start(); |
| } |
| |
| @override |
| Status startSpinner({ |
| VoidCallback? onFinish, |
| Duration? timeout, |
| SlowWarningCallback? slowWarningCallback, |
| }) { |
| return SilentStatus( |
| stopwatch: Stopwatch(), |
| onFinish: onFinish, |
| )..start(); |
| } |
| |
| bool _interrupt = false; |
| |
| void interrupt() { |
| _interrupt = true; |
| } |
| |
| final StreamController<String> _controller = StreamController<String>.broadcast(); |
| |
| void _log(String message) { |
| _controller.add(message); |
| if (_interrupt) { |
| _interrupt = false; |
| throw const LoggerInterrupted(); |
| } |
| } |
| |
| Stream<String> get stream => _controller.stream; |
| |
| @override |
| void sendEvent(String name, [Map<String, dynamic>? args]) { } |
| |
| @override |
| bool get supportsColor => throw UnimplementedError(); |
| |
| @override |
| bool get hasTerminal => false; |
| |
| @override |
| void clear() => _log('[stdout] ${globals.terminal.clearScreen()}\n'); |
| |
| @override |
| Terminal get terminal => Terminal.test(); |
| } |
| |
| class LoggerInterrupted implements Exception { |
| const LoggerInterrupted(); |
| } |
| |
| Future<void> expectLoggerInterruptEndsTask(Future<void> task, StreamLogger logger) async { |
| logger.interrupt(); // an exception during the task should cause it to fail... |
| await expectLater( |
| () => task, |
| throwsA(isA<ToolExit>().having((ToolExit error) => error.exitCode, 'exitCode', 2)), |
| ); |
| } |
| |
| class FakeDartDevelopmentService extends Fake implements DartDevelopmentService { |
| @override |
| Future<void> get done => noopCompleter.future; |
| final Completer<void> noopCompleter = Completer<void>(); |
| |
| @override |
| Future<void> startDartDevelopmentService( |
| Uri observatoryUri, { |
| required Logger logger, |
| int? hostPort, |
| bool? ipv6, |
| bool? disableServiceAuthCodes, |
| bool cacheStartupProfile = false, |
| }) async {} |
| |
| @override |
| Uri get uri => Uri.parse('http://localhost:8181'); |
| } |
| |
| // Unfortunately Device, despite not being immutable, has an `operator ==`. |
| // Until we fix that, we have to also ignore related lints here. |
| // ignore: avoid_implementing_value_types |
| class FakeAndroidDevice extends Fake implements AndroidDevice { |
| FakeAndroidDevice({required this.id}); |
| |
| @override |
| late DartDevelopmentService dds; |
| |
| @override |
| final String id; |
| |
| @override |
| String get name => 'd$id'; |
| |
| @override |
| Future<bool> get isLocalEmulator async => false; |
| |
| @override |
| Future<String> get sdkNameAndVersion async => 'Android 46'; |
| |
| @override |
| Future<String> get targetPlatformDisplayName async => 'android'; |
| |
| @override |
| Future<TargetPlatform> get targetPlatform async => TargetPlatform.android_arm; |
| |
| @override |
| bool isSupported() => true; |
| |
| @override |
| bool get supportsHotRestart => true; |
| |
| @override |
| bool get supportsFlutterExit => false; |
| |
| @override |
| bool isSupportedForProject(FlutterProject flutterProject) => true; |
| |
| @override |
| DevicePortForwarder? portForwarder; |
| |
| DeviceLogReader Function()? onGetLogReader; |
| |
| @override |
| FutureOr<DeviceLogReader> getLogReader({ |
| AndroidApk? app, |
| bool includePastLogs = false, |
| }) { |
| if (onGetLogReader == null) { |
| throw UnimplementedError( |
| 'Called getLogReader but no onGetLogReader callback was supplied in the constructor to FakeAndroidDevice.', |
| ); |
| } |
| return onGetLogReader!(); |
| } |
| |
| @override |
| OverrideArtifacts? get artifactOverrides => null; |
| |
| @override |
| final PlatformType platformType = PlatformType.android; |
| |
| @override |
| Category get category => Category.mobile; |
| |
| @override |
| bool get ephemeral => true; |
| } |
| |
| // Unfortunately Device, despite not being immutable, has an `operator ==`. |
| // Until we fix that, we have to also ignore related lints here. |
| // ignore: avoid_implementing_value_types |
| class FakeIOSDevice extends Fake implements IOSDevice { |
| FakeIOSDevice({ |
| DevicePortForwarder? portForwarder, |
| DeviceLogReader? logReader, |
| this.onGetLogReader, |
| }) : _portForwarder = portForwarder, _logReader = logReader; |
| |
| final DevicePortForwarder? _portForwarder; |
| |
| @override |
| DevicePortForwarder get portForwarder => _portForwarder!; |
| |
| @override |
| DartDevelopmentService get dds => throw UnimplementedError('getter dds not implemented'); |
| |
| final DeviceLogReader? _logReader; |
| DeviceLogReader get logReader => _logReader!; |
| |
| final DeviceLogReader Function()? onGetLogReader; |
| |
| @override |
| DeviceLogReader getLogReader({ |
| IOSApp? app, |
| bool includePastLogs = false, |
| }) { |
| if (onGetLogReader == null) { |
| throw UnimplementedError( |
| 'Called getLogReader but no onGetLogReader callback was supplied in the constructor to FakeIOSDevice', |
| ); |
| } |
| return onGetLogReader!(); |
| } |
| |
| @override |
| OverrideArtifacts? get artifactOverrides => null; |
| |
| @override |
| final String name = 'name'; |
| |
| @override |
| Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios; |
| |
| @override |
| final PlatformType platformType = PlatformType.ios; |
| |
| @override |
| bool isSupported() => true; |
| |
| @override |
| bool isSupportedForProject(FlutterProject project) => true; |
| } |
| |
| class FakeMDnsClient extends Fake implements MDnsClient { |
| FakeMDnsClient(this.ptrRecords, this.srvResponse, { |
| this.txtResponse = const <String, List<TxtResourceRecord>>{}, |
| this.osErrorOnStart = false, |
| }); |
| |
| final List<PtrResourceRecord> ptrRecords; |
| final Map<String, List<SrvResourceRecord>> srvResponse; |
| final Map<String, List<TxtResourceRecord>> txtResponse; |
| final bool osErrorOnStart; |
| |
| @override |
| Future<void> start({ |
| InternetAddress? listenAddress, |
| NetworkInterfacesFactory? interfacesFactory, |
| int mDnsPort = 5353, |
| InternetAddress? mDnsAddress, |
| }) async { |
| if (osErrorOnStart) { |
| throw const OSError('Operation not supported on socket', 102); |
| } |
| } |
| |
| @override |
| Stream<T> lookup<T extends ResourceRecord>( |
| ResourceRecordQuery query, { |
| Duration timeout = const Duration(seconds: 5), |
| }) { |
| if (T == PtrResourceRecord && query.fullyQualifiedName == MDnsObservatoryDiscovery.dartObservatoryName) { |
| return Stream<PtrResourceRecord>.fromIterable(ptrRecords) as Stream<T>; |
| } |
| if (T == SrvResourceRecord) { |
| final String key = query.fullyQualifiedName; |
| return Stream<SrvResourceRecord>.fromIterable(srvResponse[key] ?? <SrvResourceRecord>[]) as Stream<T>; |
| } |
| if (T == TxtResourceRecord) { |
| final String key = query.fullyQualifiedName; |
| return Stream<TxtResourceRecord>.fromIterable(txtResponse[key] ?? <TxtResourceRecord>[]) as Stream<T>; |
| } |
| throw UnsupportedError('Unsupported query type $T'); |
| } |
| |
| @override |
| void stop() {} |
| } |
| |
| class TestDeviceManager extends DeviceManager { |
| TestDeviceManager({required this.logger}) : super(logger: logger); |
| List<Device> devices = <Device>[]; |
| |
| final BufferLogger logger; |
| |
| @override |
| List<DeviceDiscovery> get deviceDiscoverers { |
| final FakePollingDeviceDiscovery discoverer = FakePollingDeviceDiscovery(); |
| devices.forEach(discoverer.addDevice); |
| return <DeviceDiscovery>[discoverer]; |
| } |
| } |
| |
| class FakeTerminal extends Fake implements AnsiTerminal { |
| FakeTerminal({this.stdinHasTerminal = true}); |
| |
| @override |
| final bool stdinHasTerminal; |
| } |