diff options
Diffstat (limited to 'js/src/jit-test/tests/async/debugger-reject-after-fulfill.js')
-rw-r--r-- | js/src/jit-test/tests/async/debugger-reject-after-fulfill.js | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/async/debugger-reject-after-fulfill.js b/js/src/jit-test/tests/async/debugger-reject-after-fulfill.js new file mode 100644 index 0000000000..6f147e3705 --- /dev/null +++ b/js/src/jit-test/tests/async/debugger-reject-after-fulfill.js @@ -0,0 +1,229 @@ +// Throwing error after resolving async function's promise should not +// overwrite the promise's state or value/reason. +// This situation can happen either with debugger interaction or OOM. + +// This testcase relies on the fact that there's a breakpoint after resolving +// the async function's promise, before leaving the async function's frame. +// This function searches for the last breakpoint before leaving the frame. +// +// - `declCode` should declare an async function `f`, and the function should +// set global variable `returning` to `true` just before return +// - `callCode` should call the function `f` and make sure the function's +// execution reaches the last breakpoint +function searchLastBreakpointBeforeReturn(declCode, callCode) { + const g = newGlobal({ newCompartment: true }); + const dbg = new Debugger(g); + g.eval(declCode); + + let hit = false; + let offset = 0; + dbg.onEnterFrame = function(frame) { + if (frame.callee && frame.callee.name == "f") { + frame.onStep = () => { + if (!g.returning) { + return undefined; + } + + offset = frame.offset; + return undefined; + }; + } + }; + + g.eval(callCode); + + drainJobQueue(); + + assertEq(offset != 0, true); + + return offset; +} + +function testWithoutAwait() { + const declCode = ` + var returning = false; + async function f() { + return (returning = true, "expected"); + }; + `; + + const callCode = ` + var p = f(); + `; + + const offset = searchLastBreakpointBeforeReturn(declCode, callCode); + + const g = newGlobal({ newCompartment: true }); + const dbg = new Debugger(g); + g.eval(declCode); + + let onPromiseSettledCount = 0; + dbg.onPromiseSettled = function(promise) { + onPromiseSettledCount++; + // No promise should be rejected. + assertEq(promise.promiseState, "fulfilled"); + + // Async function's promise should have expected value. + if (onPromiseSettledCount == 1) { + assertEq(promise.promiseValue, "expected"); + } + }; + + let hitBreakpoint = false; + dbg.onEnterFrame = function(frame) { + if (frame.callee && frame.callee.name == "f") { + dbg.onEnterFrame = undefined; + frame.script.setBreakpoint(offset, { + hit() { + hitBreakpoint = true; + return { throw: "unexpected" }; + } + }); + } + }; + + enableLastWarning(); + + g.eval(` + var fulfilledValue; + var rejected = false; + `); + + g.eval(callCode); + + // The execution reaches to the last breakpoint without running job queue. + assertEq(hitBreakpoint, true); + + const warn = getLastWarning(); + assertEq(warn.message, + "unhandlable error after resolving async function's promise"); + clearLastWarning(); + + // Add reaction handler after resolution. + // This handler's job will be enqueued immediately. + g.eval(` + p.then(x => { + fulfilledValue = x; + }, e => { + rejected = true; + }); + `); + + // Run the above handler. + drainJobQueue(); + + assertEq(g.fulfilledValue, "expected"); + assertEq(onPromiseSettledCount >= 1, true); +} + +function testWithAwait() { + const declCode = ` + var resolve; + var p = new Promise(r => { resolve = r }); + var returning = false; + async function f() { + await p; + return (returning = true, "expected"); + }; + `; + + const callCode = ` + var p = f(); + `; + + const resolveCode = ` + resolve("resolve"); + `; + + const offset = searchLastBreakpointBeforeReturn(declCode, + callCode + resolveCode); + + const g = newGlobal({newCompartment: true}); + const dbg = new Debugger(g); + g.eval(declCode); + + let onPromiseSettledCount = 0; + dbg.onPromiseSettled = function(promise) { + onPromiseSettledCount++; + + // No promise should be rejected. + assertEq(promise.promiseState, "fulfilled"); + + // Async function's promise should have expected value. + if (onPromiseSettledCount == 2) { + assertEq(promise.promiseValue, "expected"); + } + }; + + let hitBreakpoint = false; + dbg.onEnterFrame = function(frame) { + if (frame.callee && frame.callee.name == "f") { + dbg.onEnterFrame = undefined; + frame.script.setBreakpoint(offset, { + hit() { + hitBreakpoint = true; + return { throw: "unexpected" }; + } + }); + } + }; + + enableLastWarning(); + + g.eval(` + var fulfilledValue1; + var fulfilledValue2; + var rejected = false; + `); + + g.eval(callCode); + + assertEq(getLastWarning(), null); + + // Add reaction handler before resolution. + // This handler's job will be enqueued when `p` is resolved. + g.eval(` + p.then(x => { + fulfilledValue1 = x; + }, e => { + rejected = true; + }); + `); + + g.eval(resolveCode); + + // Run the remaining part of async function, and the above handler. + drainJobQueue(); + + // The execution reaches to the last breakpoint after running job queue for + // resolving `p`. + assertEq(hitBreakpoint, true); + + const warn = getLastWarning(); + assertEq(warn.message, + "unhandlable error after resolving async function's promise"); + clearLastWarning(); + + assertEq(g.fulfilledValue1, "expected"); + assertEq(g.rejected, false); + + // Add reaction handler after resolution. + // This handler's job will be enqueued immediately. + g.eval(` + p.then(x => { + fulfilledValue2 = x; + }, e => { + rejected = true; + }); + `); + + // Run the above handler. + drainJobQueue(); + + assertEq(g.fulfilledValue2, "expected"); + assertEq(g.rejected, false); + assertEq(onPromiseSettledCount >= 3, true); +} + +testWithoutAwait(); +testWithAwait(); |