// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. import { retry, RetryError } from "./retry.ts"; import { assertEquals, assertRejects } from "@std/assert"; import { FakeTime } from "@std/testing/time"; function generateErroringFunction(errorsBeforeSucceeds: number) { let errorCount = 0; return () => { if (errorCount >= errorsBeforeSucceeds) { return errorCount; } errorCount++; throw `Only errored ${errorCount} times`; }; } Deno.test("retry()", async () => { const threeErrors = generateErroringFunction(3); const result = await retry(threeErrors, { minTimeout: 100, }); assertEquals(result, 3); }); Deno.test("retry() fails after five errors by default", async () => { const fiveErrors = generateErroringFunction(5); await assertRejects(() => retry(fiveErrors, { minTimeout: 100, }) ); }); Deno.test("retry() fails after five errors when undefined is passed", async () => { const fiveErrors = generateErroringFunction(5); await assertRejects(() => // @ts-expect-error: explicitly giving undefined retry(fiveErrors, { maxAttempts: undefined, minTimeout: 100, }) ); }); Deno.test("retry() waits four times by default", async () => { let callCount = 0; const onlyErrors = function () { callCount++; throw new Error("Failure"); }; using time = new FakeTime(); const callCounts: Array = []; const promise = retry(onlyErrors); queueMicrotask(() => callCounts.push(callCount)); await time.next(); queueMicrotask(() => callCounts.push(callCount)); await time.next(); queueMicrotask(() => callCounts.push(callCount)); await time.next(); queueMicrotask(() => callCounts.push(callCount)); await time.next(); queueMicrotask(() => callCounts.push(callCount)); await assertRejects(() => promise, RetryError); assertEquals(callCounts, [1, 2, 3, 4, 5]); }); Deno.test( "retry() throws if minTimeout is less than maxTimeout", async () => { await assertRejects( () => retry(() => {}, { minTimeout: 1000, maxTimeout: 100, }), TypeError, "Cannot retry as 'minTimeout' must be <= 'maxTimeout': current values 'minTimeout=1000', 'maxTimeout=100'", ); }, ); Deno.test( "retry() throws if maxTimeout is less than 0", async () => { await assertRejects( () => retry(() => {}, { maxTimeout: -1, }), TypeError, "Cannot retry as 'maxTimeout' must be positive: current value is -1", ); }, ); Deno.test( "retry() throws if jitter is bigger than 1", async () => { await assertRejects( () => retry(() => {}, { jitter: 2, }), TypeError, "Cannot retry as 'jitter' must be <= 1: current value is 2", ); }, ); Deno.test("retry() checks backoff function timings", async (t) => { const originalMathRandom = Math.random; await t.step("wait fixed times without jitter", async () => { using time = new FakeTime(); let resolved = false; const checkResolved = async () => { try { await retry(() => { throw new Error("Failure"); }, { jitter: 0 }); } catch { resolved = true; } }; const promise = checkResolved(); const startTime = time.now; await time.nextAsync(); assertEquals(time.now - startTime, 1000); await time.nextAsync(); assertEquals(time.now - startTime, 3000); await time.nextAsync(); assertEquals(time.now - startTime, 7000); await time.nextAsync(); assertEquals(time.now - startTime, 15000); assertEquals(resolved, false); await time.runMicrotasks(); assertEquals(time.now - startTime, 15000); assertEquals(resolved, true); await time.runAllAsync(); assertEquals(time.now - startTime, 15000); await promise; }); Math.random = originalMathRandom; });