Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
168 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import {
5 answered,
6 capReached,
7 describeError,
8 OWED_GIVE_UP_MS,
9 OWED_MAX_DELAY_SECONDS,
10 owedDelaySeconds,
11 recordOwed,
12 stoppedShort,
13 tellStopped,
14 withinTimeCap,
15} from "./lifecycle.ts";
16
17const quiet = { waitMs: 0 };
18
19test("a stop that is told the first time is told once and logs nothing", async () => {
20 const logged: unknown[][] = [];
21 let calls = 0;
22 const told = await tellStopped("checks", async () => void calls++, { ...quiet, log: (...data) => logged.push(data) });
23 assert.equal(told, true);
24 assert.equal(calls, 1);
25 assert.deepEqual(logged, []);
26});
27
28test("a stop that fails once is tried again, and gets through", async () => {
29 const logged: unknown[][] = [];
30 let calls = 0;
31 const told = await tellStopped(
32 "queue",
33 async () => {
34 calls++;
35 if (calls === 1) throw new Error("report_queue failed with status 500");
36 },
37 { ...quiet, log: (...data) => logged.push(data) },
38 );
39 assert.equal(told, true);
40 assert.equal(calls, 2);
41 assert.deepEqual(logged, []);
42});
43
44test("a stop that cannot be told never throws, and is logged with what and why", async () => {
45 const logged: unknown[][] = [];
46 let calls = 0;
47 const told = await tellStopped(
48 "actions",
49 async () => {
50 calls++;
51 throw new Error("job_report answered 503");
52 },
53 { ...quiet, log: (...data) => logged.push(data) },
54 );
55 assert.equal(told, false);
56 assert.equal(calls, 2);
57 assert.deepEqual(logged, [["sandbox stop not reported", "actions", "job_report answered 503"]]);
58});
59
60test("whatever is thrown, even nothing, is logged as one line", async () => {
61 const logged: unknown[][] = [];
62 await tellStopped("plan", () => Promise.reject(undefined), { ...quiet, attempts: 1, log: (...data) => logged.push(data) });
63 assert.deepEqual(logged, [["sandbox stop not reported", "plan", "no reason given"]]);
64 assert.equal(describeError("gone"), "gone");
65 assert.equal(describeError({ code: 7 }), '{"code":7}');
66 assert.equal(describeError(new TypeError("bad")), "bad");
67});
68
69test("a service that cannot answer is a failure; one that refuses is not", async () => {
70 await answered("job_report", new Response("{}", { status: 200 }));
71 // The deployments service's answer for a build that already finished.
72 await answered("deploy fail", new Response("{}", { status: 404 }));
73 await answered("job_report", new Response("{}", { status: 409 }));
74 await assert.rejects(answered("job_report", new Response("worker threw", { status: 500 })), /job_report answered 500: worker threw/);
75 await assert.rejects(answered("deploy fail", new Response("", { status: 503 })), /^Error: deploy fail answered 503$/);
76 await assert.rejects(answered("job_report", new Response("slow down", { status: 429 })), /429/);
77});
78
79test("a run inside its time cap is not stopped for inactivity", () => {
80 const started = Date.UTC(2026, 9, 8, 12, 0, 0);
81 const minute = 60_000;
82 const grace = 180;
83 // A 240-minute cap outlives the 100-minute sleepAfter.
84 assert.equal(withinTimeCap(started, 240, started + 100 * minute, grace), true);
85 assert.equal(withinTimeCap(started, 240, started + 242 * minute, grace), true);
86 // Past the cap and its alarm's grace: the library may stop it.
87 assert.equal(withinTimeCap(started, 240, started + 243 * minute, grace), false);
88 assert.equal(withinTimeCap(started, 90, started + 100 * minute, grace), false);
89});
90
91test("a sandbox with no cap or no start is stopped for inactivity as before", () => {
92 const now = Date.now();
93 assert.equal(withinTimeCap(undefined, 90, now, 180), false);
94 assert.equal(withinTimeCap(now - 1000, undefined, now, 180), false);
95 assert.equal(withinTimeCap(now - 1000, null, now, 180), false);
96 assert.equal(withinTimeCap(now - 1000, 0, now, 180), false);
97});
98
99test("an earlier attempt's time cap does not stop the attempt running now", () => {
100 const minute = 60_000;
101 // Desktop release, Linux job: attempt 1 started 12:47:17 with a 60-minute
102 // cap, so its alarm fired at 13:50:17, four minutes into attempt 3 in the
103 // same Durable Object.
104 const attempt3 = Date.UTC(2026, 9, 12, 13, 46, 15);
105 const alarm = Date.UTC(2026, 9, 12, 13, 50, 17);
106 assert.equal(capReached(attempt3, 60, alarm), false);
107 // Its own alarm, at its cap and the alarm's grace, does stop it.
108 assert.equal(capReached(attempt3, 60, attempt3 + 63 * minute), true);
109 assert.equal(capReached(attempt3, 60, attempt3 + 60 * minute), true);
110 assert.equal(capReached(attempt3, 60, attempt3 + 60 * minute - 1), false);
111});
112
113test("a sandbox with no run or no cap is never stopped for time", () => {
114 const now = Date.now();
115 assert.equal(capReached(undefined, 60, now), false);
116 assert.equal(capReached(now - 120 * 60_000, undefined, now), false);
117 assert.equal(capReached(now - 120 * 60_000, 0, now), false);
118});
119
120test("a sandbox g1t stopped fails even when its exit code says 0", () => {
121 assert.equal(stoppedShort(0, null), false);
122 assert.equal(stoppedShort(0, "It ran into its time cap of 60 minutes."), true);
123 assert.equal(stoppedShort(137, null), true);
124 assert.equal(stoppedShort(1, "why"), true);
125});
126
127test("sandbox time billing confirms is forgotten; the rest is kept and tried again later", async () => {
128 const now = 1_000_000_000;
129 const owed = [
130 { usage: { reference: "sandbox/a/1" }, attempts: 0, since: now },
131 { usage: { reference: "sandbox/b/2" }, attempts: 2, since: now - 1000 },
132 ];
133 const sent: string[] = [];
134 const result = await recordOwed(
135 owed,
136 async (usage) => {
137 sent.push(usage.reference);
138 if (usage.reference === "sandbox/b/2") throw new Error("billing answered 503");
139 return true;
140 },
141 now,
142 );
143 assert.deepEqual(sent, ["sandbox/a/1", "sandbox/b/2"]);
144 assert.deepEqual(result.recorded.map((entry) => entry.usage.reference), ["sandbox/a/1"]);
145 // The same usage, under the same reference: billing records it once.
146 assert.deepEqual(result.kept, [{ usage: { reference: "sandbox/b/2" }, attempts: 3, since: now - 1000 }]);
147 assert.deepEqual(result.lost, []);
148 assert.equal(result.retryInSeconds, owedDelaySeconds(3));
149});
150
151test("nothing kept means no retry; time tried for a week is given up", async () => {
152 const now = 1_000_000_000;
153 const done = await recordOwed([{ usage: { reference: "r" }, attempts: 0, since: now }], async () => true, now);
154 assert.equal(done.retryInSeconds, null);
155 const old = await recordOwed([{ usage: { reference: "r" }, attempts: 40, since: now - OWED_GIVE_UP_MS }], async () => false, now);
156 assert.deepEqual(old.kept, []);
157 assert.equal(old.lost.length, 1);
158 assert.equal(old.retryInSeconds, null);
159});
160
161test("tries back off from 30 seconds to an hour", () => {
162 assert.equal(owedDelaySeconds(0), 30);
163 assert.equal(owedDelaySeconds(1), 30);
164 assert.equal(owedDelaySeconds(2), 60);
165 assert.equal(owedDelaySeconds(5), 480);
166 assert.equal(owedDelaySeconds(8), OWED_MAX_DELAY_SECONDS);
167 assert.equal(owedDelaySeconds(1000), OWED_MAX_DELAY_SECONDS);
168});