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.

184 linesCodeBlameRaw
1import { test } from "node:test";
2import assert from "node:assert/strict";
3import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
4import { dirname, join, relative, resolve, sep } from "node:path";
5import { fileURLToPath } from "node:url";
6import { DESKTOP, EVERYTHING, READS, affected, needs, partCrates, workspace } from "./ci-affected.mjs";
7
8const ROOT = fileURLToPath(new URL("../..", import.meta.url));
9
10// contracts <- kit <- api, and work, which also takes rules; sshd alone.
11const CRATES = [
12 { name: "g1t-contracts", dir: "crates/contracts", deps: [] },
13 { name: "g1t-kit", dir: "crates/kit", deps: ["g1t-contracts"] },
14 { name: "g1t-rules", dir: "crates/rules", deps: ["g1t-contracts"] },
15 { name: "g1t-api", dir: "apps/api", deps: ["g1t-contracts", "g1t-kit"] },
16 { name: "g1t-work", dir: "services/work", deps: ["g1t-kit", "g1t-rules"] },
17 { name: "g1t-sshd", dir: "crates/sshd", deps: [] },
18 { name: "g1t-actions", dir: "crates/actions", deps: [] },
19];
20
21test("a crate's change tests it and every crate that depends on it, however far", () => {
22 assert.deepEqual(affected(CRATES, ["crates/sshd/src/main.rs"]), ["g1t-sshd"]);
23 assert.deepEqual(affected(CRATES, ["crates/kit/src/lib.rs"]), ["g1t-api", "g1t-kit", "g1t-work"]);
24 assert.deepEqual(affected(CRATES, ["crates/contracts/src/lib.rs"]), ["g1t-api", "g1t-contracts", "g1t-kit", "g1t-rules", "g1t-work"]);
25 assert.deepEqual(affected(CRATES, ["crates/rules/Cargo.toml", "crates/sshd/README.md"]), ["g1t-rules", "g1t-sshd", "g1t-work"]);
26 // A folder that only starts like a crate's is not it.
27 assert.deepEqual(affected(CRATES, ["crates/kitchen/x.rs"]), []);
28});
29
30test("the workspace's own files, the toolchain and this workflow test every crate", () => {
31 for (const file of [
32 "Cargo.lock",
33 "Cargo.toml",
34 "rust-toolchain.toml",
35 ".cargo/config.toml",
36 ".g1t/workflows/ci.yml",
37 "scripts/sccache.sh",
38 "scripts/deploy/ci-cache.mjs",
39 ]) {
40 assert.equal(affected(CRATES, ["crates/sshd/src/main.rs", file]), null, file);
41 }
42 // A crate's own manifest is only the crate's.
43 assert.deepEqual(affected(CRATES, ["crates/sshd/Cargo.toml"]), ["g1t-sshd"]);
44});
45
46test("a file outside the crates tests those that read it, and nothing when none does", () => {
47 assert.deepEqual(affected(CRATES, ["apps/web/src/app.tsx", "docs/notes.md", "services/chat/src/index.ts", "package-lock.json"]), []);
48 assert.deepEqual(affected(CRATES, [".g1t/workflows/deploy.yml"]), ["g1t-actions"]);
49 assert.deepEqual(affected(CRATES, ["packages/contracts/src/scopes.ts"]), ["g1t-api", "g1t-contracts", "g1t-kit", "g1t-rules", "g1t-work"]);
50 assert.deepEqual(affected(CRATES, []), []);
51});
52
53test("the desktop job runs for its own files and the build's, and for nothing else", () => {
54 for (const file of [
55 "apps/desktop/src-tauri/src/main.rs",
56 "apps/desktop/package.json",
57 "scripts/cli-release.test.mjs",
58 "scripts/apt-install.sh",
59 ".g1t/workflows/ci.yml",
60 ]) {
61 assert.equal(needs("desktop", ["crates/kit/src/lib.rs", file]), true, file);
62 }
63 assert.equal(needs("desktop", ["crates/kit/src/lib.rs", "apps/web/app/root.tsx", "Cargo.lock", "apps/desktopper/x.ts"]), false);
64});
65
66test("the site, sudo and the docs build for any change but Rust source alone", () => {
67 assert.equal(needs("build", ["crates/kit/src/lib.rs", "services/work/src/items.rs"]), false);
68 assert.equal(needs("build", ["crates/kit/src/lib.rs", "crates/kit/Cargo.toml"]), true);
69 assert.equal(needs("build", ["apps/docs/src/content/docs/index.md"]), true);
70 assert.equal(needs("build", []), false);
71});
72
73test("every file the desktop job names is there", () => {
74 // The toolchain's files, like EVERYTHING's, are there only when pinned.
75 for (const entry of DESKTOP.filter((e) => !EVERYTHING.includes(e))) assert.ok(existsSync(join(ROOT, entry)), entry);
76 // Its tests run each release script's test file.
77 const ci = readFileSync(join(ROOT, ".g1t/workflows/ci.yml"), "utf8");
78 const tests = ci.match(/node --test scripts\/desktop-release\.test\.mjs.*/)[0];
79 for (const test of tests.match(/scripts\/[\w-]+\.test\.mjs/g)) assert.ok(DESKTOP.includes(test), test);
80 // Outside the workspace, with no path dependency on it.
81 assert.doesNotMatch(readFileSync(join(ROOT, "apps/desktop/src-tauri/Cargo.toml"), "utf8"), /\bpath\s*=/);
82});
83
84test("a part's crates are read from its -p arguments", () => {
85 assert.deepEqual(partCrates(["-p", "g1t-kit", "-p", "g1t"]), ["g1t-kit", "g1t"]);
86 assert.throws(() => partCrates(["-p", "g1t-kit", "g1t"]));
87 assert.throws(() => partCrates(["--workspace"]));
88});
89
90test("the workspace comes from cargo metadata: its members, their folders and their path dependencies", () => {
91 const root = join(ROOT, "w");
92 const pkg = (name, dir, deps) => ({
93 id: `path+file://${name}`,
94 name,
95 manifest_path: join(root, dir, "Cargo.toml"),
96 dependencies: deps.map(([dep, path]) => ({ name: dep, kind: path === "dev" ? "dev" : null, ...(path ? { path: join(root, "x") } : {}) })),
97 });
98 const metadata = {
99 workspace_root: root,
100 workspace_members: ["path+file://g1t-kit", "path+file://g1t-api"],
101 packages: [
102 pkg("g1t-kit", "crates/kit", [["serde"]]),
103 pkg("g1t-api", "apps/api", [
104 ["g1t-kit", true],
105 ["g1t-kit", "dev"],
106 ["outside", true],
107 ]),
108 pkg("outside", "apps/desktop/src-tauri", []),
109 ],
110 };
111 assert.deepEqual(workspace(metadata), [
112 { name: "g1t-kit", dir: "crates/kit", deps: [] },
113 { name: "g1t-api", dir: "apps/api", deps: ["g1t-kit"] },
114 ]);
115});
116
117/** The real workspace's crates, from Cargo.toml's members: names and folders. */
118function members() {
119 const manifest = readFileSync(join(ROOT, "Cargo.toml"), "utf8");
120 const line = manifest.split("\n").find((l) => l.trimStart().startsWith("members"));
121 const dirs = [];
122 for (const member of [...line.matchAll(/"([^"]+)"/g)].map((m) => m[1])) {
123 if (member.endsWith("/*")) {
124 const parent = member.slice(0, -2);
125 for (const name of readdirSync(join(ROOT, parent)).sort()) if (existsSync(join(ROOT, parent, name, "Cargo.toml"))) dirs.push(`${parent}/${name}`);
126 } else dirs.push(member);
127 }
128 return dirs.map((dir) => ({ name: readFileSync(join(ROOT, dir, "Cargo.toml"), "utf8").match(/^name = "([^"]+)"/m)[1], dir, deps: [] }));
129}
130
131function rustFiles(dir) {
132 const out = [];
133 for (const name of readdirSync(dir)) {
134 if (name === "target" || name === "node_modules") continue;
135 const path = join(dir, name);
136 if (statSync(path).isDirectory()) out.push(...rustFiles(path));
137 else if (name.endsWith(".rs")) out.push(path);
138 }
139 return out;
140}
141
142test("every file a crate names outside its own folder tests that crate when it changes", () => {
143 const crates = members();
144 // include_str!/include_bytes! from the file's folder; CARGO_MANIFEST_DIR
145 // joined or concatenated, and other joins, from the crate's.
146 const named = [
147 [/include_(?:str|bytes)!\(\s*"([^"]+)"/g, "file"],
148 [/CARGO_MANIFEST_DIR"\),\s*"\/?([^"]+)"/g, "crate"],
149 [/\.join\(\s*"(\.\.[^"]*)"/g, "crate"],
150 ];
151 let checked = 0;
152 for (const crate of crates) {
153 const at = join(ROOT, crate.dir);
154 for (const file of rustFiles(at)) {
155 const source = readFileSync(file, "utf8");
156 for (const [pattern, from] of named) {
157 for (const [, path] of source.matchAll(pattern)) {
158 const target = resolve(from === "file" ? dirname(file) : at, path);
159 const rel = relative(ROOT, target).split(sep).join("/");
160 // Inside the crate, missing, or the whole repository (docs.rs
161 // walks named folders under it, in READS by hand).
162 if (!rel || rel.startsWith("..") || !relative(at, target).startsWith("..") || !existsSync(target)) continue;
163 const changed = statSync(target).isDirectory() ? `${rel}/x` : rel;
164 const tested = affected(crates, [changed]);
165 assert.ok(tested === null || tested.includes(crate.name), `${relative(ROOT, file)} reads ${rel}: add it to READS for ${crate.name}`);
166 checked++;
167 }
168 }
169 }
170 }
171 assert.ok(checked >= 20, `${checked}`);
172});
173
174test("what READS and EVERYTHING name is there, and read by crates of the workspace", () => {
175 const names = new Set(members().map((c) => c.name));
176 for (const [path, readers] of READS) {
177 assert.ok(existsSync(join(ROOT, path)), path);
178 for (const name of readers) assert.ok(names.has(name), `${path}: ${name}`);
179 }
180 for (const path of EVERYTHING) {
181 if (/^(rust-toolchain|\.cargo\/)/.test(path)) continue;
182 assert.ok(existsSync(join(ROOT, path)), path);
183 }
184});