Index: tools/coverage.dart |
diff --git a/tools/coverage.dart b/tools/coverage.dart |
deleted file mode 100644 |
index b5cc61812d5e41e8578626f0cc08050e0c680166..0000000000000000000000000000000000000000 |
--- a/tools/coverage.dart |
+++ /dev/null |
@@ -1,545 +0,0 @@ |
-// Copyright (c) 2013, 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. |
- |
-// This test forks a second vm process that runs a dart script as |
-// a debug target, single stepping through the entire program, and |
-// recording each breakpoint. At the end, a coverage map of the source |
-// is printed. |
-// |
-// Usage: dart coverage.dart [--wire] [--verbose] target_script.dart |
-// |
-// --wire see json messages sent between the processes. |
-// --verbose see the stdout and stderr output of the debug |
-// target process. |
- |
-import "dart:convert"; |
-import "dart:io"; |
- |
- |
-// Whether or not to print debug target process on the console. |
-var showDebuggeeOutput = false; |
- |
-// Whether or not to print the debugger wire messages on the console. |
-var verboseWire = false; |
- |
-var debugger = null; |
- |
-class Program { |
- static int numBps = 0; |
- |
- // Maps source code url to source. |
- static var sources = new Map<String, Source>(); |
- |
- // Takes a JSON Debugger response and increments the count for |
- // the source position. |
- static void recordBp(Map<String,dynamic> msg) { |
- // Progress indicator. |
- if (++numBps % 1000 == 0) print(numBps); |
- var location = msg["params"]["location"]; |
- if (location == null) return; |
- String url = location["url"]; |
- assert(url != null); |
- int libId = location["libraryId"]; |
- assert(libId != null); |
- int tokenPos = location["tokenOffset"];; |
- Source s = sources[url]; |
- if (s == null) { |
- debugger.getLineNumberTable(url, libId); |
- s = new Source(url); |
- sources[url] = s; |
- } |
- s.recordBp(tokenPos); |
- } |
- |
- // Prints the annotated source code. |
- static void printCoverage() { |
- print("Coverage info collected from $numBps breakpoints:"); |
- for(Source s in sources.values) s.printCoverage(); |
- } |
-} |
- |
- |
-class Source { |
- final String url; |
- |
- // Maps token position to breakpoint count. |
- final tokenCounts = new Map<int,int>(); |
- |
- // Maps token position to line number. |
- final tokenPosToLine = new Map<int,int>(); |
- |
- Source(this.url); |
- |
- void recordBp(int tokenPos) { |
- var count = tokenCounts[tokenPos]; |
- tokenCounts[tokenPos] = count == null ? 1 : count + 1; |
- } |
- |
- void SetLineInfo(List lineInfoTable) { |
- // Each line is encoded as an array with first element being the line |
- // number, followed by pairs of (tokenPosition, columnNumber). |
- lineInfoTable.forEach((List<int> line) { |
- int lineNumber = line[0]; |
- for (int t = 1; t < line.length; t += 2) { |
- assert(tokenPosToLine[line[t]] == null); |
- tokenPosToLine[line[t]] = lineNumber; |
- } |
- }); |
- } |
- |
- // Print out the annotated source code. For each line that has seen |
- // a breakpoint, print out the maximum breakpoint count for all |
- // tokens in the line. |
- void printCoverage() { |
- var lineCounts = new Map<int,int>(); // BP counts for each line. |
- print(url); |
- tokenCounts.forEach((tp, bpCount) { |
- int lineNumber = tokenPosToLine[tp]; |
- var lineCount = lineCounts[lineNumber]; |
- // Remember maximum breakpoint count of all tokens in this line. |
- if (lineCount == null || lineCount < bpCount) { |
- lineCounts[lineNumber] = bpCount; |
- } |
- }); |
- |
- String srcPath = Uri.parse(url).toFilePath(); |
- List lines = new File(srcPath).readAsLinesSync(); |
- for (int line = 1; line <= lines.length; line++) { |
- String prefix = " "; |
- if (lineCounts.containsKey(line)) { |
- prefix = lineCounts[line].toString(); |
- StringBuffer b = new StringBuffer(); |
- for (int i = prefix.length; i < 6; i++) b.write(" "); |
- b.write(prefix); |
- prefix = b.toString(); |
- } |
- print("${prefix}|${lines[line-1]}"); |
- } |
- } |
-} |
- |
- |
-class StepCmd { |
- Map msg; |
- StepCmd(int isolateId) { |
- msg = {"id": 0, "command": "stepInto", "params": {"isolateId": isolateId}}; |
- } |
- void handleResponse(Map response) {} |
-} |
- |
- |
-class GetLineTableCmd { |
- Map msg; |
- GetLineTableCmd(int isolateId, int libraryId, String url) { |
- msg = { "id": 0, |
- "command": "getLineNumberTable", |
- "params": { "isolateId" : isolateId, |
- "libraryId": libraryId, |
- "url": url } }; |
- } |
- |
- void handleResponse(Map response) { |
- var url = msg["params"]["url"]; |
- Source s = Program.sources[url]; |
- assert(s != null); |
- s.SetLineInfo(response["result"]["lines"]); |
- } |
-} |
- |
- |
-class GetLibrariesCmd { |
- Map msg; |
- GetLibrariesCmd(int isolateId) { |
- msg = { "id": 0, |
- "command": "getLibraries", |
- "params": { "isolateId" : isolateId } }; |
- } |
- |
- void handleResponse(Map response) { |
- List libs = response["result"]["libraries"]; |
- for (var lib in libs) { |
- String url = lib["url"]; |
- int libraryId = lib["id"]; |
- bool enable = !url.startsWith("dart:") && !url.startsWith("package:"); |
- if (enable) { |
- print("Enable stepping for '$url'"); |
- debugger.enableDebugging(libraryId, true); |
- } |
- } |
- } |
-} |
- |
- |
-class SetLibraryPropertiesCmd { |
- Map msg; |
- SetLibraryPropertiesCmd(int isolateId, int libraryId, bool enableDebugging) { |
- // Note that in the debugger protocol, boolean values true and false |
- // must be sent as string literals. |
- msg = { "id": 0, |
- "command": "setLibraryProperties", |
- "params": { "isolateId" : isolateId, |
- "libraryId": libraryId, |
- "debuggingEnabled": "$enableDebugging" } }; |
- } |
- |
- void handleResponse(Map response) { |
- // Nothing to do. |
- } |
-} |
- |
- |
-class Debugger { |
- // Debug target process properties. |
- Process targetProcess; |
- Socket socket; |
- bool cleanupDone = false; |
- JsonBuffer responses = new JsonBuffer(); |
- List<String> errors = new List(); |
- |
- // Data collected from debug target. |
- Map currentMessage = null; // Currently handled message sent by target. |
- var outstandingCommand = null; |
- var queuedCommands = new List(); |
- String scriptUrl = null; |
- bool shutdownEventSeen = false; |
- int isolateId = 0; |
- int libraryId = null; |
- |
- int nextMessageId = 0; |
- bool isPaused = false; |
- bool pendingAck = false; |
- |
- Debugger(this.targetProcess) { |
- var stdoutStringStream = targetProcess.stdout |
- .transform(UTF8.decoder) |
- .transform(new LineSplitter()); |
- stdoutStringStream.listen((line) { |
- if (showDebuggeeOutput) { |
- print("TARG: $line"); |
- } |
- if (line.startsWith("Debugger listening")) { |
- RegExp portExpr = new RegExp(r"\d+"); |
- var port = portExpr.stringMatch(line); |
- var pid = targetProcess.pid; |
- print("Coverage target process (pid $pid) found " |
- "listening on port $port."); |
- openConnection(int.parse(port)); |
- } |
- }); |
- |
- var stderrStringStream = targetProcess.stderr |
- .transform(UTF8.decoder) |
- .transform(new LineSplitter()); |
- stderrStringStream.listen((line) { |
- if (showDebuggeeOutput) { |
- print("TARG: $line"); |
- } |
- }); |
- } |
- |
- // Handle debugger events, updating the debugger state. |
- void handleEvent(Map<String,dynamic> msg) { |
- if (msg["event"] == "isolate") { |
- if (msg["params"]["reason"] == "created") { |
- isolateId = msg["params"]["id"]; |
- assert(isolateId != null); |
- print("Debuggee isolate id $isolateId created."); |
- } else if (msg["params"]["reason"] == "shutdown") { |
- print("Debuggee isolate id ${msg["params"]["id"]} shut down."); |
- shutdownEventSeen = true; |
- } |
- } else if (msg["event"] == "breakpointResolved") { |
- var bpId = msg["params"]["breakpointId"]; |
- assert(bpId != null); |
- var isolateId = msg["params"]["isolateId"]; |
- assert(isolateId != null); |
- var location = msg["params"]["location"]; |
- assert(location != null); |
- print("Isolate $isolateId: breakpoint $bpId resolved" |
- " at location $location"); |
- // We may want to maintain a table of breakpoints in the future. |
- } else if (msg["event"] == "paused") { |
- isPaused = true; |
- if (libraryId == null) { |
- libraryId = msg["params"]["location"]["libraryId"]; |
- assert(libraryId != null); |
- // This is the first paused event we got. Get all libraries from |
- // the debugger so we can turn on debugging events for them. |
- getLibraries(); |
- } |
- if (msg["params"]["reason"] == "breakpoint") { |
- Program.recordBp(msg); |
- } |
- } else { |
- error("Error: unknown debugger event received"); |
- } |
- } |
- |
- // Handle one JSON message object. |
- void handleMessage(Map<String,dynamic> receivedMsg) { |
- currentMessage = receivedMsg; |
- if (receivedMsg["event"] != null) { |
- handleEvent(receivedMsg); |
- if (errorsDetected) { |
- error("Error while handling event message"); |
- error("Event received from coverage target: $receivedMsg"); |
- } |
- } else if (receivedMsg["id"] != null) { |
- // This is a response to the last command we sent. |
- int id = receivedMsg["id"]; |
- assert(outstandingCommand != null); |
- assert(outstandingCommand.msg["id"] == id); |
- outstandingCommand.handleResponse(receivedMsg); |
- outstandingCommand = null; |
- } else { |
- error("Unexpected message from target"); |
- } |
- } |
- |
- // Handle data received over the wire from the coverage target |
- // process. Split input from JSON wire format into individual |
- // message objects (maps). |
- void handleMessages() { |
- var msg = responses.getNextMessage(); |
- while (msg != null) { |
- if (verboseWire) print("RECV: $msg"); |
- if (responses.haveGarbage()) { |
- error("Error: leftover text after message: '${responses.buffer}'"); |
- error("Previous message may be malformed, was: '$msg'"); |
- cleanup(); |
- return; |
- } |
- var msgObj = JSON.decode(msg); |
- handleMessage(msgObj); |
- if (errorsDetected) { |
- error("Error while handling message from coverage target"); |
- error("Message received from coverage target: $msg"); |
- cleanup(); |
- return; |
- } |
- if (shutdownEventSeen) { |
- if (outstandingCommand != null) { |
- error("Error: outstanding command when shutdown received"); |
- } |
- cleanup(); |
- return; |
- } |
- if (isPaused && (outstandingCommand == null)) { |
- var cmd = queuedCommands.length > 0 ? queuedCommands.removeAt(0) : null; |
- if (cmd == null) { |
- cmd = new StepCmd(isolateId); |
- isPaused = false; |
- } |
- sendMessage(cmd.msg); |
- outstandingCommand = cmd; |
- } |
- msg = responses.getNextMessage(); |
- } |
- } |
- |
- // Send a debugger command to the target VM. |
- void sendMessage(Map<String,dynamic> msg) { |
- assert(msg["id"] != null); |
- msg["id"] = nextMessageId++; |
- String jsonMsg = JSON.encode(msg); |
- if (verboseWire) print("SEND: $jsonMsg"); |
- socket.write(jsonMsg); |
- } |
- |
- void getLineNumberTable(String url, int libId) { |
- queuedCommands.add(new GetLineTableCmd(isolateId, libId, url)); |
- } |
- |
- void getLibraries() { |
- queuedCommands.add(new GetLibrariesCmd(isolateId)); |
- } |
- |
- void enableDebugging(libraryId, enable) { |
- queuedCommands.add(new SetLibraryPropertiesCmd(isolateId, libraryId, enable)); |
- } |
- |
- bool get errorsDetected => errors.length > 0; |
- |
- // Record error message. |
- void error(String s) { |
- errors.add(s); |
- } |
- |
- void openConnection(int portNumber) { |
- Socket.connect("127.0.0.1", portNumber).then((s) { |
- socket = s; |
- socket.setOption(SocketOption.TCP_NODELAY, true); |
- var stringStream = socket.transform(UTF8.decoder); |
- stringStream.listen( |
- (str) { |
- try { |
- responses.append(str); |
- handleMessages(); |
- } catch(e, trace) { |
- print("Unexpected exception:\n$e\n$trace"); |
- cleanup(); |
- } |
- }, |
- onDone: () { |
- print("Connection closed by coverage target"); |
- cleanup(); |
- }, |
- onError: (e) { |
- print("Error '$e' detected in input stream from coverage target"); |
- cleanup(); |
- }); |
- }, |
- onError: (e, trace) { |
- String msg = "Error while connecting to coverage target: $e"; |
- if (trace != null) msg += "\nStackTrace: $trace"; |
- error(msg); |
- cleanup(); |
- }); |
- } |
- |
- void cleanup() { |
- if (cleanupDone) return; |
- if (socket != null) { |
- socket.close().catchError((error) { |
- // Print this directly in addition to adding it to the |
- // error message queue, in case the error message queue |
- // gets printed before this error handler is called. |
- print("Error occurred while closing socket: $error"); |
- error("Error while closing socket: $error"); |
- }); |
- } |
- var targetPid = targetProcess.pid; |
- print("Sending kill signal to process $targetPid."); |
- targetProcess.kill(); |
- // If the process was already dead exitCode is already |
- // available and we call exit() in the next event loop cycle. |
- // Otherwise this will wait for the process to exit. |
- |
- targetProcess.exitCode.then((exitCode) { |
- print("Process $targetPid terminated with exit code $exitCode."); |
- if (errorsDetected) { |
- print("\n===== Errors detected: ====="); |
- for (int i = 0; i < errors.length; i++) print(errors[i]); |
- print("============================\n"); |
- } |
- Program.printCoverage(); |
- exit(errors.length); |
- }); |
- cleanupDone = true; |
- } |
-} |
- |
- |
-// Class to buffer wire protocol data from coverage target and |
-// break it down to individual json messages. |
-class JsonBuffer { |
- String buffer = null; |
- |
- append(String s) { |
- if (buffer == null || buffer.length == 0) { |
- buffer = s; |
- } else { |
- buffer = buffer + s; |
- } |
- } |
- |
- String getNextMessage() { |
- if (buffer == null) return null; |
- int msgLen = objectLength(); |
- if (msgLen == 0) return null; |
- String msg = null; |
- if (msgLen == buffer.length) { |
- msg = buffer; |
- buffer = null; |
- } else { |
- assert(msgLen < buffer.length); |
- msg = buffer.substring(0, msgLen); |
- buffer = buffer.substring(msgLen); |
- } |
- return msg; |
- } |
- |
- bool haveGarbage() { |
- if (buffer == null || buffer.length == 0) return false; |
- var i = 0, char = " "; |
- while (i < buffer.length) { |
- char = buffer[i]; |
- if (char != " " && char != "\n" && char != "\r" && char != "\t") break; |
- i++; |
- } |
- if (i >= buffer.length) { |
- return false; |
- } else { |
- return char != "{"; |
- } |
- } |
- |
- // Returns the character length of the next json message in the |
- // buffer, or 0 if there is only a partial message in the buffer. |
- // The object value must start with '{' and continues to the |
- // matching '}'. No attempt is made to otherwise validate the contents |
- // as JSON. If it is invalid, a later JSON.decode() will fail. |
- int objectLength() { |
- int skipWhitespace(int index) { |
- while (index < buffer.length) { |
- String char = buffer[index]; |
- if (char != " " && char != "\n" && char != "\r" && char != "\t") break; |
- index++; |
- } |
- return index; |
- } |
- int skipString(int index) { |
- assert(buffer[index - 1] == '"'); |
- while (index < buffer.length) { |
- String char = buffer[index]; |
- if (char == '"') return index + 1; |
- if (char == r'\') index++; |
- if (index == buffer.length) return index; |
- index++; |
- } |
- return index; |
- } |
- int index = 0; |
- index = skipWhitespace(index); |
- // Bail out if the first non-whitespace character isn't '{'. |
- if (index == buffer.length || buffer[index] != '{') return 0; |
- int nesting = 0; |
- while (index < buffer.length) { |
- String char = buffer[index++]; |
- if (char == '{') { |
- nesting++; |
- } else if (char == '}') { |
- nesting--; |
- if (nesting == 0) return index; |
- } else if (char == '"') { |
- // Strings can contain braces. Skip their content. |
- index = skipString(index); |
- } |
- } |
- return 0; |
- } |
-} |
- |
- |
-void main(List<String> arguments) { |
- var targetOpts = [ "--debug:0" ]; |
- for (String str in arguments) { |
- switch (str) { |
- case "--verbose": |
- showDebuggeeOutput = true; |
- break; |
- case "--wire": |
- verboseWire = true; |
- break; |
- default: |
- targetOpts.add(str); |
- break; |
- } |
- } |
- |
- Process.start(Platform.executable, targetOpts).then((Process process) { |
- process.stdin.close(); |
- debugger = new Debugger(process); |
- }); |
-} |