Skip to content
229 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

A run opens on its summary: what triggered it, its status, duration and artifacts, a graph of its jobs (what needs what, matrices folded, called workflows boxed, deploys with their address), annotations, and each job's summary; the job list groups the same way1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import {
5 GRAPH,
6 type GraphJob,
7 baseName,
8 columns,
9 connector,
10 crossings,
11 groupJobs,
12 layoutRun,
13 orderColumns,
14 standingOf,
15} from "./run-graph.ts";
16
17let next = 0;
18function job(key: string, needs: string[] = [], more: Partial<GraphJob> = {}): GraphJob {
19 return {
20 id: `job_${next++}`,
21 key,
22 name: key,
23 needs,
24 status: "completed",
25 conclusion: "success",
26 startedAt: null,
27 finishedAt: null,
28 ...more,
29 };
30}
31
32test("columns go by the longest path of needs", () => {
33 const units = groupJobs([job("lint"), job("build"), job("test", ["build"]), job("deploy", ["build", "test"])]);
34 const column = columns(units);
35 assert.deepEqual(Object.fromEntries(column), { lint: 0, build: 0, test: 1, deploy: 2 });
36});
37
38test("a need of a job not in the run is left out", () => {
39 const column = columns(groupJobs([job("a", ["gone"]), job("b", ["a"])]));
40 assert.deepEqual(Object.fromEntries(column), { a: 0, b: 1 });
41});
42
43test("a cycle cannot loop the layout", () => {
44 const jobs = [job("a", ["b"]), job("b", ["a"]), job("c", ["c"])];
45 const column = columns(groupJobs(jobs));
46 for (const value of column.values()) assert.ok(Number.isFinite(value) && value >= 0);
47 const layout = layoutRun(jobs);
48 // Never a connector going back a column.
49 assert.ok(layout.edges.length <= 1);
50 for (const edge of layout.edges) assert.ok(column.get(edge.from)! < column.get(edge.to)!);
51});
52
53test("ordering a column by its needs takes out a crossing", () => {
54 // As listed: a, b over x, y, with x needing b and y needing a: they cross.
55 const units = groupJobs([job("a"), job("b"), job("x", ["b"]), job("y", ["a"])]);
56 const column = columns(units);
57 const edges: [string, string][] = [
58 ["b", "x"],
59 ["a", "y"],
60 ];
61 assert.equal(crossings([["a", "b"], ["x", "y"]], edges), 1);
62 const order = orderColumns(units, column);
63 assert.deepEqual(order, [
64 ["a", "b"],
65 ["y", "x"],
66 ]);
67 assert.equal(crossings(order, edges), 0);
68});
69
70test("ordering keeps the order as listed when nothing crosses", () => {
71 const units = groupJobs([job("a"), job("b"), job("x", ["a"]), job("y", ["b"])]);
72 assert.deepEqual(orderColumns(units, columns(units)), [
73 ["a", "b"],
74 ["x", "y"],
75 ]);
76});
77
78test("a matrix's jobs are one node, named without their combination", () => {
79 const units = groupJobs([
80 job("build"),
81 job("test", ["build"], { name: "test (ubuntu-latest, 20)" }),
82 job("test", ["build"], { name: "test (ubuntu-latest, 22)" }),
83 job("test", ["build"], { name: "test (macos-latest, 22)" }),
84 ]);
85 assert.equal(units.length, 2);
86 const matrix = units[1]!;
87 assert.equal(matrix.kind, "matrix");
88 assert.equal(matrix.label, "test");
89 assert.equal(matrix.kind === "matrix" && matrix.jobs.length, 3);
90 assert.equal(baseName("test (a, b)"), "test");
91 assert.equal(baseName("plain"), "plain");
92});
93
94test("an expanded matrix is taller, and its connectors still meet its first line", () => {
95 const jobs = [job("build"), job("test", ["build"], { name: "test (1)" }), job("test", ["build"], { name: "test (2)" })];
96 const closed = layoutRun(jobs);
97 const open = layoutRun(jobs, new Set(["test"]));
98 const node = (layout: typeof closed) => layout.nodes.find((n) => n.unit.key === "test")!;
99 assert.equal(node(closed).h, GRAPH.nodeHeight);
100 assert.equal(node(open).h, GRAPH.nodeHeight + 2 * GRAPH.rowHeight + 6);
101 assert.ok(node(open).expanded);
102 assert.equal(open.edges.length, 1);
103 assert.equal(open.edges[0]!.path, closed.edges[0]!.path);
104});
105
Merge branch 'claude/cool-newton-7nm8r9' (#36): real GitHub Actions on g1t, managed deploy workflows, g1t/deploy and the Sysbox runner 0.2.0. The merge keeps both sides' webhook events and API operations, says agent images need tar, guards the job engine's stop to Unix so the runner builds on Windows again, and keeps the moves tests' previews from going idle as the calendar passes their fixture's date.106test("a matrix's calls share the caller's box, each call's jobs named by its combination", () => {
107 const uses = "./.g1t/workflows/build.yml";
108 const jobs = [
109 job("build", [], { name: "Build (linux)", uses }),
110 job("build", [], { name: "Build (mac)", uses }),
111 job("build[0]/test", [], { name: "Build (linux) / test" }),
112 job("build[1]/test", [], { name: "Build (mac) / test" }),
113 job("deploy", ["build"]),
114 ];
115 const units = groupJobs(jobs);
116 assert.deepEqual(
117 units.map((unit) => [unit.kind, unit.key]),
118 [
119 ["call", "build"],
120 ["job", "deploy"],
121 ],
122 );
123 const call = units[0]!;
124 assert.ok(call.kind === "call");
125 assert.equal(call.callers.length, 2);
126 assert.deepEqual(
127 call.units.map((unit) => [unit.key, unit.label]),
128 [
129 ["build[0]/test", "Build (linux) / test"],
130 ["build[1]/test", "Build (mac) / test"],
131 ],
132 );
133});
134
A run opens on its summary: what triggered it, its status, duration and artifacts, a graph of its jobs (what needs what, matrices folded, called workflows boxed, deploys with their address), annotations, and each job's summary; the job list groups the same way135test("a called workflow's jobs sit in a box under the job calling it", () => {
136 const jobs = [
137 job("build", [], { name: "Build", uses: "./.g1t/workflows/build.yml" }),
138 job("build/compile", [], { name: "Build / compile" }),
139 job("build/package", ["build/compile"], { name: "Build / package" }),
140 job("deploy", ["build"]),
141 ];
142 const units = groupJobs(jobs);
143 assert.deepEqual(
144 units.map((unit) => [unit.kind, unit.key]),
145 [
146 ["call", "build"],
147 ["job", "deploy"],
148 ],
149 );
150 const call = units[0]!;
151 assert.ok(call.kind === "call");
152 assert.equal(call.uses, "./.g1t/workflows/build.yml");
153 assert.deepEqual(
154 call.units.map((unit) => unit.label),
155 ["compile", "package"],
156 );
157
158 const layout = layoutRun(jobs);
159 const box = layout.groups[0]!;
160 const compile = layout.nodes.find((n) => n.unit.key === "build/compile")!;
161 const pack = layout.nodes.find((n) => n.unit.key === "build/package")!;
162 const deploy = layout.nodes.find((n) => n.unit.key === "deploy")!;
163 // Inside the box, one after the other; the job needing the caller after the box.
164 assert.ok(compile.x >= box.x && compile.x + compile.w <= box.x + box.w);
165 assert.ok(pack.x > compile.x && pack.x + pack.w <= box.x + box.w);
166 assert.ok(compile.y >= box.y + GRAPH.groupHead);
167 assert.ok(deploy.x >= box.x + box.w + GRAPH.columnGap);
168 assert.deepEqual(
169 layout.edges.map((edge) => [edge.from, edge.to]).sort(),
170 [
171 ["build", "deploy"],
172 ["build/compile", "build/package"],
173 ],
174 );
175 assert.equal(layout.width, deploy.x + deploy.w + GRAPH.margin);
176});
177
178test("a connector's colour follows the run: red from a failure, moving into a running job", () => {
179 const layout = layoutRun([
180 job("a", [], { conclusion: "failure" }),
181 job("b", ["a"], { conclusion: "skipped" }),
182 job("c"),
183 job("d", ["c"], { status: "in_progress", conclusion: null }),
184 job("e", ["c"], { status: "waiting", conclusion: null }),
185 ]);
186 const state = Object.fromEntries(layout.edges.map((edge) => [`${edge.from}>${edge.to}`, edge.state]));
187 assert.deepEqual(state, { "a>b": "failed", "c>d": "active", "c>e": "idle" });
188});
189
190test("a deployment's address makes its node taller", () => {
191 const layout = layoutRun([job("deploy", [], { environment: "production", environmentUrl: "https://g1t.page" })]);
192 assert.equal(layout.nodes[0]!.h, GRAPH.nodeHeight + GRAPH.urlHeight);
193});
194
195test("several jobs stand together as running, then as the worst ending", () => {
196 assert.deepEqual(standingOf([job("a"), job("b", [], { status: "calling", conclusion: null })]), { status: "in_progress", conclusion: null });
197 assert.deepEqual(standingOf([job("a"), job("b", [], { conclusion: "failure" }), job("c", [], { conclusion: "cancelled" })]), {
198 status: "completed",
199 conclusion: "failure",
200 });
201 assert.deepEqual(standingOf([job("a", [], { conclusion: "skipped" })]), { status: "completed", conclusion: "skipped" });
202});
203
204test("connectors run straight on one row, and turn with rounded corners between rows", () => {
205 assert.equal(connector(0, 10, 50, 10), "M0 10H50");
206 const turn = connector(0, 10, 100, 70);
207 assert.match(turn, /^M0 10H\d+Q/);
208 assert.ok(turn.endsWith("H100"));
209});
210
211test("an empty run lays out as nothing", () => {
212 const layout = layoutRun([]);
213 assert.equal(layout.nodes.length, 0);
214 assert.equal(layout.edges.length, 0);
215});
216
217test("a matrix whose jobs deploy to one address shows it once", () => {
218 const url = "https://g1t.sh";
219 const shared = layoutRun([
220 job("core", [], { name: "core (a)", environmentUrl: url }),
221 job("core", [], { name: "core (b)", environmentUrl: url }),
222 ]);
223 assert.equal(shared.nodes[0]!.h, GRAPH.nodeHeight + GRAPH.urlHeight);
224 const mixed = layoutRun([
225 job("core", [], { name: "core (a)", environmentUrl: url }),
226 job("core", [], { name: "core (b)", environmentUrl: null }),
227 ]);
228 assert.equal(mixed.nodes[0]!.h, GRAPH.nodeHeight);
229});

This file's history is long; its oldest lines are credited to the oldest commit read.