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

Side by Side Diff: tools/coverage.dart

Issue 16203003: Add coverage tool (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 7 years, 6 months 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 | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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:io";
17 import "dart:utf";
18 import "dart:uri";
19 import "dart:json" as JSON;
20
21
22 // Whether or not to print debug target process on the console.
23 var showDebuggeeOutput = false;
24
25 // Whether or not to print the debugger wire messages on the console.
26 var verboseWire = false;
27
28
29 class Program {
30 static int numBps = 0;
31
32 // Maps source code url to source.
33 static var sources = new Map<String, Source>();
34
35 // Takes a JSON Debugger response and increments the count for the source posi tion
Ivan Posva 2013/05/29 21:42:44 long liner
hausner 2013/05/29 21:55:34 Done.
36 static void recordBp(Debugger debugger, Map<String,dynamic> msg) {
37 // Progress indicator.
38 if (++numBps % 100 == 0) print(numBps);
39 String url = msg["params"]["location"]["url"];
40 assert(url != null);
41 int tokenPos = msg["params"]["location"]["tokenOffset"];;
Ivan Posva 2013/05/29 21:42:44 What if the breakpoint message does not have a loc
hausner 2013/05/29 21:55:34 True. Changed to ignore the breakpoint if it has n
42 Source s = sources[url];
43 if (s == null) {
44 debugger.GetLineNumberTable(url);
45 s = new Source(url);
46 sources[url] = s;
47 }
48 s.recordBp(tokenPos);
49 }
50
51 // Prints the annotated source code.
52 static void printCoverage() {
53 print("Coverage info collected from $numBps breakpoints:");
54 for(Source s in sources.values) s.printCoverage();
55 }
56 }
57
58
59 class Source {
60 final String url;
61
62 // Maps token position to breakpoint count.
63 final tokenCounts = new Map<int,int>();
64
65 // Maps token position to line number.
66 final tokenPosToLine = new Map<int,int>();
67
68 Source(this.url);
69
70 void recordBp(int tokenPos) {
71 var count = tokenCounts[tokenPos];
72 tokenCounts[tokenPos] = count == null ? 1 : count + 1;
73 }
74
75 void SetLineInfo(List lineInfoTable) {
76 // Each line is encoded as an array with first element being the line
77 // number, followed by pairs of (tokenPosition, textOffset).
78 lineInfoTable.forEach((List<int> line) {
79 int lineNumber = line[0];
80 for (int t = 1; t < line.length; t += 2) {
81 assert(tokenPosToLine[line[t]] == null);
82 tokenPosToLine[line[t]] = lineNumber;
83 }
84 });
85 }
86
87 // Print out the annotated source code. For each line that has seen
88 // a breakpoint, print out the maximum breakpoint count for all
89 // tokens in the line.
90 void printCoverage() {
91 var lineCounts = new Map<int,int>(); // BP counts for each line.
92 print(url);
93 tokenCounts.forEach((tp, bpCount) {
94 int lineNumber = tokenPosToLine[tp];
95 var lineCount = lineCounts[lineNumber];
96 // Remember maximum breakpoint count of all tokens in this line.
97 if (lineCount == null || lineCount < bpCount) {
98 lineCounts[lineNumber] = bpCount;
99 }
100 });
101
102 List lines = new File(Uri.parse(url).path).readAsLinesSync();
103 for (int line = 1; line <= lines.length; line++) {
104 String prefix = " ";
105 if (lineCounts.containsKey(line)) {
106 prefix = lineCounts[line].toString();
107 StringBuffer b = new StringBuffer();
108 for (int i = prefix.length; i < 6; i++) b.write(" ");
109 b.write(prefix);
110 prefix = b.toString();
111 }
112 print("${prefix}|${lines[line-1]}");
113 }
114 }
115 }
116
117
118 class StepCmd {
119 Map msg;
120 StepCmd(int isolateId) {
121 msg = {"id": 0, "command": "stepInto", "params": {"isolateId": isolateId}};
122 }
123 void handleResponse(Map response) {}
124 }
125
126
127 class GetLineTableCmd {
128 Map msg;
129 GetLineTableCmd(int isolateId, int libraryId, String url) {
130 msg = { "id": 0,
131 "command": "getLineNumberTable",
132 "params": { "isolateId" : isolateId,
133 "libraryId": libraryId,
134 "url": url } };
135 }
136
137 void handleResponse(Map response) {
138 var url = msg["params"]["url"];
139 Source s = Program.sources[url];
140 assert(s != null);
141 s.SetLineInfo(response["result"]["lines"]);
142 }
143 }
144
145
146 class Debugger {
147 // Debug target process properties.
148 Process targetProcess;
149 Socket socket;
150 bool cleanupDone = false;
151 JsonBuffer responses = new JsonBuffer();
152 List<String> errors = new List();
153
154 // Data collected from debug target.
155 Map currentMessage = null; // Currently handled message sent by target.
156 var outstandingCommand = null;
157 var queuedCommand = null;
158 String scriptUrl = null;
159 bool shutdownEventSeen = false;
160 int isolateId = 0;
161 int libraryId = null;
162
163 int nextMessageId = 0;
164 bool isPaused = false;
165 bool pendingAck = false;
166
167 Debugger(this.targetProcess) {
168 var stdoutStringStream = targetProcess.stdout
169 .transform(new StringDecoder())
170 .transform(new LineTransformer());
171 stdoutStringStream.listen((line) {
172 if (showDebuggeeOutput) {
173 print("TARG: $line");
174 }
175 if (line.startsWith("Debugger listening")) {
176 RegExp portExpr = new RegExp(r"\d+");
177 var port = portExpr.stringMatch(line);
178 print("Debug target found listening at port '$port'");
179 openConnection(int.parse(port));
180 }
181 });
182
183 var stderrStringStream = targetProcess.stderr
184 .transform(new StringDecoder())
185 .transform(new LineTransformer());
186 stderrStringStream.listen((line) {
187 if (showDebuggeeOutput) {
188 print("TARG: $line");
189 }
190 });
191 }
192
193 // Handle debugger events, updating the debugger state.
194 void handleEvent(Map<String,dynamic> msg) {
195 if (msg["event"] == "isolate") {
196 if (msg["params"]["reason"] == "created") {
197 isolateId = msg["params"]["id"];
198 assert(isolateId != null);
199 print("Debuggee isolate id $isolateId created.");
200 } else if (msg["params"]["reason"] == "shutdown") {
201 print("Debuggee isolate id ${msg["params"]["id"]} shut down.");
202 shutdownEventSeen = true;
203 }
204 } else if (msg["event"] == "breakpointResolved") {
205 var bpId = msg["params"]["breakpointId"];
206 assert(bpId != null);
207 var isolateId = msg["params"]["isolateId"];
208 assert(isolateId != null);
209 var location = msg["params"]["location"];
210 assert(location != null);
211 print("Isolate $isolateId: breakpoint $bpId resolved"
212 " at location $location");
213 // We may want to maintain a table of breakpoints in the future.
214 } else if (msg["event"] == "paused") {
215 isPaused = true;
216 if (libraryId == null) {
217 libraryId = msg["params"]["location"]["libraryId"];
218 assert(libraryId != null);
219 }
220 if (msg["params"]["reason"] == "breakpoint") {
221 Program.recordBp(this, msg);
222 }
223 } else {
224 error("Error: unknown debugger event received");
225 }
226 }
227
228 // Handle one JSON message object and match it to the
229 // expected events and responses in the debugging script.
230 void handleMessage(Map<String,dynamic> receivedMsg) {
231 currentMessage = receivedMsg;
232 if (receivedMsg["event"] != null) {
233 handleEvent(receivedMsg);
234 if (errorsDetected) {
235 error("Error while handling debugger event");
236 error("Event received from debug target: $receivedMsg");
237 }
238 } else if (receivedMsg["id"] != null) {
239 // This is a response to the last command we sent.
240 int id = receivedMsg["id"];
241 assert(outstandingCommand != null);
242 assert(outstandingCommand.msg["id"] == id);
243 outstandingCommand.handleResponse(receivedMsg);
244 outstandingCommand = null;
245 } else {
246 error("Unexpected message from target");
247 }
248 }
249
250 // Handle data received over the wire from the debug target
251 // process. Split input from JSON wire format into individual
252 // message objects (maps).
253 void handleMessages() {
254 var msg = responses.getNextMessage();
255 while (msg != null) {
256 if (verboseWire) print("RECV: $msg");
257 if (responses.haveGarbage()) {
258 error("Error: leftover text after message: '${responses.buffer}'");
259 error("Previous message may be malformed, was: '$msg'");
260 cleanup();
261 return;
262 }
263 var msgObj = JSON.parse(msg);
264 handleMessage(msgObj);
265 if (errorsDetected) {
266 error("Error while handling message from debug target");
267 error("Message received from debug target: $msg");
268 cleanup();
269 return;
270 }
271 if (shutdownEventSeen) {
272 if (outstandingCommand != null) {
273 error("Error: outstanding command when shutdown received");
274 }
275 cleanup();
276 return;
277 }
278 if (isPaused && (outstandingCommand == null)) {
279 var cmd = queuedCommand;
280 queuedCommand = null;
281 if (cmd == null) {
282 cmd = new StepCmd(isolateId);
283 isPaused = false;
284 }
285 sendMessage(cmd.msg);
286 outstandingCommand = cmd;
287 }
288 msg = responses.getNextMessage();
289 }
290 }
291
292 // Send a debugger command to the target VM.
293 void sendMessage(Map<String,dynamic> msg) {
294 assert(msg["id"] != null);
295 msg["id"] = nextMessageId++;
296 String jsonMsg = JSON.stringify(msg);
297 if (verboseWire) print("SEND: $jsonMsg");
298 socket.write(jsonMsg);
299 }
300
301 void GetLineNumberTable(String url) {
302 assert(queuedCommand == null);
303 queuedCommand = new GetLineTableCmd(isolateId, libraryId, url);
304 }
305
306 bool get errorsDetected => errors.length > 0;
307
308 // Record error message.
309 void error(String s) {
310 errors.add(s);
311 }
312
313 void openConnection(int portNumber) {
314 Socket.connect("127.0.0.1", portNumber).then((s) {
315 socket = s;
316 var stringStream = socket.transform(new StringDecoder());
317 stringStream.listen(
318 (str) {
319 try {
320 responses.append(str);
321 handleMessages();
322 } catch(e, trace) {
323 print("Unexpected exception:\n$e\n$trace");
324 cleanup();
325 }
326 },
327 onDone: () {
328 print("Connection closed by debug target");
329 cleanup();
330 },
331 onError: (e) {
332 print("Error '$e' detected in input stream from debug target");
333 cleanup();
334 });
335 },
336 onError: (e) {
337 String msg = "Error while connecting to debugee: $e";
338 var trace = getAttachedStackTrace(e);
339 if (trace != null) msg += "\nStackTrace: $trace";
340 error(msg);
341 cleanup();
342 });
343 }
344
345 void cleanup() {
346 if (cleanupDone) return;
347 if (socket != null) {
348 socket.close().catchError((error) {
349 // Print this directly in addition to adding it to the
350 // error message queue, in case the error message queue
351 // gets printed before this error handler is called.
352 print("Error occurred while closing socket: $error");
353 error("Error while closing socket: $error");
354 });
355 }
356 var targetPid = targetProcess.pid;
357 print("Sending kill signal to process $targetPid...");
358 targetProcess.kill();
359 // If the process was already dead exitCode is already
360 // available and we call exit() in the next event loop cycle.
361 // Otherwise this will wait for the process to exit.
362
363 targetProcess.exitCode.then((exitCode) {
364 print("process $targetPid terminated with exit code $exitCode.");
365 if (errorsDetected) {
366 print("\n===== Errors detected: =====");
367 for (int i = 0; i < errors.length; i++) print(errors[i]);
368 print("============================\n");
369 }
370 Program.printCoverage();
371 exit(errors.length);
372 });
373 cleanupDone = true;
374 }
375 }
376
377
378 // Class to buffer wire protocol data from debug target and
379 // break it down to individual json messages.
380 class JsonBuffer {
381 String buffer = null;
382
383 append(String s) {
384 if (buffer == null || buffer.length == 0) {
385 buffer = s;
386 } else {
387 buffer = buffer.concat(s);
388 }
389 }
390
391 String getNextMessage() {
392 if (buffer == null) return null;
393 int msgLen = objectLength();
394 if (msgLen == 0) return null;
395 String msg = null;
396 if (msgLen == buffer.length) {
397 msg = buffer;
398 buffer = null;
399 } else {
400 assert(msgLen < buffer.length);
401 msg = buffer.substring(0, msgLen);
402 buffer = buffer.substring(msgLen);
403 }
404 return msg;
405 }
406
407 bool haveGarbage() {
408 if (buffer == null || buffer.length == 0) return false;
409 var i = 0, char = " ";
410 while (i < buffer.length) {
411 char = buffer[i];
412 if (char != " " && char != "\n" && char != "\r" && char != "\t") break;
413 i++;
414 }
415 if (i >= buffer.length) {
416 return false;
417 } else {
418 return char != "{";
419 }
420 }
421
422 // Returns the character length of the newxt json message in the
Ivan Posva 2013/05/29 21:42:44 next
hausner 2013/05/29 21:55:34 Downe
423 // buffer, or 0 if there is only a partial message in the buffer.
424 // The object value must start with '{' and continues to the
425 // matching '}'. No attempt is made to otherwise validate the contents
426 // as JSON. If it is invalid, a later JSON.parse() will fail.
427 int objectLength() {
428 int skipWhitespace(int index) {
429 while (index < buffer.length) {
430 String char = buffer[index];
431 if (char != " " && char != "\n" && char != "\r" && char != "\t") break;
432 index++;
433 }
434 return index;
435 }
436 int skipString(int index) {
437 assert(buffer[index - 1] == '"');
438 while (index < buffer.length) {
439 String char = buffer[index];
440 if (char == '"') return index + 1;
441 if (char == r'\') index++;
442 if (index == buffer.length) return index;
443 index++;
444 }
445 return index;
446 }
447 int index = 0;
448 index = skipWhitespace(index);
449 // Bail out if the first non-whitespace character isn't '{'.
450 if (index == buffer.length || buffer[index] != '{') return 0;
451 int nesting = 0;
452 while (index < buffer.length) {
453 String char = buffer[index++];
454 if (char == '{') {
455 nesting++;
456 } else if (char == '}') {
457 nesting--;
458 if (nesting == 0) return index;
459 } else if (char == '"') {
460 // Strings can contain braces. Skip their content.
461 index = skipString(index);
462 }
463 }
464 return 0;
465 }
466 }
467
468
469 void main() {
470 var options = new Options();
471 var targetOpts = [ "--debug:0" ];
472 for (String str in options.arguments) {
473 switch (str) {
474 case "--verbose":
475 showDebuggeeOutput = true;
476 break;
477 case "--wire":
478 verboseWire = true;
479 break;
480 default:
481 targetOpts.add(str);
482 break;
483 }
484 }
485
486 Process.start(options.executable, targetOpts).then((Process process) {
487 process.stdin.close();
488 var debugger = new Debugger(process);
489 });
490 }
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698