diff --git a/packages/bun-internal-test/src/banned.json b/packages/bun-internal-test/src/banned.json index d3df1c1a0772b0..92f96ecfbdd6f2 100644 --- a/packages/bun-internal-test/src/banned.json +++ b/packages/bun-internal-test/src/banned.json @@ -3,5 +3,9 @@ "@import(\"root\").bun.": "Only import 'bun' once", "std.mem.indexOfAny": "Use bun.strings.indexAny or bun.strings.indexAnyComptime", "std.debug.print": "Don't let this be committed", + " == undefined": "This is by definition Undefined Behavior.", + " != undefined": "This is by definition Undefined Behavior.", + "undefined == ": "This is by definition Undefined Behavior.", + "undefined != ": "This is by definition Undefined Behavior.", "": "" } diff --git a/scripts/make-old-js.ps1 b/scripts/make-old-js.ps1 index 42cdf20c85242d..b59d95f8675a0b 100644 --- a/scripts/make-old-js.ps1 +++ b/scripts/make-old-js.ps1 @@ -3,7 +3,13 @@ $npm_client = "npm" # & ${npm_client} i $root = Join-Path (Split-Path -Path $MyInvocation.MyCommand.Definition -Parent) "..\" + +# search for .cmd or .exe $esbuild = Join-Path $root "node_modules\.bin\esbuild.cmd" +if (!(Test-Path $esbuild)) { + $esbuild = Join-Path $root "node_modules\.bin\esbuild.exe" +} + $env:NODE_ENV = "production" diff --git a/src/bun.zig b/src/bun.zig index 62cbcce34cf164..94838b5cac2f47 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -3119,6 +3119,16 @@ pub fn assert(value: bool) callconv(callconv_inline) void { } } +/// This has no effect on the real code but capturing 'a' and 'b' into parameters makes assertion failures much easier inspect in a debugger. +pub inline fn assert_eql(a: anytype, b: anytype) void { + return assert(a == b); +} + +/// This has no effect on the real code but capturing 'a' and 'b' into parameters makes assertion failures much easier inspect in a debugger. +pub inline fn assert_neql(a: anytype, b: anytype) void { + return assert(a != b); +} + pub inline fn unsafeAssert(condition: bool) void { if (!condition) { unreachable; diff --git a/src/install/semver.zig b/src/install/semver.zig index 84562dc2ea67aa..fb90aa78159192 100644 --- a/src/install/semver.zig +++ b/src/install/semver.zig @@ -47,22 +47,6 @@ pub const String = extern struct { }; } - pub inline fn init( - buf: string, - in: string, - ) String { - if (comptime Environment.isDebug) { - const out = realInit(buf, in); - if (!out.isInline()) { - assert(@as(u64, @bitCast(out.slice(buf)[0..8].*)) != undefined); - } - - return out; - } else { - return realInit(buf, in); - } - } - pub const Formatter = struct { str: *const String, buf: string, @@ -150,7 +134,7 @@ pub const String = extern struct { } }; - fn realInit( + pub fn init( buf: string, in: string, ) String { diff --git a/src/js/node/assert.js b/src/js/node/assert.ts similarity index 99% rename from src/js/node/assert.js rename to src/js/node/assert.ts index 33f58a9e50d28c..f2445c83da7bb9 100644 --- a/src/js/node/assert.js +++ b/src/js/node/assert.ts @@ -2,7 +2,7 @@ const util = require("node:util"); var isDeepEqual = Bun.deepEquals; -var __commonJS = (cb, mod) => +var __commonJS = (cb, mod: typeof module | undefined = undefined) => function () { return mod || (0, cb[Object.keys(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; diff --git a/src/js/node/child_process.js b/src/js/node/child_process.ts similarity index 100% rename from src/js/node/child_process.js rename to src/js/node/child_process.ts diff --git a/src/js/node/crypto.js b/src/js/node/crypto.ts similarity index 99% rename from src/js/node/crypto.js rename to src/js/node/crypto.ts index 2009b0a4ae06dc..83e0355a2599a8 100644 --- a/src/js/node/crypto.js +++ b/src/js/node/crypto.ts @@ -78,7 +78,7 @@ function getArrayBufferOrView(buffer, name, encoding) { const crypto = globalThis.crypto; const globalCrypto = crypto; -var __commonJS = (cb, mod) => +var __commonJS = (cb, mod: typeof module | undefined = undefined) => function () { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; diff --git a/src/js/node/diagnostics_channel.js b/src/js/node/diagnostics_channel.ts similarity index 100% rename from src/js/node/diagnostics_channel.js rename to src/js/node/diagnostics_channel.ts diff --git a/src/js/node/dns.js b/src/js/node/dns.ts similarity index 100% rename from src/js/node/dns.js rename to src/js/node/dns.ts diff --git a/src/js/node/events.js b/src/js/node/events.ts similarity index 100% rename from src/js/node/events.js rename to src/js/node/events.ts diff --git a/src/js/node/fs.js b/src/js/node/fs.ts similarity index 100% rename from src/js/node/fs.js rename to src/js/node/fs.ts diff --git a/src/js/node/net.js b/src/js/node/net.ts similarity index 100% rename from src/js/node/net.js rename to src/js/node/net.ts diff --git a/src/js/node/punycode.js b/src/js/node/punycode.ts similarity index 100% rename from src/js/node/punycode.js rename to src/js/node/punycode.ts diff --git a/src/js/node/querystring.js b/src/js/node/querystring.ts similarity index 98% rename from src/js/node/querystring.js rename to src/js/node/querystring.ts index d7104cfcda659c..73a9a0ac15a72a 100644 --- a/src/js/node/querystring.js +++ b/src/js/node/querystring.ts @@ -1,4 +1,6 @@ -var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports); +var __commonJS = + (cb, mod: typeof module | undefined = undefined) => + () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports); var Buffer = require("node:buffer").Buffer; diff --git a/src/js/node/readline.js b/src/js/node/readline.ts similarity index 100% rename from src/js/node/readline.js rename to src/js/node/readline.ts diff --git a/src/js/node/stream.consumers.js b/src/js/node/stream.consumers.ts similarity index 100% rename from src/js/node/stream.consumers.js rename to src/js/node/stream.consumers.ts diff --git a/src/js/node/stream.js b/src/js/node/stream.ts similarity index 99% rename from src/js/node/stream.js rename to src/js/node/stream.ts index 245b77eac2a03b..58967763c6ac35 100644 --- a/src/js/node/stream.js +++ b/src/js/node/stream.ts @@ -16,7 +16,7 @@ const { var __getOwnPropNames = Object.getOwnPropertyNames; -var __commonJS = (cb, mod) => +var __commonJS = (cb, mod: typeof module | undefined = undefined) => function __require2() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; diff --git a/src/js/node/stream.web.js b/src/js/node/stream.web.ts similarity index 100% rename from src/js/node/stream.web.js rename to src/js/node/stream.web.ts diff --git a/src/js/node/timers.promises.js b/src/js/node/timers.promises.ts similarity index 100% rename from src/js/node/timers.promises.js rename to src/js/node/timers.promises.ts diff --git a/src/js/node/timers.js b/src/js/node/timers.ts similarity index 100% rename from src/js/node/timers.js rename to src/js/node/timers.ts diff --git a/src/js/node/tls.js b/src/js/node/tls.ts similarity index 100% rename from src/js/node/tls.js rename to src/js/node/tls.ts diff --git a/src/js/node/url.js b/src/js/node/url.ts similarity index 100% rename from src/js/node/url.js rename to src/js/node/url.ts diff --git a/src/js/node/util.js b/src/js/node/util.ts similarity index 100% rename from src/js/node/util.js rename to src/js/node/util.ts diff --git a/src/js/node/wasi.js b/src/js/node/wasi.ts similarity index 99% rename from src/js/node/wasi.js rename to src/js/node/wasi.ts index 53a74483337576..23c8bfac013758 100644 --- a/src/js/node/wasi.js +++ b/src/js/node/wasi.ts @@ -10,7 +10,7 @@ const nodeFsConstants = $processBindingConstants.fs; var __getOwnPropNames = Object.getOwnPropertyNames; -var __commonJS = (cb, mod) => +var __commonJS = (cb, mod: typeof module | undefined = undefined) => function __require2() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; diff --git a/src/js/node/zlib.js b/src/js/node/zlib.ts similarity index 99% rename from src/js/node/zlib.js rename to src/js/node/zlib.ts index de1260d0ed575f..938ad6b82a7c60 100644 --- a/src/js/node/zlib.js +++ b/src/js/node/zlib.ts @@ -10,7 +10,7 @@ const Util = require("node:util"); const { isAnyArrayBuffer, isArrayBufferView } = require("node:util/types"); var __getOwnPropNames = Object.getOwnPropertyNames; -var __commonJS = (cb, mod) => +var __commonJS = (cb, mod: typeof module | undefined = undefined) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; diff --git a/src/js/private.d.ts b/src/js/private.d.ts index 9b4474c031b463..2b8d7d5bf6b70b 100644 --- a/src/js/private.d.ts +++ b/src/js/private.d.ts @@ -105,7 +105,7 @@ declare module "bun" { var TOML: { parse(contents: string): any; }; - function jest(): typeof import("bun:test"); + function jest(path: string): typeof import("bun:test"); var main: string; var tty: Array<{ hasColors: boolean }>; var FFI: any; diff --git a/src/shell/interpreter.zig b/src/shell/interpreter.zig index 6f185daa848546..a0952133b62846 100644 --- a/src/shell/interpreter.zig +++ b/src/shell/interpreter.zig @@ -58,7 +58,7 @@ const stderr_no = 2; pub fn OOM(e: anyerror) noreturn { if (comptime bun.Environment.allow_assert) { - if (e != error.OutOfMemory) @panic("Ruh roh"); + if (e != error.OutOfMemory) bun.outOfMemory(); } @panic("Out of memory"); } @@ -2123,7 +2123,7 @@ pub const Interpreter = struct { return; } - unreachable; + @panic("Invalid child to Expansion, this indicates a bug in Bun. Please file a report on Github."); } fn onGlobWalkDone(this: *Expansion, task: *ShellGlobTask) void { @@ -2742,7 +2742,7 @@ pub const Interpreter = struct { return; } - unreachable; + @panic("Invalid child to Assigns expression, this indicates a bug in Bun. Please file a report on Github."); } }; @@ -2910,7 +2910,7 @@ pub const Interpreter = struct { parent: ParentPtr, io: IO, ) *Binary { - var binary = interpreter.allocator.create(Binary) catch |err| std.debug.panic("Ruh roh: {any}\n", .{err}); + var binary = interpreter.allocator.create(Binary) catch bun.outOfMemory(); binary.node = node; binary.base = .{ .kind = .binary, .interpreter = interpreter, .shell = shell_state }; binary.parent = parent; @@ -3234,7 +3234,7 @@ pub const Interpreter = struct { if (ptr == @as(usize, @intCast(child.ptr.repr._ptr))) break :brk i; } } - unreachable; + @panic("Invalid pipeline state"); }; log("pipeline child done {x} ({d}) i={d}", .{ @intFromPtr(this), exit_code, idx }); @@ -4347,7 +4347,7 @@ pub const Interpreter = struct { parent: ParentPtr, io: IO, ) *Cmd { - var cmd = interpreter.allocator.create(Cmd) catch |err| std.debug.panic("Ruh roh: {any}\n", .{err}); + var cmd = interpreter.allocator.create(Cmd) catch bun.outOfMemory(); cmd.* = .{ .base = .{ .kind = .cmd, .interpreter = interpreter, .shell = shell_state }, .node = node, @@ -4522,7 +4522,8 @@ pub const Interpreter = struct { this.next(); return; } - unreachable; + + @panic("Expected Cmd child to be Assigns or Expansion. This indicates a bug in Bun. Please file a GitHub issue. "); } fn initSubproc(this: *Cmd) void { @@ -7128,7 +7129,8 @@ pub const Interpreter = struct { while (!(this.state == .err or this.state == .done)) { switch (this.state) { .waiting_io => return, - .idle, .done, .err => unreachable, + .idle => @panic("Unexpected \"idle\" state in Pwd. This indicates a bug in Bun. Please file a GitHub issue."), + .done, .err => unreachable, } } @@ -9693,7 +9695,7 @@ pub const Interpreter = struct { pub fn next(this: *Exit) void { switch (this.state) { - .idle => unreachable, + .idle => @panic("Unexpected \"idle\" state in Exit. This indicates a bug in Bun. Please file a GitHub issue."), .waiting_io => { return; }, diff --git a/test/js/bun/shell/bunshell.test.ts b/test/js/bun/shell/bunshell.test.ts index b69fa71006b808..3fbe3da4b27106 100644 --- a/test/js/bun/shell/bunshell.test.ts +++ b/test/js/bun/shell/bunshell.test.ts @@ -10,7 +10,8 @@ import { mkdir, mkdtemp, realpath, rm, stat } from "fs/promises"; import { bunEnv, bunExe, runWithErrorPromise, tempDirWithFiles } from "harness"; import { tmpdir } from "os"; import { join, sep } from "path"; -import { TestBuilder, sortedShellOutput } from "./util"; +import { createTestBuilder, sortedShellOutput } from "./util"; +const TestBuilder = createTestBuilder(import.meta.path); $.env(bunEnv); $.cwd(process.cwd()); @@ -797,9 +798,10 @@ describe("deno_task", () => { TestBuilder.command`echo 1 | echo 2 && echo 3`.stdout("2\n3\n").runAsTest("pipe in conditional"); - await TestBuilder.command`echo $(sleep 0.1 && echo 2 & echo 1) | BUN_DEBUG_QUIET_LOGS=1 BUN_TEST_VAR=1 ${BUN} -e 'await process.stdin.pipe(process.stdout)'` + TestBuilder.command`echo $(sleep 0.1 && echo 2 & echo 1) | BUN_DEBUG_QUIET_LOGS=1 BUN_TEST_VAR=1 ${BUN} -e 'await process.stdin.pipe(process.stdout)'` .stdout("1 2\n") - .run(); + .todo("& not supported") + .runAsTest("complicated pipeline"); TestBuilder.command`echo 2 | echo 1 | BUN_TEST_VAR=1 ${BUN} -e 'process.stdin.pipe(process.stdout)'` .stdout("1\n") @@ -834,9 +836,12 @@ describe("deno_task", () => { }); describe("redirects", async function igodf() { - await TestBuilder.command`echo 5 6 7 > test.txt`.fileEquals("test.txt", "5 6 7\n").run(); + TestBuilder.command`echo 5 6 7 > test.txt`.fileEquals("test.txt", "5 6 7\n").runAsTest("basic redirect"); - await TestBuilder.command`echo 1 2 3 && echo 1 > test.txt`.stdout("1 2 3\n").fileEquals("test.txt", "1\n").run(); + TestBuilder.command`echo 1 2 3 && echo 1 > test.txt` + .stdout("1 2 3\n") + .fileEquals("test.txt", "1\n") + .runAsTest("basic redirect with &&"); // subdir TestBuilder.command`mkdir subdir && cd subdir && echo 1 2 3 > test.txt` diff --git a/test/js/bun/shell/commands/basename.test.ts b/test/js/bun/shell/commands/basename.test.ts index 844df2ff3e3235..d5dce0f923dd7f 100644 --- a/test/js/bun/shell/commands/basename.test.ts +++ b/test/js/bun/shell/commands/basename.test.ts @@ -1,6 +1,7 @@ import { $ } from "bun"; import { describe, test, expect } from "bun:test"; -import { TestBuilder } from "../test_builder"; +import { createTestBuilder } from "../test_builder"; +const TestBuilder = createTestBuilder(import.meta.path); $.nothrow(); describe("basename", async () => { diff --git a/test/js/bun/shell/commands/dirname.test.ts b/test/js/bun/shell/commands/dirname.test.ts index 2845ef3a885b9f..30b9a6e869cbe1 100644 --- a/test/js/bun/shell/commands/dirname.test.ts +++ b/test/js/bun/shell/commands/dirname.test.ts @@ -1,6 +1,7 @@ import { $ } from "bun"; import { describe, test, expect } from "bun:test"; -import { TestBuilder } from "../test_builder"; +import { createTestBuilder } from "../test_builder"; +const TestBuilder = createTestBuilder(import.meta.path); $.nothrow(); describe("dirname", async () => { diff --git a/test/js/bun/shell/commands/exit.test.ts b/test/js/bun/shell/commands/exit.test.ts index d12694769f3d58..eb0ac971e33b9b 100644 --- a/test/js/bun/shell/commands/exit.test.ts +++ b/test/js/bun/shell/commands/exit.test.ts @@ -1,6 +1,7 @@ import { $ } from "bun"; import { describe, test, expect } from "bun:test"; -import { TestBuilder } from "../test_builder"; +import { createTestBuilder } from "../test_builder"; +const TestBuilder = createTestBuilder(import.meta.path); import { sortedShellOutput } from "../util"; import { join } from "path"; diff --git a/test/js/bun/shell/commands/false.test.ts b/test/js/bun/shell/commands/false.test.ts index 16375f9514c3d0..162c3ad940d65e 100644 --- a/test/js/bun/shell/commands/false.test.ts +++ b/test/js/bun/shell/commands/false.test.ts @@ -1,6 +1,7 @@ import { $ } from "bun"; import { describe, test, expect } from "bun:test"; -import { TestBuilder } from "../test_builder"; +import { createTestBuilder } from "../test_builder"; +const TestBuilder = createTestBuilder(import.meta.path); $.nothrow(); describe("false", async () => { diff --git a/test/js/bun/shell/commands/mv.test.ts b/test/js/bun/shell/commands/mv.test.ts index ceea68aca2cb07..0a4fdec5201b52 100644 --- a/test/js/bun/shell/commands/mv.test.ts +++ b/test/js/bun/shell/commands/mv.test.ts @@ -1,6 +1,7 @@ import { $ } from "bun"; import { describe, test, expect } from "bun:test"; -import { TestBuilder } from "../test_builder"; +import { createTestBuilder } from "../test_builder"; +const TestBuilder = createTestBuilder(import.meta.path); import { sortedShellOutput } from "../util"; import { join } from "path"; diff --git a/test/js/bun/shell/commands/rm.test.ts b/test/js/bun/shell/commands/rm.test.ts index cff064032413c9..51987e5984b259 100644 --- a/test/js/bun/shell/commands/rm.test.ts +++ b/test/js/bun/shell/commands/rm.test.ts @@ -10,7 +10,8 @@ import { $ } from "bun"; import path from "path"; import { mkdirSync, writeFileSync } from "node:fs"; import { ShellOutput } from "bun"; -import { TestBuilder, sortedShellOutput } from "../util"; +import { createTestBuilder, sortedShellOutput } from "../util"; +const TestBuilder = createTestBuilder(import.meta.path); const fileExists = async (path: string): Promise => $`ls -d ${path}`.then(o => o.stdout.toString() === `${path}\n`); diff --git a/test/js/bun/shell/commands/seq.test.ts b/test/js/bun/shell/commands/seq.test.ts index a4af40d056b9ff..b5f7264262d5e0 100644 --- a/test/js/bun/shell/commands/seq.test.ts +++ b/test/js/bun/shell/commands/seq.test.ts @@ -1,6 +1,7 @@ import { $ } from "bun"; import { describe } from "bun:test"; -import { TestBuilder } from "../test_builder"; +import { createTestBuilder } from "../test_builder"; +const TestBuilder = createTestBuilder(import.meta.path); $.nothrow(); describe("seq", async () => { diff --git a/test/js/bun/shell/commands/true.test.ts b/test/js/bun/shell/commands/true.test.ts index b3d23aa1845107..35899fa8baf8de 100644 --- a/test/js/bun/shell/commands/true.test.ts +++ b/test/js/bun/shell/commands/true.test.ts @@ -1,6 +1,7 @@ import { $ } from "bun"; import { describe, test, expect } from "bun:test"; -import { TestBuilder } from "../test_builder"; +import { createTestBuilder } from "../test_builder"; +const TestBuilder = createTestBuilder(import.meta.path); $.nothrow(); describe("true", async () => { diff --git a/test/js/bun/shell/env.positionals.test.ts b/test/js/bun/shell/env.positionals.test.ts index 987fedcf2f5bb5..2a87c303612066 100644 --- a/test/js/bun/shell/env.positionals.test.ts +++ b/test/js/bun/shell/env.positionals.test.ts @@ -1,6 +1,7 @@ import { $, spawn } from "bun"; import { describe, test, expect } from "bun:test"; -import { TestBuilder } from "./test_builder"; +import { createTestBuilder } from "./test_builder"; +const TestBuilder = createTestBuilder(import.meta.path); import { bunEnv, bunExe } from "harness"; import * as path from "node:path"; diff --git a/test/js/bun/shell/exec.test.ts b/test/js/bun/shell/exec.test.ts index a65b2e2096c46d..2bf67e29fad19f 100644 --- a/test/js/bun/shell/exec.test.ts +++ b/test/js/bun/shell/exec.test.ts @@ -1,6 +1,7 @@ import { $ } from "bun"; import { describe, test, expect } from "bun:test"; -import { TestBuilder } from "./test_builder"; +import { createTestBuilder } from "./test_builder"; +const TestBuilder = createTestBuilder(import.meta.path); import { bunEnv } from "harness"; const BUN = process.argv0; diff --git a/test/js/bun/shell/leak.test.ts b/test/js/bun/shell/leak.test.ts index fbfb4992a37202..980510c86585a0 100644 --- a/test/js/bun/shell/leak.test.ts +++ b/test/js/bun/shell/leak.test.ts @@ -4,7 +4,9 @@ import { bunEnv } from "harness"; import { appendFileSync, closeSync, openSync, writeFileSync } from "node:fs"; import { tmpdir, devNull } from "os"; import { join } from "path"; -import { TestBuilder } from "./util"; +import { createTestBuilder } from "./util"; +const TestBuilder = createTestBuilder(import.meta.path); +type TestBuilder = InstanceType; $.env(bunEnv); $.cwd(process.cwd()); @@ -50,7 +52,7 @@ const TESTS: [name: string, builder: () => TestBuilder, runs?: number][] = [ ]; describe("fd leak", () => { - function fdLeakTest(name: string, builder: () => TestBuilder, runs: number = 500, threshold: number = 5) { + function fdLeakTest(name: string, builder: () => TestBuilder, runs: number = 1000, threshold: number = 5) { test(`fdleak_${name}`, async () => { Bun.gc(true); const baseline = openSync(devNull, "r"); @@ -83,13 +85,15 @@ describe("fd leak", () => { writeFileSync(tempfile, testcode); const impl = /* ts */ ` + const TestBuilder = createTestBuilder(import.meta.path); + const threshold = ${threshold} let prev: number | undefined = undefined; let prevprev: number | undefined = undefined; for (let i = 0; i < ${runs}; i++) { Bun.gc(true); await (async function() { - await ${builder.toString().slice("() =>".length)}.quiet().run() + await ${builder.toString().slice("() =>".length)}.quiet().runAsTest('iter:', i) })() Bun.gc(true); Bun.gc(true); @@ -111,7 +115,9 @@ describe("fd leak", () => { env: bunEnv, }); // console.log('STDOUT:', stdout.toString(), '\n\nSTDERR:', stderr.toString()); - console.log("\n\nSTDERR:", stderr.toString()); + if (exitCode != 0) { + console.log("\n\nSTDERR:", stderr.toString()); + } expect(exitCode).toBe(0); }, 100_000); } diff --git a/test/js/bun/shell/lex.test.ts b/test/js/bun/shell/lex.test.ts index 88b5cd883723c4..b3bb1ba5c57223 100644 --- a/test/js/bun/shell/lex.test.ts +++ b/test/js/bun/shell/lex.test.ts @@ -1,7 +1,8 @@ import { $ } from "bun"; -import { TestBuilder, redirect } from "./util"; +import { createTestBuilder, redirect } from "./util"; import { shellInternals } from "bun:internal-for-testing"; const { lex } = shellInternals; +const TestBuilder = createTestBuilder(import.meta.path); const BUN = process.argv0; diff --git a/test/js/bun/shell/parse.test.ts b/test/js/bun/shell/parse.test.ts index dbbb4dea21ce02..0f42686f683543 100644 --- a/test/js/bun/shell/parse.test.ts +++ b/test/js/bun/shell/parse.test.ts @@ -1,6 +1,7 @@ -import { TestBuilder, redirect } from "./util"; +import { createTestBuilder, redirect } from "./util"; import { shellInternals } from "bun:internal-for-testing"; const { parse } = shellInternals; +const TestBuilder = createTestBuilder(import.meta.path); describe("parse shell", () => { test("basic", () => { diff --git a/test/js/bun/shell/test_builder.ts b/test/js/bun/shell/test_builder.ts index a5f4b92db94259..4cdc53756ad1f8 100644 --- a/test/js/bun/shell/test_builder.ts +++ b/test/js/bun/shell/test_builder.ts @@ -1,53 +1,60 @@ -import { describe, test, afterAll, beforeAll, expect } from "bun:test"; -import { ShellError, ShellExpression, ShellOutput } from "bun"; -import { ShellPromise, BunFile } from "bun"; +import { ShellError, ShellOutput } from "bun"; +import { ShellPromise, ShellExpression } from "bun"; // import { tempDirWithFiles } from "harness"; import { join } from "node:path"; import * as os from "node:os"; import * as fs from "node:fs"; // import { bunExe } from "harness"; -export class TestBuilder { - // private promise: { type: "ok"; val: ShellPromise } | { type: "err"; val: Error }; - private _testName: string | undefined = undefined; - - private expected_stdout: string | ((stdout: string, tempdir: string) => void) = ""; - private expected_stderr: string | ((stderr: string, tempdir: string) => void) | { contains: string } = ""; - private expected_exit_code: number = 0; - private expected_error: ShellError | string | boolean | undefined = undefined; - private file_equals: { [filename: string]: string } = {}; - private _doesNotExist: string[] = []; - private _timeout: number | undefined = undefined; - - private tempdir: string | undefined = undefined; - private _env: { [key: string]: string } | undefined = undefined; - private _cwd: string | undefined = undefined; - _miniCwd: string | undefined = undefined; - - private __todo: boolean | string = false; - - _quiet: boolean = false; - - _testMini: boolean = false; - _onlyMini: boolean = false; - __insideExec: boolean = false; - _scriptStr: TemplateStringsArray; - _expresssions: ShellExpression[]; - - _skipExecOnUnknownType: boolean = false; - - static UNEXPECTED_SUBSHELL_ERROR_OPEN = - "Unexpected `(`, subshells are currently not supported right now. Escape the `(` or open a GitHub issue."; +export function createTestBuilder(path: string) { + var { describe, test, afterAll, beforeAll, expect, beforeEach, afterEach } = Bun.jest(path); + + var insideTestScope = false; + beforeEach(() => { + insideTestScope = true; + }); + afterEach(() => { + insideTestScope = false; + }); + + class TestBuilder { + // promise: { type: "ok"; val: ShellPromise } | { type: "err"; val: Error }; + _testName: string | undefined = undefined; + + expected_stdout: string | ((stdout: string, tempdir: string) => void) = ""; + expected_stderr: string | ((stderr: string, tempdir: string) => void) | { contains: string } = ""; + expected_exit_code: number = 0; + expected_error: ShellError | string | boolean | undefined = undefined; + file_equals: { [filename: string]: string } = {}; + _doesNotExist: string[] = []; + _timeout: number | undefined = undefined; + + tempdir: string | undefined = undefined; + _env: { [key: string]: string } | undefined = undefined; + _cwd: string | undefined = undefined; + + _miniCwd: string | undefined = undefined; + _quiet: boolean = false; + + _testMini: boolean = false; + _onlyMini: boolean = false; + __insideExec: boolean = false; + _scriptStr: TemplateStringsArray; + _expresssions: ShellExpression[]; + + _skipExecOnUnknownType: boolean = false; + + __todo: boolean | string = false; + + constructor(_scriptStr: TemplateStringsArray, _expressions: any[]) { + this._scriptStr = _scriptStr; + this._expresssions = _expressions; + } - static UNEXPECTED_SUBSHELL_ERROR_CLOSE = - "Unexpected `)`, subshells are currently not supported right now. Escape the `)` or open a GitHub issue."; + UNEXPECTED_SUBSHELL_ERROR_CLOSE = + "Unexpected `)`, subshells are currently not supported right now. Escape the `)` or open a GitHub issue."; - constructor(_scriptStr: TemplateStringsArray, _expressions: any[]) { - this._scriptStr = _scriptStr; - this._expresssions = _expressions; - } - - /** + /** * Start the test builder with a command: * * @example @@ -57,292 +64,307 @@ export class TestBuilder { * TestBuilder.command`echo hi!`.stdout('hi!\n').runAsTest('echo works') * ``` */ - static command(strings: TemplateStringsArray, ...expressions: any[]): TestBuilder { - return new TestBuilder(strings, expressions); - } - - cwd(path: string): this { - this._cwd = path; - return this; - } + static command(strings: TemplateStringsArray, ...expressions: any[]): TestBuilder { + return new TestBuilder(strings, expressions); + } - directory(path: string): this { - const tempdir = this.getTempDir(); - fs.mkdirSync(join(tempdir, path), { recursive: true }); - return this; - } + cwd(path: string): this { + this._cwd = path; + return this; + } - doesNotExist(path: string): this { - this._doesNotExist.push(path); - return this; - } + directory(path: string): this { + const tempdir = this.getTempDir(); + fs.mkdirSync(join(tempdir, path), { recursive: true }); + return this; + } - /** - * @param opts - * @returns - */ - testMini(opts?: { errorOnSupportedTemplate?: boolean; onlyMini?: boolean; cwd?: string }): this { - this._testMini = true; - this._skipExecOnUnknownType = opts?.errorOnSupportedTemplate ?? false; - this._onlyMini = opts?.onlyMini ?? false; - this._miniCwd = opts?.cwd; - return this; - } + doesNotExist(path: string): this { + this._doesNotExist.push(path); + return this; + } - /** - * Create a file in a temp directory - * @param path Path to the new file, this will be inside the TestBuilder's temp directory - * @param contents Contents of the new file - * @returns - * - * @example - * ```ts - * TestBuilder.command`ls .` - * .file('hi.txt', 'hi!') - * .file('hello.txt', 'hello!') - * .runAsTest('List files') - * ``` - */ - file(path: string, contents: string): this { - const tempdir = this.getTempDir(); - fs.writeFileSync(join(tempdir, path), contents); - return this; - } + /** + * @param opts + * @returns + */ + testMini(opts?: { errorOnSupportedTemplate?: boolean; onlyMini?: boolean; cwd?: string }): this { + this._testMini = true; + this._skipExecOnUnknownType = opts?.errorOnSupportedTemplate ?? false; + this._onlyMini = opts?.onlyMini ?? false; + this._miniCwd = opts?.cwd; + return this; + } - env(env: { [key: string]: string }): this { - this._env = env; - return this; - } + /** + * Create a file in a temp directory + * @param path Path to the new file, this will be inside the TestBuilder's temp directory + * @param contents Contents of the new file + * @returns + * + * @example + * ```ts + * TestBuilder.command`ls .` + * .file('hi.txt', 'hi!') + * .file('hello.txt', 'hello!') + * .runAsTest('List files') + * ``` + */ + file(path: string, contents: string): this { + const tempdir = this.getTempDir(); + fs.writeFileSync(join(tempdir, path), contents); + return this; + } - quiet(): this { - this._quiet = true; - return this; - } + env(env: { [key: string]: string }): this { + this._env = env; + return this; + } - testName(name: string): this { - this._testName = name; - return this; - } + quiet(): this { + this._quiet = true; + return this; + } - /** - * Expect output from stdout - * - * @param expected - can either be a string or a function which itself calls `expect()` - */ - stdout(expected: string | ((stdout: string, tempDir: string) => void)): this { - this.expected_stdout = expected; - return this; - } + testName(name: string): this { + this._testName = name; + return this; + } - stderr(expected: string | ((stderr: string, tempDir: string) => void)): this { - this.expected_stderr = expected; - return this; - } + /** + * Expect output from stdout + * + * @param expected - can either be a string or a function which itself calls `expect()` + */ + stdout(expected: string | ((stdout: string, tempDir: string) => void)): this { + this.expected_stdout = expected; + return this; + } - stderr_contains(expected: string): this { - this.expected_stderr = { contains: expected }; - return this; - } + stderr(expected: string | ((stderr: string, tempDir: string) => void)): this { + this.expected_stderr = expected; + return this; + } - /** - * Makes this test use a temp directory: - * - The shell's cwd will be set to the temp directory - * - All FS functions on the `TestBuilder` will use this temp directory. - * @returns - */ - ensureTempDir(str?: string): this { - if (str !== undefined) { - this.setTempdir(str); - } else this.getTempDir(); + stderr_contains(expected: string): this { + this.expected_stderr = { contains: expected }; + return this; + } - return this; - } + /** + * Makes this test use a temp directory: + * - The shell's cwd will be set to the temp directory + * - All FS functions on the `TestBuilder` will use this temp directory. + * @returns + */ + ensureTempDir(str?: string): this { + if (str !== undefined) { + this.setTempdir(str); + } else this.getTempDir(); + + return this; + } - error(expected?: ShellError | string | boolean): this { - if (expected === undefined || expected === true) { - this.expected_error = true; - } else if (expected === false) { - this.expected_error = false; - } else { - this.expected_error = expected; + error(expected?: ShellError | string | boolean): this { + if (expected === undefined || expected === true) { + this.expected_error = true; + } else if (expected === false) { + this.expected_error = false; + } else { + this.expected_error = expected; + } + return this; } - return this; - } - exitCode(expected: number): this { - this.expected_exit_code = expected; - return this; - } + exitCode(expected: number): this { + this.expected_exit_code = expected; + return this; + } - fileEquals(filename: string, expected: string): this { - this.getTempDir(); - this.file_equals[filename] = expected; - return this; - } + fileEquals(filename: string, expected: string): this { + this.getTempDir(); + this.file_equals[filename] = expected; + return this; + } - static tmpdir(): string { - const tmp = os.tmpdir(); - return fs.mkdtempSync(join(tmp, "test_builder")); - } + static tmpdir(): string { + const tmp = os.tmpdir(); + return fs.mkdtempSync(join(tmp, "test_builder")); + } - setTempdir(tempdir: string): this { - this.tempdir = tempdir; - return this; - } + setTempdir(tempdir: string): this { + this.tempdir = tempdir; + return this; + } - newTempdir(): string { - this.tempdir = undefined; - return this.getTempDir(); - } + newTempdir(): string { + this.tempdir = undefined; + return this.getTempDir(); + } - getTempDir(): string { - if (this.tempdir === undefined) { - this.tempdir = TestBuilder.tmpdir(); - return this.tempdir!; + getTempDir(): string { + if (this.tempdir === undefined) { + this.tempdir = TestBuilder.tmpdir(); + return this.tempdir!; + } + return this.tempdir; } - return this.tempdir; - } - timeout(ms: number): this { - this._timeout = ms; - return this; - } + timeout(ms: number): this { + this._timeout = ms; + return this; + } - async run(): Promise { - try { - let finalPromise = Bun.$(this._scriptStr, ...this._expresssions); - if (this.tempdir) finalPromise = finalPromise.cwd(this.tempdir); - if (this._cwd) finalPromise = finalPromise.cwd(this._cwd); - if (this._env) finalPromise = finalPromise.env(this._env); - if (this._quiet) finalPromise = finalPromise.quiet(); - const output = await finalPromise; - - const { stdout, stderr, exitCode } = output!; - const tempdir = this.tempdir || "NO_TEMP_DIR"; - if (this.expected_stdout !== undefined) { - if (typeof this.expected_stdout === "string") { - expect(stdout.toString()).toEqual(this.expected_stdout.replaceAll("$TEMP_DIR", tempdir)); - } else { - this.expected_stdout(stdout.toString(), tempdir); + async run(): Promise { + try { + let finalPromise = Bun.$(this._scriptStr, ...this._expresssions); + if (this.tempdir) finalPromise = finalPromise.cwd(this.tempdir); + if (this._cwd) finalPromise = finalPromise.cwd(this._cwd); + if (this._env) finalPromise = finalPromise.env(this._env); + if (this._quiet) finalPromise = finalPromise.quiet(); + const output = await finalPromise; + + const { stdout, stderr, exitCode } = output!; + const tempdir = this.tempdir || "NO_TEMP_DIR"; + if (this.expected_stdout !== undefined) { + if (typeof this.expected_stdout === "string") { + expect(stdout.toString()).toEqual(this.expected_stdout.replaceAll("$TEMP_DIR", tempdir)); + } else { + this.expected_stdout(stdout.toString(), tempdir); + } } - } - if (this.expected_stderr !== undefined) { - if (typeof this.expected_stderr === "string") { - expect(stderr.toString()).toEqual(this.expected_stderr.replaceAll("$TEMP_DIR", tempdir)); - } else if (typeof this.expected_stderr === "function") { - this.expected_stderr(stderr.toString(), tempdir); - } else { - expect(stderr.toString()).toContain(this.expected_stderr.contains); + if (this.expected_stderr !== undefined) { + if (typeof this.expected_stderr === "string") { + expect(stderr.toString()).toEqual(this.expected_stderr.replaceAll("$TEMP_DIR", tempdir)); + } else if (typeof this.expected_stderr === "function") { + this.expected_stderr(stderr.toString(), tempdir); + } else { + expect(stderr.toString()).toContain(this.expected_stderr.contains); + } } - } - if (this.expected_exit_code !== undefined) expect(exitCode).toEqual(this.expected_exit_code); + if (this.expected_exit_code !== undefined) expect(exitCode).toEqual(this.expected_exit_code); - for (const [filename, expected] of Object.entries(this.file_equals)) { - const actual = await Bun.file(join(this.tempdir!, filename)).text(); - expect(actual).toEqual(expected); - } + for (const [filename, expected] of Object.entries(this.file_equals)) { + const actual = await Bun.file(join(this.tempdir!, filename)).text(); + expect(actual).toEqual(expected); + } - for (const fsname of this._doesNotExist) { - expect(fs.existsSync(join(this.tempdir!, fsname))).toBeFalsy(); - } - } catch (err) { - if (this.expected_error === undefined) throw err; - if (this.expected_error === true) return undefined; - if (this.expected_error === false) expect(err).toBeUndefined(); - if (typeof this.expected_error === "string") { - expect(err.message).toEqual(this.expected_error); - } else if (this.expected_error instanceof ShellError) { - expect(err).toBeInstanceOf(ShellError); - const e = err as ShellError; - expect(e.exitCode).toEqual(this.expected_error.exitCode); - expect(e.stdout.toString()).toEqual(this.expected_error.stdout.toString()); - expect(e.stderr.toString()).toEqual(this.expected_error.stderr.toString()); + for (const fsname of this._doesNotExist) { + expect(fs.existsSync(join(this.tempdir!, fsname))).toBeFalsy(); + } + } catch (err) { + if (this.expected_error === undefined) throw err; + if (this.expected_error === true) return undefined; + if (this.expected_error === false) expect(err).toBeUndefined(); + if (typeof this.expected_error === "string") { + expect(err.message).toEqual(this.expected_error); + } else if (this.expected_error instanceof ShellError) { + expect(err).toBeInstanceOf(ShellError); + const e = err as ShellError; + expect(e.exitCode).toEqual(this.expected_error.exitCode); + expect(e.stdout.toString()).toEqual(this.expected_error.stdout.toString()); + expect(e.stderr.toString()).toEqual(this.expected_error.stderr.toString()); + } } - return undefined; + + // return output; } - // return output; - } + todo(reason?: string): this { + this.__todo = typeof reason === "string" ? reason : true; + return this; + } - todo(reason?: string): this { - this.__todo = typeof reason === "string" ? reason : true; - return this; - } + runAsTest(name: string) { + // biome-ignore lint/complexity/noUselessThisAlias: + const tb = this; + if (this.__todo) { + test.todo(typeof this.__todo === "string" ? `${name} skipped: ${this.__todo}` : name, async () => { + await tb.run(); + }); + return; + } else { + if (!this._onlyMini) { + test( + name, + async () => { + await tb.run(); + }, + this._timeout, + ); + } - runAsTest(name: string) { - // biome-ignore lint/complexity/noUselessThisAlias: - const tb = this; - if (this.__todo) { - test.todo(typeof this.__todo === "string" ? `${name} skipped: ${this.__todo}` : name, async () => { - await tb.run(); - }); - return; - } else { - if (!this._onlyMini) { - test( - name, - async () => { - await tb.run(); - }, - this._timeout, - ); + if (this._testMini) { + test( + name + " (exec)", + async () => { + let cwd: string = ""; + if (tb._miniCwd === undefined) { + cwd = tb.newTempdir(); + } else { + tb._cwd = tb._miniCwd; + cwd = tb._cwd; + } + const joinedstr = tb.joinTemplate(); + console.log("JOIEND", joinedstr); + await Bun.$`echo ${joinedstr} > script.bun.sh`.cwd(cwd); + ((script: TemplateStringsArray, ...exprs: any[]) => { + tb._scriptStr = script; + tb._expresssions = exprs; + })`${bunExe()} run script.bun.sh`; + await tb.run(); + }, + this._timeout, + ); + } } + } - if (this._testMini) { - test( - name + " (exec)", - async () => { - let cwd: string = ""; - if (tb._miniCwd === undefined) { - cwd = tb.newTempdir(); - } else { - tb._cwd = tb._miniCwd; - cwd = tb._cwd; - } - const joinedstr = tb.joinTemplate(); - console.log("JOIEND", joinedstr); - await Bun.$`echo ${joinedstr} > script.bun.sh`.cwd(cwd); - ((script: TemplateStringsArray, ...exprs: any[]) => { - tb._scriptStr = script; - tb._expresssions = exprs; - })`${bunExe()} run script.bun.sh`; - await tb.run(); - }, - this._timeout, - ); + generateRandomString(length: number): string { + const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let result = ""; + const charactersLength = characters.length; + + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); } + + return result; } - } - joinTemplate(): string { - let buf = []; - for (let i = 0; i < this._scriptStr.length; i++) { - buf.push(this._scriptStr[i]); - if (this._expresssions[i] !== undefined) { - const expr = this._expresssions[i]; - this.processShellExpr(buf, expr); + joinTemplate(): string { + let buf = []; + for (let i = 0; i < this._scriptStr.length; i++) { + buf.push(this._scriptStr[i]); + if (this._expresssions[i] !== undefined) { + const expr = this._expresssions[i]; + this.processShellExpr(buf, expr); + } } - } - return buf.join(""); - } + return buf.join(""); + } - processShellExpr(buf: string[], expr: ShellExpression) { - if (typeof expr === "string") { - buf.push(Bun.$.escape(expr)); - } else if (typeof expr?.raw === "string") { - buf.push(Bun.$.escape(expr.raw)); - } else if (Array.isArray(expr)) { - expr.forEach(e => this.processShellExpr(buf, e)); - } else { - if (this._skipExecOnUnknownType) { - console.warn(`Unexpected expression type: ${expr}\nSkipping.`); - return; + processShellExpr(buf: string[], expr: ShellExpression) { + if (typeof expr === "string") { + buf.push(Bun.$.escape(expr)); + } else if (typeof expr === "number") { + buf.push(expr.toString()); + } else if (typeof expr?.raw === "string") { + buf.push(Bun.$.escape(expr.raw)); + } else if (Array.isArray(expr)) { + expr.forEach(e => this.processShellExpr(buf, e)); + } else { + if (this._skipExecOnUnknownType) { + console.warn(`Unexpected expression type: ${expr}\nSkipping.`); + return; + } + throw new Error(`Unexpected expression type ${expr}`); } - throw new Error(`Unexpected expression type ${expr}`); } } } + function generateRandomString(length: number): string { const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let result = ""; diff --git a/test/js/bun/shell/util.ts b/test/js/bun/shell/util.ts index cafe83ce04fb69..08a632d9b28908 100644 --- a/test/js/bun/shell/util.ts +++ b/test/js/bun/shell/util.ts @@ -4,9 +4,9 @@ import { ShellPromise } from "bun"; import { tempDirWithFiles } from "harness"; import { join } from "node:path"; import * as fs from "node:fs"; -import { TestBuilder } from "./test_builder"; +import { createTestBuilder } from "./test_builder"; -export { TestBuilder }; +export { createTestBuilder }; declare module "bun" { // Define the additional methods