Unverified Commit adf45d1e authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Refactor signal and command line handler from resident runner (#35406)

parent a429991a
...@@ -437,8 +437,8 @@ class AndroidDevice extends Device { ...@@ -437,8 +437,8 @@ class AndroidDevice extends Device {
DebuggingOptions debuggingOptions, DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs, Map<String, dynamic> platformArgs,
bool prebuiltApplication = false, bool prebuiltApplication = false,
bool usesTerminalUi = true,
bool ipv6 = false, bool ipv6 = false,
bool usesTerminalUi = true,
}) async { }) async {
if (!await _checkForSupportedAdbVersion() || !await _checkForSupportedAndroidVersion()) if (!await _checkForSupportedAdbVersion() || !await _checkForSupportedAndroidVersion())
return LaunchResult.failed(); return LaunchResult.failed();
......
...@@ -277,7 +277,7 @@ class AttachCommand extends FlutterCommand { ...@@ -277,7 +277,7 @@ class AttachCommand extends FlutterCommand {
target: targetFile, target: targetFile,
debuggingOptions: debuggingOptions, debuggingOptions: debuggingOptions,
packagesFilePath: globalResults['packages'], packagesFilePath: globalResults['packages'],
usesTerminalUI: daemon == null, usesTerminalUi: daemon == null,
projectRootPath: argResults['project-root'], projectRootPath: argResults['project-root'],
dillOutputPath: argResults['output-dill'], dillOutputPath: argResults['output-dill'],
ipv6: usesIpv6, ipv6: usesIpv6,
...@@ -312,7 +312,15 @@ class AttachCommand extends FlutterCommand { ...@@ -312,7 +312,15 @@ class AttachCommand extends FlutterCommand {
result = await app.runner.waitForAppToFinish(); result = await app.runner.waitForAppToFinish();
assert(result != null); assert(result != null);
} else { } else {
result = await runner.attach(); final Completer<void> onAppStart = Completer<void>.sync();
unawaited(onAppStart.future.whenComplete(() {
TerminalHandler(runner)
..setupTerminal()
..registerSignalHandlers();
}));
result = await runner.attach(
appStartedCompleter: onAppStart,
);
assert(result != null); assert(result != null);
} }
if (result != 0) { if (result != 0) {
...@@ -350,7 +358,7 @@ class HotRunnerFactory { ...@@ -350,7 +358,7 @@ class HotRunnerFactory {
List<FlutterDevice> devices, { List<FlutterDevice> devices, {
String target, String target,
DebuggingOptions debuggingOptions, DebuggingOptions debuggingOptions,
bool usesTerminalUI = true, bool usesTerminalUi = true,
bool benchmarkMode = false, bool benchmarkMode = false,
File applicationBinary, File applicationBinary,
bool hostIsIde = false, bool hostIsIde = false,
...@@ -364,7 +372,7 @@ class HotRunnerFactory { ...@@ -364,7 +372,7 @@ class HotRunnerFactory {
devices, devices,
target: target, target: target,
debuggingOptions: debuggingOptions, debuggingOptions: debuggingOptions,
usesTerminalUI: usesTerminalUI, usesTerminalUi: usesTerminalUi,
benchmarkMode: benchmarkMode, benchmarkMode: benchmarkMode,
applicationBinary: applicationBinary, applicationBinary: applicationBinary,
hostIsIde: hostIsIde, hostIsIde: hostIsIde,
......
...@@ -419,7 +419,7 @@ class AppDomain extends Domain { ...@@ -419,7 +419,7 @@ class AppDomain extends Domain {
<FlutterDevice>[flutterDevice], <FlutterDevice>[flutterDevice],
target: target, target: target,
debuggingOptions: options, debuggingOptions: options,
usesTerminalUI: false, usesTerminalUi: false,
applicationBinary: applicationBinary, applicationBinary: applicationBinary,
projectRootPath: projectRootPath, projectRootPath: projectRootPath,
packagesFilePath: packagesFilePath, packagesFilePath: packagesFilePath,
...@@ -432,8 +432,8 @@ class AppDomain extends Domain { ...@@ -432,8 +432,8 @@ class AppDomain extends Domain {
<FlutterDevice>[flutterDevice], <FlutterDevice>[flutterDevice],
target: target, target: target,
debuggingOptions: options, debuggingOptions: options,
usesTerminalUI: false,
applicationBinary: applicationBinary, applicationBinary: applicationBinary,
usesTerminalUi: false,
ipv6: ipv6, ipv6: ipv6,
); );
} }
......
...@@ -450,8 +450,8 @@ class RunCommand extends RunCommandBase { ...@@ -450,8 +450,8 @@ class RunCommand extends RunCommandBase {
applicationBinary: applicationBinaryPath == null applicationBinary: applicationBinaryPath == null
? null ? null
: fs.file(applicationBinaryPath), : fs.file(applicationBinaryPath),
stayResident: stayResident,
ipv6: ipv6, ipv6: ipv6,
stayResident: stayResident,
); );
} }
...@@ -463,7 +463,14 @@ class RunCommand extends RunCommandBase { ...@@ -463,7 +463,14 @@ class RunCommand extends RunCommandBase {
final Completer<void> appStartedTimeRecorder = Completer<void>.sync(); final Completer<void> appStartedTimeRecorder = Completer<void>.sync();
// This callback can't throw. // This callback can't throw.
unawaited(appStartedTimeRecorder.future.then<void>( unawaited(appStartedTimeRecorder.future.then<void>(
(_) { appStartedTime = systemClock.now(); } (_) {
appStartedTime = systemClock.now();
if (stayResident) {
TerminalHandler(runner)
..setupTerminal()
..registerSignalHandlers();
}
}
)); ));
final int result = await runner.run( final int result = await runner.run(
...@@ -471,8 +478,9 @@ class RunCommand extends RunCommandBase { ...@@ -471,8 +478,9 @@ class RunCommand extends RunCommandBase {
route: route, route: route,
shouldBuild: !runningWithPrebuiltApplication && argResults['build'], shouldBuild: !runningWithPrebuiltApplication && argResults['build'],
); );
if (result != 0) if (result != 0) {
throwToolExit(null, exitCode: result); throwToolExit(null, exitCode: result);
}
return FlutterCommandResult( return FlutterCommandResult(
ExitStatus.success, ExitStatus.success,
timingLabelParts: <String>[ timingLabelParts: <String>[
......
...@@ -406,8 +406,8 @@ abstract class Device { ...@@ -406,8 +406,8 @@ abstract class Device {
DebuggingOptions debuggingOptions, DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs, Map<String, dynamic> platformArgs,
bool prebuiltApplication = false, bool prebuiltApplication = false,
bool usesTerminalUi = true,
bool ipv6 = false, bool ipv6 = false,
bool usesTerminalUi = true,
}); });
/// Whether this device implements support for hot reload. /// Whether this device implements support for hot reload.
......
...@@ -115,7 +115,7 @@ class FlutterDevice { ...@@ -115,7 +115,7 @@ class FlutterDevice {
/// expressions requested during debugging of the application. /// expressions requested during debugging of the application.
/// This ensures that the reload process follows the normal orchestration of /// This ensures that the reload process follows the normal orchestration of
/// the Flutter Tools and not just the VM internal service. /// the Flutter Tools and not just the VM internal service.
Future<void> _connect({ Future<void> connect({
ReloadSources reloadSources, ReloadSources reloadSources,
Restart restart, Restart restart,
CompileExpression compileExpression, CompileExpression compileExpression,
...@@ -375,7 +375,7 @@ class FlutterDevice { ...@@ -375,7 +375,7 @@ class FlutterDevice {
platformArgs: platformArgs, platformArgs: platformArgs,
route: route, route: route,
prebuiltApplication: prebuiltMode, prebuiltApplication: prebuiltMode,
usesTerminalUi: hotRunner.usesTerminalUI, usesTerminalUi: hotRunner.usesTerminalUi,
ipv6: hotRunner.ipv6, ipv6: hotRunner.ipv6,
); );
...@@ -437,7 +437,7 @@ class FlutterDevice { ...@@ -437,7 +437,7 @@ class FlutterDevice {
platformArgs: platformArgs, platformArgs: platformArgs,
route: route, route: route,
prebuiltApplication: prebuiltMode, prebuiltApplication: prebuiltMode,
usesTerminalUi: coldRunner.usesTerminalUI, usesTerminalUi: coldRunner.usesTerminalUi,
ipv6: coldRunner.ipv6, ipv6: coldRunner.ipv6,
); );
...@@ -509,11 +509,12 @@ abstract class ResidentRunner { ...@@ -509,11 +509,12 @@ abstract class ResidentRunner {
this.flutterDevices, { this.flutterDevices, {
this.target, this.target,
this.debuggingOptions, this.debuggingOptions,
this.usesTerminalUI = true,
String projectRootPath, String projectRootPath,
String packagesFilePath, String packagesFilePath,
this.stayResident,
this.ipv6, this.ipv6,
this.usesTerminalUi = true,
this.stayResident = true,
this.hotMode = true,
}) { }) {
_mainPath = findMainDartFile(target); _mainPath = findMainDartFile(target);
_projectRootPath = projectRootPath ?? fs.currentDirectory.path; _projectRootPath = projectRootPath ?? fs.currentDirectory.path;
...@@ -525,11 +526,12 @@ abstract class ResidentRunner { ...@@ -525,11 +526,12 @@ abstract class ResidentRunner {
final List<FlutterDevice> flutterDevices; final List<FlutterDevice> flutterDevices;
final String target; final String target;
final DebuggingOptions debuggingOptions; final DebuggingOptions debuggingOptions;
final bool usesTerminalUI; final bool usesTerminalUi;
final bool stayResident; final bool stayResident;
final bool ipv6; final bool ipv6;
final Completer<int> _finished = Completer<int>(); final Completer<int> _finished = Completer<int>();
bool _exited = false; bool _exited = false;
bool hotMode ;
String _packagesFilePath; String _packagesFilePath;
String get packagesFilePath => _packagesFilePath; String get packagesFilePath => _packagesFilePath;
String _projectRootPath; String _projectRootPath;
...@@ -601,68 +603,68 @@ abstract class ResidentRunner { ...@@ -601,68 +603,68 @@ abstract class ResidentRunner {
await Future.wait(futures); await Future.wait(futures);
} }
Future<void> _debugDumpApp() async { Future<void> debugDumpApp() async {
await refreshViews(); await refreshViews();
for (FlutterDevice device in flutterDevices) for (FlutterDevice device in flutterDevices)
await device.debugDumpApp(); await device.debugDumpApp();
} }
Future<void> _debugDumpRenderTree() async { Future<void> debugDumpRenderTree() async {
await refreshViews(); await refreshViews();
for (FlutterDevice device in flutterDevices) for (FlutterDevice device in flutterDevices)
await device.debugDumpRenderTree(); await device.debugDumpRenderTree();
} }
Future<void> _debugDumpLayerTree() async { Future<void> debugDumpLayerTree() async {
await refreshViews(); await refreshViews();
for (FlutterDevice device in flutterDevices) for (FlutterDevice device in flutterDevices)
await device.debugDumpLayerTree(); await device.debugDumpLayerTree();
} }
Future<void> _debugDumpSemanticsTreeInTraversalOrder() async { Future<void> debugDumpSemanticsTreeInTraversalOrder() async {
await refreshViews(); await refreshViews();
for (FlutterDevice device in flutterDevices) for (FlutterDevice device in flutterDevices)
await device.debugDumpSemanticsTreeInTraversalOrder(); await device.debugDumpSemanticsTreeInTraversalOrder();
} }
Future<void> _debugDumpSemanticsTreeInInverseHitTestOrder() async { Future<void> debugDumpSemanticsTreeInInverseHitTestOrder() async {
await refreshViews(); await refreshViews();
for (FlutterDevice device in flutterDevices) for (FlutterDevice device in flutterDevices)
await device.debugDumpSemanticsTreeInInverseHitTestOrder(); await device.debugDumpSemanticsTreeInInverseHitTestOrder();
} }
Future<void> _debugToggleDebugPaintSizeEnabled() async { Future<void> debugToggleDebugPaintSizeEnabled() async {
await refreshViews(); await refreshViews();
for (FlutterDevice device in flutterDevices) for (FlutterDevice device in flutterDevices)
await device.toggleDebugPaintSizeEnabled(); await device.toggleDebugPaintSizeEnabled();
} }
Future<void> _debugToggleDebugCheckElevationsEnabled() async { Future<void> debugToggleDebugCheckElevationsEnabled() async {
await refreshViews(); await refreshViews();
for (FlutterDevice device in flutterDevices) for (FlutterDevice device in flutterDevices)
await device.toggleDebugCheckElevationsEnabled(); await device.toggleDebugCheckElevationsEnabled();
} }
Future<void> _debugTogglePerformanceOverlayOverride() async { Future<void> debugTogglePerformanceOverlayOverride() async {
await refreshViews(); await refreshViews();
for (FlutterDevice device in flutterDevices) for (FlutterDevice device in flutterDevices)
await device.debugTogglePerformanceOverlayOverride(); await device.debugTogglePerformanceOverlayOverride();
} }
Future<void> _debugToggleWidgetInspector() async { Future<void> debugToggleWidgetInspector() async {
await refreshViews(); await refreshViews();
for (FlutterDevice device in flutterDevices) for (FlutterDevice device in flutterDevices)
await device.toggleWidgetInspector(); await device.toggleWidgetInspector();
} }
Future<void> _debugToggleProfileWidgetBuilds() async { Future<void> debugToggleProfileWidgetBuilds() async {
await refreshViews(); await refreshViews();
for (FlutterDevice device in flutterDevices) { for (FlutterDevice device in flutterDevices) {
await device.toggleProfileWidgetBuilds(); await device.toggleProfileWidgetBuilds();
} }
} }
Future<void> _screenshot(FlutterDevice device) async { Future<void> screenshot(FlutterDevice device) async {
final Status status = logger.startProgress('Taking screenshot for ${device.device.name}...', timeout: timeoutConfiguration.fastOperation); final Status status = logger.startProgress('Taking screenshot for ${device.device.name}...', timeout: timeoutConfiguration.fastOperation);
final File outputFile = getUniqueFile(fs.currentDirectory, 'flutter', 'png'); final File outputFile = getUniqueFile(fs.currentDirectory, 'flutter', 'png');
try { try {
...@@ -700,7 +702,7 @@ abstract class ResidentRunner { ...@@ -700,7 +702,7 @@ abstract class ResidentRunner {
} }
} }
Future<void> _debugTogglePlatform() async { Future<void> debugTogglePlatform() async {
await refreshViews(); await refreshViews();
final String from = await flutterDevices[0].views[0].uiIsolate.flutterPlatformOverride(); final String from = await flutterDevices[0].views[0].uiIsolate.flutterPlatformOverride();
String to; String to;
...@@ -709,39 +711,6 @@ abstract class ResidentRunner { ...@@ -709,39 +711,6 @@ abstract class ResidentRunner {
printStatus('Switched operating system to $to'); printStatus('Switched operating system to $to');
} }
void registerSignalHandlers() {
assert(stayResident);
io.ProcessSignal.SIGINT.watch().listen(_cleanUpAndExit);
io.ProcessSignal.SIGTERM.watch().listen(_cleanUpAndExit);
if (!supportsServiceProtocol || !supportsRestart)
return;
io.ProcessSignal.SIGUSR1.watch().listen(_handleSignal);
io.ProcessSignal.SIGUSR2.watch().listen(_handleSignal);
}
Future<void> _cleanUpAndExit(io.ProcessSignal signal) async {
_resetTerminal();
await cleanupAfterSignal();
io.exit(0);
}
bool _processingUserRequest = false;
Future<void> _handleSignal(io.ProcessSignal signal) async {
if (_processingUserRequest) {
printTrace('Ignoring signal: "$signal" because we are busy.');
return;
}
_processingUserRequest = true;
final bool fullRestart = signal == io.ProcessSignal.SIGUSR2;
try {
await restart(fullRestart: fullRestart);
} finally {
_processingUserRequest = false;
}
}
Future<void> stopEchoingDeviceLog() async { Future<void> stopEchoingDeviceLog() async {
await Future.wait<void>( await Future.wait<void>(
flutterDevices.map<Future<void>>((FlutterDevice device) => device.stopEchoingDeviceLog()) flutterDevices.map<Future<void>>((FlutterDevice device) => device.stopEchoingDeviceLog())
...@@ -764,7 +733,7 @@ abstract class ResidentRunner { ...@@ -764,7 +733,7 @@ abstract class ResidentRunner {
bool viewFound = false; bool viewFound = false;
for (FlutterDevice device in flutterDevices) { for (FlutterDevice device in flutterDevices) {
await device._connect( await device.connect(
reloadSources: reloadSources, reloadSources: reloadSources,
restart: restart, restart: restart,
compileExpression: compileExpression, compileExpression: compileExpression,
...@@ -805,125 +774,6 @@ abstract class ResidentRunner { ...@@ -805,125 +774,6 @@ abstract class ResidentRunner {
return Future<void>.error(error, stack); return Future<void>.error(error, stack);
} }
/// Returns [true] if the input has been handled by this function.
Future<bool> _commonTerminalInputHandler(String character) async {
printStatus(''); // the key the user tapped might be on this line
switch(character) {
case 'a':
if (supportsServiceProtocol) {
await _debugToggleProfileWidgetBuilds();
return true;
}
return false;
case 'd':
case 'D':
await detach();
return true;
case 'h':
case 'H':
case '?':
// help
printHelp(details: true);
return true;
case 'i':
case 'I':
if (supportsServiceProtocol) {
await _debugToggleWidgetInspector();
return true;
}
return false;
case 'L':
if (supportsServiceProtocol) {
await _debugDumpLayerTree();
return true;
}
return false;
case 'o':
case 'O':
if (supportsServiceProtocol && isRunningDebug) {
await _debugTogglePlatform();
return true;
}
return false;
case 'p':
if (supportsServiceProtocol && isRunningDebug) {
await _debugToggleDebugPaintSizeEnabled();
return true;
}
return false;
case 'P':
if (supportsServiceProtocol) {
await _debugTogglePerformanceOverlayOverride();
return true;
}
return false;
case 'q':
case 'Q':
// exit
await exit();
return true;
case 's':
for (FlutterDevice device in flutterDevices) {
if (device.device.supportsScreenshot)
await _screenshot(device);
}
return true;
case 'S':
if (supportsServiceProtocol) {
await _debugDumpSemanticsTreeInTraversalOrder();
return true;
}
return false;
case 't':
case 'T':
if (supportsServiceProtocol) {
await _debugDumpRenderTree();
return true;
}
return false;
case 'U':
if (supportsServiceProtocol) {
await _debugDumpSemanticsTreeInInverseHitTestOrder();
return true;
}
return false;
case 'w':
case 'W':
if (supportsServiceProtocol) {
await _debugDumpApp();
return true;
}
return false;
case 'z':
case 'Z':
await _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();
if (_processingUserRequest) {
printTrace('Ignoring terminal input: "$command" because we are busy.');
return;
}
_processingUserRequest = true;
try {
final bool handled = await _commonTerminalInputHandler(command);
if (!handled)
await handleTerminalCommand(command);
} catch (error, st) {
printError('$error\n$st');
await _cleanUpAndExit(null);
} finally {
_processingUserRequest = false;
}
}
void _serviceDisconnected() { void _serviceDisconnected() {
if (_exited) { if (_exited) {
// User requested the application exit. // User requested the application exit.
...@@ -932,7 +782,6 @@ abstract class ResidentRunner { ...@@ -932,7 +782,6 @@ abstract class ResidentRunner {
if (_finished.isCompleted) if (_finished.isCompleted)
return; return;
printStatus('Lost connection to device.'); printStatus('Lost connection to device.');
_resetTerminal();
_finished.complete(0); _finished.complete(0);
} }
...@@ -940,27 +789,9 @@ abstract class ResidentRunner { ...@@ -940,27 +789,9 @@ abstract class ResidentRunner {
if (_finished.isCompleted) if (_finished.isCompleted)
return; return;
printStatus('Application finished.'); printStatus('Application finished.');
_resetTerminal();
_finished.complete(0); _finished.complete(0);
} }
void _resetTerminal() {
if (usesTerminalUI)
terminal.singleCharMode = false;
}
void setupTerminal() {
assert(stayResident);
if (usesTerminalUI) {
if (!logger.quiet) {
printStatus('');
printHelp(details: false);
}
terminal.singleCharMode = true;
terminal.keystrokes.listen(processTerminalInput);
}
}
Future<int> waitForAppToFinish() async { Future<int> waitForAppToFinish() async {
final int exitCode = await _finished.future; final int exitCode = await _finished.future;
assert(exitCode != null); assert(exitCode != null);
...@@ -1004,10 +835,38 @@ abstract class ResidentRunner { ...@@ -1004,10 +835,38 @@ abstract class ResidentRunner {
/// Called when a signal has requested we exit. /// Called when a signal has requested we exit.
Future<void> cleanupAfterSignal(); Future<void> cleanupAfterSignal();
/// 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. /// Called when the runner should handle a terminal command.
Future<void> handleTerminalCommand(String code); 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 {
...@@ -1051,6 +910,176 @@ Future<String> getMissingPackageHintForPlatform(TargetPlatform platform) async { ...@@ -1051,6 +910,176 @@ Future<String> getMissingPackageHintForPlatform(TargetPlatform platform) async {
} }
} }
/// Redirects terminal commands to the correct resident runner methods.
class TerminalHandler {
TerminalHandler(this.residentRunner);
final ResidentRunner residentRunner;
bool _processingUserRequest = false;
StreamSubscription<void> subscription;
void setupTerminal() {
if (!logger.quiet) {
printStatus('');
residentRunner.printHelp(details: false);
}
terminal.singleCharMode = true;
subscription = terminal.keystrokes.listen(processTerminalInput);
}
void registerSignalHandlers() {
assert(residentRunner.stayResident);
io.ProcessSignal.SIGINT.watch().listen(_cleanUpAndExit);
io.ProcessSignal.SIGTERM.watch().listen(_cleanUpAndExit);
if (!residentRunner.supportsServiceProtocol || !residentRunner.supportsRestart)
return;
io.ProcessSignal.SIGUSR1.watch().listen(_handleSignal);
io.ProcessSignal.SIGUSR2.watch().listen(_handleSignal);
}
/// Returns [true] if the input has been handled by this function.
Future<bool> _commonTerminalInputHandler(String character) async {
printStatus(''); // the key the user tapped might be on this line
switch(character) {
case 'a':
if (residentRunner.supportsServiceProtocol) {
await residentRunner.debugToggleProfileWidgetBuilds();
return true;
}
return false;
case 'd':
case 'D':
await residentRunner.detach();
return true;
case 'h':
case 'H':
case '?':
// help
residentRunner.printHelp(details: true);
return true;
case 'i':
case 'I':
if (residentRunner.supportsServiceProtocol) {
await residentRunner.debugToggleWidgetInspector();
return true;
}
return false;
case 'L':
if (residentRunner.supportsServiceProtocol) {
await residentRunner.debugDumpLayerTree();
return true;
}
return false;
case 'o':
case 'O':
if (residentRunner.supportsServiceProtocol && residentRunner.isRunningDebug) {
await residentRunner.debugTogglePlatform();
return true;
}
return false;
case 'p':
if (residentRunner.supportsServiceProtocol && residentRunner.isRunningDebug) {
await residentRunner.debugToggleDebugPaintSizeEnabled();
return true;
}
return false;
case 'P':
if (residentRunner.supportsServiceProtocol) {
await residentRunner.debugTogglePerformanceOverlayOverride();
return true;
}
return false;
case 'q':
case 'Q':
// exit
await residentRunner.exit();
return true;
case 's':
for (FlutterDevice device in residentRunner.flutterDevices) {
if (device.device.supportsScreenshot)
await residentRunner.screenshot(device);
}
return true;
case 'S':
if (residentRunner.supportsServiceProtocol) {
await residentRunner.debugDumpSemanticsTreeInTraversalOrder();
return true;
}
return false;
case 't':
case 'T':
if (residentRunner.supportsServiceProtocol) {
await residentRunner.debugDumpRenderTree();
return true;
}
return false;
case 'U':
if (residentRunner.supportsServiceProtocol) {
await residentRunner.debugDumpSemanticsTreeInInverseHitTestOrder();
return true;
}
return false;
case 'w':
case 'W':
if (residentRunner.supportsServiceProtocol) {
await residentRunner.debugDumpApp();
return true;
}
return false;
case 'z':
case 'Z':
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();
if (_processingUserRequest) {
printTrace('Ignoring terminal input: "$command" because we are busy.');
return;
}
_processingUserRequest = true;
try {
final bool handled = await _commonTerminalInputHandler(command);
if (!handled)
await residentRunner.handleTerminalCommand(command);
} catch (error, st) {
printError('$error\n$st');
await _cleanUpAndExit(null);
} finally {
_processingUserRequest = false;
}
}
Future<void> _handleSignal(io.ProcessSignal signal) async {
if (_processingUserRequest) {
printTrace('Ignoring signal: "$signal" because we are busy.');
return;
}
_processingUserRequest = true;
final bool fullRestart = signal == io.ProcessSignal.SIGUSR2;
try {
await residentRunner.restart(fullRestart: fullRestart);
} finally {
_processingUserRequest = false;
}
}
Future<void> _cleanUpAndExit(io.ProcessSignal signal) async {
terminal.singleCharMode = false;
await subscription.cancel();
await residentRunner.cleanupAfterSignal();
io.exit(0);
}
}
class DebugConnectionInfo { class DebugConnectionInfo {
DebugConnectionInfo({ this.httpUri, this.wsUri, this.baseUri }); DebugConnectionInfo({ this.httpUri, this.wsUri, this.baseUri });
......
...@@ -37,10 +37,10 @@ class ResidentWebRunner extends ResidentRunner { ...@@ -37,10 +37,10 @@ class ResidentWebRunner extends ResidentRunner {
}) : super( }) : super(
flutterDevices, flutterDevices,
target: target, target: target,
usesTerminalUI: true,
stayResident: true,
debuggingOptions: debuggingOptions, debuggingOptions: debuggingOptions,
ipv6: ipv6, ipv6: ipv6,
usesTerminalUi: true,
stayResident: true,
); );
WebAssetServer _server; WebAssetServer _server;
...@@ -54,7 +54,6 @@ class ResidentWebRunner extends ResidentRunner { ...@@ -54,7 +54,6 @@ class ResidentWebRunner extends ResidentRunner {
{Completer<DebugConnectionInfo> connectionInfoCompleter, {Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<void> appStartedCompleter}) async { Completer<void> appStartedCompleter}) async {
connectionInfoCompleter?.complete(DebugConnectionInfo()); connectionInfoCompleter?.complete(DebugConnectionInfo());
setupTerminal();
final int result = await waitForAppToFinish(); final int result = await waitForAppToFinish();
await cleanupAtFinish(); await cleanupAtFinish();
return result; return result;
......
...@@ -19,16 +19,17 @@ class ColdRunner extends ResidentRunner { ...@@ -19,16 +19,17 @@ class ColdRunner extends ResidentRunner {
List<FlutterDevice> devices, { List<FlutterDevice> devices, {
String target, String target,
DebuggingOptions debuggingOptions, DebuggingOptions debuggingOptions,
bool usesTerminalUI = true,
this.traceStartup = false, this.traceStartup = false,
this.awaitFirstFrameWhenTracing = true, this.awaitFirstFrameWhenTracing = true,
this.applicationBinary, this.applicationBinary,
bool stayResident = true,
bool ipv6 = false, bool ipv6 = false,
bool usesTerminalUi = false,
bool stayResident = true,
}) : super(devices, }) : super(devices,
target: target, target: target,
debuggingOptions: debuggingOptions, debuggingOptions: debuggingOptions,
usesTerminalUI: usesTerminalUI, hotMode: false,
usesTerminalUi: usesTerminalUi,
stayResident: stayResident, stayResident: stayResident,
ipv6: ipv6); ipv6: ipv6);
...@@ -104,9 +105,6 @@ class ColdRunner extends ResidentRunner { ...@@ -104,9 +105,6 @@ class ColdRunner extends ResidentRunner {
); );
} }
appFinished(); appFinished();
} else if (stayResident) {
setupTerminal();
registerSignalHandlers();
} }
appStartedCompleter?.complete(); appStartedCompleter?.complete();
...@@ -138,10 +136,6 @@ class ColdRunner extends ResidentRunner { ...@@ -138,10 +136,6 @@ class ColdRunner extends ResidentRunner {
printTrace('Connected to $view.'); printTrace('Connected to $view.');
} }
} }
if (stayResident) {
setupTerminal();
registerSignalHandlers();
}
appStartedCompleter?.complete(); appStartedCompleter?.complete();
if (stayResident) { if (stayResident) {
return waitForAppToFinish(); return waitForAppToFinish();
...@@ -150,9 +144,6 @@ class ColdRunner extends ResidentRunner { ...@@ -150,9 +144,6 @@ class ColdRunner extends ResidentRunner {
return 0; return 0;
} }
@override
Future<void> handleTerminalCommand(String code) async { }
@override @override
Future<void> cleanupAfterSignal() async { Future<void> cleanupAfterSignal() async {
await stopEchoingDeviceLog(); await stopEchoingDeviceLog();
......
...@@ -57,7 +57,7 @@ class HotRunner extends ResidentRunner { ...@@ -57,7 +57,7 @@ class HotRunner extends ResidentRunner {
List<FlutterDevice> devices, { List<FlutterDevice> devices, {
String target, String target,
DebuggingOptions debuggingOptions, DebuggingOptions debuggingOptions,
bool usesTerminalUI = true, bool usesTerminalUi = true,
this.benchmarkMode = false, this.benchmarkMode = false,
this.applicationBinary, this.applicationBinary,
this.hostIsIde = false, this.hostIsIde = false,
...@@ -69,10 +69,11 @@ class HotRunner extends ResidentRunner { ...@@ -69,10 +69,11 @@ class HotRunner extends ResidentRunner {
}) : super(devices, }) : super(devices,
target: target, target: target,
debuggingOptions: debuggingOptions, debuggingOptions: debuggingOptions,
usesTerminalUI: usesTerminalUI, usesTerminalUi: usesTerminalUi,
projectRootPath: projectRootPath, projectRootPath: projectRootPath,
packagesFilePath: packagesFilePath, packagesFilePath: packagesFilePath,
stayResident: stayResident, stayResident: stayResident,
hotMode: true,
ipv6: ipv6); ipv6: ipv6);
final bool benchmarkMode; final bool benchmarkMode;
...@@ -194,11 +195,6 @@ class HotRunner extends ResidentRunner { ...@@ -194,11 +195,6 @@ class HotRunner extends ResidentRunner {
printTrace('Connected to $view.'); printTrace('Connected to $view.');
} }
if (stayResident) {
setupTerminal();
registerSignalHandlers();
}
appStartedCompleter?.complete(); appStartedCompleter?.complete();
if (benchmarkMode) { if (benchmarkMode) {
...@@ -264,32 +260,6 @@ class HotRunner extends ResidentRunner { ...@@ -264,32 +260,6 @@ class HotRunner extends ResidentRunner {
); );
} }
@override
Future<void> handleTerminalCommand(String code) async {
final String lower = code.toLowerCase();
if (lower == 'r') {
OperationResult result;
if (code == 'R') {
// If hot restart is not supported for all devices, ignore the command.
if (!canHotRestart) {
return;
}
result = await restart(fullRestart: true);
} else {
result = await restart(fullRestart: false);
}
if (!result.isOk) {
printStatus('Try again after fixing the above error(s).', emphasis: true);
}
} else if (lower == '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);
}
}
}
Future<List<Uri>> _initDevFS() async { Future<List<Uri>> _initDevFS() async {
final String fsName = fs.path.basename(projectRootPath); final String fsName = fs.path.basename(projectRootPath);
final List<Uri> devFSUris = <Uri>[]; final List<Uri> devFSUris = <Uri>[];
......
...@@ -24,15 +24,18 @@ import '../src/context.dart'; ...@@ -24,15 +24,18 @@ import '../src/context.dart';
import '../src/mocks.dart'; import '../src/mocks.dart';
void main() { void main() {
final StreamLogger logger = StreamLogger();
group('attach', () { group('attach', () {
final FileSystem testFileSystem = MemoryFileSystem( StreamLogger logger;
style: platform.isWindows ? FileSystemStyle.windows : FileSystemStyle FileSystem testFileSystem;
.posix,
);
setUp(() { setUp(() {
Cache.disableLocking(); Cache.disableLocking();
logger = StreamLogger();
testFileSystem = MemoryFileSystem(
style: platform.isWindows
? FileSystemStyle.windows
: FileSystemStyle.posix,
);
testFileSystem.directory('lib').createSync(); testFileSystem.directory('lib').createSync();
testFileSystem.file(testFileSystem.path.join('lib', 'main.dart')).createSync(); testFileSystem.file(testFileSystem.path.join('lib', 'main.dart')).createSync();
}); });
...@@ -108,7 +111,8 @@ void main() { ...@@ -108,7 +111,8 @@ void main() {
const String outputDill = '/tmp/output.dill'; const String outputDill = '/tmp/output.dill';
final MockHotRunner mockHotRunner = MockHotRunner(); final MockHotRunner mockHotRunner = MockHotRunner();
when(mockHotRunner.attach()).thenAnswer((_) async => 0); when(mockHotRunner.attach(appStartedCompleter: anyNamed('appStartedCompleter')))
.thenAnswer((_) async => 0);
final MockHotRunnerFactory mockHotRunnerFactory = MockHotRunnerFactory(); final MockHotRunnerFactory mockHotRunnerFactory = MockHotRunnerFactory();
when( when(
...@@ -119,7 +123,7 @@ void main() { ...@@ -119,7 +123,7 @@ void main() {
dillOutputPath: anyNamed('dillOutputPath'), dillOutputPath: anyNamed('dillOutputPath'),
debuggingOptions: anyNamed('debuggingOptions'), debuggingOptions: anyNamed('debuggingOptions'),
packagesFilePath: anyNamed('packagesFilePath'), packagesFilePath: anyNamed('packagesFilePath'),
usesTerminalUI: anyNamed('usesTerminalUI'), usesTerminalUi: anyNamed('usesTerminalUi'),
flutterProject: anyNamed('flutterProject'), flutterProject: anyNamed('flutterProject'),
ipv6: false, ipv6: false,
), ),
...@@ -151,7 +155,7 @@ void main() { ...@@ -151,7 +155,7 @@ void main() {
dillOutputPath: outputDill, dillOutputPath: outputDill,
debuggingOptions: anyNamed('debuggingOptions'), debuggingOptions: anyNamed('debuggingOptions'),
packagesFilePath: anyNamed('packagesFilePath'), packagesFilePath: anyNamed('packagesFilePath'),
usesTerminalUI: anyNamed('usesTerminalUI'), usesTerminalUi: anyNamed('usesTerminalUi'),
flutterProject: anyNamed('flutterProject'), flutterProject: anyNamed('flutterProject'),
ipv6: false, ipv6: false,
), ),
...@@ -219,14 +223,14 @@ void main() { ...@@ -219,14 +223,14 @@ void main() {
.thenReturn(<ForwardedPort>[ForwardedPort(hostPort, devicePort)]); .thenReturn(<ForwardedPort>[ForwardedPort(hostPort, devicePort)]);
when(portForwarder.unforward(any)) when(portForwarder.unforward(any))
.thenAnswer((_) async => null); .thenAnswer((_) async => null);
when(mockHotRunner.attach()) when(mockHotRunner.attach(appStartedCompleter: anyNamed('appStartedCompleter')))
.thenAnswer((_) async => 0); .thenAnswer((_) async => 0);
when(mockHotRunnerFactory.build( when(mockHotRunnerFactory.build(
any, any,
target: anyNamed('target'), target: anyNamed('target'),
debuggingOptions: anyNamed('debuggingOptions'), debuggingOptions: anyNamed('debuggingOptions'),
packagesFilePath: anyNamed('packagesFilePath'), packagesFilePath: anyNamed('packagesFilePath'),
usesTerminalUI: anyNamed('usesTerminalUI'), usesTerminalUi: anyNamed('usesTerminalUi'),
flutterProject: anyNamed('flutterProject'), flutterProject: anyNamed('flutterProject'),
ipv6: false, ipv6: false,
)).thenReturn(mockHotRunner); )).thenReturn(mockHotRunner);
...@@ -256,7 +260,7 @@ void main() { ...@@ -256,7 +260,7 @@ void main() {
target: foo.path, target: foo.path,
debuggingOptions: anyNamed('debuggingOptions'), debuggingOptions: anyNamed('debuggingOptions'),
packagesFilePath: anyNamed('packagesFilePath'), packagesFilePath: anyNamed('packagesFilePath'),
usesTerminalUI: anyNamed('usesTerminalUI'), usesTerminalUi: anyNamed('usesTerminalUi'),
flutterProject: anyNamed('flutterProject'), flutterProject: anyNamed('flutterProject'),
ipv6: false, ipv6: false,
)).called(1); )).called(1);
......
...@@ -3,91 +3,114 @@ ...@@ -3,91 +3,114 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/run_hot.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';
import 'src/context.dart'; import 'src/testbed.dart';
class TestRunner extends ResidentRunner {
TestRunner(List<FlutterDevice> devices)
: super(devices);
bool hasHelpBeenPrinted = false;
String receivedCommand;
@override
Future<void> cleanupAfterSignal() async { }
@override
Future<void> cleanupAtFinish() async { }
@override
Future<void> handleTerminalCommand(String code) async {
receivedCommand = code;
}
@override
void printHelp({ bool details }) {
hasHelpBeenPrinted = true;
}
@override
Future<int> run({
Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<void> appStartedCompleter,
String route,
bool shouldBuild = true,
}) async => null;
@override
Future<int> attach({
Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<void> appStartedCompleter,
}) async => null;
}
void main() { void main() {
TestRunner createTestRunner() { group('ResidentRunner', () {
// TODO(jacobr): make these tests run with `trackWidgetCreation: true` as final Uri testUri = Uri.parse('foo://bar');
// well as the default flags. Testbed testbed;
return TestRunner( MockDevice mockDevice;
<FlutterDevice>[FlutterDevice(MockDevice(), trackWidgetCreation: false, buildMode: BuildMode.debug)], MockVMService mockVMService;
); MockDevFS mockDevFS;
} ResidentRunner residentRunner;
group('keyboard input handling', () { setUp(() {
testUsingContext('single help character', () async { testbed = Testbed(setup: () {
final TestRunner testRunner = createTestRunner(); residentRunner = HotRunner(
expect(testRunner.hasHelpBeenPrinted, isFalse); <FlutterDevice>[
await testRunner.processTerminalInput('h'); mockDevice,
expect(testRunner.hasHelpBeenPrinted, isTrue); ],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
);
}); });
testUsingContext('help character surrounded with newlines', () async { mockDevice = MockDevice();
final TestRunner testRunner = createTestRunner(); mockVMService = MockVMService();
expect(testRunner.hasHelpBeenPrinted, isFalse); mockDevFS = MockDevFS();
await testRunner.processTerminalInput('\nh\n'); // DevFS Mocks
expect(testRunner.hasHelpBeenPrinted, isTrue); when(mockDevFS.lastCompiled).thenReturn(DateTime(2000));
when(mockDevFS.sources).thenReturn(<Uri>[]);
when(mockDevFS.destroy()).thenAnswer((Invocation invocation) async { });
// FlutterDevice Mocks.
when(mockDevice.updateDevFS(
// Intentionally provide empty list to match above mock.
invalidatedFiles: <Uri>[],
mainPath: anyNamed('mainPath'),
target: anyNamed('target'),
bundle: anyNamed('bundle'),
firstBuildTime: anyNamed('firstBuildTime'),
bundleFirstUpload: anyNamed('bundleFirstUpload'),
bundleDirty: anyNamed('bundleDirty'),
fullRestart: anyNamed('fullRestart'),
projectRootPath: anyNamed('projectRootPath'),
pathToReload: anyNamed('pathToReload'),
)).thenAnswer((Invocation invocation) async {
return UpdateFSReport(
success: true,
syncedBytes: 0,
invalidatedSourcesCount: 0,
);
}); });
testUsingContext('reload character with trailing newline', () async { when(mockDevice.devFS).thenReturn(mockDevFS);
final TestRunner testRunner = createTestRunner(); when(mockDevice.views).thenReturn(<FlutterView>[
expect(testRunner.receivedCommand, isNull); MockFlutterView(),
await testRunner.processTerminalInput('r\n'); ]);
expect(testRunner.receivedCommand, equals('r')); when(mockDevice.stopEchoingDeviceLog()).thenAnswer((Invocation invocation) async { });
when(mockDevice.observatoryUris).thenReturn(<Uri>[
testUri,
]);
when(mockDevice.connect(
reloadSources: anyNamed('reloadSources'),
restart: anyNamed('restart'),
compileExpression: anyNamed('compileExpression')
)).thenAnswer((Invocation invocation) async { });
when(mockDevice.setupDevFS(any, any, packagesFilePath: anyNamed('packagesFilePath')))
.thenAnswer((Invocation invocation) async {
return testUri;
}); });
testUsingContext('newlines', () async { when(mockDevice.vmServices).thenReturn(<VMService>[
final TestRunner testRunner = createTestRunner(); mockVMService,
expect(testRunner.receivedCommand, isNull); ]);
await testRunner.processTerminalInput('\n\n'); when(mockDevice.refreshViews()).thenAnswer((Invocation invocation) async { });
expect(testRunner.receivedCommand, equals('')); // VMService mocks.
when(mockVMService.wsAddress).thenReturn(testUri);
when(mockVMService.done).thenAnswer((Invocation invocation) {
final Completer<void> result = Completer<void>.sync();
return result.future;
}); });
}); });
}
class MockDevice extends Mock implements Device { test('Can attach to device successfully', () => testbed.run(() async {
MockDevice() { final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
when(isSupported()).thenReturn(true); final Completer<void> onAppStart = Completer<void>.sync();
} final Future<int> result = residentRunner.attach(
appStartedCompleter: onAppStart,
connectionInfoCompleter: onConnectionInfo,
);
final Future<DebugConnectionInfo> connectionInfo = onConnectionInfo.future;
expect(await result, 0);
verify(mockDevice.initLogReader()).called(1);
expect(onConnectionInfo.isCompleted, true);
expect((await connectionInfo).baseUri, 'foo://bar');
expect(onAppStart.isCompleted, true);
}));
});
} }
class MockDevice extends Mock implements FlutterDevice {}
class MockFlutterView extends Mock implements FlutterView {}
class MockVMService extends Mock implements VMService {}
class MockDevFS extends Mock implements DevFS {}
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/resident_runner.dart';
import 'package:mockito/mockito.dart';
import 'src/common.dart';
import 'src/context.dart';
void main() {
TestRunner createTestRunner() {
// TODO(jacobr): make these tests run with `trackWidgetCreation: true` as
// well as the default flags.
return TestRunner(
<FlutterDevice>[FlutterDevice(MockDevice(), trackWidgetCreation: false, buildMode: BuildMode.debug)],
);
}
group('keyboard input handling', () {
testUsingContext('single help character', () async {
final TestRunner testRunner = createTestRunner();
final TerminalHandler terminalHandler = TerminalHandler(testRunner);
expect(testRunner.hasHelpBeenPrinted, isFalse);
await terminalHandler.processTerminalInput('h');
expect(testRunner.hasHelpBeenPrinted, isTrue);
});
testUsingContext('help character surrounded with newlines', () async {
final TestRunner testRunner = createTestRunner();
final TerminalHandler terminalHandler = TerminalHandler(testRunner);
expect(testRunner.hasHelpBeenPrinted, isFalse);
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(''));
});
});
group('keycode verification, brought to you by the letter r', () {
MockResidentRunner mockResidentRunner;
TerminalHandler terminalHandler;
setUp(() {
mockResidentRunner = MockResidentRunner();
terminalHandler = TerminalHandler(mockResidentRunner);
when(mockResidentRunner.supportsServiceProtocol).thenReturn(true);
when(mockResidentRunner.handleTerminalCommand(any)).thenReturn(null);
});
testUsingContext('a - debugToggleProfileWidgetBuilds with service protocol', () async {
await terminalHandler.processTerminalInput('a');
verify(mockResidentRunner.debugToggleProfileWidgetBuilds()).called(1);
});
testUsingContext('a - debugToggleProfileWidgetBuilds without service protocol', () async {
when(mockResidentRunner.supportsServiceProtocol).thenReturn(false);
await terminalHandler.processTerminalInput('a');
verifyNever(mockResidentRunner.debugToggleProfileWidgetBuilds());
});
testUsingContext('a - debugToggleProfileWidgetBuilds', () async {
when(mockResidentRunner.supportsServiceProtocol).thenReturn(true);
await terminalHandler.processTerminalInput('a');
verify(mockResidentRunner.debugToggleProfileWidgetBuilds()).called(1);
});
testUsingContext('d,D - detach', () async {
await terminalHandler.processTerminalInput('d');
await terminalHandler.processTerminalInput('D');
verify(mockResidentRunner.detach()).called(2);
});
testUsingContext('h,H,? - printHelp', () async {
await terminalHandler.processTerminalInput('h');
await terminalHandler.processTerminalInput('H');
await terminalHandler.processTerminalInput('?');
verify(mockResidentRunner.printHelp(details: true)).called(3);
});
testUsingContext('i, I - debugToggleWidgetInspector with service protocol', () async {
await terminalHandler.processTerminalInput('i');
await terminalHandler.processTerminalInput('I');
verify(mockResidentRunner.debugToggleWidgetInspector()).called(2);
});
testUsingContext('i, I - debugToggleWidgetInspector without service protocol', () async {
when(mockResidentRunner.supportsServiceProtocol).thenReturn(false);
await terminalHandler.processTerminalInput('i');
await terminalHandler.processTerminalInput('I');
verifyNever(mockResidentRunner.debugToggleWidgetInspector());
});
testUsingContext('L - debugDumpLayerTree with service protocol', () async {
await terminalHandler.processTerminalInput('L');
verify(mockResidentRunner.debugDumpLayerTree()).called(1);
});
testUsingContext('L - debugDumpLayerTree without service protocol', () async {
when(mockResidentRunner.supportsServiceProtocol).thenReturn(false);
await terminalHandler.processTerminalInput('L');
verifyNever(mockResidentRunner.debugDumpLayerTree());
});
testUsingContext('o,O - debugTogglePlatform with service protocol and debug mode', () async {
when(mockResidentRunner.isRunningDebug).thenReturn(true);
await terminalHandler.processTerminalInput('o');
await terminalHandler.processTerminalInput('O');
verify(mockResidentRunner.debugTogglePlatform()).called(2);
});
testUsingContext('o,O - debugTogglePlatform without service protocol', () async {
when(mockResidentRunner.supportsServiceProtocol).thenReturn(false);
when(mockResidentRunner.isRunningDebug).thenReturn(true);
await terminalHandler.processTerminalInput('o');
await terminalHandler.processTerminalInput('O');
verifyNever(mockResidentRunner.debugTogglePlatform());
});
testUsingContext('p - debugToggleDebugPaintSizeEnabled with service protocol and debug mode', () async {
when(mockResidentRunner.isRunningDebug).thenReturn(true);
await terminalHandler.processTerminalInput('p');
verify(mockResidentRunner.debugToggleDebugPaintSizeEnabled()).called(1);
});
testUsingContext('p - debugTogglePlatform without service protocol', () async {
when(mockResidentRunner.supportsServiceProtocol).thenReturn(false);
when(mockResidentRunner.isRunningDebug).thenReturn(true);
await terminalHandler.processTerminalInput('p');
verifyNever(mockResidentRunner.debugToggleDebugPaintSizeEnabled());
});
testUsingContext('p - debugToggleDebugPaintSizeEnabled with service protocol and debug mode', () async {
when(mockResidentRunner.isRunningDebug).thenReturn(true);
await terminalHandler.processTerminalInput('p');
verify(mockResidentRunner.debugToggleDebugPaintSizeEnabled()).called(1);
});
testUsingContext('p - debugTogglePlatform without service protocol', () async {
when(mockResidentRunner.supportsServiceProtocol).thenReturn(false);
when(mockResidentRunner.isRunningDebug).thenReturn(true);
await terminalHandler.processTerminalInput('p');
verifyNever(mockResidentRunner.debugToggleDebugPaintSizeEnabled());
});
testUsingContext('P - debugTogglePerformanceOverlayOverride with service protocol', () async {
await terminalHandler.processTerminalInput('P');
verify(mockResidentRunner.debugTogglePerformanceOverlayOverride()).called(1);
});
testUsingContext('P - debugTogglePerformanceOverlayOverride without service protocol', () async {
when(mockResidentRunner.supportsServiceProtocol).thenReturn(false);
await terminalHandler.processTerminalInput('P');
verifyNever(mockResidentRunner.debugTogglePerformanceOverlayOverride());
});
testUsingContext('q,Q - exit', () async {
await terminalHandler.processTerminalInput('q');
await terminalHandler.processTerminalInput('Q');
verify(mockResidentRunner.exit()).called(2);
});
testUsingContext('s - screenshot', () async {
final MockDevice mockDevice = MockDevice();
final MockFlutterDevice mockFlutterDevice = MockFlutterDevice();
when(mockResidentRunner.isRunningDebug).thenReturn(true);
when(mockResidentRunner.flutterDevices).thenReturn(<FlutterDevice>[mockFlutterDevice]);
when(mockFlutterDevice.device).thenReturn(mockDevice);
when(mockDevice.supportsScreenshot).thenReturn(true);
await terminalHandler.processTerminalInput('s');
verify(mockResidentRunner.screenshot(mockFlutterDevice)).called(1);
});
testUsingContext('S - debugDumpSemanticsTreeInTraversalOrder with service protocol', () async {
await terminalHandler.processTerminalInput('S');
verify(mockResidentRunner.debugDumpSemanticsTreeInTraversalOrder()).called(1);
});
testUsingContext('S - debugDumpSemanticsTreeInTraversalOrder without service protocol', () async {
when(mockResidentRunner.supportsServiceProtocol).thenReturn(false);
await terminalHandler.processTerminalInput('S');
verifyNever(mockResidentRunner.debugDumpSemanticsTreeInTraversalOrder());
});
testUsingContext('t,T - debugDumpRenderTree with service protocol', () async {
await terminalHandler.processTerminalInput('t');
await terminalHandler.processTerminalInput('T');
verify(mockResidentRunner.debugDumpRenderTree()).called(2);
});
testUsingContext('t,T - debugDumpSemanticsTreeInTraversalOrder without service protocol', () async {
when(mockResidentRunner.supportsServiceProtocol).thenReturn(false);
await terminalHandler.processTerminalInput('t');
await terminalHandler.processTerminalInput('T');
verifyNever(mockResidentRunner.debugDumpRenderTree());
});
testUsingContext('U - debugDumpRenderTree with service protocol', () async {
await terminalHandler.processTerminalInput('U');
verify(mockResidentRunner.debugDumpSemanticsTreeInInverseHitTestOrder()).called(1);
});
testUsingContext('U - debugDumpSemanticsTreeInTraversalOrder without service protocol', () async {
when(mockResidentRunner.supportsServiceProtocol).thenReturn(false);
await terminalHandler.processTerminalInput('U');
verifyNever(mockResidentRunner.debugDumpSemanticsTreeInInverseHitTestOrder());
});
testUsingContext('w,W - debugDumpApp with service protocol', () async {
await terminalHandler.processTerminalInput('w');
await terminalHandler.processTerminalInput('W');
verify(mockResidentRunner.debugDumpApp()).called(2);
});
testUsingContext('w,W - debugDumpApp without service protocol', () async {
when(mockResidentRunner.supportsServiceProtocol).thenReturn(false);
await terminalHandler.processTerminalInput('w');
await terminalHandler.processTerminalInput('W');
verifyNever(mockResidentRunner.debugDumpApp());
});
testUsingContext('z,Z - debugToggleDebugCheckElevationsEnabled with service protocol', () async {
await terminalHandler.processTerminalInput('z');
await terminalHandler.processTerminalInput('Z');
verify(mockResidentRunner.debugToggleDebugCheckElevationsEnabled()).called(2);
});
testUsingContext('z,Z - debugToggleDebugCheckElevationsEnabled without service protocol', () async {
when(mockResidentRunner.supportsServiceProtocol).thenReturn(false);
await terminalHandler.processTerminalInput('z');
await terminalHandler.processTerminalInput('Z');
// This should probably be disable when the service protocol is not enabled.
verify(mockResidentRunner.debugToggleDebugCheckElevationsEnabled()).called(2);
});
});
}
class MockDevice extends Mock implements Device {
MockDevice() {
when(isSupported()).thenReturn(true);
}
}
class MockResidentRunner extends Mock implements ResidentRunner {}
class MockFlutterDevice extends Mock implements FlutterDevice {}
class TestRunner extends ResidentRunner {
TestRunner(List<FlutterDevice> devices)
: super(devices);
bool hasHelpBeenPrinted = false;
String receivedCommand;
@override
Future<void> cleanupAfterSignal() async { }
@override
Future<void> cleanupAtFinish() async { }
@override
Future<void> handleTerminalCommand(String code) async {
receivedCommand = code;
}
@override
void printHelp({ bool details }) {
hasHelpBeenPrinted = true;
}
@override
Future<int> run({
Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<void> appStartedCompleter,
String route,
bool shouldBuild = true,
}) async => null;
@override
Future<int> attach({
Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<void> appStartedCompleter,
}) async => null;
}
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