Add --device-timeout flag to device-related commands (#64834)
diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart index 7272767..0cde25a 100644 --- a/packages/flutter_tools/lib/src/commands/attach.dart +++ b/packages/flutter_tools/lib/src/commands/attach.dart
@@ -102,6 +102,7 @@ ); usesTrackWidgetCreation(verboseHelp: verboseHelp); addDdsOptions(verboseHelp: verboseHelp); + usesDeviceTimeoutOption(); hotRunnerFactory ??= HotRunnerFactory(); }
diff --git a/packages/flutter_tools/lib/src/commands/devices.dart b/packages/flutter_tools/lib/src/commands/devices.dart index 53c9eea..b513539 100644 --- a/packages/flutter_tools/lib/src/commands/devices.dart +++ b/packages/flutter_tools/lib/src/commands/devices.dart
@@ -22,8 +22,9 @@ 'timeout', abbr: 't', defaultsTo: null, - help: 'Time in seconds to wait for devices to attach. Longer timeouts may be necessary for networked devices.' + help: '(deprecated) Use --device-timeout instead', ); + usesDeviceTimeoutOption(); } @override @@ -32,20 +33,25 @@ @override final String description = 'List all connected devices.'; - Duration get timeout { - if (argResults['timeout'] == null) { - return null; - } - if (_timeout == null) { + @override + Duration get deviceDiscoveryTimeout { + if (argResults['timeout'] != null) { final int timeoutSeconds = int.tryParse(stringArg('timeout')); if (timeoutSeconds == null) { throwToolExit( 'Could not parse -t/--timeout argument. It must be an integer.'); } - _timeout = Duration(seconds: timeoutSeconds); + return Duration(seconds: timeoutSeconds); } - return _timeout; + return super.deviceDiscoveryTimeout; } - Duration _timeout; + + @override + Future<void> validateCommand() { + if (argResults['timeout'] != null) { + globals.printError('--timeout has been deprecated, use --${FlutterOptions.kDeviceTimeout} instead'); + } + return super.validateCommand(); + } @override Future<FlutterCommandResult> runCommand() async { @@ -56,7 +62,7 @@ exitCode: 1); } - final List<Device> devices = await globals.deviceManager.refreshAllConnectedDevices(timeout: timeout); + final List<Device> devices = await globals.deviceManager.refreshAllConnectedDevices(timeout: deviceDiscoveryTimeout); if (boolArg('machine')) { await printDevicesAsJson(devices); @@ -68,8 +74,8 @@ status.writeln('Run "flutter emulators" to list and start any available device emulators.'); status.writeln(); status.write('If you expected your device to be detected, please run "flutter doctor" to diagnose potential issues. '); - if (timeout == null) { - status.write('You may also try increasing the time to wait for connected devices with the --timeout flag. '); + if (deviceDiscoveryTimeout == null) { + status.write('You may also try increasing the time to wait for connected devices with the --${FlutterOptions.kDeviceTimeout} flag. '); } status.write('Visit https://flutter.dev/setup/ for troubleshooting tips.');
diff --git a/packages/flutter_tools/lib/src/commands/drive.dart b/packages/flutter_tools/lib/src/commands/drive.dart index 0980e68..1f9772e 100644 --- a/packages/flutter_tools/lib/src/commands/drive.dart +++ b/packages/flutter_tools/lib/src/commands/drive.dart
@@ -146,7 +146,7 @@ @override Future<void> validateCommand() async { if (userIdentifier != null) { - final Device device = await findTargetDevice(); + final Device device = await findTargetDevice(timeout: deviceDiscoveryTimeout); if (device is! AndroidDevice) { throwToolExit('--${FlutterOptions.kDeviceUser} is only supported for Android'); } @@ -161,7 +161,7 @@ throwToolExit(null); } - _device = await findTargetDevice(); + _device = await findTargetDevice(timeout: deviceDiscoveryTimeout); if (device == null) { throwToolExit(null); } @@ -388,9 +388,9 @@ } } -Future<Device> findTargetDevice() async { +Future<Device> findTargetDevice({ @required Duration timeout }) async { final DeviceManager deviceManager = globals.deviceManager; - final List<Device> devices = await deviceManager.findTargetDevices(FlutterProject.current()); + final List<Device> devices = await deviceManager.findTargetDevices(FlutterProject.current(), timeout: timeout); if (deviceManager.hasSpecifiedDeviceId) { if (devices.isEmpty) {
diff --git a/packages/flutter_tools/lib/src/commands/install.dart b/packages/flutter_tools/lib/src/commands/install.dart index 8228cbf..62fd7cd 100644 --- a/packages/flutter_tools/lib/src/commands/install.dart +++ b/packages/flutter_tools/lib/src/commands/install.dart
@@ -16,6 +16,7 @@ InstallCommand() { requiresPubspecYaml(); usesDeviceUserOption(); + usesDeviceTimeoutOption(); argParser.addFlag('uninstall-only', negatable: true, defaultsTo: false,
diff --git a/packages/flutter_tools/lib/src/commands/logs.dart b/packages/flutter_tools/lib/src/commands/logs.dart index 5b9b9cb..5305e14 100644 --- a/packages/flutter_tools/lib/src/commands/logs.dart +++ b/packages/flutter_tools/lib/src/commands/logs.dart
@@ -18,6 +18,7 @@ abbr: 'c', help: 'Clear log history before reading from logs.', ); + usesDeviceTimeoutOption(); } @override
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index fd5b739..b70a1a7 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -75,6 +75,7 @@ usesTrackWidgetCreation(verboseHelp: verboseHelp); addNullSafetyModeOptions(hide: !verboseHelp); usesDeviceUserOption(); + usesDeviceTimeoutOption(); } bool get traceStartup => boolArg('trace-startup');
diff --git a/packages/flutter_tools/lib/src/commands/screenshot.dart b/packages/flutter_tools/lib/src/commands/screenshot.dart index 3c32de7..566affd 100644 --- a/packages/flutter_tools/lib/src/commands/screenshot.dart +++ b/packages/flutter_tools/lib/src/commands/screenshot.dart
@@ -51,6 +51,7 @@ }, defaultsTo: _kDeviceType, ); + usesDeviceTimeoutOption(); } @override
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 2a16f8e..84d0a05 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart
@@ -209,7 +209,12 @@ /// * If the user did not specify a device id and there is more than one /// device connected, then filter out unsupported devices and prioritize /// ephemeral devices. - Future<List<Device>> findTargetDevices(FlutterProject flutterProject) async { + Future<List<Device>> findTargetDevices(FlutterProject flutterProject, { Duration timeout }) async { + if (timeout != null) { + // Reset the cache with the specified timeout. + await refreshAllConnectedDevices(timeout: timeout); + } + List<Device> devices = await getDevices(); // Always remove web and fuchsia devices from `--all`. This setting
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index e42548b..8fa2413 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -113,6 +113,7 @@ static const String kPerformanceMeasurementFile = 'performance-measurement-file'; static const String kNullSafety = 'sound-null-safety'; static const String kDeviceUser = 'device-user'; + static const String kDeviceTimeout = 'device-timeout'; static const String kAnalyzeSize = 'analyze-size'; static const String kNullAssertions = 'null-assertions'; } @@ -404,6 +405,28 @@ valueHelp: '10'); } + void usesDeviceTimeoutOption() { + argParser.addOption( + FlutterOptions.kDeviceTimeout, + help: 'Time in seconds to wait for devices to attach. Longer timeouts may be necessary for networked devices.', + valueHelp: '10' + ); + } + + Duration get deviceDiscoveryTimeout { + if (_deviceDiscoveryTimeout == null + && argResults.options.contains(FlutterOptions.kDeviceTimeout) + && argResults.wasParsed(FlutterOptions.kDeviceTimeout)) { + final int timeoutSeconds = int.tryParse(stringArg(FlutterOptions.kDeviceTimeout)); + if (timeoutSeconds == null) { + throwToolExit( 'Could not parse --${FlutterOptions.kDeviceTimeout} argument. It must be an integer.'); + } + _deviceDiscoveryTimeout = Duration(seconds: timeoutSeconds); + } + return _deviceDiscoveryTimeout; + } + Duration _deviceDiscoveryTimeout; + void addBuildModeFlags({ bool defaultToRelease = true, bool verboseHelp = false, bool excludeDebug = false }) { // A release build must be the default if a debug build is not possible. assert(defaultToRelease || !excludeDebug); @@ -970,7 +993,7 @@ return null; } final DeviceManager deviceManager = globals.deviceManager; - List<Device> devices = await deviceManager.findTargetDevices(FlutterProject.current()); + List<Device> devices = await deviceManager.findTargetDevices(FlutterProject.current(), timeout: deviceDiscoveryTimeout); if (devices.isEmpty && deviceManager.hasSpecifiedDeviceId) { globals.printStatus(userMessages.flutterNoMatchingDevice(deviceManager.specifiedDeviceId));
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 c2a94e1..c820e5a 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
@@ -342,6 +342,8 @@ '-v', '--device-user', '10', + '--device-timeout', + '15', ]); final VerificationResult verificationResult = verify( mockHotRunnerFactory.build(
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 8a36a9e..7f33308 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart
@@ -281,7 +281,7 @@ when(mockDevice.name).thenReturn('specified-device'); when(mockDevice.id).thenReturn('123'); - final Device device = await findTargetDevice(); + final Device device = await findTargetDevice(timeout: null); expect(device.name, 'specified-device'); }, overrides: <Type, Generator>{ FileSystem: () => fs, @@ -293,7 +293,7 @@ Platform platform() => FakePlatform(operatingSystem: operatingSystem); testUsingContext('returns null if no devices found', () async { - expect(await findTargetDevice(), isNull); + expect(await findTargetDevice(timeout: null), isNull); }, overrides: <Type, Generator>{ FileSystem: () => fs, ProcessManager: () => FakeProcessManager.any(), @@ -305,7 +305,7 @@ when(mockDevice.name).thenReturn('mock-android-device'); testDeviceManager.addDevice(mockDevice); - final Device device = await findTargetDevice(); + final Device device = await findTargetDevice(timeout: null); expect(device.name, 'mock-android-device'); }, overrides: <Type, Generator>{ FileSystem: () => fs, @@ -337,7 +337,7 @@ testDeviceManager.addDevice(mockDevice); testDeviceManager.addDevice(mockUnsupportedDevice); - final Device device = await findTargetDevice(); + final Device device = await findTargetDevice(timeout: null); expect(device.name, 'mock-android-device'); }, overrides: <Type, Generator>{ FileSystem: () => fs, @@ -366,7 +366,7 @@ when(mockDevice.isLocalEmulator) .thenAnswer((Invocation invocation) => Future<bool>.value(true)); - final Device device = await findTargetDevice(); + final Device device = await findTargetDevice(timeout: null); expect(device.name, 'mock-simulator'); }, overrides: <Type, Generator>{ FileSystem: () => fs,
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 52f3324..edd6060 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
@@ -178,7 +178,7 @@ when(mockDeviceManager.getDevices()).thenAnswer( (Invocation invocation) => Future<List<Device>>.value(noDevices) ); - when(mockDeviceManager.findTargetDevices(any)).thenAnswer( + when(mockDeviceManager.findTargetDevices(any, timeout: anyNamed('timeout'))).thenAnswer( (Invocation invocation) => Future<List<Device>>.value(noDevices) ); @@ -214,7 +214,7 @@ when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) async { return <Device>[device]; }); - when(mockDeviceManager.findTargetDevices(any)).thenAnswer((Invocation invocation) async { + when(mockDeviceManager.findTargetDevices(any, timeout: anyNamed('timeout'))).thenAnswer((Invocation invocation) async { return <Device>[device]; }); when(mockDeviceManager.hasSpecifiedAllDevices).thenReturn(false); @@ -254,7 +254,7 @@ ]); }); - when(mockDeviceManager.findTargetDevices(any)).thenAnswer( + when(mockDeviceManager.findTargetDevices(any, timeout: anyNamed('timeout'))).thenAnswer( (Invocation invocation) => Future<List<Device>>.value(<Device>[]), ); @@ -301,7 +301,8 @@ ); // No devices are attached, we just want to verify update the cache // BEFORE checking for devices - when(mockDeviceManager.findTargetDevices(any)).thenAnswer( + const Duration timeout = Duration(seconds: 10); + when(mockDeviceManager.findTargetDevices(any, timeout: timeout)).thenAnswer( (Invocation invocation) => Future<List<Device>>.value(<Device>[]) ); @@ -309,6 +310,8 @@ await createTestCommandRunner(command).run(<String>[ 'run', '--no-pub', + '--device-timeout', + '10', ]); fail('Exception expected'); } on ToolExit catch (e) { @@ -324,7 +327,7 @@ // as part of gathering `requiredArtifacts` mockDeviceManager.getDevices(), // in validateCommand() - mockDeviceManager.findTargetDevices(any), + mockDeviceManager.findTargetDevices(any, timeout: anyNamed('timeout')), ]); }, overrides: <Type, Generator>{ ApplicationPackageFactory: () => mockApplicationPackageFactory, @@ -365,7 +368,7 @@ (Invocation invocation) => Future<List<Device>>.value(<Device>[mockDevice]) ); - when(mockDeviceManager.findTargetDevices(any)).thenAnswer( + when(mockDeviceManager.findTargetDevices(any, timeout: anyNamed('timeout'))).thenAnswer( (Invocation invocation) => Future<List<Device>>.value(<Device>[mockDevice]) ); @@ -434,7 +437,7 @@ when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) { return Future<List<Device>>.value(<Device>[fakeDevice]); }); - when(mockDeviceManager.findTargetDevices(any)).thenAnswer( + when(mockDeviceManager.findTargetDevices(any, timeout: anyNamed('timeout'))).thenAnswer( (Invocation invocation) => Future<List<Device>>.value(<Device>[fakeDevice]) ); });
diff --git a/packages/flutter_tools/test/general.shard/device_test.dart b/packages/flutter_tools/test/general.shard/device_test.dart index c39d428..f2e62ec 100644 --- a/packages/flutter_tools/test/general.shard/device_test.dart +++ b/packages/flutter_tools/test/general.shard/device_test.dart
@@ -58,8 +58,10 @@ // 3. A device discoverer that succeeds. final DeviceManager deviceManager = TestDeviceManager( devices, - testLongPollingDeviceDiscovery: true, - testThrowingDeviceDiscovery: true, + deviceDiscoveryOverrides: <DeviceDiscovery>[ + ThrowingPollingDeviceDiscovery(), + LongPollingDeviceDiscovery(), + ], ); Future<void> expectDevice(String id, List<Device> expected) async { @@ -89,7 +91,9 @@ // 2. A device discoverer that succeeds. final DeviceManager deviceManager = TestDeviceManager( devices, - testThrowingDeviceDiscovery: true + deviceDiscoveryOverrides: <DeviceDiscovery>[ + ThrowingPollingDeviceDiscovery(), + ], ); Future<void> expectDevice(String id, List<Device> expected) async { @@ -387,7 +391,62 @@ Artifacts: () => Artifacts.test(), Cache: () => cache, }); + + testUsingContext('does not refresh device cache without a timeout', () async { + final List<Device> devices = <Device>[ + ephemeralOne, + ]; + final MockDeviceDiscovery mockDeviceDiscovery = MockDeviceDiscovery(); + when(mockDeviceDiscovery.supportsPlatform).thenReturn(true); + // when(mockDeviceDiscovery.discoverDevices(timeout: timeout)).thenAnswer((_) async => devices); + when(mockDeviceDiscovery.devices).thenAnswer((_) async => devices); + // when(mockDeviceDiscovery.discoverDevices(timeout: timeout)).thenAnswer((_) async => devices); + + final DeviceManager deviceManager = TestDeviceManager(<Device>[], deviceDiscoveryOverrides: <DeviceDiscovery>[ + mockDeviceDiscovery + ]); + deviceManager.specifiedDeviceId = ephemeralOne.id; + final List<Device> filtered = await deviceManager.findTargetDevices( + FlutterProject.current(), + ); + + expect(filtered.single, ephemeralOne); + verify(mockDeviceDiscovery.devices).called(1); + verifyNever(mockDeviceDiscovery.discoverDevices(timeout: anyNamed('timeout'))); + }, overrides: <Type, Generator>{ + Artifacts: () => Artifacts.test(), + Cache: () => cache, + }); + + testUsingContext('refreshes device cache with a timeout', () async { + final List<Device> devices = <Device>[ + ephemeralOne, + ]; + const Duration timeout = Duration(seconds: 2); + final MockDeviceDiscovery mockDeviceDiscovery = MockDeviceDiscovery(); + when(mockDeviceDiscovery.supportsPlatform).thenReturn(true); + when(mockDeviceDiscovery.discoverDevices(timeout: timeout)).thenAnswer((_) async => devices); + when(mockDeviceDiscovery.devices).thenAnswer((_) async => devices); + // when(mockDeviceDiscovery.discoverDevices(timeout: timeout)).thenAnswer((_) async => devices); + + final DeviceManager deviceManager = TestDeviceManager(<Device>[], deviceDiscoveryOverrides: <DeviceDiscovery>[ + mockDeviceDiscovery + ]); + deviceManager.specifiedDeviceId = ephemeralOne.id; + final List<Device> filtered = await deviceManager.findTargetDevices( + FlutterProject.current(), + timeout: timeout, + ); + + expect(filtered.single, ephemeralOne); + verify(mockDeviceDiscovery.devices).called(1); + verify(mockDeviceDiscovery.discoverDevices(timeout: anyNamed('timeout'))).called(1); + }, overrides: <Type, Generator>{ + Artifacts: () => Artifacts.test(), + Cache: () => cache, + }); }); + group('ForwardedPort', () { group('dispose()', () { testUsingContext('does not throw exception if no process is present', () { @@ -427,16 +486,13 @@ class TestDeviceManager extends DeviceManager { TestDeviceManager(List<Device> allDevices, { - bool testLongPollingDeviceDiscovery = false, - bool testThrowingDeviceDiscovery = false, + List<DeviceDiscovery> deviceDiscoveryOverrides, }) { _fakeDeviceDiscoverer = FakePollingDeviceDiscovery(); _deviceDiscoverers = <DeviceDiscovery>[ - if (testLongPollingDeviceDiscovery) - LongPollingDeviceDiscovery(), - if (testThrowingDeviceDiscovery) - ThrowingPollingDeviceDiscovery(), _fakeDeviceDiscoverer, + if (deviceDiscoveryOverrides != null) + ...deviceDiscoveryOverrides ]; resetDevices(allDevices); } @@ -464,3 +520,4 @@ class MockTerminal extends Mock implements AnsiTerminal {} class MockStdio extends Mock implements Stdio {} class MockCache extends Mock implements Cache {} +class MockDeviceDiscovery extends Mock implements DeviceDiscovery {}
diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart index 559f362..f244796 100644 --- a/packages/flutter_tools/test/src/context.dart +++ b/packages/flutter_tools/test/src/context.dart
@@ -248,7 +248,7 @@ } @override - Future<List<Device>> findTargetDevices(FlutterProject flutterProject) async { + Future<List<Device>> findTargetDevices(FlutterProject flutterProject, { Duration timeout }) async { return devices; } }