| // 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<String, dynamic>?; |
| 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<IsolateRef> 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<String, dynamic> params) async { |
| assert(params['input'] == movedValue); |
| return <String, dynamic>{ |
| '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: <String, dynamic>{'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); |
| } |