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

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;
+}