// Copyright 2008 Google Inc. All Rights Reserved. // test_protocol.js /** * @fileoverview Unit tests for testing debugger protocol betweeen front-end JS * and back-end. * Run with the following command line: * v8_shell_sample.exe --allow-natives-syntax --expose-debug-as debugContext * chrome/browser/resources/shell.js * v8/tests/mjsunit.js * chrome/test/debugger/test_protocol.js */ /** * shell() is normally a native function exposed to shell.js in Chrome that * sets the global DebugShell (shell_) object used for the debugger front-end. */ function shell(sh) { shell_ = sh; }; /** * @class The global chrome object has some functions to control some status UI * in the debugger window. Stub them out here. */ function chrome() { }; chrome.setDebuggerReady = function(ready) {}; chrome.setDebuggerBreak = function(brk) {}; /** * @constructor a pseudo namespace to wrap the various functions and data for * the test */ function DebuggerTest() { }; /** * initialize the member */ DebuggerTest.initialize = function() { DebuggerTest.pendingCommand = null; DebuggerTest.output = []; // swap out the built-in print with our own so that we can verify output DebuggerTest.realPrint = print; print = DebuggerTest.print; // uncomment this to see more verbose information //dprint = DebuggerTest.realPrint; debugContext.Debug.addListener(DebuggerTest.listener); }; /** * Collects calls to print() in an array for test verification. */ DebuggerTest.print = function(str) { DebuggerTest.output.push(str); // uncomment this if you need to trace what's happening while it's happening // rather than waiting for the end //DebuggerTest.realPrint(str); }; /** * Processes pendingCommand and sends response to shell_. Since that may in * turn generate a new pendingCommand, repeat this until there is no * pendingCommand. */ DebuggerTest.processCommands = function(dcp) { while (DebuggerTest.pendingCommand) { var json = DebuggerTest.pendingCommand; DebuggerTest.pendingCommand = null; var result = dcp.processDebugJSONRequest(json); shell_.response(result); } }; /** * Handles DebugEvents from the Debug object. Hooked in via addListener above. */ DebuggerTest.listener = function(event, exec_state, event_data, data) { try { if (event == debugContext.Debug.DebugEvent.Break) { var dcp = exec_state.debugCommandProcessor(); // process any pending commands prior to handling the breakpoint DebuggerTest.processCommands(dcp); var json = event_data.toJSONProtocol(); shell_.response(json); // response() may have added another command to process DebuggerTest.processCommands(dcp); } } catch(e) { print(e); } }; /** * Send the next command from the command-list. */ DebuggerTest.sendNextCommand = function() { var cmd = DebuggerTest.commandList.shift(); print("$ " + cmd); shell_.command(cmd); }; /** * Verify that the actual output matches the expected output * depends on mjsunit */ DebuggerTest.verifyOutput = function() { // restore print since mjsunit depends on it print = DebuggerTest.realPrint; var out = DebuggerTest.output; var expected = DebuggerTest.expectedOutput; if (out.length != expected.length) { assertTrue(out.length == expected.length, "length mismatch: " + out.length + " == " + expected.length); } else { var succeeded = true; for (var i in out) { // match the front of the string so we can avoid testing changes in frames // that are in the test harness if (out[i].indexOf(expected[i]) != 0) { assertTrue(out[i] == expected[i], "actual '" + out[i] + "' == " + "expected '" + expected[i] + "'"); succeeded = false; break; } } if (succeeded) print("Success"); } // useful for generating a new version of DebuggerTest.expectedOutput for (var i in DebuggerTest.output) { //print(" \"" + DebuggerTest.output[i] + "\","); } }; /** * @class DebugShell is passed a "Tab" object that it uses to communicate with * the debugger. This mock simulates that. * @param {string} title */ DebuggerTest.TabMock = function(title) { this.title = title; this.attach = function() {}; this.debugBreak = function() { // TODO(erikkay) }; this.sendToDebugger = function(str) { DebuggerTest.pendingCommand = str; }; }; /** * @class Uses prototype chaining to allow DebugShell methods to be overridden * selectively. */ function DebugShellOverrides() { this._origPrototype = DebugShell.prototype; }; DebugShellOverrides.prototype = DebugShell.prototype; DebugShell.prototype = new DebugShellOverrides; /** * Overrides DebugShell.prototype.response so that we can log the responses * and trigger the next command to be processed. */ DebugShell.prototype.response = function(str) { var msg = eval('(' + str + ')'); print("< " + msg.type + ":" + (msg.command || msg.event)); var sendAnother = (msg.type == "event"); if (!sendAnother && shell_.current_command) { sendAnother = (shell_.current_command.from_user && msg.type == "response" && msg.command != "continue") } this._origPrototype.response.call(this, str) // Send the next command, but only if the program is paused (a continue // command response means that we're about to be running again) and the // command we just processed isn't a continue if (!this.running && sendAnother) DebuggerTest.sendNextCommand(); }; // The list of commands to be processed // TODO(erikkay): this doesn't test the full set of debugger commands yet, // but it should be enough to verify that the protocol is still working. DebuggerTest.commandList = [ "next", "step", "backtrace", "source", "print x", "args", "locals", "frame 1", "stepout", "continue" ]; DebuggerTest.expectedOutput = [ "< event:break", "g(), foo.html", "60: debugger;", "$ next", "< response:continue", "< event:break", "61: f(1);", "$ step", "< response:continue", "< event:break", "f(x=1, y=undefined), foo.html", "29: return a;", "$ backtrace", "< response:backtrace", "Frames #0 to #3 of 4:", "#00 f(x=1, y=undefined) foo.html line 29 column 3 (position 62)", "#01 g() foo.html line 61 column 3 (position 30)", "#02 function DebuggerTest()", // prefix "#03 [anonymous]()", // prefix "$ source", "< response:source", "24: function f(x, y) {", "25: var a=1;", "26: if (x == 1) {", "27: a++;", "28: }", ">>>> return a;", "30: };", "$ print x", "< response:evaluate", "1", "$ args", "< response:frame", "x = 1", "y = undefined", "$ locals", "< response:frame", "a = 2", "$ frame 1", "< response:frame", "#01 g, foo.html", "61: f(1);", "$ stepout", "< response:continue", "< event:break", "g(), foo.html", "62: };", "$ continue", "< response:continue" ]; /** * Tests the debugger and protocol for a couple of simple scripts. */ DebuggerTest.testProtocol = function() { DebuggerTest.initialize(); // Startup the front-end. debug(new DebuggerTest.TabMock("testing")); script1 = "function f(x, y) {\n" + // line 23 " var a=1;\n" + // line 24 " if (x == 1) {\n" + // line 25 " a++;\n }\n" + // line 26 " return a;\n" + // line 27 "};"; // line 28 script2 = "function g() {\n" + // line 58 " debugger;\n" + // line 59 " f(1);\n" + // line 60 "};"; // line 61 %CompileScript(script1, "foo.html", 23, 0)(); %CompileScript(script2, "foo.html", 58, 0)(); try { g(); } catch(e) { print(e); } DebuggerTest.verifyOutput(); } DebuggerTest.testProtocol();