forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			348 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			348 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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 = Promise.withResolvers();
 | |
|     let deferredInner = Promise.withResolvers();
 | |
|     let deferredBlockInner = Promise.withResolvers();
 | |
| 
 | |
|     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 Promise.withResolvers().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 Promise.withResolvers().promise;
 | |
|       },
 | |
|       () => {
 | |
|         info("This blocker will self-destruct twice");
 | |
|         do_remove_blocker(lock, blockers[1], true);
 | |
|         do_remove_blocker(lock, blockers[1], false);
 | |
|         return Promise.withResolvers().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 = Promise.withResolvers();
 | |
|     let resolved1 = false;
 | |
|     let deferred2 = Promise.withResolvers();
 | |
|     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 = Promise.withResolvers();
 | |
|   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 = Promise.withResolvers();
 | |
|   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");
 | |
| });
 | 
