// 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. library service_tester; import 'dart:async'; import 'dart:collection'; import 'dart:convert'; import 'dart:io'; import 'package:path/path.dart' as path; import 'package:pedantic/pedantic.dart'; import 'package:test/test.dart'; import 'package:vm_service/vm_service.dart'; import 'package:vm_service/vm_service_io.dart'; final String host = 'localhost'; final int port = 7575; late VmService serviceClient; void main() { Process? process; tearDown(() { process?.kill(); }); test('integration', () async { String sdk = path.dirname(path.dirname(Platform.resolvedExecutable)); print('Using sdk at ${sdk}.'); // pause_isolates_on_start, pause_isolates_on_exit process = await Process.start('${sdk}/bin/dart', [ '--pause_isolates_on_start', '--enable-vm-service=${port}', '--disable-service-auth-codes', 'example/sample_main.dart' ]); print('dart process started'); // ignore: unawaited_futures process!.exitCode.then((code) => print('vm exited: ${code}')); process!.stdout.transform(utf8.decoder).listen(print); process!.stderr.transform(utf8.decoder).listen(print); await Future.delayed(Duration(milliseconds: 500)); // ignore: deprecated_member_use_from_same_package serviceClient = await vmServiceConnect(host, port, log: StdoutLog()); print('socket connected'); serviceClient.onSend.listen((str) => print('--> ${str}')); // The next listener will bail out if you toggle this to false, which we need // to do 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). var 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'); var instance = createServiceObject(originalJson, const ['Event', 'Success']); expect(instance, isNotNull, reason: 'failed to deserialize object $originalJson!'); var reserializedJson = (instance as dynamic).toJson(); forEachNestedMap(originalJson!, (obj) { // Private fields that we don't reproduce obj.removeWhere((k, v) => k.startsWith('_')); // Extra fields that aren't specified and we don't reproduce obj.remove('isExport'); }); 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)); VM vm = await serviceClient.getVM(); print('hostCPU=${vm.hostCPU}'); print(await serviceClient.getVersion()); List isolates = await 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); IsolateRef isolateRef = isolates.first; print(await serviceClient.resume(isolateRef.id!)); print('waiting for client to shut down...'); await serviceClient.dispose(); await serviceClient.onDone; print('service client shut down'); }); } // Deeply traverses a map and calls [cb] with each nested map and the // parent map. void forEachNestedMap(Map input, Function(Map) cb) { var queue = Queue.from([input]); while (queue.isNotEmpty) { var 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); VmService otherClient = // ignore: deprecated_member_use_from_same_package await vmServiceConnect(host, port, log: StdoutLog()); Completer completer = Completer(); otherClient.onEvent('Service').listen((e) async { if (e.service == serviceName && e.kind == EventKind.kServiceRegistered) { assert(e.alias == serviceAlias); Response? 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 isolate = await serviceClient.getIsolate(isolateId); final Library rootLibrary = await serviceClient.getObject(isolateId, isolate.rootLib!.id!) as Library; final ScriptRef scriptRef = rootLibrary.scripts!.first; final Script 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 isolate = await serviceClient.getIsolate(isolateId); final Library rootLibrary = await serviceClient.getObject(isolateId, isolate.rootLib!.id!) as Library; final ScriptRef scriptRef = rootLibrary.scripts!.first; // make sure some code has run await serviceClient.resume(isolateId); await Future.delayed(const Duration(milliseconds: 25)); final SourceReport sourceReport = await serviceClient.getSourceReport( isolateId, [SourceReportKind.kCoverage], scriptId: scriptRef.id); for (SourceReportRange range in sourceReport.ranges!) { print(' $range'); if (range.coverage != null) { print(' ${range.coverage}'); } } print(sourceReport); } class StdoutLog extends Log { void warning(String message) => print(message); void severe(String message) => print(message); }