-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathvm_service_tester.dart
251 lines (209 loc) · 8.02 KB
/
vm_service_tester.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
// 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);
}