Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Side by Side Diff: tools/coverage.dart

Issue 1497033003: - Remove the legacy debug protocol. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 // This test forks a second vm process that runs a dart script as
6 // a debug target, single stepping through the entire program, and
7 // recording each breakpoint. At the end, a coverage map of the source
8 // is printed.
9 //
10 // Usage: dart coverage.dart [--wire] [--verbose] target_script.dart
11 //
12 // --wire see json messages sent between the processes.
13 // --verbose see the stdout and stderr output of the debug
14 // target process.
15
16 import "dart:convert";
17 import "dart:io";
18
19
20 // Whether or not to print debug target process on the console.
21 var showDebuggeeOutput = false;
22
23 // Whether or not to print the debugger wire messages on the console.
24 var verboseWire = false;
25
26 var debugger = null;
27
28 class Program {
29 static int numBps = 0;
30
31 // Maps source code url to source.
32 static var sources = new Map<String, Source>();
33
34 // Takes a JSON Debugger response and increments the count for
35 // the source position.
36 static void recordBp(Map<String,dynamic> msg) {
37 // Progress indicator.
38 if (++numBps % 1000 == 0) print(numBps);
39 var location = msg["params"]["location"];
40 if (location == null) return;
41 String url = location["url"];
42 assert(url != null);
43 int libId = location["libraryId"];
44 assert(libId != null);
45 int tokenPos = location["tokenOffset"];;
46 Source s = sources[url];
47 if (s == null) {
48 debugger.getLineNumberTable(url, libId);
49 s = new Source(url);
50 sources[url] = s;
51 }
52 s.recordBp(tokenPos);
53 }
54
55 // Prints the annotated source code.
56 static void printCoverage() {
57 print("Coverage info collected from $numBps breakpoints:");
58 for(Source s in sources.values) s.printCoverage();
59 }
60 }
61
62
63 class Source {
64 final String url;
65
66 // Maps token position to breakpoint count.
67 final tokenCounts = new Map<int,int>();
68
69 // Maps token position to line number.
70 final tokenPosToLine = new Map<int,int>();
71
72 Source(this.url);
73
74 void recordBp(int tokenPos) {
75 var count = tokenCounts[tokenPos];
76 tokenCounts[tokenPos] = count == null ? 1 : count + 1;
77 }
78
79 void SetLineInfo(List lineInfoTable) {
80 // Each line is encoded as an array with first element being the line
81 // number, followed by pairs of (tokenPosition, columnNumber).
82 lineInfoTable.forEach((List<int> line) {
83 int lineNumber = line[0];
84 for (int t = 1; t < line.length; t += 2) {
85 assert(tokenPosToLine[line[t]] == null);
86 tokenPosToLine[line[t]] = lineNumber;
87 }
88 });
89 }
90
91 // Print out the annotated source code. For each line that has seen
92 // a breakpoint, print out the maximum breakpoint count for all
93 // tokens in the line.
94 void printCoverage() {
95 var lineCounts = new Map<int,int>(); // BP counts for each line.
96 print(url);
97 tokenCounts.forEach((tp, bpCount) {
98 int lineNumber = tokenPosToLine[tp];
99 var lineCount = lineCounts[lineNumber];
100 // Remember maximum breakpoint count of all tokens in this line.
101 if (lineCount == null || lineCount < bpCount) {
102 lineCounts[lineNumber] = bpCount;
103 }
104 });
105
106 String srcPath = Uri.parse(url).toFilePath();
107 List lines = new File(srcPath).readAsLinesSync();
108 for (int line = 1; line <= lines.length; line++) {
109 String prefix = " ";
110 if (lineCounts.containsKey(line)) {
111 prefix = lineCounts[line].toString();
112 StringBuffer b = new StringBuffer();
113 for (int i = prefix.length; i < 6; i++) b.write(" ");
114 b.write(prefix);
115 prefix = b.toString();
116 }
117 print("${prefix}|${lines[line-1]}");
118 }
119 }
120 }
121
122
123 class StepCmd {
124 Map msg;
125 StepCmd(int isolateId) {
126 msg = {"id": 0, "command": "stepInto", "params": {"isolateId": isolateId}};
127 }
128 void handleResponse(Map response) {}
129 }
130
131
132 class GetLineTableCmd {
133 Map msg;
134 GetLineTableCmd(int isolateId, int libraryId, String url) {
135 msg = { "id": 0,
136 "command": "getLineNumberTable",
137 "params": { "isolateId" : isolateId,
138 "libraryId": libraryId,
139 "url": url } };
140 }
141
142 void handleResponse(Map response) {
143 var url = msg["params"]["url"];
144 Source s = Program.sources[url];
145 assert(s != null);
146 s.SetLineInfo(response["result"]["lines"]);
147 }
148 }
149
150
151 class GetLibrariesCmd {
152 Map msg;
153 GetLibrariesCmd(int isolateId) {
154 msg = { "id": 0,
155 "command": "getLibraries",
156 "params": { "isolateId" : isolateId } };
157 }
158
159 void handleResponse(Map response) {
160 List libs = response["result"]["libraries"];
161 for (var lib in libs) {
162 String url = lib["url"];
163 int libraryId = lib["id"];
164 bool enable = !url.startsWith("dart:") && !url.startsWith("package:");
165 if (enable) {
166 print("Enable stepping for '$url'");
167 debugger.enableDebugging(libraryId, true);
168 }
169 }
170 }
171 }
172
173
174 class SetLibraryPropertiesCmd {
175 Map msg;
176 SetLibraryPropertiesCmd(int isolateId, int libraryId, bool enableDebugging) {
177 // Note that in the debugger protocol, boolean values true and false
178 // must be sent as string literals.
179 msg = { "id": 0,
180 "command": "setLibraryProperties",
181 "params": { "isolateId" : isolateId,
182 "libraryId": libraryId,
183 "debuggingEnabled": "$enableDebugging" } };
184 }
185
186 void handleResponse(Map response) {
187 // Nothing to do.
188 }
189 }
190
191
192 class Debugger {
193 // Debug target process properties.
194 Process targetProcess;
195 Socket socket;
196 bool cleanupDone = false;
197 JsonBuffer responses = new JsonBuffer();
198 List<String> errors = new List();
199
200 // Data collected from debug target.
201 Map currentMessage = null; // Currently handled message sent by target.
202 var outstandingCommand = null;
203 var queuedCommands = new List();
204 String scriptUrl = null;
205 bool shutdownEventSeen = false;
206 int isolateId = 0;
207 int libraryId = null;
208
209 int nextMessageId = 0;
210 bool isPaused = false;
211 bool pendingAck = false;
212
213 Debugger(this.targetProcess) {
214 var stdoutStringStream = targetProcess.stdout
215 .transform(UTF8.decoder)
216 .transform(new LineSplitter());
217 stdoutStringStream.listen((line) {
218 if (showDebuggeeOutput) {
219 print("TARG: $line");
220 }
221 if (line.startsWith("Debugger listening")) {
222 RegExp portExpr = new RegExp(r"\d+");
223 var port = portExpr.stringMatch(line);
224 var pid = targetProcess.pid;
225 print("Coverage target process (pid $pid) found "
226 "listening on port $port.");
227 openConnection(int.parse(port));
228 }
229 });
230
231 var stderrStringStream = targetProcess.stderr
232 .transform(UTF8.decoder)
233 .transform(new LineSplitter());
234 stderrStringStream.listen((line) {
235 if (showDebuggeeOutput) {
236 print("TARG: $line");
237 }
238 });
239 }
240
241 // Handle debugger events, updating the debugger state.
242 void handleEvent(Map<String,dynamic> msg) {
243 if (msg["event"] == "isolate") {
244 if (msg["params"]["reason"] == "created") {
245 isolateId = msg["params"]["id"];
246 assert(isolateId != null);
247 print("Debuggee isolate id $isolateId created.");
248 } else if (msg["params"]["reason"] == "shutdown") {
249 print("Debuggee isolate id ${msg["params"]["id"]} shut down.");
250 shutdownEventSeen = true;
251 }
252 } else if (msg["event"] == "breakpointResolved") {
253 var bpId = msg["params"]["breakpointId"];
254 assert(bpId != null);
255 var isolateId = msg["params"]["isolateId"];
256 assert(isolateId != null);
257 var location = msg["params"]["location"];
258 assert(location != null);
259 print("Isolate $isolateId: breakpoint $bpId resolved"
260 " at location $location");
261 // We may want to maintain a table of breakpoints in the future.
262 } else if (msg["event"] == "paused") {
263 isPaused = true;
264 if (libraryId == null) {
265 libraryId = msg["params"]["location"]["libraryId"];
266 assert(libraryId != null);
267 // This is the first paused event we got. Get all libraries from
268 // the debugger so we can turn on debugging events for them.
269 getLibraries();
270 }
271 if (msg["params"]["reason"] == "breakpoint") {
272 Program.recordBp(msg);
273 }
274 } else {
275 error("Error: unknown debugger event received");
276 }
277 }
278
279 // Handle one JSON message object.
280 void handleMessage(Map<String,dynamic> receivedMsg) {
281 currentMessage = receivedMsg;
282 if (receivedMsg["event"] != null) {
283 handleEvent(receivedMsg);
284 if (errorsDetected) {
285 error("Error while handling event message");
286 error("Event received from coverage target: $receivedMsg");
287 }
288 } else if (receivedMsg["id"] != null) {
289 // This is a response to the last command we sent.
290 int id = receivedMsg["id"];
291 assert(outstandingCommand != null);
292 assert(outstandingCommand.msg["id"] == id);
293 outstandingCommand.handleResponse(receivedMsg);
294 outstandingCommand = null;
295 } else {
296 error("Unexpected message from target");
297 }
298 }
299
300 // Handle data received over the wire from the coverage target
301 // process. Split input from JSON wire format into individual
302 // message objects (maps).
303 void handleMessages() {
304 var msg = responses.getNextMessage();
305 while (msg != null) {
306 if (verboseWire) print("RECV: $msg");
307 if (responses.haveGarbage()) {
308 error("Error: leftover text after message: '${responses.buffer}'");
309 error("Previous message may be malformed, was: '$msg'");
310 cleanup();
311 return;
312 }
313 var msgObj = JSON.decode(msg);
314 handleMessage(msgObj);
315 if (errorsDetected) {
316 error("Error while handling message from coverage target");
317 error("Message received from coverage target: $msg");
318 cleanup();
319 return;
320 }
321 if (shutdownEventSeen) {
322 if (outstandingCommand != null) {
323 error("Error: outstanding command when shutdown received");
324 }
325 cleanup();
326 return;
327 }
328 if (isPaused && (outstandingCommand == null)) {
329 var cmd = queuedCommands.length > 0 ? queuedCommands.removeAt(0) : null;
330 if (cmd == null) {
331 cmd = new StepCmd(isolateId);
332 isPaused = false;
333 }
334 sendMessage(cmd.msg);
335 outstandingCommand = cmd;
336 }
337 msg = responses.getNextMessage();
338 }
339 }
340
341 // Send a debugger command to the target VM.
342 void sendMessage(Map<String,dynamic> msg) {
343 assert(msg["id"] != null);
344 msg["id"] = nextMessageId++;
345 String jsonMsg = JSON.encode(msg);
346 if (verboseWire) print("SEND: $jsonMsg");
347 socket.write(jsonMsg);
348 }
349
350 void getLineNumberTable(String url, int libId) {
351 queuedCommands.add(new GetLineTableCmd(isolateId, libId, url));
352 }
353
354 void getLibraries() {
355 queuedCommands.add(new GetLibrariesCmd(isolateId));
356 }
357
358 void enableDebugging(libraryId, enable) {
359 queuedCommands.add(new SetLibraryPropertiesCmd(isolateId, libraryId, enable) );
360 }
361
362 bool get errorsDetected => errors.length > 0;
363
364 // Record error message.
365 void error(String s) {
366 errors.add(s);
367 }
368
369 void openConnection(int portNumber) {
370 Socket.connect("127.0.0.1", portNumber).then((s) {
371 socket = s;
372 socket.setOption(SocketOption.TCP_NODELAY, true);
373 var stringStream = socket.transform(UTF8.decoder);
374 stringStream.listen(
375 (str) {
376 try {
377 responses.append(str);
378 handleMessages();
379 } catch(e, trace) {
380 print("Unexpected exception:\n$e\n$trace");
381 cleanup();
382 }
383 },
384 onDone: () {
385 print("Connection closed by coverage target");
386 cleanup();
387 },
388 onError: (e) {
389 print("Error '$e' detected in input stream from coverage target");
390 cleanup();
391 });
392 },
393 onError: (e, trace) {
394 String msg = "Error while connecting to coverage target: $e";
395 if (trace != null) msg += "\nStackTrace: $trace";
396 error(msg);
397 cleanup();
398 });
399 }
400
401 void cleanup() {
402 if (cleanupDone) return;
403 if (socket != null) {
404 socket.close().catchError((error) {
405 // Print this directly in addition to adding it to the
406 // error message queue, in case the error message queue
407 // gets printed before this error handler is called.
408 print("Error occurred while closing socket: $error");
409 error("Error while closing socket: $error");
410 });
411 }
412 var targetPid = targetProcess.pid;
413 print("Sending kill signal to process $targetPid.");
414 targetProcess.kill();
415 // If the process was already dead exitCode is already
416 // available and we call exit() in the next event loop cycle.
417 // Otherwise this will wait for the process to exit.
418
419 targetProcess.exitCode.then((exitCode) {
420 print("Process $targetPid terminated with exit code $exitCode.");
421 if (errorsDetected) {
422 print("\n===== Errors detected: =====");
423 for (int i = 0; i < errors.length; i++) print(errors[i]);
424 print("============================\n");
425 }
426 Program.printCoverage();
427 exit(errors.length);
428 });
429 cleanupDone = true;
430 }
431 }
432
433
434 // Class to buffer wire protocol data from coverage target and
435 // break it down to individual json messages.
436 class JsonBuffer {
437 String buffer = null;
438
439 append(String s) {
440 if (buffer == null || buffer.length == 0) {
441 buffer = s;
442 } else {
443 buffer = buffer + s;
444 }
445 }
446
447 String getNextMessage() {
448 if (buffer == null) return null;
449 int msgLen = objectLength();
450 if (msgLen == 0) return null;
451 String msg = null;
452 if (msgLen == buffer.length) {
453 msg = buffer;
454 buffer = null;
455 } else {
456 assert(msgLen < buffer.length);
457 msg = buffer.substring(0, msgLen);
458 buffer = buffer.substring(msgLen);
459 }
460 return msg;
461 }
462
463 bool haveGarbage() {
464 if (buffer == null || buffer.length == 0) return false;
465 var i = 0, char = " ";
466 while (i < buffer.length) {
467 char = buffer[i];
468 if (char != " " && char != "\n" && char != "\r" && char != "\t") break;
469 i++;
470 }
471 if (i >= buffer.length) {
472 return false;
473 } else {
474 return char != "{";
475 }
476 }
477
478 // Returns the character length of the next json message in the
479 // buffer, or 0 if there is only a partial message in the buffer.
480 // The object value must start with '{' and continues to the
481 // matching '}'. No attempt is made to otherwise validate the contents
482 // as JSON. If it is invalid, a later JSON.decode() will fail.
483 int objectLength() {
484 int skipWhitespace(int index) {
485 while (index < buffer.length) {
486 String char = buffer[index];
487 if (char != " " && char != "\n" && char != "\r" && char != "\t") break;
488 index++;
489 }
490 return index;
491 }
492 int skipString(int index) {
493 assert(buffer[index - 1] == '"');
494 while (index < buffer.length) {
495 String char = buffer[index];
496 if (char == '"') return index + 1;
497 if (char == r'\') index++;
498 if (index == buffer.length) return index;
499 index++;
500 }
501 return index;
502 }
503 int index = 0;
504 index = skipWhitespace(index);
505 // Bail out if the first non-whitespace character isn't '{'.
506 if (index == buffer.length || buffer[index] != '{') return 0;
507 int nesting = 0;
508 while (index < buffer.length) {
509 String char = buffer[index++];
510 if (char == '{') {
511 nesting++;
512 } else if (char == '}') {
513 nesting--;
514 if (nesting == 0) return index;
515 } else if (char == '"') {
516 // Strings can contain braces. Skip their content.
517 index = skipString(index);
518 }
519 }
520 return 0;
521 }
522 }
523
524
525 void main(List<String> arguments) {
526 var targetOpts = [ "--debug:0" ];
527 for (String str in arguments) {
528 switch (str) {
529 case "--verbose":
530 showDebuggeeOutput = true;
531 break;
532 case "--wire":
533 verboseWire = true;
534 break;
535 default:
536 targetOpts.add(str);
537 break;
538 }
539 }
540
541 Process.start(Platform.executable, targetOpts).then((Process process) {
542 process.stdin.close();
543 debugger = new Debugger(process);
544 });
545 }
OLDNEW
« tests/standalone/coverage_test.dart ('K') | « tests/standalone/coverage_test.dart ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698