diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index d19bdc0ac3e..814e1210712 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -77,6 +77,7 @@ dev_dependencies: collection: 1.14.11 mockito: 4.0.0 file_testing: 2.0.3 + vm_service_lib: 0.3.10 http_multi_server: 2.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -93,4 +94,4 @@ dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: e719 +# PUBSPEC CHECKSUM: 0163 diff --git a/packages/flutter_tools/test/integration/expression_evaluation_test.dart b/packages/flutter_tools/test/integration/expression_evaluation_test.dart index 248f5e71b14..eb6bb3c8f9f 100644 --- a/packages/flutter_tools/test/integration/expression_evaluation_test.dart +++ b/packages/flutter_tools/test/integration/expression_evaluation_test.dart @@ -7,7 +7,7 @@ import 'dart:async'; import 'package:file/file.dart'; import 'package:flutter_tools/src/base/file_system.dart'; -import 'package:vm_service_client/vm_service_client.dart'; +import 'package:vm_service_lib/vm_service_lib.dart'; import '../src/common.dart'; import 'test_data/basic_project.dart'; @@ -31,45 +31,45 @@ void main() { tryToDelete(tempDir); }); - Future breakInBuildMethod(FlutterTestDriver flutter) async { + Future breakInBuildMethod(FlutterTestDriver flutter) async { return _flutter.breakAt( _project.buildMethodBreakpointUri, _project.buildMethodBreakpointLine); } - Future breakInTopLevelFunction(FlutterTestDriver flutter) async { + Future breakInTopLevelFunction(FlutterTestDriver flutter) async { return _flutter.breakAt( _project.topLevelFunctionBreakpointUri, _project.topLevelFunctionBreakpointLine); } Future evaluateTrivialExpressions() async { - VMInstanceRef res; + InstanceRef res; - res = await _flutter.evaluateExpression('"test"'); - expect(res is VMStringInstanceRef && res.value == 'test', isTrue); + res = await _flutter.evaluateInFrame('"test"'); + expect(res.kind == InstanceKind.kString && res.valueAsString == 'test', isTrue); - res = await _flutter.evaluateExpression('1'); - expect(res is VMIntInstanceRef && res.value == 1, isTrue); + res = await _flutter.evaluateInFrame('1'); + expect(res.kind == InstanceKind.kInt && res.valueAsString == 1.toString(), isTrue); - res = await _flutter.evaluateExpression('true'); - expect(res is VMBoolInstanceRef && res.value == true, isTrue); + res = await _flutter.evaluateInFrame('true'); + expect(res.kind == InstanceKind.kBool && res.valueAsString == true.toString(), isTrue); } Future evaluateComplexExpressions() async { - final VMInstanceRef res = await _flutter.evaluateExpression('new DateTime.now().year'); - expect(res is VMIntInstanceRef && res.value == DateTime.now().year, isTrue); + final InstanceRef res = await _flutter.evaluateInFrame('new DateTime.now().year'); + expect(res.kind == InstanceKind.kInt && res.valueAsString == DateTime.now().year.toString(), isTrue); } Future evaluateComplexReturningExpressions() async { final DateTime now = DateTime.now(); - final VMInstanceRef resp = await _flutter.evaluateExpression('new DateTime.now()'); - expect(resp.klass.name, equals('DateTime')); + 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 VMStringInstanceRef res = await resp.evaluate(r'"$year-$month-$day"'); - expect(res.value, + final InstanceRef res = await _flutter.evaluate(resp.id, r'"$year-$month-$day"'); + expect(res.valueAsString, equals('${now.year}-${now.month}-${now.day}')); } diff --git a/packages/flutter_tools/test/integration/hot_reload_test.dart b/packages/flutter_tools/test/integration/hot_reload_test.dart index 93b2ccf61c5..46c3d3012bb 100644 --- a/packages/flutter_tools/test/integration/hot_reload_test.dart +++ b/packages/flutter_tools/test/integration/hot_reload_test.dart @@ -6,7 +6,7 @@ import 'dart:async'; import 'package:file/file.dart'; import 'package:flutter_tools/src/base/file_system.dart'; -import 'package:vm_service_client/vm_service_client.dart'; +import 'package:vm_service_lib/vm_service_lib.dart'; import '../src/common.dart'; import 'test_data/hot_reload_project.dart'; @@ -55,10 +55,10 @@ void main() { test('reload hits breakpoints after reload', () async { await _flutter.run(withDebugger: true); - final VMIsolate isolate = await _flutter.breakAt( + final Isolate isolate = await _flutter.breakAt( _project.breakpointUri, _project.breakpointLine); - expect(isolate.pauseEvent, isInstanceOf()); + expect(isolate.pauseEvent.kind, equals(EventKind.kPauseBreakpoint)); }); }, timeout: const Timeout.factor(6)); } diff --git a/packages/flutter_tools/test/integration/test_driver.dart b/packages/flutter_tools/test/integration/test_driver.dart index 5322de64bd5..3098d36668e 100644 --- a/packages/flutter_tools/test/integration/test_driver.dart +++ b/packages/flutter_tools/test/integration/test_driver.dart @@ -9,15 +9,13 @@ import 'package:file/file.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:process/process.dart'; -import 'package:source_span/source_span.dart'; -import 'package:stream_channel/stream_channel.dart'; -import 'package:vm_service_client/vm_service_client.dart'; -import 'package:web_socket_channel/io.dart'; +import 'package:vm_service_lib/vm_service_lib.dart'; +import 'package:vm_service_lib/vm_service_lib_io.dart'; import '../src/common.dart'; // Set this to true for debugging to get JSON written to stdout. -const bool _printJsonAndStderr = false; +const bool _printDebugOutputToStdOut = false; const Duration defaultTimeout = Duration(seconds: 40); const Duration appStartTimeout = Duration(seconds: 120); const Duration quitTimeout = Duration(seconds: 10); @@ -37,13 +35,12 @@ class FlutterTestDriver { String _lastResponse; String _currentRunningAppId; Uri _vmServiceWsUri; - int _vmServicePort; bool _hasExited = false; - VMServiceClient vmService; + VmService _vmService; String get lastErrorInfo => _errorBuffer.toString(); Stream get stdout => _stdout.stream; - int get vmServicePort => _vmServicePort; + int get vmServicePort => _vmServiceWsUri.port; bool get hasExited => _hasExited; String _debugPrint(String msg) { @@ -51,7 +48,7 @@ class FlutterTestDriver { final String truncatedMsg = msg.length > maxLength ? msg.substring(0, maxLength) + '...' : msg; _allMessages.add(truncatedMsg); - if (_printJsonAndStderr) { + if (_printDebugOutputToStdOut) { print('$_logPrefix$truncatedMsg'); } return msg; @@ -141,22 +138,21 @@ class FlutterTestDriver { timeout: appStartTimeout); final String wsUriString = debugPort['params']['wsUri']; _vmServiceWsUri = Uri.parse(wsUriString); - _vmServicePort = debugPort['params']['port']; - // Proxy the stream/sink for the VM Client so we can debugPrint it. - final StreamChannel channel = IOWebSocketChannel.connect(_vmServiceWsUri) - .cast() - .changeStream((Stream stream) => stream.map(_debugPrint)) - .changeSink((StreamSink sink) => - StreamController() - ..stream.listen((String s) => sink.add(_debugPrint(s)))); - vmService = VMServiceClient(channel); + _vmService = + await vmServiceConnectUri(_vmServiceWsUri.toString()); + _vmService.onSend.listen((String s) => _debugPrint('==> $s')); + _vmService.onReceive.listen((String s) => _debugPrint('<== $s')); + await Future.wait(>[ + _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 (await getFlutterIsolate()).setExceptionPauseMode(VMExceptionPauseMode.unhandled); + await _vmService.setExceptionPauseMode(await _getFlutterIsolateId(), ExceptionPauseMode.kUnhandled); } await resume(wait: false); } @@ -175,7 +171,7 @@ class FlutterTestDriver { final dynamic hotReloadResp = await _sendRequest( 'app.restart', - {'appId': _currentRunningAppId, 'fullRestart': fullRestart, 'pause': pause} + {'appId': _currentRunningAppId, 'fullRestart': fullRestart, 'pause': pause}, ); if (hotReloadResp == null || hotReloadResp['code'] != 0) @@ -183,11 +179,9 @@ class FlutterTestDriver { } Future detach() async { - if (vmService != null) { + if (_vmService != null) { _debugPrint('Closing VM service'); - await vmService.close() - .timeout(quitTimeout, - onTimeout: () { _debugPrint('VM Service did not quit within $quitTimeout'); }); + _vmService.dispose(); } if (_currentRunningAppId != null) { _debugPrint('Detaching from app'); @@ -195,11 +189,11 @@ class FlutterTestDriver { _proc.exitCode, _sendRequest( 'app.detach', - {'appId': _currentRunningAppId} + {'appId': _currentRunningAppId}, ), ]).timeout( quitTimeout, - onTimeout: () { _debugPrint('app.detach did not return within $quitTimeout'); } + onTimeout: () { _debugPrint('app.detach did not return within $quitTimeout'); }, ); _currentRunningAppId = null; } @@ -208,11 +202,9 @@ class FlutterTestDriver { } Future stop() async { - if (vmService != null) { + if (_vmService != null) { _debugPrint('Closing VM service'); - await vmService.close() - .timeout(quitTimeout, - onTimeout: () { _debugPrint('VM Service did not quit within $quitTimeout'); }); + _vmService.dispose(); } if (_currentRunningAppId != null) { _debugPrint('Stopping app'); @@ -220,11 +212,11 @@ class FlutterTestDriver { _proc.exitCode, _sendRequest( 'app.stop', - {'appId': _currentRunningAppId} + {'appId': _currentRunningAppId}, ), ]).timeout( quitTimeout, - onTimeout: () { _debugPrint('app.stop did not return within $quitTimeout'); } + onTimeout: () { _debugPrint('app.stop did not return within $quitTimeout'); }, ); _currentRunningAppId = null; } @@ -248,43 +240,73 @@ class FlutterTestDriver { return _proc.exitCode; } - Future getFlutterIsolate() async { + String _flutterIsolateId; + Future _getFlutterIsolateId() async { // Currently these tests only have a single isolate. If this // ceases to be the case, this code will need changing. - final VM vm = await vmService.getVM(); - return await vm.isolates.single.load(); + if (_flutterIsolateId == null) { + final VM vm = await _vmService.getVM(); + _flutterIsolateId = vm.isolates.first.id; + } + return _flutterIsolateId; + } + + Future _getFlutterIsolate() async { + final Isolate isolate = await _vmService.getIsolate(await _getFlutterIsolateId()); + return isolate; } Future addBreakpoint(Uri uri, int line) async { - final VMIsolate isolate = await getFlutterIsolate(); _debugPrint('Sending breakpoint for $uri:$line'); - await isolate.addBreakpoint(uri, line); + await _vmService.addBreakpointWithScriptUri( + await _getFlutterIsolateId(), uri.toString(), line); } - Future waitForPause() async { - final VM vm = await vmService.getVM(); - final VMIsolate isolate = await vm.isolates.first.load(); + Future waitForPause() async { _debugPrint('Waiting for isolate to pause'); - await _timeoutWithMessages(isolate.waitUntilPaused, + final String flutterIsolate = await _getFlutterIsolateId(); + + Future waitForPause() async { + final Completer pauseEvent = Completer(); + + // Start listening for pause events. + final StreamSubscription pauseSub = _vmService.onDebugEvent + .where((Event event) => + event.isolate.id == flutterIsolate && + event.kind.startsWith('Pause')) + .listen(pauseEvent.complete); + + // But also check if the isolate was already paused (only after we've set + // up the sub) to avoid races. If it was paused, we don't need to wait + // for the event. + final Isolate isolate = await _vmService.getIsolate(flutterIsolate); + if (!isolate.pauseEvent.kind.startsWith('Pause')) { + await pauseEvent.future; + } + + // Cancel the sub on either of the above. + await pauseSub.cancel(); + + return _getFlutterIsolate(); + } + + return _timeoutWithMessages(waitForPause, message: 'Isolate did not pause'); - return isolate.load(); } - Future resume({ bool wait = true }) => _resume(wait: wait); - Future stepOver({ bool wait = true }) => _resume(step: VMStep.over, wait: wait); - Future stepInto({ bool wait = true }) => _resume(step: VMStep.into, wait: wait); - Future stepOut({ bool wait = true }) => _resume(step: VMStep.out, wait: wait); + Future resume({bool wait = true}) => _resume(wait: wait); + Future stepOver({bool wait = true}) => _resume(step: StepOption.kOver, wait: wait); + Future stepInto({bool wait = true}) => _resume(step: StepOption.kInto, wait: wait); + Future stepOut({bool wait = true}) => _resume(step: StepOption.kOut, wait: wait); - Future _resume({VMStep step, bool wait = true}) async { - final VM vm = await vmService.getVM(); - final VMIsolate isolate = await vm.isolates.first.load(); + Future _resume({String step, bool wait = true}) async { _debugPrint('Sending resume ($step)'); - await _timeoutWithMessages(() => isolate.resume(step: step), + await _timeoutWithMessages(() async => _vmService.resume(await _getFlutterIsolateId(), step: step), message: 'Isolate did not respond to resume ($step)'); return wait ? waitForPause() : null; } - Future breakAt(Uri uri, int line, { bool restart = false }) async { + Future 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. @@ -298,26 +320,47 @@ class FlutterTestDriver { } } - Future evaluateExpression(String expression) async { - final VMFrame topFrame = await getTopStackFrame(); - return _timeoutWithMessages(() => topFrame.evaluate(expression), + Future evaluateInFrame(String expression) async { + return _timeoutWithMessages( + () async => await _vmService.evaluateInFrame(await _getFlutterIsolateId(), 0, expression), message: 'Timed out evaluating expression ($expression)'); } - Future getTopStackFrame() async { - final VM vm = await vmService.getVM(); - final VMIsolate isolate = await vm.isolates.first.load(); - final VMStack stack = await isolate.getStack(); + Future evaluate(String targetId, String expression) async { + return _timeoutWithMessages( + () async => await _vmService.evaluate(await _getFlutterIsolateId(), targetId, expression), + message: 'Timed out evaluating expression ($expression for $targetId)'); + } + + Future getTopStackFrame() async { + final String flutterIsolateId = await _getFlutterIsolateId(); + final Stack stack = await _vmService.getStack(flutterIsolateId); if (stack.frames.isEmpty) { throw Exception('Stack is empty'); } return stack.frames.first; } - Future getSourceLocation() async { - final VMFrame frame = await getTopStackFrame(); - final VMScript script = await frame.location.script.load(); - return script.sourceLocation(frame.location.token); + Future getSourceLocation() async { + final String flutterIsolateId = await _getFlutterIsolateId(); + final Frame frame = await getTopStackFrame(); + final Script script = await _vmService.getObject(flutterIsolateId, frame.location.script.id); + return _lookupTokenPos(script.tokenPosTable, frame.location.tokenPos); + } + + SourcePosition _lookupTokenPos(List> table, int tokenPos) { + for (List row in table) { + final int lineNumber = row[0]; + int index = 1; + + for (index = 1; index < row.length - 1; index += 2) { + if (row[index] == tokenPos) { + return SourcePosition(lineNumber, row[index + 1]); + } + } + } + + return null; } Future> _waitFor({ @@ -428,3 +471,10 @@ class FlutterTestDriver { Stream _transformToLines(Stream> byteStream) { return byteStream.transform(utf8.decoder).transform(const LineSplitter()); } + +class SourcePosition { + SourcePosition(this.line, this.column); + + final int line; + final int column; +}