Skip to content

Commit fd6bc39

Browse files
committed
test(vnext): close worker evidence gaps
1 parent 3065ed1 commit fd6bc39

6 files changed

Lines changed: 130 additions & 61 deletions

File tree

docs/adr/0004-isolated-parser-execution.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,10 @@ The latest local Node 24 / Chromium 149 / arm64 macOS sample recorded:
238238

239239
| Output | Raw | gzip |
240240
| --- | ---: | ---: |
241-
| Core-only fixture | 24,462 B | 7,475 B |
242-
| PostgreSQL transitive worker graph | 320,495 B | 67,214 B |
243-
| BigQuery transitive worker graph | 224,648 B | 50,205 B |
244-
| Complete worker fixture | 549,003 B | 117,941 B |
241+
| Core-only fixture | 24,680 B | 7,572 B |
242+
| PostgreSQL transitive worker graph | 321,156 B | 67,396 B |
243+
| BigQuery transitive worker graph | 225,309 B | 50,389 B |
244+
| Complete worker fixture | 549,885 B | 118,160 B |
245245

246246
The core module trace contained no `node-sql-parser` module. No dialect
247247
resource loaded before explicit construction. A single static module worker
@@ -254,10 +254,10 @@ One sequential cold/warm run on the shared worker measured:
254254

255255
| Dialect | Grammar load and initialization | First parse | First round trip | Warm parse | Warm round trip |
256256
| --- | ---: | ---: | ---: | ---: | ---: |
257-
| PostgreSQL | 8.7 ms | 2.6 ms | 11.5 ms | 0.2 ms | 0.3 ms |
258-
| BigQuery | 4.3 ms | 2.3 ms | 6.6 ms | 0.4 ms | 0.5 ms |
257+
| PostgreSQL | 8.2 ms | 2.3 ms | 10.7 ms | 0.2 ms | 0.2 ms |
258+
| BigQuery | 4.3 ms | 2.2 ms | 6.7 ms | 0.3 ms | 0.3 ms |
259259

260-
The worker ready handshake took 7.0 ms in that run.
260+
The worker ready handshake took 7.4 ms in that run.
261261

262262
These numbers establish packaging feasibility and initial size guards. They
263263
are not percentile claims. Stable latency decisions require repeated,

scripts/worker-placement.mjs

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -428,40 +428,50 @@ function verifyWorkerAssets(workersDirectory) {
428428
}
429429

