/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; add_task(async function test_no_condition() { for (let kind of [ "phase", "barrier", "xpcom-barrier", "xpcom-barrier-unwrapped", ]) { info("Testing a barrier with no condition (" + kind + ")"); let lock = makeLock(kind); await lock.wait(); info("Barrier with no condition didn't lock"); } }); add_task(async function test_phase_various_failures() { for (let kind of [ "phase", "barrier", "xpcom-barrier", "xpcom-barrier-unwrapped", ]) { info("Kind: " + kind); // Testing with wrong arguments let lock = makeLock(kind); Assert.throws( () => lock.addBlocker(), /TypeError|NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS/ ); Assert.throws( () => lock.addBlocker(null, true), /TypeError|NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS/ ); if (kind != "xpcom-barrier") { // xpcom-barrier actually expects a string in that position Assert.throws( () => lock.addBlocker("Test 2", () => true, "not a function"), /TypeError/ ); } if (kind == "xpcom-barrier") { const blocker = () => true; lock.addBlocker("Test 3", blocker); Assert.throws( () => lock.addBlocker("Test 3", blocker), /We have already registered the blocker \(Test 3\)/ ); } // Attempting to add a blocker after we are done waiting Assert.ok(!lock.isClosed, "Barrier is open"); await lock.wait(); Assert.throws(() => lock.addBlocker("Test 4", () => true), /is finished/); Assert.ok(lock.isClosed, "Barrier is closed"); } }); add_task(async function test_reentrant() { info("Ensure that we can call addBlocker from within a blocker"); for (let kind of [ "phase", "barrier", "xpcom-barrier", "xpcom-barrier-unwrapped", ]) { info("Kind: " + kind); let lock = makeLock(kind); let deferredOuter = PromiseUtils.defer(); let deferredInner = PromiseUtils.defer(); let deferredBlockInner = PromiseUtils.defer(); lock.addBlocker("Outer blocker", () => { info("Entering outer blocker"); deferredOuter.resolve(); lock.addBlocker("Inner blocker", () => { info("Entering inner blocker"); deferredInner.resolve(); return deferredBlockInner.promise; }); }); // Note that phase-style locks spin the event loop and do not return from // `lock.wait()` until after all blockers have been resolved. Therefore, // to be able to test them, we need to dispatch the following steps to the // event loop before calling `lock.wait()`, which we do by forcing // a Promise.resolve(). // let promiseSteps = (async function () { await Promise.resolve(); info("Waiting until we have entered the outer blocker"); await deferredOuter.promise; info("Waiting until we have entered the inner blocker"); await deferredInner.promise; info("Allowing the lock to resolve"); deferredBlockInner.resolve(); })(); info("Starting wait"); await lock.wait(); info("Waiting until all steps have been walked"); await promiseSteps; } }); add_task(async function test_phase_removeBlocker() { info( "Testing that we can call removeBlocker before, during and after the call to wait()" ); for (let kind of [ "phase", "barrier", "xpcom-barrier", "xpcom-barrier-unwrapped", ]) { info("Switching to kind " + kind); info("Attempt to add then remove a blocker before wait()"); let lock = makeLock(kind); let blocker = () => { info("This promise will never be resolved"); return PromiseUtils.defer().promise; }; lock.addBlocker("Wait forever", blocker); let do_remove_blocker = function (aLock, aBlocker, aShouldRemove) { info( "Attempting to remove blocker " + aBlocker + ", expecting result " + aShouldRemove ); if (kind == "xpcom-barrier") { // The xpcom variant always returns `undefined`, so we can't // check its result. aLock.removeBlocker(aBlocker); return; } Assert.equal(aLock.removeBlocker(aBlocker), aShouldRemove); }; do_remove_blocker(lock, blocker, true); do_remove_blocker(lock, blocker, false); info("Attempt to remove non-registered blockers before wait()"); do_remove_blocker(lock, "foo", false); do_remove_blocker(lock, null, false); info("Waiting (should lift immediately)"); await lock.wait(); info("Attempt to add a blocker then remove it during wait()"); lock = makeLock(kind); let blockers = [ () => { info("This blocker will self-destruct"); do_remove_blocker(lock, blockers[0], true); return PromiseUtils.defer().promise; }, () => { info("This blocker will self-destruct twice"); do_remove_blocker(lock, blockers[1], true); do_remove_blocker(lock, blockers[1], false); return PromiseUtils.defer().promise; }, () => { info("Attempt to remove non-registered blockers during wait()"); do_remove_blocker(lock, "foo", false); do_remove_blocker(lock, null, false); }, ]; for (let i in blockers) { lock.addBlocker("Wait forever again: " + i, blockers[i]); } info("Waiting (should lift very quickly)"); await lock.wait(); do_remove_blocker(lock, blockers[0], false); info("Attempt to remove a blocker after wait"); lock = makeLock(kind); blocker = Promise.resolve.bind(Promise); await lock.wait(); do_remove_blocker(lock, blocker, false); info("Attempt to remove non-registered blocker after wait()"); do_remove_blocker(lock, "foo", false); do_remove_blocker(lock, null, false); } }); add_task(async function test_addBlocker_noDistinctNamesConstraint() { info("Testing that we can add two distinct blockers with identical names"); for (let kind of [ "phase", "barrier", "xpcom-barrier", "xpcom-barrier-unwrapped", ]) { info("Switching to kind " + kind); let lock = makeLock(kind); let deferred1 = PromiseUtils.defer(); let resolved1 = false; let deferred2 = PromiseUtils.defer(); let resolved2 = false; let blocker1 = () => { info("Entering blocker1"); return deferred1.promise; }; let blocker2 = () => { info("Entering blocker2"); return deferred2.promise; }; info("Attempt to add two distinct blockers with identical names"); lock.addBlocker("Blocker", blocker1); lock.addBlocker("Blocker", blocker2); // Note that phase-style locks spin the event loop and do not return from // `lock.wait()` until after all blockers have been resolved. Therefore, // to be able to test them, we need to dispatch the following steps to the // event loop before calling `lock.wait()`, which we do by forcing // a Promise.resolve(). // let promiseSteps = (async () => { info("Waiting for an event-loop spin"); await Promise.resolve(); info("Resolving blocker1"); deferred1.resolve(); resolved1 = true; info("Waiting for an event-loop spin"); await Promise.resolve(); info("Resolving blocker2"); deferred2.resolve(); resolved2 = true; })(); info("Waiting for lock"); await lock.wait(); Assert.ok(resolved1); Assert.ok(resolved2); await promiseSteps; } }); add_task(async function test_state() { info("Testing information contained in `state`"); let BLOCKER_NAME = "test_state blocker " + Math.random(); // Set up the barrier. Note that we cannot test `barrier.state` // immediately, as it initially contains "Not started" let barrier = new AsyncShutdown.Barrier("test_filename"); let deferred = PromiseUtils.defer(); let { filename, lineNumber } = Components.stack; barrier.client.addBlocker(BLOCKER_NAME, function () { return deferred.promise; }); let promiseDone = barrier.wait(); // Now that we have called `wait()`, the state contains interesting things info("State: " + JSON.stringify(barrier.state, null, "\t")); let state = barrier.state[0]; Assert.equal(state.filename, filename); Assert.equal(state.lineNumber, lineNumber + 1); Assert.equal(state.name, BLOCKER_NAME); Assert.ok( state.stack.some(x => x.includes("test_state")), "The stack contains the caller function's name" ); Assert.ok( state.stack.some(x => x.includes(filename)), "The stack contains the calling file's name" ); deferred.resolve(); await promiseDone; }); add_task(async function test_multistate() { info("Testing information contained in multiple `state`"); let BLOCKER_NAMES = [ "test_state blocker " + Math.random(), "test_state blocker " + Math.random(), ]; // Set up the barrier. Note that we cannot test `barrier.state` // immediately, as it initially contains "Not started" let barrier = asyncShutdownService.makeBarrier("test_filename"); let deferred = PromiseUtils.defer(); let { filename, lineNumber } = Components.stack; for (let name of BLOCKER_NAMES) { barrier.client.jsclient.addBlocker(name, () => deferred.promise, { fetchState: () => ({ progress: name }), }); } let promiseDone = new Promise(r => barrier.wait(r)); // Now that we have called `wait()`, the state contains interesting things. Assert.ok( barrier.state instanceof Ci.nsIPropertyBag, "State is a PropertyBag" ); for (let i = 0; i < BLOCKER_NAMES.length; ++i) { let state = barrier.state.getProperty(i.toString()); Assert.equal(typeof state, "string", "state is a string"); info("State: " + state + "\t"); state = JSON.parse(state); Assert.equal(state.filename, filename); Assert.equal(state.lineNumber, lineNumber + 2); Assert.equal(state.name, BLOCKER_NAMES[i]); Assert.ok( state.stack.some(x => x.includes("test_multistate")), "The stack contains the caller function's name" ); Assert.ok( state.stack.some(x => x.includes(filename)), "The stack contains the calling file's name" ); Assert.equal( state.state.progress, BLOCKER_NAMES[i], "The state contains the fetchState provided value" ); } deferred.resolve(); await promiseDone; }); add_task(async function () { Services.prefs.clearUserPref("toolkit.asyncshutdown.testing"); });