test(bundler): add memory option to avoid disk I/O in tests

Add a `memory: true` option to expectBundled that keeps build outputs
in memory instead of writing to disk. This reduces I/O overhead for
tests that only need to verify bundler output content.

Enable for WPT CSS tests which were timing out on macOS due to slow
disk I/O with many small test cases.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Bot
2026-01-06 14:58:18 +00:00
parent 27ff6aaae0
commit ce6658832c
5 changed files with 76 additions and 7 deletions

View File

@@ -12,6 +12,7 @@ h1 {
`,
},
outfile: "out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`

View File

@@ -12,6 +12,7 @@ h1 {
`,
},
outfile: "out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`

View File

@@ -11,6 +11,7 @@ h1 {
`,
},
outfile: "out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`

View File

@@ -13,6 +13,7 @@ h1 {
`,
},
outfile: "out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -33,6 +34,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -53,6 +55,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -73,6 +76,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -93,6 +97,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -113,6 +118,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -133,6 +139,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -153,6 +160,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -173,6 +181,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -193,6 +202,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -213,6 +223,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -233,6 +244,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -253,6 +265,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -273,6 +286,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -293,6 +307,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -313,6 +328,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -333,6 +349,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -353,6 +370,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -373,6 +391,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -393,6 +412,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -413,6 +433,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -433,6 +454,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -453,6 +475,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -473,6 +496,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -493,6 +517,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -513,6 +538,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
@@ -533,6 +559,7 @@ h1 {
`,
},
outfile: "/out.css",
memory: true,
onAfterBundle(api) {
api.expectFile("/out.css").toEqualIgnoringWhitespace(`

View File

@@ -299,6 +299,9 @@ export interface BundlerTestInput {
/** Run after the bun.build function is called with its output */
onAfterApiBundle?(build: BuildOutput): Promise<void> | void;
/** If true, build outputs are kept in memory instead of written to disk. Defaults to false. */
memory?: boolean;
}
export interface SourceMapTests {
@@ -494,6 +497,7 @@ function expectBundled(
generateOutput = true,
onAfterApiBundle,
throw: _throw = false,
memory = false,
...unknownProps
} = opts;
@@ -584,7 +588,10 @@ function expectBundled(
}
return (async () => {
if (!backend) {
// memory mode requires API backend since we need in-memory outputs
if (memory) {
backend = "api";
} else if (!backend) {
backend =
dotenv ||
typeof production !== "undefined" ||
@@ -701,6 +708,8 @@ function expectBundled(
// Run bun build cli. In the future we can move to using `Bun.Transpiler.`
let warningReference: Record<string, ErrorMeta[]> = {};
// Map to store build outputs in memory when memory=true
const memoryOutputs: Map<string, string> = new Map();
const expectedErrors = bundleErrors
? Object.entries(bundleErrors).flatMap(([file, v]) => v.map(error => ({ file, error })))
: null;
@@ -1111,7 +1120,7 @@ function expectBundled(
},
plugins: pluginArray,
treeShaking,
outdir: generateOutput ? buildOutDir : undefined,
outdir: generateOutput && !memory ? buildOutDir : undefined,
sourcemap: sourceMap,
splitting,
target,
@@ -1284,6 +1293,20 @@ for (const [key, blob] of build.outputs) {
} else if (expectedErrors && expectedErrors.length > 0) {
throw new Error("Errors were expected while bundling:\n" + expectedErrors.map(formatError).join("\n"));
}
// Populate memoryOutputs if memory mode is enabled
if (memory && build.success) {
for (const artifact of build.outputs) {
let normalizedPath = artifact.path;
// Normalize path to have leading slash
if (normalizedPath.startsWith("./")) {
normalizedPath = normalizedPath.slice(1);
} else if (!normalizedPath.startsWith("/")) {
normalizedPath = "/" + normalizedPath;
}
memoryOutputs.set(normalizedPath, await artifact.text());
}
}
} else {
await esbuild.build({
bundle: true,
@@ -1295,11 +1318,23 @@ for (const [key, blob] of build.outputs) {
}
const readCache: Record<string, string> = {};
const readFile = (file: string) =>
readCache[file] || (readCache[file] = readFileSync(path.join(root, file)).toUnixString());
const readFile = (file: string) => {
if (readCache[file]) return readCache[file];
// Check memory outputs first
if (memoryOutputs.size > 0) {
const memContent = memoryOutputs.get(file);
if (memContent !== undefined) {
readCache[file] = memContent;
return memContent;
}
}
return (readCache[file] = readFileSync(path.join(root, file)).toUnixString());
};
const writeFile = (file: string, contents: string) => {
readCache[file] = contents;
writeFileSync(path.join(root, file), contents);
if (!memory) {
writeFileSync(path.join(root, file), contents);
}
};
const api = {
root,
@@ -1312,7 +1347,11 @@ for (const [key, blob] of build.outputs) {
prependFile: (file, contents) => writeFile(file, dedent(contents) + "\n" + readFile(file)),
appendFile: (file, contents) => writeFile(file, readFile(file) + "\n" + dedent(contents)),
assertFileExists: file => {
if (!existsSync(path.join(root, file))) {
if (memoryOutputs.size > 0) {
if (!memoryOutputs.has(file)) {
throw new Error("Expected file to be in memory: " + file);
}
} else if (!existsSync(path.join(root, file))) {
throw new Error("Expected file to be written: " + file);
}
},
@@ -1369,7 +1408,7 @@ for (const [key, blob] of build.outputs) {
// Check that the bundle failed with status code 0 by verifying all files exist.
// TODO: clean up this entire bit into one main loop\
if (!compile) {
if (!compile && !memory) {
if (outfile) {
if (!existsSync(outfile)) {
throw new Error("Bundle was not written to disk: " + outfile);