blob: eb5158b522d560b54d42775312b1d2e1e421ac39 [file] [log] [blame]
// 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(() {
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', [
print('dart process started');
// ignore: unawaited_futures
process!.exitCode.then((code) => print('vm exited: ${code}'));
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
forEachNestedMap(reserializedJson, (obj) {
// We provide explicit defaults for these, need to remove them.
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}'));
VM vm = await serviceClient.getVM();
print(await serviceClient.getVersion());
List<IsolateRef> isolates = await vm.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(!));
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) {
} else if (next is List) {
Future testServiceRegistration() async {
const String serviceName = 'serviceName';
const String serviceAlias = 'serviceAlias';
const String movedValue = 'movedValue';
(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(
args: <String, dynamic>{'input': movedValue},
assert(response.json!['output'] == movedValue);
await otherClient.streamListen('Service');
await completer.future;
await otherClient.dispose();
Future testScriptParse(IsolateRef isolateRef) async {
final isolateId =!;
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,!) as Script;
Future testSourceReport(IsolateRef isolateRef) async {
final isolateId =!;
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],
for (SourceReportRange range in sourceReport.ranges!) {
print(' $range');
if (range.coverage != null) {
print(' ${range.coverage}');
class StdoutLog extends Log {
void warning(String message) => print(message);
void severe(String message) => print(message);