| // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| /// An example of using the libraries provided by `package:vm_service`. |
| library; |
| |
| import 'dart:async'; |
| import 'dart:collection'; |
| import 'dart:convert'; |
| import 'dart:io'; |
| |
| import 'package:path/path.dart' as path; |
| import 'package:test/test.dart'; |
| import 'package:vm_service/vm_service.dart'; |
| import 'package:vm_service/vm_service_io.dart'; |
| |
| const String host = 'localhost'; |
| const int port = 7575; |
| |
| late VmService serviceClient; |
| |
| void main() { |
| Process? process; |
| |
| tearDown(() { |
| process?.kill(); |
| }); |
| |
| test('integration', () async { |
| final sdkPath = path.dirname(path.dirname(Platform.resolvedExecutable)); |
| print('Using sdk at $sdkPath.'); |
| |
| // pause_isolates_on_start, pause_isolates_on_exit |
| final sampleProcess = process = await Process.start( |
| Platform.resolvedExecutable, |
| [ |
| '--pause-isolates-on-start', |
| '--enable-vm-service=$port', |
| '--disable-service-auth-codes', |
| 'example/sample_main.dart', |
| ], |
| ); |
| |
| print('Dart process started.'); |
| |
| unawaited(sampleProcess.exitCode.then((code) => print('vm exited: $code'))); |
| sampleProcess.stdout.transform(utf8.decoder).listen(print); |
| sampleProcess.stderr.transform(utf8.decoder).listen(print); |
| |
| await Future.delayed(const Duration(milliseconds: 500)); |
| |
| final wsUri = Uri(scheme: 'ws', host: host, port: port, path: 'ws'); |
| serviceClient = await vmServiceConnectUri( |
| wsUri.toString(), |
| log: StdoutLog(), |
| ); |
| |
| print('VM service web socket connected.'); |
| |
| serviceClient.onSend.listen((str) => print('--> $str')); |
| |
| // The next listener will bail out if you toggle this to false, which is |
| // needed for some things like the custom service registration tests. |
| var checkResponseJsonCompatibility = true; |
| serviceClient.onReceive.listen((str) { |
| print('<-- $str'); |
| |
| if (!checkResponseJsonCompatibility) return; |
| |
| // For each received event, check that we can deserialize it and |
| // reserialize it back to the same exact representation (minus private |
| // fields). |
| final json = jsonDecode(str); |
| var originalJson = json['result'] as Map<String, dynamic>?; |
| if (originalJson == null && json['method'] == 'streamNotify') { |
| originalJson = json['params']['event']; |
| } |
| expect(originalJson, isNotNull, reason: 'Unrecognized event type! $json'); |
| |
| final instance = |
| createServiceObject(originalJson!, const ['Event', 'Success']); |
| expect(instance, isNotNull, |
| reason: 'Failed to deserialize object $originalJson!'); |
| |
| final reserializedJson = (instance as dynamic).toJson(); |
| |
| forEachNestedMap(originalJson, (obj) { |
| // Remove private fields that we don't reproduce. |
| obj.removeWhere((k, v) => k.startsWith('_')); |
| |
| // Remove extra fields that aren't specified and we don't reproduce. |
| obj.remove('isExport'); |
| obj.remove('isolate_group'); |
| obj.remove('parameterizedClass'); |
| |
| // Convert `Null` instances in the original JSON to |
| // just `null` as `createServiceObject` will use `null` |
| // to represent the reference. |
| obj.updateAll((key, value) { |
| if (value is Map && |
| value['type'] == '@Instance' && |
| value['kind'] == 'Null') { |
| return null; |
| } else { |
| return value; |
| } |
| }); |
| }); |
| |
| forEachNestedMap(reserializedJson, (obj) { |
| // We provide explicit defaults for these, need to remove them. |
| obj.remove('valueAsStringIsTruncated'); |
| }); |
| |
| expect(reserializedJson, equals(originalJson)); |
| }); |
| |
| serviceClient.onIsolateEvent.listen((e) => print('onIsolateEvent: $e')); |
| serviceClient.onDebugEvent.listen((e) => print('onDebugEvent: $e')); |
| serviceClient.onGCEvent.listen((e) => print('onGCEvent: $e')); |
| serviceClient.onStdoutEvent.listen((e) => print('onStdoutEvent: $e')); |
| serviceClient.onStderrEvent.listen((e) => print('onStderrEvent: $e')); |
| |
| unawaited(serviceClient.streamListen(EventStreams.kIsolate)); |
| unawaited(serviceClient.streamListen(EventStreams.kDebug)); |
| unawaited(serviceClient.streamListen(EventStreams.kStdout)); |
| |
| final vm = await serviceClient.getVM(); |
| print('hostCPU=${vm.hostCPU}'); |
| print(await serviceClient.getVersion()); |
| final isolates = vm.isolates!; |
| print(isolates); |
| |
| // Disable the json reserialization checks since custom services are |
| // not supported. |
| checkResponseJsonCompatibility = false; |
| await testServiceRegistration(); |
| checkResponseJsonCompatibility = true; |
| |
| await testScriptParse(vm.isolates!.first); |
| await testSourceReport(vm.isolates!.first); |
| |
| final isolateRef = isolates.first; |
| print(await serviceClient.resume(isolateRef.id!)); |
| |
| print('Waiting for service client to shut down...'); |
| await serviceClient.dispose(); |
| |
| await serviceClient.onDone; |
| print('Service client shut down.'); |
| }); |
| } |
| |
| /// Deeply traverses the [input] map and calls [cb] with |
| /// each nested map and the parent map. |
| void forEachNestedMap(Map input, void Function(Map) cb) { |
| final queue = Queue.from([input]); |
| while (queue.isNotEmpty) { |
| final next = queue.removeFirst(); |
| if (next is Map) { |
| cb(next); |
| queue.addAll(next.values); |
| } else if (next is List) { |
| queue.addAll(next); |
| } |
| } |
| } |
| |
| Future<void> testServiceRegistration() async { |
| const String serviceName = 'serviceName'; |
| const String serviceAlias = 'serviceAlias'; |
| const String movedValue = 'movedValue'; |
| serviceClient.registerServiceCallback(serviceName, |
| (Map<String, dynamic> params) async { |
| assert(params['input'] == movedValue); |
| return <String, dynamic>{ |
| 'result': {'output': params['input']} |
| }; |
| }); |
| await serviceClient.registerService(serviceName, serviceAlias); |
| final wsUri = Uri(scheme: 'ws', host: host, port: port, path: 'ws'); |
| final otherClient = await vmServiceConnectUri( |
| wsUri.toString(), |
| log: StdoutLog(), |
| ); |
| final completer = Completer(); |
| otherClient.onEvent('Service').listen((e) async { |
| if (e.service == serviceName && e.kind == EventKind.kServiceRegistered) { |
| assert(e.alias == serviceAlias); |
| final response = await serviceClient.callMethod( |
| e.method!, |
| args: {'input': movedValue}, |
| ); |
| assert(response.json!['output'] == movedValue); |
| completer.complete(); |
| } |
| }); |
| await otherClient.streamListen('Service'); |
| await completer.future; |
| await otherClient.dispose(); |
| } |
| |
| Future<void> testScriptParse(IsolateRef isolateRef) async { |
| final isolateId = isolateRef.id!; |
| final isolate = await serviceClient.getIsolate(isolateId); |
| final rootLibrary = |
| await serviceClient.getObject(isolateId, isolate.rootLib!.id!) as Library; |
| final scriptRef = rootLibrary.scripts!.first; |
| |
| final script = |
| await serviceClient.getObject(isolateId, scriptRef.id!) as Script; |
| print(script); |
| print(script.uri); |
| print(script.library); |
| print(script.source!.length); |
| print(script.tokenPosTable!.length); |
| } |
| |
| Future<void> testSourceReport(IsolateRef isolateRef) async { |
| final isolateId = isolateRef.id!; |
| final isolate = await serviceClient.getIsolate(isolateId); |
| final rootLibrary = |
| await serviceClient.getObject(isolateId, isolate.rootLib!.id!) as Library; |
| final scriptRef = rootLibrary.scripts!.first; |
| |
| // Make sure that some code has run. |
| await serviceClient.resume(isolateId); |
| await Future.delayed(const Duration(milliseconds: 25)); |
| |
| final sourceReport = await serviceClient.getSourceReport( |
| isolateId, |
| [SourceReportKind.kCoverage], |
| scriptId: scriptRef.id, |
| ); |
| for (final range in sourceReport.ranges!) { |
| print(' $range'); |
| if (range.coverage != null) { |
| print(' ${range.coverage}'); |
| } |
| } |
| print(sourceReport); |
| } |
| |
| class StdoutLog extends Log { |
| @override |
| void warning(String message) => print(message); |
| |
| @override |
| void severe(String message) => print(message); |
| } |