| 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);
|
| - });
|
| -}
|
|
|