From c0a6e24857dad7e8061b93fade3d4b8fb68b4843 Mon Sep 17 00:00:00 2001
From: Michael Herzner <michael.b.herzner@gmail.com>
Date: Wed, 10 Apr 2024 23:17:07 +0200
Subject: [PATCH 1/2] test(async): improve test coverage

---
 async/_util.ts                   |  9 +++++
 async/_util_test.ts              | 32 ++++++++++++++++--
 async/abortable.ts               | 11 ++-----
 async/delay.ts                   |  2 +-
 async/delay_test.ts              | 34 +++++++++++++++++++
 async/mux_async_iterator_test.ts | 56 +++++++++++++++++++++++++-------
 async/pool_test.ts               |  4 +--
 7 files changed, 122 insertions(+), 26 deletions(-)

diff --git a/async/_util.ts b/async/_util.ts
index 31de9f0abc3c..235e5ed3c71b 100644
--- a/async/_util.ts
+++ b/async/_util.ts
@@ -1,6 +1,15 @@
 // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 // This module is browser compatible.
 
+// This `reason` comes from `AbortSignal` thus must be `any`.
+// deno-lint-ignore no-explicit-any
+export function createAbortError(reason?: any): DOMException {
+  return new DOMException(
+    reason ? `Aborted: ${reason}` : "Aborted",
+    "AbortError",
+  );
+}
+
 export function exponentialBackoffWithJitter(
   cap: number,
   base: number,
diff --git a/async/_util_test.ts b/async/_util_test.ts
index 7ff460a51c23..6c3fa49366b3 100644
--- a/async/_util_test.ts
+++ b/async/_util_test.ts
@@ -1,6 +1,6 @@
 // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-import { exponentialBackoffWithJitter } from "./_util.ts";
-import { assertEquals } from "../assert/mod.ts";
+import { createAbortError, exponentialBackoffWithJitter } from "./_util.ts";
+import { assertEquals, assertInstanceOf } from "../assert/mod.ts";
 
 // test util to ensure deterministic results during testing of backoff function by polyfilling Math.random
 function prngMulberry32(seed: number) {
@@ -50,3 +50,31 @@ Deno.test("exponentialBackoffWithJitter()", () => {
     assertEquals(results as typeof row, row);
   }
 });
+
+Deno.test("createAbortError()", () => {
+  const error = createAbortError();
+  assertInstanceOf(error, DOMException);
+  assertEquals(error.name, "AbortError");
+  assertEquals(error.message, "Aborted");
+});
+
+Deno.test("createAbortError() handles aborted signal with reason", () => {
+  const c = new AbortController();
+  c.abort("Expected Reason");
+  const error = createAbortError(c.signal.reason);
+  assertInstanceOf(error, DOMException);
+  assertEquals(error.name, "AbortError");
+  assertEquals(error.message, "Aborted: Expected Reason");
+});
+
+Deno.test("createAbortError() handles aborted signal without reason", () => {
+  const c = new AbortController();
+  c.abort();
+  const error = createAbortError(c.signal.reason);
+  assertInstanceOf(error, DOMException);
+  assertEquals(error.name, "AbortError");
+  assertEquals(
+    error.message,
+    "Aborted: AbortError: The signal has been aborted",
+  );
+});
diff --git a/async/abortable.ts b/async/abortable.ts
index b25e187fc1a2..9549d4e3bd1a 100644
--- a/async/abortable.ts
+++ b/async/abortable.ts
@@ -1,6 +1,8 @@
 // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
 // This module is browser compatible.
 
+import { createAbortError } from "./_util.ts";
+
 /**
  * Make {@linkcode Promise} abortable with the given signal.
  *
@@ -145,12 +147,3 @@ export async function* abortableAsyncIterable<T>(
     yield value;
   }
 }
-
-// This `reason` comes from `AbortSignal` thus must be `any`.
-// deno-lint-ignore no-explicit-any
-function createAbortError(reason?: any): DOMException {
-  return new DOMException(
-    reason ? `Aborted: ${reason}` : "Aborted",
-    "AbortError",
-  );
-}
diff --git a/async/delay.ts b/async/delay.ts
index 2e8449966846..f8023bd292e8 100644
--- a/async/delay.ts
+++ b/async/delay.ts
@@ -36,7 +36,7 @@ export interface DelayOptions {
  * ```
  */
 export function delay(ms: number, options: DelayOptions = {}): Promise<void> {
-  const { signal, persistent } = options;
+  const { signal, persistent = true } = options;
   if (signal?.aborted) return Promise.reject(signal.reason);
   return new Promise((resolve, reject) => {
     const abort = () => {
diff --git a/async/delay_test.ts b/async/delay_test.ts
index bc22b35a7332..f5d7fb6a34ed 100644
--- a/async/delay_test.ts
+++ b/async/delay_test.ts
@@ -2,10 +2,12 @@
 import { delay } from "./delay.ts";
 import {
   assert,
+  assertEquals,
   assertInstanceOf,
   assertRejects,
   assertStrictEquals,
 } from "../assert/mod.ts";
+import { assertSpyCalls, stub } from "../testing/mock.ts";
 
 // https://dom.spec.whatwg.org/#interface-AbortSignal
 function assertIsDefaultAbortReason(reason: unknown) {
@@ -103,3 +105,35 @@ Deno.test("delay() handles already aborted signal", async function () {
   assert(diff < 100);
   assertIsDefaultAbortReason(cause);
 });
+
+Deno.test("delay() handles persitent option", async function () {
+  using unrefTimer = stub(Deno, "unrefTimer");
+  await delay(100, { persistent: false });
+  assertSpyCalls(unrefTimer, 1);
+});
+
+Deno.test("delay() handles persistent option with reference error", async function () {
+  using unrefTimer = stub(Deno, "unrefTimer", () => {
+    throw new ReferenceError();
+  });
+  await delay(100, { persistent: false });
+  assertSpyCalls(unrefTimer, 1);
+});
+
+Deno.test({
+  name: "delay() handles persistent option with error",
+  fn: async function () {
+    using unrefTimer = stub(Deno, "unrefTimer", () => {
+      throw new Error("Error!");
+    });
+    try {
+      await delay(100, { persistent: false });
+    } catch (e) {
+      assert(e instanceof Error);
+      assertEquals(e.message, "Error!");
+      assertSpyCalls(unrefTimer, 1);
+    }
+  },
+  sanitizeResources: false,
+  sanitizeOps: false,
+});
diff --git a/async/mux_async_iterator_test.ts b/async/mux_async_iterator_test.ts
index 72feb7c32717..7852b028713a 100644
--- a/async/mux_async_iterator_test.ts
+++ b/async/mux_async_iterator_test.ts
@@ -34,6 +34,26 @@ Deno.test("MuxAsyncIterator()", async function () {
   assertEquals(results, new Set([1, 2, 3, 4, 5, 6]));
 });
 
+Deno.test("MuxAsyncIterator() works with no iterables", async function () {
+  const mux = new MuxAsyncIterator<number>();
+  const results = new Set(await Array.fromAsync(mux));
+  assertEquals(results.size, 0);
+  assertEquals(results, new Set([]));
+});
+
+Deno.test("MuxAsyncIterator() clears iterables after successful iteration", async function () {
+  const mux = new MuxAsyncIterator<number>();
+  mux.add(gen123());
+  mux.add(gen456());
+  const results = new Set(await Array.fromAsync(mux));
+  assertEquals(results.size, 6);
+  assertEquals(results, new Set([1, 2, 3, 4, 5, 6]));
+  mux.add(gen123());
+  const results2 = new Set(await Array.fromAsync(mux));
+  assertEquals(results2.size, 3);
+  assertEquals(results2, new Set([1, 2, 3]));
+});
+
 Deno.test("MuxAsyncIterator() takes async iterable as source", async function () {
   const mux = new MuxAsyncIterator<number>();
   mux.add(new CustomAsyncIterable());
@@ -42,16 +62,28 @@ Deno.test("MuxAsyncIterator() takes async iterable as source", async function ()
   assertEquals(results, new Set([1, 2, 3]));
 });
 
-Deno.test({
-  name: "MuxAsyncIterator() throws when the source throws",
-  async fn() {
-    const mux = new MuxAsyncIterator<number>();
-    mux.add(gen123());
-    mux.add(genThrows());
-    await assertRejects(
-      async () => await Array.fromAsync(mux),
-      Error,
-      "something went wrong",
-    );
-  },
+Deno.test("MuxAsyncIterator() throws when the source throws", async function () {
+  const mux = new MuxAsyncIterator<number>();
+  mux.add(gen123());
+  mux.add(genThrows());
+  await assertRejects(
+    async () => await Array.fromAsync(mux),
+    Error,
+    "something went wrong",
+  );
+});
+
+Deno.test("MuxAsyncIterator() doesn't clear iterables after throwing", async function () {
+  const mux = new MuxAsyncIterator<number>();
+  mux.add(genThrows());
+  await assertRejects(
+    async () => await Array.fromAsync(mux),
+    Error,
+    "something went wrong",
+  );
+  await assertRejects(
+    async () => await Array.fromAsync(mux),
+    Error,
+    "something went wrong",
+  );
 });
diff --git a/async/pool_test.ts b/async/pool_test.ts
index 3d005ba55b1a..8c2870cd1ce6 100644
--- a/async/pool_test.ts
+++ b/async/pool_test.ts
@@ -22,7 +22,7 @@ Deno.test("pooledMap()", async function () {
   assert(diff < 3000);
 });
 
-Deno.test("pooledMap() handles errors", async () => {
+Deno.test("pooledMap() handles errors", async function () {
   async function mapNumber(n: number): Promise<number> {
     if (n <= 2) {
       throw new Error(`Bad number: ${n}`);
@@ -46,7 +46,7 @@ Deno.test("pooledMap() handles errors", async () => {
   assertEquals(mappedNumbers, [3]);
 });
 
-Deno.test("pooledMap() returns ordered items", async () => {
+Deno.test("pooledMap() returns ordered items", async function () {
   function getRandomInt(min: number, max: number): number {
     min = Math.ceil(min);
     max = Math.floor(max);

From 656a3343695f09f736d5743b234150365b0258b8 Mon Sep 17 00:00:00 2001
From: Michael Herzner <michael.b.herzner@gmail.com>
Date: Thu, 11 Apr 2024 22:39:08 +0200
Subject: [PATCH 2/2] chore: implement review remarks

---
 async/debounce_test.ts           | 10 +++++-----
 async/delay_test.ts              | 18 +++++++++---------
 async/mux_async_iterator.ts      |  1 -
 async/mux_async_iterator_test.ts | 12 ++++++------
 async/pool_test.ts               |  8 ++++----
 async/retry_test.ts              | 14 +++++++-------
 6 files changed, 31 insertions(+), 32 deletions(-)

diff --git a/async/debounce_test.ts b/async/debounce_test.ts
index 8b187aadc750..9c4bb3f23936 100644
--- a/async/debounce_test.ts
+++ b/async/debounce_test.ts
@@ -3,7 +3,7 @@ import { assertEquals, assertStrictEquals } from "../assert/mod.ts";
 import { debounce, type DebouncedFunction } from "./debounce.ts";
 import { delay } from "./delay.ts";
 
-Deno.test("debounce() handles called", async function () {
+Deno.test("debounce() handles called", async () => {
   let called = 0;
   const d = debounce(() => called++, 100);
   d();
@@ -16,7 +16,7 @@ Deno.test("debounce() handles called", async function () {
   assertEquals(d.pending, false);
 });
 
-Deno.test("debounce() handles cancelled", async function () {
+Deno.test("debounce() handles cancelled", async () => {
   let called = 0;
   const d = debounce(() => called++, 100);
   d();
@@ -30,7 +30,7 @@ Deno.test("debounce() handles cancelled", async function () {
   assertEquals(d.pending, false);
 });
 
-Deno.test("debounce() handles flush", function () {
+Deno.test("debounce() handles flush", () => {
   let called = 0;
   const d = debounce(() => called++, 100);
   d();
@@ -43,7 +43,7 @@ Deno.test("debounce() handles flush", function () {
   assertEquals(d.pending, false);
 });
 
-Deno.test("debounce() handles params and context", async function () {
+Deno.test("debounce() handles params and context", async () => {
   const params: Array<string | number> = [];
   const d: DebouncedFunction<[string, number]> = debounce(
     function (param1: string, param2: number) {
@@ -66,7 +66,7 @@ Deno.test("debounce() handles params and context", async function () {
   assertEquals(d.pending, false);
 });
 
-Deno.test("debounce() handles number and string types", async function () {
+Deno.test("debounce() handles number and string types", async () => {
   const params: Array<string> = [];
   const fn = (param: string) => params.push(param);
   const d: DebouncedFunction<[string]> = debounce(fn, 100);
diff --git a/async/delay_test.ts b/async/delay_test.ts
index f5d7fb6a34ed..2a7f7c38d3c7 100644
--- a/async/delay_test.ts
+++ b/async/delay_test.ts
@@ -15,7 +15,7 @@ function assertIsDefaultAbortReason(reason: unknown) {
   assertStrictEquals(reason.name, "AbortError");
 }
 
-Deno.test("delay()", async function () {
+Deno.test("delay()", async () => {
   const start = new Date();
   const delayedPromise = delay(100);
   const result = await delayedPromise;
@@ -24,7 +24,7 @@ Deno.test("delay()", async function () {
   assert(diff >= 100);
 });
 
-Deno.test("delay() handles abort", async function () {
+Deno.test("delay() handles abort", async () => {
   const start = new Date();
   const abort = new AbortController();
   const { signal } = abort;
@@ -36,7 +36,7 @@ Deno.test("delay() handles abort", async function () {
   assertIsDefaultAbortReason(cause);
 });
 
-Deno.test("delay() checks abort reason", async function (ctx) {
+Deno.test("delay() checks abort reason", async (ctx) => {
   async function assertRejectsReason(reason: unknown) {
     const start = new Date();
     const abort = new AbortController();
@@ -71,7 +71,7 @@ Deno.test("delay() checks abort reason", async function (ctx) {
   });
 });
 
-Deno.test("delay() handles non-aborted signal", async function () {
+Deno.test("delay() handles non-aborted signal", async () => {
   const start = new Date();
   const abort = new AbortController();
   const { signal } = abort;
@@ -82,7 +82,7 @@ Deno.test("delay() handles non-aborted signal", async function () {
   assert(diff >= 100);
 });
 
-Deno.test("delay() handles aborted signal after delay", async function () {
+Deno.test("delay() handles aborted signal after delay", async () => {
   const start = new Date();
   const abort = new AbortController();
   const { signal } = abort;
@@ -94,7 +94,7 @@ Deno.test("delay() handles aborted signal after delay", async function () {
   assert(diff >= 100);
 });
 
-Deno.test("delay() handles already aborted signal", async function () {
+Deno.test("delay() handles already aborted signal", async () => {
   const start = new Date();
   const abort = new AbortController();
   abort.abort();
@@ -106,13 +106,13 @@ Deno.test("delay() handles already aborted signal", async function () {
   assertIsDefaultAbortReason(cause);
 });
 
-Deno.test("delay() handles persitent option", async function () {
+Deno.test("delay() handles persitent option", async () => {
   using unrefTimer = stub(Deno, "unrefTimer");
   await delay(100, { persistent: false });
   assertSpyCalls(unrefTimer, 1);
 });
 
-Deno.test("delay() handles persistent option with reference error", async function () {
+Deno.test("delay() handles persistent option with reference error", async () => {
   using unrefTimer = stub(Deno, "unrefTimer", () => {
     throw new ReferenceError();
   });
@@ -122,7 +122,7 @@ Deno.test("delay() handles persistent option with reference error", async functi
 
 Deno.test({
   name: "delay() handles persistent option with error",
-  fn: async function () {
+  async fn() {
     using unrefTimer = stub(Deno, "unrefTimer", () => {
       throw new Error("Error!");
     });
diff --git a/async/mux_async_iterator.ts b/async/mux_async_iterator.ts
index 1bb4802881af..be53d0d8413a 100644
--- a/async/mux_async_iterator.ts
+++ b/async/mux_async_iterator.ts
@@ -82,7 +82,6 @@ export class MuxAsyncIterator<T> implements AsyncIterable<T> {
         for (const e of this.#throws) {
           throw e;
         }
-        this.#throws.length = 0;
       }
       // Clear the `yields` list and reset the `signal` promise.
       this.#yields.length = 0;
diff --git a/async/mux_async_iterator_test.ts b/async/mux_async_iterator_test.ts
index 7852b028713a..3fb883b0f022 100644
--- a/async/mux_async_iterator_test.ts
+++ b/async/mux_async_iterator_test.ts
@@ -25,7 +25,7 @@ class CustomAsyncIterable {
   }
 }
 
-Deno.test("MuxAsyncIterator()", async function () {
+Deno.test("MuxAsyncIterator()", async () => {
   const mux = new MuxAsyncIterator<number>();
   mux.add(gen123());
   mux.add(gen456());
@@ -34,14 +34,14 @@ Deno.test("MuxAsyncIterator()", async function () {
   assertEquals(results, new Set([1, 2, 3, 4, 5, 6]));
 });
 
-Deno.test("MuxAsyncIterator() works with no iterables", async function () {
+Deno.test("MuxAsyncIterator() works with no iterables", async () => {
   const mux = new MuxAsyncIterator<number>();
   const results = new Set(await Array.fromAsync(mux));
   assertEquals(results.size, 0);
   assertEquals(results, new Set([]));
 });
 
-Deno.test("MuxAsyncIterator() clears iterables after successful iteration", async function () {
+Deno.test("MuxAsyncIterator() clears iterables after successful iteration", async () => {
   const mux = new MuxAsyncIterator<number>();
   mux.add(gen123());
   mux.add(gen456());
@@ -54,7 +54,7 @@ Deno.test("MuxAsyncIterator() clears iterables after successful iteration", asyn
   assertEquals(results2, new Set([1, 2, 3]));
 });
 
-Deno.test("MuxAsyncIterator() takes async iterable as source", async function () {
+Deno.test("MuxAsyncIterator() takes async iterable as source", async () => {
   const mux = new MuxAsyncIterator<number>();
   mux.add(new CustomAsyncIterable());
   const results = new Set(await Array.fromAsync(mux));
@@ -62,7 +62,7 @@ Deno.test("MuxAsyncIterator() takes async iterable as source", async function ()
   assertEquals(results, new Set([1, 2, 3]));
 });
 
-Deno.test("MuxAsyncIterator() throws when the source throws", async function () {
+Deno.test("MuxAsyncIterator() throws when the source throws", async () => {
   const mux = new MuxAsyncIterator<number>();
   mux.add(gen123());
   mux.add(genThrows());
@@ -73,7 +73,7 @@ Deno.test("MuxAsyncIterator() throws when the source throws", async function ()
   );
 });
 
-Deno.test("MuxAsyncIterator() doesn't clear iterables after throwing", async function () {
+Deno.test("MuxAsyncIterator() doesn't clear iterables after throwing", async () => {
   const mux = new MuxAsyncIterator<number>();
   mux.add(genThrows());
   await assertRejects(
diff --git a/async/pool_test.ts b/async/pool_test.ts
index 8c2870cd1ce6..e49c38324251 100644
--- a/async/pool_test.ts
+++ b/async/pool_test.ts
@@ -8,7 +8,7 @@ import {
   assertStringIncludes,
 } from "../assert/mod.ts";
 
-Deno.test("pooledMap()", async function () {
+Deno.test("pooledMap()", async () => {
   const start = new Date();
   const results = pooledMap(
     2,
@@ -22,7 +22,7 @@ Deno.test("pooledMap()", async function () {
   assert(diff < 3000);
 });
 
-Deno.test("pooledMap() handles errors", async function () {
+Deno.test("pooledMap() handles errors", async () => {
   async function mapNumber(n: number): Promise<number> {
     if (n <= 2) {
       throw new Error(`Bad number: ${n}`);
@@ -46,7 +46,7 @@ Deno.test("pooledMap() handles errors", async function () {
   assertEquals(mappedNumbers, [3]);
 });
 
-Deno.test("pooledMap() returns ordered items", async function () {
+Deno.test("pooledMap() returns ordered items", async () => {
   function getRandomInt(min: number, max: number): number {
     min = Math.ceil(min);
     max = Math.floor(max);
@@ -66,7 +66,7 @@ Deno.test("pooledMap() returns ordered items", async function () {
   assertEquals(returned, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
 });
 
-Deno.test("pooledMap() checks browser compat", async function () {
+Deno.test("pooledMap() checks browser compat", async () => {
   // Simulates the environment where Symbol.asyncIterator is not available
   const asyncIterFunc = ReadableStream.prototype[Symbol.asyncIterator];
   // deno-lint-ignore no-explicit-any
diff --git a/async/retry_test.ts b/async/retry_test.ts
index e348bc3d5593..7bf1a811a046 100644
--- a/async/retry_test.ts
+++ b/async/retry_test.ts
@@ -15,7 +15,7 @@ function generateErroringFunction(errorsBeforeSucceeds: number) {
   };
 }
 
-Deno.test("retry()", async function () {
+Deno.test("retry()", async () => {
   const threeErrors = generateErroringFunction(3);
   const result = await retry(threeErrors, {
     minTimeout: 100,
@@ -23,7 +23,7 @@ Deno.test("retry()", async function () {
   assertEquals(result, 3);
 });
 
-Deno.test("retry() fails after max errors is passed", async function () {
+Deno.test("retry() fails after max errors is passed", async () => {
   const fiveErrors = generateErroringFunction(5);
   await assertRejects(() =>
     retry(fiveErrors, {
@@ -32,7 +32,7 @@ Deno.test("retry() fails after max errors is passed", async function () {
   );
 });
 
-Deno.test("retry() waits four times by default", async function () {
+Deno.test("retry() waits four times by default", async () => {
   let callCount = 0;
   const onlyErrors = function () {
     callCount++;
@@ -56,7 +56,7 @@ Deno.test("retry() waits four times by default", async function () {
 
 Deno.test(
   "retry() throws if minTimeout is less than maxTimeout",
-  async function () {
+  async () => {
     await assertRejects(() =>
       retry(() => {}, {
         minTimeout: 1000,
@@ -68,7 +68,7 @@ Deno.test(
 
 Deno.test(
   "retry() throws if maxTimeout is less than 0",
-  async function () {
+  async () => {
     await assertRejects(() =>
       retry(() => {}, {
         maxTimeout: -1,
@@ -79,7 +79,7 @@ Deno.test(
 
 Deno.test(
   "retry() throws if jitter is bigger than 1",
-  async function () {
+  async () => {
     await assertRejects(() =>
       retry(() => {}, {
         jitter: 2,
@@ -91,7 +91,7 @@ Deno.test(
 Deno.test("retry() checks backoff function timings", async (t) => {
   const originalMathRandom = Math.random;
 
-  await t.step("wait fixed times without jitter", async function () {
+  await t.step("wait fixed times without jitter", async () => {
     using time = new FakeTime();
     let resolved = false;
     const checkResolved = async () => {