[vm_service] Get tester example functional and clean up

Makes adjustments so `example/vm_service_tester.dart` runs successfully, and complete some clean up to make the code and comments in the file more consistent.

Closes https://github.com/dart-lang/sdk/issues/55753

TEST=N/A
Change-Id: Ib91985792a25a27008c2ef96786a40bb7a26ad57
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/374242
Reviewed-by: Derek Xu <derekx@google.com>
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
This commit is contained in:
Parker Lougheed
2024-08-06 15:37:44 +00:00
committed by Commit Queue
parent 951fbd9c49
commit d60221ed47
3 changed files with 113 additions and 64 deletions
+89 -62
View File
@@ -2,7 +2,8 @@
// 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;
/// An example of using the the libraries provided by `package:vm_service`.
library;
import 'dart:async';
import 'dart:collection';
@@ -14,8 +15,8 @@ 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;
const String host = 'localhost';
const int port = 7575;
late VmService serviceClient;
@@ -27,35 +28,40 @@ void main() {
});
test('integration', () async {
String sdk = path.dirname(path.dirname(Platform.resolvedExecutable));
print('Using sdk at $sdk.');
final sdkPath = path.dirname(path.dirname(Platform.resolvedExecutable));
print('Using sdk at $sdkPath.');
// 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'
]);
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');
print('Dart process started.');
unawaited(process!.exitCode.then((code) => print('vm exited: $code')));
process!.stdout.transform(utf8.decoder).listen(print);
process!.stderr.transform(utf8.decoder).listen(print);
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(Duration(milliseconds: 500));
await Future.delayed(const Duration(milliseconds: 500));
final wsUri = 'ws://$host$port/ws';
serviceClient = await vmServiceConnectUri(wsUri, log: StdoutLog());
final wsUri = Uri(scheme: 'ws', host: host, port: port, path: 'ws');
serviceClient = await vmServiceConnectUri(
wsUri.toString(),
log: StdoutLog(),
);
print('socket connected');
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 we need
// to do for some things like the custom service registration tests.
// 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');
@@ -65,25 +71,41 @@ void main() {
// 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);
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');
var instance =
createServiceObject(originalJson, const ['Event', 'Success']);
final instance =
createServiceObject(originalJson!, const ['Event', 'Success']);
expect(instance, isNotNull,
reason: 'failed to deserialize object $originalJson!');
reason: 'Failed to deserialize object $originalJson!');
var reserializedJson = (instance as dynamic).toJson();
final reserializedJson = (instance as dynamic).toJson();
forEachNestedMap(originalJson!, (obj) {
// Private fields that we don't reproduce
forEachNestedMap(originalJson, (obj) {
// Remove private fields that we don't reproduce.
obj.removeWhere((k, v) => k.startsWith('_'));
// Extra fields that aren't specified and we don't reproduce
// 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) {
@@ -104,14 +126,14 @@ void main() {
unawaited(serviceClient.streamListen(EventStreams.kDebug));
unawaited(serviceClient.streamListen(EventStreams.kStdout));
VM vm = await serviceClient.getVM();
final vm = await serviceClient.getVM();
print('hostCPU=${vm.hostCPU}');
print(await serviceClient.getVersion());
List<IsolateRef> isolates = vm.isolates!;
final isolates = vm.isolates!;
print(isolates);
// Disable the json reserialization checks since custom services are not
// supported.
// Disable the json reserialization checks since custom services are
// not supported.
checkResponseJsonCompatibility = false;
await testServiceRegistration();
checkResponseJsonCompatibility = true;
@@ -119,23 +141,23 @@ void main() {
await testScriptParse(vm.isolates!.first);
await testSourceReport(vm.isolates!.first);
IsolateRef isolateRef = isolates.first;
final isolateRef = isolates.first;
print(await serviceClient.resume(isolateRef.id!));
print('waiting for client to shut down...');
print('Waiting for service client to shut down...');
await serviceClient.dispose();
await serviceClient.onDone;
print('service client shut down');
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]);
/// 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) {
var next = queue.removeFirst();
final next = queue.removeFirst();
if (next is Map) {
cb(next);
queue.addAll(next.values);
@@ -145,7 +167,7 @@ void forEachNestedMap(Map input, Function(Map) cb) {
}
}
Future testServiceRegistration() async {
Future<void> testServiceRegistration() async {
const String serviceName = 'serviceName';
const String serviceAlias = 'serviceAlias';
const String movedValue = 'movedValue';
@@ -157,15 +179,18 @@ Future testServiceRegistration() async {
};
});
await serviceClient.registerService(serviceName, serviceAlias);
final wsUri = 'ws://$host$port/ws';
VmService otherClient = await vmServiceConnectUri(wsUri, log: StdoutLog());
Completer completer = Completer();
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);
Response? response = await serviceClient.callMethod(
final response = await serviceClient.callMethod(
e.method!,
args: <String, dynamic>{'input': movedValue},
args: {'input': movedValue},
);
assert(response.json!['output'] == movedValue);
completer.complete();
@@ -176,14 +201,14 @@ Future testServiceRegistration() async {
await otherClient.dispose();
}
Future testScriptParse(IsolateRef isolateRef) async {
Future<void> testScriptParse(IsolateRef isolateRef) async {
final isolateId = isolateRef.id!;
final Isolate isolate = await serviceClient.getIsolate(isolateId);
final Library rootLibrary =
final isolate = await serviceClient.getIsolate(isolateId);
final rootLibrary =
await serviceClient.getObject(isolateId, isolate.rootLib!.id!) as Library;
final ScriptRef scriptRef = rootLibrary.scripts!.first;
final scriptRef = rootLibrary.scripts!.first;
final Script script =
final script =
await serviceClient.getObject(isolateId, scriptRef.id!) as Script;
print(script);
print(script.uri);
@@ -192,21 +217,23 @@ Future testScriptParse(IsolateRef isolateRef) async {
print(script.tokenPosTable!.length);
}
Future testSourceReport(IsolateRef isolateRef) async {
Future<void> testSourceReport(IsolateRef isolateRef) async {
final isolateId = isolateRef.id!;
final Isolate isolate = await serviceClient.getIsolate(isolateId);
final Library rootLibrary =
final isolate = await serviceClient.getIsolate(isolateId);
final rootLibrary =
await serviceClient.getObject(isolateId, isolate.rootLib!.id!) as Library;
final ScriptRef scriptRef = rootLibrary.scripts!.first;
final scriptRef = rootLibrary.scripts!.first;
// make sure some code has run
// Make sure that 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!) {
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}');
+17 -1
View File
@@ -2512,7 +2512,6 @@ class AllocationProfile extends Response {
///
/// If the field is uninitialized, the `value` will be the `NotInitialized`
/// [Sentinel].
///
class BoundField {
static BoundField? parse(Map<String, dynamic>? json) =>
json == null ? null : BoundField._fromJson(json);
@@ -3073,10 +3072,15 @@ class CodeRef extends ObjRef {
/// What kind of code object is this?
/*CodeKind*/ String? kind;
/// This code object's corresponding function.
@optional
FuncRef? function;
CodeRef({
this.name,
this.kind,
required String id,
this.function,
}) : super(
id: id,
);
@@ -3084,6 +3088,8 @@ class CodeRef extends ObjRef {
CodeRef._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
name = json['name'] ?? '';
kind = json['kind'] ?? '';
function =
createServiceObject(json['function'], const ['FuncRef']) as FuncRef?;
}
@override
@@ -3097,6 +3103,7 @@ class CodeRef extends ObjRef {
'name': name ?? '',
'kind': kind ?? '',
});
_setIfNotNull(json, 'function', function?.toJson());
return json;
}
@@ -3123,10 +3130,16 @@ class Code extends Obj implements CodeRef {
@override
/*CodeKind*/ String? kind;
/// This code object's corresponding function.
@optional
@override
FuncRef? function;
Code({
this.name,
this.kind,
required String id,
this.function,
}) : super(
id: id,
);
@@ -3134,6 +3147,8 @@ class Code extends Obj implements CodeRef {
Code._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
name = json['name'] ?? '';
kind = json['kind'] ?? '';
function =
createServiceObject(json['function'], const ['FuncRef']) as FuncRef?;
}
@override
@@ -3147,6 +3162,7 @@ class Code extends Obj implements CodeRef {
'name': name ?? '',
'kind': kind ?? '',
});
_setIfNotNull(json, 'function', function?.toJson());
return json;
}
+7 -1
View File
@@ -2085,6 +2085,9 @@ class @Code extends @Object {
// What kind of code object is this?
CodeKind kind;
// This code object's corresponding function.
@Function function [optional];
}
```
@@ -2097,6 +2100,9 @@ class Code extends Object {
// What kind of code object is this?
CodeKind kind;
// This code object's corresponding function.
@Function function [optional];
}
```
@@ -3188,7 +3194,7 @@ class Instance extends Object {
// RegExp
@Instance pattern [optional];
// The function associated with a Closure instance.
// The function associated with a Closure instance.
//
// Provided for instance kinds:
// Closure