diff options
-rw-r--r-- | test/bundler/bundler_compile.test.ts | 84 | ||||
-rw-r--r-- | test/bundler/bundler_npm.test.ts | 46 | ||||
-rw-r--r-- | test/bundler/esbuild/loader.test.ts | 40 | ||||
-rw-r--r-- | test/bundler/expectBundled.ts | 242 |
4 files changed, 300 insertions, 112 deletions
diff --git a/test/bundler/bundler_compile.test.ts b/test/bundler/bundler_compile.test.ts new file mode 100644 index 000000000..16982ce5a --- /dev/null +++ b/test/bundler/bundler_compile.test.ts @@ -0,0 +1,84 @@ +import assert from "assert"; +import dedent from "dedent"; +import { ESBUILD, itBundled, testForFile } from "./expectBundled"; +var { describe, test, expect } = testForFile(import.meta.path); + +describe("bundler", () => { + itBundled("compile/HelloWorld", { + compile: true, + files: { + "/entry.ts": /* js */ ` + console.log("Hello, world!"); + `, + }, + run: { stdout: "Hello, world!" }, + }); + itBundled("compile/VariousBunAPIs", { + compile: true, + files: { + "/entry.ts": ` + // testing random features of bun + import { Database } from "bun:sqlite"; + import { serve } from 'bun'; + import { getRandomSeed } from 'bun:jsc'; + const db = new Database("test.db"); + const query = db.query(\`select "Hello world" as message\`); + if (query.get().message !== "Hello world") throw "fail from sqlite"; + const icon = await fetch("https://bun.sh/favicon.ico").then(x=>x.arrayBuffer()) + if(icon.byteLength < 100) throw "fail from icon"; + if (typeof getRandomSeed() !== 'number') throw "fail from bun:jsc"; + const server = serve({ + fetch() { + return new Response("Hello world"); + }, + port: 0, + }); + const res = await fetch(\`http://\${server.hostname}:\${server.port}\`); + if (res.status !== 200) throw "fail from server"; + if (await res.text() !== "Hello world") throw "fail from server"; + server.stop(); + `, + }, + run: true, + }); + itBundled("compile/ReactSSR", { + install: ["react@next", "react-dom@next"], + files: { + "/entry.tsx": /* tsx */ ` + import React from "react"; + import { renderToReadableStream } from "react-dom/server"; + + const headers = { + headers: { + "Content-Type": "text/html", + }, + }; + + const App = () => ( + <html> + <body> + <h1>Hello World</h1> + <p>This is an example.</p> + </body> + </html> + ); + + const port = 42001; + const server = Bun.serve({ + port, + async fetch(req) { + return new Response(await renderToReadableStream(<App />), headers); + }, + }); + const res = await fetch("http://localhost:" + port); + if (res.status !== 200) throw "status error"; + console.log(await res.text()); + server.stop(); + `, + }, + run: { + stdout: "<!DOCTYPE html><html><head></head><body><h1>Hello World</h1><p>This is an example.</p></body></html>", + }, + compile: true, + }); +}); diff --git a/test/bundler/bundler_npm.test.ts b/test/bundler/bundler_npm.test.ts new file mode 100644 index 000000000..c421912ed --- /dev/null +++ b/test/bundler/bundler_npm.test.ts @@ -0,0 +1,46 @@ +import assert from "assert"; +import dedent from "dedent"; +import { ESBUILD, itBundled, testForFile } from "./expectBundled"; +var { describe, test, expect } = testForFile(import.meta.path); + +describe("bundler", () => { + itBundled("npm/ReactSSR", { + install: ["react@next", "react-dom@next"], + files: { + "/entry.tsx": /* tsx */ ` + import React from "react"; + import { renderToReadableStream } from "react-dom/server"; + + const headers = { + headers: { + "Content-Type": "text/html", + }, + }; + + const App = () => ( + <html> + <body> + <h1>Hello World</h1> + <p>This is an example.</p> + </body> + </html> + ); + + const port = 42001; + const server = Bun.serve({ + port, + async fetch(req) { + return new Response(await renderToReadableStream(<App />), headers); + }, + }); + const res = await fetch("http://localhost:" + port); + if (res.status !== 200) throw "status error"; + console.log(await res.text()); + server.stop(); + `, + }, + run: { + stdout: "<!DOCTYPE html><html><head></head><body><h1>Hello World</h1><p>This is an example.</p></body></html>", + }, + }); +}); diff --git a/test/bundler/esbuild/loader.test.ts b/test/bundler/esbuild/loader.test.ts index 97e26eb61..ce3e8caac 100644 --- a/test/bundler/esbuild/loader.test.ts +++ b/test/bundler/esbuild/loader.test.ts @@ -1,4 +1,5 @@ -import { RUN_UNCHECKED_TESTS, itBundled, testForFile } from "../expectBundled"; +import fs from "fs"; +import { itBundled, testForFile } from "../expectBundled"; var { describe, test, expect } = testForFile(import.meta.path); // Tests ported from: @@ -63,13 +64,20 @@ describe("bundler", () => { }); itBundled("loader/File", { files: { - "/entry.js": `console.log(require('./test.svg'))`, + "/entry.js": ` + import path from 'path'; + const file = require('./test.svg'); + console.log(file); + const contents = await Bun.file(path.join(import.meta.dir, file)).text(); + if(contents !== '<svg></svg>') throw new Error('Contents did not match'); + `, "/test.svg": `<svg></svg>`, }, outdir: "/out", loader: { - // ".svg": "file", + ".svg": "file", }, + target: "bun", run: { stdout: /\.\/test-.*\.svg/, }, @@ -77,8 +85,15 @@ describe("bundler", () => { itBundled("loader/FileMultipleNoCollision", { files: { "/entry.js": /* js */ ` - console.log(require('./a/test.svg')) - console.log(require('./b/test.svg')) + import path from 'path'; + const file1 = require('./a/test.svg'); + console.log(file1); + const contents = await Bun.file(path.join(import.meta.dir, file1)).text(); + if(contents !== '<svg></svg>') throw new Error('Contents did not match'); + const file2 = require('./b/test.svg'); + console.log(file2); + const contents2 = await Bun.file(path.join(import.meta.dir, file2)).text(); + if(contents2 !== '<svg></svg>') throw new Error('Contents did not match'); `, "/a/test.svg": `<svg></svg>`, "/b/test.svg": `<svg></svg>`, @@ -86,6 +101,7 @@ describe("bundler", () => { loader: { ".svg": "file", }, + target: "bun", outdir: "/out", run: { stdout: /\.\/test-.*\.svg\n\.\/test-.*\.svg/, @@ -94,8 +110,15 @@ describe("bundler", () => { itBundled("loader/FileMultipleNoCollisionAssetNames", { files: { "/entry.js": /* js */ ` - console.log(require('./a/test.svg')) - console.log(require('./b/test.svg')) + import path from 'path'; + const file1 = require('./a/test.svg'); + console.log(file1); + const contents = await Bun.file(path.join(import.meta.dir, file1)).text(); + if(contents !== '<svg></svg>') throw new Error('Contents did not match'); + const file2 = require('./b/test.svg'); + console.log(file2); + const contents2 = await Bun.file(path.join(import.meta.dir, file2)).text(); + if(contents2 !== '<svg></svg>') throw new Error('Contents did not match'); `, "/a/test.svg": `<svg></svg>`, "/b/test.svg": `<svg></svg>`, @@ -105,8 +128,9 @@ describe("bundler", () => { loader: { ".svg": "file", }, + target: "bun", run: { - stdout: /\.\/test-.*\.svg \.\/test-.*\.svg/, + stdout: /\.\/assets\/test-.*\.svg\n\.\/assets\/test-.*\.svg/, }, }); itBundled("loader/JSXSyntaxInJSWithJSXLoader", { diff --git a/test/bundler/expectBundled.ts b/test/bundler/expectBundled.ts index 97e863655..b47dc7300 100644 --- a/test/bundler/expectBundled.ts +++ b/test/bundler/expectBundled.ts @@ -106,6 +106,8 @@ export interface BundlerTestInput { stdin?: { contents: string; cwd: string }; /** Use when doing something weird with entryPoints and you need to check other output paths. */ outputPaths?: string[]; + /** Use --compile */ + compile?: boolean; /** force using cli or js api. defaults to api if possible, then cli otherwise */ backend?: "cli" | "api"; @@ -161,6 +163,7 @@ export interface BundlerTestInput { useDefineForClassFields?: boolean; sourceMap?: "inline" | "external" | "none"; plugins?: BunPlugin[] | ((builder: PluginBuilder) => void | Promise<void>); + install?: string[]; // pass subprocess.env env?: Record<string, any>; @@ -319,6 +322,7 @@ function expectBundled( capture, chunkNaming, cjs2esm, + compile, dce, dceKeepMarkerCount, define, @@ -331,6 +335,7 @@ function expectBundled( format, globalName, inject, + install, jsx = {}, keepNames, legalComments, @@ -449,7 +454,7 @@ function expectBundled( external = external.map(x => (typeof x !== "string" ? x : x.replace(/\{\{root\}\}/g, root))); } - outfile = useOutFile ? path.join(root, outfile ?? "/out.js") : undefined; + outfile = useOutFile ? path.join(root, outfile ?? (compile ? "/out" : "/out.js")) : undefined; outdir = !useOutFile ? path.join(root, outdir ?? "/out") : undefined; metafile = metafile ? path.join(root, metafile) : undefined; outputPaths = ( @@ -480,6 +485,16 @@ function expectBundled( if (existsSync(root)) { rmSync(root, { recursive: true }); } + mkdirSync(root, { recursive: true }); + if (install) { + const installProcess = Bun.spawnSync({ + cmd: [bunExe(), "install", ...install], + cwd: root, + }); + if (!installProcess.success) { + throw new Error("Failed to install dependencies"); + } + } for (const [file, contents] of Object.entries(files)) { const filename = path.join(root, file); mkdirSync(path.dirname(filename), { recursive: true }); @@ -525,6 +540,7 @@ function expectBundled( ...entryPaths, ...(entryPointsRaw ?? []), bundling === false ? "--no-bundle" : [], + compile ? "--compile" : [], outfile ? `--outfile=${outfile}` : `--outdir=${outdir}`, define && Object.entries(define).map(([k, v]) => ["--define", `${k}=${v}`]), `--target=${target}`, @@ -618,6 +634,22 @@ function expectBundled( { "version": "0.2.0", "configurations": [ + ...(compile + ? [ + { + "type": "lldb", + "request": "launch", + "name": "run compiled exe", + "program": outfile, + "args": [], + "cwd": root, + "env": { + "FORCE_COLOR": "1", + }, + "console": "internalConsole", + }, + ] + : []), { "type": "lldb", "request": "launch", @@ -1000,125 +1032,127 @@ 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 (outfile) { - if (!existsSync(outfile)) { - throw new Error("Bundle was not written to disk: " + outfile); - } else { - const content = readFileSync(outfile).toString(); - if (dce) { - const dceFails = [...content.matchAll(/FAIL|FAILED|DROP|REMOVE/gi)]; - if (dceFails.length) { - throw new Error("DCE test did not remove all expected code in " + outfile + "."); - } - if (dceKeepMarkerCount !== false) { - const keepMarkersThisFile = [...content.matchAll(/KEEP/gi)].length; - keepMarkersFound += keepMarkersThisFile; - if ( - (typeof dceKeepMarkerCount === "number" - ? dceKeepMarkerCount - : Object.values(keepMarkers).reduce((a, b) => a + b, 0)) !== keepMarkersThisFile - ) { - throw new Error( - "DCE keep markers were not preserved in " + - outfile + - ". Expected " + - keepMarkers[outfile] + - " but found " + - keepMarkersThisFile + - ".", - ); + // TODO: clean up this entire bit into one main loop\ + if (!compile) { + if (outfile) { + if (!existsSync(outfile)) { + throw new Error("Bundle was not written to disk: " + outfile); + } else { + if (dce) { + const content = readFileSync(outfile).toString(); + const dceFails = [...content.matchAll(/FAIL|FAILED|DROP|REMOVE/gi)]; + if (dceFails.length) { + throw new Error("DCE test did not remove all expected code in " + outfile + "."); + } + if (dceKeepMarkerCount !== false) { + const keepMarkersThisFile = [...content.matchAll(/KEEP/gi)].length; + keepMarkersFound += keepMarkersThisFile; + if ( + (typeof dceKeepMarkerCount === "number" + ? dceKeepMarkerCount + : Object.values(keepMarkers).reduce((a, b) => a + b, 0)) !== keepMarkersThisFile + ) { + throw new Error( + "DCE keep markers were not preserved in " + + outfile + + ". Expected " + + keepMarkers[outfile] + + " but found " + + keepMarkersThisFile + + ".", + ); + } } } + if (!ESBUILD) { + // expect(readFileSync(outfile).toString()).toMatchSnapshot(outfile.slice(root.length)); + } } - if (!ESBUILD) { - // expect(readFileSync(outfile).toString()).toMatchSnapshot(outfile.slice(root.length)); - } - } - } else { - // entryNames makes it so we cannot predict the output file - if (!entryNaming || entryNaming === "[dir]/[name].[ext]") { - for (const fullpath of outputPaths) { - if (!existsSync(fullpath)) { - throw new Error("Bundle was not written to disk: " + fullpath); - } else { - if (!ESBUILD) { - // expect(readFileSync(fullpath).toString()).toMatchSnapshot(fullpath.slice(root.length)); - } - if (dce) { - const content = readFileSync(fullpath, "utf8"); - const dceFails = [...content.replace(/\/\*.*?\*\//g, "").matchAll(/FAIL|FAILED|DROP|REMOVE/gi)]; - const key = fullpath.slice(root.length); - if (dceFails.length) { - throw new Error("DCE test did not remove all expected code in " + key + "."); + } else { + // entryNames makes it so we cannot predict the output file + if (!entryNaming || entryNaming === "[dir]/[name].[ext]") { + for (const fullpath of outputPaths) { + if (!existsSync(fullpath)) { + throw new Error("Bundle was not written to disk: " + fullpath); + } else { + if (!ESBUILD) { + // expect(readFileSync(fullpath).toString()).toMatchSnapshot(fullpath.slice(root.length)); } - if (dceKeepMarkerCount !== false) { - const keepMarkersThisFile = [...content.matchAll(/KEEP/gi)].length; - keepMarkersFound += keepMarkersThisFile; - if (keepMarkers[key] !== keepMarkersThisFile) { - throw new Error( - "DCE keep markers were not preserved in " + - key + - ". Expected " + - keepMarkers[key] + - " but found " + - keepMarkersThisFile + - ".", - ); + if (dce) { + const content = readFileSync(fullpath, "utf8"); + const dceFails = [...content.replace(/\/\*.*?\*\//g, "").matchAll(/FAIL|FAILED|DROP|REMOVE/gi)]; + const key = fullpath.slice(root.length); + if (dceFails.length) { + throw new Error("DCE test did not remove all expected code in " + key + "."); + } + if (dceKeepMarkerCount !== false) { + const keepMarkersThisFile = [...content.matchAll(/KEEP/gi)].length; + keepMarkersFound += keepMarkersThisFile; + if (keepMarkers[key] !== keepMarkersThisFile) { + throw new Error( + "DCE keep markers were not preserved in " + + key + + ". Expected " + + keepMarkers[key] + + " but found " + + keepMarkersThisFile + + ".", + ); + } } } } } + } else if (!ESBUILD) { + // TODO: snapshot these test cases } - } else if (!ESBUILD) { - // TODO: snapshot these test cases } - } - if ( - dce && - dceKeepMarkerCount !== false && - typeof dceKeepMarkerCount === "number" && - keepMarkersFound !== dceKeepMarkerCount - ) { - throw new Error( - `DCE keep markers were not preserved. Expected ${dceKeepMarkerCount} KEEP markers, but found ${keepMarkersFound}.`, - ); - } + if ( + dce && + dceKeepMarkerCount !== false && + typeof dceKeepMarkerCount === "number" && + keepMarkersFound !== dceKeepMarkerCount + ) { + throw new Error( + `DCE keep markers were not preserved. Expected ${dceKeepMarkerCount} KEEP markers, but found ${keepMarkersFound}.`, + ); + } - if (assertNotPresent) { - for (const [key, value] of Object.entries(assertNotPresent)) { - const filepath = path.join(root, key); - if (existsSync(filepath)) { - const strings = Array.isArray(value) ? value : [value]; - for (const str of strings) { - if (api.readFile(key).includes(str)) throw new Error(`Expected ${key} to not contain "${str}"`); + if (assertNotPresent) { + for (const [key, value] of Object.entries(assertNotPresent)) { + const filepath = path.join(root, key); + if (existsSync(filepath)) { + const strings = Array.isArray(value) ? value : [value]; + for (const str of strings) { + if (api.readFile(key).includes(str)) throw new Error(`Expected ${key} to not contain "${str}"`); + } } } } - } - if (capture) { - const captures = api.captureFile(path.relative(root, outfile ?? outputPaths[0])); - expect(captures).toEqual(capture); - } + if (capture) { + const captures = api.captureFile(path.relative(root, outfile ?? outputPaths[0])); + expect(captures).toEqual(capture); + } - // cjs2esm checks - if (cjs2esm) { - const outfiletext = api.readFile(path.relative(root, outfile ?? outputPaths[0])); - const regex = /\/\/\s+(.+?)\nvar\s+([a-zA-Z0-9_$]+)\s+=\s+__commonJS/g; - const matches = [...outfiletext.matchAll(regex)].map(match => "/" + match[1]); - const expectedMatches = cjs2esm === true ? [] : cjs2esm.unhandled ?? []; - try { - expect(matches.sort()).toEqual(expectedMatches.sort()); - } catch (error) { - if (matches.length === expectedMatches.length) { - console.error(`cjs2esm check failed.`); - } else { - console.error( - `cjs2esm check failed. expected ${expectedMatches.length} __commonJS helpers but found ${matches.length}.`, - ); + // cjs2esm checks + if (cjs2esm) { + const outfiletext = api.readFile(path.relative(root, outfile ?? outputPaths[0])); + const regex = /\/\/\s+(.+?)\nvar\s+([a-zA-Z0-9_$]+)\s+=\s+__commonJS/g; + const matches = [...outfiletext.matchAll(regex)].map(match => "/" + match[1]); + const expectedMatches = cjs2esm === true ? [] : cjs2esm.unhandled ?? []; + try { + expect(matches.sort()).toEqual(expectedMatches.sort()); + } catch (error) { + if (matches.length === expectedMatches.length) { + console.error(`cjs2esm check failed.`); + } else { + console.error( + `cjs2esm check failed. expected ${expectedMatches.length} __commonJS helpers but found ${matches.length}.`, + ); + } + throw error; } - throw error; } } @@ -1175,7 +1209,7 @@ for (const [key, blob] of build.outputs) { const { success, stdout, stderr } = Bun.spawnSync({ cmd: [ - (run.runtime ?? "bun") === "bun" ? bunExe() : "node", + ...(compile ? [] : [(run.runtime ?? "bun") === "bun" ? bunExe() : "node"]), ...(run.bunArgs ?? []), file, ...(run.args ?? []), |