blob: 19c8edc57ce097b935f0199218873f6d3a504a6f [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.
/// 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);
}