Unverified Commit 15e3df25 authored by Danny Tuppeny's avatar Danny Tuppeny Committed by GitHub

Refactor Test Driver in preparation for `flutter test` integration tests (#24060)

* Refactor Test Driver in preperation for `flutter test` integration tests

* Fix indent
parent 113f8f1a
...@@ -15,15 +15,15 @@ import 'test_driver.dart'; ...@@ -15,15 +15,15 @@ import 'test_driver.dart';
import 'test_utils.dart'; import 'test_utils.dart';
void main() { void main() {
group('expression evaluation', () { group('flutter run expression evaluation', () {
Directory tempDir; Directory tempDir;
final BasicProject _project = BasicProject(); final BasicProject _project = BasicProject();
FlutterTestDriver _flutter; FlutterRunTestDriver _flutter;
setUp(() async { setUp(() async {
tempDir = createResolvedTempDirectorySync(); tempDir = createResolvedTempDirectorySync();
await _project.setUpIn(tempDir); await _project.setUpIn(tempDir);
_flutter = FlutterTestDriver(tempDir); _flutter = FlutterRunTestDriver(tempDir);
}); });
tearDown(() async { tearDown(() async {
...@@ -43,70 +43,70 @@ void main() { ...@@ -43,70 +43,70 @@ void main() {
_project.topLevelFunctionBreakpointLine); _project.topLevelFunctionBreakpointLine);
} }
Future<void> evaluateTrivialExpressions() async {
InstanceRef res;
res = await _flutter.evaluateInFrame('"test"');
expect(res.kind == InstanceKind.kString && res.valueAsString == 'test', isTrue);
res = await _flutter.evaluateInFrame('1');
expect(res.kind == InstanceKind.kInt && res.valueAsString == 1.toString(), isTrue);
res = await _flutter.evaluateInFrame('true');
expect(res.kind == InstanceKind.kBool && res.valueAsString == true.toString(), isTrue);
}
Future<void> evaluateComplexExpressions() async {
final InstanceRef res = await _flutter.evaluateInFrame('new DateTime.now().year');
expect(res.kind == InstanceKind.kInt && res.valueAsString == DateTime.now().year.toString(), isTrue);
}
Future<void> evaluateComplexReturningExpressions() async {
final DateTime now = DateTime.now();
final InstanceRef resp = await _flutter.evaluateInFrame('new DateTime.now()');
expect(resp.classRef.name, equals('DateTime'));
// Ensure we got a reasonable approximation. The more accurate we try to
// make this, the more likely it'll fail due to differences in the time
// in the remote VM and the local VM at the time the code runs.
final InstanceRef res = await _flutter.evaluate(resp.id, r'"$year-$month-$day"');
expect(res.valueAsString,
equals('${now.year}-${now.month}-${now.day}'));
}
test('can evaluate trivial expressions in top level function', () async { test('can evaluate trivial expressions in top level function', () async {
await _flutter.run(withDebugger: true); await _flutter.run(withDebugger: true);
await breakInTopLevelFunction(_flutter); await breakInTopLevelFunction(_flutter);
await evaluateTrivialExpressions(); await evaluateTrivialExpressions(_flutter);
}); });
test('can evaluate trivial expressions in build method', () async { test('can evaluate trivial expressions in build method', () async {
await _flutter.run(withDebugger: true); await _flutter.run(withDebugger: true);
await breakInBuildMethod(_flutter); await breakInBuildMethod(_flutter);
await evaluateTrivialExpressions(); await evaluateTrivialExpressions(_flutter);
}); });
test('can evaluate complex expressions in top level function', () async { test('can evaluate complex expressions in top level function', () async {
await _flutter.run(withDebugger: true); await _flutter.run(withDebugger: true);
await breakInTopLevelFunction(_flutter); await breakInTopLevelFunction(_flutter);
await evaluateComplexExpressions(); await evaluateComplexExpressions(_flutter);
}); });
test('can evaluate complex expressions in build method', () async { test('can evaluate complex expressions in build method', () async {
await _flutter.run(withDebugger: true); await _flutter.run(withDebugger: true);
await breakInBuildMethod(_flutter); await breakInBuildMethod(_flutter);
await evaluateComplexExpressions(); await evaluateComplexExpressions(_flutter);
}); });
test('can evaluate expressions returning complex objects in top level function', () async { test('can evaluate expressions returning complex objects in top level function', () async {
await _flutter.run(withDebugger: true); await _flutter.run(withDebugger: true);
await breakInTopLevelFunction(_flutter); await breakInTopLevelFunction(_flutter);
await evaluateComplexReturningExpressions(); await evaluateComplexReturningExpressions(_flutter);
}); });
test('can evaluate expressions returning complex objects in build method', () async { test('can evaluate expressions returning complex objects in build method', () async {
await _flutter.run(withDebugger: true); await _flutter.run(withDebugger: true);
await breakInBuildMethod(_flutter); await breakInBuildMethod(_flutter);
await evaluateComplexReturningExpressions(); await evaluateComplexReturningExpressions(_flutter);
}); });
}, timeout: const Timeout.factor(6)); }, timeout: const Timeout.factor(6));
} }
Future<void> evaluateTrivialExpressions(FlutterTestDriver flutter) async {
InstanceRef res;
res = await flutter.evaluateInFrame('"test"');
expect(res.kind == InstanceKind.kString && res.valueAsString == 'test', isTrue);
res = await flutter.evaluateInFrame('1');
expect(res.kind == InstanceKind.kInt && res.valueAsString == 1.toString(), isTrue);
res = await flutter.evaluateInFrame('true');
expect(res.kind == InstanceKind.kBool && res.valueAsString == true.toString(), isTrue);
}
Future<void> evaluateComplexExpressions(FlutterTestDriver flutter) async {
final InstanceRef res = await flutter.evaluateInFrame('new DateTime.now().year');
expect(res.kind == InstanceKind.kInt && res.valueAsString == DateTime.now().year.toString(), isTrue);
}
Future<void> evaluateComplexReturningExpressions(FlutterTestDriver flutter) async {
final DateTime now = DateTime.now();
final InstanceRef resp = await flutter.evaluateInFrame('new DateTime.now()');
expect(resp.classRef.name, equals('DateTime'));
// Ensure we got a reasonable approximation. The more accurate we try to
// make this, the more likely it'll fail due to differences in the time
// in the remote VM and the local VM at the time the code runs.
final InstanceRef res = await flutter.evaluate(resp.id, r'"$year-$month-$day"');
expect(res.valueAsString,
equals('${now.year}-${now.month}-${now.day}'));
}
...@@ -11,15 +11,15 @@ import 'test_driver.dart'; ...@@ -11,15 +11,15 @@ import 'test_driver.dart';
import 'test_utils.dart'; import 'test_utils.dart';
void main() { void main() {
FlutterTestDriver _flutterRun, _flutterAttach; FlutterRunTestDriver _flutterRun, _flutterAttach;
final BasicProject _project = BasicProject(); final BasicProject _project = BasicProject();
Directory tempDir; Directory tempDir;
setUp(() async { setUp(() async {
tempDir = createResolvedTempDirectorySync(); tempDir = createResolvedTempDirectorySync();
await _project.setUpIn(tempDir); await _project.setUpIn(tempDir);
_flutterRun = FlutterTestDriver(tempDir, logPrefix: 'RUN'); _flutterRun = FlutterRunTestDriver(tempDir, logPrefix: 'RUN');
_flutterAttach = FlutterTestDriver(tempDir, logPrefix: 'ATTACH'); _flutterAttach = FlutterRunTestDriver(tempDir, logPrefix: 'ATTACH');
}); });
tearDown(() async { tearDown(() async {
...@@ -54,7 +54,7 @@ void main() { ...@@ -54,7 +54,7 @@ void main() {
await _flutterRun.run(withDebugger: true); await _flutterRun.run(withDebugger: true);
await _flutterAttach.attach(_flutterRun.vmServicePort); await _flutterAttach.attach(_flutterRun.vmServicePort);
await _flutterAttach.quit(); await _flutterAttach.quit();
_flutterAttach = FlutterTestDriver(tempDir, logPrefix: 'ATTACH-2'); _flutterAttach = FlutterRunTestDriver(tempDir, logPrefix: 'ATTACH-2');
await _flutterAttach.attach(_flutterRun.vmServicePort); await _flutterAttach.attach(_flutterRun.vmServicePort);
await _flutterAttach.hotReload(); await _flutterAttach.hotReload();
}); });
......
...@@ -16,12 +16,12 @@ void main() { ...@@ -16,12 +16,12 @@ void main() {
group('flutter_run', () { group('flutter_run', () {
Directory tempDir; Directory tempDir;
final BasicProject _project = BasicProject(); final BasicProject _project = BasicProject();
FlutterTestDriver _flutter; FlutterRunTestDriver _flutter;
setUp(() async { setUp(() async {
tempDir = createResolvedTempDirectorySync(); tempDir = createResolvedTempDirectorySync();
await _project.setUpIn(tempDir); await _project.setUpIn(tempDir);
_flutter = FlutterTestDriver(tempDir); _flutter = FlutterRunTestDriver(tempDir);
}); });
tearDown(() async { tearDown(() async {
......
...@@ -17,12 +17,12 @@ void main() { ...@@ -17,12 +17,12 @@ void main() {
group('hot', () { group('hot', () {
Directory tempDir; Directory tempDir;
final HotReloadProject _project = HotReloadProject(); final HotReloadProject _project = HotReloadProject();
FlutterTestDriver _flutter; FlutterRunTestDriver _flutter;
setUp(() async { setUp(() async {
tempDir = createResolvedTempDirectorySync(); tempDir = createResolvedTempDirectorySync();
await _project.setUpIn(tempDir); await _project.setUpIn(tempDir);
_flutter = FlutterTestDriver(tempDir); _flutter = FlutterRunTestDriver(tempDir);
}); });
tearDown(() async { tearDown(() async {
......
...@@ -20,13 +20,13 @@ const Duration requiredLifespan = Duration(seconds: 5); ...@@ -20,13 +20,13 @@ const Duration requiredLifespan = Duration(seconds: 5);
void main() { void main() {
group('flutter run', () { group('flutter run', () {
final BasicProject _project = BasicProject(); final BasicProject _project = BasicProject();
FlutterTestDriver _flutter; FlutterRunTestDriver _flutter;
Directory tempDir; Directory tempDir;
setUp(() async { setUp(() async {
tempDir = createResolvedTempDirectorySync(); tempDir = createResolvedTempDirectorySync();
await _project.setUpIn(tempDir); await _project.setUpIn(tempDir);
_flutter = FlutterTestDriver(tempDir); _flutter = FlutterRunTestDriver(tempDir);
}); });
tearDown(() async { tearDown(() async {
......
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'test_project.dart'; import 'project.dart';
class BasicProject extends TestProject { class BasicProject extends Project {
@override @override
final String pubspec = ''' final String pubspec = '''
......
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import '../test_utils.dart'; import '../test_utils.dart';
import 'test_project.dart'; import 'project.dart';
class HotReloadProject extends TestProject { class HotReloadProject extends Project {
@override @override
final String pubspec = ''' final String pubspec = '''
name: test name: test
......
...@@ -9,7 +9,7 @@ import 'package:flutter_tools/src/base/file_system.dart'; ...@@ -9,7 +9,7 @@ import 'package:flutter_tools/src/base/file_system.dart';
import '../test_utils.dart'; import '../test_utils.dart';
abstract class TestProject { abstract class Project {
Directory dir; Directory dir;
String get pubspec; String get pubspec;
...@@ -22,7 +22,9 @@ abstract class TestProject { ...@@ -22,7 +22,9 @@ abstract class TestProject {
Future<void> setUpIn(Directory dir) async { Future<void> setUpIn(Directory dir) async {
this.dir = dir; this.dir = dir;
writeFile(fs.path.join(dir.path, 'pubspec.yaml'), pubspec); writeFile(fs.path.join(dir.path, 'pubspec.yaml'), pubspec);
if (main != null) {
writeFile(fs.path.join(dir.path, 'lib', 'main.dart'), main); writeFile(fs.path.join(dir.path, 'lib', 'main.dart'), main);
}
await getPackages(dir.path); await getPackages(dir.path);
} }
......
...@@ -20,7 +20,7 @@ const Duration defaultTimeout = Duration(seconds: 40); ...@@ -20,7 +20,7 @@ const Duration defaultTimeout = Duration(seconds: 40);
const Duration appStartTimeout = Duration(seconds: 120); const Duration appStartTimeout = Duration(seconds: 120);
const Duration quitTimeout = Duration(seconds: 10); const Duration quitTimeout = Duration(seconds: 10);
class FlutterTestDriver { abstract class FlutterTestDriver {
FlutterTestDriver(this._projectFolder, {String logPrefix}): FlutterTestDriver(this._projectFolder, {String logPrefix}):
_logPrefix = logPrefix != null ? '$logPrefix: ' : ''; _logPrefix = logPrefix != null ? '$logPrefix: ' : '';
...@@ -33,7 +33,6 @@ class FlutterTestDriver { ...@@ -33,7 +33,6 @@ class FlutterTestDriver {
final StreamController<String> _allMessages = StreamController<String>.broadcast(); final StreamController<String> _allMessages = StreamController<String>.broadcast();
final StringBuffer _errorBuffer = StringBuffer(); final StringBuffer _errorBuffer = StringBuffer();
String _lastResponse; String _lastResponse;
String _currentRunningAppId;
Uri _vmServiceWsUri; Uri _vmServiceWsUri;
bool _hasExited = false; bool _hasExited = false;
...@@ -54,35 +53,6 @@ class FlutterTestDriver { ...@@ -54,35 +53,6 @@ class FlutterTestDriver {
return msg; return msg;
} }
Future<void> run({
bool withDebugger = false,
bool pauseOnExceptions = false,
File pidFile,
}) async {
await _setupProcess(<String>[
'run',
'--machine',
'-d',
'flutter-tester',
], withDebugger: withDebugger, pauseOnExceptions: pauseOnExceptions, pidFile: pidFile);
}
Future<void> attach(
int port, {
bool withDebugger = false,
bool pauseOnExceptions = false,
File pidFile,
}) async {
await _setupProcess(<String>[
'attach',
'--machine',
'-d',
'flutter-tester',
'--debug-port',
'$port',
], withDebugger: withDebugger, pauseOnExceptions: pauseOnExceptions, pidFile: pidFile);
}
Future<void> _setupProcess( Future<void> _setupProcess(
List<String> args, { List<String> args, {
bool withDebugger = false, bool withDebugger = false,
...@@ -121,110 +91,6 @@ class FlutterTestDriver { ...@@ -121,110 +91,6 @@ class FlutterTestDriver {
// This is just debug printing to aid running/debugging tests locally. // This is just debug printing to aid running/debugging tests locally.
_stdout.stream.listen(_debugPrint); _stdout.stream.listen(_debugPrint);
_stderr.stream.listen(_debugPrint); _stderr.stream.listen(_debugPrint);
// Stash the PID so that we can terminate the VM more reliably than using
// _proc.kill() (because _proc is a shell, because `flutter` is a shell
// script).
final Map<String, dynamic> connected = await _waitFor(event: 'daemon.connected');
_procPid = connected['params']['pid'];
// Set this up now, but we don't wait it yet. We want to make sure we don't
// miss it while waiting for debugPort below.
final Future<Map<String, dynamic>> started = _waitFor(event: 'app.started',
timeout: appStartTimeout);
if (withDebugger) {
final Map<String, dynamic> debugPort = await _waitFor(event: 'app.debugPort',
timeout: appStartTimeout);
final String wsUriString = debugPort['params']['wsUri'];
_vmServiceWsUri = Uri.parse(wsUriString);
_vmService =
await vmServiceConnectUri(_vmServiceWsUri.toString());
_vmService.onSend.listen((String s) => _debugPrint('==> $s'));
_vmService.onReceive.listen((String s) => _debugPrint('<== $s'));
await Future.wait(<Future<Success>>[
_vmService.streamListen('Isolate'),
_vmService.streamListen('Debug'),
]);
// Because we start paused, resume so the app is in a "running" state as
// expected by tests. Tests will reload/restart as required if they need
// to hit breakpoints, etc.
await waitForPause();
if (pauseOnExceptions) {
await _vmService.setExceptionPauseMode(await _getFlutterIsolateId(), ExceptionPauseMode.kUnhandled);
}
await resume(wait: false);
}
// Now await the started event; if it had already happened the future will
// have already completed.
_currentRunningAppId = (await started)['params']['appId'];
}
Future<void> hotRestart({bool pause = false}) => _restart(fullRestart: true, pause: pause);
Future<void> hotReload() => _restart(fullRestart: false);
Future<void> _restart({bool fullRestart = false, bool pause = false}) async {
if (_currentRunningAppId == null)
throw Exception('App has not started yet');
final dynamic hotReloadResp = await _sendRequest(
'app.restart',
<String, dynamic>{'appId': _currentRunningAppId, 'fullRestart': fullRestart, 'pause': pause},
);
if (hotReloadResp == null || hotReloadResp['code'] != 0)
_throwErrorResponse('Hot ${fullRestart ? 'restart' : 'reload'} request failed');
}
Future<int> detach() async {
if (_vmService != null) {
_debugPrint('Closing VM service');
_vmService.dispose();
}
if (_currentRunningAppId != null) {
_debugPrint('Detaching from app');
await Future.any<void>(<Future<void>>[
_proc.exitCode,
_sendRequest(
'app.detach',
<String, dynamic>{'appId': _currentRunningAppId},
),
]).timeout(
quitTimeout,
onTimeout: () { _debugPrint('app.detach did not return within $quitTimeout'); },
);
_currentRunningAppId = null;
}
_debugPrint('Waiting for process to end');
return _proc.exitCode.timeout(quitTimeout, onTimeout: _killGracefully);
}
Future<int> stop() async {
if (_vmService != null) {
_debugPrint('Closing VM service');
_vmService.dispose();
}
if (_currentRunningAppId != null) {
_debugPrint('Stopping app');
await Future.any<void>(<Future<void>>[
_proc.exitCode,
_sendRequest(
'app.stop',
<String, dynamic>{'appId': _currentRunningAppId},
),
]).timeout(
quitTimeout,
onTimeout: () { _debugPrint('app.stop did not return within $quitTimeout'); },
);
_currentRunningAppId = null;
}
if (_proc != null) {
_debugPrint('Waiting for process to end');
return _proc.exitCode.timeout(quitTimeout, onTimeout: _killGracefully);
}
return 0;
} }
Future<int> quit() => _killGracefully(); Future<int> quit() => _killGracefully();
...@@ -309,20 +175,6 @@ class FlutterTestDriver { ...@@ -309,20 +175,6 @@ class FlutterTestDriver {
return wait ? waitForPause() : null; return wait ? waitForPause() : null;
} }
Future<Isolate> breakAt(Uri uri, int line, {bool restart = false}) async {
if (restart) {
// For a hot restart, we need to send the breakpoints after the restart
// so we need to pause during the restart to avoid races.
await hotRestart(pause: true);
await addBreakpoint(uri, line);
return resume();
} else {
await addBreakpoint(uri, line);
await hotReload();
return waitForPause();
}
}
Future<InstanceRef> evaluateInFrame(String expression) async { Future<InstanceRef> evaluateInFrame(String expression) async {
return _timeoutWithMessages<InstanceRef>( return _timeoutWithMessages<InstanceRef>(
() async => await _vmService.evaluateInFrame(await _getFlutterIsolateId(), 0, expression), () async => await _vmService.evaluateInFrame(await _getFlutterIsolateId(), 0, expression),
...@@ -438,6 +290,175 @@ class FlutterTestDriver { ...@@ -438,6 +290,175 @@ class FlutterTestDriver {
} }
return null; return null;
} }
}
class FlutterRunTestDriver extends FlutterTestDriver {
FlutterRunTestDriver(Directory _projectFolder, {String logPrefix}):
super(_projectFolder, logPrefix: logPrefix);
String _currentRunningAppId;
Future<void> run({
bool withDebugger = false,
bool pauseOnExceptions = false,
File pidFile,
}) async {
await _setupProcess(<String>[
'run',
'--machine',
'-d',
'flutter-tester',
], withDebugger: withDebugger, pauseOnExceptions: pauseOnExceptions, pidFile: pidFile);
}
Future<void> attach(
int port, {
bool withDebugger = false,
bool pauseOnExceptions = false,
File pidFile,
}) async {
await _setupProcess(<String>[
'attach',
'--machine',
'-d',
'flutter-tester',
'--debug-port',
'$port',
], withDebugger: withDebugger, pauseOnExceptions: pauseOnExceptions, pidFile: pidFile);
}
@override
Future<void> _setupProcess(
List<String> args, {
bool withDebugger = false,
bool pauseOnExceptions = false,
File pidFile,
}) async {
await super._setupProcess(
args,
withDebugger: withDebugger,
pauseOnExceptions: pauseOnExceptions,
pidFile: pidFile,
);
// Stash the PID so that we can terminate the VM more reliably than using
// _proc.kill() (because _proc is a shell, because `flutter` is a shell
// script).
final Map<String, dynamic> connected = await _waitFor(event: 'daemon.connected');
_procPid = connected['params']['pid'];
// Set this up now, but we don't wait it yet. We want to make sure we don't
// miss it while waiting for debugPort below.
final Future<Map<String, dynamic>> started = _waitFor(event: 'app.started',
timeout: appStartTimeout);
if (withDebugger) {
final Map<String, dynamic> debugPort = await _waitFor(event: 'app.debugPort',
timeout: appStartTimeout);
final String wsUriString = debugPort['params']['wsUri'];
_vmServiceWsUri = Uri.parse(wsUriString);
_vmService =
await vmServiceConnectUri(_vmServiceWsUri.toString());
_vmService.onSend.listen((String s) => _debugPrint('==> $s'));
_vmService.onReceive.listen((String s) => _debugPrint('<== $s'));
await Future.wait(<Future<Success>>[
_vmService.streamListen('Isolate'),
_vmService.streamListen('Debug'),
]);
// Because we start paused, resume so the app is in a "running" state as
// expected by tests. Tests will reload/restart as required if they need
// to hit breakpoints, etc.
await waitForPause();
if (pauseOnExceptions) {
await _vmService.setExceptionPauseMode(await _getFlutterIsolateId(), ExceptionPauseMode.kUnhandled);
}
await resume(wait: false);
}
// Now await the started event; if it had already happened the future will
// have already completed.
_currentRunningAppId = (await started)['params']['appId'];
}
Future<void> hotRestart({bool pause = false}) => _restart(fullRestart: true, pause: pause);
Future<void> hotReload() => _restart(fullRestart: false);
Future<void> _restart({bool fullRestart = false, bool pause = false}) async {
if (_currentRunningAppId == null)
throw Exception('App has not started yet');
final dynamic hotReloadResp = await _sendRequest(
'app.restart',
<String, dynamic>{'appId': _currentRunningAppId, 'fullRestart': fullRestart, 'pause': pause},
);
if (hotReloadResp == null || hotReloadResp['code'] != 0)
_throwErrorResponse('Hot ${fullRestart ? 'restart' : 'reload'} request failed');
}
Future<int> detach() async {
if (_vmService != null) {
_debugPrint('Closing VM service');
_vmService.dispose();
}
if (_currentRunningAppId != null) {
_debugPrint('Detaching from app');
await Future.any<void>(<Future<void>>[
_proc.exitCode,
_sendRequest(
'app.detach',
<String, dynamic>{'appId': _currentRunningAppId},
),
]).timeout(
quitTimeout,
onTimeout: () { _debugPrint('app.detach did not return within $quitTimeout'); },
);
_currentRunningAppId = null;
}
_debugPrint('Waiting for process to end');
return _proc.exitCode.timeout(quitTimeout, onTimeout: _killGracefully);
}
Future<int> stop() async {
if (_vmService != null) {
_debugPrint('Closing VM service');
_vmService.dispose();
}
if (_currentRunningAppId != null) {
_debugPrint('Stopping app');
await Future.any<void>(<Future<void>>[
_proc.exitCode,
_sendRequest(
'app.stop',
<String, dynamic>{'appId': _currentRunningAppId},
),
]).timeout(
quitTimeout,
onTimeout: () { _debugPrint('app.stop did not return within $quitTimeout'); },
);
_currentRunningAppId = null;
}
if (_proc != null) {
_debugPrint('Waiting for process to end');
return _proc.exitCode.timeout(quitTimeout, onTimeout: _killGracefully);
}
return 0;
}
Future<Isolate> breakAt(Uri uri, int line, { bool restart = false }) async {
if (restart) {
// For a hot restart, we need to send the breakpoints after the restart
// so we need to pause during the restart to avoid races.
await hotRestart(pause: true);
await addBreakpoint(uri, line);
return resume();
} else {
await addBreakpoint(uri, line);
await hotReload();
return waitForPause();
}
}
int id = 1; int id = 1;
Future<dynamic> _sendRequest(String method, dynamic params) async { Future<dynamic> _sendRequest(String method, dynamic params) async {
......
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