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.
......
...@@ -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 void main() {
Future<void> cleanupAtFinish() async { } group('ResidentRunner', () {
final Uri testUri = Uri.parse('foo://bar');
@override Testbed testbed;
Future<void> handleTerminalCommand(String code) async { MockDevice mockDevice;
receivedCommand = code; MockVMService mockVMService;
} MockDevFS mockDevFS;
ResidentRunner residentRunner;
@override setUp(() {
void printHelp({ bool details }) { testbed = Testbed(setup: () {
hasHelpBeenPrinted = true; 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;
});
});
@override test('Can attach to device successfully', () => testbed.run(() async {
Future<int> run({ final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
Completer<DebugConnectionInfo> connectionInfoCompleter, final Completer<void> onAppStart = Completer<void>.sync();
Completer<void> appStartedCompleter, final Future<int> result = residentRunner.attach(
String route, appStartedCompleter: onAppStart,
bool shouldBuild = true, connectionInfoCompleter: onConnectionInfo,
}) async => null; );
final Future<DebugConnectionInfo> connectionInfo = onConnectionInfo.future;
@override expect(await result, 0);
Future<int> attach({
Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<void> appStartedCompleter,
}) async => null;
}
void main() { verify(mockDevice.initLogReader()).called(1);
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', () { expect(onConnectionInfo.isCompleted, true);
testUsingContext('single help character', () async { expect((await connectionInfo).baseUri, 'foo://bar');
final TestRunner testRunner = createTestRunner(); expect(onAppStart.isCompleted, true);
expect(testRunner.hasHelpBeenPrinted, isFalse); }));
await testRunner.processTerminalInput('h');
expect(testRunner.hasHelpBeenPrinted, isTrue);
});
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(''));
});
}); });
} }
class MockDevice extends Mock implements Device { class MockDevice extends Mock implements FlutterDevice {}
MockDevice() { class MockFlutterView extends Mock implements FlutterView {}
when(isSupported()).thenReturn(true); 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