430430
function verifyCoreExcludesParser(coreDirectory) {
431-
const moduleIds = JSON.parse(
432-
readFileSync(join(coreDirectory, "module-ids.json"), "utf8"),
431+
const trace = requireModuleTrace(
432+
join(coreDirectory, "core-module-trace.json"),
433433
);
434-
if (!Array.isArray(moduleIds)) {
435-
throw new Error("Core module trace was not an array");
434+
const chunks = chunkMap(trace, coreDirectory);
435+
const entries = trace.chunks.filter(
436+
(chunk) => chunk.isEntry === true,
437+
);
438+
if (entries.length !== 1) {
439+
throw new Error("Core module trace did not contain exactly one entry");
436440
}
437-
const parserModules = moduleIds.filter(
438-
(moduleId) =>
439-
typeof moduleId === "string" &&
440-
moduleId.includes("/node-sql-parser/"),
441+
const reachable = reachableChunks(
442+
chunks,
443+
[entries[0].fileName],
444+
true,
441445
);
442-
if (parserModules.length > 0) {
446+
const javascriptFiles = listFiles(coreDirectory)
447+
.filter((path) => extname(path) === ".js")
448+
.map((path) =>
449+
path.slice(coreDirectory.length + 1).split(sep).join("/"),
450+
);
451+
const orphanJavascript = javascriptFiles.filter(
452+
(fileName) => !reachable.has(fileName),
453+
);
454+
if (orphanJavascript.length > 0) {
443455
throw new Error(
444-
`Core-only build included parser modules: ${parserModules.join(", ")}`,
456+
`Core-only build emitted unreachable JavaScript: ${orphanJavascript.join(", ")}`,
445457
);
446458
}
447-
const workerModules = moduleIds.filter(
459+
const moduleIds = trace.chunks.flatMap((chunk) => chunk.moduleIds);
460+
const parserModules = moduleIds.filter(
448461
(moduleId) =>
449462
typeof moduleId === "string" &&
450-
/(?:^|[/\\])[^/\\]*worker[^/\\]*\.[cm]?[jt]s$/i.test(moduleId),
463+
moduleId.includes("/node-sql-parser/"),
451464
);
452-
if (workerModules.length > 0) {
465+
if (parserModules.length > 0) {
453466
throw new Error(
454-
`Core-only build included worker modules: ${workerModules.join(", ")}`,
467+
`Core-only build included parser modules: ${parserModules.join(", ")}`,
455468
);
456469
}
457470
for (const path of listFiles(coreDirectory)) {
458471
const extension = extname(path);
459472
if (extension !== ".js" && extension !== ".json") {
460473
continue;
461474
}
462-
if (/worker/i.test(basename(path))) {
463-
throw new Error(`Core-only build emitted worker asset ${basename(path)}`);
464-
}
465475
const contents = readFileSync(path, "utf8");
466476
const marker = PARSER_MARKERS.find((candidate) =>
467477
contents.includes(candidate),
@@ -471,13 +481,8 @@ function verifyCoreExcludesParser(coreDirectory) {
471481
`Core-only output ${basename(path)} contained parser marker ${marker}`,
472482
);
473483
}
474-
if (extension === ".js" && /new\s+Worker\s*\(/.test(contents)) {
475-
throw new Error(
476-
`Core-only output ${basename(path)} contained a worker constructor`,
477-
);
478-
}
479484
}
480-
return moduleIds.length;
485+
return new Set(moduleIds).size;
481486
}
482487

483488
function verifySsrImport(fixtureDirectory) {
@@ -697,7 +702,12 @@ try {
697702
const fixtureDirectory = join(temporaryDirectory, "fixture");
698703
cpSync(fixtureSource, fixtureDirectory, { recursive: true });
699704
runPackageManager(
700-
["install", "--frozen-lockfile", "--ignore-scripts"],
705+
[
706+
"install",
707+
"--frozen-lockfile",
708+
"--ignore-scripts",
709+
"--offline",
710+
],
701711
fixtureDirectory,
702712
);
703713

test/worker-placement/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ fixture dependencies are installed before the exact tarball is extracted, and
77
the harness verifies that its exact `5.4.0` version matches the packed
88
package's dependency.
99

10-
The minified Vite 8 single-worker baseline is 67,214 gzip bytes for the
11-
PostgreSQL transitive graph, 50,205 gzip bytes for the BigQuery transitive
12-
graph, and 117,941 gzip/549,003 raw bytes for the complete worker build output.
10+
The minified Vite 8 single-worker baseline is 67,396 gzip bytes for the
11+
PostgreSQL transitive graph, 50,389 gzip bytes for the BigQuery transitive
12+
graph, and 118,160 gzip/549,885 raw bytes for the complete worker build output.
1313
The PostgreSQL and BigQuery figures each include their transitive shared
1414
chunks; the report also identifies those shared chunks explicitly. The
1515
fail-closed ceilings include small explicit headroom over that measured

test/worker-placement/src/parser-worker.js

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,57 @@ function resourceEntries() {
151151
}));
152152
}
153153

154-
async function syntheticCleanupPoisonEvidence() {
154+
async function syntheticCleanupEvidence() {
155+
const successfulTarget = {};
156+
const originalNodeSqlParser = Object.freeze({
157+
owner: "original-node-sql-parser",
158+
});
159+
const originalGlobal = () => "original-global";
160+
Object.defineProperties(successfulTarget, {
161+
NodeSQLParser: {
162+
configurable: true,
163+
enumerable: true,
164+
value: originalNodeSqlParser,
165+
writable: false,
166+
},
167+
global: {
168+
configurable: true,
169+
enumerable: false,
170+
get: originalGlobal,
171+
},
172+
});
173+
const successfulLoad = createGuardedModuleLoader(successfulTarget);
174+
let successfulEvaluations = 0;
175+
const successful = await successfulLoad(async () => {
176+
successfulEvaluations += 1;
177+
Object.defineProperties(successfulTarget, {
178+
NodeSQLParser: {
179+
configurable: true,
180+
enumerable: false,
181+
value: "temporary-node-sql-parser",
182+
writable: true,
183+
},
184+
global: {
185+
configurable: true,
186+
enumerable: true,
187+
value: "temporary-global",
188+
writable: true,
189+
},
190+
});
191+
return "first-load";
192+
});
193+
const reusable = await successfulLoad(async () => {
194+
successfulEvaluations += 1;
195+
return "second-load";
196+
});
197+
155198
const target = {};
156199
const load = createGuardedModuleLoader(target);
157-
let evaluations = 0;
200+
let poisonedEvaluations = 0;
158201
let cleanupFailed = false;
159202
try {
160203
await load(async () => {
161-
evaluations += 1;
204+
poisonedEvaluations += 1;
162205
Object.defineProperty(target, "NodeSQLParser", {
163206
configurable: false,
164207
value: "synthetic-pollution",
@@ -171,16 +214,21 @@ async function syntheticCleanupPoisonEvidence() {
171214
let poisonedRetryFailed = false;
172215
try {
173216
await load(async () => {
174-
evaluations += 1;
217+
poisonedEvaluations += 1;
175218
return {};
176219
});
177220
} catch {
178221
poisonedRetryFailed = true;
179222
}
180223
return {
181224
cleanupFailed,
182-
evaluations,
225+
poisonedEvaluations,
183226
poisonedRetryFailed,
227+
successfulDescriptorEquality: successful.descriptorEquality,
228+
successfulEvaluations,
229+
successfulModuleValues:
230+
successful.moduleValue === "first-load" &&
231+
reusable.moduleValue === "second-load",
184232
};
185233
}
186234

@@ -240,11 +288,11 @@ export function installParserWorker(moduleLoaders) {
240288
});
241289
return;
242290
}
243-
if (request.kind === "test-cleanup-poison") {
291+
if (request.kind === "test-cleanup") {
244292
globalThis.postMessage({
245-
evidence: await syntheticCleanupPoisonEvidence(),
293+
evidence: await syntheticCleanupEvidence(),
246294
id: request.id,
247-
kind: "cleanup-poison-result",
295+
kind: "cleanup-result",
248296
resources: resourceEntries(),
249297
});
250298
return;

test/worker-placement/src/workers.js

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -217,27 +217,32 @@ async function run() {
217217
);
218218
}
219219

220-
const cleanupPoison = await request(worker, {
220+
const cleanup = await request(worker, {
221221
id: 5,
222-
kind: "test-cleanup-poison",
222+
kind: "test-cleanup",
223223
});
224224
if (
225-
cleanupPoison.response.kind !==
226-
"cleanup-poison-result" ||
227-
cleanupPoison.response.evidence.cleanupFailed !== true ||
228-
cleanupPoison.response.evidence.poisonedRetryFailed !== true ||
229-
cleanupPoison.response.evidence.evaluations !== 1
225+
cleanup.response.kind !== "cleanup-result" ||
226+
cleanup.response.evidence.cleanupFailed !== true ||
227+
cleanup.response.evidence.poisonedRetryFailed !== true ||
228+
cleanup.response.evidence.poisonedEvaluations !== 1 ||
229+
cleanup.response.evidence.successfulEvaluations !== 2 ||
230+
cleanup.response.evidence.successfulModuleValues !== true ||
231+
cleanup.response.evidence.successfulDescriptorEquality
232+
.NodeSQLParser !== true ||
233+
cleanup.response.evidence.successfulDescriptorEquality.global !==
234+
true
230235
) {
231236
throw new Error(
232-
"Synthetic cleanup failure did not poison its loader",
237+
"Synthetic cleanup did not prove restoration and poisoning",
233238
);
234239
}
235240
if (globalThis.NodeSQLParser !== sentinel) {
236241
throw new Error("A parser bundle changed the browser main global");
237242
}
238243
const report = Object.freeze({
239244
bigquery,
240-
cleanupPoison: cleanupPoison.response.evidence,
245+
cleanup: cleanup.response.evidence,
241246
postgresql,
242247
resources: {
243248
mainAfterReady: mainResourcesAfterReady,

test/worker-placement/vite.core.config.mjs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,23 @@ function moduleTrace() {
55
return {
66
name: "worker-placement-core-module-trace",
77
generateBundle(_options, bundle) {
8-
const moduleIds = new Set();
9-
for (const output of Object.values(bundle)) {
10-
if (output.type === "chunk") {
11-
for (const moduleId of Object.keys(output.modules)) {
12-
moduleIds.add(moduleId.replaceAll("\\", "/"));
13-
}
14-
}
15-
}
8+
const chunks = Object.values(bundle)
9+
.filter((output) => output.type === "chunk")
10+
.map((chunk) => ({
11+
dynamicImports: [...chunk.dynamicImports].sort(),
12+
fileName: chunk.fileName,
13+
imports: [...chunk.imports].sort(),
14+
isEntry: chunk.isEntry,
15+
moduleIds: Object.keys(chunk.modules)
16+
.map((moduleId) => moduleId.replaceAll("\\", "/"))
17+
.sort(),
18+
}))
19+
.sort((left, right) =>
20+
left.fileName.localeCompare(right.fileName),
21+
);
1622
this.emitFile({
17-
fileName: "module-ids.json",
18-
source: `${JSON.stringify([...moduleIds].sort(), null, 2)}\n`,
23+
fileName: "core-module-trace.json",
24+
source: `${JSON.stringify({ chunks }, null, 2)}\n`,
1925
type: "asset",
2026
});
2127
},

0 commit comments

Comments
 (0)