move reload and restart handling into terminal (#35846)
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index 94ab882..77c9e94 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -559,6 +559,9 @@
});
}
+ /// Whether this runner can hot reload.
+ bool get canHotReload => hotMode;
+
/// Start the app and keep the process running during its lifetime.
///
/// Returns the exit code that we should use for the flutter tool process; 0
@@ -838,35 +841,6 @@
/// Called right before we exit.
Future<void> cleanupAtFinish();
-
- /// Called when the runner should handle a terminal command.
- Future<void> handleTerminalCommand(String code) async {
- switch (code) {
- case 'r':
- final OperationResult result = await restart(fullRestart: false);
- if (!result.isOk) {
- printStatus('Try again after fixing the above error(s).', emphasis: true);
- }
- return;
- case 'R':
- // If hot restart is not supported for all devices, ignore the command.
- if (!canHotRestart) {
- return;
- }
- final OperationResult result = await restart(fullRestart: true);
- if (!result.isOk) {
- printStatus('Try again after fixing the above error(s).', emphasis: true);
- }
- return;
- case 'l':
- case 'L':
- final List<FlutterView> views = flutterDevices.expand((FlutterDevice d) => d.views).toList();
- printStatus('Connected ${pluralize('view', views.length)}:');
- for (FlutterView v in views) {
- printStatus('${v.uiIsolate.name} (${v.uiIsolate.id})', indent: 2);
- }
- }
- }
}
class OperationResult {
@@ -918,6 +892,9 @@
bool _processingUserRequest = false;
StreamSubscription<void> subscription;
+ @visibleForTesting
+ String lastReceivedCommand;
+
void setupTerminal() {
if (!logger.quiet) {
printStatus('');
@@ -964,6 +941,14 @@
return true;
}
return false;
+ case 'l':
+ final List<FlutterView> views = residentRunner.flutterDevices
+ .expand((FlutterDevice d) => d.views).toList();
+ printStatus('Connected ${pluralize('view', views.length)}:');
+ for (FlutterView v in views) {
+ printStatus('${v.uiIsolate.name} (${v.uiIsolate.id})', indent: 2);
+ }
+ return true;
case 'L':
if (residentRunner.supportsServiceProtocol) {
await residentRunner.debugDumpLayerTree();
@@ -1000,6 +985,25 @@
await residentRunner.screenshot(device);
}
return true;
+ case 'r':
+ if (!residentRunner.canHotReload) {
+ return false;
+ }
+ final OperationResult result = await residentRunner.restart(fullRestart: false);
+ if (!result.isOk) {
+ printStatus('Try again after fixing the above error(s).', emphasis: true);
+ }
+ return true;
+ case 'R':
+ // If hot restart is not supported for all devices, ignore the command.
+ if (!residentRunner.canHotRestart || !residentRunner.hotMode) {
+ return false;
+ }
+ final OperationResult result = await residentRunner.restart(fullRestart: true);
+ if (!result.isOk) {
+ printStatus('Try again after fixing the above error(s).', emphasis: true);
+ }
+ return true;
case 'S':
if (residentRunner.supportsServiceProtocol) {
await residentRunner.debugDumpSemanticsTreeInTraversalOrder();
@@ -1031,11 +1035,9 @@
await residentRunner.debugToggleDebugCheckElevationsEnabled();
return true;
}
-
return false;
}
-
Future<void> processTerminalInput(String command) async {
// When terminal doesn't support line mode, '\n' can sneak into the input.
command = command.trim();
@@ -1045,9 +1047,8 @@
}
_processingUserRequest = true;
try {
- final bool handled = await _commonTerminalInputHandler(command);
- if (!handled)
- await residentRunner.handleTerminalCommand(command);
+ lastReceivedCommand = command;
+ await _commonTerminalInputHandler(command);
} catch (error, st) {
printError('$error\n$st');
await _cleanUpAndExit(null);
diff --git a/packages/flutter_tools/lib/src/resident_web_runner.dart b/packages/flutter_tools/lib/src/resident_web_runner.dart
index 55dad48..be37536 100644
--- a/packages/flutter_tools/lib/src/resident_web_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_web_runner.dart
@@ -50,6 +50,9 @@
final FlutterProject flutterProject;
@override
+ bool get canHotReload => false;
+
+ @override
Future<int> attach(
{Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<void> appStartedCompleter}) async {
@@ -74,17 +77,6 @@
}
@override
- Future<void> handleTerminalCommand(String code) async {
- if (code == 'R') {
- // If hot restart is not supported for all devices, ignore the command.
- if (!canHotRestart) {
- return;
- }
- await restart(fullRestart: true);
- }
- }
-
- @override
void printHelp({bool details}) {
const String fire = '🔥';
const String rawMessage =
diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart
index ad91f56..6a79afc 100644
--- a/packages/flutter_tools/lib/src/run_cold.dart
+++ b/packages/flutter_tools/lib/src/run_cold.dart
@@ -39,6 +39,12 @@
bool _didAttach = false;
@override
+ bool get canHotReload => false;
+
+ @override
+ bool get canHotRestart => false;
+
+ @override
Future<int> run({
Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<void> appStartedCompleter,
diff --git a/packages/flutter_tools/test/terminal_handler_test.dart b/packages/flutter_tools/test/terminal_handler_test.dart
index 088c712..6e182ba 100644
--- a/packages/flutter_tools/test/terminal_handler_test.dart
+++ b/packages/flutter_tools/test/terminal_handler_test.dart
@@ -3,9 +3,12 @@
// found in the LICENSE file.
import 'dart:async';
+import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/device.dart';
+import 'package:flutter_tools/src/globals.dart';
import 'package:flutter_tools/src/resident_runner.dart';
+import 'package:flutter_tools/src/vmservice.dart';
import 'package:mockito/mockito.dart';
import 'src/common.dart';
@@ -24,37 +27,21 @@
testUsingContext('single help character', () async {
final TestRunner testRunner = createTestRunner();
final TerminalHandler terminalHandler = TerminalHandler(testRunner);
- expect(testRunner.hasHelpBeenPrinted, isFalse);
+ expect(testRunner.hasHelpBeenPrinted, false);
await terminalHandler.processTerminalInput('h');
- expect(testRunner.hasHelpBeenPrinted, isTrue);
+ expect(testRunner.hasHelpBeenPrinted, true);
});
testUsingContext('help character surrounded with newlines', () async {
final TestRunner testRunner = createTestRunner();
final TerminalHandler terminalHandler = TerminalHandler(testRunner);
- expect(testRunner.hasHelpBeenPrinted, isFalse);
+ expect(testRunner.hasHelpBeenPrinted, false);
await terminalHandler.processTerminalInput('\nh\n');
- expect(testRunner.hasHelpBeenPrinted, isTrue);
- });
-
- testUsingContext('reload character with trailing newline', () async {
- final TestRunner testRunner = createTestRunner();
- final TerminalHandler terminalHandler = TerminalHandler(testRunner);
- expect(testRunner.receivedCommand, isNull);
- await terminalHandler.processTerminalInput('r\n');
- expect(testRunner.receivedCommand, equals('r'));
- });
-
- testUsingContext('newlines', () async {
- final TestRunner testRunner = createTestRunner();
- final TerminalHandler terminalHandler = TerminalHandler(testRunner);
- expect(testRunner.receivedCommand, isNull);
- await terminalHandler.processTerminalInput('\n\n');
- expect(testRunner.receivedCommand, equals(''));
+ expect(testRunner.hasHelpBeenPrinted, true);
});
});
- group('keycode verification, brought to you by the letter r', () {
+ group('keycode verification, brought to you by the letter', () {
MockResidentRunner mockResidentRunner;
TerminalHandler terminalHandler;
@@ -62,7 +49,18 @@
mockResidentRunner = MockResidentRunner();
terminalHandler = TerminalHandler(mockResidentRunner);
when(mockResidentRunner.supportsServiceProtocol).thenReturn(true);
- when(mockResidentRunner.handleTerminalCommand(any)).thenReturn(null);
+ });
+
+ testUsingContext('a, can handle trailing newlines', () async {
+ await terminalHandler.processTerminalInput('a\n');
+
+ expect(terminalHandler.lastReceivedCommand, 'a');
+ });
+
+ testUsingContext('n, can handle trailing only newlines', () async {
+ await terminalHandler.processTerminalInput('\n\n');
+
+ expect(terminalHandler.lastReceivedCommand, '');
});
testUsingContext('a - debugToggleProfileWidgetBuilds with service protocol', () async {
@@ -116,6 +114,19 @@
verifyNever(mockResidentRunner.debugToggleWidgetInspector());
});
+ testUsingContext('l - list flutter views', () async {
+ final MockFlutterDevice mockFlutterDevice = MockFlutterDevice();
+ when(mockResidentRunner.isRunningDebug).thenReturn(true);
+ when(mockResidentRunner.flutterDevices).thenReturn(<FlutterDevice>[mockFlutterDevice]);
+ when(mockFlutterDevice.views).thenReturn(<FlutterView>[]);
+
+ await terminalHandler.processTerminalInput('l');
+
+ final BufferLogger bufferLogger = logger;
+
+ expect(bufferLogger.statusText, contains('Connected views:\n'));
+ });
+
testUsingContext('L - debugDumpLayerTree with service protocol', () async {
await terminalHandler.processTerminalInput('L');
@@ -209,6 +220,74 @@
verify(mockResidentRunner.screenshot(mockFlutterDevice)).called(1);
});
+ testUsingContext('r - hotReload supported and succeeds', () async {
+ when(mockResidentRunner.canHotReload).thenReturn(true);
+ when(mockResidentRunner.restart(fullRestart: false))
+ .thenAnswer((Invocation invocation) async {
+ return OperationResult(0, '');
+ });
+ await terminalHandler.processTerminalInput('r');
+
+ verify(mockResidentRunner.restart(fullRestart: false)).called(1);
+ });
+
+ testUsingContext('r - hotReload supported and fails', () async {
+ when(mockResidentRunner.canHotReload).thenReturn(true);
+ when(mockResidentRunner.restart(fullRestart: false))
+ .thenAnswer((Invocation invocation) async {
+ return OperationResult(1, '');
+ });
+ await terminalHandler.processTerminalInput('r');
+
+ verify(mockResidentRunner.restart(fullRestart: false)).called(1);
+
+ final BufferLogger bufferLogger = logger;
+
+ expect(bufferLogger.statusText, contains('Try again after fixing the above error(s).'));
+ });
+
+ testUsingContext('r - hotReload unsupported', () async {
+ when(mockResidentRunner.canHotReload).thenReturn(false);
+ await terminalHandler.processTerminalInput('r');
+
+ verifyNever(mockResidentRunner.restart(fullRestart: false));
+ });
+
+ testUsingContext('R - hotRestart supported and succeeds', () async {
+ when(mockResidentRunner.canHotRestart).thenReturn(true);
+ when(mockResidentRunner.hotMode).thenReturn(true);
+ when(mockResidentRunner.restart(fullRestart: true))
+ .thenAnswer((Invocation invocation) async {
+ return OperationResult(0, '');
+ });
+ await terminalHandler.processTerminalInput('R');
+
+ verify(mockResidentRunner.restart(fullRestart: true)).called(1);
+ });
+
+ testUsingContext('R - hotRestart supported and fails', () async {
+ when(mockResidentRunner.canHotRestart).thenReturn(true);
+ when(mockResidentRunner.hotMode).thenReturn(true);
+ when(mockResidentRunner.restart(fullRestart: true))
+ .thenAnswer((Invocation invocation) async {
+ return OperationResult(1, 'fail');
+ });
+ await terminalHandler.processTerminalInput('R');
+
+ verify(mockResidentRunner.restart(fullRestart: true)).called(1);
+
+ final BufferLogger bufferLogger = logger;
+
+ expect(bufferLogger.statusText, contains('Try again after fixing the above error(s).'));
+ });
+
+ testUsingContext('R - hot restart unsupported', () async {
+ when(mockResidentRunner.canHotRestart).thenReturn(false);
+ await terminalHandler.processTerminalInput('R');
+
+ verifyNever(mockResidentRunner.restart(fullRestart: true));
+ });
+
testUsingContext('S - debugDumpSemanticsTreeInTraversalOrder with service protocol', () async {
await terminalHandler.processTerminalInput('S');
@@ -307,11 +386,6 @@
Future<void> cleanupAtFinish() async { }
@override
- Future<void> handleTerminalCommand(String code) async {
- receivedCommand = code;
- }
-
- @override
void printHelp({ bool details }) {
hasHelpBeenPrinted = true;
}