Skip to content

Commit

Permalink
feat(runtime): add retry callback handler (#89)
Browse files Browse the repository at this point in the history
  • Loading branch information
c4spar authored Jul 16, 2022
1 parent 3e52bfb commit 34984c2
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 20 deletions.
7 changes: 6 additions & 1 deletion dev_deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@ export {
assertObjectMatch,
assertRejects,
assertStringIncludes,
} from "https://deno.land/std@0.130.0/testing/asserts.ts";
} from "https://deno.land/std@0.139.0/testing/asserts.ts";

export {
assertSpyCalls,
spy,
} from "https://deno.land/std@0.148.0/testing/mock.ts";
42 changes: 23 additions & 19 deletions src/runtime/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { ProcessError } from "./process_error.ts";
import { ProcessOutput } from "./process_output.ts";
import { $ } from "./shell.ts";

export type RetryCallback = (error: ProcessError) => boolean | Promise<boolean>;

export interface ProcessOptions {
// deno-lint-ignore ban-types
errorContext?: Function;
Expand All @@ -18,13 +20,13 @@ export class Process implements Promise<ProcessOutput> {
#stdout: Deno.RunOptions["stdout"] = $.stdout;
#stderr: Deno.RunOptions["stderr"] = $.stderr;
#baseError: ProcessError;
#maxRetries = 0;
#retries = 0;
#timeout = 0;
#timers: Array<number> = [];
#delay = 500;
#throwErrors = true;
#isKilled = false;
#shouldRetry?: RetryCallback;

constructor(cmd: string, { errorContext }: ProcessOptions = {}) {
this.#cmd = cmd;
Expand Down Expand Up @@ -113,8 +115,11 @@ export class Process implements Promise<ProcessOutput> {
return this.#resolve().then(({ stderr }) => stderr);
}

retry(retries: number): this {
this.#maxRetries = retries;
retry(retries: number | RetryCallback): this {
this.#shouldRetry = typeof retries === "number"
? (error) => error.retries < retries
: (error) => retries(error);

return this;
}

Expand Down Expand Up @@ -205,34 +210,33 @@ export class Process implements Promise<ProcessOutput> {
});

if (!status.success) {
output = ProcessError.merge(
const error = ProcessError.merge(
this.#baseError,
new ProcessError(output),
);

if (this.#throwErrors || this.#retries < this.#maxRetries) {
throw output;
if (await this.#shouldRetry?.(error)) {
if (this.#delay) {
await new Promise((resolve) =>
this.#timers.push(setTimeout(resolve, this.#delay))
);
}
this.#close();
this.#proc = null;
this.#retries++;

return this.#run();
} else if (this.#throwErrors) {
throw error;
}
output = error;
}
this.#close();
this.#closeTimer();

return output;
} catch (error) {
this.#close();

if (this.#retries < this.#maxRetries) {
this.#retries++;
this.#proc = null;

if (this.#delay) {
await new Promise((resolve) =>
this.#timers.push(setTimeout(resolve, this.#delay))
);
}

return this.#run();
}
this.#closeTimer();

throw error;
Expand Down
13 changes: 13 additions & 0 deletions test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import {
assert,
assertEquals,
assertRejects,
assertSpyCalls,
assertStringIncludes,
spy,
} from "./dev_deps.ts";

import { $, $e, $o, cd, path, ProcessError } from "./mod.ts";
Expand Down Expand Up @@ -171,6 +173,17 @@ Deno.test({
},
});

Deno.test({
name: "$ should retry command with custom callback if it fails",
async fn() {
const retrySpy = spy(({ retries }: ProcessError) => retries < 3);
const result = await $`exit 1`.noThrow.delay(100).retry(retrySpy);
assertEquals(result.retries, 3);
assertEquals(result.status.code, 1);
assertSpyCalls(retrySpy, 4);
},
});

Deno.test({
name: "$ should resolve stdout promise",
async fn() {
Expand Down

0 comments on commit 34984c2

Please sign in to comment.