// 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?; 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 testServiceRegistration() async { const String serviceName = 'serviceName'; const String serviceAlias = 'serviceAlias'; const String movedValue = 'movedValue'; serviceClient.registerServiceCallback(serviceName, (Map params) async { assert(params['input'] == movedValue); return { '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 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 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); }