Refactor signal and command line handler from resident runner (#35406)
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index ff3661a..ef6eb69 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -437,8 +437,8 @@ DebuggingOptions debuggingOptions, Map<String, dynamic> platformArgs, bool prebuiltApplication = false, - bool usesTerminalUi = true, bool ipv6 = false, + bool usesTerminalUi = true, }) async { if (!await _checkForSupportedAdbVersion() || !await _checkForSupportedAndroidVersion()) return LaunchResult.failed();
diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart index e06d1c9..b7a477d 100644 --- a/packages/flutter_tools/lib/src/commands/attach.dart +++ b/packages/flutter_tools/lib/src/commands/attach.dart
@@ -277,7 +277,7 @@ target: targetFile, debuggingOptions: debuggingOptions, packagesFilePath: globalResults['packages'], - usesTerminalUI: daemon == null, + usesTerminalUi: daemon == null, projectRootPath: argResults['project-root'], dillOutputPath: argResults['output-dill'], ipv6: usesIpv6, @@ -312,7 +312,15 @@ result = await app.runner.waitForAppToFinish(); assert(result != null); } 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); } if (result != 0) { @@ -350,7 +358,7 @@ List<FlutterDevice> devices, { String target, DebuggingOptions debuggingOptions, - bool usesTerminalUI = true, + bool usesTerminalUi = true, bool benchmarkMode = false, File applicationBinary, bool hostIsIde = false, @@ -364,7 +372,7 @@ devices, target: target, debuggingOptions: debuggingOptions, - usesTerminalUI: usesTerminalUI, + usesTerminalUi: usesTerminalUi, benchmarkMode: benchmarkMode, applicationBinary: applicationBinary, hostIsIde: hostIsIde,
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart index dff4b34..a441ecf 100644 --- a/packages/flutter_tools/lib/src/commands/daemon.dart +++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -419,7 +419,7 @@ <FlutterDevice>[flutterDevice], target: target, debuggingOptions: options, - usesTerminalUI: false, + usesTerminalUi: false, applicationBinary: applicationBinary, projectRootPath: projectRootPath, packagesFilePath: packagesFilePath, @@ -432,8 +432,8 @@ <FlutterDevice>[flutterDevice], target: target, debuggingOptions: options, - usesTerminalUI: false, applicationBinary: applicationBinary, + usesTerminalUi: false, ipv6: ipv6, ); }
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index aeabe1f..35251f2 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -450,8 +450,8 @@ applicationBinary: applicationBinaryPath == null ? null : fs.file(applicationBinaryPath), - stayResident: stayResident, ipv6: ipv6, + stayResident: stayResident, ); } @@ -463,7 +463,14 @@ final Completer<void> appStartedTimeRecorder = Completer<void>.sync(); // This callback can't throw. unawaited(appStartedTimeRecorder.future.then<void>( - (_) { appStartedTime = systemClock.now(); } + (_) { + appStartedTime = systemClock.now(); + if (stayResident) { + TerminalHandler(runner) + ..setupTerminal() + ..registerSignalHandlers(); + } + } )); final int result = await runner.run( @@ -471,8 +478,9 @@ route: route, shouldBuild: !runningWithPrebuiltApplication && argResults['build'], ); - if (result != 0) + if (result != 0) { throwToolExit(null, exitCode: result); + } return FlutterCommandResult( ExitStatus.success, timingLabelParts: <String>[
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 1bda148..5865744 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart
@@ -406,8 +406,8 @@ DebuggingOptions debuggingOptions, Map<String, dynamic> platformArgs, bool prebuiltApplication = false, - bool usesTerminalUi = true, bool ipv6 = false, + bool usesTerminalUi = true, }); /// Whether this device implements support for hot reload.
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 2cb9109..94ab882 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -115,7 +115,7 @@ /// expressions requested during debugging of the application. /// This ensures that the reload process follows the normal orchestration of /// the Flutter Tools and not just the VM internal service. - Future<void> _connect({ + Future<void> connect({ ReloadSources reloadSources, Restart restart, CompileExpression compileExpression, @@ -375,7 +375,7 @@ platformArgs: platformArgs, route: route, prebuiltApplication: prebuiltMode, - usesTerminalUi: hotRunner.usesTerminalUI, + usesTerminalUi: hotRunner.usesTerminalUi, ipv6: hotRunner.ipv6, ); @@ -437,7 +437,7 @@ platformArgs: platformArgs, route: route, prebuiltApplication: prebuiltMode, - usesTerminalUi: coldRunner.usesTerminalUI, + usesTerminalUi: coldRunner.usesTerminalUi, ipv6: coldRunner.ipv6, ); @@ -509,11 +509,12 @@ this.flutterDevices, { this.target, this.debuggingOptions, - this.usesTerminalUI = true, String projectRootPath, String packagesFilePath, - this.stayResident, this.ipv6, + this.usesTerminalUi = true, + this.stayResident = true, + this.hotMode = true, }) { _mainPath = findMainDartFile(target); _projectRootPath = projectRootPath ?? fs.currentDirectory.path; @@ -525,11 +526,12 @@ final List<FlutterDevice> flutterDevices; final String target; final DebuggingOptions debuggingOptions; - final bool usesTerminalUI; + final bool usesTerminalUi; final bool stayResident; final bool ipv6; final Completer<int> _finished = Completer<int>(); bool _exited = false; + bool hotMode ; String _packagesFilePath; String get packagesFilePath => _packagesFilePath; String _projectRootPath; @@ -601,68 +603,68 @@ await Future.wait(futures); } - Future<void> _debugDumpApp() async { + Future<void> debugDumpApp() async { await refreshViews(); for (FlutterDevice device in flutterDevices) await device.debugDumpApp(); } - Future<void> _debugDumpRenderTree() async { + Future<void> debugDumpRenderTree() async { await refreshViews(); for (FlutterDevice device in flutterDevices) await device.debugDumpRenderTree(); } - Future<void> _debugDumpLayerTree() async { + Future<void> debugDumpLayerTree() async { await refreshViews(); for (FlutterDevice device in flutterDevices) await device.debugDumpLayerTree(); } - Future<void> _debugDumpSemanticsTreeInTraversalOrder() async { + Future<void> debugDumpSemanticsTreeInTraversalOrder() async { await refreshViews(); for (FlutterDevice device in flutterDevices) await device.debugDumpSemanticsTreeInTraversalOrder(); } - Future<void> _debugDumpSemanticsTreeInInverseHitTestOrder() async { + Future<void> debugDumpSemanticsTreeInInverseHitTestOrder() async { await refreshViews(); for (FlutterDevice device in flutterDevices) await device.debugDumpSemanticsTreeInInverseHitTestOrder(); } - Future<void> _debugToggleDebugPaintSizeEnabled() async { + Future<void> debugToggleDebugPaintSizeEnabled() async { await refreshViews(); for (FlutterDevice device in flutterDevices) await device.toggleDebugPaintSizeEnabled(); } - Future<void> _debugToggleDebugCheckElevationsEnabled() async { + Future<void> debugToggleDebugCheckElevationsEnabled() async { await refreshViews(); for (FlutterDevice device in flutterDevices) await device.toggleDebugCheckElevationsEnabled(); } - Future<void> _debugTogglePerformanceOverlayOverride() async { + Future<void> debugTogglePerformanceOverlayOverride() async { await refreshViews(); for (FlutterDevice device in flutterDevices) await device.debugTogglePerformanceOverlayOverride(); } - Future<void> _debugToggleWidgetInspector() async { + Future<void> debugToggleWidgetInspector() async { await refreshViews(); for (FlutterDevice device in flutterDevices) await device.toggleWidgetInspector(); } - Future<void> _debugToggleProfileWidgetBuilds() async { + Future<void> debugToggleProfileWidgetBuilds() async { await refreshViews(); for (FlutterDevice device in flutterDevices) { 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 File outputFile = getUniqueFile(fs.currentDirectory, 'flutter', 'png'); try { @@ -700,7 +702,7 @@ } } - Future<void> _debugTogglePlatform() async { + Future<void> debugTogglePlatform() async { await refreshViews(); final String from = await flutterDevices[0].views[0].uiIsolate.flutterPlatformOverride(); String to; @@ -709,39 +711,6 @@ 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 { await Future.wait<void>( flutterDevices.map<Future<void>>((FlutterDevice device) => device.stopEchoingDeviceLog()) @@ -764,7 +733,7 @@ bool viewFound = false; for (FlutterDevice device in flutterDevices) { - await device._connect( + await device.connect( reloadSources: reloadSources, restart: restart, compileExpression: compileExpression, @@ -805,125 +774,6 @@ 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() { if (_exited) { // User requested the application exit. @@ -932,7 +782,6 @@ if (_finished.isCompleted) return; printStatus('Lost connection to device.'); - _resetTerminal(); _finished.complete(0); } @@ -940,27 +789,9 @@ if (_finished.isCompleted) return; printStatus('Application finished.'); - _resetTerminal(); _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 { final int exitCode = await _finished.future; assert(exitCode != null); @@ -1004,10 +835,38 @@ /// Called when a signal has requested we exit. Future<void> cleanupAfterSignal(); + /// Called right before we exit. Future<void> cleanupAtFinish(); + /// 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 { @@ -1051,6 +910,176 @@ } } +/// 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 { DebugConnectionInfo({ this.httpUri, this.wsUri, this.baseUri });
diff --git a/packages/flutter_tools/lib/src/resident_web_runner.dart b/packages/flutter_tools/lib/src/resident_web_runner.dart index 36c3d12..55dad48 100644 --- a/packages/flutter_tools/lib/src/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/resident_web_runner.dart
@@ -37,10 +37,10 @@ }) : super( flutterDevices, target: target, - usesTerminalUI: true, - stayResident: true, debuggingOptions: debuggingOptions, ipv6: ipv6, + usesTerminalUi: true, + stayResident: true, ); WebAssetServer _server; @@ -54,7 +54,6 @@ {Completer<DebugConnectionInfo> connectionInfoCompleter, Completer<void> appStartedCompleter}) async { connectionInfoCompleter?.complete(DebugConnectionInfo()); - setupTerminal(); final int result = await waitForAppToFinish(); await cleanupAtFinish(); return result;
diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart index b2f7214..ad91f56 100644 --- a/packages/flutter_tools/lib/src/run_cold.dart +++ b/packages/flutter_tools/lib/src/run_cold.dart
@@ -19,16 +19,17 @@ List<FlutterDevice> devices, { String target, DebuggingOptions debuggingOptions, - bool usesTerminalUI = true, this.traceStartup = false, this.awaitFirstFrameWhenTracing = true, this.applicationBinary, - bool stayResident = true, bool ipv6 = false, + bool usesTerminalUi = false, + bool stayResident = true, }) : super(devices, target: target, debuggingOptions: debuggingOptions, - usesTerminalUI: usesTerminalUI, + hotMode: false, + usesTerminalUi: usesTerminalUi, stayResident: stayResident, ipv6: ipv6); @@ -104,9 +105,6 @@ ); } appFinished(); - } else if (stayResident) { - setupTerminal(); - registerSignalHandlers(); } appStartedCompleter?.complete(); @@ -138,10 +136,6 @@ printTrace('Connected to $view.'); } } - if (stayResident) { - setupTerminal(); - registerSignalHandlers(); - } appStartedCompleter?.complete(); if (stayResident) { return waitForAppToFinish(); @@ -151,9 +145,6 @@ } @override - Future<void> handleTerminalCommand(String code) async { } - - @override Future<void> cleanupAfterSignal() async { await stopEchoingDeviceLog(); if (_didAttach) {
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index 767b4b5..c19057e 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -57,7 +57,7 @@ List<FlutterDevice> devices, { String target, DebuggingOptions debuggingOptions, - bool usesTerminalUI = true, + bool usesTerminalUi = true, this.benchmarkMode = false, this.applicationBinary, this.hostIsIde = false, @@ -69,10 +69,11 @@ }) : super(devices, target: target, debuggingOptions: debuggingOptions, - usesTerminalUI: usesTerminalUI, + usesTerminalUi: usesTerminalUi, projectRootPath: projectRootPath, packagesFilePath: packagesFilePath, stayResident: stayResident, + hotMode: true, ipv6: ipv6); final bool benchmarkMode; @@ -194,11 +195,6 @@ printTrace('Connected to $view.'); } - if (stayResident) { - setupTerminal(); - registerSignalHandlers(); - } - appStartedCompleter?.complete(); if (benchmarkMode) { @@ -264,32 +260,6 @@ ); } - @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 { final String fsName = fs.path.basename(projectRootPath); final List<Uri> devFSUris = <Uri>[];
diff --git a/packages/flutter_tools/test/commands/attach_test.dart b/packages/flutter_tools/test/commands/attach_test.dart index 769f315..ecd9df9 100644 --- a/packages/flutter_tools/test/commands/attach_test.dart +++ b/packages/flutter_tools/test/commands/attach_test.dart
@@ -24,15 +24,18 @@ import '../src/mocks.dart'; void main() { - final StreamLogger logger = StreamLogger(); group('attach', () { - final FileSystem testFileSystem = MemoryFileSystem( - style: platform.isWindows ? FileSystemStyle.windows : FileSystemStyle - .posix, - ); + StreamLogger logger; + FileSystem testFileSystem; setUp(() { Cache.disableLocking(); + logger = StreamLogger(); + testFileSystem = MemoryFileSystem( + style: platform.isWindows + ? FileSystemStyle.windows + : FileSystemStyle.posix, + ); testFileSystem.directory('lib').createSync(); testFileSystem.file(testFileSystem.path.join('lib', 'main.dart')).createSync(); }); @@ -108,7 +111,8 @@ const String outputDill = '/tmp/output.dill'; final MockHotRunner mockHotRunner = MockHotRunner(); - when(mockHotRunner.attach()).thenAnswer((_) async => 0); + when(mockHotRunner.attach(appStartedCompleter: anyNamed('appStartedCompleter'))) + .thenAnswer((_) async => 0); final MockHotRunnerFactory mockHotRunnerFactory = MockHotRunnerFactory(); when( @@ -119,7 +123,7 @@ dillOutputPath: anyNamed('dillOutputPath'), debuggingOptions: anyNamed('debuggingOptions'), packagesFilePath: anyNamed('packagesFilePath'), - usesTerminalUI: anyNamed('usesTerminalUI'), + usesTerminalUi: anyNamed('usesTerminalUi'), flutterProject: anyNamed('flutterProject'), ipv6: false, ), @@ -151,7 +155,7 @@ dillOutputPath: outputDill, debuggingOptions: anyNamed('debuggingOptions'), packagesFilePath: anyNamed('packagesFilePath'), - usesTerminalUI: anyNamed('usesTerminalUI'), + usesTerminalUi: anyNamed('usesTerminalUi'), flutterProject: anyNamed('flutterProject'), ipv6: false, ), @@ -219,14 +223,14 @@ .thenReturn(<ForwardedPort>[ForwardedPort(hostPort, devicePort)]); when(portForwarder.unforward(any)) .thenAnswer((_) async => null); - when(mockHotRunner.attach()) - .thenAnswer((_) async => 0); + when(mockHotRunner.attach(appStartedCompleter: anyNamed('appStartedCompleter'))) + .thenAnswer((_) async => 0); when(mockHotRunnerFactory.build( any, target: anyNamed('target'), debuggingOptions: anyNamed('debuggingOptions'), packagesFilePath: anyNamed('packagesFilePath'), - usesTerminalUI: anyNamed('usesTerminalUI'), + usesTerminalUi: anyNamed('usesTerminalUi'), flutterProject: anyNamed('flutterProject'), ipv6: false, )).thenReturn(mockHotRunner); @@ -256,7 +260,7 @@ target: foo.path, debuggingOptions: anyNamed('debuggingOptions'), packagesFilePath: anyNamed('packagesFilePath'), - usesTerminalUI: anyNamed('usesTerminalUI'), + usesTerminalUi: anyNamed('usesTerminalUi'), flutterProject: anyNamed('flutterProject'), ipv6: false, )).called(1);
diff --git a/packages/flutter_tools/test/resident_runner_test.dart b/packages/flutter_tools/test/resident_runner_test.dart index a4c96b3..024d08a 100644 --- a/packages/flutter_tools/test/resident_runner_test.dart +++ b/packages/flutter_tools/test/resident_runner_test.dart
@@ -3,91 +3,114 @@ // found in the LICENSE file. import 'dart:async'; + 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/resident_runner.dart'; +import 'package:flutter_tools/src/run_hot.dart'; +import 'package:flutter_tools/src/vmservice.dart'; import 'package:mockito/mockito.dart'; import 'src/common.dart'; -import 'src/context.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; -} +import 'src/testbed.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('ResidentRunner', () { + final Uri testUri = Uri.parse('foo://bar'); + Testbed testbed; + MockDevice mockDevice; + MockVMService mockVMService; + MockDevFS mockDevFS; + ResidentRunner residentRunner; - group('keyboard input handling', () { - testUsingContext('single help character', () async { - final TestRunner testRunner = createTestRunner(); - expect(testRunner.hasHelpBeenPrinted, isFalse); - await testRunner.processTerminalInput('h'); - expect(testRunner.hasHelpBeenPrinted, isTrue); + setUp(() { + testbed = Testbed(setup: () { + residentRunner = HotRunner( + <FlutterDevice>[ + mockDevice, + ], + stayResident: false, + debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), + ); + }); + mockDevice = MockDevice(); + mockVMService = MockVMService(); + mockDevFS = MockDevFS(); + // DevFS Mocks + 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, + ); + }); + when(mockDevice.devFS).thenReturn(mockDevFS); + when(mockDevice.views).thenReturn(<FlutterView>[ + MockFlutterView(), + ]); + 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; + }); + when(mockDevice.vmServices).thenReturn(<VMService>[ + mockVMService, + ]); + when(mockDevice.refreshViews()).thenAnswer((Invocation invocation) async { }); + // VMService mocks. + when(mockVMService.wsAddress).thenReturn(testUri); + when(mockVMService.done).thenAnswer((Invocation invocation) { + final Completer<void> result = Completer<void>.sync(); + return result.future; + }); }); - testUsingContext('help character surrounded with newlines', () async { - final TestRunner testRunner = createTestRunner(); - expect(testRunner.hasHelpBeenPrinted, isFalse); - await testRunner.processTerminalInput('\nh\n'); - expect(testRunner.hasHelpBeenPrinted, isTrue); - }); - testUsingContext('reload character with trailing newline', () async { - final TestRunner testRunner = createTestRunner(); - expect(testRunner.receivedCommand, isNull); - await testRunner.processTerminalInput('r\n'); - expect(testRunner.receivedCommand, equals('r')); - }); - testUsingContext('newlines', () async { - final TestRunner testRunner = createTestRunner(); - expect(testRunner.receivedCommand, isNull); - await testRunner.processTerminalInput('\n\n'); - expect(testRunner.receivedCommand, equals('')); - }); + + test('Can attach to device successfully', () => testbed.run(() async { + final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync(); + 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 Device { - MockDevice() { - when(isSupported()).thenReturn(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 {}
diff --git a/packages/flutter_tools/test/terminal_handler_test.dart b/packages/flutter_tools/test/terminal_handler_test.dart new file mode 100644 index 0000000..088c712 --- /dev/null +++ b/packages/flutter_tools/test/terminal_handler_test.dart
@@ -0,0 +1,332 @@ +// 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; +}