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 {
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();
......
......@@ -277,7 +277,7 @@ class AttachCommand extends FlutterCommand {
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 @@ class AttachCommand extends FlutterCommand {
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 @@ class HotRunnerFactory {
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 @@ class HotRunnerFactory {
devices,
target: target,
debuggingOptions: debuggingOptions,
usesTerminalUI: usesTerminalUI,
usesTerminalUi: usesTerminalUi,
benchmarkMode: benchmarkMode,
applicationBinary: applicationBinary,
hostIsIde: hostIsIde,
......
......@@ -419,7 +419,7 @@ class AppDomain extends Domain {
<FlutterDevice>[flutterDevice],
target: target,
debuggingOptions: options,
usesTerminalUI: false,
usesTerminalUi: false,
applicationBinary: applicationBinary,
projectRootPath: projectRootPath,
packagesFilePath: packagesFilePath,
......@@ -432,8 +432,8 @@ class AppDomain extends Domain {
<FlutterDevice>[flutterDevice],
target: target,
debuggingOptions: options,
usesTerminalUI: false,
applicationBinary: applicationBinary,
usesTerminalUi: false,
ipv6: ipv6,
);
}
......
......@@ -450,8 +450,8 @@ class RunCommand extends RunCommandBase {
applicationBinary: applicationBinaryPath == null
? null
: fs.file(applicationBinaryPath),
stayResident: stayResident,
ipv6: ipv6,
stayResident: stayResident,
);
}
......@@ -463,7 +463,14 @@ class RunCommand extends RunCommandBase {
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 @@ class RunCommand extends RunCommandBase {
route: route,
shouldBuild: !runningWithPrebuiltApplication && argResults['build'],
);
if (result != 0)
if (result != 0) {
throwToolExit(null, exitCode: result);
}
return FlutterCommandResult(
ExitStatus.success,
timingLabelParts: <String>[
......
......@@ -406,8 +406,8 @@ abstract class Device {
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.
......
......@@ -37,10 +37,10 @@ class ResidentWebRunner extends ResidentRunner {
}) : super(
flutterDevices,
target: target,
usesTerminalUI: true,
stayResident: true,
debuggingOptions: debuggingOptions,
ipv6: ipv6,
usesTerminalUi: true,
stayResident: true,
);
WebAssetServer _server;
......@@ -54,7 +54,6 @@ class ResidentWebRunner extends ResidentRunner {
{Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<void> appStartedCompleter}) async {
connectionInfoCompleter?.complete(DebugConnectionInfo());
setupTerminal();
final int result = await waitForAppToFinish();
await cleanupAtFinish();
return result;
......
......@@ -19,16 +19,17 @@ class ColdRunner extends ResidentRunner {
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 @@ class ColdRunner extends ResidentRunner {
);
}
appFinished();
} else if (stayResident) {
setupTerminal();
registerSignalHandlers();
}
appStartedCompleter?.complete();
......@@ -138,10 +136,6 @@ class ColdRunner extends ResidentRunner {
printTrace('Connected to $view.');
}
}
if (stayResident) {
setupTerminal();
registerSignalHandlers();
}
appStartedCompleter?.complete();
if (stayResident) {
return waitForAppToFinish();
......@@ -150,9 +144,6 @@ class ColdRunner extends ResidentRunner {
return 0;
}
@override
Future<void> handleTerminalCommand(String code) async { }
@override
Future<void> cleanupAfterSignal() async {
await stopEchoingDeviceLog();
......
......@@ -57,7 +57,7 @@ class HotRunner extends ResidentRunner {
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 @@ class HotRunner extends ResidentRunner {
}) : 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 @@ class HotRunner extends ResidentRunner {
printTrace('Connected to $view.');
}
if (stayResident) {
setupTerminal();
registerSignalHandlers();
}
appStartedCompleter?.complete();
if (benchmarkMode) {
......@@ -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 {
final String fsName = fs.path.basename(projectRootPath);
final List<Uri> devFSUris = <Uri>[];
......
......@@ -24,15 +24,18 @@ import '../src/context.dart';
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 @@ void main() {
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 @@ void main() {
dillOutputPath: anyNamed('dillOutputPath'),
debuggingOptions: anyNamed('debuggingOptions'),
packagesFilePath: anyNamed('packagesFilePath'),
usesTerminalUI: anyNamed('usesTerminalUI'),
usesTerminalUi: anyNamed('usesTerminalUi'),
flutterProject: anyNamed('flutterProject'),
ipv6: false,
),
......@@ -151,7 +155,7 @@ void main() {
dillOutputPath: outputDill,
debuggingOptions: anyNamed('debuggingOptions'),
packagesFilePath: anyNamed('packagesFilePath'),
usesTerminalUI: anyNamed('usesTerminalUI'),
usesTerminalUi: anyNamed('usesTerminalUi'),
flutterProject: anyNamed('flutterProject'),
ipv6: false,
),
......@@ -219,14 +223,14 @@ void main() {
.thenReturn(<ForwardedPort>[ForwardedPort(hostPort, devicePort)]);
when(portForwarder.unforward(any))
.thenAnswer((_) async => null);
when(mockHotRunner.attach())
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 @@ void main() {
target: foo.path,
debuggingOptions: anyNamed('debuggingOptions'),
packagesFilePath: anyNamed('packagesFilePath'),
usesTerminalUI: anyNamed('usesTerminalUI'),
usesTerminalUi: anyNamed('usesTerminalUi'),
flutterProject: anyNamed('flutterProject'),
ipv6: false,
)).called(1);
......
......@@ -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),
);
});
testUsingContext('help character surrounded with newlines', () async {
final TestRunner testRunner = createTestRunner();
expect(testRunner.hasHelpBeenPrinted, isFalse);
await testRunner.processTerminalInput('\nh\n');
expect(testRunner.hasHelpBeenPrinted, isTrue);
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,
);
});
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'));
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;
});
testUsingContext('newlines', () async {
final TestRunner testRunner = createTestRunner();
expect(testRunner.receivedCommand, isNull);
await testRunner.processTerminalInput('\n\n');
expect(testRunner.receivedCommand, equals(''));
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;
});
});
}
class MockDevice extends Mock implements Device {
MockDevice() {
when(isSupported()).thenReturn(true);
}
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 FlutterDevice {}
class MockFlutterView extends Mock implements FlutterView {}
class MockVMService extends Mock implements VMService {}
class MockDevFS extends Mock implements DevFS {}
This diff is collapsed.
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