From 2a73ce9b50b2b3c6d4c4b5d0763dcef6b971e43c Mon Sep 17 00:00:00 2001 From: Lau Ching Jun Date: Fri, 23 Sep 2022 17:10:35 -0400 Subject: [PATCH] Refactor `DeviceManager.findTargetDevices()` and `FlutterCommand.findAllTargetDevices()`, and add a flag to not show prompt. (#112223) --- .../flutter_tools/lib/src/context_runner.dart | 1 - packages/flutter_tools/lib/src/device.dart | 129 ++++------- .../lib/src/flutter_device_manager.dart | 4 +- .../lib/src/runner/flutter_command.dart | 131 +++++++---- .../commands.shard/hermetic/attach_test.dart | 80 +++++-- .../commands.shard/hermetic/devices_test.dart | 6 +- .../commands.shard/hermetic/drive_test.dart | 2 +- .../commands.shard/hermetic/run_test.dart | 134 ++++------- .../commands.shard/hermetic/test_test.dart | 4 +- .../test/general.shard/device_test.dart | 210 +++++------------- .../runner/flutter_command_test.dart | 167 ++++++++++++++ packages/flutter_tools/test/src/context.dart | 2 +- 12 files changed, 477 insertions(+), 393 deletions(-) diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index 020efc5f7e..36069c21ae 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart @@ -205,7 +205,6 @@ Future runInContext( ), fuchsiaSdk: globals.fuchsiaSdk!, operatingSystemUtils: globals.os, - terminal: globals.terminal, customDevicesConfig: globals.customDevicesConfig, ), DevtoolsLauncher: () => DevtoolsServerLauncher( diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 9eeef33db1..9e6017e97f 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -9,13 +9,10 @@ import 'package:meta/meta.dart'; import 'application_package.dart'; import 'artifacts.dart'; -import 'base/common.dart'; import 'base/context.dart'; import 'base/dds.dart'; import 'base/file_system.dart'; import 'base/logger.dart'; -import 'base/terminal.dart'; -import 'base/user_messages.dart' hide userMessages; import 'base/utils.dart'; import 'build_info.dart'; import 'devfs.dart'; @@ -83,15 +80,9 @@ class PlatformType { abstract class DeviceManager { DeviceManager({ required Logger logger, - required Terminal terminal, - required UserMessages userMessages, - }) : _logger = logger, - _terminal = terminal, - _userMessages = userMessages; + }) : _logger = logger; final Logger _logger; - final Terminal _terminal; - final UserMessages _userMessages; /// Constructing DeviceManagers is cheap; they only do expensive work if some /// of their methods are called. @@ -219,7 +210,12 @@ abstract class DeviceManager { ]; } - /// Find and return a list of devices based on the current project and environment. + /// Find and return all target [Device]s based upon currently connected + /// devices, the current project, and criteria entered by the user on + /// the command line. + /// + /// If no device can be found that meets specified criteria, + /// then print an error message and return null. /// /// Returns a list of devices specified by the user. /// @@ -233,9 +229,13 @@ abstract class DeviceManager { /// device connected, then filter out unsupported devices and prioritize /// ephemeral devices. /// - /// * If [flutterProject] is null, then assume the project supports all - /// device types. - Future> findTargetDevices(FlutterProject? flutterProject, { Duration? timeout }) async { + /// * If [promptUserToChooseDevice] is true, and there are more than one + /// device after the aforementioned filters, and the user is connected to a + /// terminal, then show a prompt asking the user to choose one. + Future> findTargetDevices( + FlutterProject? flutterProject, { + Duration? timeout, + }) async { if (timeout != null) { // Reset the cache with the specified timeout. await refreshAllConnectedDevices(timeout: timeout); @@ -244,97 +244,56 @@ abstract class DeviceManager { List devices = (await getDevices()) .where((Device device) => device.isSupported()).toList(); - // Always remove web and fuchsia devices from `--all`. This setting - // currently requires devices to share a frontend_server and resident - // runner instance. Both web and fuchsia require differently configured - // compilers, and web requires an entirely different resident runner. if (hasSpecifiedAllDevices) { + // User has specified `--device all`. + // + // Always remove web and fuchsia devices from `--all`. This setting + // currently requires devices to share a frontend_server and resident + // runner instance. Both web and fuchsia require differently configured + // compilers, and web requires an entirely different resident runner. devices = [ for (final Device device in devices) if (await device.targetPlatform != TargetPlatform.fuchsia_arm64 && await device.targetPlatform != TargetPlatform.fuchsia_x64 && - await device.targetPlatform != TargetPlatform.web_javascript) + await device.targetPlatform != TargetPlatform.web_javascript && + isDeviceSupportedForProject(device, flutterProject)) device, ]; - } + } else if (!hasSpecifiedDeviceId) { + // User did not specify the device. - // If there is no specified device, the remove all devices which are not - // supported by the current application. For example, if there was no - // 'android' folder then don't attempt to launch with an Android device. - if (devices.length > 1 && !hasSpecifiedDeviceId) { + // Remove all devices which are not supported by the current application. + // For example, if there was no 'android' folder then don't attempt to + // launch with an Android device. devices = [ for (final Device device in devices) if (isDeviceSupportedForProject(device, flutterProject)) device, ]; - } else if (devices.length == 1 && - !hasSpecifiedDeviceId && - !isDeviceSupportedForProject(devices.single, flutterProject)) { - // If there is only a single device but it is not supported, then return - // early. - return []; - } - // If there are still multiple devices and the user did not specify to run - // all, then attempt to prioritize ephemeral devices. For example, if the - // user only typed 'flutter run' and both an Android device and desktop - // device are available, choose the Android device. - if (devices.length > 1 && !hasSpecifiedAllDevices) { - // Note: ephemeral is nullable for device types where this is not well - // defined. - if (devices.any((Device device) => device.ephemeral == true)) { - // if there is only one ephemeral device, get it - final List ephemeralDevices = devices - .where((Device device) => device.ephemeral == true) - .toList(); + if (devices.length > 1) { + // If there are still multiple devices and the user did not specify to run + // all, then attempt to prioritize ephemeral devices. For example, if the + // user only typed 'flutter run' and both an Android device and desktop + // device are available, choose the Android device. - if (ephemeralDevices.length == 1) { - devices = ephemeralDevices; - } - } - // If it was not able to prioritize a device. For example, if the user - // has two active Android devices running, then we request the user to - // choose one. If the user has two nonEphemeral devices running, we also - // request input to choose one. - if (devices.length > 1 && _terminal.stdinHasTerminal) { - _logger.printStatus(_userMessages.flutterMultipleDevicesFound); - await Device.printDevices(devices, _logger); - final Device chosenDevice = await _chooseOneOfAvailableDevices(devices); - specifiedDeviceId = chosenDevice.id; - devices = [chosenDevice]; + // Note: ephemeral is nullable for device types where this is not well + // defined. + final List ephemeralDevices = [ + for (final Device device in devices) + if (device.ephemeral == true) + device, + ]; + + if (ephemeralDevices.length == 1) { + devices = ephemeralDevices; + } } } + return devices; } - Future _chooseOneOfAvailableDevices(List devices) async { - _displayDeviceOptions(devices); - final String userInput = await _readUserInput(devices.length); - if (userInput.toLowerCase() == 'q') { - throwToolExit(''); - } - return devices[int.parse(userInput) - 1]; - } - - void _displayDeviceOptions(List devices) { - int count = 1; - for (final Device device in devices) { - _logger.printStatus(_userMessages.flutterChooseDevice(count, device.name, device.id)); - count++; - } - } - - Future _readUserInput(int deviceCount) async { - _terminal.usesTerminalUi = true; - final String result = await _terminal.promptForCharInput( - [ for (int i = 0; i < deviceCount; i++) '${i + 1}', 'q', 'Q'], - displayAcceptedCharacters: false, - logger: _logger, - prompt: _userMessages.flutterChooseOne, - ); - return result; - } - /// Returns whether the device is supported for the project. /// /// This exists to allow the check to be overridden for google3 clients. If diff --git a/packages/flutter_tools/lib/src/flutter_device_manager.dart b/packages/flutter_tools/lib/src/flutter_device_manager.dart index f30a0da5c8..47411190ca 100644 --- a/packages/flutter_tools/lib/src/flutter_device_manager.dart +++ b/packages/flutter_tools/lib/src/flutter_device_manager.dart @@ -11,6 +11,7 @@ import 'artifacts.dart'; import 'base/file_system.dart'; import 'base/os.dart'; import 'base/platform.dart'; +import 'base/user_messages.dart'; import 'custom_devices/custom_device.dart'; import 'custom_devices/custom_devices_config.dart'; import 'device.dart'; @@ -51,10 +52,9 @@ class FlutterDeviceManager extends DeviceManager { required Artifacts artifacts, required MacOSWorkflow macOSWorkflow, required FuchsiaSdk fuchsiaSdk, - required super.userMessages, + required UserMessages userMessages, required OperatingSystemUtils operatingSystemUtils, required WindowsWorkflow windowsWorkflow, - required super.terminal, required CustomDevicesConfig customDevicesConfig, }) : deviceDiscoverers = [ AndroidDevices( diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index 41eee55e51..b6c628b577 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -1413,52 +1413,105 @@ abstract class FlutterCommand extends Command { timeout: deviceDiscoveryTimeout, ); - if (devices.isEmpty && deviceManager.hasSpecifiedDeviceId) { - globals.printStatus(userMessages.flutterNoMatchingDevice(deviceManager.specifiedDeviceId!)); - final List allDevices = await deviceManager.getAllConnectedDevices(); - if (allDevices.isNotEmpty) { - globals.printStatus(''); - globals.printStatus('The following devices were found:'); - await Device.printDevices(allDevices, globals.logger); - } - return null; - } else if (devices.isEmpty) { - if (deviceManager.hasSpecifiedAllDevices) { - globals.printStatus(userMessages.flutterNoDevicesFound); - } else { - globals.printStatus(userMessages.flutterNoSupportedDevices); - } - final List unsupportedDevices = await deviceManager.getDevices(); - if (unsupportedDevices.isNotEmpty) { - final StringBuffer result = StringBuffer(); - result.writeln(userMessages.flutterFoundButUnsupportedDevices); - result.writeAll( - (await Device.descriptions(unsupportedDevices)) - .map((String desc) => desc) - .toList(), - '\n', - ); - result.writeln(); - result.writeln(userMessages.flutterMissPlatformProjects( - Device.devicesPlatformTypes(unsupportedDevices), - )); - globals.printStatus(result.toString()); - } - return null; - } else if (devices.length > 1 && !deviceManager.hasSpecifiedAllDevices) { + if (devices.isEmpty) { if (deviceManager.hasSpecifiedDeviceId) { - globals.printStatus(userMessages.flutterFoundSpecifiedDevices(devices.length, deviceManager.specifiedDeviceId!)); + globals.logger.printStatus(userMessages.flutterNoMatchingDevice(deviceManager.specifiedDeviceId!)); + final List allDevices = await deviceManager.getAllConnectedDevices(); + if (allDevices.isNotEmpty) { + globals.logger.printStatus(''); + globals.logger.printStatus('The following devices were found:'); + await Device.printDevices(allDevices, globals.logger); + } + return null; + } else if (deviceManager.hasSpecifiedAllDevices) { + globals.logger.printStatus(userMessages.flutterNoDevicesFound); + await _printUnsupportedDevice(deviceManager); + return null; } else { - globals.printStatus(userMessages.flutterSpecifyDeviceWithAllOption); - devices = await deviceManager.getAllConnectedDevices(); + globals.logger.printStatus(userMessages.flutterNoSupportedDevices); + await _printUnsupportedDevice(deviceManager); + return null; + } + } else if (devices.length > 1) { + if (deviceManager.hasSpecifiedDeviceId) { + globals.logger.printStatus(userMessages.flutterFoundSpecifiedDevices(devices.length, deviceManager.specifiedDeviceId!)); + return null; + } else if (!deviceManager.hasSpecifiedAllDevices) { + if (globals.terminal.stdinHasTerminal) { + // If DeviceManager was not able to prioritize a device. For example, if the user + // has two active Android devices running, then we request the user to + // choose one. If the user has two nonEphemeral devices running, we also + // request input to choose one. + globals.logger.printStatus(userMessages.flutterMultipleDevicesFound); + await Device.printDevices(devices, globals.logger); + final Device chosenDevice = await _chooseOneOfAvailableDevices(devices); + + // Update the [DeviceManager.specifiedDeviceId] so that we will not be prompted again. + deviceManager.specifiedDeviceId = chosenDevice.id; + + devices = [chosenDevice]; + } else { + // Show an error message asking the user to specify `-d all` if they + // want to run on multiple devices. + final List allDevices = await deviceManager.getAllConnectedDevices(); + globals.logger.printStatus(userMessages.flutterSpecifyDeviceWithAllOption); + globals.logger.printStatus(''); + await Device.printDevices(allDevices, globals.logger); + return null; + } } - globals.printStatus(''); - await Device.printDevices(devices, globals.logger); - return null; } + return devices; } + Future _printUnsupportedDevice(DeviceManager deviceManager) async { + final List unsupportedDevices = await deviceManager.getDevices(); + if (unsupportedDevices.isNotEmpty) { + final StringBuffer result = StringBuffer(); + result.writeln(userMessages.flutterFoundButUnsupportedDevices); + result.writeAll( + (await Device.descriptions(unsupportedDevices)) + .map((String desc) => desc) + .toList(), + '\n', + ); + result.writeln(); + result.writeln(userMessages.flutterMissPlatformProjects( + Device.devicesPlatformTypes(unsupportedDevices), + )); + globals.logger.printStatus(result.toString()); + } + } + + Future _chooseOneOfAvailableDevices(List devices) async { + _displayDeviceOptions(devices); + final String userInput = await _readUserInput(devices.length); + if (userInput.toLowerCase() == 'q') { + throwToolExit(''); + } + return devices[int.parse(userInput) - 1]; + } + + void _displayDeviceOptions(List devices) { + int count = 1; + for (final Device device in devices) { + globals.logger.printStatus(userMessages.flutterChooseDevice(count, device.name, device.id)); + count++; + } + } + + Future _readUserInput(int deviceCount) async { + globals.terminal.usesTerminalUi = true; + final String result = await globals.terminal.promptForCharInput( + [ for (int i = 0; i < deviceCount; i++) '${i + 1}', 'q', 'Q'], + displayAcceptedCharacters: false, + logger: globals.logger, + prompt: userMessages.flutterChooseOne, + ); + return result; + } + /// Find and return the target [Device] based upon currently connected /// devices and criteria entered by the user on the command line. /// If a device cannot be found that meets specified criteria, 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 0610be0b62..4c7482befd 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart @@ -46,6 +46,7 @@ void main() { group('attach', () { late StreamLogger logger; late FileSystem testFileSystem; + late TestDeviceManager testDeviceManager; setUp(() { Cache.disableLocking(); @@ -57,6 +58,7 @@ void main() { ); 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', () { @@ -92,7 +94,7 @@ void main() { }, ); - testDeviceManager.addDevice(device); + testDeviceManager.devices = [device]; final Completer completer = Completer(); final StreamSubscription loggerSubscription = logger.stream.listen((String message) { if (message == '[verbose] Observatory URL on device: http://127.0.0.1:$devicePort') { @@ -113,6 +115,7 @@ void main() { FileSystem: () => testFileSystem, ProcessManager: () => FakeProcessManager.any(), Logger: () => logger, + DeviceManager: () => testDeviceManager, MDnsObservatoryDiscovery: () => MDnsObservatoryDiscovery( mdnsClient: FakeMDnsClient([], >{}), logger: logger, @@ -126,7 +129,7 @@ void main() { fakeLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:$devicePort'); return fakeLogReader; }; - testDeviceManager.addDevice(device); + testDeviceManager.devices = [device]; final Completer completer = Completer(); final StreamSubscription loggerSubscription = logger.stream.listen((String message) { if (message == '[verbose] Observatory URL on device: http://127.0.0.1:$devicePort') { @@ -147,6 +150,7 @@ void main() { FileSystem: () => testFileSystem, ProcessManager: () => FakeProcessManager.any(), Logger: () => logger, + DeviceManager: () => testDeviceManager, }); testUsingContext('Fails with tool exit on bad Observatory uri', () async { @@ -156,12 +160,13 @@ void main() { fakeLogReader.dispose(); return fakeLogReader; }; - testDeviceManager.addDevice(device); + testDeviceManager.devices = [device]; expect(() => createTestCommandRunner(AttachCommand()).run(['attach']), throwsToolExit()); }, overrides: { FileSystem: () => testFileSystem, ProcessManager: () => FakeProcessManager.any(), Logger: () => logger, + DeviceManager: () => testDeviceManager, }); testUsingContext('accepts filesystem parameters', () async { @@ -170,7 +175,7 @@ void main() { fakeLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:$devicePort'); return fakeLogReader; }; - testDeviceManager.addDevice(device); + testDeviceManager.devices = [device]; const String filesystemScheme = 'foo'; const String filesystemRoot = '/build-output/'; @@ -221,10 +226,11 @@ void main() { }, overrides: { FileSystem: () => testFileSystem, ProcessManager: () => FakeProcessManager.any(), + DeviceManager: () => testDeviceManager, }); testUsingContext('exits when ipv6 is specified and debug-port is not', () async { - testDeviceManager.addDevice(device); + testDeviceManager.devices = [device]; final AttachCommand command = AttachCommand(); await expectLater( @@ -237,6 +243,7 @@ void main() { }, overrides: { FileSystem: () => testFileSystem, ProcessManager: () => FakeProcessManager.any(), + DeviceManager: () => testDeviceManager, },); testUsingContext('exits when observatory-port is specified and debug-port is not', () async { @@ -245,7 +252,7 @@ void main() { fakeLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:$devicePort'); return fakeLogReader; }; - testDeviceManager.addDevice(device); + testDeviceManager.devices = [device]; final AttachCommand command = AttachCommand(); await expectLater( @@ -258,6 +265,7 @@ void main() { }, overrides: { FileSystem: () => testFileSystem, ProcessManager: () => FakeProcessManager.any(), + DeviceManager: () => testDeviceManager, },); }); @@ -276,7 +284,7 @@ void main() { }); testUsingContext('succeeds in ipv4 mode', () async { - testDeviceManager.addDevice(device); + testDeviceManager.devices = [device]; final Completer completer = Completer(); final StreamSubscription loggerSubscription = logger.stream.listen((String message) { @@ -298,10 +306,11 @@ void main() { FileSystem: () => testFileSystem, ProcessManager: () => FakeProcessManager.any(), Logger: () => logger, + DeviceManager: () => testDeviceManager, }); testUsingContext('succeeds in ipv6 mode', () async { - testDeviceManager.addDevice(device); + testDeviceManager.devices = [device]; final Completer completer = Completer(); final StreamSubscription loggerSubscription = logger.stream.listen((String message) { @@ -324,10 +333,11 @@ void main() { FileSystem: () => testFileSystem, ProcessManager: () => FakeProcessManager.any(), Logger: () => logger, + DeviceManager: () => testDeviceManager, }); testUsingContext('skips in ipv4 mode with a provided observatory port', () async { - testDeviceManager.addDevice(device); + testDeviceManager.devices = [device]; final Completer completer = Completer(); final StreamSubscription loggerSubscription = logger.stream.listen((String message) { @@ -359,10 +369,11 @@ void main() { FileSystem: () => testFileSystem, ProcessManager: () => FakeProcessManager.any(), Logger: () => logger, + DeviceManager: () => testDeviceManager, }); testUsingContext('skips in ipv6 mode with a provided observatory port', () async { - testDeviceManager.addDevice(device); + testDeviceManager.devices = [device]; final Completer completer = Completer(); final StreamSubscription loggerSubscription = logger.stream.listen((String message) { @@ -395,6 +406,7 @@ void main() { FileSystem: () => testFileSystem, ProcessManager: () => FakeProcessManager.any(), Logger: () => logger, + DeviceManager: () => testDeviceManager, }); }); @@ -408,11 +420,12 @@ void main() { }, overrides: { FileSystem: () => testFileSystem, ProcessManager: () => FakeProcessManager.any(), + DeviceManager: () => testDeviceManager, }); testUsingContext('fails when targeted device is not Android with --device-user', () async { final FakeIOSDevice device = FakeIOSDevice(); - testDeviceManager.addDevice(device); + testDeviceManager.devices = [device]; expect(createTestCommandRunner(AttachCommand()).run([ 'attach', '--device-user', @@ -421,12 +434,15 @@ void main() { }, overrides: { FileSystem: () => testFileSystem, ProcessManager: () => FakeProcessManager.any(), + DeviceManager: () => testDeviceManager, }); testUsingContext('exits when multiple devices connected', () async { final AttachCommand command = AttachCommand(); - testDeviceManager.addDevice(FakeAndroidDevice(id: 'xx1')); - testDeviceManager.addDevice(FakeAndroidDevice(id: 'yy2')); + testDeviceManager.devices = [ + FakeAndroidDevice(id: 'xx1'), + FakeAndroidDevice(id: 'yy2'), + ]; await expectLater( createTestCommandRunner(command).run(['attach']), throwsToolExit(), @@ -438,6 +454,8 @@ void main() { }, overrides: { FileSystem: () => testFileSystem, ProcessManager: () => FakeProcessManager.any(), + DeviceManager: () => testDeviceManager, + AnsiTerminal: () => FakeTerminal(stdinHasTerminal: false), }); testUsingContext('Catches service disappeared error', () async { @@ -457,7 +475,7 @@ void main() { throw vm_service.RPCError('flutter._listViews', RPCErrorCodes.kServiceDisappeared, ''); }; - testDeviceManager.addDevice(device); + testDeviceManager.devices = [device]; testFileSystem.file('lib/main.dart').createSync(); final AttachCommand command = AttachCommand(hotRunnerFactory: hotRunnerFactory); @@ -467,6 +485,7 @@ void main() { }, overrides: { FileSystem: () => testFileSystem, ProcessManager: () => FakeProcessManager.any(), + DeviceManager: () => testDeviceManager, }); testUsingContext('Does not catch generic RPC error', () async { @@ -487,7 +506,7 @@ void main() { throw vm_service.RPCError('flutter._listViews', RPCErrorCodes.kInvalidParams, ''); }; - testDeviceManager.addDevice(device); + testDeviceManager.devices = [device]; testFileSystem.file('lib/main.dart').createSync(); final AttachCommand command = AttachCommand(hotRunnerFactory: hotRunnerFactory); @@ -497,6 +516,7 @@ void main() { }, overrides: { FileSystem: () => testFileSystem, ProcessManager: () => FakeProcessManager.any(), + DeviceManager: () => testDeviceManager, }); }); } @@ -791,6 +811,9 @@ class FakeAndroidDevice extends Fake implements AndroidDevice { @override Category get category => Category.mobile; + + @override + bool get ephemeral => true; } // Unfortunately Device, despite not being immutable, has an `operator ==`. @@ -840,6 +863,12 @@ class FakeIOSDevice extends Fake implements IOSDevice { @override final PlatformType platformType = PlatformType.ios; + + @override + bool isSupported() => true; + + @override + bool isSupportedForProject(FlutterProject project) => true; } class FakeMDnsClient extends Fake implements MDnsClient { @@ -887,3 +916,24 @@ class FakeMDnsClient extends Fake implements MDnsClient { @override void stop() {} } + +class TestDeviceManager extends DeviceManager { + TestDeviceManager({required this.logger}) : super(logger: logger); + List devices = []; + + final BufferLogger logger; + + @override + List get deviceDiscoverers { + final FakePollingDeviceDiscovery discoverer = FakePollingDeviceDiscovery(); + devices.forEach(discoverer.addDevice); + return [discoverer]; + } +} + +class FakeTerminal extends Fake implements AnsiTerminal { + FakeTerminal({this.stdinHasTerminal = true}); + + @override + final bool stdinHasTerminal; +} diff --git a/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart index 46bfb7fbe4..cc0e3d4d5d 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart @@ -6,8 +6,6 @@ import 'dart:convert'; import 'package:flutter_tools/src/android/android_sdk.dart'; import 'package:flutter_tools/src/artifacts.dart'; -import 'package:flutter_tools/src/base/terminal.dart'; -import 'package:flutter_tools/src/base/user_messages.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/devices.dart'; import 'package:flutter_tools/src/device.dart'; @@ -133,7 +131,7 @@ webby (mobile) • webby • web-javascript • Web SDK (1.2.4) (emulato } class _FakeDeviceManager extends DeviceManager { - _FakeDeviceManager() : super(logger: testLogger, terminal: Terminal.test(), userMessages: userMessages); + _FakeDeviceManager() : super(logger: testLogger); @override Future> getAllConnectedDevices() => @@ -153,7 +151,7 @@ class _FakeDeviceManager extends DeviceManager { } class NoDevicesManager extends DeviceManager { - NoDevicesManager() : super(logger: testLogger, terminal: Terminal.test(), userMessages: userMessages); + NoDevicesManager() : super(logger: testLogger); @override Future> getAllConnectedDevices() async => []; 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 966d552503..224d18c583 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart @@ -328,7 +328,7 @@ class FakeDeviceManager extends Fake implements DeviceManager { Future> getDevices() async => devices; @override - Future> findTargetDevices(FlutterProject? flutterProject, {Duration? timeout}) async => devices; + Future> findTargetDevices(FlutterProject? flutterProject, {Duration? timeout, bool promptUserToChooseDevice = true}) async => devices; } class FailingFakeFlutterDriverFactory extends Fake implements FlutterDriverFactory { 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 8ad2c8787d..ecdac200c8 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart @@ -44,11 +44,11 @@ void main() { }); group('run', () { - late FakeDeviceManager mockDeviceManager; + late TestDeviceManager testDeviceManager; late FileSystem fileSystem; setUp(() { - mockDeviceManager = FakeDeviceManager(); + testDeviceManager = TestDeviceManager(logger: BufferLogger.test()); fileSystem = MemoryFileSystem.test(); }); @@ -166,9 +166,7 @@ void main() { testUsingContext('exits with a user message when no supported devices attached', () async { final RunCommand command = RunCommand(); - mockDeviceManager - ..devices = [] - ..targetDevices = []; + testDeviceManager.devices = []; await expectLater( () => createTestCommandRunner(command).run([ @@ -184,7 +182,7 @@ void main() { containsIgnoringWhitespace(userMessages.flutterNoSupportedDevices), ); }, overrides: { - DeviceManager: () => mockDeviceManager, + DeviceManager: () => testDeviceManager, FileSystem: () => fs, ProcessManager: () => FakeProcessManager.any(), Cache: () => Cache.test(processManager: FakeProcessManager.any()), @@ -193,9 +191,9 @@ void main() { testUsingContext('exits and lists available devices when specified device not found', () async { final RunCommand command = RunCommand(); final FakeDevice device = FakeDevice(isLocalEmulator: true); - mockDeviceManager + testDeviceManager ..devices = [device] - ..hasSpecifiedDeviceId = true; + ..specifiedDeviceId = 'invalid-device-id'; await expectLater( () => createTestCommandRunner(command).run([ @@ -211,7 +209,7 @@ void main() { expect(testLogger.statusText, contains('The following devices were found:')); expect(testLogger.statusText, contains('FakeDevice (mobile) • fake_device • ios • (simulator)')); }, overrides: { - DeviceManager: () => mockDeviceManager, + DeviceManager: () => testDeviceManager, FileSystem: () => fs, ProcessManager: () => FakeProcessManager.any(), Cache: () => Cache.test(processManager: FakeProcessManager.any()), @@ -220,9 +218,7 @@ void main() { testUsingContext('fails when targeted device is not Android with --device-user', () async { final FakeDevice device = FakeDevice(isLocalEmulator: true); - mockDeviceManager - ..devices = [device] - ..targetDevices = [device]; + testDeviceManager.devices = [device]; final TestRunCommandThatOnlyValidates command = TestRunCommandThatOnlyValidates(); await expectLater(createTestCommandRunner(command).run([ @@ -234,7 +230,7 @@ void main() { }, overrides: { FileSystem: () => fs, ProcessManager: () => FakeProcessManager.any(), - DeviceManager: () => mockDeviceManager, + DeviceManager: () => testDeviceManager, Stdio: () => FakeStdio(), Cache: () => Cache.test(processManager: FakeProcessManager.any()), }); @@ -242,9 +238,7 @@ void main() { testUsingContext('succeeds when targeted device is an Android device with --device-user', () async { final FakeDevice device = FakeDevice(isLocalEmulator: true, platformType: PlatformType.android); - mockDeviceManager - ..devices = [device] - ..targetDevices = [device]; + testDeviceManager.devices = [device]; final TestRunCommandThatOnlyValidates command = TestRunCommandThatOnlyValidates(); await createTestCommandRunner(command).run([ @@ -257,7 +251,7 @@ void main() { }, overrides: { FileSystem: () => fs, ProcessManager: () => FakeProcessManager.any(), - DeviceManager: () => mockDeviceManager, + DeviceManager: () => testDeviceManager, Stdio: () => FakeStdio(), Cache: () => Cache.test(processManager: FakeProcessManager.any()), }); @@ -285,9 +279,7 @@ void main() { processManager: FakeProcessManager.any(), ); - mockDeviceManager - ..devices = [device] - ..targetDevices = [device]; + testDeviceManager.devices = [device]; final RunCommand command = RunCommand(); await expectLater(createTestCommandRunner(command).run([ @@ -297,7 +289,7 @@ void main() { }, overrides: { FileSystem: () => fs, ProcessManager: () => FakeProcessManager.any(), - DeviceManager: () => mockDeviceManager, + DeviceManager: () => testDeviceManager, Stdio: () => FakeStdio(), Cache: () => Cache.test(processManager: FakeProcessManager.any()), }); @@ -327,9 +319,7 @@ void main() { processManager: FakeProcessManager.any(), ); - mockDeviceManager - ..devices = [device] - ..targetDevices = [device]; + testDeviceManager.devices = [device]; final RunCommand command = RunCommand(); await expectLater(createTestCommandRunner(command).run([ @@ -339,17 +329,20 @@ void main() { }, overrides: { FileSystem: () => fs, ProcessManager: () => FakeProcessManager.any(), - DeviceManager: () => mockDeviceManager, + DeviceManager: () => testDeviceManager, Stdio: () => FakeStdio(), Cache: () => Cache.test(processManager: FakeProcessManager.any()), }); testUsingContext('shows unsupported devices when no supported devices are found', () async { final RunCommand command = RunCommand(); - final FakeDevice mockDevice = FakeDevice(targetPlatform: TargetPlatform.android_arm, isLocalEmulator: true, sdkNameAndVersion: 'api-14'); - mockDeviceManager - ..devices = [mockDevice] - ..targetDevices = []; + final FakeDevice mockDevice = FakeDevice( + targetPlatform: TargetPlatform.android_arm, + isLocalEmulator: true, + sdkNameAndVersion: 'api-14', + isSupported: false, + ); + testDeviceManager.devices = [mockDevice]; await expectLater( () => createTestCommandRunner(command).run([ @@ -377,7 +370,7 @@ void main() { ), ); }, overrides: { - DeviceManager: () => mockDeviceManager, + DeviceManager: () => testDeviceManager, FileSystem: () => fs, ProcessManager: () => FakeProcessManager.any(), Cache: () => Cache.test(processManager: FakeProcessManager.any()), @@ -389,13 +382,7 @@ void main() { sdkNameAndVersion: 'iOS 13', )..startAppSuccess = false; - mockDeviceManager - ..devices = [ - mockDevice, - ] - ..targetDevices = [ - mockDevice, - ]; + testDeviceManager.devices = [mockDevice]; // Causes swift to be detected in the analytics. fs.currentDirectory.childDirectory('ios').childFile('AppDelegate.swift').createSync(recursive: true); @@ -412,7 +399,7 @@ void main() { }, overrides: { Artifacts: () => artifacts, Cache: () => Cache.test(processManager: FakeProcessManager.any()), - DeviceManager: () => mockDeviceManager, + DeviceManager: () => testDeviceManager, FileSystem: () => fs, ProcessManager: () => FakeProcessManager.any(), Usage: () => usage, @@ -423,13 +410,7 @@ void main() { final FakeDevice mockDevice = FakeDevice(sdkNameAndVersion: 'iOS 13') ..startAppSuccess = false; - mockDeviceManager - ..devices = [ - mockDevice, - ] - ..targetDevices = [ - mockDevice, - ]; + testDeviceManager.devices = [mockDevice]; // Causes swift to be detected in the analytics. fs.currentDirectory.childDirectory('ios').childFile('AppDelegate.swift').createSync(recursive: true); @@ -451,7 +432,7 @@ void main() { AnsiTerminal: () => fakeTerminal, Artifacts: () => artifacts, Cache: () => Cache.test(processManager: FakeProcessManager.any()), - DeviceManager: () => mockDeviceManager, + DeviceManager: () => testDeviceManager, FileSystem: () => fs, ProcessManager: () => FakeProcessManager.any(), Stdio: () => FakeStdio(), @@ -462,9 +443,7 @@ void main() { testUsingContext('enables multidex by default', () async { final DaemonCapturingRunCommand command = DaemonCapturingRunCommand(); final FakeDevice device = FakeDevice(); - mockDeviceManager - ..devices = [device] - ..targetDevices = [device]; + testDeviceManager.devices = [device]; await expectLater( () => createTestCommandRunner(command).run([ @@ -480,7 +459,7 @@ void main() { }, overrides: { Artifacts: () => artifacts, Cache: () => Cache.test(processManager: FakeProcessManager.any()), - DeviceManager: () => mockDeviceManager, + DeviceManager: () => testDeviceManager, FileSystem: () => fs, ProcessManager: () => FakeProcessManager.any(), Usage: () => usage, @@ -491,9 +470,7 @@ void main() { testUsingContext('can disable multidex with --no-multidex', () async { final DaemonCapturingRunCommand command = DaemonCapturingRunCommand(); final FakeDevice device = FakeDevice(); - mockDeviceManager - ..devices = [device] - ..targetDevices = [device]; + testDeviceManager.devices = [device]; await expectLater( () => createTestCommandRunner(command).run([ @@ -510,7 +487,7 @@ void main() { }, overrides: { Artifacts: () => artifacts, Cache: () => Cache.test(processManager: FakeProcessManager.any()), - DeviceManager: () => mockDeviceManager, + DeviceManager: () => testDeviceManager, FileSystem: () => fs, ProcessManager: () => FakeProcessManager.any(), Usage: () => usage, @@ -521,9 +498,7 @@ void main() { testUsingContext('can pass --device-user', () async { final DaemonCapturingRunCommand command = DaemonCapturingRunCommand(); final FakeDevice device = FakeDevice(platformType: PlatformType.android); - mockDeviceManager - ..devices = [device] - ..targetDevices = [device]; + testDeviceManager.devices = [device]; await expectLater( () => createTestCommandRunner(command).run([ @@ -541,7 +516,7 @@ void main() { }, overrides: { Artifacts: () => artifacts, Cache: () => Cache.test(processManager: FakeProcessManager.any()), - DeviceManager: () => mockDeviceManager, + DeviceManager: () => testDeviceManager, FileSystem: () => fs, ProcessManager: () => FakeProcessManager.any(), Usage: () => usage, @@ -621,21 +596,21 @@ void main() { }); testUsingContext('should only request artifacts corresponding to connected devices', () async { - mockDeviceManager.devices = [FakeDevice(targetPlatform: TargetPlatform.android_arm)]; + testDeviceManager.devices = [FakeDevice(targetPlatform: TargetPlatform.android_arm)]; expect(await RunCommand().requiredArtifacts, unorderedEquals({ DevelopmentArtifact.universal, DevelopmentArtifact.androidGenSnapshot, })); - mockDeviceManager.devices = [FakeDevice()]; + testDeviceManager.devices = [FakeDevice()]; expect(await RunCommand().requiredArtifacts, unorderedEquals({ DevelopmentArtifact.universal, DevelopmentArtifact.iOS, })); - mockDeviceManager.devices = [ + testDeviceManager.devices = [ FakeDevice(), FakeDevice(targetPlatform: TargetPlatform.android_arm), ]; @@ -646,7 +621,7 @@ void main() { DevelopmentArtifact.androidGenSnapshot, })); - mockDeviceManager.devices = [ + testDeviceManager.devices = [ FakeDevice(targetPlatform: TargetPlatform.web_javascript), ]; @@ -655,7 +630,7 @@ void main() { DevelopmentArtifact.web, })); }, overrides: { - DeviceManager: () => mockDeviceManager, + DeviceManager: () => testDeviceManager, Cache: () => Cache.test(processManager: FakeProcessManager.any()), FileSystem: () => MemoryFileSystem.test(), ProcessManager: () => FakeProcessManager.any(), @@ -881,28 +856,11 @@ void main() { }); } -class FakeDeviceManager extends Fake implements DeviceManager { +class TestDeviceManager extends DeviceManager { + TestDeviceManager({required this.logger}) : super(logger: logger); List devices = []; - List targetDevices = []; - @override - String? specifiedDeviceId; - - @override - bool hasSpecifiedAllDevices = false; - - @override - bool hasSpecifiedDeviceId = false; - - @override - Future> getDevices() async { - return devices; - } - - @override - Future> findTargetDevices(FlutterProject? flutterProject, {Duration? timeout}) async { - return targetDevices; - } + final Logger logger; @override List get deviceDiscoverers { @@ -910,9 +868,6 @@ class FakeDeviceManager extends Fake implements DeviceManager { devices.forEach(discoverer.addDevice); return [discoverer]; } - - @override - Future> getAllConnectedDevices() async => devices; } class FakeAndroidSdk extends Fake implements AndroidSdk { @@ -929,10 +884,12 @@ class FakeDevice extends Fake implements Device { TargetPlatform targetPlatform = TargetPlatform.ios, String sdkNameAndVersion = '', PlatformType platformType = PlatformType.ios, + bool isSupported = true, }): _isLocalEmulator = isLocalEmulator, _targetPlatform = targetPlatform, _sdkNameAndVersion = sdkNameAndVersion, - _platformType = platformType; + _platformType = platformType, + _isSupported = isSupported; static const int kSuccess = 1; static const int kFailure = -1; @@ -940,6 +897,7 @@ class FakeDevice extends Fake implements Device { final bool _isLocalEmulator; final String _sdkNameAndVersion; final PlatformType _platformType; + final bool _isSupported; @override Category get category => Category.mobile; @@ -970,7 +928,7 @@ class FakeDevice extends Fake implements Device { bool supported = true; @override - bool isSupportedForProject(FlutterProject flutterProject) => true; + bool isSupportedForProject(FlutterProject flutterProject) => _isSupported; @override bool isSupported() => supported; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart index 8fa700837a..22b737aa93 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart @@ -10,8 +10,6 @@ import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; -import 'package:flutter_tools/src/base/terminal.dart'; -import 'package:flutter_tools/src/base/user_messages.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/test.dart'; import 'package:flutter_tools/src/device.dart'; @@ -851,7 +849,7 @@ class FakePackageTest implements TestWrapper { } class _FakeDeviceManager extends DeviceManager { - _FakeDeviceManager(this._connectedDevices) : super(logger: testLogger, terminal: Terminal.test(), userMessages: userMessages); + _FakeDeviceManager(this._connectedDevices) : super(logger: testLogger); final List _connectedDevices; diff --git a/packages/flutter_tools/test/general.shard/device_test.dart b/packages/flutter_tools/test/general.shard/device_test.dart index a21e3410e0..e03ed0ed67 100644 --- a/packages/flutter_tools/test/general.shard/device_test.dart +++ b/packages/flutter_tools/test/general.shard/device_test.dart @@ -7,8 +7,6 @@ import 'dart:async'; import 'package:fake_async/fake_async.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/base/user_messages.dart'; import 'package:flutter_tools/src/base/utils.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/convert.dart'; @@ -30,7 +28,6 @@ void main() { final DeviceManager deviceManager = TestDeviceManager( devices, logger: BufferLogger.test(), - terminal: Terminal.test(), ); expect(await deviceManager.getDevices(), devices); @@ -56,7 +53,6 @@ void main() { LongPollingDeviceDiscovery(), ], logger: logger, - terminal: Terminal.test(), ); Future expectDevice(String id, List expected) async { @@ -85,7 +81,6 @@ void main() { LongPollingDeviceDiscovery(), ], logger: logger, - terminal: Terminal.test(), wellKnownId: 'windows', ); @@ -114,7 +109,6 @@ void main() { ThrowingPollingDeviceDiscovery(), ], logger: logger, - terminal: Terminal.test(), ); Future expectDevice(String id, List expected) async { @@ -133,7 +127,6 @@ void main() { final TestDeviceManager deviceManager = TestDeviceManager( [device1], logger: BufferLogger.test(), - terminal: Terminal.test(), ); expect(await deviceManager.getAllConnectedDevices(), [device1]); @@ -147,7 +140,6 @@ void main() { final TestDeviceManager deviceManager = TestDeviceManager( [device1], logger: BufferLogger.test(), - terminal: Terminal.test(), ); expect(await deviceManager.refreshAllConnectedDevices(), [device1]); @@ -199,95 +191,13 @@ void main() { final DeviceManager deviceManager = TestDeviceManager( devices, logger: BufferLogger.test(), - terminal: Terminal.test(), ); final List filtered = await deviceManager.findTargetDevices(FakeFlutterProject()); expect(filtered.single, ephemeralOne); }); - testWithoutContext('choose first non-ephemeral device', () async { - final List devices = [ - nonEphemeralOne, - nonEphemeralTwo, - ]; - final FakeTerminal terminal = FakeTerminal() - ..setPrompt(['1', '2', 'q', 'Q'], '1'); - - final DeviceManager deviceManager = TestDeviceManager( - devices, - logger: BufferLogger.test(), - terminal: terminal, - ); - final List filtered = await deviceManager.findTargetDevices(FakeFlutterProject()); - - expect(filtered, [ - nonEphemeralOne, - ]); - }); - - testWithoutContext('choose second non-ephemeral device', () async { - final List devices = [ - nonEphemeralOne, - nonEphemeralTwo, - ]; - final FakeTerminal terminal = FakeTerminal() - ..setPrompt(['1', '2', 'q', 'Q'], '2'); - - final DeviceManager deviceManager = TestDeviceManager( - devices, - logger: BufferLogger.test(), - terminal: terminal, - ); - final List filtered = await deviceManager.findTargetDevices(FakeFlutterProject()); - - expect(filtered, [ - nonEphemeralTwo, - ]); - }); - - testWithoutContext('choose first ephemeral device', () async { - final List devices = [ - ephemeralOne, - ephemeralTwo, - ]; - - final FakeTerminal terminal = FakeTerminal() - ..setPrompt(['1', '2', 'q', 'Q'], '1'); - - final DeviceManager deviceManager = TestDeviceManager( - devices, - logger: BufferLogger.test(), - terminal: terminal, - ); - final List filtered = await deviceManager.findTargetDevices(FakeFlutterProject()); - - expect(filtered, [ - ephemeralOne, - ]); - }); - - testWithoutContext('choose second ephemeral device', () async { - final List devices = [ - ephemeralOne, - ephemeralTwo, - ]; - final FakeTerminal terminal = FakeTerminal() - ..setPrompt(['1', '2', 'q', 'Q'], '2'); - - final DeviceManager deviceManager = TestDeviceManager( - devices, - logger: BufferLogger.test(), - terminal: terminal, - ); - final List filtered = await deviceManager.findTargetDevices(FakeFlutterProject()); - - expect(filtered, [ - ephemeralTwo, - ]); - }); - - testWithoutContext('choose non-ephemeral device', () async { + testWithoutContext('returns all devices when multiple non ephemeral devices are found', () async { final List devices = [ ephemeralOne, ephemeralTwo, @@ -295,40 +205,19 @@ void main() { nonEphemeralTwo, ]; - final FakeTerminal terminal = FakeTerminal() - ..setPrompt(['1', '2', '3', '4', 'q', 'Q'], '3'); - final DeviceManager deviceManager = TestDeviceManager( devices, logger: BufferLogger.test(), - terminal: terminal, ); final List filtered = await deviceManager.findTargetDevices(FakeFlutterProject()); expect(filtered, [ - nonEphemeralOne, - ]); - }); - - testWithoutContext('exit from choose one of available devices', () async { - final List devices = [ ephemeralOne, ephemeralTwo, - ]; - - final FakeTerminal terminal = FakeTerminal() - ..setPrompt(['1', '2', 'q', 'Q'], 'q'); - - final DeviceManager deviceManager = TestDeviceManager( - devices, - logger: BufferLogger.test(), - terminal: terminal, - ); - await expectLater( - () async => deviceManager.findTargetDevices(FakeFlutterProject()), - throwsToolExit(), - ); + nonEphemeralOne, + nonEphemeralTwo, + ]); }); testWithoutContext('Unsupported devices listed in all connected devices', () async { @@ -340,7 +229,6 @@ void main() { final DeviceManager deviceManager = TestDeviceManager( devices, logger: BufferLogger.test(), - terminal: Terminal.test(), ); final List filtered = await deviceManager.getAllConnectedDevices(); @@ -355,11 +243,9 @@ void main() { unsupported, unsupportedForProject, ]; - final DeviceManager deviceManager = TestDeviceManager( devices, logger: BufferLogger.test(), - terminal: Terminal.test(), ); final List filtered = await deviceManager.findTargetDevices(FakeFlutterProject()); @@ -375,7 +261,6 @@ void main() { final DeviceManager deviceManager = TestDeviceManager( devices, logger: BufferLogger.test(), - terminal: Terminal.test(), ); final List filtered = await deviceManager.findTargetDevices(null); @@ -390,7 +275,6 @@ void main() { final DeviceManager deviceManager = TestDeviceManager( devices, logger: BufferLogger.test(), - terminal: Terminal.test(), ); deviceManager.specifiedDeviceId = 'all'; @@ -409,7 +293,6 @@ void main() { final DeviceManager deviceManager = TestDeviceManager( devices, logger: BufferLogger.test(), - terminal: Terminal.test(), ); deviceManager.specifiedDeviceId = 'all'; @@ -421,6 +304,57 @@ void main() { ]); }); + testWithoutContext('Returns device with the specified id', () async { + final List devices = [ + nonEphemeralOne, + ]; + final DeviceManager deviceManager = TestDeviceManager( + devices, + logger: BufferLogger.test(), + ); + deviceManager.specifiedDeviceId = nonEphemeralOne.id; + + final List filtered = await deviceManager.findTargetDevices(FakeFlutterProject()); + + expect(filtered, [ + nonEphemeralOne, + ]); + }); + + testWithoutContext('Returns multiple devices when multiple devices matches the specified id', () async { + final List devices = [ + nonEphemeralOne, + nonEphemeralTwo, + ]; + final DeviceManager deviceManager = TestDeviceManager( + devices, + logger: BufferLogger.test(), + ); + deviceManager.specifiedDeviceId = 'nonEphemeral'; // This prefix matches both devices + + final List filtered = await deviceManager.findTargetDevices(FakeFlutterProject()); + + expect(filtered, [ + nonEphemeralOne, + nonEphemeralTwo, + ]); + }); + + testWithoutContext('Returns empty when device of specified id is not found', () async { + final List devices = [ + nonEphemeralOne, + ]; + final DeviceManager deviceManager = TestDeviceManager( + devices, + logger: BufferLogger.test(), + ); + deviceManager.specifiedDeviceId = nonEphemeralTwo.id; + + final List filtered = await deviceManager.findTargetDevices(FakeFlutterProject()); + + expect(filtered, []); + }); + testWithoutContext('uses DeviceManager.isDeviceSupportedForProject instead of device.isSupportedForProject', () async { final List devices = [ unsupported, @@ -429,7 +363,6 @@ void main() { final TestDeviceManager deviceManager = TestDeviceManager( devices, logger: BufferLogger.test(), - terminal: Terminal.test(), ); deviceManager.isAlwaysSupportedForProjectOverride = true; @@ -453,7 +386,6 @@ void main() { deviceDiscovery, ], logger: BufferLogger.test(), - terminal: Terminal.test(), ); deviceManager.specifiedDeviceId = ephemeralOne.id; final List filtered = await deviceManager.findTargetDevices( @@ -479,7 +411,6 @@ void main() { deviceDiscovery, ], logger: BufferLogger.test(), - terminal: Terminal.test(), ); deviceManager.specifiedDeviceId = ephemeralOne.id; final List filtered = await deviceManager.findTargetDevices( @@ -542,11 +473,10 @@ class TestDeviceManager extends DeviceManager { List allDevices, { List? deviceDiscoveryOverrides, required super.logger, - required super.terminal, String? wellKnownId, }) : _fakeDeviceDiscoverer = FakePollingDeviceDiscovery(), _deviceDiscoverers = [], - super(userMessages: UserMessages()) { + super() { if (wellKnownId != null) { _fakeDeviceDiscoverer.wellKnownIds.add(wellKnownId); } @@ -650,31 +580,3 @@ class ThrowingPollingDeviceDiscovery extends PollingDeviceDiscovery { @override List get wellKnownIds => []; } - -class FakeTerminal extends Fake implements Terminal { - @override - bool stdinHasTerminal = true; - - @override - bool usesTerminalUi = true; - - void setPrompt(List characters, String result) { - _nextPrompt = characters; - _nextResult = result; - } - - List? _nextPrompt; - late String _nextResult; - - @override - Future promptForCharInput( - List acceptedCharacters, { - Logger? logger, - String? prompt, - int? defaultChoiceIndex, - bool displayAcceptedCharacters = true, - }) async { - expect(acceptedCharacters, _nextPrompt); - return _nextResult; - } -} diff --git a/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart b/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart index 2d7343509a..765773ed9d 100644 --- a/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart +++ b/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart @@ -11,11 +11,15 @@ import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/error_handling_io.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/signals.dart'; +import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/base/time.dart'; +import 'package:flutter_tools/src/base/user_messages.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/dart/pub.dart'; +import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/pre_run_validator.dart'; import 'package:flutter_tools/src/project.dart'; @@ -25,6 +29,7 @@ import 'package:test/fake.dart'; import '../../src/common.dart'; import '../../src/context.dart'; +import '../../src/fake_devices.dart'; import '../../src/test_flutter_command_runner.dart'; import 'utils.dart'; @@ -669,6 +674,138 @@ void main() { FileSystem: () => fileSystem, ProcessManager: () => processManager, }); + + group('findAllTargetDevices', () { + final FakeDevice device1 = FakeDevice('device1', 'device1'); + final FakeDevice device2 = FakeDevice('device2', 'device2'); + group('when specified device id', () { + testUsingContext('returns device when device is found', () async { + testDeviceManager.specifiedDeviceId = 'device-id'; + testDeviceManager.addDevice(device1); + final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); + final List? devices = await flutterCommand.findAllTargetDevices(); + expect(devices, [device1]); + }); + + testUsingContext('show error when no device found', () async { + testDeviceManager.specifiedDeviceId = 'device-id'; + final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); + final List? devices = await flutterCommand.findAllTargetDevices(); + expect(devices, null); + expect(testLogger.statusText, contains(UserMessages().flutterNoMatchingDevice('device-id'))); + }); + + testUsingContext('show error when multiple devices found', () async { + testDeviceManager.specifiedDeviceId = 'device-id'; + testDeviceManager.addDevice(device1); + testDeviceManager.addDevice(device2); + final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); + final List? devices = await flutterCommand.findAllTargetDevices(); + expect(devices, null); + expect(testLogger.statusText, contains(UserMessages().flutterFoundSpecifiedDevices(2, 'device-id'))); + }); + }); + + group('when specified all', () { + testUsingContext('can return one device', () async { + testDeviceManager.specifiedDeviceId = 'all'; + testDeviceManager.addDevice(device1); + final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); + final List? devices = await flutterCommand.findAllTargetDevices(); + expect(devices, [device1]); + }); + + testUsingContext('can return multiple devices', () async { + testDeviceManager.specifiedDeviceId = 'all'; + testDeviceManager.addDevice(device1); + testDeviceManager.addDevice(device2); + final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); + final List? devices = await flutterCommand.findAllTargetDevices(); + expect(devices, [device1, device2]); + }); + + testUsingContext('show error when no device found', () async { + testDeviceManager.specifiedDeviceId = 'all'; + final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); + final List? devices = await flutterCommand.findAllTargetDevices(); + expect(devices, null); + expect(testLogger.statusText, contains(UserMessages().flutterNoDevicesFound)); + }); + }); + + group('when device not specified', () { + testUsingContext('returns one device when only one device connected', () async { + testDeviceManager.addDevice(device1); + final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); + final List? devices = await flutterCommand.findAllTargetDevices(); + expect(devices, [device1]); + }); + + testUsingContext('show error when no device found', () async { + final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); + final List? devices = await flutterCommand.findAllTargetDevices(); + expect(devices, null); + expect(testLogger.statusText, contains(UserMessages().flutterNoSupportedDevices)); + }); + + testUsingContext('show error when multiple devices found and not connected to terminal', () async { + testDeviceManager.addDevice(device1); + testDeviceManager.addDevice(device2); + final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); + final List? devices = await flutterCommand.findAllTargetDevices(); + expect(devices, null); + expect(testLogger.statusText, contains(UserMessages().flutterSpecifyDeviceWithAllOption)); + }, overrides: { + AnsiTerminal: () => FakeTerminal(stdinHasTerminal: false), + }); + + // Prompt to choose device when multiple devices found and connected to terminal + group('show prompt', () { + late FakeTerminal terminal; + setUp(() { + terminal = FakeTerminal(); + }); + + testUsingContext('choose first device', () async { + testDeviceManager.addDevice(device1); + testDeviceManager.addDevice(device2); + terminal.setPrompt(['1', '2', 'q', 'Q'], '1'); + final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); + final List? devices = await flutterCommand.findAllTargetDevices(); + + expect(devices, [device1]); + }, overrides: { + AnsiTerminal: () => terminal, + }); + + testUsingContext('choose second device', () async { + testDeviceManager.addDevice(device1); + testDeviceManager.addDevice(device2); + terminal.setPrompt(['1', '2', 'q', 'Q'], '2'); + final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); + final List? devices = await flutterCommand.findAllTargetDevices(); + + expect(devices, [device2]); + }, overrides: { + AnsiTerminal: () => terminal, + }); + + testUsingContext('exits without choosing device', () async { + testDeviceManager.addDevice(device1); + testDeviceManager.addDevice(device2); + terminal.setPrompt(['1', '2', 'q', 'Q'], 'q'); + final DummyFlutterCommand flutterCommand = DummyFlutterCommand(); + + await expectLater( + flutterCommand.findAllTargetDevices(), + throwsToolExit(), + ); + }, overrides: { + AnsiTerminal: () => terminal, + }); + }); + }); + }); }); } @@ -823,3 +960,33 @@ class FakePub extends Fake implements Pub { bool printProgress = true, }) async { } } + +class FakeTerminal extends Fake implements AnsiTerminal { + FakeTerminal({this.stdinHasTerminal = true}); + + @override + final bool stdinHasTerminal; + + @override + bool usesTerminalUi = true; + + void setPrompt(List characters, String result) { + _nextPrompt = characters; + _nextResult = result; + } + + List? _nextPrompt; + late String _nextResult; + + @override + Future promptForCharInput( + List acceptedCharacters, { + Logger? logger, + String? prompt, + int? defaultChoiceIndex, + bool displayAcceptedCharacters = true, + }) async { + expect(acceptedCharacters, _nextPrompt); + return _nextResult; + } +} diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart index d292e99c36..8a2dd5d02c 100644 --- a/packages/flutter_tools/test/src/context.dart +++ b/packages/flutter_tools/test/src/context.dart @@ -241,7 +241,7 @@ class FakeDeviceManager implements DeviceManager { } @override - Future> findTargetDevices(FlutterProject? flutterProject, { Duration? timeout }) async { + Future> findTargetDevices(FlutterProject? flutterProject, { Duration? timeout, bool promptUserToChooseDevice = true }) async { return devices; } }