Skip to content

Commit

Permalink
chore: internal refactor of cron (#271)
Browse files Browse the repository at this point in the history
Co-authored-by: Wojtek Czekalski <me@wczekalski.com>
  • Loading branch information
benjie and wokalski committed Jun 24, 2022
1 parent 79f2160 commit abcb9de
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 73 deletions.
51 changes: 1 addition & 50 deletions __tests__/cron.test.ts
@@ -1,5 +1,4 @@
import { parseCronItem, run, TimestampDigest } from "../src";
import { cronItemMatches } from "../src/cronMatcher";
import { run } from "../src";
import {
ESCAPED_GRAPHILE_WORKER_SCHEMA,
EventMonitor,
Expand Down Expand Up @@ -145,51 +144,3 @@ test("backfills if identifier already registered (25h)", () =>
expect(jobs.every((j) => j.task_identifier === "do_it")).toBe(true);
}
}));

describe("matches datetime", () => {
const makeMatcher = (pattern: string) => (digest: TimestampDigest) => {
return cronItemMatches(
parseCronItem({ pattern, task: "" }, "test"),
digest,
);
};

const _0_0_1_1_0 = { min: 0, hour: 0, date: 1, month: 1, dow: 0 };
const _0_0_1_1_5 = { ..._0_0_1_1_0, dow: 5 };
const _0_0_1_1_4 = { ..._0_0_1_1_0, dow: 4 };
const _0_15_1_7_0 = { ..._0_0_1_1_0, hour: 15, month: 7 };
const _0_15_4_7_2 = { ..._0_0_1_1_0, hour: 15, date: 4, month: 7, dow: 2 };
const _0_15_4_7_5 = { ..._0_0_1_1_0, hour: 15, date: 4, month: 7, dow: 5 };
const _6_15_4_7_5 = { min: 6, hour: 15, date: 4, month: 7, dow: 5 };

test("every minute", () => {
const match = makeMatcher("* * * * *");
expect(match(_0_0_1_1_0)).toBeTruthy();
expect(match(_0_0_1_1_5)).toBeTruthy();
expect(match(_0_0_1_1_4)).toBeTruthy();
expect(match(_0_15_1_7_0)).toBeTruthy();
expect(match(_0_15_4_7_2)).toBeTruthy();
expect(match(_0_15_4_7_5)).toBeTruthy();
expect(match(_6_15_4_7_5)).toBeTruthy();
});
test("dow range", () => {
const match = makeMatcher("* * * * 5-6");
expect(match(_0_0_1_1_0)).toBeFalsy();
expect(match(_0_0_1_1_5)).toBeTruthy();
expect(match(_0_0_1_1_4)).toBeFalsy();
expect(match(_0_15_1_7_0)).toBeFalsy();
expect(match(_0_15_4_7_2)).toBeFalsy();
expect(match(_0_15_4_7_5)).toBeTruthy();
expect(match(_6_15_4_7_5)).toBeTruthy();
});
test("dow and date range", () => {
const match = makeMatcher("0-5 15 3-4 7 0-2");
expect(match(_0_0_1_1_0)).toBeFalsy();
expect(match(_0_0_1_1_5)).toBeFalsy();
expect(match(_0_0_1_1_4)).toBeFalsy();
expect(match(_0_15_1_7_0)).toBeTruthy();
expect(match(_0_15_4_7_2)).toBeTruthy();
expect(match(_0_15_4_7_5)).toBeTruthy();
expect(match(_6_15_4_7_5)).toBeFalsy();
});
});
47 changes: 47 additions & 0 deletions __tests__/cronMatcher.test.ts
@@ -0,0 +1,47 @@
import { cronItemMatches, parseCronRangeString } from "../src/cronMatcher";
import { TimestampDigest } from "../src/interfaces";

describe("matches datetime", () => {
const makeMatcher = (pattern: string) => (digest: TimestampDigest) => {
return cronItemMatches(parseCronRangeString(pattern, "test"), digest);
};

const _0_0_1_1_0 = { min: 0, hour: 0, date: 1, month: 1, dow: 0 };
const _0_0_1_1_5 = { ..._0_0_1_1_0, dow: 5 };
const _0_0_1_1_4 = { ..._0_0_1_1_0, dow: 4 };
const _0_15_1_7_0 = { ..._0_0_1_1_0, hour: 15, month: 7 };
const _0_15_4_7_2 = { ..._0_0_1_1_0, hour: 15, date: 4, month: 7, dow: 2 };
const _0_15_4_7_5 = { ..._0_0_1_1_0, hour: 15, date: 4, month: 7, dow: 5 };
const _6_15_4_7_5 = { min: 6, hour: 15, date: 4, month: 7, dow: 5 };

test("every minute", () => {
const match = makeMatcher("* * * * *");
expect(match(_0_0_1_1_0)).toBeTruthy();
expect(match(_0_0_1_1_5)).toBeTruthy();
expect(match(_0_0_1_1_4)).toBeTruthy();
expect(match(_0_15_1_7_0)).toBeTruthy();
expect(match(_0_15_4_7_2)).toBeTruthy();
expect(match(_0_15_4_7_5)).toBeTruthy();
expect(match(_6_15_4_7_5)).toBeTruthy();
});
test("dow range", () => {
const match = makeMatcher("* * * * 5-6");
expect(match(_0_0_1_1_0)).toBeFalsy();
expect(match(_0_0_1_1_5)).toBeTruthy();
expect(match(_0_0_1_1_4)).toBeFalsy();
expect(match(_0_15_1_7_0)).toBeFalsy();
expect(match(_0_15_4_7_2)).toBeFalsy();
expect(match(_0_15_4_7_5)).toBeTruthy();
expect(match(_6_15_4_7_5)).toBeTruthy();
});
test("dow and date range", () => {
const match = makeMatcher("0-5 15 3-4 7 0-2");
expect(match(_0_0_1_1_0)).toBeFalsy();
expect(match(_0_0_1_1_5)).toBeFalsy();
expect(match(_0_0_1_1_4)).toBeFalsy();
expect(match(_0_15_1_7_0)).toBeTruthy();
expect(match(_0_15_4_7_2)).toBeTruthy();
expect(match(_0_15_4_7_5)).toBeTruthy();
expect(match(_6_15_4_7_5)).toBeFalsy();
});
});
21 changes: 18 additions & 3 deletions src/cronMatcher.ts
@@ -1,16 +1,17 @@
import {
CRONTAB_NUMBER,
CRONTAB_RANGE,
CRONTAB_TIME_PARTS,
CRONTAB_WILDCARD,
} from "./cronConstants";
import { ParsedCronItem, TimestampDigest } from "./interfaces";
import { ParsedCronMatch, TimestampDigest } from "./interfaces";

