test(async): improve test coverage (#4567)

This commit is contained in:
Michael Herzner 2024-04-11 23:23:54 +02:00 committed by GitHub
parent 7396d36ce7
commit 61183bcbcc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 142 additions and 47 deletions

View File

@ -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,

View File

@ -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",
);
});

View File

@ -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",
);
}

View File

@ -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);

View File

@ -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 = () => {

View File

@ -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) {
@ -13,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;
@ -22,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;
@ -34,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();
@ -69,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;
@ -80,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;
@ -92,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();
@ -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 () => {
using unrefTimer = stub(Deno, "unrefTimer");
await delay(100, { persistent: false });
assertSpyCalls(unrefTimer, 1);
});
Deno.test("delay() handles persistent option with reference error", async () => {
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",
async fn() {
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,
});

View File

@ -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;

View File

@ -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,7 +34,27 @@ Deno.test("MuxAsyncIterator()", async function () {
assertEquals(results, new Set([1, 2, 3, 4, 5, 6]));
});
Deno.test("MuxAsyncIterator() takes async iterable as source", 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 () => {
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 () => {
const mux = new MuxAsyncIterator<number>();
mux.add(new CustomAsyncIterable());
const results = new Set(await Array.fromAsync(mux));
@ -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 () => {
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 () => {
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",
);
});

View File

@ -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,
@ -66,7 +66,7 @@ Deno.test("pooledMap() returns ordered items", async () => {
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

View File

@ -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 () => {