-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathanalysis-queue.test.ts
More file actions
108 lines (91 loc) · 3.24 KB
/
Copy pathanalysis-queue.test.ts
File metadata and controls
108 lines (91 loc) · 3.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import {
AnalysisJobQueue,
buildAnalysisJobKey,
markerFileName,
} from "../src/analysis-queue";
import type { AnalysisJobPayload } from "../src/types";
function payload(overrides: Partial<AnalysisJobPayload> = {}): AnalysisJobPayload {
return {
installationId: 1,
owner: "acme",
repo: "app",
pr: 42,
headSha: "aaa111",
...overrides,
};
}
describe("AnalysisJobQueue", () => {
test("buildAnalysisJobKey includes owner/repo/pr/sha", () => {
expect(buildAnalysisJobKey(payload())).toBe("pr:acme/app/42:aaa111");
});
test("skips duplicate completed or in-flight jobs for same key", async () => {
const queue = new AnalysisJobQueue({ maxConcurrent: 1 });
let runs = 0;
const first = queue.enqueue(payload(), async () => {
runs += 1;
await new Promise((r) => setTimeout(r, 40));
});
expect(first.enqueued).toBe(true);
const second = queue.enqueue(payload(), async () => {
runs += 1;
});
expect(second.enqueued).toBe(false);
expect(second.reason).toBe("in_flight");
await new Promise((r) => setTimeout(r, 80));
expect(runs).toBe(1);
const third = queue.enqueue(payload(), async () => {
runs += 1;
});
expect(third.enqueued).toBe(false);
expect(third.reason).toBe("duplicate");
});
test("supersedes older SHAs on synchronize", async () => {
const queue = new AnalysisJobQueue({ maxConcurrent: 1 });
const order: string[] = [];
queue.enqueue(payload({ headSha: "oldsha" }), async (job) => {
await new Promise((r) => setTimeout(r, 60));
if (job.status === "superseded") {
return;
}
order.push(job.payload.headSha);
});
const next = queue.enqueue(payload({ headSha: "newsha" }), async (job) => {
order.push(job.payload.headSha);
});
expect(next.enqueued).toBe(true);
expect(next.reason).toBe("superseded_prior");
const old = queue.getJob(buildAnalysisJobKey(payload({ headSha: "oldsha" })));
expect(old?.status).toBe("superseded");
await new Promise((r) => setTimeout(r, 100));
expect(order).toEqual(["newsha"]);
});
test("optional job markers skip completed keys across queue instances", async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "specsync-jobs-"));
try {
const queueA = new AnalysisJobQueue({ maxConcurrent: 1, markersDir: dir });
let runs = 0;
queueA.enqueue(payload(), async () => {
runs += 1;
});
await new Promise((r) => setTimeout(r, 50));
expect(runs).toBe(1);
const key = buildAnalysisJobKey(payload());
const markerPath = path.join(dir, markerFileName(key));
expect(fs.existsSync(markerPath)).toBe(true);
const marker = JSON.parse(fs.readFileSync(markerPath, "utf8"));
expect(marker.status).toBe("completed");
const queueB = new AnalysisJobQueue({ maxConcurrent: 1, markersDir: dir });
const again = queueB.enqueue(payload(), async () => {
runs += 1;
});
expect(again.enqueued).toBe(false);
expect(again.reason).toBe("marker_completed");
expect(runs).toBe(1);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
});