diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /toolkit/components/asyncshutdown/tests/xpcshell/test_AsyncShutdown.js | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/asyncshutdown/tests/xpcshell/test_AsyncShutdown.js')
-rw-r--r-- | toolkit/components/asyncshutdown/tests/xpcshell/test_AsyncShutdown.js | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/toolkit/components/asyncshutdown/tests/xpcshell/test_AsyncShutdown.js b/toolkit/components/asyncshutdown/tests/xpcshell/test_AsyncShutdown.js new file mode 100644 index 0000000000..50534c7e3c --- /dev/null +++ b/toolkit/components/asyncshutdown/tests/xpcshell/test_AsyncShutdown.js @@ -0,0 +1,348 @@ +/* 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"); +}); |