michael@0: // Comprehensive test of get/setVariable on many kinds of environments and michael@0: // bindings. michael@0: michael@0: load(libdir + "asserts.js"); michael@0: michael@0: var cases = [ michael@0: // global bindings and bindings on the global prototype chain michael@0: "x = VAL; @@", michael@0: "var x = VAL; @@", michael@0: "Object.prototype.x = VAL; @@", michael@0: michael@0: // let, catch, and comprehension bindings michael@0: "let x = VAL; @@", michael@0: "{ let x = VAL; @@ }", michael@0: "let (x = VAL) { @@ }", michael@0: "try { throw VAL; } catch (x) { @@ }", michael@0: "try { throw VAL; } catch (x) { @@ }", michael@0: "for (let x of [VAL]) { @@ }", michael@0: "for each (let x in [VAL]) { @@ }", michael@0: "switch (0) { default: let x = VAL; @@ }", michael@0: "[function () { @@ }() for (x of [VAL])];", michael@0: // "((function () { @@ })() for (x of [VAL])).next();", // bug 709367 michael@0: michael@0: // arguments michael@0: "function f(x) { @@ } f(VAL);", michael@0: "function f([w, x]) { @@ } f([0, VAL]);", michael@0: "function f({v: x}) { @@ } f({v: VAL});", michael@0: "function f([w, {v: x}]) { @@ } f([0, {v: VAL}]);", michael@0: michael@0: // bindings in functions michael@0: "function f() { var x = VAL; @@ } f();", michael@0: "function f() { let x = VAL; @@ } f();", michael@0: "function f([x]) { let x = VAL; @@ } f(['fail']);", michael@0: "function f(x) { { let x = VAL; @@ } } f('fail');", michael@0: "function f() { function x() {} x = VAL; @@ } f();", michael@0: michael@0: // dynamic bindings michael@0: "function f(s) { eval(s); @@ } f('var x = VAL');", michael@0: "function f(s) { let (x = 'fail') { eval(s); } x = VAL; @@ } f('var x;');", michael@0: "var x = VAL; function f(s) { eval('var x = 0;'); eval(s); @@ } f('delete x;');", michael@0: "function f(obj) { with (obj) { @@ } } f({x: VAL});", michael@0: "function f(obj) { with (obj) { @@ } } f(Object.create({x: VAL}));", michael@0: "function f(b) { if (b) { function x(){} } x = VAL; @@ } f(1);", michael@0: ]; michael@0: michael@0: var nextval = 1000; michael@0: michael@0: function test(code, debugStmts, followupStmts) { michael@0: var val = nextval++; michael@0: var hits = 0; michael@0: michael@0: var g = newGlobal(); michael@0: g.eval("function debugMe() { var x = 'wrong-x'; debugger; }"); michael@0: g.capture = null; michael@0: michael@0: var dbg = Debugger(g); michael@0: dbg.onDebuggerStatement = function (frame) { michael@0: if (frame.callee !== null && frame.callee.name == 'debugMe') michael@0: frame = frame.older; michael@0: var env = frame.environment.find("x"); michael@0: assertEq(env.getVariable("x"), val) michael@0: assertEq(env.setVariable("x", 'ok'), undefined); michael@0: assertEq(env.getVariable("x"), 'ok'); michael@0: michael@0: // setVariable cannot create new variables. michael@0: assertThrowsInstanceOf(function () { env.setVariable("newVar", 0); }, TypeError); michael@0: hits++; michael@0: }; michael@0: michael@0: code = code.replace("@@", debugStmts); michael@0: if (followupStmts !== undefined) michael@0: code += " " + followupStmts; michael@0: code = code.replace(/VAL/g, uneval(val)); michael@0: g.eval(code); michael@0: assertEq(hits, 1); michael@0: } michael@0: michael@0: for (var s of cases) { michael@0: // Test triggering the debugger right in the scope in which x is bound. michael@0: test(s, "debugger; assertEq(x, 'ok');"); michael@0: michael@0: // Test calling a function that triggers the debugger. michael@0: test(s, "debugMe(); assertEq(x, 'ok');"); michael@0: michael@0: // Test triggering the debugger from a scope nested in x's scope. michael@0: test(s, "let (y = 'irrelevant') { (function (z) { let (zz = y) { debugger; }})(); } assertEq(x, 'ok');"), michael@0: michael@0: // Test closing over the variable and triggering the debugger later, after michael@0: // leaving the variable's scope. michael@0: test(s, "capture = {dbg: function () { debugger; }, get x() { return x; }};", michael@0: "assertEq(capture.x, VAL); capture.dbg(); assertEq(capture.x, 'ok');"); michael@0: }