initial commit

This commit is contained in:
i2p
2026-08-27 21:09:14 +00:00
commit a5b6d59437
12681 changed files with 3253832 additions and 0 deletions
+198
View File
@@ -0,0 +1,198 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
// https://github.com/oven-sh/bun/issues/30887
//
// Under `--parallel` / `--isolate`, `bun test` reads transpiled preloads from
// the isolation source provider cache. That path dropped the `has_tla` flag
// on the cached module record, so JSC treated TLA preloads as having none
// and let their evaluation promise resolve before the top-level-await
// actually completed. The preload's side effects (like `Bun.env.X = ...`)
// then happened *after* the test file started running.
//
// These tests assert that the preload's top-level await is awaited to
// completion before the test file runs, across all three modes, and that
// the on-disk runtime transpiler cache survives a round-trip with TLA
// intact.
async function runTest(cwd: string, extraArgs: string[], extraEnv?: Record<string, string>) {
await using proc = Bun.spawn({
cmd: [bunExe(), "test", ...extraArgs],
env: { ...bunEnv, ...extraEnv },
cwd,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
return { stdout, stderr, exitCode };
}
test.concurrent.each([
["sequential (default)", []],
["--isolate", ["--isolate"]],
["--parallel", ["--parallel"]],
])("async preload is awaited before tests run (%s)", async (_name, flags) => {
using dir = tempDir("bun-test-30887-", {
"preload.ts": `
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
Bun.env.MY_ENV = "MUST_NOT_BE_UNDEFINED";
`,
"my.test.ts": `
import { test, expect } from "bun:test";
test("MY_ENV must not be undefined", () => {
expect(Bun.env.MY_ENV).toBe("MUST_NOT_BE_UNDEFINED");
});
`,
"bunfig.toml": `
[test]
preload = ["./preload.ts"]
`,
});
const { stderr, exitCode } = await runTest(String(dir), flags);
expect(stderr).toContain("1 pass");
expect(stderr).toContain("0 fail");
expect(exitCode).toBe(0);
});
// Runtime transpiler cache round-trip: a ≥4 KiB preload with top-level-await
// is cached on the first run, then read back from the on-disk cache on the
// second run. The cache-HIT path deserializes `module_info` straight from the
// stored `esm_record` without touching the AST, so if the serializer or the
// cache version doesn't preserve the `has_tla` flag, the second run will
// fail exactly like the original #30887 repro.
test("async preload survives runtime transpiler cache round-trip (--isolate)", async () => {
// Pad the preload past MINIMUM_CACHE_SIZE (4 KiB). Declared variables are
// enough — we just need byte-length, not any particular JS behavior.
const padding = "\n" + Array.from({ length: 400 }, (_, i) => `const pad_${i} = ${i};`).join("\n");
using dir = tempDir("bun-test-30887-cache-", {
"preload.ts": `
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
Bun.env.MY_ENV = "MUST_NOT_BE_UNDEFINED";
${padding}
`,
"my.test.ts": `
import { test, expect } from "bun:test";
test("MY_ENV must not be undefined", () => {
expect(Bun.env.MY_ENV).toBe("MUST_NOT_BE_UNDEFINED");
});
`,
"bunfig.toml": `
[test]
preload = ["./preload.ts"]
`,
});
const cachePath = join(String(dir), ".bun-cache");
// `BUN_DEBUG_ENABLE_RESTORE_FROM_TRANSPILER_CACHE=1` is required on debug
// builds so `RuntimeTranspilerCache::get()` actually returns the loaded
// entry instead of discarding it; without it the cache-HIT branch in
// `RuntimeTranspilerStore` is never taken (precedent:
// `test/cli/run/transpiler-cache.test.ts`).
const extraEnv = {
BUN_RUNTIME_TRANSPILER_CACHE_PATH: cachePath,
BUN_DEBUG_ENABLE_RESTORE_FROM_TRANSPILER_CACHE: "1",
};
// First run populates the cache.
const first = await runTest(String(dir), ["--isolate"], extraEnv);
expect(first.stderr).toContain("1 pass");
expect(first.stderr).toContain("0 fail");
expect(first.exitCode).toBe(0);
// Second run reads from the cache. The cache-HIT branch builds
// `module_info` via `create_from_cached_record`, never touching the AST —
// so `has_tla` here comes entirely from the serialized bytes.
const second = await runTest(String(dir), ["--isolate"], extraEnv);
expect(second.stderr).toContain("1 pass");
expect(second.stderr).toContain("0 fail");
expect(second.exitCode).toBe(0);
});
// On-disk cache entries written before #30888 have `has_tla=false` baked
// into their serialized ESM record; without bumping `EXPECTED_VERSION`, the
// post-fix binary would happily read them back and reinstate the original
// bug. Simulate the stale-entry scenario by populating the cache, rewriting
// the 4-byte LE version header to the prior version (20), and asserting the
// next run overwrites it with the current version rather than silently
// accepting the stale entry.
test("stale transpiler cache entries are rejected (version bump)", async () => {
const padding = "\n" + Array.from({ length: 400 }, (_, i) => `const pad_${i} = ${i};`).join("\n");
using dir = tempDir("bun-test-30887-stale-", {
"preload.ts": `
await Promise.resolve();
Bun.env.MY_ENV = "MUST_NOT_BE_UNDEFINED";
${padding}
`,
"my.test.ts": `
import { test, expect } from "bun:test";
test("MY_ENV must not be undefined", () => {
expect(Bun.env.MY_ENV).toBe("MUST_NOT_BE_UNDEFINED");
});
`,
"bunfig.toml": `
[test]
preload = ["./preload.ts"]
`,
});
const cachePath = join(String(dir), ".bun-cache");
const extraEnv = {
BUN_RUNTIME_TRANSPILER_CACHE_PATH: cachePath,
BUN_DEBUG_ENABLE_RESTORE_FROM_TRANSPILER_CACHE: "1",
};
// First run populates the cache (so the filename and path layout exist).
const first = await runTest(String(dir), ["--isolate"], extraEnv);
expect(first.stderr).toContain("1 pass");
expect(first.exitCode).toBe(0);
// Locate the written `.pile` file(s) (covers `.debug.pile` on debug builds
// and `.pile` on release builds). Read the current cache version from the
// first file, then downgrade every entry's 4-byte LE version header to
// `current - 1`. A binary that reverted the #30888 bump (back to 20)
// would accept a `has_tla=false` entry verbatim; the fix requires the
// decode to fail and the file to be re-transpiled and rewritten at the
// current version. Reading the current version from the file (rather
// than hardcoding 21) means this test doesn't need editing on every
// future `EXPECTED_VERSION` bump — it still fails iff the version is
// ever reverted to ≤ 20.
const entries = readdirSync(cachePath).filter(n => n.endsWith(".pile"));
expect(entries.length).toBeGreaterThan(0);
const firstBytes = readFileSync(join(cachePath, entries[0]));
expect(firstBytes.length).toBeGreaterThan(4);
const currentVersion = firstBytes.readUInt32LE(0);
// Guard against an accidental revert of the #30888 bump (20 → 21).
expect(currentVersion).toBeGreaterThan(20);
for (const name of entries) {
const p = join(cachePath, name);
const bytes = readFileSync(p);
expect(bytes.readUInt32LE(0)).toBe(currentVersion);
bytes.writeUInt32LE(currentVersion - 1, 0);
writeFileSync(p, bytes);
}
// Second run must reject the stale entry, re-transpile, and overwrite.
const second = await runTest(String(dir), ["--isolate"], extraEnv);
expect(second.stderr).toContain("1 pass");
expect(second.stderr).toContain("0 fail");
expect(second.exitCode).toBe(0);
// The cache file must now carry the current version byte, proving the
// decode path rejected the stale header and forced a rewrite.
for (const name of entries) {
const bytes = readFileSync(join(cachePath, name));
expect(bytes.readUInt32LE(0)).toBe(currentVersion);
}
});