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

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