/**
* Returns true if the cronItem should fire for the given timestamp digest,
* false otherwise.
*/
export function cronItemMatches(
cronItem: ParsedCronItem,
cronItem: ParsedCronMatch,
digest: TimestampDigest,
): boolean {
const { min, hour, date, month, dow } = digest;
Expand Down Expand Up @@ -136,7 +137,10 @@ const parseCrontabRange = (
return uniqueNumbers;
};

export function parseCrontabRanges(matches: string[], source: string) {
export function parseCrontabRanges(
matches: string[],
source: string,
): ParsedCronMatch {
const minutes = parseCrontabRange(
`minutes range in ${source}`,
matches[1],
Expand Down Expand Up @@ -170,3 +174,14 @@ export function parseCrontabRanges(matches: string[], source: string) {
);
return { minutes, hours, dates, months, dows };
}

export const parseCronRangeString = (
pattern: string,
source: string,
): ParsedCronMatch => {
const matches = CRONTAB_TIME_PARTS.exec(pattern);
if (!matches) {
throw new Error(`Invalid cron pattern '${pattern}' in ${source}`);
}
return parseCrontabRanges(matches, source);
};
11 changes: 3 additions & 8 deletions src/crontab.ts
Expand Up @@ -9,11 +9,10 @@ import {
CRONTAB_OPTIONS_MAX,
CRONTAB_OPTIONS_PRIORITY,
CRONTAB_OPTIONS_QUEUE,
CRONTAB_TIME_PARTS,
PERIOD_DURATIONS,
TIMEPHRASE_PART,
} from "./cronConstants";
import { parseCrontabRanges } from "./cronMatcher";
import { parseCronRangeString, parseCrontabRanges } from "./cronMatcher";
import { CronItem, CronItemOptions, ParsedCronItem } from "./interfaces";

/**
Expand Down Expand Up @@ -267,12 +266,8 @@ export const parseCronItem = (
payload = {},
identifier = task,
} = cronItem;
const matches = CRONTAB_TIME_PARTS.exec(pattern);
if (!matches) {
throw new Error(`Invalid cron pattern '${pattern}' in ${source}`);
}
const { minutes, hours, dates, months, dows } = parseCrontabRanges(
matches,
const { minutes, hours, dates, months, dows } = parseCronRangeString(
pattern,
source,
);
const item: ParsedCronItem = {
Expand Down
30 changes: 18 additions & 12 deletions src/interfaces.ts
Expand Up @@ -184,6 +184,23 @@ export interface CronItemOptions {
priority?: number;
}

/**
* Crontab ranges from the minute, hour, day of month, month and day of week
* parts of the crontab line
*/
export interface ParsedCronMatch {
/** Minutes (0-59) on which to run the item; must contain unique numbers from the allowed range, ordered ascending. */
minutes: number[];
/** Hours (0-23) on which to run the item; must contain unique numbers from the allowed range, ordered ascending. */
hours: number[];
/** Dates (1-31) on which to run the item; must contain unique numbers from the allowed range, ordered ascending. */
dates: number[];
/** Months (1-12) on which to run the item; must contain unique numbers from the allowed range, ordered ascending. */
months: number[];
/** Days of the week (0-6) on which to run the item; must contain unique numbers from the allowed range, ordered ascending. */
dows: number[];
}

/**
* A recurring task schedule; this may represent a line in the `crontab` file,
* or may be the result of calling `parseCronItems` on a list of `CronItem`s
Expand All @@ -204,18 +221,7 @@ export interface CronItemOptions {
* constructing `ParsedCronItem`s internally, you should use the Graphile
* Worker helpers to construct this type.
*/
export interface ParsedCronItem {
/** Minutes (0-59) on which to run the item; must contain unique numbers from the allowed range, ordered ascending. */
minutes: number[];
/** Hours (0-23) on which to run the item; must contain unique numbers from the allowed range, ordered ascending. */
hours: number[];
/** Dates (1-31) on which to run the item; must contain unique numbers from the allowed range, ordered ascending. */
dates: number[];
/** Months (1-12) on which to run the item; must contain unique numbers from the allowed range, ordered ascending. */
months: number[];
/** Days of the week (0-6) on which to run the item; must contain unique numbers from the allowed range, ordered ascending. */
dows: number[];

export interface ParsedCronItem extends ParsedCronMatch {
/** The identifier of the task to execute */
task: string;

Expand Down

0 comments on commit abcb9de

Please sign in to comment.