Unverified Commit 7b150f81 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

move reload and restart handling into terminal (#35846)

parent 14d489ad
...@@ -559,6 +559,9 @@ abstract class ResidentRunner { ...@@ -559,6 +559,9 @@ abstract class ResidentRunner {
}); });
} }
/// Whether this runner can hot reload.
bool get canHotReload => hotMode;
/// Start the app and keep the process running during its lifetime. /// 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 /// Returns the exit code that we should use for the flutter tool process; 0
...@@ -838,35 +841,6 @@ abstract class ResidentRunner { ...@@ -838,35 +841,6 @@ abstract class ResidentRunner {
/// Called right before we exit. /// Called right before we exit.
Future<void> cleanupAtFinish(); 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 { class OperationResult {
...@@ -918,6 +892,9 @@ class TerminalHandler { ...@@ -918,6 +892,9 @@ class TerminalHandler {
bool _processingUserRequest = false; bool _processingUserRequest = false;
StreamSubscription<void> subscription; StreamSubscription<void> subscription;
@visibleForTesting
String lastReceivedCommand;
void setupTerminal() { void setupTerminal() {
if (!logger.quiet) { if (!logger.quiet) {
printStatus(''); printStatus('');
...@@ -964,6 +941,14 @@ class TerminalHandler { ...@@ -964,6 +941,14 @@ class TerminalHandler {
return true; return true;
} }
return false; 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': case 'L':
if (residentRunner.supportsServiceProtocol) { if (residentRunner.supportsServiceProtocol) {
await residentRunner.debugDumpLayerTree(); await residentRunner.debugDumpLayerTree();
...@@ -1000,6 +985,25 @@ class TerminalHandler { ...@@ -1000,6 +985,25 @@ class TerminalHandler {
await residentRunner.screenshot(device); await residentRunner.screenshot(device);
} }
return true; 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': case 'S':
if (residentRunner.supportsServiceProtocol) { if (residentRunner.supportsServiceProtocol) {
await residentRunner.debugDumpSemanticsTreeInTraversalOrder(); await residentRunner.debugDumpSemanticsTreeInTraversalOrder();
...@@ -1031,11 +1035,9 @@ class TerminalHandler { ...@@ -1031,11 +1035,9 @@ class TerminalHandler {
await residentRunner.debugToggleDebugCheckElevationsEnabled(); await residentRunner.debugToggleDebugCheckElevationsEnabled();
return true; return true;
} }
return false; return false;
} }
Future<void> processTerminalInput(String command) async { Future<void> processTerminalInput(String command) async {
// When terminal doesn't support line mode, '\n' can sneak into the input. // When terminal doesn't support line mode, '\n' can sneak into the input.
command = command.trim(); command = command.trim();
...@@ -1045,9 +1047,8 @@ class TerminalHandler { ...@@ -1045,9 +1047,8 @@ class TerminalHandler {
} }
_processingUserRequest = true; _processingUserRequest = true;
try { try {
final bool handled = await _commonTerminalInputHandler(command); lastReceivedCommand = command;
if (!handled) await _commonTerminalInputHandler(command);
await residentRunner.handleTerminalCommand(command);
} catch (error, st) { } catch (error, st) {
printError('$error\n$st'); printError('$error\n$st');
await _cleanUpAndExit(null); await _cleanUpAndExit(null);
......
...@@ -49,6 +49,9 @@ class ResidentWebRunner extends ResidentRunner { ...@@ -49,6 +49,9 @@ class ResidentWebRunner extends ResidentRunner {
WipConnection _connection; WipConnection _connection;
final FlutterProject flutterProject; final FlutterProject flutterProject;
@override
bool get canHotReload => false;
@override @override
Future<int> attach( Future<int> attach(
{Completer<DebugConnectionInfo> connectionInfoCompleter, {Completer<DebugConnectionInfo> connectionInfoCompleter,
...@@ -73,17 +76,6 @@ class ResidentWebRunner extends ResidentRunner { ...@@ -73,17 +76,6 @@ class ResidentWebRunner extends ResidentRunner {
await _server?.dispose(); await _server?.dispose();
} }
@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 @override
void printHelp({bool details}) { void printHelp({bool details}) {
const String fire = '🔥'; const String fire = '🔥';
......
...@@ -38,6 +38,12 @@ class ColdRunner extends ResidentRunner { ...@@ -38,6 +38,12 @@ class ColdRunner extends ResidentRunner {
final File applicationBinary; final File applicationBinary;
bool _didAttach = false; bool _didAttach = false;
@override
bool get canHotReload => false;
@override
bool get canHotRestart => false;
@override @override
Future<int> run({ Future<int> run({
Completer<DebugConnectionInfo> connectionInfoCompleter, Completer<DebugConnectionInfo> connectionInfoCompleter,
......
...@@ -3,9 +3,12 @@ ...@@ -3,9 +3,12 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/device.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/resident_runner.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'src/common.dart'; import 'src/common.dart';
...@@ -24,37 +27,21 @@ void main() { ...@@ -24,37 +27,21 @@ void main() {
testUsingContext('single help character', () async { testUsingContext('single help character', () async {
final TestRunner testRunner = createTestRunner(); final TestRunner testRunner = createTestRunner();
final TerminalHandler terminalHandler = TerminalHandler(testRunner); final TerminalHandler terminalHandler = TerminalHandler(testRunner);
expect(testRunner.hasHelpBeenPrinted, isFalse); expect(testRunner.hasHelpBeenPrinted, false);
await terminalHandler.processTerminalInput('h'); await terminalHandler.processTerminalInput('h');
expect(testRunner.hasHelpBeenPrinted, isTrue); expect(testRunner.hasHelpBeenPrinted, true);
}); });
testUsingContext('help character surrounded with newlines', () async { testUsingContext('help character surrounded with newlines', () async {
final TestRunner testRunner = createTestRunner(); final TestRunner testRunner = createTestRunner();
final TerminalHandler terminalHandler = TerminalHandler(testRunner); final TerminalHandler terminalHandler = TerminalHandler(testRunner);
expect(testRunner.hasHelpBeenPrinted, isFalse); expect(testRunner.hasHelpBeenPrinted, false);
await terminalHandler.processTerminalInput('\nh\n'); await terminalHandler.processTerminalInput('\nh\n');
expect(testRunner.hasHelpBeenPrinted, isTrue); expect(testRunner.hasHelpBeenPrinted, true);
});
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(''));
}); });
}); });
group('keycode verification, brought to you by the letter r', () { group('keycode verification, brought to you by the letter', () {
MockResidentRunner mockResidentRunner; MockResidentRunner mockResidentRunner;
TerminalHandler terminalHandler; TerminalHandler terminalHandler;
...@@ -62,7 +49,18 @@ void main() { ...@@ -62,7 +49,18 @@ void main() {
mockResidentRunner = MockResidentRunner(); mockResidentRunner = MockResidentRunner();
terminalHandler = TerminalHandler(mockResidentRunner); terminalHandler = TerminalHandler(mockResidentRunner);
when(mockResidentRunner.supportsServiceProtocol).thenReturn(true); 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 { testUsingContext('a - debugToggleProfileWidgetBuilds with service protocol', () async {
...@@ -116,6 +114,19 @@ void main() { ...@@ -116,6 +114,19 @@ void main() {
verifyNever(mockResidentRunner.debugToggleWidgetInspector()); 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 { testUsingContext('L - debugDumpLayerTree with service protocol', () async {
await terminalHandler.processTerminalInput('L'); await terminalHandler.processTerminalInput('L');
...@@ -209,6 +220,74 @@ void main() { ...@@ -209,6 +220,74 @@ void main() {
verify(mockResidentRunner.screenshot(mockFlutterDevice)).called(1); 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 { testUsingContext('S - debugDumpSemanticsTreeInTraversalOrder with service protocol', () async {
await terminalHandler.processTerminalInput('S'); await terminalHandler.processTerminalInput('S');
...@@ -306,11 +385,6 @@ class TestRunner extends ResidentRunner { ...@@ -306,11 +385,6 @@ class TestRunner extends ResidentRunner {
@override @override
Future<void> cleanupAtFinish() async { } Future<void> cleanupAtFinish() async { }
@override
Future<void> handleTerminalCommand(String code) async {
receivedCommand = code;
}
@override @override
void printHelp({ bool details }) { void printHelp({ bool details }) {
hasHelpBeenPrinted = true; hasHelpBeenPrinted = true;
......
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