diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a7af027..fc5910e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,7 +26,7 @@ jobs: - name: Type check run: deno task check - test: + test-deno: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -45,6 +45,37 @@ jobs: files: ./coverage.lcov token: ${{ secrets.CODECOV_TOKEN }} + test-node: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v1 + with: + version: 22.x + - name: Install deps + run: | + npx jsr install + - name: Test + run: | + npx --yes tsx --test *_test.ts + timeout-minutes: 5 + + test-bun: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: antongolub/action-setup-bun@v1 + with: + bun-version: v1.x # Uses latest bun 1 + - name: Install deps + run: | + bun install + - name: Test + run: | + # https://github.com/cross-org/test/issues/1 + ls -v1 *_test.ts | xargs -n1 bun test '{}' + timeout-minutes: 5 + jsr-publish: runs-on: ubuntu-latest steps: diff --git a/.gitignore b/.gitignore index 519c448..f45dd64 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +/node_modules /.tools /.deno diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..41583e3 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@jsr:registry=https://npm.jsr.io diff --git a/_testutil.ts b/_testutil.ts new file mode 100644 index 0000000..109c4ec --- /dev/null +++ b/_testutil.ts @@ -0,0 +1,17 @@ +// Using `deadline` in `@std/async@1.0.2` cause the following error: +// 'Promise resolution is still pending but the event loop has already resolved' +// So we need to implement `deadline` by ourselves. +export async function deadline( + promise: Promise, + timeout: number, +): Promise { + const waiter = Promise.withResolvers(); + const timer = setTimeout( + () => waiter.reject(new DOMException("Signal timed out.")), + timeout, + ); + return await Promise.race([ + waiter.promise, + promise.finally(() => clearTimeout(timer)), + ]); +} diff --git a/async_value_test.ts b/async_value_test.ts index 327c614..73d767e 100644 --- a/async_value_test.ts +++ b/async_value_test.ts @@ -1,14 +1,13 @@ +import { test } from "@cross/test"; import { assertEquals } from "@std/assert"; import { AsyncValue } from "./async_value.ts"; -Deno.test("AsyncValue", async (t) => { - await t.step( - "'get' returns a promise that resolves to the value set by 'set'", - async () => { - const v = new AsyncValue(0); - assertEquals(await v.get(), 0); - await v.set(1); - assertEquals(await v.get(), 1); - }, - ); -}); +test( + "AsyncValue 'get' returns a promise that resolves to the value set by 'set'", + async () => { + const v = new AsyncValue(0); + assertEquals(await v.get(), 0); + await v.set(1); + assertEquals(await v.get(), 1); + }, +); diff --git a/barrier_test.ts b/barrier_test.ts index 22d0275..1e9ba2f 100644 --- a/barrier_test.ts +++ b/barrier_test.ts @@ -1,94 +1,94 @@ +import { test } from "@cross/test"; import { assertEquals, assertRejects, assertThrows } from "@std/assert"; -import { deadline, delay } from "@std/async"; +import { delay } from "@std/async"; import { Barrier } from "./barrier.ts"; +import { deadline } from "./_testutil.ts"; -Deno.test("Barrier", async (t) => { - await t.step( - "'wait' waits until the number of waiters reached the size specified to the barrier", - async () => { - const barrier = new Barrier(5); - const workers = []; - const results: string[] = []; - for (let i = 0; i < 5; i++) { - workers.push((async () => { - results.push(`before wait ${i}`); - await barrier.wait(); - results.push(`after wait ${i}`); - })()); - } - await Promise.all(workers); - assertEquals(results, [ - "before wait 0", - "before wait 1", - "before wait 2", - "before wait 3", - "before wait 4", - "after wait 0", - "after wait 1", - "after wait 2", - "after wait 3", - "after wait 4", - ]); - }, - ); +test( + "Barrier 'wait' waits until the number of waiters reached the size specified to the barrier", + async () => { + const barrier = new Barrier(5); + const workers = []; + const results: string[] = []; + for (let i = 0; i < 5; i++) { + workers.push((async () => { + results.push(`before wait ${i}`); + await barrier.wait(); + results.push(`after wait ${i}`); + })()); + } + await Promise.all(workers); + assertEquals(results, [ + "before wait 0", + "before wait 1", + "before wait 2", + "before wait 3", + "before wait 4", + "after wait 0", + "after wait 1", + "after wait 2", + "after wait 3", + "after wait 4", + ]); + }, +); - await t.step( - "'wait' with non-aborted signal", - async () => { - const controller = new AbortController(); - const barrier = new Barrier(2); +test( + "Barrier 'wait' with non-aborted signal", + async () => { + const controller = new AbortController(); + const barrier = new Barrier(2); - await assertRejects( - () => deadline(barrier.wait({ signal: controller.signal }), 100), - DOMException, - "Signal timed out.", - ); - }, - ); + await assertRejects( + () => deadline(barrier.wait({ signal: controller.signal }), 100), + DOMException, + "Signal timed out.", + ); + }, +); - await t.step( - "'wait' with signal aborted after delay", - async () => { - const controller = new AbortController(); - const barrier = new Barrier(2); - const reason = new Error("Aborted"); +test( + "Barrier 'wait' with signal aborted after delay", + async () => { + const controller = new AbortController(); + const barrier = new Barrier(2); + const reason = new Error("Aborted"); - delay(50).then(() => controller.abort(reason)); + delay(50).then(() => controller.abort(reason)); - await assertRejects( - () => deadline(barrier.wait({ signal: controller.signal }), 100), - Error, - "Aborted", - ); - }, - ); + await assertRejects( + () => deadline(barrier.wait({ signal: controller.signal }), 100), + Error, + "Aborted", + ); + }, +); - await t.step( - "'wait' with already aborted signal", - async () => { - const controller = new AbortController(); - const barrier = new Barrier(2); - const reason = new Error("Aborted"); +test( + "Barrier 'wait' with already aborted signal", + async () => { + const controller = new AbortController(); + const barrier = new Barrier(2); + const reason = new Error("Aborted"); - controller.abort(reason); + controller.abort(reason); - await assertRejects( - () => deadline(barrier.wait({ signal: controller.signal }), 100), - Error, - "Aborted", - ); - }, - ); + await assertRejects( + () => deadline(barrier.wait({ signal: controller.signal }), 100), + Error, + "Aborted", + ); + }, +); - await t.step( - "throws RangeError if size is not a positive safe integer", - () => { - assertThrows(() => new Barrier(NaN), RangeError); - assertThrows(() => new Barrier(Infinity), RangeError); - assertThrows(() => new Barrier(-Infinity), RangeError); - assertThrows(() => new Barrier(-1), RangeError); - assertThrows(() => new Barrier(1.1), RangeError); - assertThrows(() => new Barrier(0), RangeError); - }, - ); -}); +test( + "Barrier throws RangeError if size is not a positive safe integer", + () => { + assertThrows(() => new Barrier(NaN), RangeError); + assertThrows(() => new Barrier(Infinity), RangeError); + assertThrows(() => new Barrier(-Infinity), RangeError); + assertThrows(() => new Barrier(-1), RangeError); + assertThrows(() => new Barrier(1.1), RangeError); + assertThrows(() => new Barrier(0), RangeError); + }, +); diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..a6c7de4 Binary files /dev/null and b/bun.lockb differ diff --git a/deno.jsonc b/deno.jsonc index 801b3d2..ae1868d 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -43,6 +43,7 @@ "@core/asyncutil/stack": "./stack.ts", "@core/asyncutil/wait-group": "./wait_group.ts", "@core/iterutil": "jsr:@core/iterutil@^0.6.0", + "@cross/test": "jsr:@cross/test@^0.0.9", "@std/assert": "jsr:@std/assert@^1.0.2", "@std/async": "jsr:@std/async@^1.0.2" }, diff --git a/deno.lock b/deno.lock index b2a1a57..9286bc4 100644 --- a/deno.lock +++ b/deno.lock @@ -3,14 +3,26 @@ "packages": { "specifiers": { "jsr:@core/iterutil@^0.6.0": "jsr:@core/iterutil@0.6.0", + "jsr:@cross/runtime@^1.0.0": "jsr:@cross/runtime@1.0.0", + "jsr:@cross/test@^0.0.9": "jsr:@cross/test@0.0.9", "jsr:@std/assert@^1.0.2": "jsr:@std/assert@1.0.2", "jsr:@std/async@^1.0.2": "jsr:@std/async@1.0.2", - "jsr:@std/internal@^1.0.1": "jsr:@std/internal@1.0.1" + "jsr:@std/internal@^1.0.1": "jsr:@std/internal@1.0.1", + "npm:@types/node": "npm:@types/node@18.16.19" }, "jsr": { "@core/iterutil@0.6.0": { "integrity": "8de0d0062a515496ae744983941d7e379668c2ee2edf43f63423e8da753828b1" }, + "@cross/runtime@1.0.0": { + "integrity": "dddecdf99182df13d50279d1e473f715e83d41961c5c22edd7bb0c4c3cf8a76a" + }, + "@cross/test@0.0.9": { + "integrity": "2aa8237a96a2f8f51ccc8fec71135616d223bf4f38dd89ba4f863037c85ddc56", + "dependencies": [ + "jsr:@cross/runtime@^1.0.0" + ] + }, "@std/assert@1.0.2": { "integrity": "ccacec332958126deaceb5c63ff8b4eaf9f5ed0eac9feccf124110435e59e49c", "dependencies": [ @@ -23,14 +35,29 @@ "@std/internal@1.0.1": { "integrity": "6f8c7544d06a11dd256c8d6ba54b11ed870aac6c5aeafff499892662c57673e6" } + }, + "npm": { + "@types/node@18.16.19": { + "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", + "dependencies": {} + } } }, "remote": {}, "workspace": { "dependencies": [ "jsr:@core/iterutil@^0.6.0", + "jsr:@cross/test@^0.0.9", "jsr:@std/assert@^1.0.2", "jsr:@std/async@^1.0.2" - ] + ], + "packageJson": { + "dependencies": [ + "npm:@jsr/core__iterutil@^0.6.0-pre.0", + "npm:@jsr/cross__test", + "npm:@jsr/std__assert", + "npm:@jsr/std__async@^1.0.3" + ] + } } } diff --git a/lock_test.ts b/lock_test.ts index 3f2f0d4..300321a 100644 --- a/lock_test.ts +++ b/lock_test.ts @@ -1,54 +1,53 @@ +import { test } from "@cross/test"; import { assertEquals } from "@std/assert"; import { AsyncValue } from "./async_value.ts"; import { Lock } from "./lock.ts"; -Deno.test("Lock", async (t) => { - await t.step( - "Processing over multiple event loops is not atomic", - async () => { - const count = new AsyncValue(0); - const operation = async () => { +test( + "Lock Processing over multiple event loops is not atomic", + async () => { + const count = new AsyncValue(0); + const operation = async () => { + const v = await count.get(); + await count.set(v + 1); + }; + await Promise.all([...Array(10)].map(() => operation())); + assertEquals(await count.get(), 1); + }, +); + +test( + "Lock Processing over multiple event loops is not atomic, but can be changed to atomic by using Lock", + async () => { + const count = new Lock(new AsyncValue(0)); + const operation = () => { + return count.lock(async (count) => { const v = await count.get(); await count.set(v + 1); - }; - await Promise.all([...Array(10)].map(() => operation())); - assertEquals(await count.get(), 1); - }, - ); - - await t.step( - "Processing over multiple event loops is not atomic, but can be changed to atomic by using Lock", - async () => { - const count = new Lock(new AsyncValue(0)); - const operation = () => { - return count.lock(async (count) => { - const v = await count.get(); - await count.set(v + 1); - }); - }; - await Promise.all([...Array(10)].map(() => operation())); - assertEquals(await count.lock((v) => v.get()), 10); - }, - ); + }); + }; + await Promise.all([...Array(10)].map(() => operation())); + assertEquals(await count.lock((v) => v.get()), 10); + }, +); - await t.step( - "'lock' should allow only one operation at a time", - async () => { - let noperations = 0; - const results: number[] = []; - const count = new Lock(new AsyncValue(0)); - const operation = () => { - return count.lock(async (count) => { - noperations += 1; - results.push(noperations); - const v = await count.get(); - await count.set(v + 1); - noperations -= 1; - }); - }; - await Promise.all([...Array(10)].map(() => operation())); - assertEquals(noperations, 0); - assertEquals(results, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]); - }, - ); -}); +test( + "Lock 'lock' should allow only one operation at a time", + async () => { + let noperations = 0; + const results: number[] = []; + const count = new Lock(new AsyncValue(0)); + const operation = () => { + return count.lock(async (count) => { + noperations += 1; + results.push(noperations); + const v = await count.get(); + await count.set(v + 1); + noperations -= 1; + }); + }; + await Promise.all([...Array(10)].map(() => operation())); + assertEquals(noperations, 0); + assertEquals(results, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]); + }, +); diff --git a/mutex_test.ts b/mutex_test.ts index c4ce0ee..53417c3 100644 --- a/mutex_test.ts +++ b/mutex_test.ts @@ -1,33 +1,32 @@ +import { test } from "@cross/test"; import { assertEquals } from "@std/assert"; import { AsyncValue } from "./async_value.ts"; import { Mutex } from "./mutex.ts"; -Deno.test("Mutex", async (t) => { - await t.step( - "Processing over multiple event loops is not atomic", - async () => { - const count = new AsyncValue(0); - const operation = async () => { - const v = await count.get(); - await count.set(v + 1); - }; - await Promise.all([...Array(10)].map(() => operation())); - assertEquals(await count.get(), 1); - }, - ); +test( + "Mutex Processing over multiple event loops is not atomic", + async () => { + const count = new AsyncValue(0); + const operation = async () => { + const v = await count.get(); + await count.set(v + 1); + }; + await Promise.all([...Array(10)].map(() => operation())); + assertEquals(await count.get(), 1); + }, +); - await t.step( - "Processing over multiple event loops is not atomic, but can be changed to atomic by using Mutex", - async () => { - const mu = new Mutex(); - const count = new AsyncValue(0); - const operation = async () => { - using _lock = await mu.acquire(); - const v = await count.get(); - await count.set(v + 1); - }; - await Promise.all([...Array(10)].map(() => operation())); - assertEquals(await count.get(), 10); - }, - ); -}); +test( + "Mutex Processing over multiple event loops is not atomic, but can be changed to atomic by using Mutex", + async () => { + const mu = new Mutex(); + const count = new AsyncValue(0); + const operation = async () => { + using _lock = await mu.acquire(); + const v = await count.get(); + await count.set(v + 1); + }; + await Promise.all([...Array(10)].map(() => operation())); + assertEquals(await count.get(), 10); + }, +); diff --git a/notify_test.ts b/notify_test.ts index 6e7bd86..bd96902 100644 --- a/notify_test.ts +++ b/notify_test.ts @@ -1,125 +1,124 @@ +import { test } from "@cross/test"; import { delay } from "@std/async/delay"; import { assertEquals, assertRejects, assertThrows } from "@std/assert"; import { promiseState } from "./promise_state.ts"; import { Notify } from "./notify.ts"; -Deno.test("Notify", async (t) => { - await t.step("'notify' wakes up a single waiter", async () => { +test("Notify 'notify' wakes up a single waiter", async () => { + const notify = new Notify(); + const waiter1 = notify.notified(); + const waiter2 = notify.notified(); + assertEquals(notify.waiterCount, 2); + + notify.notify(); + assertEquals(notify.waiterCount, 1); + assertEquals(await promiseState(waiter1), "fulfilled"); + assertEquals(await promiseState(waiter2), "pending"); + + notify.notify(); + assertEquals(notify.waiterCount, 0); + assertEquals(await promiseState(waiter1), "fulfilled"); + assertEquals(await promiseState(waiter2), "fulfilled"); +}); + +test("Notify 'notify' wakes up a multiple waiters", async () => { + const notify = new Notify(); + const waiter1 = notify.notified(); + const waiter2 = notify.notified(); + const waiter3 = notify.notified(); + const waiter4 = notify.notified(); + const waiter5 = notify.notified(); + assertEquals(notify.waiterCount, 5); + + notify.notify(2); + assertEquals(notify.waiterCount, 3); + assertEquals(await promiseState(waiter1), "fulfilled"); + assertEquals(await promiseState(waiter2), "fulfilled"); + assertEquals(await promiseState(waiter3), "pending"); + assertEquals(await promiseState(waiter4), "pending"); + assertEquals(await promiseState(waiter5), "pending"); + + notify.notify(2); + assertEquals(notify.waiterCount, 1); + assertEquals(await promiseState(waiter1), "fulfilled"); + assertEquals(await promiseState(waiter2), "fulfilled"); + assertEquals(await promiseState(waiter3), "fulfilled"); + assertEquals(await promiseState(waiter4), "fulfilled"); + assertEquals(await promiseState(waiter5), "pending"); + + notify.notify(2); + assertEquals(notify.waiterCount, 0); + assertEquals(await promiseState(waiter1), "fulfilled"); + assertEquals(await promiseState(waiter2), "fulfilled"); + assertEquals(await promiseState(waiter3), "fulfilled"); + assertEquals(await promiseState(waiter4), "fulfilled"); + assertEquals(await promiseState(waiter5), "fulfilled"); +}); + +test("Notify 'notifyAll' wakes up all waiters", async () => { + const notify = new Notify(); + const waiter1 = notify.notified(); + const waiter2 = notify.notified(); + assertEquals(notify.waiterCount, 2); + + notify.notifyAll(); + assertEquals(notify.waiterCount, 0); + assertEquals(await promiseState(waiter1), "fulfilled"); + assertEquals(await promiseState(waiter2), "fulfilled"); +}); + +test( + "Notify 'notified' with non-aborted signal", + async () => { + const controller = new AbortController(); const notify = new Notify(); - const waiter1 = notify.notified(); - const waiter2 = notify.notified(); - assertEquals(notify.waiterCount, 2); - - notify.notify(); - assertEquals(notify.waiterCount, 1); - assertEquals(await promiseState(waiter1), "fulfilled"); - assertEquals(await promiseState(waiter2), "pending"); - - notify.notify(); - assertEquals(notify.waiterCount, 0); - assertEquals(await promiseState(waiter1), "fulfilled"); - assertEquals(await promiseState(waiter2), "fulfilled"); - }); - - await t.step("'notify' wakes up a multiple waiters", async () => { + + const waiter = notify.notified({ signal: controller.signal }); + assertEquals(await promiseState(waiter), "pending"); + }, +); + +test( + "Notify 'notified' with signal aborted after delay", + async () => { + const controller = new AbortController(); const notify = new Notify(); - const waiter1 = notify.notified(); - const waiter2 = notify.notified(); - const waiter3 = notify.notified(); - const waiter4 = notify.notified(); - const waiter5 = notify.notified(); - assertEquals(notify.waiterCount, 5); - - notify.notify(2); - assertEquals(notify.waiterCount, 3); - assertEquals(await promiseState(waiter1), "fulfilled"); - assertEquals(await promiseState(waiter2), "fulfilled"); - assertEquals(await promiseState(waiter3), "pending"); - assertEquals(await promiseState(waiter4), "pending"); - assertEquals(await promiseState(waiter5), "pending"); - - notify.notify(2); - assertEquals(notify.waiterCount, 1); - assertEquals(await promiseState(waiter1), "fulfilled"); - assertEquals(await promiseState(waiter2), "fulfilled"); - assertEquals(await promiseState(waiter3), "fulfilled"); - assertEquals(await promiseState(waiter4), "fulfilled"); - assertEquals(await promiseState(waiter5), "pending"); - - notify.notify(2); - assertEquals(notify.waiterCount, 0); - assertEquals(await promiseState(waiter1), "fulfilled"); - assertEquals(await promiseState(waiter2), "fulfilled"); - assertEquals(await promiseState(waiter3), "fulfilled"); - assertEquals(await promiseState(waiter4), "fulfilled"); - assertEquals(await promiseState(waiter5), "fulfilled"); - }); - - await t.step("'notifyAll' wakes up all waiters", async () => { + const reason = new Error("Aborted"); + + delay(100).then(() => controller.abort(reason)); + await assertRejects( + () => notify.notified({ signal: controller.signal }), + Error, + "Aborted", + ); + }, +); + +test( + "Notify 'notified' with already aborted signal", + async () => { + const controller = new AbortController(); const notify = new Notify(); - const waiter1 = notify.notified(); - const waiter2 = notify.notified(); - assertEquals(notify.waiterCount, 2); - - notify.notifyAll(); - assertEquals(notify.waiterCount, 0); - assertEquals(await promiseState(waiter1), "fulfilled"); - assertEquals(await promiseState(waiter2), "fulfilled"); - }); - - await t.step( - "'notified' with non-aborted signal", - async () => { - const controller = new AbortController(); - const notify = new Notify(); - - const waiter = notify.notified({ signal: controller.signal }); - assertEquals(await promiseState(waiter), "pending"); - }, - ); - - await t.step( - "'notified' with signal aborted after delay", - async () => { - const controller = new AbortController(); - const notify = new Notify(); - const reason = new Error("Aborted"); - - delay(100).then(() => controller.abort(reason)); - await assertRejects( - () => notify.notified({ signal: controller.signal }), - Error, - "Aborted", - ); - }, - ); - - await t.step( - "'notified' with already aborted signal", - async () => { - const controller = new AbortController(); - const notify = new Notify(); - const reason = new Error("Aborted"); - - controller.abort(reason); - await assertRejects( - () => notify.notified({ signal: controller.signal }), - Error, - "Aborted", - ); - }, - ); - - await t.step( - "'notify' throws RangeError if size is not a positive safe integer", - () => { - const notify = new Notify(); - assertThrows(() => notify.notify(NaN), RangeError); - assertThrows(() => notify.notify(Infinity), RangeError); - assertThrows(() => notify.notify(-Infinity), RangeError); - assertThrows(() => notify.notify(-1), RangeError); - assertThrows(() => notify.notify(1.1), RangeError); - assertThrows(() => notify.notify(0), RangeError); - }, - ); -}); + const reason = new Error("Aborted"); + + controller.abort(reason); + await assertRejects( + () => notify.notified({ signal: controller.signal }), + Error, + "Aborted", + ); + }, +); + +test( + "Notify 'notify' throws RangeError if size is not a positive safe integer", + () => { + const notify = new Notify(); + assertThrows(() => notify.notify(NaN), RangeError); + assertThrows(() => notify.notify(Infinity), RangeError); + assertThrows(() => notify.notify(-Infinity), RangeError); + assertThrows(() => notify.notify(-1), RangeError); + assertThrows(() => notify.notify(1.1), RangeError); + assertThrows(() => notify.notify(0), RangeError); + }, +); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a4445b5 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,55 @@ +{ + "name": "asyncutil", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@core/iterutil": "npm:@jsr/core__iterutil@^0.6.0-pre.0", + "@cross/test": "npm:@jsr/cross__test", + "@std/assert": "npm:@jsr/std__assert", + "@std/async": "npm:@jsr/std__async@^1.0.3" + } + }, + "node_modules/@core/iterutil": { + "name": "@jsr/core__iterutil", + "version": "0.6.0-pre.0", + "resolved": "https://npm.jsr.io/~/11/@jsr/core__iterutil/0.6.0-pre.0.tgz", + "integrity": "sha512-O/ka3467LURyXdWqvorYUPQirCaEQOueYwO7RSnKRDd1aGYK0yzfEvfGZkYwYC2PRT745+MOi4lsX6SKKMs97A==" + }, + "node_modules/@cross/test": { + "name": "@jsr/cross__test", + "version": "0.0.9", + "resolved": "https://npm.jsr.io/~/11/@jsr/cross__test/0.0.9.tgz", + "integrity": "sha512-zwDSXQHw8n6k/gBj1Q67Td34Lb1PfkzLTggXnNZzcRO9SxcdAlzyOKFCF62kTFM7ZjVPqYvqu2gHzMLtj6cayw==", + "dependencies": { + "@jsr/cross__runtime": "^1.0.0" + } + }, + "node_modules/@jsr/cross__runtime": { + "version": "1.0.0", + "resolved": "https://npm.jsr.io/~/11/@jsr/cross__runtime/1.0.0.tgz", + "integrity": "sha512-wUtjVBTk65ae4AKQRnxD5x3h4vVmopdKAYie/uS01Qolii2XQ81bKtRTvJ4kx133GYYgIAgyl3ihQ0OK8LcPmQ==" + }, + "node_modules/@jsr/std__internal": { + "version": "1.0.1", + "resolved": "https://npm.jsr.io/~/11/@jsr/std__internal/1.0.1.tgz", + "integrity": "sha512-ssI1kvluIero6cCfiWNmYItqCR8QpQB+STBJoe/xQVZ79SDpoqjK5VF2Eq/2M+Dz8WbqHVmrXRCaGN162x+Ebw==" + }, + "node_modules/@std/assert": { + "name": "@jsr/std__assert", + "version": "1.0.2", + "resolved": "https://npm.jsr.io/~/11/@jsr/std__assert/1.0.2.tgz", + "integrity": "sha512-xujHXXeT3zvMNZeCXiDyfiITaqP4rgH8wqHNUD0Iyr4c2R0Ea//zJ4pASA1utIEIxeVu1jpeSlHU4+pagscqgQ==", + "dependencies": { + "@jsr/std__internal": "^1.0.1" + } + }, + "node_modules/@std/async": { + "name": "@jsr/std__async", + "version": "1.0.3", + "resolved": "https://npm.jsr.io/~/11/@jsr/std__async/1.0.3.tgz", + "integrity": "sha512-j5NZqYHN/czhfjBKh0jvPU5IRhP3Y5Lk7X3qL5ghw0gDSwI8h/kzlxSMV98ML0L6tXN9SvZU8lqa8Q5evtL4sA==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..7f52a41 --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "type": "module", + "dependencies": { + "@core/iterutil": "npm:@jsr/core__iterutil@^0.6.0-pre.0", + "@cross/test": "npm:@jsr/cross__test", + "@std/assert": "npm:@jsr/std__assert", + "@std/async": "npm:@jsr/std__async@^1.0.3" + } +} diff --git a/promise_state_test.ts b/promise_state_test.ts index 2129fc0..ec20ee9 100644 --- a/promise_state_test.ts +++ b/promise_state_test.ts @@ -1,7 +1,8 @@ +import { test } from "@cross/test"; import { assertEquals } from "@std/assert"; import { promiseState } from "./promise_state.ts"; -Deno.test( +test( "promiseState() returns 'fulfilled' for resolved promise", async () => { const p = Promise.resolve("Resolved promise"); @@ -9,7 +10,7 @@ Deno.test( }, ); -Deno.test( +test( "promiseState() returns 'rejected' for rejected promise", async () => { const p = Promise.reject("Rejected promise"); @@ -18,7 +19,7 @@ Deno.test( }, ); -Deno.test( +test( "promiseState() returns 'pending' for not resolved promise", async () => { const p = new Promise(() => undefined); @@ -26,7 +27,7 @@ Deno.test( }, ); -Deno.test("promiseState() returns refreshed status", async () => { +test("promiseState() returns refreshed status", async () => { const { promise, resolve } = Promise.withResolvers(); const p = (async () => { await promise; diff --git a/queue_test.ts b/queue_test.ts index c69d0a4..9113ead 100644 --- a/queue_test.ts +++ b/queue_test.ts @@ -1,60 +1,59 @@ +import { test } from "@cross/test"; import { delay } from "@std/async/delay"; import { assertEquals, assertRejects } from "@std/assert"; import { promiseState } from "./promise_state.ts"; import { Queue } from "./queue.ts"; -Deno.test("Queue", async (t) => { - await t.step("'pop' returns pushed items", async () => { - const q = new Queue(); - q.push(1); - q.push(2); - q.push(3); - assertEquals(await q.pop(), 1); - assertEquals(await q.pop(), 2); - assertEquals(await q.pop(), 3); - }); - - await t.step("'pop' waits for an item is pushed", async () => { - const q = new Queue(); - const popper = q.pop(); - assertEquals(await promiseState(popper), "pending"); - q.push(1); - assertEquals(await promiseState(popper), "fulfilled"); - assertEquals(await popper, 1); - }); - - await t.step("'pop' with non-aborted signal", async () => { - const controller = new AbortController(); - const q = new Queue(); - const popper = q.pop({ signal: controller.signal }); - assertEquals(await promiseState(popper), "pending"); - }); - - await t.step("'pop' with signal aborted after delay", async () => { - const controller = new AbortController(); - const q = new Queue(); - const reason = new Error("Aborted"); - - delay(100).then(() => controller.abort(reason)); - - await assertRejects( - () => q.pop({ signal: controller.signal }), - Error, - "Aborted", - ); - }); - - await t.step("'pop' with signal already aborted", async () => { - const controller = new AbortController(); - const q = new Queue(); - const reason = new Error("Aborted"); - - controller.abort(reason); - - await assertRejects( - () => q.pop({ signal: controller.signal }), - Error, - "Aborted", - ); - }); +test("Queue 'pop' returns pushed items", async () => { + const q = new Queue(); + q.push(1); + q.push(2); + q.push(3); + assertEquals(await q.pop(), 1); + assertEquals(await q.pop(), 2); + assertEquals(await q.pop(), 3); +}); + +test("Queue 'pop' waits for an item is pushed", async () => { + const q = new Queue(); + const popper = q.pop(); + assertEquals(await promiseState(popper), "pending"); + q.push(1); + assertEquals(await promiseState(popper), "fulfilled"); + assertEquals(await popper, 1); +}); + +test("Queue 'pop' with non-aborted signal", async () => { + const controller = new AbortController(); + const q = new Queue(); + const popper = q.pop({ signal: controller.signal }); + assertEquals(await promiseState(popper), "pending"); +}); + +test("Queue 'pop' with signal aborted after delay", async () => { + const controller = new AbortController(); + const q = new Queue(); + const reason = new Error("Aborted"); + + delay(100).then(() => controller.abort(reason)); + + await assertRejects( + () => q.pop({ signal: controller.signal }), + Error, + "Aborted", + ); +}); + +test("Queue 'pop' with signal already aborted", async () => { + const controller = new AbortController(); + const q = new Queue(); + const reason = new Error("Aborted"); + + controller.abort(reason); + + await assertRejects( + () => q.pop({ signal: controller.signal }), + Error, + "Aborted", + ); }); diff --git a/rw_lock_test.ts b/rw_lock_test.ts index a59a4a1..2893b4f 100644 --- a/rw_lock_test.ts +++ b/rw_lock_test.ts @@ -1,124 +1,123 @@ +import { test } from "@cross/test"; import { assertEquals } from "@std/assert"; import { promiseState } from "./promise_state.ts"; import { AsyncValue } from "./async_value.ts"; import { RwLock } from "./rw_lock.ts"; -Deno.test("RwLock", async (t) => { - await t.step( - "Processing over multiple event loops is not atomic", - async () => { - const count = new AsyncValue(0); - const operation = async () => { +test( + "RwLock Processing over multiple event loops is not atomic", + async () => { + const count = new AsyncValue(0); + const operation = async () => { + const v = await count.get(); + await count.set(v + 1); + }; + await Promise.all([...Array(10)].map(() => operation())); + assertEquals(await count.get(), 1); + }, +); + +test( + "RwLock Processing over multiple event loops is not atomic, but can be changed to atomic by using RwLock", + async () => { + const count = new RwLock(new AsyncValue(0)); + const operation = () => { + return count.lock(async (count) => { const v = await count.get(); await count.set(v + 1); - }; - await Promise.all([...Array(10)].map(() => operation())); - assertEquals(await count.get(), 1); - }, - ); - - await t.step( - "Processing over multiple event loops is not atomic, but can be changed to atomic by using RwLock", - async () => { - const count = new RwLock(new AsyncValue(0)); - const operation = () => { - return count.lock(async (count) => { - const v = await count.get(); - await count.set(v + 1); - }); - }; - await Promise.all([...Array(10)].map(() => operation())); - assertEquals(await count.lock((v) => v.get()), 10); - }, - ); + }); + }; + await Promise.all([...Array(10)].map(() => operation())); + assertEquals(await count.lock((v) => v.get()), 10); + }, +); - await t.step( - "'lock' should allow only one writer at a time", - async () => { - let nwriters = 0; - const results: number[] = []; - const count = new RwLock(new AsyncValue(0)); - const writer = () => { - return count.lock(async (count) => { - nwriters += 1; - results.push(nwriters); - await count.set(await count.get() + 1); - nwriters -= 1; - }); - }; - await Promise.all([...Array(10)].map(() => writer())); - assertEquals(nwriters, 0); - assertEquals(results, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]); - }, - ); +test( + "RwLock 'lock' should allow only one writer at a time", + async () => { + let nwriters = 0; + const results: number[] = []; + const count = new RwLock(new AsyncValue(0)); + const writer = () => { + return count.lock(async (count) => { + nwriters += 1; + results.push(nwriters); + await count.set(await count.get() + 1); + nwriters -= 1; + }); + }; + await Promise.all([...Array(10)].map(() => writer())); + assertEquals(nwriters, 0); + assertEquals(results, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]); + }, +); - await t.step( - "'rlock' should allow multiple readers at a time", - async () => { - let nreaders = 0; - const results: number[] = []; - const count = new RwLock(new AsyncValue(0)); - const reader = () => { - return count.rlock(async (count) => { - nreaders += 1; - results.push(nreaders); - assertEquals(await count.get(), 0); - nreaders -= 1; - }); - }; - await Promise.all([...Array(10)].map(() => reader())); - assertEquals(nreaders, 0); - assertEquals(results, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - }, - ); +test( + "RwLock 'rlock' should allow multiple readers at a time", + async () => { + let nreaders = 0; + const results: number[] = []; + const count = new RwLock(new AsyncValue(0)); + const reader = () => { + return count.rlock(async (count) => { + nreaders += 1; + results.push(nreaders); + assertEquals(await count.get(), 0); + nreaders -= 1; + }); + }; + await Promise.all([...Array(10)].map(() => reader())); + assertEquals(nreaders, 0); + assertEquals(results, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + }, +); - await t.step( - "'lock' should block until all readers are done", - async () => { - const count = new RwLock(new AsyncValue(0)); - const { promise, resolve } = Promise.withResolvers(); - const writer = () => { - return count.lock(() => { - // Do nothing - }); - }; - const reader = () => { - return count.rlock(async () => { - await promise; - }); - }; - const r = reader(); - const w = writer(); - assertEquals(await promiseState(r), "pending"); - assertEquals(await promiseState(w), "pending"); - resolve(); - assertEquals(await promiseState(r), "fulfilled"); - assertEquals(await promiseState(w), "fulfilled"); - }, - ); +test( + "RwLock 'lock' should block until all readers are done", + async () => { + const count = new RwLock(new AsyncValue(0)); + const { promise, resolve } = Promise.withResolvers(); + const writer = () => { + return count.lock(() => { + // Do nothing + }); + }; + const reader = () => { + return count.rlock(async () => { + await promise; + }); + }; + const r = reader(); + const w = writer(); + assertEquals(await promiseState(r), "pending"); + assertEquals(await promiseState(w), "pending"); + resolve(); + assertEquals(await promiseState(r), "fulfilled"); + assertEquals(await promiseState(w), "fulfilled"); + }, +); - await t.step( - "'rlock' should block until all writers are done", - async () => { - const count = new RwLock(new AsyncValue(0)); - const { promise, resolve } = Promise.withResolvers(); - const writer = () => { - return count.lock(async () => { - await promise; - }); - }; - const reader = () => { - return count.rlock(() => { - // Do nothing - }); - }; - const w = writer(); - const r = reader(); - assertEquals(await promiseState(w), "pending"); - assertEquals(await promiseState(r), "pending"); - resolve(); - assertEquals(await promiseState(w), "fulfilled"); - assertEquals(await promiseState(r), "fulfilled"); - }, - ); -}); +test( + "RwLock 'rlock' should block until all writers are done", + async () => { + const count = new RwLock(new AsyncValue(0)); + const { promise, resolve } = Promise.withResolvers(); + const writer = () => { + return count.lock(async () => { + await promise; + }); + }; + const reader = () => { + return count.rlock(() => { + // Do nothing + }); + }; + const w = writer(); + const r = reader(); + assertEquals(await promiseState(w), "pending"); + assertEquals(await promiseState(r), "pending"); + resolve(); + assertEquals(await promiseState(w), "fulfilled"); + assertEquals(await promiseState(r), "fulfilled"); + }, +); diff --git a/semaphore_test.ts b/semaphore_test.ts index 85be7ce..7081c74 100644 --- a/semaphore_test.ts +++ b/semaphore_test.ts @@ -1,109 +1,108 @@ +import { test } from "@cross/test"; import { assertEquals, assertThrows } from "@std/assert"; import { Semaphore } from "./semaphore.ts"; -Deno.test("Semaphore", async (t) => { - await t.step( - "regulates the number of workers concurrently running (n=5)", - async () => { - let nworkers = 0; - const results: number[] = []; - const sem = new Semaphore(5); - const worker = () => { - return sem.lock(async () => { - nworkers++; - results.push(nworkers); - await new Promise((resolve) => setTimeout(resolve, 10)); - nworkers--; - }); - }; - await Promise.all([...Array(10)].map(() => worker())); - assertEquals(nworkers, 0); - assertEquals(results, [ - 1, - 2, - 3, - 4, - 5, - 5, - 5, - 5, - 5, - 5, - ]); - }, - ); +test( + "Semaphore regulates the number of workers concurrently running (n=5)", + async () => { + let nworkers = 0; + const results: number[] = []; + const sem = new Semaphore(5); + const worker = () => { + return sem.lock(async () => { + nworkers++; + results.push(nworkers); + await new Promise((resolve) => setTimeout(resolve, 10)); + nworkers--; + }); + }; + await Promise.all([...Array(10)].map(() => worker())); + assertEquals(nworkers, 0); + assertEquals(results, [ + 1, + 2, + 3, + 4, + 5, + 5, + 5, + 5, + 5, + 5, + ]); + }, +); - await t.step( - "regulates the number of workers concurrently running (n=1)", - async () => { - let nworkers = 0; - const results: number[] = []; - const sem = new Semaphore(1); - const worker = () => { - return sem.lock(async () => { - nworkers++; - results.push(nworkers); - await new Promise((resolve) => setTimeout(resolve, 10)); - nworkers--; - }); - }; - await Promise.all([...Array(10)].map(() => worker())); - assertEquals(nworkers, 0); - assertEquals(results, [ - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - ]); - }, - ); +test( + "Semaphore regulates the number of workers concurrently running (n=1)", + async () => { + let nworkers = 0; + const results: number[] = []; + const sem = new Semaphore(1); + const worker = () => { + return sem.lock(async () => { + nworkers++; + results.push(nworkers); + await new Promise((resolve) => setTimeout(resolve, 10)); + nworkers--; + }); + }; + await Promise.all([...Array(10)].map(() => worker())); + assertEquals(nworkers, 0); + assertEquals(results, [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + ]); + }, +); - await t.step( - "regulates the number of workers concurrently running (n=10)", - async () => { - let nworkers = 0; - const results: number[] = []; - const sem = new Semaphore(10); - const worker = () => { - return sem.lock(async () => { - nworkers++; - results.push(nworkers); - await new Promise((resolve) => setTimeout(resolve, 10)); - nworkers--; - }); - }; - await Promise.all([...Array(10)].map(() => worker())); - assertEquals(nworkers, 0); - assertEquals(results, [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - ]); - }, - ); +test( + "Semaphore regulates the number of workers concurrently running (n=10)", + async () => { + let nworkers = 0; + const results: number[] = []; + const sem = new Semaphore(10); + const worker = () => { + return sem.lock(async () => { + nworkers++; + results.push(nworkers); + await new Promise((resolve) => setTimeout(resolve, 10)); + nworkers--; + }); + }; + await Promise.all([...Array(10)].map(() => worker())); + assertEquals(nworkers, 0); + assertEquals(results, [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + ]); + }, +); - await t.step( - "throws RangeError if size is not a positive safe integer", - () => { - assertThrows(() => new Semaphore(NaN), RangeError); - assertThrows(() => new Semaphore(Infinity), RangeError); - assertThrows(() => new Semaphore(-Infinity), RangeError); - assertThrows(() => new Semaphore(-1), RangeError); - assertThrows(() => new Semaphore(1.1), RangeError); - assertThrows(() => new Semaphore(0), RangeError); - }, - ); -}); +test( + "Semaphore throws RangeError if size is not a positive safe integer", + () => { + assertThrows(() => new Semaphore(NaN), RangeError); + assertThrows(() => new Semaphore(Infinity), RangeError); + assertThrows(() => new Semaphore(-Infinity), RangeError); + assertThrows(() => new Semaphore(-1), RangeError); + assertThrows(() => new Semaphore(1.1), RangeError); + assertThrows(() => new Semaphore(0), RangeError); + }, +); diff --git a/stack_test.ts b/stack_test.ts index 347225d..ef0ffcb 100644 --- a/stack_test.ts +++ b/stack_test.ts @@ -1,60 +1,59 @@ +import { test } from "@cross/test"; import { delay } from "@std/async/delay"; import { assertEquals, assertRejects } from "@std/assert"; import { promiseState } from "./promise_state.ts"; import { Stack } from "./stack.ts"; -Deno.test("Stack", async (t) => { - await t.step("'pop' returns pushed items", async () => { - const q = new Stack(); - q.push(1); - q.push(2); - q.push(3); - assertEquals(await q.pop(), 3); - assertEquals(await q.pop(), 2); - assertEquals(await q.pop(), 1); - }); - - await t.step("'pop' waits for an item is pushed", async () => { - const q = new Stack(); - const popper = q.pop(); - assertEquals(await promiseState(popper), "pending"); - q.push(1); - assertEquals(await promiseState(popper), "fulfilled"); - assertEquals(await popper, 1); - }); - - await t.step("'pop' with non-aborted signal", async () => { - const controller = new AbortController(); - const q = new Stack(); - const popper = q.pop({ signal: controller.signal }); - assertEquals(await promiseState(popper), "pending"); - }); - - await t.step("'pop' with signal aborted after delay", async () => { - const controller = new AbortController(); - const q = new Stack(); - const reason = new Error("Aborted"); - - delay(100).then(() => controller.abort(reason)); - - await assertRejects( - () => q.pop({ signal: controller.signal }), - Error, - "Aborted", - ); - }); - - await t.step("'pop' with signal already aborted", async () => { - const controller = new AbortController(); - const q = new Stack(); - const reason = new Error("Aborted"); - - controller.abort(reason); - - await assertRejects( - () => q.pop({ signal: controller.signal }), - Error, - "Aborted", - ); - }); +test("Stack 'pop' returns pushed items", async () => { + const q = new Stack(); + q.push(1); + q.push(2); + q.push(3); + assertEquals(await q.pop(), 3); + assertEquals(await q.pop(), 2); + assertEquals(await q.pop(), 1); +}); + +test("Stack 'pop' waits for an item is pushed", async () => { + const q = new Stack(); + const popper = q.pop(); + assertEquals(await promiseState(popper), "pending"); + q.push(1); + assertEquals(await promiseState(popper), "fulfilled"); + assertEquals(await popper, 1); +}); + +test("Stack 'pop' with non-aborted signal", async () => { + const controller = new AbortController(); + const q = new Stack(); + const popper = q.pop({ signal: controller.signal }); + assertEquals(await promiseState(popper), "pending"); +}); + +test("Stack 'pop' with signal aborted after delay", async () => { + const controller = new AbortController(); + const q = new Stack(); + const reason = new Error("Aborted"); + + delay(100).then(() => controller.abort(reason)); + + await assertRejects( + () => q.pop({ signal: controller.signal }), + Error, + "Aborted", + ); +}); + +test("Stack 'pop' with signal already aborted", async () => { + const controller = new AbortController(); + const q = new Stack(); + const reason = new Error("Aborted"); + + controller.abort(reason); + + await assertRejects( + () => q.pop({ signal: controller.signal }), + Error, + "Aborted", + ); }); diff --git a/wait_group_test.ts b/wait_group_test.ts index bcbbca2..53b8ca2 100644 --- a/wait_group_test.ts +++ b/wait_group_test.ts @@ -1,97 +1,97 @@ +import { test } from "@cross/test"; import { assertEquals, assertRejects, assertThrows } from "@std/assert"; -import { deadline, delay } from "@std/async"; +import { delay } from "@std/async"; import { WaitGroup } from "./wait_group.ts"; +import { deadline } from "./_testutil.ts"; -Deno.test("WaitGroup", async (t) => { - await t.step( - "Ensure WaitGroup synchronizes multiple workers", - async () => { - const wg = new WaitGroup(); - const workers = []; - const results: string[] = []; - for (let i = 0; i < 5; i++) { - workers.push((async () => { - wg.add(1); - results.push(`before wait ${i}`); - await delay(100); - results.push(`after wait ${i}`); - wg.done(); - })()); - } - await wg.wait(); - assertEquals(results, [ - "before wait 0", - "before wait 1", - "before wait 2", - "before wait 3", - "before wait 4", - "after wait 0", - "after wait 1", - "after wait 2", - "after wait 3", - "after wait 4", - ]); - }, - ); +test( + "WaitGroup Ensure WaitGroup synchronizes multiple workers", + async () => { + const wg = new WaitGroup(); + const workers = []; + const results: string[] = []; + for (let i = 0; i < 5; i++) { + workers.push((async () => { + wg.add(1); + results.push(`before wait ${i}`); + await delay(100); + results.push(`after wait ${i}`); + wg.done(); + })()); + } + await wg.wait(); + assertEquals(results, [ + "before wait 0", + "before wait 1", + "before wait 2", + "before wait 3", + "before wait 4", + "after wait 0", + "after wait 1", + "after wait 2", + "after wait 3", + "after wait 4", + ]); + }, +); - await t.step( - "'wait' with non-aborted signal", - async () => { - const controller = new AbortController(); - const wg = new WaitGroup(); - wg.add(1); - await assertRejects( - () => deadline(wg.wait({ signal: controller.signal }), 100), - DOMException, - "Signal timed out.", - ); - }, - ); +test( + "WaitGroup 'wait' with non-aborted signal", + async () => { + const controller = new AbortController(); + const wg = new WaitGroup(); + wg.add(1); + await assertRejects( + () => deadline(wg.wait({ signal: controller.signal }), 100), + DOMException, + "Signal timed out.", + ); + }, +); - await t.step( - "'wait' with signal aborted after delay", - async () => { - const controller = new AbortController(); - const wg = new WaitGroup(); - wg.add(1); +test( + "WaitGroup 'wait' with signal aborted after delay", + async () => { + const controller = new AbortController(); + const wg = new WaitGroup(); + wg.add(1); - const reason = new Error("Aborted"); - delay(50).then(() => controller.abort(reason)); + const reason = new Error("Aborted"); + delay(50).then(() => controller.abort(reason)); - await assertRejects( - () => deadline(wg.wait({ signal: controller.signal }), 100), - Error, - "Aborted", - ); - }, - ); + await assertRejects( + () => deadline(wg.wait({ signal: controller.signal }), 100), + Error, + "Aborted", + ); + }, +); - await t.step( - "'wait' with already aborted signal", - async () => { - const controller = new AbortController(); - const wg = new WaitGroup(); - wg.add(1); +test( + "WaitGroup 'wait' with already aborted signal", + async () => { + const controller = new AbortController(); + const wg = new WaitGroup(); + wg.add(1); - const reason = new Error("Aborted"); - controller.abort(reason); + const reason = new Error("Aborted"); + controller.abort(reason); - await assertRejects( - () => deadline(wg.wait({ signal: controller.signal }), 100), - Error, - "Aborted", - ); - }, - ); + await assertRejects( + () => deadline(wg.wait({ signal: controller.signal }), 100), + Error, + "Aborted", + ); + }, +); - await t.step( - "'add' throws RangeError if delta is not a safe integer", - () => { - const wg = new WaitGroup(); - assertThrows(() => wg.add(NaN), RangeError); - assertThrows(() => wg.add(Infinity), RangeError); - assertThrows(() => wg.add(-Infinity), RangeError); - assertThrows(() => wg.add(1.1), RangeError); - }, - ); -}); +test( + "WaitGroup 'add' throws RangeError if delta is not a safe integer", + () => { + const wg = new WaitGroup(); + assertThrows(() => wg.add(NaN), RangeError); + assertThrows(() => wg.add(Infinity), RangeError); + assertThrows(() => wg.add(-Infinity), RangeError); + assertThrows(() => wg.add(1.1), RangeError); + }, +);