Skip to content
Source
Sign inSign up
Open History to see the last commit

smoke.ts

130 lines5.3 KB

1// @effect-diagnostics nodeBuiltinImport:off - exercises real child processes and isolated workspaces.
2// @effect-diagnostics globalConsole:off - emits the deployment acceptance transcript.
3// @effect-diagnostics globalFetch:off - probes the deployed Worker directly.
4import * as NodeChildProcess from "node:child_process";
5import * as NodeCrypto from "node:crypto";
6import * as NodeFSP from "node:fs/promises";
7import * as NodeOS from "node:os";
8import * as NodePath from "node:path";
9​
10const project = NodePath.join(import.meta.dirname, "../../..");
11const env = {
12 ...process.env,
13 T3_TASK_CACHE_TELEMETRY: "off",
14 VP_REMOTE_CACHE: "read-write",
15 VP_REMOTE_CACHE_URL: undefined,
16};
17const root = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-cache-probe-"));
18const name = `t3-cache-probe-${NodeCrypto.randomUUID()}`;
19​
20async function run(directory: string, home = NodeOS.homedir()) {
21 return new Promise<string>((resolve, reject) => {
22 const child = NodeChildProcess.spawn(
23 NodePath.join(project, "node_modules/.bin/vp"),
24 ["run", "--cache", "build"],
25 {
26 cwd: NodePath.join(root, directory),
27 env: {
28 ...env,
29 HOME: home,
30 PATH: `${NodePath.join(project, "node_modules/.bin")}${NodePath.delimiter}${process.env.PATH}`,
31 },
32 stdio: ["ignore", "pipe", "pipe"],
33 },
34 );
35 let output = "";
36 child.stdout.on("data", (chunk) => {
37 output += chunk;
38 });
39 child.stderr.on("data", (chunk) => {
40 output += chunk;
41 });
42 child.on("error", reject);
43 child.on("close", (code) => (code === 0 ? resolve(output) : reject(new Error(output))));
44 });
46​
47try {
48 const { endpoint } = JSON.parse(
49 await NodeFSP.readFile(NodePath.join(project, ".task-cache.json"), "utf8"),
50 );
51 for (const [path, method] of [
52 ["fetch", "POST"],
53 ["blob/12345678-1234-1234-1234-123456789abc", "GET"],
54 ] as const) {
55 const response = await fetch(`${endpoint}/${path}`, { method });
56 await response.body?.cancel();
57 if (response.status !== 401)
58 throw new Error(`Anonymous ${path} returned ${response.status}, expected 401.`);
59 }
60 const unauthorized = await fetch(`${endpoint}/store`, { method: "POST" });
61 await unauthorized.body?.cancel();
62 if (unauthorized.status !== 401)
63 throw new Error(`Anonymous upload returned ${unauthorized.status}, expected 401.`);
64 for (const directory of ["a", "b"]) {
65 const cwd = NodePath.join(root, directory);
66 await NodeFSP.mkdir(cwd);
67 await NodeFSP.writeFile(NodePath.join(cwd, ".task-cache.json"), JSON.stringify({ endpoint }));
68 await NodeFSP.writeFile(
69 NodePath.join(cwd, "vite.config.ts"),
70 `import { remoteCacheUrl } from ${JSON.stringify(NodePath.join(project, "infra/task-cache/src/remoteCache.ts"))}; export default {run:{cache:{scripts:true,remote:{url:remoteCacheUrl(import.meta.dirname)}}}}`,
71 );
72 await NodeFSP.writeFile(
73 NodePath.join(cwd, "package.json"),
74 JSON.stringify({
75 name,
76 private: true,
77 scripts: { build: `node build.cjs ${name}` },
78 }),
79 );
80 await NodeFSP.writeFile(
81 NodePath.join(cwd, "build.cjs"),
82 'if(process.env.VP_REMOTE_CACHE_URL) throw new Error("Cache URL leaked into task environment"); const fs=require("node:fs"); fs.writeFileSync("output.txt",fs.readFileSync("input.txt")); console.log("computed");',
83 );
84 await NodeFSP.writeFile(NodePath.join(cwd, "input.txt"), "version-a");
85 }
86 console.log("First checkout:\n" + (await run("a")));
87 const readerFile = NodePath.join(NodeOS.homedir(), ".config/t3-task-cache/ci-reader.json");
88 let reader;
89 try {
90 reader = JSON.parse(await NodeFSP.readFile(readerFile, "utf8"));
91 } catch (error) {
92 if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") throw error;
93 const credentials = JSON.parse(
94 await NodeFSP.readFile(
95 NodePath.join(NodeOS.homedir(), ".config/t3-task-cache/credentials.json"),
96 "utf8",
97 ),
98 );
99 reader = credentials[endpoint];
100 }
101 const otherHome = NodePath.join(root, "other-developer");
102 await NodeFSP.mkdir(NodePath.join(otherHome, ".config/t3-task-cache"), { recursive: true });
103 await NodeFSP.writeFile(
104 NodePath.join(otherHome, ".config/t3-task-cache/credentials.json"),
105 JSON.stringify({
106 [endpoint]: reader,
107 }),
108 );
109 const second = await run("b", otherHome);
110 console.log("Cold second checkout:\n" + second);
111 if (!second.includes("remote cache hit"))
112 throw new Error("Second checkout did not reuse the remote result.");
113 if ((await NodeFSP.readFile(NodePath.join(root, "b/output.txt"), "utf8")) !== "version-a")
114 throw new Error("Output was not restored.");
115 await NodeFSP.writeFile(NodePath.join(root, "b/input.txt"), "version-b");
116 const changed = await run("b");
117 console.log("Changed input:\n" + changed);
118 if (
119 changed.includes("cache hit") ||
120 (await NodeFSP.readFile(NodePath.join(root, "b/output.txt"), "utf8")) !== "version-b"
121 )
122 throw new Error("Changed input reused stale output.");
123 await NodeFSP.rm(NodePath.join(root, "a/node_modules"), { recursive: true, force: true });
124 console.log("Original input after competing writer:\n" + (await run("a")));
125 console.log(
126 "PASS: authenticated upload, cold-checkout restoration, input validation, and anonymous-write rejection.",
127 );
128} finally {
129 await NodeFSP.rm(root, { recursive: true, force: true });