@scriptc/compiler 0.0.21 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ambient/scriptc-node-fallback.d.ts +117 -37
- package/dist/backend/cc-cache.test.d.ts +1 -0
- package/dist/backend/cc-cache.test.js +2507 -0
- package/dist/backend/cc-cache.test.js.map +1 -0
- package/dist/backend/cc.d.ts +69 -11
- package/dist/backend/cc.js +1937 -222
- package/dist/backend/cc.js.map +1 -1
- package/dist/backend/emission/emit-async.js +3 -1
- package/dist/backend/emission/emit-async.js.map +1 -1
- package/dist/backend/emission/emit-exprs.d.ts +10 -0
- package/dist/backend/emission/emit-exprs.js +482 -18
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emit-stmts.js +13 -5
- package/dist/backend/emission/emit-stmts.js.map +1 -1
- package/dist/backend/emission/emit-types.js +4 -1
- package/dist/backend/emission/emit-types.js.map +1 -1
- package/dist/backend/emission/emit-walkers.js +108 -7
- package/dist/backend/emission/emit-walkers.js.map +1 -1
- package/dist/backend/emission/emitter.d.ts +27 -1
- package/dist/backend/emission/emitter.js +84 -3
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/llvm/dyn.d.ts +1 -0
- package/dist/backend/llvm/dyn.js +305 -8
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.js +831 -23
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/shapes.js +3 -2
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/compat/fetch-profile.d.ts +118 -0
- package/dist/compat/fetch-profile.js +531 -0
- package/dist/compat/fetch-profile.js.map +1 -0
- package/dist/coverage/surface-manifest.js +69 -0
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +10 -0
- package/dist/diagnostics/diagnostic.js +27 -1
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/frontend/lowering/lower-builtins.d.ts +24 -15
- package/dist/frontend/lowering/lower-builtins.js +217 -69
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.js +41 -0
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.js +32 -11
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +3 -3
- package/dist/frontend/lowering/lower-exprs.js +87 -15
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-generators.js +7 -1
- package/dist/frontend/lowering/lower-generators.js.map +1 -1
- package/dist/frontend/lowering/lower-island.d.ts +123 -7
- package/dist/frontend/lowering/lower-island.js +1976 -26
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.js +17 -1
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +33 -9
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +40 -0
- package/dist/frontend/lowering/lowerer.js +290 -18
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/surfaces.js +55 -12
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/program.d.ts +20 -4
- package/dist/frontend/program.js +391 -49
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/resolve.d.ts +10 -0
- package/dist/frontend/resolve.js +37 -0
- package/dist/frontend/resolve.js.map +1 -1
- package/dist/frontend/ts7/enums.d.ts +9 -0
- package/dist/frontend/ts7/enums.js +8 -6
- package/dist/frontend/ts7/enums.js.map +1 -1
- package/dist/frontend/ts7/program.js +4 -1
- package/dist/frontend/ts7/program.js.map +1 -1
- package/dist/frontend/types.d.ts +16 -9
- package/dist/frontend/types.js +119 -38
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +70 -14
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +55 -26
- package/dist/ir/nodes.js +42 -29
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +106 -9
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/int-infer.js +7 -1
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/int-infer.test.js +80 -0
- package/dist/library/int-infer.test.js.map +1 -1
- package/dist/library/sidecar-validate.js +5 -0
- package/dist/library/sidecar-validate.js.map +1 -1
- package/dist/library/sidecar.d.ts +3 -0
- package/dist/library/sidecar.js.map +1 -1
- package/package.json +2 -2
- package/surface-manifest.json +1097 -130
|
@@ -0,0 +1,2507 @@
|
|
|
1
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { chmod, cp, mkdir, mkdtemp, readdir, readFile, rm, stat, utimes, writeFile } from "node:fs/promises";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { delimiter, join } from "node:path";
|
|
7
|
+
import { afterAll, afterEach, expect, test as vitestTest } from "vitest";
|
|
8
|
+
import { cacheTargetIdentity, ccVersionOnce, compileC, compileLibArchive, implicitDependencyProbeIncludes, parseLinkTraceFiles, resolveBuildCacheRoot, runtimeFingerprint, runtimeSrcDir, stageRuntimeObjects, toolchainEnvironmentCachePolicy, toolchainEnvironmentFingerprint, vendorCacheBuildIdentity, vendorCacheTargetFlavor, } from "./cc.js";
|
|
9
|
+
const scratch = [];
|
|
10
|
+
const TEST_CACHE_IDENTITY = "cc-cache-tests-v1";
|
|
11
|
+
const originalStableToolchain = process.env["SCRIPTC_TEST_STABLE_TOOLCHAIN"];
|
|
12
|
+
// This file is the strict-path contract: its fixtures replace wrappers,
|
|
13
|
+
// headers, SDK selectors, and native inputs in place between invocations.
|
|
14
|
+
// The rest of the immutable differential harness may memoize those probes.
|
|
15
|
+
delete process.env["SCRIPTC_TEST_STABLE_TOOLCHAIN"];
|
|
16
|
+
const originalTrustedCompilerWrapper = process.env["SCRIPTC_TEST_TRUST_COMPILER_WRAPPER"];
|
|
17
|
+
/** The cache suite mutates process-wide compiler state and must stay serial
|
|
18
|
+
* within one worker, but CI/Sandbox workers can safely divide its independent
|
|
19
|
+
* tests across processes. Unset runs the complete file for ordinary focused
|
|
20
|
+
* use; i/n uses the same stable hash partition as the corpus harness. */
|
|
21
|
+
function cacheTestSelected(name) {
|
|
22
|
+
const spec = process.env["SCRIPTC_CACHE_TEST_SHARD"];
|
|
23
|
+
if (spec === undefined || spec === "")
|
|
24
|
+
return true;
|
|
25
|
+
const match = /^(\d+)\/(\d+)$/.exec(spec);
|
|
26
|
+
if (match === null)
|
|
27
|
+
throw new Error(`invalid SCRIPTC_CACHE_TEST_SHARD '${spec}' (expected i/n)`);
|
|
28
|
+
const index = Number(match[1]);
|
|
29
|
+
const total = Number(match[2]);
|
|
30
|
+
if (index < 1 || total < 1 || index > total) {
|
|
31
|
+
throw new Error(`invalid SCRIPTC_CACHE_TEST_SHARD '${spec}' (expected 1 <= i <= n)`);
|
|
32
|
+
}
|
|
33
|
+
return createHash("sha1").update(name).digest().readUInt32BE(0) % total === index - 1;
|
|
34
|
+
}
|
|
35
|
+
const test = Object.assign((name, ...args) => Reflect.apply(cacheTestSelected(name) ? vitestTest : vitestTest.skip, undefined, [name, ...args]), {
|
|
36
|
+
skipIf: (condition) => (name, ...args) => Reflect.apply(condition || !cacheTestSelected(name) ? vitestTest.skip : vitestTest, undefined, [
|
|
37
|
+
name,
|
|
38
|
+
...args,
|
|
39
|
+
]),
|
|
40
|
+
});
|
|
41
|
+
const trustInstrumentedCompilerWrapper = () => {
|
|
42
|
+
process.env["SCRIPTC_TEST_TRUST_COMPILER_WRAPPER"] = "1";
|
|
43
|
+
};
|
|
44
|
+
const zigExecutable = (process.env["PATH"] ?? "")
|
|
45
|
+
.split(delimiter)
|
|
46
|
+
.map((entry) => join(entry === "" ? process.cwd() : entry, "zig"))
|
|
47
|
+
.find((candidate) => existsSync(candidate));
|
|
48
|
+
const completeArtifacts = async (root, kind) => (await readdir(join(root, kind))).filter((name) => !name.endsWith(".sha256"));
|
|
49
|
+
const cacheTreeBytes = async (root) => {
|
|
50
|
+
let total = 0;
|
|
51
|
+
const walk = async (directory) => {
|
|
52
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
53
|
+
const path = join(directory, entry.name);
|
|
54
|
+
if (entry.isDirectory())
|
|
55
|
+
await walk(path);
|
|
56
|
+
else if (entry.isFile())
|
|
57
|
+
total += (await stat(path)).size;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
await walk(root);
|
|
61
|
+
return total;
|
|
62
|
+
};
|
|
63
|
+
afterAll(async () => {
|
|
64
|
+
await Promise.all(scratch.map((dir) => rm(dir, { recursive: true, force: true })));
|
|
65
|
+
if (originalStableToolchain === undefined) {
|
|
66
|
+
delete process.env["SCRIPTC_TEST_STABLE_TOOLCHAIN"];
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
process.env["SCRIPTC_TEST_STABLE_TOOLCHAIN"] = originalStableToolchain;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
afterEach(() => {
|
|
73
|
+
if (originalTrustedCompilerWrapper === undefined) {
|
|
74
|
+
delete process.env["SCRIPTC_TEST_TRUST_COMPILER_WRAPPER"];
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
process.env["SCRIPTC_TEST_TRUST_COMPILER_WRAPPER"] = originalTrustedCompilerWrapper;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
test("the production cache root follows overrides, platform defaults, and the hard disable", () => {
|
|
81
|
+
expect(resolveBuildCacheRoot({ SCRIPTC_NO_CACHE: "1" }, "linux", "/home/tester")).toBeNull();
|
|
82
|
+
expect(resolveBuildCacheRoot({ SCRIPTC_CACHE_DIR: "" }, "linux", "/home/tester")).toBeNull();
|
|
83
|
+
expect(resolveBuildCacheRoot({ SCRIPTC_CACHE_DIR: "/var/tmp/custom" }, "linux", "/home/tester")).toBe("/var/tmp/custom");
|
|
84
|
+
expect(resolveBuildCacheRoot({ XDG_CACHE_HOME: "/var/tmp/xdg" }, "linux", "/home/tester")).toBe("/var/tmp/xdg/scriptc/build");
|
|
85
|
+
expect(resolveBuildCacheRoot({}, "darwin", "/Users/tester")).toBe("/Users/tester/Library/Caches/scriptc/build");
|
|
86
|
+
expect(resolveBuildCacheRoot({ LOCALAPPDATA: "/Users/tester/AppData/Local" }, "win32", "/Users/tester")).toBe("/Users/tester/AppData/Local/scriptc/cache/build");
|
|
87
|
+
});
|
|
88
|
+
test("native cache identities separate host architectures while cross targets remain explicit", () => {
|
|
89
|
+
expect(cacheTargetIdentity({ target: null }, "darwin", "arm64")).toBe("native:darwin:arm64");
|
|
90
|
+
expect(cacheTargetIdentity({ target: null }, "darwin", "x64")).toBe("native:darwin:x64");
|
|
91
|
+
expect(cacheTargetIdentity({ target: "x86_64-linux-gnu.2.36" }, "darwin", "arm64")).toBe("cross:x86_64-linux-gnu.2.36");
|
|
92
|
+
expect(vendorCacheTargetFlavor({ target: null }, "darwin", "arm64")).toBe("native-darwin-arm64");
|
|
93
|
+
expect(vendorCacheTargetFlavor({ target: null }, "darwin", "x64")).toBe("native-darwin-x64");
|
|
94
|
+
expect(vendorCacheTargetFlavor({ target: "x86_64-linux-gnu.2.36" }, "darwin", "arm64")).toBe("x86_64-linux-gnu.2.36");
|
|
95
|
+
});
|
|
96
|
+
test("Zig COFF dry-run parsing retains every linker input on its single command line", async () => {
|
|
97
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-link-trace-"));
|
|
98
|
+
scratch.push(dir);
|
|
99
|
+
const probeDir = join(dir, "probe");
|
|
100
|
+
const first = join(dir, "crt2.obj");
|
|
101
|
+
const second = join(dir, "compiler_rt.lib");
|
|
102
|
+
const third = join(dir, "kernel32.lib");
|
|
103
|
+
await mkdir(probeDir);
|
|
104
|
+
await Promise.all([
|
|
105
|
+
writeFile(join(probeDir, "empty.o"), "probe"),
|
|
106
|
+
writeFile(first, "first"),
|
|
107
|
+
writeFile(second, "second"),
|
|
108
|
+
writeFile(third, "third"),
|
|
109
|
+
]);
|
|
110
|
+
const output = [
|
|
111
|
+
"lld-link",
|
|
112
|
+
`\"${join(probeDir, "empty.o")}\"`,
|
|
113
|
+
`\"${first}\"`,
|
|
114
|
+
`\"${second}\"`,
|
|
115
|
+
`\"${third}\"`,
|
|
116
|
+
].join(" ");
|
|
117
|
+
expect(await parseLinkTraceFiles(output, probeDir, probeDir, true)).toEqual([first, second, third].sort());
|
|
118
|
+
});
|
|
119
|
+
test("the toolchain environment joins cache identities", () => {
|
|
120
|
+
const base = toolchainEnvironmentFingerprint({ PATH: "/usr/bin", CPATH: "/headers/one" });
|
|
121
|
+
expect(toolchainEnvironmentFingerprint({ PATH: "/usr/bin", CPATH: "/headers/two" })).not.toBe(base);
|
|
122
|
+
expect(toolchainEnvironmentFingerprint({ ZIG_LIB_DIR: "/zig/one" })).not.toBe(toolchainEnvironmentFingerprint({ ZIG_LIB_DIR: "/zig/two" }));
|
|
123
|
+
expect(toolchainEnvironmentFingerprint({ ZIG_LIBC: "/libc/one.conf" })).not.toBe(toolchainEnvironmentFingerprint({ ZIG_LIBC: "/libc/two.conf" }));
|
|
124
|
+
// PATH is deliberately absent from this generic environment hash: the
|
|
125
|
+
// resolved executable identity is keyed separately, while the compiler
|
|
126
|
+
// must still resolve on every cache-enabled call.
|
|
127
|
+
expect(toolchainEnvironmentFingerprint({ PATH: "", CPATH: "/headers/one" })).toBe(base);
|
|
128
|
+
expect(toolchainEnvironmentFingerprint({
|
|
129
|
+
PATH: "/usr/bin",
|
|
130
|
+
CPATH: "/headers/one",
|
|
131
|
+
SCRIPTC_CACHE_MAX_MB: "1",
|
|
132
|
+
})).toBe(base);
|
|
133
|
+
expect(toolchainEnvironmentCachePolicy({ MACOSX_DEPLOYMENT_TARGET: "14.0" })).toEqual({
|
|
134
|
+
completeArtifacts: true,
|
|
135
|
+
runtimeObjects: true,
|
|
136
|
+
});
|
|
137
|
+
expect(toolchainEnvironmentCachePolicy({ LIBRARY_PATH: "/libraries" })).toEqual({
|
|
138
|
+
completeArtifacts: false,
|
|
139
|
+
runtimeObjects: true,
|
|
140
|
+
});
|
|
141
|
+
expect(toolchainEnvironmentCachePolicy({ CPATH: "/headers" })).toEqual({
|
|
142
|
+
completeArtifacts: false,
|
|
143
|
+
runtimeObjects: false,
|
|
144
|
+
});
|
|
145
|
+
expect(toolchainEnvironmentCachePolicy({ CFLAGS: "-I/headers" })).toEqual({
|
|
146
|
+
completeArtifacts: false,
|
|
147
|
+
runtimeObjects: false,
|
|
148
|
+
});
|
|
149
|
+
expect(toolchainEnvironmentCachePolicy({ ZIG_LIB_DIR: "/zig/lib" })).toEqual({
|
|
150
|
+
completeArtifacts: false,
|
|
151
|
+
runtimeObjects: false,
|
|
152
|
+
});
|
|
153
|
+
expect(toolchainEnvironmentCachePolicy({ ZIG_LIBC: "/zig/libc.conf" })).toEqual({
|
|
154
|
+
completeArtifacts: false,
|
|
155
|
+
runtimeObjects: false,
|
|
156
|
+
});
|
|
157
|
+
expect(vendorCacheBuildIdentity(base, "compiler-one")).not.toBe(vendorCacheBuildIdentity(base, "compiler-two"));
|
|
158
|
+
expect(vendorCacheBuildIdentity(base, "compiler-one")).not.toBe(vendorCacheBuildIdentity(toolchainEnvironmentFingerprint({ MACOSX_DEPLOYMENT_TARGET: "11.0" }), "compiler-one"));
|
|
159
|
+
});
|
|
160
|
+
test.skipIf(process.platform === "win32")("compiler version probes use and remove a private working directory", async () => {
|
|
161
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-version-probe-"));
|
|
162
|
+
scratch.push(dir);
|
|
163
|
+
const probeTmp = join(dir, "tmp");
|
|
164
|
+
const fakeCompiler = join(dir, "fake-zig");
|
|
165
|
+
await mkdir(probeTmp);
|
|
166
|
+
await writeFile(fakeCompiler, `#!/bin/sh
|
|
167
|
+
: > a.o
|
|
168
|
+
printf 'fake zig cc version probe\n'
|
|
169
|
+
`);
|
|
170
|
+
await chmod(fakeCompiler, 0o755);
|
|
171
|
+
const oldTmpdir = process.env["TMPDIR"];
|
|
172
|
+
try {
|
|
173
|
+
process.env["TMPDIR"] = probeTmp;
|
|
174
|
+
// Zig 0.16 creates cwd/a.o for `zig cc --version`. Model that side
|
|
175
|
+
// effect directly so this regression does not depend on Zig being
|
|
176
|
+
// installed in every generic test shard.
|
|
177
|
+
await ccVersionOnce([fakeCompiler, "cc"], `version-probe-${dir}`);
|
|
178
|
+
expect(await readdir(probeTmp)).toEqual([]);
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
if (oldTmpdir === undefined)
|
|
182
|
+
delete process.env["TMPDIR"];
|
|
183
|
+
else
|
|
184
|
+
process.env["TMPDIR"] = oldTmpdir;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
test("the runtime fingerprint includes the textually included Ryū sources", async () => {
|
|
188
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-runtime-fingerprint-"));
|
|
189
|
+
scratch.push(dir);
|
|
190
|
+
const rtDir = join(dir, "src");
|
|
191
|
+
const ryuDir = join(dir, "vendor", "ryu");
|
|
192
|
+
await Promise.all([mkdir(rtDir, { recursive: true }), mkdir(ryuDir, { recursive: true })]);
|
|
193
|
+
const pinnedTime = new Date("2000-01-01T00:00:00.000Z");
|
|
194
|
+
await Promise.all([
|
|
195
|
+
writeFile(join(rtDir, "scr_number.c"), '#include "../vendor/ryu/d2s.c"\n'),
|
|
196
|
+
writeFile(join(ryuDir, "d2s.c"), "int ryu_probe = 1;\n"),
|
|
197
|
+
]);
|
|
198
|
+
await utimes(join(ryuDir, "d2s.c"), pinnedTime, pinnedTime);
|
|
199
|
+
const first = await runtimeFingerprint(rtDir);
|
|
200
|
+
// Timestamp-preserving sync/copy tools can replace bytes without changing
|
|
201
|
+
// the old stat-memo signature. Content, not metadata, owns this identity.
|
|
202
|
+
await writeFile(join(ryuDir, "d2s.c"), "int ryu_probe = 2;\n");
|
|
203
|
+
await utimes(join(ryuDir, "d2s.c"), pinnedTime, pinnedTime);
|
|
204
|
+
expect(await runtimeFingerprint(rtDir)).not.toBe(first);
|
|
205
|
+
});
|
|
206
|
+
test("implicit dependency seeds include separately compiled vendor system headers", async () => {
|
|
207
|
+
const includes = await implicitDependencyProbeIncludes(runtimeSrcDir());
|
|
208
|
+
// zlib's crc32.c is compiled independently and is not textually reachable
|
|
209
|
+
// from packages/runtime/src. Its ambient C-library dependency still belongs
|
|
210
|
+
// to the native object/cache identity.
|
|
211
|
+
expect(includes).toContain("<stdatomic.h>");
|
|
212
|
+
});
|
|
213
|
+
test.skipIf(process.platform === "win32")("opaque compiler wrappers bypass persistent caches", async () => {
|
|
214
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-compiler-wrapper-"));
|
|
215
|
+
scratch.push(dir);
|
|
216
|
+
const cacheRoot = join(dir, "cache");
|
|
217
|
+
const binDir = join(dir, "bin");
|
|
218
|
+
const helperSource = join(dir, "helper.c");
|
|
219
|
+
const helperObject = join(dir, "helper.o");
|
|
220
|
+
const cPath = join(dir, "program.c");
|
|
221
|
+
const outPath = join(dir, "program");
|
|
222
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
223
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
224
|
+
const oldDisableCcache = process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
225
|
+
const oldPath = process.env["PATH"];
|
|
226
|
+
const oldRealClang = process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
227
|
+
const oldProgramSource = process.env["SCRIPTC_TEST_PROGRAM_SOURCE"];
|
|
228
|
+
const oldWrapperObject = process.env["SCRIPTC_TEST_WRAPPER_OBJECT"];
|
|
229
|
+
const originalClang = (oldPath ?? "")
|
|
230
|
+
.split(delimiter)
|
|
231
|
+
.map((entry) => join(entry === "" ? process.cwd() : entry, "clang"))
|
|
232
|
+
.find((candidate) => existsSync(candidate));
|
|
233
|
+
expect(originalClang).toBeDefined();
|
|
234
|
+
const buildHelper = async (value) => {
|
|
235
|
+
await writeFile(helperSource, `int scriptc_wrapper_helper(void) { return ${value}; }\n`);
|
|
236
|
+
execFileSync(originalClang, ["-c", helperSource, "-o", helperObject]);
|
|
237
|
+
};
|
|
238
|
+
try {
|
|
239
|
+
await mkdir(binDir);
|
|
240
|
+
const wrapper = join(binDir, "clang");
|
|
241
|
+
await writeFile(wrapper, `#!/bin/sh
|
|
242
|
+
link=1
|
|
243
|
+
real_build=0
|
|
244
|
+
for arg in "$@"; do
|
|
245
|
+
case "$arg" in
|
|
246
|
+
-c|-M|-MM|-E|-S) link=0 ;;
|
|
247
|
+
"$SCRIPTC_TEST_PROGRAM_SOURCE"|*/scriptc-cache-build-*/program.c) real_build=1 ;;
|
|
248
|
+
esac
|
|
249
|
+
done
|
|
250
|
+
if [ "$link" = 1 ] && [ "$real_build" = 1 ]; then
|
|
251
|
+
exec "$SCRIPTC_TEST_REAL_CLANG" "$SCRIPTC_TEST_WRAPPER_OBJECT" "$@"
|
|
252
|
+
fi
|
|
253
|
+
exec "$SCRIPTC_TEST_REAL_CLANG" "$@"
|
|
254
|
+
`);
|
|
255
|
+
await chmod(wrapper, 0o755);
|
|
256
|
+
await writeFile(cPath, '#include <stdio.h>\nint scriptc_wrapper_helper(void);\nint main(void) { printf("%d\\n", scriptc_wrapper_helper()); return 0; }\n');
|
|
257
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
258
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = "1";
|
|
259
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = originalClang;
|
|
260
|
+
process.env["SCRIPTC_TEST_PROGRAM_SOURCE"] = cPath;
|
|
261
|
+
process.env["SCRIPTC_TEST_WRAPPER_OBJECT"] = helperObject;
|
|
262
|
+
process.env["PATH"] = `${binDir}${delimiter}${oldPath ?? ""}`;
|
|
263
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
264
|
+
await buildHelper(1);
|
|
265
|
+
await compileC({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
266
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("1");
|
|
267
|
+
// The wrapper injects this same-path object only for the actual program
|
|
268
|
+
// link, not for scriptc's synthetic metadata probes. Reusing the first
|
|
269
|
+
// complete artifact here would return the stale value.
|
|
270
|
+
await buildHelper(2);
|
|
271
|
+
await compileC({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
272
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("2");
|
|
273
|
+
await expect(stat(cacheRoot)).rejects.toMatchObject({ code: "ENOENT" });
|
|
274
|
+
}
|
|
275
|
+
finally {
|
|
276
|
+
if (oldCacheDir === undefined)
|
|
277
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
278
|
+
else
|
|
279
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
280
|
+
if (oldNoCache === undefined)
|
|
281
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
282
|
+
else
|
|
283
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
284
|
+
if (oldDisableCcache === undefined)
|
|
285
|
+
delete process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
286
|
+
else
|
|
287
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = oldDisableCcache;
|
|
288
|
+
if (oldPath === undefined)
|
|
289
|
+
delete process.env["PATH"];
|
|
290
|
+
else
|
|
291
|
+
process.env["PATH"] = oldPath;
|
|
292
|
+
if (oldRealClang === undefined)
|
|
293
|
+
delete process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
294
|
+
else
|
|
295
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = oldRealClang;
|
|
296
|
+
if (oldProgramSource === undefined)
|
|
297
|
+
delete process.env["SCRIPTC_TEST_PROGRAM_SOURCE"];
|
|
298
|
+
else
|
|
299
|
+
process.env["SCRIPTC_TEST_PROGRAM_SOURCE"] = oldProgramSource;
|
|
300
|
+
if (oldWrapperObject === undefined)
|
|
301
|
+
delete process.env["SCRIPTC_TEST_WRAPPER_OBJECT"];
|
|
302
|
+
else
|
|
303
|
+
process.env["SCRIPTC_TEST_WRAPPER_OBJECT"] = oldWrapperObject;
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
test.skipIf(process.platform === "win32")("opaque archiver wrappers rebuild library archives while retaining runtime objects", async () => {
|
|
307
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-archiver-wrapper-"));
|
|
308
|
+
scratch.push(dir);
|
|
309
|
+
const cacheRoot = join(dir, "cache");
|
|
310
|
+
const binDir = join(dir, "bin");
|
|
311
|
+
const helperSource = join(dir, "helper.c");
|
|
312
|
+
const helperObject = join(dir, "helper.o");
|
|
313
|
+
const cPath = join(dir, "program.c");
|
|
314
|
+
const archivePath = join(dir, "program.lib.a");
|
|
315
|
+
const probeSource = join(dir, "probe.c");
|
|
316
|
+
const probePath = join(dir, "probe");
|
|
317
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
318
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
319
|
+
const oldDisableCcache = process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
320
|
+
const oldPath = process.env["PATH"];
|
|
321
|
+
const oldRealAr = process.env["SCRIPTC_TEST_REAL_AR"];
|
|
322
|
+
const oldArExtra = process.env["SCRIPTC_TEST_AR_EXTRA"];
|
|
323
|
+
const originalClang = (oldPath ?? "")
|
|
324
|
+
.split(delimiter)
|
|
325
|
+
.map((entry) => join(entry === "" ? process.cwd() : entry, "clang"))
|
|
326
|
+
.find((candidate) => existsSync(candidate));
|
|
327
|
+
const originalAr = (oldPath ?? "")
|
|
328
|
+
.split(delimiter)
|
|
329
|
+
.map((entry) => join(entry === "" ? process.cwd() : entry, "ar"))
|
|
330
|
+
.find((candidate) => existsSync(candidate));
|
|
331
|
+
expect(originalClang).toBeDefined();
|
|
332
|
+
expect(originalAr).toBeDefined();
|
|
333
|
+
const buildHelper = async (value) => {
|
|
334
|
+
await writeFile(helperSource, `int scriptc_archiver_helper(void) { return ${value}; }\n`);
|
|
335
|
+
execFileSync(originalClang, ["-c", helperSource, "-o", helperObject]);
|
|
336
|
+
};
|
|
337
|
+
const runProbe = () => {
|
|
338
|
+
execFileSync(originalClang, [probeSource, archivePath, "-o", probePath]);
|
|
339
|
+
return execFileSync(probePath, { encoding: "utf8" }).trim();
|
|
340
|
+
};
|
|
341
|
+
try {
|
|
342
|
+
await mkdir(binDir);
|
|
343
|
+
await writeFile(join(binDir, "ar"), `#!/bin/sh
|
|
344
|
+
if [ "$1" = "--version" ]; then
|
|
345
|
+
exec "$SCRIPTC_TEST_REAL_AR" "$@"
|
|
346
|
+
fi
|
|
347
|
+
exec "$SCRIPTC_TEST_REAL_AR" "$@" "$SCRIPTC_TEST_AR_EXTRA"
|
|
348
|
+
`);
|
|
349
|
+
await chmod(join(binDir, "ar"), 0o755);
|
|
350
|
+
await writeFile(cPath, "int scriptc_program_member = 1;\n");
|
|
351
|
+
await writeFile(probeSource, '#include <stdio.h>\nint scriptc_archiver_helper(void);\nint main(void) { printf("%d\\n", scriptc_archiver_helper()); return 0; }\n');
|
|
352
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
353
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = "1";
|
|
354
|
+
process.env["SCRIPTC_TEST_REAL_AR"] = originalAr;
|
|
355
|
+
process.env["SCRIPTC_TEST_AR_EXTRA"] = helperObject;
|
|
356
|
+
process.env["PATH"] = `${binDir}${delimiter}${oldPath ?? ""}`;
|
|
357
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
358
|
+
await buildHelper(1);
|
|
359
|
+
await compileLibArchive({
|
|
360
|
+
cPath,
|
|
361
|
+
outPath: archivePath,
|
|
362
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
363
|
+
});
|
|
364
|
+
expect(runProbe()).toBe("1");
|
|
365
|
+
const objectSets = (await readdir(join(cacheRoot, "obj"), { withFileTypes: true }))
|
|
366
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith("build-"));
|
|
367
|
+
expect(objectSets).toHaveLength(1);
|
|
368
|
+
await expect(stat(join(cacheRoot, "lib"))).rejects.toMatchObject({ code: "ENOENT" });
|
|
369
|
+
// The wrapper executable/version and every scriptc-owned input remain
|
|
370
|
+
// unchanged. Only the mutable object it injects into `ar rcs` changes.
|
|
371
|
+
// A complete archive hit would silently retain the first definition.
|
|
372
|
+
await buildHelper(2);
|
|
373
|
+
await compileLibArchive({
|
|
374
|
+
cPath,
|
|
375
|
+
outPath: archivePath,
|
|
376
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
377
|
+
});
|
|
378
|
+
expect(runProbe()).toBe("2");
|
|
379
|
+
expect((await readdir(join(cacheRoot, "obj"), { withFileTypes: true }))
|
|
380
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith("build-"))).toHaveLength(1);
|
|
381
|
+
await expect(stat(join(cacheRoot, "lib"))).rejects.toMatchObject({ code: "ENOENT" });
|
|
382
|
+
}
|
|
383
|
+
finally {
|
|
384
|
+
if (oldCacheDir === undefined)
|
|
385
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
386
|
+
else
|
|
387
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
388
|
+
if (oldNoCache === undefined)
|
|
389
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
390
|
+
else
|
|
391
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
392
|
+
if (oldDisableCcache === undefined)
|
|
393
|
+
delete process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
394
|
+
else
|
|
395
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = oldDisableCcache;
|
|
396
|
+
if (oldPath === undefined)
|
|
397
|
+
delete process.env["PATH"];
|
|
398
|
+
else
|
|
399
|
+
process.env["PATH"] = oldPath;
|
|
400
|
+
if (oldRealAr === undefined)
|
|
401
|
+
delete process.env["SCRIPTC_TEST_REAL_AR"];
|
|
402
|
+
else
|
|
403
|
+
process.env["SCRIPTC_TEST_REAL_AR"] = oldRealAr;
|
|
404
|
+
if (oldArExtra === undefined)
|
|
405
|
+
delete process.env["SCRIPTC_TEST_AR_EXTRA"];
|
|
406
|
+
else
|
|
407
|
+
process.env["SCRIPTC_TEST_AR_EXTRA"] = oldArExtra;
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
test.skipIf(process.platform === "win32")("build-flavor-specific compiler-wrapper flags join the cache identity", async () => {
|
|
411
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-wrapper-env-"));
|
|
412
|
+
scratch.push(dir);
|
|
413
|
+
const cacheRoot = join(dir, "cache");
|
|
414
|
+
const binDir = join(dir, "bin");
|
|
415
|
+
const cPath = join(dir, "program.c");
|
|
416
|
+
const outPath = join(dir, "program");
|
|
417
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
418
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
419
|
+
const oldDisableCcache = process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
420
|
+
const oldPath = process.env["PATH"];
|
|
421
|
+
const oldRealClang = process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
422
|
+
const oldWrapperValue = process.env["SCRIPTC_TEST_WRAPPER_VALUE"];
|
|
423
|
+
const realClang = (oldPath ?? "")
|
|
424
|
+
.split(delimiter)
|
|
425
|
+
.map((entry) => join(entry === "" ? process.cwd() : entry, "clang"))
|
|
426
|
+
.find((candidate) => existsSync(candidate));
|
|
427
|
+
expect(realClang).toBeDefined();
|
|
428
|
+
try {
|
|
429
|
+
await mkdir(binDir);
|
|
430
|
+
const wrapper = join(binDir, "clang");
|
|
431
|
+
await writeFile(wrapper, `#!/bin/sh
|
|
432
|
+
if [ "$1" = "--version" ]; then
|
|
433
|
+
exec "$SCRIPTC_TEST_REAL_CLANG" "$@"
|
|
434
|
+
fi
|
|
435
|
+
inject=0
|
|
436
|
+
for arg in "$@"; do
|
|
437
|
+
if [ "$arg" = "-O2" ]; then inject=1; fi
|
|
438
|
+
done
|
|
439
|
+
if [ "$inject" = 1 ]; then
|
|
440
|
+
exec "$SCRIPTC_TEST_REAL_CLANG" "-DSCRIPTC_WRAPPER_VALUE=$SCRIPTC_TEST_WRAPPER_VALUE" "$@"
|
|
441
|
+
fi
|
|
442
|
+
exec "$SCRIPTC_TEST_REAL_CLANG" "$@"
|
|
443
|
+
`);
|
|
444
|
+
await chmod(wrapper, 0o755);
|
|
445
|
+
await writeFile(cPath, '#include <stdio.h>\nint main(void) { printf("%d\\n", SCRIPTC_WRAPPER_VALUE); return 0; }\n');
|
|
446
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
447
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = "1";
|
|
448
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = realClang;
|
|
449
|
+
process.env["PATH"] = `${binDir}${delimiter}${oldPath ?? ""}`;
|
|
450
|
+
trustInstrumentedCompilerWrapper();
|
|
451
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
452
|
+
process.env["SCRIPTC_TEST_WRAPPER_VALUE"] = "1";
|
|
453
|
+
await compileC({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
454
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("1");
|
|
455
|
+
// This variable is intentionally absent from scriptc's fixed environment
|
|
456
|
+
// allowlist, and the wrapper injects it only for the real -O2 build
|
|
457
|
+
// flavor. A generic metadata-probe trace must not hide the change.
|
|
458
|
+
process.env["SCRIPTC_TEST_WRAPPER_VALUE"] = "2";
|
|
459
|
+
await compileC({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
460
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("2");
|
|
461
|
+
expect(await completeArtifacts(cacheRoot, "bin")).toHaveLength(2);
|
|
462
|
+
const objectSets = (await readdir(join(cacheRoot, "obj"), { withFileTypes: true }))
|
|
463
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith("build-"));
|
|
464
|
+
expect(objectSets).toHaveLength(2);
|
|
465
|
+
}
|
|
466
|
+
finally {
|
|
467
|
+
if (oldCacheDir === undefined)
|
|
468
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
469
|
+
else
|
|
470
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
471
|
+
if (oldNoCache === undefined)
|
|
472
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
473
|
+
else
|
|
474
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
475
|
+
if (oldDisableCcache === undefined)
|
|
476
|
+
delete process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
477
|
+
else
|
|
478
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = oldDisableCcache;
|
|
479
|
+
if (oldPath === undefined)
|
|
480
|
+
delete process.env["PATH"];
|
|
481
|
+
else
|
|
482
|
+
process.env["PATH"] = oldPath;
|
|
483
|
+
if (oldRealClang === undefined)
|
|
484
|
+
delete process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
485
|
+
else
|
|
486
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = oldRealClang;
|
|
487
|
+
if (oldWrapperValue === undefined)
|
|
488
|
+
delete process.env["SCRIPTC_TEST_WRAPPER_VALUE"];
|
|
489
|
+
else
|
|
490
|
+
process.env["SCRIPTC_TEST_WRAPPER_VALUE"] = oldWrapperValue;
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
test.skipIf(process.platform === "win32")("build-flavor-specific compiler-wrapper headers key runtime objects by content", async () => {
|
|
494
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-wrapper-header-"));
|
|
495
|
+
scratch.push(dir);
|
|
496
|
+
const cacheRoot = join(dir, "cache");
|
|
497
|
+
const binDir = join(dir, "bin");
|
|
498
|
+
const fakeRuntime = join(dir, "runtime", "src");
|
|
499
|
+
const header = join(dir, "wrapper-input.h");
|
|
500
|
+
const cPath = join(dir, "program.c");
|
|
501
|
+
const outPath = join(dir, "program");
|
|
502
|
+
const originalRuntime = runtimeSrcDir();
|
|
503
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
504
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
505
|
+
const oldRuntimeDir = process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"];
|
|
506
|
+
const oldDisableCcache = process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
507
|
+
const oldPath = process.env["PATH"];
|
|
508
|
+
const oldRealClang = process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
509
|
+
const oldWrapperHeader = process.env["SCRIPTC_TEST_WRAPPER_HEADER"];
|
|
510
|
+
const realClang = (oldPath ?? "")
|
|
511
|
+
.split(delimiter)
|
|
512
|
+
.map((entry) => join(entry === "" ? process.cwd() : entry, "clang"))
|
|
513
|
+
.find((candidate) => existsSync(candidate));
|
|
514
|
+
expect(realClang).toBeDefined();
|
|
515
|
+
try {
|
|
516
|
+
await Promise.all([
|
|
517
|
+
mkdir(binDir),
|
|
518
|
+
cp(originalRuntime, fakeRuntime, { recursive: true }),
|
|
519
|
+
mkdir(join(dir, "runtime", "vendor"), { recursive: true }).then(() => cp(join(originalRuntime, "..", "vendor", "ryu"), join(dir, "runtime", "vendor", "ryu"), {
|
|
520
|
+
recursive: true,
|
|
521
|
+
})),
|
|
522
|
+
]);
|
|
523
|
+
const numberSource = join(fakeRuntime, "scr_number.c");
|
|
524
|
+
await writeFile(numberSource, `${await readFile(numberSource, "utf8")}\nint scriptc_wrapper_runtime_value(void) { return SCRIPTC_WRAPPER_HEADER_VALUE; }\n`);
|
|
525
|
+
const wrapper = join(binDir, "clang");
|
|
526
|
+
await writeFile(wrapper, `#!/bin/sh
|
|
527
|
+
case "$1" in
|
|
528
|
+
--version|-print-prog-name=*) exec "$SCRIPTC_TEST_REAL_CLANG" "$@" ;;
|
|
529
|
+
esac
|
|
530
|
+
flavor=0
|
|
531
|
+
for arg in "$@"; do
|
|
532
|
+
if [ "$arg" = "-O2" ]; then flavor=1; fi
|
|
533
|
+
done
|
|
534
|
+
if [ "$flavor" = 1 ]; then
|
|
535
|
+
exec "$SCRIPTC_TEST_REAL_CLANG" -include "$SCRIPTC_TEST_WRAPPER_HEADER" "$@"
|
|
536
|
+
fi
|
|
537
|
+
exec "$SCRIPTC_TEST_REAL_CLANG" "$@"
|
|
538
|
+
`);
|
|
539
|
+
await chmod(wrapper, 0o755);
|
|
540
|
+
await writeFile(cPath, '#include <stdio.h>\nint scriptc_wrapper_runtime_value(void);\nint main(void) { printf("%d\\n", scriptc_wrapper_runtime_value()); return 0; }\n');
|
|
541
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
542
|
+
process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"] = fakeRuntime;
|
|
543
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = "1";
|
|
544
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = realClang;
|
|
545
|
+
process.env["SCRIPTC_TEST_WRAPPER_HEADER"] = header;
|
|
546
|
+
process.env["PATH"] = `${binDir}${delimiter}${oldPath ?? ""}`;
|
|
547
|
+
trustInstrumentedCompilerWrapper();
|
|
548
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
549
|
+
await writeFile(header, "#define SCRIPTC_WRAPPER_HEADER_VALUE 1\n");
|
|
550
|
+
await compileC({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
551
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("1");
|
|
552
|
+
// The wrapper's -### text still names the same -include path. Its bytes,
|
|
553
|
+
// not merely that spelling, must re-key every runtime object it affects.
|
|
554
|
+
await writeFile(header, "#define SCRIPTC_WRAPPER_HEADER_VALUE 2\n");
|
|
555
|
+
await compileC({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
556
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("2");
|
|
557
|
+
const objectSets = (await readdir(join(cacheRoot, "obj"), { withFileTypes: true }))
|
|
558
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith("build-"));
|
|
559
|
+
expect(objectSets).toHaveLength(2);
|
|
560
|
+
}
|
|
561
|
+
finally {
|
|
562
|
+
if (oldCacheDir === undefined)
|
|
563
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
564
|
+
else
|
|
565
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
566
|
+
if (oldNoCache === undefined)
|
|
567
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
568
|
+
else
|
|
569
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
570
|
+
if (oldRuntimeDir === undefined)
|
|
571
|
+
delete process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"];
|
|
572
|
+
else
|
|
573
|
+
process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"] = oldRuntimeDir;
|
|
574
|
+
if (oldDisableCcache === undefined)
|
|
575
|
+
delete process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
576
|
+
else
|
|
577
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = oldDisableCcache;
|
|
578
|
+
if (oldPath === undefined)
|
|
579
|
+
delete process.env["PATH"];
|
|
580
|
+
else
|
|
581
|
+
process.env["PATH"] = oldPath;
|
|
582
|
+
if (oldRealClang === undefined)
|
|
583
|
+
delete process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
584
|
+
else
|
|
585
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = oldRealClang;
|
|
586
|
+
if (oldWrapperHeader === undefined)
|
|
587
|
+
delete process.env["SCRIPTC_TEST_WRAPPER_HEADER"];
|
|
588
|
+
else
|
|
589
|
+
process.env["SCRIPTC_TEST_WRAPPER_HEADER"] = oldWrapperHeader;
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
test.skipIf(process.platform === "win32")("build-flavor-specific compiler-wrapper link flags join the artifact identity", async () => {
|
|
593
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-wrapper-link-env-"));
|
|
594
|
+
scratch.push(dir);
|
|
595
|
+
const cacheRoot = join(dir, "cache");
|
|
596
|
+
const binDir = join(dir, "bin");
|
|
597
|
+
const cPath = join(dir, "program.c");
|
|
598
|
+
const outPath = join(dir, "program");
|
|
599
|
+
const firstRpath = join(dir, "rpath-one");
|
|
600
|
+
const secondRpath = join(dir, "rpath-two");
|
|
601
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
602
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
603
|
+
const oldDisableCcache = process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
604
|
+
const oldPath = process.env["PATH"];
|
|
605
|
+
const oldRealClang = process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
606
|
+
const oldWrapperRpath = process.env["SCRIPTC_TEST_WRAPPER_RPATH"];
|
|
607
|
+
const realClang = (oldPath ?? "")
|
|
608
|
+
.split(delimiter)
|
|
609
|
+
.map((entry) => join(entry === "" ? process.cwd() : entry, "clang"))
|
|
610
|
+
.find((candidate) => existsSync(candidate));
|
|
611
|
+
expect(realClang).toBeDefined();
|
|
612
|
+
try {
|
|
613
|
+
await mkdir(binDir);
|
|
614
|
+
const wrapper = join(binDir, "clang");
|
|
615
|
+
await writeFile(wrapper, `#!/bin/sh
|
|
616
|
+
case "$1" in
|
|
617
|
+
--version|-print-prog-name=*) exec "$SCRIPTC_TEST_REAL_CLANG" "$@" ;;
|
|
618
|
+
esac
|
|
619
|
+
link=1
|
|
620
|
+
flavor=0
|
|
621
|
+
for arg in "$@"; do
|
|
622
|
+
case "$arg" in
|
|
623
|
+
-c|-M|-MM|-E|-S) link=0 ;;
|
|
624
|
+
-O2) flavor=1 ;;
|
|
625
|
+
esac
|
|
626
|
+
done
|
|
627
|
+
if [ "$link" = 1 ] && [ "$flavor" = 1 ]; then
|
|
628
|
+
exec "$SCRIPTC_TEST_REAL_CLANG" "-Wl,-rpath,$SCRIPTC_TEST_WRAPPER_RPATH" "$@"
|
|
629
|
+
fi
|
|
630
|
+
exec "$SCRIPTC_TEST_REAL_CLANG" "$@"
|
|
631
|
+
`);
|
|
632
|
+
await chmod(wrapper, 0o755);
|
|
633
|
+
await writeFile(cPath, "int main(void) { return 0; }\n");
|
|
634
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
635
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = "1";
|
|
636
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = realClang;
|
|
637
|
+
process.env["PATH"] = `${binDir}${delimiter}${oldPath ?? ""}`;
|
|
638
|
+
trustInstrumentedCompilerWrapper();
|
|
639
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
640
|
+
process.env["SCRIPTC_TEST_WRAPPER_RPATH"] = firstRpath;
|
|
641
|
+
await compileC({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
642
|
+
expect((await readFile(outPath)).includes(Buffer.from(firstRpath))).toBe(true);
|
|
643
|
+
// The wrapper's generic link trace and resolved linker inputs are
|
|
644
|
+
// unchanged; only the real -O2 link flavor exposes this value.
|
|
645
|
+
process.env["SCRIPTC_TEST_WRAPPER_RPATH"] = secondRpath;
|
|
646
|
+
await compileC({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
647
|
+
const secondBinary = await readFile(outPath);
|
|
648
|
+
expect(secondBinary.includes(Buffer.from(secondRpath))).toBe(true);
|
|
649
|
+
expect(secondBinary.includes(Buffer.from(firstRpath))).toBe(false);
|
|
650
|
+
expect(await completeArtifacts(cacheRoot, "bin")).toHaveLength(2);
|
|
651
|
+
}
|
|
652
|
+
finally {
|
|
653
|
+
if (oldCacheDir === undefined)
|
|
654
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
655
|
+
else
|
|
656
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
657
|
+
if (oldNoCache === undefined)
|
|
658
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
659
|
+
else
|
|
660
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
661
|
+
if (oldDisableCcache === undefined)
|
|
662
|
+
delete process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
663
|
+
else
|
|
664
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = oldDisableCcache;
|
|
665
|
+
if (oldPath === undefined)
|
|
666
|
+
delete process.env["PATH"];
|
|
667
|
+
else
|
|
668
|
+
process.env["PATH"] = oldPath;
|
|
669
|
+
if (oldRealClang === undefined)
|
|
670
|
+
delete process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
671
|
+
else
|
|
672
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = oldRealClang;
|
|
673
|
+
if (oldWrapperRpath === undefined)
|
|
674
|
+
delete process.env["SCRIPTC_TEST_WRAPPER_RPATH"];
|
|
675
|
+
else
|
|
676
|
+
process.env["SCRIPTC_TEST_WRAPPER_RPATH"] = oldWrapperRpath;
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
test.skipIf(process.platform === "win32")("build-flavor-specific compiler-wrapper link inputs join by content", async () => {
|
|
680
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-wrapper-link-input-"));
|
|
681
|
+
scratch.push(dir);
|
|
682
|
+
const cacheRoot = join(dir, "cache");
|
|
683
|
+
const binDir = join(dir, "bin");
|
|
684
|
+
const helperSource = join(dir, "helper.c");
|
|
685
|
+
const helperObject = join(dir, "helper.o");
|
|
686
|
+
const cPath = join(dir, "program.c");
|
|
687
|
+
const outPath = join(dir, "program");
|
|
688
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
689
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
690
|
+
const oldDisableCcache = process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
691
|
+
const oldPath = process.env["PATH"];
|
|
692
|
+
const oldRealClang = process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
693
|
+
const oldWrapperObject = process.env["SCRIPTC_TEST_WRAPPER_OBJECT"];
|
|
694
|
+
const realClang = (oldPath ?? "")
|
|
695
|
+
.split(delimiter)
|
|
696
|
+
.map((entry) => join(entry === "" ? process.cwd() : entry, "clang"))
|
|
697
|
+
.find((candidate) => existsSync(candidate));
|
|
698
|
+
expect(realClang).toBeDefined();
|
|
699
|
+
const buildHelper = async (value) => {
|
|
700
|
+
await writeFile(helperSource, `int scriptc_wrapper_helper(void) { return ${value}; }\n`);
|
|
701
|
+
execFileSync(realClang, ["-c", helperSource, "-o", helperObject]);
|
|
702
|
+
};
|
|
703
|
+
try {
|
|
704
|
+
await mkdir(binDir);
|
|
705
|
+
const wrapper = join(binDir, "clang");
|
|
706
|
+
await writeFile(wrapper, `#!/bin/sh
|
|
707
|
+
case "$1" in
|
|
708
|
+
--version|-print-prog-name=*) exec "$SCRIPTC_TEST_REAL_CLANG" "$@" ;;
|
|
709
|
+
esac
|
|
710
|
+
link=1
|
|
711
|
+
flavor=0
|
|
712
|
+
for arg in "$@"; do
|
|
713
|
+
case "$arg" in
|
|
714
|
+
-c|-M|-MM|-E|-S) link=0 ;;
|
|
715
|
+
-O2) flavor=1 ;;
|
|
716
|
+
esac
|
|
717
|
+
done
|
|
718
|
+
if [ "$link" = 1 ] && [ "$flavor" = 1 ]; then
|
|
719
|
+
exec "$SCRIPTC_TEST_REAL_CLANG" "$SCRIPTC_TEST_WRAPPER_OBJECT" "$@"
|
|
720
|
+
fi
|
|
721
|
+
exec "$SCRIPTC_TEST_REAL_CLANG" "$@"
|
|
722
|
+
`);
|
|
723
|
+
await chmod(wrapper, 0o755);
|
|
724
|
+
await writeFile(cPath, '#include <stdio.h>\nint scriptc_wrapper_helper(void);\nint main(void) { printf("%d\\n", scriptc_wrapper_helper()); return 0; }\n');
|
|
725
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
726
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = "1";
|
|
727
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = realClang;
|
|
728
|
+
process.env["SCRIPTC_TEST_WRAPPER_OBJECT"] = helperObject;
|
|
729
|
+
process.env["PATH"] = `${binDir}${delimiter}${oldPath ?? ""}`;
|
|
730
|
+
trustInstrumentedCompilerWrapper();
|
|
731
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
732
|
+
await buildHelper(1);
|
|
733
|
+
await compileC({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
734
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("1");
|
|
735
|
+
// The injected path remains stable while the object is rebuilt in place.
|
|
736
|
+
// A flavor-mismatched trace sees neither edit and would return a stale hit.
|
|
737
|
+
await buildHelper(2);
|
|
738
|
+
await compileC({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
739
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("2");
|
|
740
|
+
expect(await completeArtifacts(cacheRoot, "bin")).toHaveLength(2);
|
|
741
|
+
}
|
|
742
|
+
finally {
|
|
743
|
+
if (oldCacheDir === undefined)
|
|
744
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
745
|
+
else
|
|
746
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
747
|
+
if (oldNoCache === undefined)
|
|
748
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
749
|
+
else
|
|
750
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
751
|
+
if (oldDisableCcache === undefined)
|
|
752
|
+
delete process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
753
|
+
else
|
|
754
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = oldDisableCcache;
|
|
755
|
+
if (oldPath === undefined)
|
|
756
|
+
delete process.env["PATH"];
|
|
757
|
+
else
|
|
758
|
+
process.env["PATH"] = oldPath;
|
|
759
|
+
if (oldRealClang === undefined)
|
|
760
|
+
delete process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
761
|
+
else
|
|
762
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = oldRealClang;
|
|
763
|
+
if (oldWrapperObject === undefined)
|
|
764
|
+
delete process.env["SCRIPTC_TEST_WRAPPER_OBJECT"];
|
|
765
|
+
else
|
|
766
|
+
process.env["SCRIPTC_TEST_WRAPPER_OBJECT"] = oldWrapperObject;
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
test.skipIf(process.platform !== "darwin")("cached Darwin executables preserve the requested code-signature identifier", async () => {
|
|
770
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-darwin-identifier-"));
|
|
771
|
+
scratch.push(dir);
|
|
772
|
+
const cacheRoot = join(dir, "cache");
|
|
773
|
+
const cPath = join(dir, "program.c");
|
|
774
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
775
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
776
|
+
const codeSignatureIdentifier = (path) => {
|
|
777
|
+
const result = spawnSync("codesign", ["-d", "-vv", path], { encoding: "utf8" });
|
|
778
|
+
expect(result.status).toBe(0);
|
|
779
|
+
return /^Identifier=(.+)$/m.exec(`${result.stdout}\n${result.stderr}`)?.[1];
|
|
780
|
+
};
|
|
781
|
+
try {
|
|
782
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
783
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
784
|
+
await writeFile(cPath, "int main(void) { return 0; }\n");
|
|
785
|
+
const firstOut = join(dir, "requested-one");
|
|
786
|
+
await compileC({ cPath, outPath: firstOut, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
787
|
+
expect(codeSignatureIdentifier(firstOut)).toBe("requested-one");
|
|
788
|
+
const secondOut = join(dir, "requested-two");
|
|
789
|
+
await compileC({ cPath, outPath: secondOut, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
790
|
+
expect(codeSignatureIdentifier(secondOut)).toBe("requested-two");
|
|
791
|
+
expect(await completeArtifacts(cacheRoot, "bin")).toHaveLength(2);
|
|
792
|
+
}
|
|
793
|
+
finally {
|
|
794
|
+
if (oldCacheDir === undefined)
|
|
795
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
796
|
+
else
|
|
797
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
798
|
+
if (oldNoCache === undefined)
|
|
799
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
800
|
+
else
|
|
801
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
test.skipIf(process.platform === "win32")("implicit system-header changes invalidate complete artifacts and runtime objects", async () => {
|
|
805
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-system-header-"));
|
|
806
|
+
scratch.push(dir);
|
|
807
|
+
const cacheRoot = join(dir, "cache");
|
|
808
|
+
const binDir = join(dir, "bin");
|
|
809
|
+
const header = join(dir, "implicit-probe.h");
|
|
810
|
+
const cPath = join(dir, "program.c");
|
|
811
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
812
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
813
|
+
const oldPath = process.env["PATH"];
|
|
814
|
+
const oldRealClang = process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
815
|
+
const oldImplicitHeader = process.env["SCRIPTC_TEST_IMPLICIT_HEADER"];
|
|
816
|
+
const realClang = (oldPath ?? "")
|
|
817
|
+
.split(delimiter)
|
|
818
|
+
.map((entry) => join(entry === "" ? process.cwd() : entry, "clang"))
|
|
819
|
+
.find((candidate) => existsSync(candidate));
|
|
820
|
+
expect(realClang).toBeDefined();
|
|
821
|
+
try {
|
|
822
|
+
await mkdir(binDir);
|
|
823
|
+
const wrapper = join(binDir, "clang");
|
|
824
|
+
await writeFile(wrapper, `#!/bin/sh
|
|
825
|
+
exec "$SCRIPTC_TEST_REAL_CLANG" -include "$SCRIPTC_TEST_IMPLICIT_HEADER" "$@"
|
|
826
|
+
`);
|
|
827
|
+
await chmod(wrapper, 0o755);
|
|
828
|
+
await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts(SCRIPTC_IMPLICIT_PROBE); return 0; }\n');
|
|
829
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
830
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = realClang;
|
|
831
|
+
process.env["SCRIPTC_TEST_IMPLICIT_HEADER"] = header;
|
|
832
|
+
process.env["PATH"] = `${binDir}${delimiter}${oldPath ?? ""}`;
|
|
833
|
+
trustInstrumentedCompilerWrapper();
|
|
834
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
835
|
+
await writeFile(header, '#define SCRIPTC_IMPLICIT_PROBE "one"\n');
|
|
836
|
+
const firstOut = join(dir, "first");
|
|
837
|
+
await compileC({ cPath, outPath: firstOut, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
838
|
+
expect(execFileSync(firstOut, { encoding: "utf8" }).trim()).toBe("one");
|
|
839
|
+
// The compiler executable, PATH, source, and environment spellings are
|
|
840
|
+
// unchanged; only a header in the driver's implicit dependency graph moves.
|
|
841
|
+
await writeFile(header, '#define SCRIPTC_IMPLICIT_PROBE "two"\n');
|
|
842
|
+
const secondOut = firstOut;
|
|
843
|
+
await compileC({ cPath, outPath: secondOut, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
844
|
+
expect(execFileSync(secondOut, { encoding: "utf8" }).trim()).toBe("two");
|
|
845
|
+
expect(await completeArtifacts(cacheRoot, "bin")).toHaveLength(2);
|
|
846
|
+
const objectSets = (await readdir(join(cacheRoot, "obj"), { withFileTypes: true }))
|
|
847
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith("build-"));
|
|
848
|
+
expect(objectSets).toHaveLength(2);
|
|
849
|
+
}
|
|
850
|
+
finally {
|
|
851
|
+
if (oldCacheDir === undefined)
|
|
852
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
853
|
+
else
|
|
854
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
855
|
+
if (oldNoCache === undefined)
|
|
856
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
857
|
+
else
|
|
858
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
859
|
+
if (oldPath === undefined)
|
|
860
|
+
delete process.env["PATH"];
|
|
861
|
+
else
|
|
862
|
+
process.env["PATH"] = oldPath;
|
|
863
|
+
if (oldRealClang === undefined)
|
|
864
|
+
delete process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
865
|
+
else
|
|
866
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = oldRealClang;
|
|
867
|
+
if (oldImplicitHeader === undefined)
|
|
868
|
+
delete process.env["SCRIPTC_TEST_IMPLICIT_HEADER"];
|
|
869
|
+
else
|
|
870
|
+
process.env["SCRIPTC_TEST_IMPLICIT_HEADER"] = oldImplicitHeader;
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
test.skipIf(process.platform === "win32")("headers selected only by the caller translation unit invalidate complete artifacts", async () => {
|
|
874
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-program-header-"));
|
|
875
|
+
scratch.push(dir);
|
|
876
|
+
const cacheRoot = join(dir, "cache");
|
|
877
|
+
const binDir = join(dir, "bin");
|
|
878
|
+
const headers = join(dir, "system-headers");
|
|
879
|
+
const header = join(headers, "scriptc_program_probe.h");
|
|
880
|
+
const cPath = join(dir, "program.c");
|
|
881
|
+
const outPath = join(dir, "program");
|
|
882
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
883
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
884
|
+
const oldDisableCcache = process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
885
|
+
const oldPath = process.env["PATH"];
|
|
886
|
+
const oldRealClang = process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
887
|
+
const oldHeaderDir = process.env["SCRIPTC_TEST_PROGRAM_HEADER_DIR"];
|
|
888
|
+
const realClang = (oldPath ?? "")
|
|
889
|
+
.split(delimiter)
|
|
890
|
+
.map((entry) => join(entry === "" ? process.cwd() : entry, "clang"))
|
|
891
|
+
.find((candidate) => existsSync(candidate));
|
|
892
|
+
expect(realClang).toBeDefined();
|
|
893
|
+
try {
|
|
894
|
+
await Promise.all([mkdir(binDir), mkdir(headers)]);
|
|
895
|
+
const wrapper = join(binDir, "clang");
|
|
896
|
+
await writeFile(wrapper, `#!/bin/sh
|
|
897
|
+
case "$1" in
|
|
898
|
+
--version|-print-prog-name=*) exec "$SCRIPTC_TEST_REAL_CLANG" "$@" ;;
|
|
899
|
+
esac
|
|
900
|
+
exec "$SCRIPTC_TEST_REAL_CLANG" -isystem "$SCRIPTC_TEST_PROGRAM_HEADER_DIR" "$@"
|
|
901
|
+
`);
|
|
902
|
+
await chmod(wrapper, 0o755);
|
|
903
|
+
await writeFile(cPath, '#include <stdio.h>\n#include <scriptc_program_probe.h>\nint main(void) { puts(SCRIPTC_PROGRAM_HEADER); return 0; }\n');
|
|
904
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
905
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = "1";
|
|
906
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = realClang;
|
|
907
|
+
process.env["SCRIPTC_TEST_PROGRAM_HEADER_DIR"] = headers;
|
|
908
|
+
process.env["PATH"] = `${binDir}${delimiter}${oldPath ?? ""}`;
|
|
909
|
+
trustInstrumentedCompilerWrapper();
|
|
910
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
911
|
+
await writeFile(header, '#define SCRIPTC_PROGRAM_HEADER "one"\n');
|
|
912
|
+
await compileC({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
913
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("one");
|
|
914
|
+
// The runtime/vendor seed probe never includes this spelling; only a
|
|
915
|
+
// dependency pass over the caller TU can observe its bytes.
|
|
916
|
+
await writeFile(header, '#define SCRIPTC_PROGRAM_HEADER "two"\n');
|
|
917
|
+
await compileC({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
918
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("two");
|
|
919
|
+
expect(await completeArtifacts(cacheRoot, "bin")).toHaveLength(2);
|
|
920
|
+
await writeFile(header, '#define SCRIPTC_PROGRAM_HEADER "lib-one-unique"\n');
|
|
921
|
+
const archivePath = join(dir, "program.lib.a");
|
|
922
|
+
await compileLibArchive({ cPath, outPath: archivePath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
923
|
+
await writeFile(header, '#define SCRIPTC_PROGRAM_HEADER "lib-two-unique"\n');
|
|
924
|
+
await compileLibArchive({ cPath, outPath: archivePath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
925
|
+
const archive = await readFile(archivePath);
|
|
926
|
+
expect(archive.includes(Buffer.from("lib-two-unique"))).toBe(true);
|
|
927
|
+
expect(archive.includes(Buffer.from("lib-one-unique"))).toBe(false);
|
|
928
|
+
expect(await completeArtifacts(cacheRoot, "lib")).toHaveLength(2);
|
|
929
|
+
}
|
|
930
|
+
finally {
|
|
931
|
+
if (oldCacheDir === undefined)
|
|
932
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
933
|
+
else
|
|
934
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
935
|
+
if (oldNoCache === undefined)
|
|
936
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
937
|
+
else
|
|
938
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
939
|
+
if (oldDisableCcache === undefined)
|
|
940
|
+
delete process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
941
|
+
else
|
|
942
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = oldDisableCcache;
|
|
943
|
+
if (oldPath === undefined)
|
|
944
|
+
delete process.env["PATH"];
|
|
945
|
+
else
|
|
946
|
+
process.env["PATH"] = oldPath;
|
|
947
|
+
if (oldRealClang === undefined)
|
|
948
|
+
delete process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
949
|
+
else
|
|
950
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = oldRealClang;
|
|
951
|
+
if (oldHeaderDir === undefined)
|
|
952
|
+
delete process.env["SCRIPTC_TEST_PROGRAM_HEADER_DIR"];
|
|
953
|
+
else
|
|
954
|
+
process.env["SCRIPTC_TEST_PROGRAM_HEADER_DIR"] = oldHeaderDir;
|
|
955
|
+
}
|
|
956
|
+
});
|
|
957
|
+
test.skipIf(process.platform === "win32")("fresh dependency discovery detects an implicit include redirected to a new path", async () => {
|
|
958
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-header-redirect-"));
|
|
959
|
+
scratch.push(dir);
|
|
960
|
+
const cacheRoot = join(dir, "cache");
|
|
961
|
+
const binDir = join(dir, "bin");
|
|
962
|
+
const selector = join(dir, "selected-header");
|
|
963
|
+
const firstHeader = join(dir, "implicit-one.h");
|
|
964
|
+
const secondHeader = join(dir, "implicit-two.h");
|
|
965
|
+
const cPath = join(dir, "program.c");
|
|
966
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
967
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
968
|
+
const oldDisableCcache = process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
969
|
+
const oldPath = process.env["PATH"];
|
|
970
|
+
const oldRealClang = process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
971
|
+
const oldSelector = process.env["SCRIPTC_TEST_IMPLICIT_SELECTOR"];
|
|
972
|
+
const realClang = (oldPath ?? "")
|
|
973
|
+
.split(delimiter)
|
|
974
|
+
.map((entry) => join(entry === "" ? process.cwd() : entry, "clang"))
|
|
975
|
+
.find((candidate) => existsSync(candidate));
|
|
976
|
+
expect(realClang).toBeDefined();
|
|
977
|
+
try {
|
|
978
|
+
await mkdir(binDir);
|
|
979
|
+
const wrapper = join(binDir, "clang");
|
|
980
|
+
await writeFile(wrapper, `#!/bin/sh
|
|
981
|
+
IFS= read -r selected < "$SCRIPTC_TEST_IMPLICIT_SELECTOR"
|
|
982
|
+
exec "$SCRIPTC_TEST_REAL_CLANG" -include "$selected" "$@"
|
|
983
|
+
`);
|
|
984
|
+
await chmod(wrapper, 0o755);
|
|
985
|
+
await Promise.all([
|
|
986
|
+
writeFile(firstHeader, '#define SCRIPTC_IMPLICIT_REDIRECT "one"\n'),
|
|
987
|
+
writeFile(secondHeader, '#define SCRIPTC_IMPLICIT_REDIRECT "two"\n'),
|
|
988
|
+
writeFile(cPath, '#include <stdio.h>\nint main(void) { puts(SCRIPTC_IMPLICIT_REDIRECT); return 0; }\n'),
|
|
989
|
+
]);
|
|
990
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
991
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = "1";
|
|
992
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = realClang;
|
|
993
|
+
process.env["SCRIPTC_TEST_IMPLICIT_SELECTOR"] = selector;
|
|
994
|
+
process.env["PATH"] = `${binDir}${delimiter}${oldPath ?? ""}`;
|
|
995
|
+
trustInstrumentedCompilerWrapper();
|
|
996
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
997
|
+
await writeFile(selector, `${firstHeader}\n`);
|
|
998
|
+
const firstOut = join(dir, "first");
|
|
999
|
+
await compileC({ cPath, outPath: firstOut, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1000
|
+
expect(execFileSync(firstOut, { encoding: "utf8" }).trim()).toBe("one");
|
|
1001
|
+
// Keep the old header and every cache/environment spelling unchanged.
|
|
1002
|
+
// Only a fresh `clang -M` can observe that the wrapper now selects a
|
|
1003
|
+
// different path whose bytes belong to a different cache identity.
|
|
1004
|
+
await writeFile(selector, `${secondHeader}\n`);
|
|
1005
|
+
const secondOut = firstOut;
|
|
1006
|
+
await compileC({ cPath, outPath: secondOut, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1007
|
+
expect(execFileSync(secondOut, { encoding: "utf8" }).trim()).toBe("two");
|
|
1008
|
+
expect(await completeArtifacts(cacheRoot, "bin")).toHaveLength(2);
|
|
1009
|
+
}
|
|
1010
|
+
finally {
|
|
1011
|
+
if (oldCacheDir === undefined)
|
|
1012
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1013
|
+
else
|
|
1014
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1015
|
+
if (oldNoCache === undefined)
|
|
1016
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1017
|
+
else
|
|
1018
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1019
|
+
if (oldDisableCcache === undefined)
|
|
1020
|
+
delete process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
1021
|
+
else
|
|
1022
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = oldDisableCcache;
|
|
1023
|
+
if (oldPath === undefined)
|
|
1024
|
+
delete process.env["PATH"];
|
|
1025
|
+
else
|
|
1026
|
+
process.env["PATH"] = oldPath;
|
|
1027
|
+
if (oldRealClang === undefined)
|
|
1028
|
+
delete process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
1029
|
+
else
|
|
1030
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = oldRealClang;
|
|
1031
|
+
if (oldSelector === undefined)
|
|
1032
|
+
delete process.env["SCRIPTC_TEST_IMPLICIT_SELECTOR"];
|
|
1033
|
+
else
|
|
1034
|
+
process.env["SCRIPTC_TEST_IMPLICIT_SELECTOR"] = oldSelector;
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
test.skipIf(process.platform === "win32")("a missing compiler cannot reuse a dependency list that may have new higher-priority headers", async () => {
|
|
1038
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-missing-compiler-header-"));
|
|
1039
|
+
scratch.push(dir);
|
|
1040
|
+
const cacheRoot = join(dir, "cache");
|
|
1041
|
+
const cPath = join(dir, "program.c");
|
|
1042
|
+
const outPath = join(dir, "program");
|
|
1043
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1044
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1045
|
+
const oldDisableCcache = process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
1046
|
+
const oldPath = process.env["PATH"];
|
|
1047
|
+
try {
|
|
1048
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
1049
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = "1";
|
|
1050
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1051
|
+
await writeFile(cPath, '#include "scr_runtime.h"\n#include <stdio.h>\n#ifndef SHADOW_VALUE\n#define SHADOW_VALUE "old"\n#endif\nint main(void) { puts(SHADOW_VALUE); return 0; }\n');
|
|
1052
|
+
await compileC({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1053
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("old");
|
|
1054
|
+
// The old runtime header remains byte-identical, but this new local
|
|
1055
|
+
// header wins quote-include resolution. Without a compiler, rehashing
|
|
1056
|
+
// only the old dependency path cannot discover the new selection.
|
|
1057
|
+
await writeFile(join(dir, "scr_runtime.h"), '#define SHADOW_VALUE "new"\n');
|
|
1058
|
+
process.env["PATH"] = "";
|
|
1059
|
+
await rm(outPath, { force: true });
|
|
1060
|
+
await expect(compileC({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY })).rejects.toThrow(/failed compiling/);
|
|
1061
|
+
process.env["PATH"] = oldPath;
|
|
1062
|
+
await compileC({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1063
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("new");
|
|
1064
|
+
expect(await completeArtifacts(cacheRoot, "bin")).toHaveLength(2);
|
|
1065
|
+
}
|
|
1066
|
+
finally {
|
|
1067
|
+
if (oldCacheDir === undefined)
|
|
1068
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1069
|
+
else
|
|
1070
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1071
|
+
if (oldNoCache === undefined)
|
|
1072
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1073
|
+
else
|
|
1074
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1075
|
+
if (oldDisableCcache === undefined)
|
|
1076
|
+
delete process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
1077
|
+
else
|
|
1078
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = oldDisableCcache;
|
|
1079
|
+
if (oldPath === undefined)
|
|
1080
|
+
delete process.env["PATH"];
|
|
1081
|
+
else
|
|
1082
|
+
process.env["PATH"] = oldPath;
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
1085
|
+
test.skipIf(process.platform === "win32")("implicit linker inputs join the complete artifact identity", async () => {
|
|
1086
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-implicit-link-"));
|
|
1087
|
+
scratch.push(dir);
|
|
1088
|
+
const cacheRoot = join(dir, "cache");
|
|
1089
|
+
const binDir = join(dir, "bin");
|
|
1090
|
+
const helperSource = join(dir, "implicit-helper.c");
|
|
1091
|
+
const helperObject = join(dir, "implicit-helper.o");
|
|
1092
|
+
const cPath = join(dir, "program.c");
|
|
1093
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1094
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1095
|
+
const oldDisableCcache = process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
1096
|
+
const oldPath = process.env["PATH"];
|
|
1097
|
+
const oldRealClang = process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
1098
|
+
const oldImplicitObject = process.env["SCRIPTC_TEST_IMPLICIT_OBJECT"];
|
|
1099
|
+
const realClang = (oldPath ?? "")
|
|
1100
|
+
.split(delimiter)
|
|
1101
|
+
.map((entry) => join(entry === "" ? process.cwd() : entry, "clang"))
|
|
1102
|
+
.find((candidate) => existsSync(candidate));
|
|
1103
|
+
expect(realClang).toBeDefined();
|
|
1104
|
+
const buildHelper = async (value) => {
|
|
1105
|
+
await writeFile(helperSource, `int scriptc_implicit_helper(void) { return ${value}; }\n`);
|
|
1106
|
+
execFileSync(realClang, ["-c", helperSource, "-o", helperObject]);
|
|
1107
|
+
};
|
|
1108
|
+
try {
|
|
1109
|
+
await mkdir(binDir);
|
|
1110
|
+
const wrapper = join(binDir, "clang");
|
|
1111
|
+
await writeFile(wrapper, `#!/bin/sh
|
|
1112
|
+
compile_only=0
|
|
1113
|
+
dependency_only=0
|
|
1114
|
+
has_output=0
|
|
1115
|
+
for arg in "$@"; do
|
|
1116
|
+
case "$arg" in
|
|
1117
|
+
-c) compile_only=1 ;;
|
|
1118
|
+
-M|-MM) dependency_only=1 ;;
|
|
1119
|
+
-o) has_output=1 ;;
|
|
1120
|
+
esac
|
|
1121
|
+
done
|
|
1122
|
+
if [ "$has_output" = 1 ] && [ "$compile_only" = 0 ] && [ "$dependency_only" = 0 ]; then
|
|
1123
|
+
exec "$SCRIPTC_TEST_REAL_CLANG" "$@" "$SCRIPTC_TEST_IMPLICIT_OBJECT"
|
|
1124
|
+
fi
|
|
1125
|
+
exec "$SCRIPTC_TEST_REAL_CLANG" "$@"
|
|
1126
|
+
`);
|
|
1127
|
+
await chmod(wrapper, 0o755);
|
|
1128
|
+
await writeFile(cPath, '#include <stdio.h>\nint scriptc_implicit_helper(void);\nint main(void) { printf("%d\\n", scriptc_implicit_helper()); return 0; }\n');
|
|
1129
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
1130
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = "1";
|
|
1131
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = realClang;
|
|
1132
|
+
process.env["SCRIPTC_TEST_IMPLICIT_OBJECT"] = helperObject;
|
|
1133
|
+
process.env["PATH"] = `${binDir}${delimiter}${oldPath ?? ""}`;
|
|
1134
|
+
trustInstrumentedCompilerWrapper();
|
|
1135
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1136
|
+
await buildHelper(1);
|
|
1137
|
+
const firstOut = join(dir, "first");
|
|
1138
|
+
await compileC({ cPath, outPath: firstOut, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1139
|
+
expect(execFileSync(firstOut, { encoding: "utf8" }).trim()).toBe("1");
|
|
1140
|
+
await buildHelper(2);
|
|
1141
|
+
const secondOut = firstOut;
|
|
1142
|
+
await compileC({ cPath, outPath: secondOut, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1143
|
+
expect(execFileSync(secondOut, { encoding: "utf8" }).trim()).toBe("2");
|
|
1144
|
+
expect(await completeArtifacts(cacheRoot, "bin")).toHaveLength(2);
|
|
1145
|
+
}
|
|
1146
|
+
finally {
|
|
1147
|
+
if (oldCacheDir === undefined)
|
|
1148
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1149
|
+
else
|
|
1150
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1151
|
+
if (oldNoCache === undefined)
|
|
1152
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1153
|
+
else
|
|
1154
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1155
|
+
if (oldDisableCcache === undefined)
|
|
1156
|
+
delete process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
1157
|
+
else
|
|
1158
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = oldDisableCcache;
|
|
1159
|
+
if (oldPath === undefined)
|
|
1160
|
+
delete process.env["PATH"];
|
|
1161
|
+
else
|
|
1162
|
+
process.env["PATH"] = oldPath;
|
|
1163
|
+
if (oldRealClang === undefined)
|
|
1164
|
+
delete process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
1165
|
+
else
|
|
1166
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = oldRealClang;
|
|
1167
|
+
if (oldImplicitObject === undefined)
|
|
1168
|
+
delete process.env["SCRIPTC_TEST_IMPLICIT_OBJECT"];
|
|
1169
|
+
else
|
|
1170
|
+
process.env["SCRIPTC_TEST_IMPLICIT_OBJECT"] = oldImplicitObject;
|
|
1171
|
+
}
|
|
1172
|
+
});
|
|
1173
|
+
test.skipIf(process.platform === "win32" || zigExecutable === undefined)("zig COFF dry-run inputs retain complete cross-target cache hits", async () => {
|
|
1174
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-zig-coff-link-"));
|
|
1175
|
+
scratch.push(dir);
|
|
1176
|
+
const cacheRoot = join(dir, "cache");
|
|
1177
|
+
const cPath = join(dir, "program.c");
|
|
1178
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1179
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1180
|
+
const oldCc = process.env["SCRIPTC_CC"];
|
|
1181
|
+
const oldTarget = process.env["SCRIPTC_TARGET"];
|
|
1182
|
+
const oldPath = process.env["PATH"];
|
|
1183
|
+
try {
|
|
1184
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
1185
|
+
process.env["SCRIPTC_CC"] = "zigcc";
|
|
1186
|
+
process.env["SCRIPTC_TARGET"] = "x86_64-windows-gnu";
|
|
1187
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1188
|
+
await writeFile(cPath, "int main(void) { return 0; }\n");
|
|
1189
|
+
const firstOut = join(dir, "first.exe");
|
|
1190
|
+
await compileC({ cPath, outPath: firstOut, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1191
|
+
expect(await completeArtifacts(cacheRoot, "bin")).toHaveLength(1);
|
|
1192
|
+
// lld-link rejects GNU ld's `-t`; the Zig `-###` fallback must still
|
|
1193
|
+
// capture every absolute CRT/import-library input. Pin the object-cache
|
|
1194
|
+
// mtimes: a complete hit never stages them, while a miss promotes them.
|
|
1195
|
+
const [objectSet] = (await readdir(join(cacheRoot, "obj"), { withFileTypes: true }))
|
|
1196
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith("build-"));
|
|
1197
|
+
expect(objectSet).toBeDefined();
|
|
1198
|
+
const objectDir = join(cacheRoot, "obj", objectSet.name);
|
|
1199
|
+
const objectNames = (await readdir(objectDir)).filter((name) => name.endsWith(".o"));
|
|
1200
|
+
const pinnedTime = new Date("2000-01-01T00:00:00.000Z");
|
|
1201
|
+
await Promise.all(objectNames.map((name) => utimes(join(objectDir, name), pinnedTime, pinnedTime)));
|
|
1202
|
+
const hitOut = join(dir, "hit.exe");
|
|
1203
|
+
await compileC({ cPath, outPath: hitOut, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1204
|
+
expect(await readFile(hitOut)).toEqual(await readFile(firstOut));
|
|
1205
|
+
for (const name of objectNames) {
|
|
1206
|
+
expect((await stat(join(objectDir, name))).mtimeMs).toBe(pinnedTime.getTime());
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
finally {
|
|
1210
|
+
if (oldCacheDir === undefined)
|
|
1211
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1212
|
+
else
|
|
1213
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1214
|
+
if (oldNoCache === undefined)
|
|
1215
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1216
|
+
else
|
|
1217
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1218
|
+
if (oldCc === undefined)
|
|
1219
|
+
delete process.env["SCRIPTC_CC"];
|
|
1220
|
+
else
|
|
1221
|
+
process.env["SCRIPTC_CC"] = oldCc;
|
|
1222
|
+
if (oldTarget === undefined)
|
|
1223
|
+
delete process.env["SCRIPTC_TARGET"];
|
|
1224
|
+
else
|
|
1225
|
+
process.env["SCRIPTC_TARGET"] = oldTarget;
|
|
1226
|
+
if (oldPath === undefined)
|
|
1227
|
+
delete process.env["PATH"];
|
|
1228
|
+
else
|
|
1229
|
+
process.env["PATH"] = oldPath;
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
test.skipIf(process.platform === "win32")("compiler metadata-probe failures fall back to a normal build", async () => {
|
|
1233
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-probe-fallback-"));
|
|
1234
|
+
scratch.push(dir);
|
|
1235
|
+
const cacheRoot = join(dir, "cache");
|
|
1236
|
+
const binDir = join(dir, "bin");
|
|
1237
|
+
const cPath = join(dir, "program.c");
|
|
1238
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1239
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1240
|
+
const oldPath = process.env["PATH"];
|
|
1241
|
+
const oldRealClang = process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
1242
|
+
const realClang = (oldPath ?? "")
|
|
1243
|
+
.split(delimiter)
|
|
1244
|
+
.map((entry) => join(entry === "" ? process.cwd() : entry, "clang"))
|
|
1245
|
+
.find((candidate) => existsSync(candidate));
|
|
1246
|
+
expect(realClang).toBeDefined();
|
|
1247
|
+
try {
|
|
1248
|
+
await mkdir(binDir);
|
|
1249
|
+
const wrapper = join(binDir, "clang");
|
|
1250
|
+
await writeFile(wrapper, `#!/bin/sh
|
|
1251
|
+
if [ "$1" = "--version" ]; then exit 7; fi
|
|
1252
|
+
exec "$SCRIPTC_TEST_REAL_CLANG" "$@"
|
|
1253
|
+
`);
|
|
1254
|
+
await chmod(wrapper, 0o755);
|
|
1255
|
+
await writeFile(cPath, "int main(void) { return 0; }\n");
|
|
1256
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
1257
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = realClang;
|
|
1258
|
+
process.env["PATH"] = `${binDir}${delimiter}${oldPath ?? ""}`;
|
|
1259
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1260
|
+
const outPath = join(dir, "out");
|
|
1261
|
+
await compileC({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1262
|
+
expect((await stat(outPath)).isFile()).toBe(true);
|
|
1263
|
+
await expect(stat(join(cacheRoot, "bin"))).rejects.toMatchObject({ code: "ENOENT" });
|
|
1264
|
+
}
|
|
1265
|
+
finally {
|
|
1266
|
+
if (oldCacheDir === undefined)
|
|
1267
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1268
|
+
else
|
|
1269
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1270
|
+
if (oldNoCache === undefined)
|
|
1271
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1272
|
+
else
|
|
1273
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1274
|
+
if (oldPath === undefined)
|
|
1275
|
+
delete process.env["PATH"];
|
|
1276
|
+
else
|
|
1277
|
+
process.env["PATH"] = oldPath;
|
|
1278
|
+
if (oldRealClang === undefined)
|
|
1279
|
+
delete process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
1280
|
+
else
|
|
1281
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = oldRealClang;
|
|
1282
|
+
}
|
|
1283
|
+
});
|
|
1284
|
+
test("staged runtime objects survive removal of their cache names and are promoted in the LRU", async () => {
|
|
1285
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-object-stage-"));
|
|
1286
|
+
scratch.push(dir);
|
|
1287
|
+
const cachedObject = join(dir, "cache", "scr_number.o");
|
|
1288
|
+
const source = join(dir, "runtime", "scr_number.c");
|
|
1289
|
+
await mkdir(join(dir, "cache"), { recursive: true });
|
|
1290
|
+
await writeFile(cachedObject, "cached object bytes");
|
|
1291
|
+
const pinnedTime = new Date("2000-01-01T00:00:00.000Z");
|
|
1292
|
+
await utimes(cachedObject, pinnedTime, pinnedTime);
|
|
1293
|
+
const staged = await stageRuntimeObjects(new Map([[source, cachedObject]]), join(dir, "stage"));
|
|
1294
|
+
expect((await stat(cachedObject)).mtimeMs).toBeGreaterThan(pinnedTime.getTime());
|
|
1295
|
+
await rm(cachedObject);
|
|
1296
|
+
expect(await readFile(staged.get(source))).toEqual(Buffer.from("cached object bytes"));
|
|
1297
|
+
});
|
|
1298
|
+
test("cache hits honor the current umask", async () => {
|
|
1299
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-environment-"));
|
|
1300
|
+
scratch.push(dir);
|
|
1301
|
+
const cacheRoot = join(dir, "cache");
|
|
1302
|
+
const cPath = join(dir, "program.c");
|
|
1303
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1304
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1305
|
+
const oldUmask = process.umask();
|
|
1306
|
+
try {
|
|
1307
|
+
await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("one"); return 0; }\n');
|
|
1308
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
1309
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1310
|
+
process.umask(0o022);
|
|
1311
|
+
const firstOut = join(dir, "first");
|
|
1312
|
+
await compileC({ cPath, outPath: firstOut, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1313
|
+
expect(execFileSync(firstOut, { encoding: "utf8" }).trim()).toBe("one");
|
|
1314
|
+
if (process.platform !== "win32") {
|
|
1315
|
+
expect((await stat(firstOut)).mode & 0o777).toBe(0o755);
|
|
1316
|
+
expect((await stat(cacheRoot)).mode & 0o777).toBe(0o700);
|
|
1317
|
+
const [cachedBinary] = await completeArtifacts(cacheRoot, "bin");
|
|
1318
|
+
expect((await stat(join(cacheRoot, "bin", cachedBinary))).mode & 0o777).toBe(0o600);
|
|
1319
|
+
}
|
|
1320
|
+
// A hit populated under a permissive umask adopts the current restrictive
|
|
1321
|
+
// one instead of restoring the cached file's broader mode.
|
|
1322
|
+
process.umask(0o077);
|
|
1323
|
+
const hitOut = firstOut;
|
|
1324
|
+
await compileC({ cPath, outPath: hitOut, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1325
|
+
expect(execFileSync(hitOut, { encoding: "utf8" }).trim()).toBe("one");
|
|
1326
|
+
if (process.platform !== "win32")
|
|
1327
|
+
expect((await stat(hitOut)).mode & 0o777).toBe(0o700);
|
|
1328
|
+
expect(await completeArtifacts(cacheRoot, "bin")).toHaveLength(1);
|
|
1329
|
+
process.umask(0o022);
|
|
1330
|
+
const firstArchivePath = join(dir, "first.lib.a");
|
|
1331
|
+
await compileLibArchive({ cPath, outPath: firstArchivePath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1332
|
+
const firstArchive = await readFile(firstArchivePath);
|
|
1333
|
+
if (process.platform !== "win32") {
|
|
1334
|
+
const [cachedArchive] = await completeArtifacts(cacheRoot, "lib");
|
|
1335
|
+
expect((await stat(join(cacheRoot, "lib", cachedArchive))).mode & 0o777).toBe(0o600);
|
|
1336
|
+
}
|
|
1337
|
+
process.umask(0o077);
|
|
1338
|
+
const hitArchivePath = join(dir, "hit.lib.a");
|
|
1339
|
+
await compileLibArchive({ cPath, outPath: hitArchivePath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1340
|
+
expect(await readFile(hitArchivePath)).toEqual(firstArchive);
|
|
1341
|
+
if (process.platform !== "win32")
|
|
1342
|
+
expect((await stat(hitArchivePath)).mode & 0o777).toBe(0o600);
|
|
1343
|
+
expect(await completeArtifacts(cacheRoot, "lib")).toHaveLength(1);
|
|
1344
|
+
}
|
|
1345
|
+
finally {
|
|
1346
|
+
process.umask(oldUmask);
|
|
1347
|
+
if (oldCacheDir === undefined)
|
|
1348
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1349
|
+
else
|
|
1350
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1351
|
+
if (oldNoCache === undefined)
|
|
1352
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1353
|
+
else
|
|
1354
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1357
|
+
test.skipIf(process.platform === "win32")("an existing cache override is never made private by mutating its permissions", async () => {
|
|
1358
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-existing-root-"));
|
|
1359
|
+
scratch.push(dir);
|
|
1360
|
+
const cacheRoot = join(dir, "shared-cache");
|
|
1361
|
+
const cPath = join(dir, "program.c");
|
|
1362
|
+
const outPath = join(dir, "program");
|
|
1363
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1364
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1365
|
+
try {
|
|
1366
|
+
await mkdir(cacheRoot);
|
|
1367
|
+
await chmod(cacheRoot, 0o755);
|
|
1368
|
+
await writeFile(cPath, "int main(void) { return 0; }\n");
|
|
1369
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
1370
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1371
|
+
await compileC({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1372
|
+
expect((await stat(outPath)).isFile()).toBe(true);
|
|
1373
|
+
expect((await stat(cacheRoot)).mode & 0o777).toBe(0o755);
|
|
1374
|
+
await expect(stat(join(cacheRoot, "bin"))).rejects.toMatchObject({ code: "ENOENT" });
|
|
1375
|
+
}
|
|
1376
|
+
finally {
|
|
1377
|
+
if (oldCacheDir === undefined)
|
|
1378
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1379
|
+
else
|
|
1380
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1381
|
+
if (oldNoCache === undefined)
|
|
1382
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1383
|
+
else
|
|
1384
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1385
|
+
}
|
|
1386
|
+
});
|
|
1387
|
+
test.skipIf(process.platform === "win32")("cache installs support destination basenames near NAME_MAX", async () => {
|
|
1388
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-long-output-"));
|
|
1389
|
+
scratch.push(dir);
|
|
1390
|
+
const cacheRoot = join(dir, "cache");
|
|
1391
|
+
const cPath = join(dir, "program.c");
|
|
1392
|
+
const executablePath = join(dir, "x".repeat(235));
|
|
1393
|
+
const archivePath = join(dir, `${"a".repeat(229)}.lib.a`);
|
|
1394
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1395
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1396
|
+
try {
|
|
1397
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
1398
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1399
|
+
await writeFile(cPath, "int main(void) { return 0; }\n");
|
|
1400
|
+
await compileC({ cPath, outPath: executablePath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1401
|
+
expect((await stat(executablePath)).isFile()).toBe(true);
|
|
1402
|
+
await compileC({ cPath, outPath: executablePath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1403
|
+
expect((await stat(executablePath)).isFile()).toBe(true);
|
|
1404
|
+
await compileLibArchive({ cPath, outPath: archivePath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1405
|
+
const firstArchive = await readFile(archivePath);
|
|
1406
|
+
await compileLibArchive({ cPath, outPath: archivePath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1407
|
+
expect(await readFile(archivePath)).toEqual(firstArchive);
|
|
1408
|
+
}
|
|
1409
|
+
finally {
|
|
1410
|
+
if (oldCacheDir === undefined)
|
|
1411
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1412
|
+
else
|
|
1413
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1414
|
+
if (oldNoCache === undefined)
|
|
1415
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1416
|
+
else
|
|
1417
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1418
|
+
}
|
|
1419
|
+
});
|
|
1420
|
+
test("the configured cache maximum is enforced after writes", async () => {
|
|
1421
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-cap-"));
|
|
1422
|
+
scratch.push(dir);
|
|
1423
|
+
const cacheRoot = join(dir, "cache");
|
|
1424
|
+
const cPath = join(dir, "program.c");
|
|
1425
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1426
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1427
|
+
const oldMax = process.env["SCRIPTC_CACHE_MAX_MB"];
|
|
1428
|
+
const capBytes = 0.5 * 1024 * 1024;
|
|
1429
|
+
try {
|
|
1430
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
1431
|
+
process.env["SCRIPTC_CACHE_MAX_MB"] = "0.5";
|
|
1432
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1433
|
+
await writeFile(cPath, "int main(void) { return 0; }\n");
|
|
1434
|
+
await compileC({
|
|
1435
|
+
cPath,
|
|
1436
|
+
outPath: join(dir, "out"),
|
|
1437
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
1438
|
+
});
|
|
1439
|
+
expect(await cacheTreeBytes(cacheRoot)).toBeLessThanOrEqual(capBytes);
|
|
1440
|
+
// Long-lived callers write repeatedly through one process. Every write,
|
|
1441
|
+
// not only the first or a periodic sample, must leave the tree under cap.
|
|
1442
|
+
await writeFile(cPath, "int main(void) { return 1; }\n");
|
|
1443
|
+
await compileC({
|
|
1444
|
+
cPath,
|
|
1445
|
+
outPath: join(dir, "out"),
|
|
1446
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
1447
|
+
});
|
|
1448
|
+
expect(await cacheTreeBytes(cacheRoot)).toBeLessThanOrEqual(capBytes);
|
|
1449
|
+
}
|
|
1450
|
+
finally {
|
|
1451
|
+
if (oldCacheDir === undefined)
|
|
1452
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1453
|
+
else
|
|
1454
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1455
|
+
if (oldNoCache === undefined)
|
|
1456
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1457
|
+
else
|
|
1458
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1459
|
+
if (oldMax === undefined)
|
|
1460
|
+
delete process.env["SCRIPTC_CACHE_MAX_MB"];
|
|
1461
|
+
else
|
|
1462
|
+
process.env["SCRIPTC_CACHE_MAX_MB"] = oldMax;
|
|
1463
|
+
}
|
|
1464
|
+
});
|
|
1465
|
+
test("mutable compiler inputs bypass caches when files change in place", async () => {
|
|
1466
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-ambient-input-"));
|
|
1467
|
+
scratch.push(dir);
|
|
1468
|
+
const cacheRoot = join(dir, "cache");
|
|
1469
|
+
const vendorCacheRoot = join(dir, "vendor-cache");
|
|
1470
|
+
const headers = join(dir, "headers");
|
|
1471
|
+
const headerPath = join(headers, "cache_probe.h");
|
|
1472
|
+
const cPath = join(dir, "program.c");
|
|
1473
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1474
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1475
|
+
const oldCpath = process.env["CPATH"];
|
|
1476
|
+
const oldVendorCacheDir = process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1477
|
+
try {
|
|
1478
|
+
await mkdir(headers);
|
|
1479
|
+
await writeFile(cPath, '#include <stdio.h>\n#include <cache_probe.h>\nint main(void) { puts(CACHE_PROBE); return 0; }\n');
|
|
1480
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
1481
|
+
process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"] = vendorCacheRoot;
|
|
1482
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1483
|
+
process.env["CPATH"] = headers;
|
|
1484
|
+
await writeFile(headerPath, '#define CACHE_PROBE "one"\n');
|
|
1485
|
+
const firstOut = join(dir, "first");
|
|
1486
|
+
await compileC({
|
|
1487
|
+
cPath,
|
|
1488
|
+
outPath: firstOut,
|
|
1489
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
1490
|
+
regex: true,
|
|
1491
|
+
});
|
|
1492
|
+
expect(execFileSync(firstOut, { encoding: "utf8" }).trim()).toBe("one");
|
|
1493
|
+
// The environment spelling is unchanged; only a file behind it moves.
|
|
1494
|
+
await writeFile(headerPath, '#define CACHE_PROBE "two"\n');
|
|
1495
|
+
const secondOut = join(dir, "second");
|
|
1496
|
+
await compileC({
|
|
1497
|
+
cPath,
|
|
1498
|
+
outPath: secondOut,
|
|
1499
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
1500
|
+
regex: true,
|
|
1501
|
+
});
|
|
1502
|
+
expect(execFileSync(secondOut, { encoding: "utf8" }).trim()).toBe("two");
|
|
1503
|
+
const firstArchivePath = join(dir, "first.lib.a");
|
|
1504
|
+
await compileLibArchive({ cPath, outPath: firstArchivePath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1505
|
+
const firstArchive = await readFile(firstArchivePath);
|
|
1506
|
+
await writeFile(headerPath, '#define CACHE_PROBE "three"\n');
|
|
1507
|
+
const secondArchivePath = join(dir, "second.lib.a");
|
|
1508
|
+
await compileLibArchive({ cPath, outPath: secondArchivePath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1509
|
+
expect(await readFile(secondArchivePath)).not.toEqual(firstArchive);
|
|
1510
|
+
// CPATH can mutate behind a stable string, so no cache tier is populated.
|
|
1511
|
+
await expect(stat(cacheRoot)).rejects.toMatchObject({ code: "ENOENT" });
|
|
1512
|
+
await expect(stat(vendorCacheRoot)).rejects.toMatchObject({ code: "ENOENT" });
|
|
1513
|
+
}
|
|
1514
|
+
finally {
|
|
1515
|
+
if (oldCacheDir === undefined)
|
|
1516
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1517
|
+
else
|
|
1518
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1519
|
+
if (oldNoCache === undefined)
|
|
1520
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1521
|
+
else
|
|
1522
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1523
|
+
if (oldCpath === undefined)
|
|
1524
|
+
delete process.env["CPATH"];
|
|
1525
|
+
else
|
|
1526
|
+
process.env["CPATH"] = oldCpath;
|
|
1527
|
+
if (oldVendorCacheDir === undefined)
|
|
1528
|
+
delete process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1529
|
+
else
|
|
1530
|
+
process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"] = oldVendorCacheDir;
|
|
1531
|
+
}
|
|
1532
|
+
});
|
|
1533
|
+
test("the hard disable bypasses vendor prerequisite caches", async () => {
|
|
1534
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-no-cache-vendor-"));
|
|
1535
|
+
scratch.push(dir);
|
|
1536
|
+
const cacheRoot = join(dir, "cache");
|
|
1537
|
+
const vendorCacheRoot = join(dir, "vendor-cache");
|
|
1538
|
+
const cPath = join(dir, "program.c");
|
|
1539
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1540
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1541
|
+
const oldVendorCacheDir = process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1542
|
+
const corruption = Buffer.from("scriptc-corrupt-vendor-object\n");
|
|
1543
|
+
try {
|
|
1544
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
1545
|
+
process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"] = vendorCacheRoot;
|
|
1546
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1547
|
+
await writeFile(cPath, "int main(void) { return 0; }\n");
|
|
1548
|
+
await compileC({
|
|
1549
|
+
cPath,
|
|
1550
|
+
outPath: join(dir, "populated"),
|
|
1551
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
1552
|
+
regex: true,
|
|
1553
|
+
});
|
|
1554
|
+
const lreCache = (await readdir(vendorCacheRoot)).find((name) => name.includes("-lre-"));
|
|
1555
|
+
expect(lreCache).toBeDefined();
|
|
1556
|
+
const cachedObject = join(vendorCacheRoot, lreCache, "libregexp.o");
|
|
1557
|
+
await writeFile(cachedObject, corruption);
|
|
1558
|
+
process.env["SCRIPTC_NO_CACHE"] = "1";
|
|
1559
|
+
await compileC({
|
|
1560
|
+
cPath,
|
|
1561
|
+
outPath: join(dir, "disabled"),
|
|
1562
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
1563
|
+
regex: true,
|
|
1564
|
+
});
|
|
1565
|
+
await compileLibArchive({
|
|
1566
|
+
cPath,
|
|
1567
|
+
outPath: join(dir, "disabled.lib.a"),
|
|
1568
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
1569
|
+
regex: true,
|
|
1570
|
+
});
|
|
1571
|
+
// Neither disabled build consulted or repaired the package-local entry;
|
|
1572
|
+
// each built its prerequisite in a private, disposable temp root instead.
|
|
1573
|
+
expect(await readFile(cachedObject)).toEqual(corruption);
|
|
1574
|
+
}
|
|
1575
|
+
finally {
|
|
1576
|
+
if (oldCacheDir === undefined)
|
|
1577
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1578
|
+
else
|
|
1579
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1580
|
+
if (oldNoCache === undefined)
|
|
1581
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1582
|
+
else
|
|
1583
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1584
|
+
if (oldVendorCacheDir === undefined)
|
|
1585
|
+
delete process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1586
|
+
else
|
|
1587
|
+
process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"] = oldVendorCacheDir;
|
|
1588
|
+
}
|
|
1589
|
+
});
|
|
1590
|
+
test.skipIf(process.platform !== "darwin")("vendor object caches separate deployment-target environments", async () => {
|
|
1591
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-vendor-environment-"));
|
|
1592
|
+
scratch.push(dir);
|
|
1593
|
+
const cacheRoot = join(dir, "cache");
|
|
1594
|
+
const vendorCacheRoot = join(dir, "vendor-cache");
|
|
1595
|
+
const cPath = join(dir, "program.c");
|
|
1596
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1597
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1598
|
+
const oldVendorCacheDir = process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1599
|
+
const oldDeploymentTarget = process.env["MACOSX_DEPLOYMENT_TARGET"];
|
|
1600
|
+
try {
|
|
1601
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
1602
|
+
process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"] = vendorCacheRoot;
|
|
1603
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1604
|
+
await writeFile(cPath, "int main(void) { return 0; }\n");
|
|
1605
|
+
process.env["MACOSX_DEPLOYMENT_TARGET"] = "14.0";
|
|
1606
|
+
await compileC({
|
|
1607
|
+
cPath,
|
|
1608
|
+
outPath: join(dir, "newer"),
|
|
1609
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
1610
|
+
regex: true,
|
|
1611
|
+
});
|
|
1612
|
+
process.env["MACOSX_DEPLOYMENT_TARGET"] = "11.0";
|
|
1613
|
+
await compileC({
|
|
1614
|
+
cPath,
|
|
1615
|
+
outPath: join(dir, "older"),
|
|
1616
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
1617
|
+
regex: true,
|
|
1618
|
+
});
|
|
1619
|
+
const lreCaches = (await readdir(vendorCacheRoot)).filter((name) => name.includes("-lre-"));
|
|
1620
|
+
expect(lreCaches).toHaveLength(2);
|
|
1621
|
+
const minVersions = lreCaches.map((cache) => execFileSync("vtool", ["-show-build", join(vendorCacheRoot, cache, "libregexp.o")], {
|
|
1622
|
+
encoding: "utf8",
|
|
1623
|
+
}).match(/minos\s+(\S+)/)?.[1]);
|
|
1624
|
+
expect(new Set(minVersions)).toEqual(new Set(["11.0", "14.0"]));
|
|
1625
|
+
}
|
|
1626
|
+
finally {
|
|
1627
|
+
if (oldCacheDir === undefined)
|
|
1628
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1629
|
+
else
|
|
1630
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1631
|
+
if (oldNoCache === undefined)
|
|
1632
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1633
|
+
else
|
|
1634
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1635
|
+
if (oldVendorCacheDir === undefined)
|
|
1636
|
+
delete process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1637
|
+
else
|
|
1638
|
+
process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"] = oldVendorCacheDir;
|
|
1639
|
+
if (oldDeploymentTarget === undefined)
|
|
1640
|
+
delete process.env["MACOSX_DEPLOYMENT_TARGET"];
|
|
1641
|
+
else
|
|
1642
|
+
process.env["MACOSX_DEPLOYMENT_TARGET"] = oldDeploymentTarget;
|
|
1643
|
+
}
|
|
1644
|
+
});
|
|
1645
|
+
test("complete binary hits precede missing vendor prerequisite materialization", async () => {
|
|
1646
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-vendor-hit-"));
|
|
1647
|
+
scratch.push(dir);
|
|
1648
|
+
const cacheRoot = join(dir, "cache");
|
|
1649
|
+
const vendorCacheRoot = join(dir, "vendor-cache");
|
|
1650
|
+
const cPath = join(dir, "program.c");
|
|
1651
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1652
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1653
|
+
const oldVendorCacheDir = process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1654
|
+
try {
|
|
1655
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
1656
|
+
process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"] = vendorCacheRoot;
|
|
1657
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1658
|
+
await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("cached"); return 0; }\n');
|
|
1659
|
+
const firstOut = join(dir, "first");
|
|
1660
|
+
await compileC({
|
|
1661
|
+
cPath,
|
|
1662
|
+
outPath: firstOut,
|
|
1663
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
1664
|
+
regex: true,
|
|
1665
|
+
});
|
|
1666
|
+
expect(execFileSync(firstOut, { encoding: "utf8" }).trim()).toBe("cached");
|
|
1667
|
+
expect(await completeArtifacts(cacheRoot, "bin")).toHaveLength(1);
|
|
1668
|
+
expect((await stat(vendorCacheRoot)).isDirectory()).toBe(true);
|
|
1669
|
+
// Simulate a package reinstall: the per-package vendor build disappears,
|
|
1670
|
+
// while the per-user complete executable remains. The hit must not
|
|
1671
|
+
// recreate that directory even though the compiler remains available for
|
|
1672
|
+
// fresh dependency discovery.
|
|
1673
|
+
await rm(vendorCacheRoot, { recursive: true, force: true });
|
|
1674
|
+
const hitOut = firstOut;
|
|
1675
|
+
await compileC({
|
|
1676
|
+
cPath,
|
|
1677
|
+
outPath: hitOut,
|
|
1678
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
1679
|
+
regex: true,
|
|
1680
|
+
});
|
|
1681
|
+
expect(execFileSync(hitOut, { encoding: "utf8" }).trim()).toBe("cached");
|
|
1682
|
+
await expect(stat(vendorCacheRoot)).rejects.toMatchObject({ code: "ENOENT" });
|
|
1683
|
+
}
|
|
1684
|
+
finally {
|
|
1685
|
+
if (oldCacheDir === undefined)
|
|
1686
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1687
|
+
else
|
|
1688
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1689
|
+
if (oldNoCache === undefined)
|
|
1690
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1691
|
+
else
|
|
1692
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1693
|
+
if (oldVendorCacheDir === undefined)
|
|
1694
|
+
delete process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
|
1695
|
+
else
|
|
1696
|
+
process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"] = oldVendorCacheDir;
|
|
1697
|
+
}
|
|
1698
|
+
});
|
|
1699
|
+
test("arbitrary C bypasses persistent artifacts so same-path header edits cannot go stale", async () => {
|
|
1700
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-arbitrary-c-cache-"));
|
|
1701
|
+
scratch.push(dir);
|
|
1702
|
+
const cacheRoot = join(dir, "cache");
|
|
1703
|
+
const cPath = join(dir, "program.c");
|
|
1704
|
+
const headerPath = join(dir, "value.h");
|
|
1705
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1706
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1707
|
+
try {
|
|
1708
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
1709
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1710
|
+
await writeFile(cPath, '#include <stdio.h>\n#include "value.h"\nint main(void) { puts(VALUE); return 0; }\n');
|
|
1711
|
+
await writeFile(headerPath, '#define VALUE "one"\n');
|
|
1712
|
+
const firstOut = join(dir, "first");
|
|
1713
|
+
await compileC({ cPath, outPath: firstOut });
|
|
1714
|
+
expect(execFileSync(firstOut, { encoding: "utf8" }).trim()).toBe("one");
|
|
1715
|
+
await writeFile(headerPath, '#define VALUE "two"\n');
|
|
1716
|
+
const secondOut = join(dir, "second");
|
|
1717
|
+
await compileC({ cPath, outPath: secondOut });
|
|
1718
|
+
expect(execFileSync(secondOut, { encoding: "utf8" }).trim()).toBe("two");
|
|
1719
|
+
await expect(stat(join(cacheRoot, "bin"))).rejects.toMatchObject({ code: "ENOENT" });
|
|
1720
|
+
const archiveOut = join(dir, "program.lib.a");
|
|
1721
|
+
await compileLibArchive({ cPath, outPath: archiveOut });
|
|
1722
|
+
expect((await stat(archiveOut)).isFile()).toBe(true);
|
|
1723
|
+
await expect(stat(join(cacheRoot, "lib"))).rejects.toMatchObject({ code: "ENOENT" });
|
|
1724
|
+
}
|
|
1725
|
+
finally {
|
|
1726
|
+
if (oldCacheDir === undefined)
|
|
1727
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1728
|
+
else
|
|
1729
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1730
|
+
if (oldNoCache === undefined)
|
|
1731
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1732
|
+
else
|
|
1733
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1734
|
+
}
|
|
1735
|
+
});
|
|
1736
|
+
test("cached translation units keep the compiler-visible source path in their identity", async () => {
|
|
1737
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-source-path-"));
|
|
1738
|
+
scratch.push(dir);
|
|
1739
|
+
const cacheRoot = join(dir, "cache");
|
|
1740
|
+
const aPath = join(dir, "a.c");
|
|
1741
|
+
const bPath = join(dir, "b.c");
|
|
1742
|
+
const headerPath = join(dir, "cache_path_header.h");
|
|
1743
|
+
const source = '#include <stdio.h>\n#include "cache_path_header.h"\nint main(void) { puts(__FILE__); puts(CACHE_PATH_HEADER); return 0; }\n';
|
|
1744
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1745
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1746
|
+
try {
|
|
1747
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
1748
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1749
|
+
await Promise.all([
|
|
1750
|
+
writeFile(aPath, source),
|
|
1751
|
+
writeFile(bPath, source),
|
|
1752
|
+
writeFile(headerPath, '#define CACHE_PATH_HEADER "header"\n'),
|
|
1753
|
+
]);
|
|
1754
|
+
const aOut = join(dir, "a-out", "program");
|
|
1755
|
+
const bOut = join(dir, "b-out", "program");
|
|
1756
|
+
await Promise.all([mkdir(join(dir, "a-out")), mkdir(join(dir, "b-out"))]);
|
|
1757
|
+
await compileC({ cPath: aPath, outPath: aOut, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1758
|
+
await compileC({ cPath: bPath, outPath: bOut, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1759
|
+
expect(execFileSync(aOut, { encoding: "utf8" }).trim()).toBe(`${aPath}\nheader`);
|
|
1760
|
+
expect(execFileSync(bOut, { encoding: "utf8" }).trim()).toBe(`${bPath}\nheader`);
|
|
1761
|
+
expect(await completeArtifacts(cacheRoot, "bin")).toHaveLength(2);
|
|
1762
|
+
}
|
|
1763
|
+
finally {
|
|
1764
|
+
if (oldCacheDir === undefined)
|
|
1765
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1766
|
+
else
|
|
1767
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1768
|
+
if (oldNoCache === undefined)
|
|
1769
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1770
|
+
else
|
|
1771
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1772
|
+
}
|
|
1773
|
+
});
|
|
1774
|
+
test.skipIf(process.platform === "win32")("cache publication compiles the exact translation-unit bytes used by its key", async () => {
|
|
1775
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-tu-race-"));
|
|
1776
|
+
scratch.push(dir);
|
|
1777
|
+
const cacheRoot = join(dir, "cache");
|
|
1778
|
+
const binDir = join(dir, "bin");
|
|
1779
|
+
const cPath = join(dir, "program.c");
|
|
1780
|
+
const signal = join(dir, "compile-started");
|
|
1781
|
+
const release = join(dir, "compile-release");
|
|
1782
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1783
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1784
|
+
const oldPath = process.env["PATH"];
|
|
1785
|
+
const oldRealClang = process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
1786
|
+
const oldRaceSource = process.env["SCRIPTC_TEST_RACE_SOURCE"];
|
|
1787
|
+
const oldRaceSignal = process.env["SCRIPTC_TEST_RACE_SIGNAL"];
|
|
1788
|
+
const oldRaceRelease = process.env["SCRIPTC_TEST_RACE_RELEASE"];
|
|
1789
|
+
const originalClang = (oldPath ?? "")
|
|
1790
|
+
.split(delimiter)
|
|
1791
|
+
.map((entry) => join(entry === "" ? process.cwd() : entry, "clang"))
|
|
1792
|
+
.find((candidate) => existsSync(candidate));
|
|
1793
|
+
expect(originalClang).toBeDefined();
|
|
1794
|
+
const source = (value) => `#include <stdio.h>\nint main(void) { puts("${value}"); return 0; }\n`;
|
|
1795
|
+
const waitForSignal = async () => {
|
|
1796
|
+
const deadline = Date.now() + 10_000;
|
|
1797
|
+
while (!existsSync(signal)) {
|
|
1798
|
+
if (Date.now() > deadline)
|
|
1799
|
+
throw new Error("timed out waiting for the compiler wrapper");
|
|
1800
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 10));
|
|
1801
|
+
}
|
|
1802
|
+
};
|
|
1803
|
+
try {
|
|
1804
|
+
await mkdir(binDir);
|
|
1805
|
+
const wrapper = join(binDir, "clang");
|
|
1806
|
+
await writeFile(wrapper, `#!/bin/sh
|
|
1807
|
+
for arg in "$@"; do
|
|
1808
|
+
case "$arg" in
|
|
1809
|
+
"$SCRIPTC_TEST_RACE_SOURCE"|"-ffile-prefix-map="*"$SCRIPTC_TEST_RACE_SOURCE")
|
|
1810
|
+
: > "$SCRIPTC_TEST_RACE_SIGNAL"
|
|
1811
|
+
while [ ! -e "$SCRIPTC_TEST_RACE_RELEASE" ]; do sleep 0.01; done
|
|
1812
|
+
break
|
|
1813
|
+
;;
|
|
1814
|
+
esac
|
|
1815
|
+
done
|
|
1816
|
+
exec "$SCRIPTC_TEST_REAL_CLANG" "$@"
|
|
1817
|
+
`);
|
|
1818
|
+
// ccache availability is memoized across this file. If an earlier test
|
|
1819
|
+
// found it, keep the controlled compiler wrapper in the path by making
|
|
1820
|
+
// a cache shim that simply forwards its compiler argv.
|
|
1821
|
+
const ccacheWrapper = join(binDir, "ccache");
|
|
1822
|
+
await writeFile(ccacheWrapper, `#!/bin/sh
|
|
1823
|
+
if [ "$1" = "--version" ]; then exit 1; fi
|
|
1824
|
+
exec "$@"
|
|
1825
|
+
`);
|
|
1826
|
+
await Promise.all([chmod(wrapper, 0o755), chmod(ccacheWrapper, 0o755)]);
|
|
1827
|
+
await writeFile(cPath, source("one"));
|
|
1828
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
1829
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = originalClang;
|
|
1830
|
+
process.env["SCRIPTC_TEST_RACE_SOURCE"] = cPath;
|
|
1831
|
+
process.env["SCRIPTC_TEST_RACE_SIGNAL"] = signal;
|
|
1832
|
+
process.env["SCRIPTC_TEST_RACE_RELEASE"] = release;
|
|
1833
|
+
process.env["PATH"] = `${binDir}${delimiter}${oldPath ?? ""}`;
|
|
1834
|
+
trustInstrumentedCompilerWrapper();
|
|
1835
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1836
|
+
const firstOut = join(dir, "first");
|
|
1837
|
+
const firstBuild = compileC({
|
|
1838
|
+
cPath,
|
|
1839
|
+
outPath: firstOut,
|
|
1840
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
1841
|
+
});
|
|
1842
|
+
await Promise.race([
|
|
1843
|
+
waitForSignal(),
|
|
1844
|
+
firstBuild.then(() => {
|
|
1845
|
+
throw new Error("compile completed without reaching the race barrier");
|
|
1846
|
+
}),
|
|
1847
|
+
]);
|
|
1848
|
+
// The caller-visible generated path changes after its bytes have joined
|
|
1849
|
+
// the key but before clang is allowed to read its input.
|
|
1850
|
+
await writeFile(cPath, source("two"));
|
|
1851
|
+
await writeFile(release, "go");
|
|
1852
|
+
await firstBuild;
|
|
1853
|
+
expect(execFileSync(firstOut, { encoding: "utf8" }).trim()).toBe("one");
|
|
1854
|
+
const secondOut = join(dir, "second");
|
|
1855
|
+
await compileC({ cPath, outPath: secondOut, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1856
|
+
expect(execFileSync(secondOut, { encoding: "utf8" }).trim()).toBe("two");
|
|
1857
|
+
await writeFile(cPath, source("one"));
|
|
1858
|
+
const hitOut = firstOut;
|
|
1859
|
+
await compileC({ cPath, outPath: hitOut, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
1860
|
+
expect(execFileSync(hitOut, { encoding: "utf8" }).trim()).toBe("one");
|
|
1861
|
+
expect(await completeArtifacts(cacheRoot, "bin")).toHaveLength(2);
|
|
1862
|
+
await Promise.all([rm(signal, { force: true }), rm(release, { force: true })]);
|
|
1863
|
+
const libPath = join(dir, "library.c");
|
|
1864
|
+
const libOne = "const char *scriptc_cache_race_value = \"lib-race-one-unique\";\n";
|
|
1865
|
+
const libTwo = "const char *scriptc_cache_race_value = \"lib-race-two-unique\";\n";
|
|
1866
|
+
process.env["SCRIPTC_TEST_RACE_SOURCE"] = libPath;
|
|
1867
|
+
await writeFile(libPath, libOne);
|
|
1868
|
+
const firstArchivePath = join(dir, "first.lib.a");
|
|
1869
|
+
const firstArchiveBuild = compileLibArchive({
|
|
1870
|
+
cPath: libPath,
|
|
1871
|
+
outPath: firstArchivePath,
|
|
1872
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
1873
|
+
});
|
|
1874
|
+
await Promise.race([
|
|
1875
|
+
waitForSignal(),
|
|
1876
|
+
firstArchiveBuild.then(() => {
|
|
1877
|
+
throw new Error("archive compile completed without reaching the race barrier");
|
|
1878
|
+
}),
|
|
1879
|
+
]);
|
|
1880
|
+
await writeFile(libPath, libTwo);
|
|
1881
|
+
await writeFile(release, "go");
|
|
1882
|
+
await firstArchiveBuild;
|
|
1883
|
+
const firstArchive = await readFile(firstArchivePath);
|
|
1884
|
+
expect(firstArchive.includes(Buffer.from("lib-race-one-unique"))).toBe(true);
|
|
1885
|
+
expect(firstArchive.includes(Buffer.from("lib-race-two-unique"))).toBe(false);
|
|
1886
|
+
await writeFile(libPath, libOne);
|
|
1887
|
+
const hitArchivePath = join(dir, "hit.lib.a");
|
|
1888
|
+
await compileLibArchive({
|
|
1889
|
+
cPath: libPath,
|
|
1890
|
+
outPath: hitArchivePath,
|
|
1891
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
1892
|
+
});
|
|
1893
|
+
expect(await readFile(hitArchivePath)).toEqual(firstArchive);
|
|
1894
|
+
}
|
|
1895
|
+
finally {
|
|
1896
|
+
if (oldCacheDir === undefined)
|
|
1897
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
1898
|
+
else
|
|
1899
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
1900
|
+
if (oldNoCache === undefined)
|
|
1901
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1902
|
+
else
|
|
1903
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
1904
|
+
if (oldPath === undefined)
|
|
1905
|
+
delete process.env["PATH"];
|
|
1906
|
+
else
|
|
1907
|
+
process.env["PATH"] = oldPath;
|
|
1908
|
+
if (oldRealClang === undefined)
|
|
1909
|
+
delete process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
1910
|
+
else
|
|
1911
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = oldRealClang;
|
|
1912
|
+
if (oldRaceSource === undefined)
|
|
1913
|
+
delete process.env["SCRIPTC_TEST_RACE_SOURCE"];
|
|
1914
|
+
else
|
|
1915
|
+
process.env["SCRIPTC_TEST_RACE_SOURCE"] = oldRaceSource;
|
|
1916
|
+
if (oldRaceSignal === undefined)
|
|
1917
|
+
delete process.env["SCRIPTC_TEST_RACE_SIGNAL"];
|
|
1918
|
+
else
|
|
1919
|
+
process.env["SCRIPTC_TEST_RACE_SIGNAL"] = oldRaceSignal;
|
|
1920
|
+
if (oldRaceRelease === undefined)
|
|
1921
|
+
delete process.env["SCRIPTC_TEST_RACE_RELEASE"];
|
|
1922
|
+
else
|
|
1923
|
+
process.env["SCRIPTC_TEST_RACE_RELEASE"] = oldRaceRelease;
|
|
1924
|
+
}
|
|
1925
|
+
});
|
|
1926
|
+
test.skipIf(process.platform === "win32")("implicit headers changed during the final compile cannot poison the old key", async () => {
|
|
1927
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-cache-final-header-race-"));
|
|
1928
|
+
scratch.push(dir);
|
|
1929
|
+
const cacheRoot = join(dir, "cache");
|
|
1930
|
+
const binDir = join(dir, "bin");
|
|
1931
|
+
const header = join(dir, "implicit-race.h");
|
|
1932
|
+
const cPath = join(dir, "program.c");
|
|
1933
|
+
const signal = join(dir, "final-compile-started");
|
|
1934
|
+
const release = join(dir, "final-compile-release");
|
|
1935
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
1936
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
1937
|
+
const oldDisableCcache = process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
1938
|
+
const oldPath = process.env["PATH"];
|
|
1939
|
+
const oldRealClang = process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
1940
|
+
const oldImplicitHeader = process.env["SCRIPTC_TEST_IMPLICIT_HEADER"];
|
|
1941
|
+
const oldRaceSignal = process.env["SCRIPTC_TEST_FINAL_RACE_SIGNAL"];
|
|
1942
|
+
const oldRaceRelease = process.env["SCRIPTC_TEST_FINAL_RACE_RELEASE"];
|
|
1943
|
+
const realClang = (oldPath ?? "")
|
|
1944
|
+
.split(delimiter)
|
|
1945
|
+
.map((entry) => join(entry === "" ? process.cwd() : entry, "clang"))
|
|
1946
|
+
.find((candidate) => existsSync(candidate));
|
|
1947
|
+
expect(realClang).toBeDefined();
|
|
1948
|
+
const waitForSignal = async () => {
|
|
1949
|
+
const deadline = Date.now() + 10_000;
|
|
1950
|
+
while (!existsSync(signal)) {
|
|
1951
|
+
if (Date.now() > deadline)
|
|
1952
|
+
throw new Error("timed out waiting for final compilation");
|
|
1953
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 10));
|
|
1954
|
+
}
|
|
1955
|
+
};
|
|
1956
|
+
try {
|
|
1957
|
+
await mkdir(binDir);
|
|
1958
|
+
const wrapper = join(binDir, "clang");
|
|
1959
|
+
await writeFile(wrapper, `#!/bin/sh
|
|
1960
|
+
for arg in "$@"; do
|
|
1961
|
+
case "$arg" in
|
|
1962
|
+
*/scriptc-cache-build-*/program.c|*/scriptc-cache-build-*/program.ll)
|
|
1963
|
+
if [ ! -e "$SCRIPTC_TEST_FINAL_RACE_SIGNAL" ]; then
|
|
1964
|
+
: > "$SCRIPTC_TEST_FINAL_RACE_SIGNAL"
|
|
1965
|
+
while [ ! -e "$SCRIPTC_TEST_FINAL_RACE_RELEASE" ]; do sleep 0.01; done
|
|
1966
|
+
fi
|
|
1967
|
+
break
|
|
1968
|
+
;;
|
|
1969
|
+
esac
|
|
1970
|
+
done
|
|
1971
|
+
exec "$SCRIPTC_TEST_REAL_CLANG" -include "$SCRIPTC_TEST_IMPLICIT_HEADER" "$@"
|
|
1972
|
+
`);
|
|
1973
|
+
await chmod(wrapper, 0o755);
|
|
1974
|
+
await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts(SCRIPTC_IMPLICIT_RACE); return 0; }\n');
|
|
1975
|
+
await writeFile(header, '#define SCRIPTC_IMPLICIT_RACE "one"\n');
|
|
1976
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
1977
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = "1";
|
|
1978
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = realClang;
|
|
1979
|
+
process.env["SCRIPTC_TEST_IMPLICIT_HEADER"] = header;
|
|
1980
|
+
process.env["SCRIPTC_TEST_FINAL_RACE_SIGNAL"] = signal;
|
|
1981
|
+
process.env["SCRIPTC_TEST_FINAL_RACE_RELEASE"] = release;
|
|
1982
|
+
process.env["PATH"] = `${binDir}${delimiter}${oldPath ?? ""}`;
|
|
1983
|
+
trustInstrumentedCompilerWrapper();
|
|
1984
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
1985
|
+
const firstOut = join(dir, "first");
|
|
1986
|
+
const firstBuild = compileC({
|
|
1987
|
+
cPath,
|
|
1988
|
+
outPath: firstOut,
|
|
1989
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
1990
|
+
});
|
|
1991
|
+
await Promise.race([
|
|
1992
|
+
waitForSignal(),
|
|
1993
|
+
firstBuild.then(() => {
|
|
1994
|
+
throw new Error("compile completed without reaching the final-build barrier");
|
|
1995
|
+
}),
|
|
1996
|
+
]);
|
|
1997
|
+
await writeFile(header, '#define SCRIPTC_IMPLICIT_RACE "two"\n');
|
|
1998
|
+
await writeFile(release, "go");
|
|
1999
|
+
await firstBuild;
|
|
2000
|
+
expect(execFileSync(firstOut, { encoding: "utf8" }).trim()).toBe("two");
|
|
2001
|
+
// The first executable was compiled from `two` after its key captured
|
|
2002
|
+
// `one`, so it must not be published. Restoring `one` must rebuild it.
|
|
2003
|
+
await writeFile(header, '#define SCRIPTC_IMPLICIT_RACE "one"\n');
|
|
2004
|
+
const secondOut = join(dir, "second");
|
|
2005
|
+
await compileC({ cPath, outPath: secondOut, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
2006
|
+
expect(execFileSync(secondOut, { encoding: "utf8" }).trim()).toBe("one");
|
|
2007
|
+
expect(await completeArtifacts(cacheRoot, "bin")).toHaveLength(1);
|
|
2008
|
+
}
|
|
2009
|
+
finally {
|
|
2010
|
+
if (oldCacheDir === undefined)
|
|
2011
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
2012
|
+
else
|
|
2013
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
2014
|
+
if (oldNoCache === undefined)
|
|
2015
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2016
|
+
else
|
|
2017
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
2018
|
+
if (oldDisableCcache === undefined)
|
|
2019
|
+
delete process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
2020
|
+
else
|
|
2021
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = oldDisableCcache;
|
|
2022
|
+
if (oldPath === undefined)
|
|
2023
|
+
delete process.env["PATH"];
|
|
2024
|
+
else
|
|
2025
|
+
process.env["PATH"] = oldPath;
|
|
2026
|
+
if (oldRealClang === undefined)
|
|
2027
|
+
delete process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
2028
|
+
else
|
|
2029
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = oldRealClang;
|
|
2030
|
+
if (oldImplicitHeader === undefined)
|
|
2031
|
+
delete process.env["SCRIPTC_TEST_IMPLICIT_HEADER"];
|
|
2032
|
+
else
|
|
2033
|
+
process.env["SCRIPTC_TEST_IMPLICIT_HEADER"] = oldImplicitHeader;
|
|
2034
|
+
if (oldRaceSignal === undefined)
|
|
2035
|
+
delete process.env["SCRIPTC_TEST_FINAL_RACE_SIGNAL"];
|
|
2036
|
+
else
|
|
2037
|
+
process.env["SCRIPTC_TEST_FINAL_RACE_SIGNAL"] = oldRaceSignal;
|
|
2038
|
+
if (oldRaceRelease === undefined)
|
|
2039
|
+
delete process.env["SCRIPTC_TEST_FINAL_RACE_RELEASE"];
|
|
2040
|
+
else
|
|
2041
|
+
process.env["SCRIPTC_TEST_FINAL_RACE_RELEASE"] = oldRaceRelease;
|
|
2042
|
+
}
|
|
2043
|
+
});
|
|
2044
|
+
test.skipIf(process.platform === "win32")("runtime edits during object compilation cannot poison the old fingerprint", async () => {
|
|
2045
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-runtime-cache-race-"));
|
|
2046
|
+
scratch.push(dir);
|
|
2047
|
+
const cacheRoot = join(dir, "cache");
|
|
2048
|
+
const fakeRuntime = join(dir, "runtime", "src");
|
|
2049
|
+
const originalRuntime = runtimeSrcDir();
|
|
2050
|
+
await Promise.all([
|
|
2051
|
+
cp(originalRuntime, fakeRuntime, { recursive: true }),
|
|
2052
|
+
mkdir(join(dir, "runtime", "vendor"), { recursive: true }).then(() => cp(join(originalRuntime, "..", "vendor", "ryu"), join(dir, "runtime", "vendor", "ryu"), {
|
|
2053
|
+
recursive: true,
|
|
2054
|
+
})),
|
|
2055
|
+
]);
|
|
2056
|
+
const binDir = join(dir, "bin");
|
|
2057
|
+
const cPath = join(dir, "program.c");
|
|
2058
|
+
const raceSource = join(fakeRuntime, "scr_number.c");
|
|
2059
|
+
const signal = join(dir, "runtime-compile-started");
|
|
2060
|
+
const release = join(dir, "runtime-compile-release");
|
|
2061
|
+
const marker = "scriptc-runtime-race-marker-unique";
|
|
2062
|
+
const originalRuntimeSource = await readFile(raceSource, "utf8");
|
|
2063
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
2064
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
2065
|
+
const oldRuntimeDir = process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"];
|
|
2066
|
+
const oldDisableCcache = process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
2067
|
+
const oldPath = process.env["PATH"];
|
|
2068
|
+
const oldRealClang = process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
2069
|
+
const oldRaceSource = process.env["SCRIPTC_TEST_RUNTIME_RACE_SOURCE"];
|
|
2070
|
+
const oldRaceSignal = process.env["SCRIPTC_TEST_RUNTIME_RACE_SIGNAL"];
|
|
2071
|
+
const oldRaceRelease = process.env["SCRIPTC_TEST_RUNTIME_RACE_RELEASE"];
|
|
2072
|
+
const realClang = (oldPath ?? "")
|
|
2073
|
+
.split(delimiter)
|
|
2074
|
+
.map((entry) => join(entry === "" ? process.cwd() : entry, "clang"))
|
|
2075
|
+
.find((candidate) => existsSync(candidate));
|
|
2076
|
+
expect(realClang).toBeDefined();
|
|
2077
|
+
try {
|
|
2078
|
+
await mkdir(binDir);
|
|
2079
|
+
const wrapper = join(binDir, "clang");
|
|
2080
|
+
await writeFile(wrapper, `#!/bin/sh
|
|
2081
|
+
for arg in "$@"; do
|
|
2082
|
+
if [ "$arg" = "$SCRIPTC_TEST_RUNTIME_RACE_SOURCE" ]; then
|
|
2083
|
+
: > "$SCRIPTC_TEST_RUNTIME_RACE_SIGNAL"
|
|
2084
|
+
while [ ! -e "$SCRIPTC_TEST_RUNTIME_RACE_RELEASE" ]; do sleep 0.01; done
|
|
2085
|
+
break
|
|
2086
|
+
fi
|
|
2087
|
+
done
|
|
2088
|
+
exec "$SCRIPTC_TEST_REAL_CLANG" "$@"
|
|
2089
|
+
`);
|
|
2090
|
+
await chmod(wrapper, 0o755);
|
|
2091
|
+
await writeFile(cPath, "int main(void) { return 0; }\n");
|
|
2092
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
2093
|
+
process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"] = fakeRuntime;
|
|
2094
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = realClang;
|
|
2095
|
+
process.env["SCRIPTC_TEST_RUNTIME_RACE_SOURCE"] = raceSource;
|
|
2096
|
+
process.env["SCRIPTC_TEST_RUNTIME_RACE_SIGNAL"] = signal;
|
|
2097
|
+
process.env["SCRIPTC_TEST_RUNTIME_RACE_RELEASE"] = release;
|
|
2098
|
+
// Keep ccache out of this controlled race: its own object store is not
|
|
2099
|
+
// the cache under test and can obscure which compiler read happened.
|
|
2100
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = "1";
|
|
2101
|
+
process.env["PATH"] = `${binDir}${delimiter}/usr/bin${delimiter}/bin`;
|
|
2102
|
+
trustInstrumentedCompilerWrapper();
|
|
2103
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2104
|
+
const firstOut = join(dir, "first");
|
|
2105
|
+
const firstBuild = compileC({
|
|
2106
|
+
cPath,
|
|
2107
|
+
outPath: firstOut,
|
|
2108
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
2109
|
+
});
|
|
2110
|
+
const deadline = Date.now() + 10_000;
|
|
2111
|
+
while (!existsSync(signal)) {
|
|
2112
|
+
if (Date.now() > deadline)
|
|
2113
|
+
throw new Error("timed out waiting for runtime compile");
|
|
2114
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 10));
|
|
2115
|
+
}
|
|
2116
|
+
await writeFile(raceSource, `${originalRuntimeSource}\nconst char scriptc_runtime_race_marker[] = "${marker}";\n`);
|
|
2117
|
+
await writeFile(release, "go");
|
|
2118
|
+
await firstBuild;
|
|
2119
|
+
expect((await readFile(firstOut)).includes(Buffer.from(marker))).toBe(true);
|
|
2120
|
+
// Restore the fingerprint, then force a program-cache miss. The old
|
|
2121
|
+
// fingerprint must not have retained the object built from edited bytes.
|
|
2122
|
+
await writeFile(raceSource, originalRuntimeSource);
|
|
2123
|
+
await writeFile(cPath, "int main(void) { return 0; } /* second */\n");
|
|
2124
|
+
const secondOut = join(dir, "second");
|
|
2125
|
+
await compileC({
|
|
2126
|
+
cPath,
|
|
2127
|
+
outPath: secondOut,
|
|
2128
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
2129
|
+
});
|
|
2130
|
+
expect((await readFile(secondOut)).includes(Buffer.from(marker))).toBe(false);
|
|
2131
|
+
expect(await completeArtifacts(cacheRoot, "bin")).toHaveLength(1);
|
|
2132
|
+
}
|
|
2133
|
+
finally {
|
|
2134
|
+
if (oldCacheDir === undefined)
|
|
2135
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
2136
|
+
else
|
|
2137
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
2138
|
+
if (oldNoCache === undefined)
|
|
2139
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2140
|
+
else
|
|
2141
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
2142
|
+
if (oldRuntimeDir === undefined)
|
|
2143
|
+
delete process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"];
|
|
2144
|
+
else
|
|
2145
|
+
process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"] = oldRuntimeDir;
|
|
2146
|
+
if (oldDisableCcache === undefined)
|
|
2147
|
+
delete process.env["SCRIPTC_TEST_DISABLE_CCACHE"];
|
|
2148
|
+
else
|
|
2149
|
+
process.env["SCRIPTC_TEST_DISABLE_CCACHE"] = oldDisableCcache;
|
|
2150
|
+
if (oldPath === undefined)
|
|
2151
|
+
delete process.env["PATH"];
|
|
2152
|
+
else
|
|
2153
|
+
process.env["PATH"] = oldPath;
|
|
2154
|
+
if (oldRealClang === undefined)
|
|
2155
|
+
delete process.env["SCRIPTC_TEST_REAL_CLANG"];
|
|
2156
|
+
else
|
|
2157
|
+
process.env["SCRIPTC_TEST_REAL_CLANG"] = oldRealClang;
|
|
2158
|
+
if (oldRaceSource === undefined)
|
|
2159
|
+
delete process.env["SCRIPTC_TEST_RUNTIME_RACE_SOURCE"];
|
|
2160
|
+
else
|
|
2161
|
+
process.env["SCRIPTC_TEST_RUNTIME_RACE_SOURCE"] = oldRaceSource;
|
|
2162
|
+
if (oldRaceSignal === undefined)
|
|
2163
|
+
delete process.env["SCRIPTC_TEST_RUNTIME_RACE_SIGNAL"];
|
|
2164
|
+
else
|
|
2165
|
+
process.env["SCRIPTC_TEST_RUNTIME_RACE_SIGNAL"] = oldRaceSignal;
|
|
2166
|
+
if (oldRaceRelease === undefined)
|
|
2167
|
+
delete process.env["SCRIPTC_TEST_RUNTIME_RACE_RELEASE"];
|
|
2168
|
+
else
|
|
2169
|
+
process.env["SCRIPTC_TEST_RUNTIME_RACE_RELEASE"] = oldRaceRelease;
|
|
2170
|
+
}
|
|
2171
|
+
});
|
|
2172
|
+
test("explicit native link inputs relink while runtime objects remain cached", async () => {
|
|
2173
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-link-input-cache-"));
|
|
2174
|
+
scratch.push(dir);
|
|
2175
|
+
const cacheRoot = join(dir, "cache");
|
|
2176
|
+
const cPath = join(dir, "program.c");
|
|
2177
|
+
const nativeSource = join(dir, "probe.c");
|
|
2178
|
+
const nativeObject = join(dir, "probe.o");
|
|
2179
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
2180
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
2181
|
+
const rebuildObject = async (value) => {
|
|
2182
|
+
await writeFile(nativeSource, `int scriptc_cache_probe(void) { return ${value}; }\n`);
|
|
2183
|
+
execFileSync("clang", ["-c", nativeSource, "-o", nativeObject]);
|
|
2184
|
+
};
|
|
2185
|
+
try {
|
|
2186
|
+
await writeFile(cPath, '#include <stdio.h>\nextern int scriptc_cache_probe(void);\nint main(void) { printf("%d\\n", scriptc_cache_probe()); return 0; }\n');
|
|
2187
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
2188
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2189
|
+
await rebuildObject(1);
|
|
2190
|
+
const firstOut = join(dir, "first");
|
|
2191
|
+
await compileC({
|
|
2192
|
+
cPath,
|
|
2193
|
+
outPath: firstOut,
|
|
2194
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
2195
|
+
linkInputs: [nativeObject],
|
|
2196
|
+
});
|
|
2197
|
+
expect(execFileSync(firstOut, { encoding: "utf8" }).trim()).toBe("1");
|
|
2198
|
+
await rebuildObject(2);
|
|
2199
|
+
const secondOut = join(dir, "second");
|
|
2200
|
+
await compileC({
|
|
2201
|
+
cPath,
|
|
2202
|
+
outPath: secondOut,
|
|
2203
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
2204
|
+
linkInputs: [nativeObject],
|
|
2205
|
+
});
|
|
2206
|
+
expect(execFileSync(secondOut, { encoding: "utf8" }).trim()).toBe("2");
|
|
2207
|
+
// A path may name a thin archive or linker script whose own bytes do not
|
|
2208
|
+
// cover its dependencies, so every native-input build performs this link.
|
|
2209
|
+
await expect(stat(join(cacheRoot, "bin"))).rejects.toMatchObject({ code: "ENOENT" });
|
|
2210
|
+
const objectSets = await readdir(join(cacheRoot, "obj"), { withFileTypes: true });
|
|
2211
|
+
expect(objectSets.some((entry) => entry.isDirectory() && !entry.name.startsWith("build-"))).toBe(true);
|
|
2212
|
+
}
|
|
2213
|
+
finally {
|
|
2214
|
+
if (oldCacheDir === undefined)
|
|
2215
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
2216
|
+
else
|
|
2217
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
2218
|
+
if (oldNoCache === undefined)
|
|
2219
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2220
|
+
else
|
|
2221
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
2222
|
+
}
|
|
2223
|
+
});
|
|
2224
|
+
test("system libraries relink after an in-place rebuild while runtime objects remain cached", async () => {
|
|
2225
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-system-library-cache-"));
|
|
2226
|
+
scratch.push(dir);
|
|
2227
|
+
const cacheRoot = join(dir, "cache");
|
|
2228
|
+
const libDir = join(dir, "lib");
|
|
2229
|
+
const cPath = join(dir, "program.c");
|
|
2230
|
+
const libSource = join(dir, "probe.c");
|
|
2231
|
+
const libObject = join(dir, "probe.o");
|
|
2232
|
+
const library = join(libDir, "libscriptc_cache_probe.a");
|
|
2233
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
2234
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
2235
|
+
const oldLibraryPath = process.env["LIBRARY_PATH"];
|
|
2236
|
+
const rebuildLibrary = async (value) => {
|
|
2237
|
+
await writeFile(libSource, `int scriptc_cache_probe(void) { return ${value}; }\n`);
|
|
2238
|
+
execFileSync("clang", ["-c", libSource, "-o", libObject]);
|
|
2239
|
+
execFileSync("ar", ["rcs", library, libObject]);
|
|
2240
|
+
};
|
|
2241
|
+
try {
|
|
2242
|
+
await mkdir(libDir);
|
|
2243
|
+
await writeFile(cPath, '#include <stdio.h>\nextern int scriptc_cache_probe(void);\nint main(void) { printf("%d\\n", scriptc_cache_probe()); return 0; }\n');
|
|
2244
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
2245
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2246
|
+
process.env["LIBRARY_PATH"] = libDir;
|
|
2247
|
+
await rebuildLibrary(1);
|
|
2248
|
+
const firstOut = join(dir, "first");
|
|
2249
|
+
await compileC({
|
|
2250
|
+
cPath,
|
|
2251
|
+
outPath: firstOut,
|
|
2252
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
2253
|
+
systemLibraries: ["scriptc_cache_probe"],
|
|
2254
|
+
});
|
|
2255
|
+
expect(execFileSync(firstOut, { encoding: "utf8" }).trim()).toBe("1");
|
|
2256
|
+
await rebuildLibrary(2);
|
|
2257
|
+
const secondOut = join(dir, "second");
|
|
2258
|
+
await compileC({
|
|
2259
|
+
cPath,
|
|
2260
|
+
outPath: secondOut,
|
|
2261
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
2262
|
+
systemLibraries: ["scriptc_cache_probe"],
|
|
2263
|
+
});
|
|
2264
|
+
expect(execFileSync(secondOut, { encoding: "utf8" }).trim()).toBe("2");
|
|
2265
|
+
// The ambient library prevents a stale complete-binary hit, but the safe
|
|
2266
|
+
// runtime-object half of the persistent cache remains active.
|
|
2267
|
+
await expect(stat(join(cacheRoot, "bin"))).rejects.toMatchObject({ code: "ENOENT" });
|
|
2268
|
+
const objectSets = await readdir(join(cacheRoot, "obj"), { withFileTypes: true });
|
|
2269
|
+
expect(objectSets.some((entry) => entry.isDirectory() && !entry.name.startsWith("build-"))).toBe(true);
|
|
2270
|
+
}
|
|
2271
|
+
finally {
|
|
2272
|
+
if (oldCacheDir === undefined)
|
|
2273
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
2274
|
+
else
|
|
2275
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
2276
|
+
if (oldNoCache === undefined)
|
|
2277
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2278
|
+
else
|
|
2279
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
2280
|
+
if (oldLibraryPath === undefined)
|
|
2281
|
+
delete process.env["LIBRARY_PATH"];
|
|
2282
|
+
else
|
|
2283
|
+
process.env["LIBRARY_PATH"] = oldLibraryPath;
|
|
2284
|
+
}
|
|
2285
|
+
});
|
|
2286
|
+
test("damaged complete artifacts are rejected and rebuilt", async () => {
|
|
2287
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-artifact-integrity-"));
|
|
2288
|
+
scratch.push(dir);
|
|
2289
|
+
const cacheRoot = join(dir, "cache");
|
|
2290
|
+
const cPath = join(dir, "program.c");
|
|
2291
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
2292
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
2293
|
+
const corruption = Buffer.from("scriptc-corrupt-complete-artifact\n");
|
|
2294
|
+
try {
|
|
2295
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
2296
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2297
|
+
await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("valid"); return 0; }\n');
|
|
2298
|
+
await compileC({
|
|
2299
|
+
cPath,
|
|
2300
|
+
outPath: join(dir, "program"),
|
|
2301
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
2302
|
+
});
|
|
2303
|
+
const [cachedBinary] = await completeArtifacts(cacheRoot, "bin");
|
|
2304
|
+
expect(cachedBinary).toBeDefined();
|
|
2305
|
+
expect((await stat(`${join(cacheRoot, "bin", cachedBinary)}.sha256`)).isFile()).toBe(true);
|
|
2306
|
+
await writeFile(join(cacheRoot, "bin", cachedBinary), corruption);
|
|
2307
|
+
const repairedBinary = join(dir, "program");
|
|
2308
|
+
await compileC({
|
|
2309
|
+
cPath,
|
|
2310
|
+
outPath: repairedBinary,
|
|
2311
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
2312
|
+
});
|
|
2313
|
+
expect(execFileSync(repairedBinary, { encoding: "utf8" }).trim()).toBe("valid");
|
|
2314
|
+
expect(await readFile(join(cacheRoot, "bin", cachedBinary))).not.toEqual(corruption);
|
|
2315
|
+
await writeFile(cPath, "int scriptc_artifact_integrity_probe = 1;\n");
|
|
2316
|
+
await compileLibArchive({
|
|
2317
|
+
cPath,
|
|
2318
|
+
outPath: join(dir, "first.lib.a"),
|
|
2319
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
2320
|
+
});
|
|
2321
|
+
const [cachedArchive] = await completeArtifacts(cacheRoot, "lib");
|
|
2322
|
+
expect(cachedArchive).toBeDefined();
|
|
2323
|
+
expect((await stat(`${join(cacheRoot, "lib", cachedArchive)}.sha256`)).isFile()).toBe(true);
|
|
2324
|
+
await writeFile(join(cacheRoot, "lib", cachedArchive), corruption);
|
|
2325
|
+
const repairedArchive = join(dir, "repaired.lib.a");
|
|
2326
|
+
await compileLibArchive({
|
|
2327
|
+
cPath,
|
|
2328
|
+
outPath: repairedArchive,
|
|
2329
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
2330
|
+
});
|
|
2331
|
+
expect((await readFile(repairedArchive)).includes(corruption)).toBe(false);
|
|
2332
|
+
expect(execFileSync("ar", ["t", repairedArchive], { encoding: "utf8" })).toContain("program.program.o");
|
|
2333
|
+
}
|
|
2334
|
+
finally {
|
|
2335
|
+
if (oldCacheDir === undefined)
|
|
2336
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
2337
|
+
else
|
|
2338
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
2339
|
+
if (oldNoCache === undefined)
|
|
2340
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2341
|
+
else
|
|
2342
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
2343
|
+
}
|
|
2344
|
+
});
|
|
2345
|
+
test("damaged runtime objects are rebuilt before linking or archiving", async () => {
|
|
2346
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-object-integrity-"));
|
|
2347
|
+
scratch.push(dir);
|
|
2348
|
+
const cacheRoot = join(dir, "cache");
|
|
2349
|
+
const cPath = join(dir, "program.c");
|
|
2350
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
2351
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
2352
|
+
const corruption = Buffer.from("scriptc-corrupt-object-unique-marker");
|
|
2353
|
+
const objectSets = async () => (await readdir(join(cacheRoot, "obj"), { withFileTypes: true }))
|
|
2354
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith("build-"))
|
|
2355
|
+
.map((entry) => entry.name);
|
|
2356
|
+
const corruptOneObject = async (set) => {
|
|
2357
|
+
const objectDir = join(cacheRoot, "obj", set);
|
|
2358
|
+
const object = (await readdir(objectDir)).find((name) => name.endsWith(".o"));
|
|
2359
|
+
expect(object).toBeDefined();
|
|
2360
|
+
await writeFile(join(objectDir, object), corruption);
|
|
2361
|
+
expect((await stat(`${join(objectDir, object)}.sha256`)).isFile()).toBe(true);
|
|
2362
|
+
return join(objectDir, object);
|
|
2363
|
+
};
|
|
2364
|
+
try {
|
|
2365
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
2366
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2367
|
+
await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("one"); return 0; }\n');
|
|
2368
|
+
await compileC({
|
|
2369
|
+
cPath,
|
|
2370
|
+
outPath: join(dir, "one"),
|
|
2371
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
2372
|
+
});
|
|
2373
|
+
const [executableSet] = await objectSets();
|
|
2374
|
+
const damagedExecutableObject = await corruptOneObject(executableSet);
|
|
2375
|
+
await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("two"); return 0; }\n');
|
|
2376
|
+
const repairedOut = join(dir, "two");
|
|
2377
|
+
await compileC({
|
|
2378
|
+
cPath,
|
|
2379
|
+
outPath: repairedOut,
|
|
2380
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
2381
|
+
});
|
|
2382
|
+
expect(execFileSync(repairedOut, { encoding: "utf8" }).trim()).toBe("two");
|
|
2383
|
+
expect(await readFile(damagedExecutableObject)).not.toEqual(corruption);
|
|
2384
|
+
const beforeLibrary = new Set(await objectSets());
|
|
2385
|
+
await writeFile(cPath, "int scriptc_integrity_probe = 1;\n");
|
|
2386
|
+
await compileLibArchive({
|
|
2387
|
+
cPath,
|
|
2388
|
+
outPath: join(dir, "one.lib.a"),
|
|
2389
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
2390
|
+
});
|
|
2391
|
+
const librarySet = (await objectSets()).find((set) => !beforeLibrary.has(set));
|
|
2392
|
+
expect(librarySet).toBeDefined();
|
|
2393
|
+
await corruptOneObject(librarySet);
|
|
2394
|
+
await writeFile(cPath, "int scriptc_integrity_probe = 2;\n");
|
|
2395
|
+
const repairedArchive = join(dir, "two.lib.a");
|
|
2396
|
+
await compileLibArchive({
|
|
2397
|
+
cPath,
|
|
2398
|
+
outPath: repairedArchive,
|
|
2399
|
+
cacheIdentity: TEST_CACHE_IDENTITY,
|
|
2400
|
+
});
|
|
2401
|
+
expect((await readFile(repairedArchive)).includes(corruption)).toBe(false);
|
|
2402
|
+
}
|
|
2403
|
+
finally {
|
|
2404
|
+
if (oldCacheDir === undefined)
|
|
2405
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
2406
|
+
else
|
|
2407
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
2408
|
+
if (oldNoCache === undefined)
|
|
2409
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2410
|
+
else
|
|
2411
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
2412
|
+
}
|
|
2413
|
+
});
|
|
2414
|
+
test("library archives hit by content, invalidate on edits, and reuse runtime objects", async () => {
|
|
2415
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-lib-cache-"));
|
|
2416
|
+
scratch.push(dir);
|
|
2417
|
+
const xdgCacheHome = join(dir, "xdg-cache");
|
|
2418
|
+
const cacheRoot = join(xdgCacheHome, "scriptc", "build");
|
|
2419
|
+
const cPath = join(dir, "program.c");
|
|
2420
|
+
const outPath = join(dir, "program.lib.a");
|
|
2421
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
2422
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
2423
|
+
const oldXdgCacheHome = process.env["XDG_CACHE_HOME"];
|
|
2424
|
+
const oldPath = process.env["PATH"];
|
|
2425
|
+
try {
|
|
2426
|
+
// No SCRIPTC_CACHE_DIR: this exercises the production-default activation,
|
|
2427
|
+
// with XDG_CACHE_HOME redirecting the per-user root into disposable space.
|
|
2428
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
2429
|
+
process.env["XDG_CACHE_HOME"] = xdgCacheHome;
|
|
2430
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2431
|
+
await writeFile(cPath, "int scriptc_cache_probe = 1;\n");
|
|
2432
|
+
await compileLibArchive({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
2433
|
+
const firstArchive = await readFile(outPath);
|
|
2434
|
+
expect(await completeArtifacts(cacheRoot, "lib")).toHaveLength(1);
|
|
2435
|
+
const objectSets = await readdir(join(cacheRoot, "obj"), { withFileTypes: true });
|
|
2436
|
+
const objectSet = objectSets.find((entry) => entry.isDirectory() && !entry.name.startsWith("build-"));
|
|
2437
|
+
expect(objectSet).toBeDefined();
|
|
2438
|
+
const objectDir = join(cacheRoot, "obj", objectSet.name);
|
|
2439
|
+
const objectNames = (await readdir(objectDir)).filter((name) => name.endsWith(".o"));
|
|
2440
|
+
expect(objectNames.length).toBeGreaterThan(10);
|
|
2441
|
+
const pinnedTime = new Date("2000-01-01T00:00:00.000Z");
|
|
2442
|
+
await Promise.all(objectNames.map((name) => utimes(join(objectDir, name), pinnedTime, pinnedTime)));
|
|
2443
|
+
// A content hit still performs fresh compiler/toolchain discovery, but it
|
|
2444
|
+
// must not stage or promote the cached runtime objects.
|
|
2445
|
+
await rm(outPath, { force: true });
|
|
2446
|
+
await compileLibArchive({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
2447
|
+
expect(await readFile(outPath)).toEqual(firstArchive);
|
|
2448
|
+
for (const name of objectNames) {
|
|
2449
|
+
expect((await stat(join(objectDir, name))).mtimeMs).toBe(pinnedTime.getTime());
|
|
2450
|
+
}
|
|
2451
|
+
// The hard disable bypasses the same valid entry in both directions.
|
|
2452
|
+
process.env["SCRIPTC_NO_CACHE"] = "1";
|
|
2453
|
+
process.env["PATH"] = "";
|
|
2454
|
+
await rm(outPath, { force: true });
|
|
2455
|
+
await expect(compileLibArchive({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY })).rejects.toThrow(/failed compiling/);
|
|
2456
|
+
expect(await completeArtifacts(cacheRoot, "lib")).toHaveLength(1);
|
|
2457
|
+
// An edit invalidates the complete archive but retains the library-flavor
|
|
2458
|
+
// runtime object set. Only the changed program TU is compiled again.
|
|
2459
|
+
process.env["PATH"] = oldPath;
|
|
2460
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2461
|
+
await writeFile(cPath, "int scriptc_cache_probe = 2;\n");
|
|
2462
|
+
await compileLibArchive({ cPath, outPath, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
2463
|
+
const editedArchive = await readFile(outPath);
|
|
2464
|
+
expect(editedArchive).not.toEqual(firstArchive);
|
|
2465
|
+
expect(await completeArtifacts(cacheRoot, "lib")).toHaveLength(2);
|
|
2466
|
+
for (const name of objectNames) {
|
|
2467
|
+
expect((await stat(join(objectDir, name))).mtimeMs).toBeGreaterThan(pinnedTime.getTime());
|
|
2468
|
+
}
|
|
2469
|
+
// BSD ar preserves member mtimes, so containers assembled from old cached
|
|
2470
|
+
// objects need not be byte-identical to a fresh archive. Their object
|
|
2471
|
+
// members must be byte-identical, and a successful disabled build must
|
|
2472
|
+
// publish nothing into the cache.
|
|
2473
|
+
process.env["SCRIPTC_NO_CACHE"] = "1";
|
|
2474
|
+
const uncachedOut = join(dir, "program.uncached.lib.a");
|
|
2475
|
+
await compileLibArchive({ cPath, outPath: uncachedOut, cacheIdentity: TEST_CACHE_IDENTITY });
|
|
2476
|
+
const cachedMembers = join(dir, "cached-members");
|
|
2477
|
+
const uncachedMembers = join(dir, "uncached-members");
|
|
2478
|
+
await Promise.all([mkdir(cachedMembers), mkdir(uncachedMembers)]);
|
|
2479
|
+
execFileSync("ar", ["x", outPath], { cwd: cachedMembers });
|
|
2480
|
+
execFileSync("ar", ["x", uncachedOut], { cwd: uncachedMembers });
|
|
2481
|
+
const memberNames = await readdir(cachedMembers);
|
|
2482
|
+
expect(await readdir(uncachedMembers)).toEqual(memberNames);
|
|
2483
|
+
for (const name of memberNames) {
|
|
2484
|
+
expect(await readFile(join(uncachedMembers, name))).toEqual(await readFile(join(cachedMembers, name)));
|
|
2485
|
+
}
|
|
2486
|
+
expect(await completeArtifacts(cacheRoot, "lib")).toHaveLength(2);
|
|
2487
|
+
}
|
|
2488
|
+
finally {
|
|
2489
|
+
if (oldCacheDir === undefined)
|
|
2490
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
2491
|
+
else
|
|
2492
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
2493
|
+
if (oldNoCache === undefined)
|
|
2494
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2495
|
+
else
|
|
2496
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
2497
|
+
if (oldXdgCacheHome === undefined)
|
|
2498
|
+
delete process.env["XDG_CACHE_HOME"];
|
|
2499
|
+
else
|
|
2500
|
+
process.env["XDG_CACHE_HOME"] = oldXdgCacheHome;
|
|
2501
|
+
if (oldPath === undefined)
|
|
2502
|
+
delete process.env["PATH"];
|
|
2503
|
+
else
|
|
2504
|
+
process.env["PATH"] = oldPath;
|
|
2505
|
+
}
|
|
2506
|
+
});
|
|
2507
|
+
//# sourceMappingURL=cc-cache.test.js.map
|