Unverified Commit f567a0cc authored by Natan Portilho's avatar Natan Portilho Committed by GitHub

Device manager choose running device (#57349)

parent 42067343
...@@ -253,6 +253,9 @@ class UserMessages { ...@@ -253,6 +253,9 @@ class UserMessages {
String get flutterFoundButUnsupportedDevices => 'The following devices were found, but are not supported by this project:'; String get flutterFoundButUnsupportedDevices => 'The following devices were found, but are not supported by this project:';
String flutterFoundSpecifiedDevices(int count, String deviceId) => String flutterFoundSpecifiedDevices(int count, String deviceId) =>
'Found $count devices with name or id matching $deviceId:'; 'Found $count devices with name or id matching $deviceId:';
String get flutterMultipleDevicesFound => 'Multiple devices found:';
String flutterChooseDevice(int option, String name, String deviceId) => '[$option]: $name ($deviceId)';
String get flutterChooseOne => 'Please choose one:';
String get flutterSpecifyDeviceWithAllOption => String get flutterSpecifyDeviceWithAllOption =>
'More than one device connected; please specify a device with ' 'More than one device connected; please specify a device with '
"the '-d <deviceId>' flag, or use '-d all' to act on all devices."; "the '-d <deviceId>' flag, or use '-d all' to act on all devices.";
......
...@@ -15,6 +15,7 @@ import 'artifacts.dart'; ...@@ -15,6 +15,7 @@ import 'artifacts.dart';
import 'base/context.dart'; import 'base/context.dart';
import 'base/file_system.dart'; import 'base/file_system.dart';
import 'base/io.dart'; import 'base/io.dart';
import 'base/user_messages.dart';
import 'base/utils.dart'; import 'base/utils.dart';
import 'build_info.dart'; import 'build_info.dart';
import 'features.dart'; import 'features.dart';
...@@ -241,20 +242,59 @@ class DeviceManager { ...@@ -241,20 +242,59 @@ class DeviceManager {
// If there are still multiple devices and the user did not specify to run // 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 // all, then attempt to prioritize ephemeral devices. For example, if the
// use only typed 'flutter run' and both an Android device and desktop // user only typed 'flutter run' and both an Android device and desktop
// device are availible, choose the Android device. // device are availible, choose the Android device.
if (devices.length > 1 && !hasSpecifiedAllDevices) { if (devices.length > 1 && !hasSpecifiedAllDevices) {
// Note: ephemeral is nullable for device types where this is not well // Note: ephemeral is nullable for device types where this is not well
// defined. // defined.
if (devices.any((Device device) => device.ephemeral == true)) { if (devices.any((Device device) => device.ephemeral == true)) {
devices = devices // if there is only one ephemeral device, get it
final List<Device> ephemeralDevices = devices
.where((Device device) => device.ephemeral == true) .where((Device device) => device.ephemeral == true)
.toList(); .toList();
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 && globals.stdio.stdinHasTerminal) {
globals.printStatus(globals.userMessages.flutterMultipleDevicesFound);
await Device.printDevices(devices);
final Device chosenDevice = await _chooseOneOfAvailableDevices(devices);
deviceManager.specifiedDeviceId = chosenDevice.id;
devices = <Device>[chosenDevice];
} }
} }
return devices; return devices;
} }
Future<Device> _chooseOneOfAvailableDevices(List<Device> devices) async {
_displayDeviceOptions(devices);
final String userInput = await _readUserInput(devices.length);
return devices[int.parse(userInput)];
}
void _displayDeviceOptions(List<Device> devices) {
int count = 0;
for (final Device device in devices) {
globals.printStatus(userMessages.flutterChooseDevice(count, device.name, device.id));
count++;
}
}
Future<String> _readUserInput(int deviceCount) async {
globals.terminal.usesTerminalUi = true;
final String result = await globals.terminal.promptForCharInput(
<String>[ for (int i = 0; i < deviceCount; i++) '$i' ],
logger: globals.logger,
prompt: userMessages.flutterChooseOne);
return result;
}
/// Returns whether the device is supported for the project. /// Returns whether the device is supported for the project.
/// ///
/// This exists to allow the check to be overridden for google3 clients. /// This exists to allow the check to be overridden for google3 clients.
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
...@@ -12,6 +13,7 @@ import 'package:flutter_tools/src/base/io.dart'; ...@@ -12,6 +13,7 @@ import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/project.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:quiver/testing/async.dart'; import 'package:quiver/testing/async.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import '../src/common.dart'; import '../src/common.dart';
import '../src/context.dart'; import '../src/context.dart';
...@@ -111,15 +113,18 @@ void main() { ...@@ -111,15 +113,18 @@ void main() {
}); });
group('Filter devices', () { group('Filter devices', () {
FakeDevice ephemeral; FakeDevice ephemeralOne;
FakeDevice ephemeralTwo;
FakeDevice nonEphemeralOne; FakeDevice nonEphemeralOne;
FakeDevice nonEphemeralTwo; FakeDevice nonEphemeralTwo;
FakeDevice unsupported; FakeDevice unsupported;
FakeDevice webDevice; FakeDevice webDevice;
FakeDevice fuchsiaDevice; FakeDevice fuchsiaDevice;
MockStdio mockStdio;
setUp(() { setUp(() {
ephemeral = FakeDevice('ephemeral', 'ephemeral', true); ephemeralOne = FakeDevice('ephemeralOne', 'ephemeralOne', true);
ephemeralTwo = FakeDevice('ephemeralTwo', 'ephemeralTwo', true);
nonEphemeralOne = FakeDevice('nonEphemeralOne', 'nonEphemeralOne', false); nonEphemeralOne = FakeDevice('nonEphemeralOne', 'nonEphemeralOne', false);
nonEphemeralTwo = FakeDevice('nonEphemeralTwo', 'nonEphemeralTwo', false); nonEphemeralTwo = FakeDevice('nonEphemeralTwo', 'nonEphemeralTwo', false);
unsupported = FakeDevice('unsupported', 'unsupported', true, false); unsupported = FakeDevice('unsupported', 'unsupported', true, false);
...@@ -127,11 +132,12 @@ void main() { ...@@ -127,11 +132,12 @@ void main() {
..targetPlatform = Future<TargetPlatform>.value(TargetPlatform.web_javascript); ..targetPlatform = Future<TargetPlatform>.value(TargetPlatform.web_javascript);
fuchsiaDevice = FakeDevice('fuchsiay', 'fuchsiay') fuchsiaDevice = FakeDevice('fuchsiay', 'fuchsiay')
..targetPlatform = Future<TargetPlatform>.value(TargetPlatform.fuchsia_x64); ..targetPlatform = Future<TargetPlatform>.value(TargetPlatform.fuchsia_x64);
mockStdio = MockStdio();
}); });
testUsingContext('chooses ephemeral device', () async { testUsingContext('chooses ephemeral device', () async {
final List<Device> devices = <Device>[ final List<Device> devices = <Device>[
ephemeral, ephemeralOne,
nonEphemeralOne, nonEphemeralOne,
nonEphemeralTwo, nonEphemeralTwo,
unsupported, unsupported,
...@@ -140,26 +146,132 @@ void main() { ...@@ -140,26 +146,132 @@ void main() {
final DeviceManager deviceManager = TestDeviceManager(devices); final DeviceManager deviceManager = TestDeviceManager(devices);
final List<Device> filtered = await deviceManager.findTargetDevices(FlutterProject.current()); final List<Device> filtered = await deviceManager.findTargetDevices(FlutterProject.current());
expect(filtered.single, ephemeral); expect(filtered.single, ephemeralOne);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Artifacts: () => Artifacts.test(), Artifacts: () => Artifacts.test(),
Cache: () => cache, Cache: () => cache,
}); });
testUsingContext('does not remove all non-ephemeral', () async { testUsingContext('choose first non-ephemeral device', () async {
final List<Device> devices = <Device>[ final List<Device> devices = <Device>[
nonEphemeralOne, nonEphemeralOne,
nonEphemeralTwo, nonEphemeralTwo,
]; ];
when(mockStdio.stdinHasTerminal).thenReturn(true);
when(globals.terminal.promptForCharInput(<String>['0', '1'],
logger: globals.logger,
prompt: globals.userMessages.flutterChooseOne)
).thenAnswer((Invocation invocation) async => '0');
final DeviceManager deviceManager = TestDeviceManager(devices);
final List<Device> filtered = await deviceManager.findTargetDevices(FlutterProject.current());
expect(filtered, <Device>[
nonEphemeralOne
]);
}, overrides: <Type, Generator>{
Artifacts: () => Artifacts.test(),
Stdio: () => mockStdio,
AnsiTerminal: () => MockTerminal(),
});
testUsingContext('choose second non-ephemeral device', () async {
final List<Device> devices = <Device>[
nonEphemeralOne,
nonEphemeralTwo,
];
when(mockStdio.stdinHasTerminal).thenReturn(true);
when(globals.terminal.promptForCharInput(<String>['0', '1'],
logger: globals.logger,
prompt: globals.userMessages.flutterChooseOne)
).thenAnswer((Invocation invocation) async => '1');
final DeviceManager deviceManager = TestDeviceManager(devices);
final List<Device> filtered = await deviceManager.findTargetDevices(FlutterProject.current());
expect(filtered, <Device>[
nonEphemeralTwo
]);
}, overrides: <Type, Generator>{
Artifacts: () => Artifacts.test(),
Stdio: () => mockStdio,
AnsiTerminal: () => MockTerminal(),
});
testUsingContext('choose first ephemeral device', () async {
final List<Device> devices = <Device>[
ephemeralOne,
ephemeralTwo,
];
when(mockStdio.stdinHasTerminal).thenReturn(true);
when(globals.terminal.promptForCharInput(<String>['0', '1'],
logger: globals.logger,
prompt: globals.userMessages.flutterChooseOne)
).thenAnswer((Invocation invocation) async => '0');
final DeviceManager deviceManager = TestDeviceManager(devices);
final List<Device> filtered = await deviceManager.findTargetDevices(FlutterProject.current());
expect(filtered, <Device>[
ephemeralOne
]);
}, overrides: <Type, Generator>{
Artifacts: () => Artifacts.test(),
Stdio: () => mockStdio,
AnsiTerminal: () => MockTerminal(),
});
testUsingContext('choose second ephemeral device', () async {
final List<Device> devices = <Device>[
ephemeralOne,
ephemeralTwo,
];
when(mockStdio.stdinHasTerminal).thenReturn(true);
when(globals.terminal.promptForCharInput(<String>['0', '1'],
logger: globals.logger,
prompt: globals.userMessages.flutterChooseOne)
).thenAnswer((Invocation invocation) async => '1');
final DeviceManager deviceManager = TestDeviceManager(devices); final DeviceManager deviceManager = TestDeviceManager(devices);
final List<Device> filtered = await deviceManager.findTargetDevices(FlutterProject.current()); final List<Device> filtered = await deviceManager.findTargetDevices(FlutterProject.current());
expect(filtered, <Device>[ expect(filtered, <Device>[
ephemeralTwo
]);
}, overrides: <Type, Generator>{
Stdio: () => mockStdio,
AnsiTerminal: () => MockTerminal(),
Artifacts: () => Artifacts.test(),
Cache: () => cache,
});
testUsingContext('choose non-ephemeral device', () async {
final List<Device> devices = <Device>[
ephemeralOne,
ephemeralTwo,
nonEphemeralOne, nonEphemeralOne,
nonEphemeralTwo, nonEphemeralTwo,
];
when(mockStdio.stdinHasTerminal).thenReturn(true);
when(globals.terminal.promptForCharInput(<String>['0', '1', '2', '3'],
logger: globals.logger,
prompt: globals.userMessages.flutterChooseOne)
).thenAnswer((Invocation invocation) async => '2');
final DeviceManager deviceManager = TestDeviceManager(devices);
final List<Device> filtered = await deviceManager.findTargetDevices(FlutterProject.current());
expect(filtered, <Device>[
nonEphemeralOne
]); ]);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Stdio: () => mockStdio,
AnsiTerminal: () => MockTerminal(),
Artifacts: () => Artifacts.test(), Artifacts: () => Artifacts.test(),
Cache: () => cache, Cache: () => cache,
}); });
...@@ -286,4 +398,6 @@ class TestDeviceManager extends DeviceManager { ...@@ -286,4 +398,6 @@ class TestDeviceManager extends DeviceManager {
} }
class MockProcess extends Mock implements Process {} class MockProcess extends Mock implements Process {}
class MockTerminal extends Mock implements AnsiTerminal {}
class MockStdio extends Mock implements Stdio {}
class MockCache extends Mock implements Cache {} class MockCache extends Mock implements Cache {}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment