diff --git a/.github/workflows/run-test.yml b/.github/workflows/run-test.yml index 4879d146cfd0c0..6b33c149808653 100644 --- a/.github/workflows/run-test.yml +++ b/.github/workflows/run-test.yml @@ -95,6 +95,7 @@ jobs: TLS_MONGODB_DATABASE_URL: ${{ secrets.TLS_MONGODB_DATABASE_URL }} TLS_POSTGRES_DATABASE_URL: ${{ secrets.TLS_POSTGRES_DATABASE_URL }} TEST_INFO_STRIPE: ${{ secrets.TEST_INFO_STRIPE }} + TEST_INFO_AZURE_SERVICE_BUS: ${{ secrets.TEST_INFO_AZURE_SERVICE_BUS }} SHELLOPTS: igncr run: | node packages/bun-internal-test/src/runner.node.mjs $(which bun) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6b83ae316396e9..05b3809feebff3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -144,59 +144,58 @@ Advanced users can pass CMake flags to customize the build. VSCode is the recommended IDE for working on Bun, as it has been configured. Once opening, you can run `Extensions: Show Recommended Extensions` to install the recommended extensions for Zig and C++. ZLS is automatically configured. -If you use a different editor, make sure that you tell ZLS to use the automatically installed Zig compiler, which is located at `./.cache/zig/zig` (`zig.exe` on Windows). +If you use a different editor, make sure that you tell ZLS to use the automatically installed Zig compiler, which is located at `./.cache/zig/zig.exe`. The filename is `zig.exe` so that it works as expected on Windows, but it still works on macOS/Linux (it just has a surprising file extension). -## Code generation scripts +We recommend adding `./build` to your `$PATH` so that you can run `bun-debug` in your terminal: -{% callout %} +```sh +$ bun-debug +``` -**Note**: This section is outdated. The code generators are run automatically by ninja, instead of by `make`. +## Code generation scripts -{% /callout %} +Several code generation scripts are used during Bun's build process. These are run automatically when changes are made to certain files. -Bun leverages a lot of code generation scripts. +In particular, these are: -The [./src/bun.js/bindings/headers.h](https://github.com/oven-sh/bun/blob/main/src/bun.js/bindings/headers.h) file has bindings to & from Zig <> C++ code. This file is generated by running the following: +- `./src/codegen/generate-jssink.ts` -- Generates `build/codegen/JSSink.cpp`, `build/codegen/JSSink.h` which implement various classes for interfacing with `ReadableStream`. This is internally how `FileSink`, `ArrayBufferSink`, `"type": "direct"` streams and other code related to streams works. +- `./src/codegen/generate-classes.ts` -- Generates `build/codegen/ZigGeneratedClasses*`, which generates Zig & C++ bindings for JavaScriptCore classes implemented in Zig. In `**/*.classes.ts` files, we define the interfaces for various classes, methods, prototypes, getters/setters etc which the code generator reads to generate boilerplate code implementing the JavaScript objects in C++ and wiring them up to Zig +- `./src/codegen/bundle-modules.ts` -- Bundles built-in modules like `node:fs`, `bun:ffi` into files we can include in the final binary. In development, these can be reloaded without rebuilding Zig (you still need to run `bun run build`, but it re-reads the transpiled files from disk afterwards). In release builds, these are embedded into the binary. +- `./src/codegen/bundle-functions.ts` -- Bundles globally-accessible functions implemented in JavaScript/TypeScript like `ReadableStream`, `WritableStream`, and a handful more. These are used similarly to the builtin modules, but the output more closely aligns with what WebKit/Safari does for Safari's built-in functions so that we can copy-paste the implementations from WebKit as a starting point. -```bash -$ make headers -``` +## Modifying ESM modules -This ensures that the types for Zig and the types for C++ match up correctly, by using comptime reflection over functions exported/imported. +Certain modules like `node:fs`, `node:stream`, `bun:sqlite`, and `ws` are implemented in JavaScript. These live in `src/js/{node,bun,thirdparty}` files and are pre-bundled using Bun. -TypeScript files that end with `*.classes.ts` are another code generation script. They generate C++ boilerplate for classes implemented in Zig. The generated code lives in: +## Release build -- [src/bun.js/bindings/ZigGeneratedClasses.cpp](https://github.com/oven-sh/bun/tree/main/src/bun.js/bindings/ZigGeneratedClasses.cpp) -- [src/bun.js/bindings/ZigGeneratedClasses.h](https://github.com/oven-sh/bun/tree/main/src/bun.js/bindings/ZigGeneratedClasses.h) -- [src/bun.js/bindings/generated_classes.zig](https://github.com/oven-sh/bun/tree/main/src/bun.js/bindings/generated_classes.zig) - To generate the code, run: +To compile a release build of Bun, run: ```bash -$ make codegen +$ bun run build:release ``` -Lastly, we also have a [code generation script](src/bun.js/scripts/generate-jssink.js) for our native stream implementations. -To run that, run: - -```bash -$ make generate-sink -``` +The binary will be located at `./build-release/bun` and `./build-release/bun-profile`. -You probably won't need to run that one much. +### Download release build from pull requests -## Modifying ESM modules +To save you time spent building a release build locally, we provide a way to run release builds from pull requests. This is useful for manully testing changes in a release build before they are merged. -Certain modules like `node:fs`, `node:stream`, `bun:sqlite`, and `ws` are implemented in JavaScript. These live in `src/js/{node,bun,thirdparty}` files and are pre-bundled using Bun. In debug builds, Bun automatically loads these from the filesystem, wherever it was compiled, so no need to re-run `make dev`. +To run a release build from a pull request, you can use the `bun-pr` npm package: -## Release build +```sh +bunx bun-pr pr-number +bunx bun-pr branch/branch-name +bunx bun-pr "https://github.com/oven-sh/bun/pull/1234566" +``` -To build a release build of Bun, run: +This will download the release build from the pull request and add it to `$PATH` as `bun-${pr-number}`. You can then run the build with `bun-${pr-number}`. -```bash -$ bun run build:release +```sh +bun-1234566 --version ``` -The binary will be located at `./build-release/bun` and `./build-release/bun-profile`. +This works by downloading the release build from the GitHub Actions artifacts on the linked pull request. You may need the `gh` CLI installed to authenticate with GitHub. ## Valgrind diff --git a/README.md b/README.md index f394d5f3672337..84567b758e4623 100644 --- a/README.md +++ b/README.md @@ -90,42 +90,286 @@ bun upgrade --canary - [What is Bun?](https://bun.sh/docs/index) - [Installation](https://bun.sh/docs/installation) - [Quickstart](https://bun.sh/docs/quickstart) -- CLI - - [`bun run`](https://bun.sh/docs/cli/run) - - [`bun install`](https://bun.sh/docs/cli/install) - - [`bun test`](https://bun.sh/docs/cli/test) + - [TypeScript](https://bun.sh/docs/typescript) + +- Templating - [`bun init`](https://bun.sh/docs/cli/init) - [`bun create`](https://bun.sh/docs/cli/bun-create) - - [`bunx`](https://bun.sh/docs/cli/bunx) + - Runtime - - [Runtime](https://bun.sh/docs/runtime/index) + - [`bun run`](https://bun.sh/docs/cli/run) + - [File types](https://bun.sh/docs/runtime/loaders) + - [TypeScript](https://bun.sh/docs/runtime/typescript) + - [JSX](https://bun.sh/docs/runtime/jsx) + - [Environment variables](https://bun.sh/docs/runtime/env) + - [Bun APIs](https://bun.sh/docs/runtime/bun-apis) + - [Web APIs](https://bun.sh/docs/runtime/web-apis) + - [Node.js compatibility](https://bun.sh/docs/runtime/nodejs-apis) + - [Single-file executable](https://bun.sh/docs/bundler/executables) + - [Plugins](https://bun.sh/docs/runtime/plugins) + - [Watch mode](https://bun.sh/docs/runtime/hot) - [Module resolution](https://bun.sh/docs/runtime/modules) - - [Hot & live reloading](https://bun.sh/docs/runtime/hot) + - [Auto-install](https://bun.sh/docs/runtime/autoimport) + - [bunfig.toml](https://bun.sh/docs/runtime/bunfig) + - [Debugger](https://bun.sh/docs/runtime/debugger) + - [Framework API](https://bun.sh/docs/runtime/framework) + +- Package manager + - [`bun install`](https://bun.sh/docs/cli/install) + - [`bun add`](https://bun.sh/docs/cli/add) + - [`bun remove`](https://bun.sh/docs/cli/remove) + - [`bun update`](https://bun.sh/docs/cli/update) + - [`bun link`](https://bun.sh/docs/cli/link) + - [`bun pm`](https://bun.sh/docs/cli/pm) + - [Global cache](https://bun.sh/docs/install/cache) + - [Workspaces](https://bun.sh/docs/install/workspaces) + - [Lifecycle scripts](https://bun.sh/docs/install/lifecycle) + - [Filter](https://bun.sh/docs/cli/filter) + - [Lockfile](https://bun.sh/docs/install/lockfile) + - [Scopes and registries](https://bun.sh/docs/install/registries) + - [Overrides and resolutions](https://bun.sh/docs/install/overrides) + +- Bundler + - [`Bun.build`](https://bun.sh/docs/bundler) + - [Loaders](https://bun.sh/docs/bundler/loaders) - [Plugins](https://bun.sh/docs/bundler/plugins) -- Ecosystem - - [Node.js](https://bun.sh/docs/ecosystem/nodejs) - - [TypeScript](https://bun.sh/docs/ecosystem/typescript) - - [React](https://bun.sh/docs/ecosystem/react) - - [Elysia](https://bun.sh/docs/ecosystem/elysia) - - [Hono](https://bun.sh/docs/ecosystem/hono) - - [Express](https://bun.sh/docs/ecosystem/express) - - [awesome-bun](https://github.com/apvarun/awesome-bun) + - [Macros](https://bun.sh/docs/bundler/macros) + - [vs esbuild](https://bun.sh/docs/bundler/vs-esbuild) + +- Test runner + - [`bun test`](https://bun.sh/docs/cli/test) + - [Writing tests](https://bun.sh/docs/test/writing) + - [Watch mode](https://bun.sh/docs/test/hot) + - [Lifecycle hooks](https://bun.sh/docs/test/lifecycle) + - [Mocks](https://bun.sh/docs/test/mocks) + - [Snapshots](https://bun.sh/docs/test/snapshots) + - [Dates and times](https://bun.sh/docs/test/time) + - [DOM testing](https://bun.sh/docs/test/dom) + - [Code coverage](https://bun.sh/docs/test/coverage) + +- Package runner + - [`bunx`](https://bun.sh/docs/cli/bunx) + - API - - [HTTP](https://bun.sh/docs/api/http) + - [HTTP server](https://bun.sh/docs/api/http) - [WebSockets](https://bun.sh/docs/api/websockets) - - [TCP Sockets](https://bun.sh/docs/api/tcp) + - [Workers](https://bun.sh/docs/api/workers) + - [Binary data](https://bun.sh/docs/api/binary-data) + - [Streams](https://bun.sh/docs/api/streams) - [File I/O](https://bun.sh/docs/api/file-io) + - [import.meta](https://bun.sh/docs/api/import-meta) - [SQLite](https://bun.sh/docs/api/sqlite) - [FileSystemRouter](https://bun.sh/docs/api/file-system-router) + - [TCP sockets](https://bun.sh/docs/api/tcp) + - [UDP sockets](https://bun.sh/docs/api/udp) - [Globals](https://bun.sh/docs/api/globals) - - [Spawn](https://bun.sh/docs/api/spawn) + - [$ Shell](https://bun.sh/docs/runtime/shell) + - [Child processes](https://bun.sh/docs/api/spawn) - [Transpiler](https://bun.sh/docs/api/transpiler) + - [Hashing](https://bun.sh/docs/api/hashing) - [Console](https://bun.sh/docs/api/console) - [FFI](https://bun.sh/docs/api/ffi) - [HTMLRewriter](https://bun.sh/docs/api/html-rewriter) - [Testing](https://bun.sh/docs/api/test) - [Utils](https://bun.sh/docs/api/utils) - [Node-API](https://bun.sh/docs/api/node-api) + - [Glob](https://bun.sh/docs/api/glob) + - [Semver](https://bun.sh/docs/api/semver) + +- Project + - [Roadmap](https://bun.sh/docs/project/roadmap) + - [Benchmarking](https://bun.sh/docs/project/benchmarking) + - [Contributing](https://bun.sh/docs/project/contributing) + - [Building Windows](https://bun.sh/docs/project/building-windows) + - [License](https://bun.sh/docs/project/licensing) + +## Guides + +- Binary + - [Convert a Blob to a DataView](https://bun.sh/guides/binary/blob-to-dataview) + - [Convert a Blob to a ReadableStream](https://bun.sh/guides/binary/blob-to-stream) + - [Convert a Blob to a string](https://bun.sh/guides/binary/blob-to-string) + - [Convert a Blob to a Uint8Array](https://bun.sh/guides/binary/blob-to-typedarray) + - [Convert a Blob to an ArrayBuffer](https://bun.sh/guides/binary/blob-to-arraybuffer) + - [Convert a Buffer to a blob](https://bun.sh/guides/binary/buffer-to-blob) + - [Convert a Buffer to a ReadableStream](https://bun.sh/guides/binary/buffer-to-readablestream) + - [Convert a Buffer to a string](https://bun.sh/guides/binary/buffer-to-string) + - [Convert a Buffer to a Uint8Array](https://bun.sh/guides/binary/buffer-to-typedarray) + - [Convert a Buffer to an ArrayBuffer](https://bun.sh/guides/binary/buffer-to-arraybuffer) + - [Convert a DataView to a string](https://bun.sh/guides/binary/dataview-to-string) + - [Convert a Uint8Array to a Blob](https://bun.sh/guides/binary/typedarray-to-blob) + - [Convert a Uint8Array to a Buffer](https://bun.sh/guides/binary/typedarray-to-buffer) + - [Convert a Uint8Array to a DataView](https://bun.sh/guides/binary/typedarray-to-dataview) + - [Convert a Uint8Array to a ReadableStream](https://bun.sh/guides/binary/typedarray-to-readablestream) + - [Convert a Uint8Array to a string](https://bun.sh/guides/binary/typedarray-to-string) + - [Convert a Uint8Array to an ArrayBuffer](https://bun.sh/guides/binary/typedarray-to-arraybuffer) + - [Convert an ArrayBuffer to a Blob](https://bun.sh/guides/binary/arraybuffer-to-blob) + - [Convert an ArrayBuffer to a Buffer](https://bun.sh/guides/binary/arraybuffer-to-buffer) + - [Convert an ArrayBuffer to a string](https://bun.sh/guides/binary/arraybuffer-to-string) + - [Convert an ArrayBuffer to a Uint8Array](https://bun.sh/guides/binary/arraybuffer-to-typedarray) + - [Convert an ArrayBuffer to an array of numbers](https://bun.sh/guides/binary/arraybuffer-to-array) + +- Ecosystem + - [Build a frontend using Vite and Bun](https://bun.sh/guides/ecosystem/vite) + - [Build an app with Astro and Bun](https://bun.sh/guides/ecosystem/astro) + - [Build an app with Next.js and Bun](https://bun.sh/guides/ecosystem/nextjs) + - [Build an app with Nuxt and Bun](https://bun.sh/guides/ecosystem/nuxt) + - [Build an app with Qwik and Bun](https://bun.sh/guides/ecosystem/qwik) + - [Build an app with Remix and Bun](https://bun.sh/guides/ecosystem/remix) + - [Build an app with SolidStart and Bun](https://bun.sh/guides/ecosystem/solidstart) + - [Build an app with SvelteKit and Bun](https://bun.sh/guides/ecosystem/sveltekit) + - [Build an HTTP server using Elysia and Bun](https://bun.sh/guides/ecosystem/elysia) + - [Build an HTTP server using Express and Bun](https://bun.sh/guides/ecosystem/express) + - [Build an HTTP server using Hono and Bun](https://bun.sh/guides/ecosystem/hono) + - [Build an HTTP server using StricJS and Bun](https://bun.sh/guides/ecosystem/stric) + - [Containerize a Bun application with Docker](https://bun.sh/guides/ecosystem/docker) + - [Create a Discord bot](https://bun.sh/guides/ecosystem/discordjs) + - [Read and write data to MongoDB using Mongoose and Bun](https://bun.sh/guides/ecosystem/mongoose) + - [Run Bun as a daemon with PM2](https://bun.sh/guides/ecosystem/pm2) + - [Run Bun as a daemon with systemd](https://bun.sh/guides/ecosystem/systemd) + - [Server-side render (SSR) a React component](https://bun.sh/guides/ecosystem/ssr-react) + - [Use Drizzle ORM with Bun](https://bun.sh/guides/ecosystem/drizzle) + - [Use EdgeDB with Bun](https://bun.sh/guides/ecosystem/edgedb) + - [Use Neon's Serverless Postgres with Bun](https://bun.sh/guides/ecosystem/neon-serverless-postgres) + - [Use Prisma with Bun](https://bun.sh/guides/ecosystem/prisma) + - [Use React and JSX](https://bun.sh/guides/ecosystem/react) + +- HTTP + - [Common HTTP server usage](https://bun.sh/guides/http/server) + - [Configure TLS on an HTTP server](https://bun.sh/guides/http/tls) + - [fetch with unix domain sockets in Bun](https://bun.sh/guides/http/fetch-unix) + - [Hot reload an HTTP server](https://bun.sh/guides/http/hot) + - [Proxy HTTP requests using fetch()](https://bun.sh/guides/http/proxy) + - [Send an HTTP request using fetch](https://bun.sh/guides/http/fetch) + - [Start a cluster of HTTP servers](https://bun.sh/guides/http/cluster) + - [Stream a file as an HTTP Response](https://bun.sh/guides/http/stream-file) + - [Streaming HTTP Server with Async Iterators](https://bun.sh/guides/http/stream-iterator) + - [Streaming HTTP Server with Node.js Streams](https://bun.sh/guides/http/stream-node-streams-in-bun) + - [Upload files via HTTP using FormData](https://bun.sh/guides/http/file-uploads) + - [Write a simple HTTP server](https://bun.sh/guides/http/simple) + +- Install + - [Add a dependency](https://bun.sh/guides/install/add) + - [Add a development dependency](https://bun.sh/guides/install/add-dev) + - [Add a Git dependency](https://bun.sh/guides/install/add-git) + - [Add a peer dependency](https://bun.sh/guides/install/add-peer) + - [Add a tarball dependency](https://bun.sh/guides/install/add-tarball) + - [Add a trusted dependency](https://bun.sh/guides/install/trusted) + - [Add an optional dependency](https://bun.sh/guides/install/add-optional) + - [Configure a private registry for an organization scope with bun install](https://bun.sh/guides/install/registry-scope) + - [Configure git to diff Bun's lockb lockfile](https://bun.sh/guides/install/git-diff-bun-lockfile) + - [Configuring a monorepo using workspaces](https://bun.sh/guides/install/workspaces) + - [Generate a human-readable lockfile](https://bun.sh/guides/install/yarnlock) + - [Install a package under a different name](https://bun.sh/guides/install/npm-alias) + - [Install dependencies with Bun in GitHub Actions](https://bun.sh/guides/install/cicd) + - [Override the default npm registry for bun install](https://bun.sh/guides/install/custom-registry) + - [Using bun install with an Azure Artifacts npm registry](https://bun.sh/guides/install/azure-artifacts) + - [Using bun install with Artifactory](https://bun.sh/guides/install/jfrog-artifactory) + +- Process + - [Get the process uptime in nanoseconds](https://bun.sh/guides/process/nanoseconds) + - [Listen for CTRL+C](https://bun.sh/guides/process/ctrl-c) + - [Listen to OS signals](https://bun.sh/guides/process/os-signals) + - [Parse command-line arguments](https://bun.sh/guides/process/argv) + - [Read from stdin](https://bun.sh/guides/process/stdin) + - [Read stderr from a child process](https://bun.sh/guides/process/spawn-stderr) + - [Read stdout from a child process](https://bun.sh/guides/process/spawn-stdout) + - [Spawn a child process](https://bun.sh/guides/process/spawn) + - [Spawn a child process and communicate using IPC](https://bun.sh/guides/process/ipc) + +- Read file + - [Check if a file exists](https://bun.sh/guides/read-file/exists) + - [Get the MIME type of a file](https://bun.sh/guides/read-file/mime) + - [Read a file as a ReadableStream](https://bun.sh/guides/read-file/stream) + - [Read a file as a string](https://bun.sh/guides/read-file/string) + - [Read a file to a Buffer](https://bun.sh/guides/read-file/buffer) + - [Read a file to a Uint8Array](https://bun.sh/guides/read-file/uint8array) + - [Read a file to an ArrayBuffer](https://bun.sh/guides/read-file/arraybuffer) + - [Read a JSON file](https://bun.sh/guides/read-file/json) + - [Watch a directory for changes](https://bun.sh/guides/read-file/watch) + +- Runtime + - [Debugging Bun with the VS Code extension](https://bun.sh/guides/runtime/vscode-debugger) + - [Debugging Bun with the web debugger](https://bun.sh/guides/runtime/web-debugger) + - [Define and replace static globals & constants](https://bun.sh/guides/runtime/define-constant) + - [Import a JSON file](https://bun.sh/guides/runtime/import-json) + - [Import a TOML file](https://bun.sh/guides/runtime/import-toml) + - [Import HTML file as text](https://bun.sh/guides/runtime/import-html) + - [Install and run Bun in GitHub Actions](https://bun.sh/guides/runtime/cicd) + - [Install TypeScript declarations for Bun](https://bun.sh/guides/runtime/typescript) + - [Re-map import paths](https://bun.sh/guides/runtime/tsconfig-paths) + - [Read environment variables](https://bun.sh/guides/runtime/read-env) + - [Run a Shell Command](https://bun.sh/guides/runtime/shell) + - [Set a time zone in Bun](https://bun.sh/guides/runtime/timezone) + - [Set environment variables](https://bun.sh/guides/runtime/set-env) + +- Streams + - [Convert a Node.js Readable to a Blob](https://bun.sh/guides/streams/node-readable-to-blob) + - [Convert a Node.js Readable to a string](https://bun.sh/guides/streams/node-readable-to-string) + - [Convert a Node.js Readable to an ArrayBuffer](https://bun.sh/guides/streams/node-readable-to-arraybuffer) + - [Convert a Node.js Readable to JSON](https://bun.sh/guides/streams/node-readable-to-json) + - [Convert a ReadableStream to a Blob](https://bun.sh/guides/streams/to-blob) + - [Convert a ReadableStream to a Buffer](https://bun.sh/guides/streams/to-buffer) + - [Convert a ReadableStream to a string](https://bun.sh/guides/streams/to-string) + - [Convert a ReadableStream to a Uint8Array](https://bun.sh/guides/streams/to-typedarray) + - [Convert a ReadableStream to an array of chunks](https://bun.sh/guides/streams/to-array) + - [Convert a ReadableStream to an ArrayBuffer](https://bun.sh/guides/streams/to-arraybuffer) + - [Convert a ReadableStream to JSON](https://bun.sh/guides/streams/to-json) + +- Test + - [Bail early with the Bun test runner](https://bun.sh/guides/test/bail) + - [Generate code coverage reports with the Bun test runner](https://bun.sh/guides/test/coverage) + - [Mark a test as a "todo" with the Bun test runner](https://bun.sh/guides/test/todo-tests) + - [Migrate from Jest to Bun's test runner](https://bun.sh/guides/test/migrate-from-jest) + - [Mock functions in `bun test`](https://bun.sh/guides/test/mock-functions) + - [Re-run tests multiple times with the Bun test runner](https://bun.sh/guides/test/rerun-each) + - [Run tests in watch mode with Bun](https://bun.sh/guides/test/watch-mode) + - [Run your tests with the Bun test runner](https://bun.sh/guides/test/run-tests) + - [Set a code coverage threshold with the Bun test runner](https://bun.sh/guides/test/coverage-threshold) + - [Set a per-test timeout with the Bun test runner](https://bun.sh/guides/test/timeout) + - [Set the system time in Bun's test runner](https://bun.sh/guides/test/mock-clock) + - [Skip tests with the Bun test runner](https://bun.sh/guides/test/skip-tests) + - [Spy on methods in `bun test`](https://bun.sh/guides/test/spy-on) + - [Update snapshots in `bun test`](https://bun.sh/guides/test/update-snapshots) + - [Use snapshot testing in `bun test`](https://bun.sh/guides/test/snapshot) + - [Write browser DOM tests with Bun and happy-dom](https://bun.sh/guides/test/happy-dom) + +- Util + - [Check if the current file is the entrypoint](https://bun.sh/guides/util/entrypoint) + - [Check if two objects are deeply equal](https://bun.sh/guides/util/deep-equals) + - [Compress and decompress data with DEFLATE](https://bun.sh/guides/util/deflate) + - [Compress and decompress data with gzip](https://bun.sh/guides/util/gzip) + - [Convert a file URL to an absolute path](https://bun.sh/guides/util/file-url-to-path) + - [Convert an absolute path to a file URL](https://bun.sh/guides/util/path-to-file-url) + - [Detect when code is executed with Bun](https://bun.sh/guides/util/detect-bun) + - [Encode and decode base64 strings](https://bun.sh/guides/util/base64) + - [Escape an HTML string](https://bun.sh/guides/util/escape-html) + - [Get the absolute path of the current file](https://bun.sh/guides/util/import-meta-path) + - [Get the absolute path to the current entrypoint](https://bun.sh/guides/util/main) + - [Get the current Bun version](https://bun.sh/guides/util/version) + - [Get the directory of the current file](https://bun.sh/guides/util/import-meta-dir) + - [Get the file name of the current file](https://bun.sh/guides/util/import-meta-file) + - [Get the path to an executable bin file](https://bun.sh/guides/util/which-path-to-executable-bin) + - [Hash a password](https://bun.sh/guides/util/hash-a-password) + - [Sleep for a fixed number of milliseconds](https://bun.sh/guides/util/sleep) + +- WebSocket + - [Build a publish-subscribe WebSocket server](https://bun.sh/guides/websocket/pubsub) + - [Build a simple WebSocket server](https://bun.sh/guides/websocket/simple) + - [Enable compression for WebSocket messages](https://bun.sh/guides/websocket/compression) + - [Set per-socket contextual data on a WebSocket](https://bun.sh/guides/websocket/context) + +- Write file + - [Append content to a file](https://bun.sh/guides/write-file/append) + - [Copy a file to another location](https://bun.sh/guides/write-file/file-cp) + - [Delete a file](https://bun.sh/guides/write-file/unlink) + - [Write a Blob to a file](https://bun.sh/guides/write-file/blob) + - [Write a file incrementally](https://bun.sh/guides/write-file/filesink) + - [Write a file to stdout](https://bun.sh/guides/write-file/cat) + - [Write a ReadableStream to a file](https://bun.sh/guides/write-file/stream) + - [Write a Response to a file](https://bun.sh/guides/write-file/response) + - [Write a string to a file](https://bun.sh/guides/write-file/basic) + - [Write to stdout](https://bun.sh/guides/write-file/stdout) ## Contributing diff --git a/docs/api/websockets.md b/docs/api/websockets.md index 0b3968d330eac4..a14290fd07689a 100644 --- a/docs/api/websockets.md +++ b/docs/api/websockets.md @@ -321,7 +321,7 @@ namespace Bun { message: string | ArrayBuffer | Uint8Array, ) => void; open?: (ws: ServerWebSocket) => void; - close?: (ws: ServerWebSocket) => void; + close?: (ws: ServerWebSocket, code: number, reason: string) => void; error?: (ws: ServerWebSocket, error: Error) => void; drain?: (ws: ServerWebSocket) => void; diff --git a/docs/guides/http/cluster.md b/docs/guides/http/cluster.md new file mode 100644 index 00000000000000..1c3537e4c03d45 --- /dev/null +++ b/docs/guides/http/cluster.md @@ -0,0 +1,66 @@ +--- +name: Start a cluster of HTTP servers +description: Run multiple HTTP servers concurrently via the "reusePort" option to share the same port across multiple processes +--- + +To run multiple HTTP servers concurrently, use the `reusePort` option in `Bun.serve()` which shares the same port across multiple processes. + +This automatically load balances incoming requests across multiple instances of Bun. + +```ts#server.ts +import { serve } from "bun"; + +const id = = Math.random().toString(36).slice(2); + +serve({ + port: process.env.PORT || 8080, + development: false, + + // Share the same port across multiple processes + // This is the important part! + reusePort: true, + + async fetch(request) { + return new Response("Hello from Bun #" + id + "!\n"); + } +}); +``` + +--- + +{% callout %} +**Linux only** — Windows and macOS ignore the `reusePort` option. This is an operating system limitation with `SO_REUSEPORT`, unfortunately. +{% /callout %} + +After saving the file, start your servers on the same port. + +Under the hood, this uses the Linux `SO_REUSEPORT` and `SO_REUSEADDR` socket options to ensure fair load balancing across multiple processes. [Learn more about `SO_REUSEPORT` and `SO_REUSEADDR`](https://lwn.net/Articles/542629/) + +```ts#cluster.ts +import { spawn } from "bun"; + +const cpus = navigator.hardwareConcurrency; // Number of CPU cores +const buns = new Array(cpus); + +for (let i = 0; i < cpus; i++) { + buns[i] = spawn({ + cmd: ["bun", "./server.ts"], + stdout: "inherit", + stderr: "inherit", + stdin: "inherit", + }); +} + +function kill() { + for (const bun of buns) { + bun.kill(); + } +} + +process.on("SIGINT", kill); +process.on("exit", kill); +``` + +--- + +At the time of writing, Bun hasn't implemented the `node:cluster` module yet, but this is a faster, simple, and limited alternative. We will also implement `node:cluster` in the future. diff --git a/scripts/nav2readme.ts b/scripts/nav2readme.ts new file mode 100644 index 00000000000000..4beeabb1074243 --- /dev/null +++ b/scripts/nav2readme.ts @@ -0,0 +1,113 @@ +// Regenerate the Table of Contents in the README by reading through nav.ts +// +// To run this: +// +// bun ./scripts/nav2readme.ts +// +// +import nav from "../docs/nav"; +import { readdirSync } from "fs"; +import path from "path"; +function getQuickLinks() { + let md = ""; + + // This ordering is intentional + for (const item of nav.items) { + if (item.type === "divider") { + md += "\n" + `- ${item.title}` + "\n"; + } else { + md += ` - [${item.title}](https://bun.sh/docs/${item.slug})` + "\n"; + } + } + + return md; +} + +async function getGuides() { + let md = ""; + const basePath = path.join(import.meta.dirname, "..", "docs/guides"); + const allGuides = readdirSync(basePath, { withFileTypes: true, recursive: true }); + const promises: Promise<{ name: string; file: string }>[] = []; + for (const guide of allGuides) { + if (guide.isFile() && guide.name.endsWith(".md")) { + const joined = path.join(basePath, guide.name); + promises.push( + Bun.file(joined) + .text() + .then(text => { + const nameI = text.indexOf("name: "); + const name = text.slice(nameI + "name: ".length, text.indexOf("\n", nameI)).trim(); + return { + name, + file: guide.name, + }; + }), + ); + } + } + + const files = await Promise.all(promises); + md += "## Guides " + "\n"; + // The guides ordering is not as intentional + // They should be grouped by category + // and then by name within the category + files.sort((a, b) => { + const aDir = path.basename(path.dirname(a.file)).toLowerCase(); + const bDir = path.basename(path.dirname(b.file)).toLowerCase(); + let cmp = aDir.localeCompare(bDir); + if (cmp !== 0) { + return cmp; + } + + return a.name.localeCompare(b.name); + }); + + let prevDirname = ""; + for (const { name, file } of files) { + const dirname = path.basename(path.dirname(file)); + if (dirname !== prevDirname) { + md += `\n- ${normalizeSectionName(dirname)} ` + "\n"; + prevDirname = dirname; + } + md += + ` - [${name}](https://bun.sh/guides/${path.dirname(file)}/${path.basename(file, path.extname(file))})` + "\n"; + } + + return md; +} + +const text = await Bun.file(Bun.fileURLToPath(import.meta.resolve("../README.md"))).text(); +const startI = text.indexOf("## Quick links\n"); +if (startI === -1) { + throw new Error("Could not find ## Quick links in README"); +} +const start = startI + "## Quick links\n".length; + +const contributing = text.indexOf("## Contributing\n", start); + +if (contributing === -1) { + throw new Error("Could not find ## Contributing in README"); +} + +const guides = await getGuides(); + +const combined = + [text.slice(0, start), getQuickLinks(), guides, text.slice(contributing)].map(text => text.trim()).join("\n\n") + + "\n"; + +await Bun.write(Bun.fileURLToPath(import.meta.resolve("../README.md")), combined); + +function normalizeSectionName(name: string) { + if (name.includes("-")) { + return name + .split("-") + .map((s, i) => (i === 0 ? s.charAt(0).toUpperCase() + s.slice(1) : s)) + .join(" "); + } + + name = name.charAt(0).toUpperCase() + name.slice(1); + name = name.replaceAll("Https", "HTTPS"); + name = name.replaceAll("Http", "HTTP"); + name = name.replaceAll("Websocket", "WebSocket"); + return name; +} diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index 0c552c2c8d4d92..0c08b037c1218b 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -633,7 +633,7 @@ pub fn inspect( } formatOptions.max_depth = @as(u16, @truncate(@as(u32, @intCast(@min(arg, std.math.maxInt(u16)))))); } else if (opt.isNumber()) { - const v = opt.asDouble(); + const v = opt.coerce(f64, globalThis); if (std.math.isInf(v)) { formatOptions.max_depth = std.math.maxInt(u16); } else { @@ -660,7 +660,7 @@ pub fn inspect( } formatOptions.max_depth = @as(u16, @truncate(@as(u32, @intCast(@min(arg, std.math.maxInt(u16)))))); } else if (depthArg.isNumber()) { - const v = depthArg.asDouble(); + const v = depthArg.coerce(f64, globalThis); if (std.math.isInf(v)) { formatOptions.max_depth = std.math.maxInt(u16); } else { diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 01a23a31bca99e..fe2613dfd7ea0b 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -3644,7 +3644,7 @@ pub const JSValue = enum(JSValueReprInt) { ZigString => this.getZigString(globalThis), bool => this.toBooleanSlow(globalThis), f64 => { - if (this.isNumber()) { + if (this.isDouble()) { return this.asDouble(); } @@ -4376,6 +4376,10 @@ pub const JSValue = enum(JSValueReprInt) { return FFI.JSVALUE_IS_NUMBER(.{ .asJSValue = this }); } + pub fn isDouble(this: JSValue) bool { + return this.isNumber() and !this.isInt32(); + } + pub fn isError(this: JSValue) bool { if (!this.isCell()) return false; @@ -4940,7 +4944,7 @@ pub const JSValue = enum(JSValueReprInt) { } if (prop.isNumber()) { - return prop.asDouble() != 0; + return prop.coerce(f64, globalThis) != 0; } globalThis.throwInvalidArguments(property_name ++ " must be a boolean", .{}); @@ -5089,6 +5093,7 @@ pub const JSValue = enum(JSValueReprInt) { } if (isNumber(this)) { + // Don't need to check for !isInt32() because above return asDouble(this); } @@ -5101,6 +5106,7 @@ pub const JSValue = enum(JSValueReprInt) { } if (isNumber(this)) { + // Don't need to check for !isInt32() because above return asDouble(this); } @@ -5116,6 +5122,7 @@ pub const JSValue = enum(JSValueReprInt) { } pub fn asDouble(this: JSValue) f64 { + bun.assert(this.isDouble()); return FFI.JSVALUE_TO_DOUBLE(.{ .asJSValue = this }); } diff --git a/src/bun.js/bindings/webcore/JSEventEmitter.cpp b/src/bun.js/bindings/webcore/JSEventEmitter.cpp index bb75f61c12d0ef..01d6b36ff06dbe 100644 --- a/src/bun.js/bindings/webcore/JSEventEmitter.cpp +++ b/src/bun.js/bindings/webcore/JSEventEmitter.cpp @@ -234,11 +234,9 @@ inline JSC::EncodedJSValue JSEventEmitter::addListener(JSC::JSGlobalObject* lexi auto eventType = argument0.value().toPropertyKey(lexicalGlobalObject); RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); EnsureStillAliveScope argument1 = callFrame->uncheckedArgument(1); - auto listener = convert>>(*lexicalGlobalObject, argument1.value(), *castedThis, [](JSC::JSGlobalObject& lexicalGlobalObject, JSC::ThrowScope& scope) { throwArgumentMustBeObjectError(lexicalGlobalObject, scope, 1, "listener", "EventEmitter", "addListener"); }); - RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); - JSValue::encode(toJS(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.addListenerForBindings(WTFMove(eventType), WTFMove(listener), once, prepend); })); - RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + // see EventEmitterPrototype.addListener in events.ts + // first, emit the newListener event JSC::Identifier newListenerEventType = JSC::Identifier::fromString(vm, "newListener"_s); JSC::MarkedArgumentBuffer args; args.append(argument0.value()); @@ -247,6 +245,12 @@ inline JSC::EncodedJSValue JSEventEmitter::addListener(JSC::JSGlobalObject* lexi JSValue::encode(toJS(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.emitForBindings(WTFMove(newListenerEventType), WTFMove(args)); })); RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + // then, add the listener + auto listener = convert>>(*lexicalGlobalObject, argument1.value(), *castedThis, [](JSC::JSGlobalObject& lexicalGlobalObject, JSC::ThrowScope& scope) { throwArgumentMustBeObjectError(lexicalGlobalObject, scope, 1, "listener", "EventEmitter", "addListener"); }); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + JSValue::encode(toJS(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.addListenerForBindings(WTFMove(eventType), WTFMove(listener), once, prepend); })); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + vm.writeBarrier(&static_cast(*castedThis), argument1.value()); impl.setThisObject(actualThis); RELEASE_AND_RETURN(throwScope, JSValue::encode(actualThis)); diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 36b028ab35af10..9024e371ed00fa 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -890,16 +890,17 @@ pub const VirtualMachine = struct { pub fn reload(this: *VirtualMachine) void { Output.debug("Reloading...", .{}); + const should_clear_terminal = !this.bundler.env.hasSetNoClearTerminalOnReload(!Output.enable_ansi_colors); if (this.hot_reload == .watch) { Output.flush(); bun.reloadProcess( bun.default_allocator, - !strings.eqlComptime(this.bundler.env.get("BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD") orelse "0", "true"), + should_clear_terminal, false, ); } - if (!strings.eqlComptime(this.bundler.env.get("BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD") orelse "0", "true")) { + if (should_clear_terminal) { Output.flush(); Output.disableBuffering(); Output.resetTerminalAll(); @@ -3588,7 +3589,7 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime this.bundler.resolver.watcher = Resolver.ResolveWatcher(*@This().Watcher, onMaybeWatchDirectory).init(this.bun_watcher.?); } - clear_screen = Output.enable_ansi_colors and !strings.eqlComptime(this.bundler.env.get("BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD") orelse "0", "true"); + clear_screen = !this.bundler.env.hasSetNoClearTerminalOnReload(!Output.enable_ansi_colors); reloader.getContext().start() catch @panic("Failed to start File Watcher"); } diff --git a/src/cli.zig b/src/cli.zig index 2d6b0f7e951af6..6cbd5086ad7be9 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -170,6 +170,7 @@ pub const Arguments = struct { const runtime_params_ = [_]ParamType{ clap.parseParam("--watch Automatically restart the process on file change") catch unreachable, clap.parseParam("--hot Enable auto reload in the Bun runtime, test runner, or bundler") catch unreachable, + clap.parseParam("--no-clear-screen Disable clearing the terminal screen on reload when --hot or --watch is enabled") catch unreachable, clap.parseParam("--smol Use less memory, but run garbage collection more often") catch unreachable, clap.parseParam("-r, --preload ... Import a module before other modules are loaded") catch unreachable, clap.parseParam("--inspect ? Activate Bun's debugger") catch unreachable, @@ -214,9 +215,10 @@ pub const Arguments = struct { const build_only_params = [_]ParamType{ clap.parseParam("--compile Generate a standalone Bun executable containing your bundled code") catch unreachable, clap.parseParam("--watch Automatically restart the process on file change") catch unreachable, + clap.parseParam("--no-clear-screen Disable clearing the terminal screen on reload when --watch is enabled") catch unreachable, clap.parseParam("--target The intended execution environment for the bundle. \"browser\", \"bun\" or \"node\"") catch unreachable, clap.parseParam("--outdir Default to \"dist\" if multiple files") catch unreachable, - clap.parseParam("--outfile Write to a file") catch unreachable, + clap.parseParam("--outfile Write to a file") catch unreachable, clap.parseParam("--sourcemap ? Build with sourcemaps - 'inline', 'external', or 'none'") catch unreachable, clap.parseParam("--format Specifies the module format to build to. Only \"esm\" is supported.") catch unreachable, clap.parseParam("--root Root directory used for multiple entry points") catch unreachable, @@ -231,7 +233,7 @@ pub const Arguments = struct { clap.parseParam("--minify Enable all minification flags") catch unreachable, clap.parseParam("--minify-syntax Minify syntax and inline data") catch unreachable, clap.parseParam("--minify-whitespace Minify whitespace") catch unreachable, - clap.parseParam("--minify-identifiers Minify identifiers") catch unreachable, + clap.parseParam("--minify-identifiers Minify identifiers") catch unreachable, clap.parseParam("--dump-environment-variables") catch unreachable, clap.parseParam("--conditions ... Pass custom conditions to resolve") catch unreachable, }; @@ -532,9 +534,15 @@ pub const Arguments = struct { if (args.flag("--hot")) { ctx.debug.hot_reload = .hot; + if (args.flag("--no-clear-screen")) { + bun.DotEnv.Loader.has_no_clear_screen_cli_flag = true; + } } else if (args.flag("--watch")) { ctx.debug.hot_reload = .watch; bun.auto_reload_on_crash = true; + if (args.flag("--no-clear-screen")) { + bun.DotEnv.Loader.has_no_clear_screen_cli_flag = true; + } } if (args.option("--origin")) |origin| { @@ -692,6 +700,10 @@ pub const Arguments = struct { if (args.flag("--watch")) { ctx.debug.hot_reload = .watch; bun.auto_reload_on_crash = true; + + if (args.flag("--no-clear-screen")) { + bun.DotEnv.Loader.has_no_clear_screen_cli_flag = true; + } } if (args.flag("--compile")) { diff --git a/src/cli/install.sh b/src/cli/install.sh index 1bb2eae9d17bc0..a2530c324ba8f5 100644 --- a/src/cli/install.sh +++ b/src/cli/install.sh @@ -1,9 +1,13 @@ #!/usr/bin/env bash set -euo pipefail +platform=$(uname -ms) + if [[ ${OS:-} = Windows_NT ]]; then + if [[ $platform != MINGW64* ]]; then powershell -c "irm bun.sh/install.ps1|iex" exit $? + fi fi # Reset @@ -56,7 +60,7 @@ if [[ $# -gt 2 ]]; then error 'Too many arguments, only 2 are allowed. The first can be a specific tag of bun to install. (e.g. "bun-v0.1.4") The second can be a build variant of bun to install. (e.g. "debug-info")' fi -case $(uname -ms) in +case $platform in 'Darwin x86_64') target=darwin-x64 ;; @@ -66,6 +70,9 @@ case $(uname -ms) in 'Linux aarch64' | 'Linux arm64') target=linux-aarch64 ;; +'MINGW64'*) + target=windows-x64 + ;; 'Linux x86_64' | *) target=linux-x64 ;; diff --git a/src/env_loader.zig b/src/env_loader.zig index e73af290033fda..9f35a35ec07299 100644 --- a/src/env_loader.zig +++ b/src/env_loader.zig @@ -240,6 +240,28 @@ pub const Loader = struct { return true; } + pub fn getAs(this: *const Loader, comptime T: type, key: string) ?T { + const value = this.get(key) orelse return null; + switch (comptime T) { + bool => { + if (strings.eqlComptime(value, "")) return false; + if (strings.eqlComptime(value, "0")) return false; + if (strings.eqlComptime(value, "NO")) return false; + if (strings.eqlComptime(value, "OFF")) return false; + if (strings.eqlComptime(value, "false")) return false; + + return true; + }, + else => @compileError("Implement getAs for this type"), + } + } + + pub var has_no_clear_screen_cli_flag: ?bool = null; + /// Returns whether the `BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD` env var is set to something truthy + pub fn hasSetNoClearTerminalOnReload(this: *const Loader, default_value: bool) bool { + return (has_no_clear_screen_cli_flag orelse this.getAs(bool, "BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD")) orelse default_value; + } + pub fn get(this: *const Loader, key: string) ?string { var _key = key; if (_key.len > 0 and _key[0] == '$') { diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig index 267423cb971b52..ad546346bf99c8 100644 --- a/src/install/lockfile.zig +++ b/src/install/lockfile.zig @@ -5015,8 +5015,14 @@ pub const Package = extern struct { @memcpy(bytes, stream.buffer[stream.pos..][0..bytes.len]); stream.pos = end_pos; if (comptime strings.eqlComptime(field.name, "meta")) { - if (value.len != 0 and value[0].needsUpdate()) { - needs_update = true; + // need to check if any values were created from an older version of bun + // (currently just `has_install_script`). If any are found, the values need + // to be updated before saving the lockfile. + for (value) |*meta| { + if (meta.needsUpdate()) { + needs_update = true; + break; + } } } } else if (comptime strings.eqlComptime(field.name, "scripts")) { diff --git a/src/js/node/http2.ts b/src/js/node/http2.ts index e36fe65127ef5d..d80f5dd609643d 100644 --- a/src/js/node/http2.ts +++ b/src/js/node/http2.ts @@ -27,6 +27,7 @@ const bunHTTP2Session = Symbol.for("::bunhttp2session::"); const ReflectGetPrototypeOf = Reflect.getPrototypeOf; const FunctionPrototypeBind = primordials.FunctionPrototypeBind; +const StringPrototypeSlice = String.prototype.slice; const proxySocketHandler = { get(session, prop) { @@ -624,6 +625,7 @@ class ClientHttp2Stream extends Duplex { } } } + function connectWithProtocol(protocol: string, options: Http2ConnectOptions | string | URL, listener?: Function) { if (protocol === "http:") { return net.connect(options, listener); @@ -1060,7 +1062,7 @@ class ClientHttp2Session extends Http2Session { } } - constructor(url: string | URL, options?: Http2ConnectOptions) { + constructor(url: string | URL, options?: Http2ConnectOptions, listener?: Function) { super(); if (typeof url === "string") { @@ -1069,23 +1071,32 @@ class ClientHttp2Session extends Http2Session { if (!(url instanceof URL)) { throw new Error("ERR_HTTP2: Invalid URL"); } + if (typeof options === "function") { + listener = options; + options = undefined; + } this.#isServer = true; this.#url = url; const protocol = url.protocol || options?.protocol || "https:"; const port = url.port ? parseInt(url.port, 10) : protocol === "http:" ? 80 : 443; + function onConnect() { + this.#onConnect(arguments); + listener?.$apply(this, arguments); + } + // h2 with ALPNProtocols let socket; if (typeof options?.createConnection === "function") { socket = options.createConnection(url, options); this[bunHTTP2Socket] = socket; if (socket.secureConnecting === true) { - socket.on("secureConnect", this.#onConnect.bind(this)); + socket.on("secureConnect", onConnect.bind(this)); } else if (socket.connecting === true) { - socket.on("connect", this.#onConnect.bind(this)); + socket.on("connect", onConnect.bind(this)); } else { - process.nextTick(this.#onConnect.bind(this)); + process.nextTick(onConnect.bind(this)); } } else { socket = connectWithProtocol( @@ -1102,7 +1113,7 @@ class ClientHttp2Session extends Http2Session { port, ALPNProtocols: ["h2", "http/1.1"], }, - this.#onConnect.bind(this), + onConnect.bind(this), ); this[bunHTTP2Socket] = socket; } @@ -1242,11 +1253,8 @@ class ClientHttp2Session extends Http2Session { req.emit("ready"); return req; } - static connect(url: string | URL, options?: Http2ConnectOptions) { - if (options) { - return new ClientHttp2Session(url, options); - } - return new ClientHttp2Session(url); + static connect(url: string | URL, options?: Http2ConnectOptions, listener?: Function) { + return new ClientHttp2Session(url, options, listener); } get [bunHTTP2Native]() { @@ -1254,11 +1262,8 @@ class ClientHttp2Session extends Http2Session { } } -function connect(url: string | URL, options?: Http2ConnectOptions) { - if (options) { - return ClientHttp2Session.connect(url, options); - } - return ClientHttp2Session.connect(url); +function connect(url: string | URL, options?: Http2ConnectOptions, listener?: Function) { + return ClientHttp2Session.connect(url, options, listener); } function createServer() { diff --git a/src/js/node/net.ts b/src/js/node/net.ts index 25e8147fc57660..ee6e63a115a170 100644 --- a/src/js/node/net.ts +++ b/src/js/node/net.ts @@ -422,66 +422,47 @@ const Socket = (function (InternalSocket) { process.nextTick(closeNT, connection); } - connect(port, host, connectListener) { - var path; - var connection = this.#socket; - var _checkServerIdentity = undefined; - if (typeof port === "string") { - path = port; - port = undefined; - - if (typeof host === "function") { - connectListener = host; - host = undefined; - } - } else if (typeof host == "function") { - if (typeof port === "string") { - path = port; - port = undefined; - } - - connectListener = host; - host = undefined; + connect(...args) { + const [options, connectListener] = normalizeArgs(args); + let connection = this.#socket; + + let { + fd, + port, + host, + path, + socket, + // TODOs + localAddress, + localPort, + family, + hints, + lookup, + noDelay, + keepAlive, + keepAliveInitialDelay, + requestCert, + rejectUnauthorized, + pauseOnConnect, + servername, + checkServerIdentity, + session, + } = options; + + this.servername = servername; + + if (socket) { + connection = socket; } - if (typeof port == "object") { - var { - fd, - port, - host, - path, - socket, - // TODOs - localAddress, - localPort, - family, - hints, - lookup, - noDelay, - keepAlive, - keepAliveInitialDelay, - requestCert, - rejectUnauthorized, - pauseOnConnect, - servername, - checkServerIdentity, - session, - } = port; - _checkServerIdentity = checkServerIdentity; - this.servername = servername; - if (socket) { - connection = socket; - } - if (fd) { - bunConnect({ - data: this, - fd, - socket: this.#handlers, - tls, - }).catch(error => { - this.emit("error", error); - this.emit("close"); - }); - } + if (fd) { + bunConnect({ + data: this, + fd: fd, + socket: this.#handlers, + }).catch(error => { + this.emit("error", error); + this.emit("close"); + }); } this.pauseOnConnect = pauseOnConnect; @@ -516,7 +497,7 @@ const Socket = (function (InternalSocket) { tls.requestCert = true; tls.session = session || tls.session; this.servername = tls.servername; - tls.checkServerIdentity = _checkServerIdentity || tls.checkServerIdentity; + tls.checkServerIdentity = checkServerIdentity || tls.checkServerIdentity; this[bunTLSConnectOptions] = tls; if (!connection && tls.socket) { connection = tls.socket; @@ -1006,6 +987,43 @@ function createServer(options, connectionListener) { return new Server(options, connectionListener); } +function normalizeArgs(args) { + while (args[args.length - 1] == null) args.pop(); + let arr; + + if (args.length === 0) { + arr = [{}, null]; + return arr; + } + + const arg0 = args[0]; + let options: any = {}; + if (typeof arg0 === "object" && arg0 !== null) { + options = arg0; + } else if (isPipeName(arg0)) { + options.path = arg0; + } else { + options.port = arg0; + if (args.length > 1 && typeof args[1] === "string") { + options.host = args[1]; + } + } + + const cb = args[args.length - 1]; + if (typeof cb !== "function") arr = [options, null]; + else arr = [options, cb]; + + return arr; +} + +function isPipeName(s) { + return typeof s === "string" && toNumber(s) === false; +} + +function toNumber(x) { + return (x = Number(x)) >= 0 ? x : false; +} + // TODO: class BlockList { constructor() {} @@ -1027,6 +1045,7 @@ export default { isIPv6, Socket, [Symbol.for("::bunternal::")]: SocketClass, + _normalizeArgs: normalizeArgs, getDefaultAutoSelectFamily: $zig("node_net_binding.zig", "getDefaultAutoSelectFamily"), setDefaultAutoSelectFamily: $zig("node_net_binding.zig", "setDefaultAutoSelectFamily"), diff --git a/src/js/node/tls.ts b/src/js/node/tls.ts index b68c8b78703f23..1df7d858cf5042 100644 --- a/src/js/node/tls.ts +++ b/src/js/node/tls.ts @@ -11,6 +11,7 @@ const SymbolReplace = Symbol.replace; const RegExpPrototypeSymbolReplace = RegExp.prototype[SymbolReplace]; const RegExpPrototypeExec = RegExp.prototype.exec; const JSONParse = JSON.parse; +const ObjectAssign = Object.assign; const StringPrototypeStartsWith = String.prototype.startsWith; const StringPrototypeSlice = String.prototype.slice; @@ -660,21 +661,41 @@ const DEFAULT_ECDH_CURVE = "auto", DEFAULT_CIPHERS = "DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256", DEFAULT_MIN_VERSION = "TLSv1.2", - DEFAULT_MAX_VERSION = "TLSv1.3", - createConnection = (port, host, connectListener) => { - if (typeof port === "object") { - port.checkServerIdentity || checkServerIdentity; - const { ALPNProtocols } = port; - if (ALPNProtocols) { - convertALPNProtocols(ALPNProtocols, port); - } - // port is option pass Socket options and let connect handle connection options - return new TLSSocket(port).connect(port, host, connectListener); - } - // port is path or host, let connect handle this - return new TLSSocket().connect(port, host, connectListener); - }, - connect = createConnection; + DEFAULT_MAX_VERSION = "TLSv1.3"; + +function normalizeConnectArgs(listArgs) { + const args = net._normalizeArgs(listArgs); + const options = args[0]; + const cb = args[1]; + + // If args[0] was options, then normalize dealt with it. + // If args[0] is port, or args[0], args[1] is host, port, we need to + // find the options and merge them in, normalize's options has only + // the host/port/path args that it knows about, not the tls options. + // This means that options.host overrides a host arg. + if (listArgs[1] !== null && typeof listArgs[1] === "object") { + ObjectAssign(options, listArgs[1]); + } else if (listArgs[2] !== null && typeof listArgs[2] === "object") { + ObjectAssign(options, listArgs[2]); + } + + return cb ? [options, cb] : [options]; +} + +// tls.connect(options[, callback]) +// tls.connect(path[, options][, callback]) +// tls.connect(port[, host][, options][, callback]) +function connect(...args) { + if (typeof args[0] !== "object") { + return new TLSSocket().connect(...args); + } + let [options, callback] = normalizeConnectArgs(args); + const { ALPNProtocols } = options; + if (ALPNProtocols) { + convertALPNProtocols(ALPNProtocols, options); + } + return new TLSSocket(options).connect(options, callback); +} function getCiphers() { return DEFAULT_CIPHERS.split(":"); @@ -732,7 +753,6 @@ export default { CLIENT_RENEG_WINDOW, connect, convertALPNProtocols, - createConnection, createSecureContext, createServer, DEFAULT_CIPHERS, diff --git a/src/shell/shell.zig b/src/shell/shell.zig index 792cec42c923a1..4830a9ffdb3e67 100644 --- a/src/shell/shell.zig +++ b/src/shell/shell.zig @@ -2345,6 +2345,8 @@ pub fn NewLexer(comptime encoding: StringEncoding) type { switch (char) { // possibly double bracket open '[' => { + comptime assertSpecialChar('['); + if (self.chars.state == .Single or self.chars.state == .Double) break :escaped; if (self.peek()) |p| { if (p.escaped or p.char != '[') break :escaped; @@ -2371,6 +2373,8 @@ pub fn NewLexer(comptime encoding: StringEncoding) type { break :escaped; }, ']' => { + comptime assertSpecialChar(']'); + if (self.chars.state == .Single or self.chars.state == .Double) break :escaped; if (self.peek()) |p| { if (p.escaped or p.char != ']') break :escaped; @@ -2398,6 +2402,8 @@ pub fn NewLexer(comptime encoding: StringEncoding) type { }, '#' => { + comptime assertSpecialChar('#'); + if (self.chars.state == .Single or self.chars.state == .Double) break :escaped; const whitespace_preceding = if (self.chars.prev) |prev| @@ -2410,12 +2416,16 @@ pub fn NewLexer(comptime encoding: StringEncoding) type { continue; }, ';' => { + comptime assertSpecialChar(';'); + if (self.chars.state == .Single or self.chars.state == .Double) break :escaped; try self.break_word(true); try self.tokens.append(.Semicolon); continue; }, '\n' => { + comptime assertSpecialChar('\n'); + if (self.chars.state == .Single or self.chars.state == .Double) break :escaped; try self.break_word_impl(true, true, false); try self.tokens.append(.Newline); @@ -2424,6 +2434,8 @@ pub fn NewLexer(comptime encoding: StringEncoding) type { // glob asterisks '*' => { + comptime assertSpecialChar('*'); + if (self.chars.state == .Single or self.chars.state == .Double) break :escaped; if (self.peek()) |next| { if (!next.escaped and next.char == '*') { @@ -2440,18 +2452,24 @@ pub fn NewLexer(comptime encoding: StringEncoding) type { // brace expansion syntax '{' => { + comptime assertSpecialChar('{'); + if (self.chars.state == .Single or self.chars.state == .Double) break :escaped; try self.break_word(false); try self.tokens.append(.BraceBegin); continue; }, ',' => { + comptime assertSpecialChar(','); + if (self.chars.state == .Single or self.chars.state == .Double) break :escaped; try self.break_word(false); try self.tokens.append(.Comma); continue; }, '}' => { + comptime assertSpecialChar('}'); + if (self.chars.state == .Single or self.chars.state == .Double) break :escaped; try self.break_word(false); try self.tokens.append(.BraceEnd); @@ -2460,6 +2478,8 @@ pub fn NewLexer(comptime encoding: StringEncoding) type { // Command substitution '`' => { + comptime assertSpecialChar('`'); + if (self.chars.state == .Single) break :escaped; if (self.in_subshell == .backtick) { try self.break_word(true); @@ -2474,6 +2494,8 @@ pub fn NewLexer(comptime encoding: StringEncoding) type { }, // Command substitution/vars '$' => { + comptime assertSpecialChar('$'); + if (self.chars.state == .Single) break :escaped; const peeked = self.peek() orelse InputChar{ .char = 0 }; @@ -2509,12 +2531,16 @@ pub fn NewLexer(comptime encoding: StringEncoding) type { continue; }, '(' => { + comptime assertSpecialChar('('); + if (self.chars.state == .Single or self.chars.state == .Double) break :escaped; try self.break_word(true); try self.eat_subshell(.normal); continue; }, ')' => { + comptime assertSpecialChar(')'); + if (self.chars.state == .Single or self.chars.state == .Double) break :escaped; if (self.in_subshell != .dollar and self.in_subshell != .normal) { self.add_error("Unexpected ')'"); @@ -2544,6 +2570,8 @@ pub fn NewLexer(comptime encoding: StringEncoding) type { }, '0'...'9' => { + comptime for ('0'..'9') |c| assertSpecialChar(c); + if (self.chars.state != .Normal) break :escaped; const snapshot = self.make_snapshot(); if (self.eat_redirect(input)) |redirect| { @@ -2557,6 +2585,8 @@ pub fn NewLexer(comptime encoding: StringEncoding) type { // Operators '|' => { + comptime assertSpecialChar('|'); + if (self.chars.state == .Single or self.chars.state == .Double) break :escaped; try self.break_word(true); @@ -2577,6 +2607,8 @@ pub fn NewLexer(comptime encoding: StringEncoding) type { continue; }, '>' => { + comptime assertSpecialChar('>'); + if (self.chars.state == .Single or self.chars.state == .Double) break :escaped; try self.break_word_impl(true, false, true); const redirect = self.eat_simple_redirect(.out); @@ -2584,6 +2616,8 @@ pub fn NewLexer(comptime encoding: StringEncoding) type { continue; }, '<' => { + comptime assertSpecialChar('<'); + if (self.chars.state == .Single or self.chars.state == .Double) break :escaped; try self.break_word_impl(true, false, true); const redirect = self.eat_simple_redirect(.in); @@ -2591,6 +2625,8 @@ pub fn NewLexer(comptime encoding: StringEncoding) type { continue; }, '&' => { + comptime assertSpecialChar('&'); + if (self.chars.state == .Single or self.chars.state == .Double) break :escaped; try self.break_word(true); @@ -2619,6 +2655,8 @@ pub fn NewLexer(comptime encoding: StringEncoding) type { // 2. State switchers '\'' => { + comptime assertSpecialChar('\''); + if (self.chars.state == .Single) { self.chars.state = .Normal; continue; @@ -2630,6 +2668,8 @@ pub fn NewLexer(comptime encoding: StringEncoding) type { break :escaped; }, '"' => { + comptime assertSpecialChar('"'); + if (self.chars.state == .Single) break :escaped; if (self.chars.state == .Normal) { try self.break_word(false); @@ -2644,6 +2684,8 @@ pub fn NewLexer(comptime encoding: StringEncoding) type { // 3. Word breakers ' ' => { + comptime assertSpecialChar(' '); + if (self.chars.state == .Normal) { try self.break_word_impl(true, true, false); continue; @@ -3961,7 +4003,18 @@ pub const ShellSrcBuilder = struct { }; /// Characters that need to escaped -const SPECIAL_CHARS = [_]u8{ '$', '>', '&', '|', '=', ';', '\n', '{', '}', ',', '(', ')', '\\', '\"', ' ', '\'' }; +const SPECIAL_CHARS = [_]u8{ '~', '[', ']', '#', ';', '\n', '*', '{', ',', '}', '`', '$', '=', '(', ')', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '|', '>', '<', '&', '\'', '"', ' ', '\\' }; +const SPECIAL_CHARS_TABLE: std.bit_set.IntegerBitSet(256) = brk: { + var table = std.bit_set.IntegerBitSet(256).initEmpty(); + for (SPECIAL_CHARS) |c| { + table.set(c); + } + break :brk table; +}; +pub fn assertSpecialChar(c: u8) void { + comptime bun.assert(@inComptime()); + bun.assert(SPECIAL_CHARS_TABLE.isSet(c)); +} /// Characters that need to be backslashed inside double quotes const BACKSLASHABLE_CHARS = [_]u8{ '$', '`', '"', '\\' }; @@ -4070,39 +4123,11 @@ pub fn needsEscapeBunstr(bunstr: bun.String) bool { return needsEscapeUtf8AsciiLatin1(bunstr.byteSlice()); } -pub fn needsEscapeUTF16Slow(str: []const u16) bool { - for (str) |codeunit| { - inline for (SPECIAL_CHARS) |spc| { - if (@as(u16, @intCast(spc)) == codeunit) return true; - } - } - - return false; -} - pub fn needsEscapeUTF16(str: []const u16) bool { - if (str.len < 64) return needsEscapeUTF16Slow(str); - - const needles = comptime brk: { - var needles: [SPECIAL_CHARS.len]@Vector(8, u16) = undefined; - for (SPECIAL_CHARS, 0..) |c, i| { - needles[i] = @splat(@as(u16, @intCast(c))); - } - break :brk needles; - }; - - var i: usize = 0; - while (i + 8 <= str.len) : (i += 8) { - const haystack: @Vector(8, u16) = str[i..][0..8].*; - - inline for (needles) |needle| { - const result = haystack == needle; - if (std.simd.firstTrue(result) != null) return true; - } + for (str) |codeunit| { + if (codeunit < 0xff and SPECIAL_CHARS_TABLE.isSet(codeunit)) return true; } - if (i < str.len) return needsEscapeUTF16Slow(str[i..]); - return false; } @@ -4111,36 +4136,8 @@ pub fn needsEscapeUTF16(str: []const u16) bool { /// false positives, but it is faster than running the shell lexer through the /// input string for a more correct implementation. pub fn needsEscapeUtf8AsciiLatin1(str: []const u8) bool { - if (str.len < 128) return needsEscapeUtf8AsciiLatin1Slow(str); - - const needles = comptime brk: { - var needles: [SPECIAL_CHARS.len]@Vector(16, u8) = undefined; - for (SPECIAL_CHARS, 0..) |c, i| { - needles[i] = @splat(c); - } - break :brk needles; - }; - - var i: usize = 0; - while (i + 16 <= str.len) : (i += 16) { - const haystack: @Vector(16, u8) = str[i..][0..16].*; - - inline for (needles) |needle| { - const result = haystack == needle; - if (std.simd.firstTrue(result) != null) return true; - } - } - - if (i < str.len) return needsEscapeUtf8AsciiLatin1Slow(str[i..]); - - return false; -} - -pub fn needsEscapeUtf8AsciiLatin1Slow(str: []const u8) bool { for (str) |c| { - inline for (SPECIAL_CHARS) |spc| { - if (spc == c) return true; - } + if (SPECIAL_CHARS_TABLE.isSet(c)) return true; } return false; } diff --git a/test/bundler/cli.test.ts b/test/bundler/cli.test.ts index 8f44881cc04aeb..ccfaa4e646a9bd 100644 --- a/test/bundler/cli.test.ts +++ b/test/bundler/cli.test.ts @@ -1,4 +1,4 @@ -import { bunEnv, bunExe } from "harness"; +import { bunEnv, bunExe, tmpdirSync } from "harness"; import { describe, expect, test } from "bun:test"; import fs from "node:fs"; import { tmpdir } from "node:os"; @@ -54,7 +54,7 @@ describe("bun build", () => { }); test("works with utf8 bom", () => { - const tmp = fs.mkdtempSync(path.join(tmpdir(), "bun-build-utf8-bom-")); + const tmp = tmpdirSync(); const src = path.join(tmp, "index.js"); fs.writeFileSync(src, '\ufeffconsole.log("hello world");', { encoding: "utf8" }); const { exitCode } = Bun.spawnSync({ diff --git a/test/bundler/fixtures/with-assets/img.png b/test/bundler/fixtures/with-assets/img.png index 894185f67f89fe..7c48c8d6a027c6 120000 Binary files a/test/bundler/fixtures/with-assets/img.png and b/test/bundler/fixtures/with-assets/img.png differ diff --git a/test/cli/hot/hot.test.ts b/test/cli/hot/hot.test.ts index 487ea2da8a60a6..682f6ec49c0e06 100644 --- a/test/cli/hot/hot.test.ts +++ b/test/cli/hot/hot.test.ts @@ -1,14 +1,13 @@ import { spawn } from "bun"; import { beforeAll, beforeEach, expect, it } from "bun:test"; -import { bunExe, bunEnv, tempDirWithFiles, bunRun, bunRunAsScript } from "harness"; +import { bunExe, bunEnv, tmpdirSync } from "harness"; import { cpSync, readFileSync, renameSync, rmSync, unlinkSync, writeFileSync, copyFileSync } from "fs"; import { join } from "path"; -import { tmpdir } from "os"; let hotRunnerRoot: string = "", cwd = ""; beforeEach(() => { - const hotPath = join(tmpdir(), "bun-hot-test-" + (Date.now() | 0) + "_" + Math.random().toString(36).slice(2)); + const hotPath = tmpdirSync(); hotRunnerRoot = join(hotPath, "hot-runner-root.js"); rmSync(hotPath, { recursive: true, force: true }); cpSync(import.meta.dir, hotPath, { recursive: true, force: true }); diff --git a/test/cli/init/init.test.ts b/test/cli/init/init.test.ts index d876b97d16a59c..a12889b88fccac 100644 --- a/test/cli/init/init.test.ts +++ b/test/cli/init/init.test.ts @@ -1,10 +1,10 @@ import fs from "fs"; import path from "path"; -import os from "os"; -import { bunExe, bunEnv } from "harness"; +import { bunExe, bunEnv, tmpdirSync } from "harness"; +import { test, expect } from "bun:test"; test("bun init works", () => { - const temp = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "bun-init-X"))); + const temp = tmpdirSync(); const out = Bun.spawnSync({ cmd: [bunExe(), "init", "-y"], @@ -40,7 +40,7 @@ test("bun init works", () => { }, 30_000); test("bun init with piped cli", () => { - const temp = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "bun-init-X"))); + const temp = tmpdirSync(); const out = Bun.spawnSync({ cmd: [bunExe(), "init"], diff --git a/test/cli/install/bad-workspace.test.ts b/test/cli/install/bad-workspace.test.ts index 3345e8976fbcb3..cd8c4b0a5c6ce8 100644 --- a/test/cli/install/bad-workspace.test.ts +++ b/test/cli/install/bad-workspace.test.ts @@ -1,14 +1,12 @@ import { spawnSync } from "bun"; -import { afterEach, beforeEach, expect, test } from "bun:test"; -import { mkdtempSync, realpathSync, rmSync, writeFileSync } from "fs"; -import { bunExe, bunEnv } from "harness"; -import { join } from "path"; -import { tmpdir } from "os"; +import { beforeEach, expect, test } from "bun:test"; +import { writeFileSync } from "fs"; +import { bunExe, bunEnv, tmpdirSync } from "harness"; let cwd: string; beforeEach(() => { - cwd = mkdtempSync(join(realpathSync(tmpdir()), "bad-workspace.test")); + cwd = tmpdirSync(); }); test("bad workspace path", () => { diff --git a/test/cli/install/bun-add.test.ts b/test/cli/install/bun-add.test.ts index f6e4a7d88abd32..9b4ba6e61c46d7 100644 --- a/test/cli/install/bun-add.test.ts +++ b/test/cli/install/bun-add.test.ts @@ -1,9 +1,8 @@ import { file, spawn } from "bun"; import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from "bun:test"; -import { bunExe, bunEnv as env, toHaveBins, toBeValidBin, toBeWorkspaceLink, ospath } from "harness"; -import { access, mkdir, mkdtemp, readlink, realpath, rm, writeFile, copyFile, appendFile } from "fs/promises"; +import { bunExe, bunEnv as env, toHaveBins, toBeValidBin, toBeWorkspaceLink, tmpdirSync } from "harness"; +import { access, mkdir, readlink, rm, writeFile, copyFile, appendFile } from "fs/promises"; import { join, relative } from "path"; -import { tmpdir } from "os"; import { dummyAfterAll, dummyAfterEach, @@ -33,7 +32,7 @@ beforeAll(() => { }); beforeEach(async () => { - add_dir = await mkdtemp(join(await realpath(tmpdir()), "bun-add.test")); + add_dir = tmpdirSync(); await dummyBeforeEach(); }); afterEach(async () => { @@ -1763,7 +1762,7 @@ async function installRedirectsToAdd(saveFlagFirst: boolean) { " 1 package installed", ]); expect(await exited).toBe(0); - expect(await file(join(package_dir, "package.json")).text()).toInclude("bun-add.test"); + expect(await file(join(package_dir, "package.json")).text()).toInclude("bun.test."); } it("should add dependency alongside peerDependencies", async () => { diff --git a/test/cli/install/bun-create.test.ts b/test/cli/install/bun-create.test.ts index a81d082f052d74..a94b5b9ac1ded0 100644 --- a/test/cli/install/bun-create.test.ts +++ b/test/cli/install/bun-create.test.ts @@ -7,7 +7,7 @@ import { join } from "path"; let x_dir: string; beforeEach(async () => { - x_dir = tmpdirSync("bun-create.test"); + x_dir = tmpdirSync(); }); describe("should not crash", async () => { diff --git a/test/cli/install/bun-install-pathname-trailing-slash.test.ts b/test/cli/install/bun-install-pathname-trailing-slash.test.ts index d9f8b72b0c95a1..42cb6116d28b26 100644 --- a/test/cli/install/bun-install-pathname-trailing-slash.test.ts +++ b/test/cli/install/bun-install-pathname-trailing-slash.test.ts @@ -1,13 +1,11 @@ import { afterEach, beforeEach, expect, test } from "bun:test"; -import { mkdtempSync, realpathSync, rmSync } from "fs"; import { bunEnv, bunExe, tmpdirSync } from "harness"; -import { tmpdir } from "os"; import { join } from "path"; let package_dir: string; beforeEach(() => { - package_dir = tmpdirSync("bun-install-path"); + package_dir = tmpdirSync(); }); // https://github.com/oven-sh/bun/issues/2462 diff --git a/test/cli/install/bun-link.test.ts b/test/cli/install/bun-link.test.ts index 7079050da262da..0174dad5b83b47 100644 --- a/test/cli/install/bun-link.test.ts +++ b/test/cli/install/bun-link.test.ts @@ -1,9 +1,8 @@ import { spawn, file } from "bun"; import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from "bun:test"; -import { bunExe, bunEnv as env, toBeValidBin, toHaveBins } from "harness"; -import { access, mkdtemp, readlink, realpath, rm, writeFile, mkdir } from "fs/promises"; -import { basename, join, sep, dirname } from "path"; -import { tmpdir } from "os"; +import { bunExe, bunEnv as env, tmpdirSync, toBeValidBin, toHaveBins } from "harness"; +import { access, writeFile, mkdir } from "fs/promises"; +import { basename, join } from "path"; import { dummyAfterAll, dummyAfterEach, @@ -24,7 +23,7 @@ expect.extend({ }); beforeEach(async () => { - link_dir = await mkdtemp(join(await realpath(tmpdir()), "bun-link.test")); + link_dir = tmpdirSync(); await dummyBeforeEach(); }); afterEach(async () => { diff --git a/test/cli/install/bun-pm.test.ts b/test/cli/install/bun-pm.test.ts index 005f25138c0dd1..62f357957edd95 100644 --- a/test/cli/install/bun-pm.test.ts +++ b/test/cli/install/bun-pm.test.ts @@ -1,6 +1,6 @@ import { hash, spawn } from "bun"; import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from "bun:test"; -import { bunEnv, bunExe, bunEnv as env } from "harness"; +import { bunEnv, bunExe, bunEnv as env, tmpdirSync } from "harness"; import { mkdir, writeFile, exists } from "fs/promises"; import { join } from "path"; import { @@ -371,7 +371,7 @@ it("should remove all cache", async () => { import { tmpdir } from "os"; it("bun pm migrate", async () => { - const test_dir = join(tmpdir(), "contoso-test" + Math.random().toString(36).slice(2)); + const test_dir = tmpdirSync(); cpSync(join(import.meta.dir, "migration/contoso-test"), test_dir, { recursive: true }); diff --git a/test/cli/install/bun-remove.test.ts b/test/cli/install/bun-remove.test.ts index b9c0dd87fd8a62..6a61a91e311c33 100644 --- a/test/cli/install/bun-remove.test.ts +++ b/test/cli/install/bun-remove.test.ts @@ -1,7 +1,6 @@ -import { bunExe, bunEnv as env } from "harness"; -import { mkdir, mkdtemp, realpath, rm, writeFile } from "fs/promises"; +import { bunExe, bunEnv as env, tmpdirSync } from "harness"; +import { mkdir, writeFile } from "fs/promises"; import { join, relative } from "path"; -import { tmpdir } from "os"; import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from "bun:test"; import { dummyAfterAll, dummyAfterEach, dummyBeforeAll, dummyBeforeEach, package_dir } from "./dummy.registry"; import { spawn } from "bun"; @@ -13,7 +12,7 @@ afterAll(dummyAfterAll); let remove_dir: string; beforeEach(async () => { - remove_dir = await mkdtemp(join(await realpath(tmpdir()), "bun-remove.test")); + remove_dir = tmpdirSync(); await dummyBeforeEach(); }); diff --git a/test/cli/install/bun-run.test.ts b/test/cli/install/bun-run.test.ts index 9b8c8cb08f02e2..974af62c51954e 100644 --- a/test/cli/install/bun-run.test.ts +++ b/test/cli/install/bun-run.test.ts @@ -1,17 +1,14 @@ import { file, spawn, spawnSync } from "bun"; import { afterEach, beforeEach, expect, it, describe } from "bun:test"; -import { bunEnv, bunExe, bunEnv as env, isWindows } from "harness"; -import { mkdtemp, realpath, rm, writeFile, exists, mkdir } from "fs/promises"; -import { tmpdir } from "os"; +import { bunEnv, bunExe, bunEnv as env, isWindows, tmpdirSync } from "harness"; +import { rm, writeFile, exists, mkdir } from "fs/promises"; import { join } from "path"; import { readdirSorted } from "./dummy.registry"; let run_dir: string; beforeEach(async () => { - run_dir = await realpath( - await mkdtemp(join(tmpdir(), "bun-run.test." + Math.trunc(Math.random() * 9999999).toString(32))), - ); + run_dir = tmpdirSync(); }); for (let withRun of [false, true]) { diff --git a/test/cli/install/bun-upgrade.test.ts b/test/cli/install/bun-upgrade.test.ts index 1d45e558949410..1cc862bfeb8130 100644 --- a/test/cli/install/bun-upgrade.test.ts +++ b/test/cli/install/bun-upgrade.test.ts @@ -1,8 +1,6 @@ import { spawn, spawnSync } from "bun"; import { beforeEach, expect, it } from "bun:test"; -import { bunExe, bunEnv as env } from "harness"; -import { mkdtemp, realpath, readFile } from "fs/promises"; -import { tmpdir } from "os"; +import { bunExe, bunEnv as env, tmpdirSync } from "harness"; import { join } from "path"; import { copyFileSync } from "js/node/fs/export-star-from"; import { upgrade_test_helpers } from "bun:internal-for-testing"; @@ -12,9 +10,7 @@ let run_dir: string; let exe_name: string = "bun-debug" + (process.platform === "win32" ? ".exe" : ""); beforeEach(async () => { - run_dir = await realpath( - await mkdtemp(join(tmpdir(), "bun-upgrade.test." + Math.trunc(Math.random() * 9999999).toString(32))), - ); + run_dir = tmpdirSync(); copyFileSync(bunExe(), join(run_dir, exe_name)); }); diff --git a/test/cli/install/bun-workspaces.test.ts b/test/cli/install/bun-workspaces.test.ts index aecea4adcc9f7e..f8e02cf4fa7f8f 100644 --- a/test/cli/install/bun-workspaces.test.ts +++ b/test/cli/install/bun-workspaces.test.ts @@ -15,7 +15,7 @@ var port: number = 4873; var packageDir: string; beforeEach(() => { - packageDir = tmpdirSync("bun-workspaces-" + testCounter++ + "-"); + packageDir = tmpdirSync(); env.BUN_INSTALL_CACHE_DIR = join(packageDir, ".bun-cache"); env.BUN_TMPDIR = env.TMPDIR = env.TEMP = join(packageDir, ".bun-tmp"); writeFileSync( diff --git a/test/cli/install/bunx.test.ts b/test/cli/install/bunx.test.ts index e5e82bee0a1fd1..dcf4eb94048f71 100644 --- a/test/cli/install/bunx.test.ts +++ b/test/cli/install/bunx.test.ts @@ -1,7 +1,7 @@ import { spawn } from "bun"; import { afterEach, beforeEach, expect, it } from "bun:test"; -import { bunExe, bunEnv, isWindows } from "harness"; -import { mkdtemp, realpath, writeFile, rm } from "fs/promises"; +import { bunExe, bunEnv, isWindows, tmpdirSync } from "harness"; +import { writeFile, rm } from "fs/promises"; import { tmpdir } from "os"; import { join } from "path"; import { readdirSorted } from "./dummy.registry"; @@ -29,9 +29,9 @@ beforeEach(async () => { } }); - install_cache_dir = await mkdtemp(join(tmpdir(), "bun-install-cache-" + Math.random().toString(36).slice(2))); - current_tmpdir = await realpath(await mkdtemp(join(tmpdir(), "bun-x-tmpdir" + Math.random().toString(36).slice(2)))); - x_dir = await realpath(await mkdtemp(join(tmpdir(), "bun-x.test" + Math.random().toString(36).slice(2)))); + install_cache_dir = tmpdirSync(); + current_tmpdir = tmpdirSync(); + x_dir = tmpdirSync(); env.TEMP = current_tmpdir; env.BUN_TMPDIR = env.TMPDIR = current_tmpdir; diff --git a/test/cli/install/dummy.registry.ts b/test/cli/install/dummy.registry.ts index b0c80c0212a6ce..0858835b5d4d61 100644 --- a/test/cli/install/dummy.registry.ts +++ b/test/cli/install/dummy.registry.ts @@ -21,7 +21,6 @@ type Pkg = { }; let handler: Handler; let server: Server; -let testCounter = 0; export let package_dir: string; export let requested: number; export let root_url: string; @@ -98,7 +97,7 @@ export function dummyAfterAll() { } let packageDirGetter: () => string = () => { - return tmpdirSync("bun-install-test-" + testCounter++ + "--"); + return tmpdirSync(); }; export async function dummyBeforeEach() { resetHandler(); diff --git a/test/cli/install/migration/complex-workspace.test.ts b/test/cli/install/migration/complex-workspace.test.ts index bd1e698edc1876..9aa98d6bcab35f 100644 --- a/test/cli/install/migration/complex-workspace.test.ts +++ b/test/cli/install/migration/complex-workspace.test.ts @@ -1,11 +1,9 @@ import fs from "fs"; import path from "path"; import { test, expect, describe, beforeAll } from "bun:test"; -import { bunEnv, bunExe } from "harness"; -import { tmpdir } from "os"; -import { join } from "path"; +import { bunEnv, bunExe, tmpdirSync } from "harness"; -let cwd = join(tmpdir(), "complex-workspace-test" + Math.random().toString(36).slice(2, 8)); +let cwd = tmpdirSync(); function validate(packageName: string, version: string, realPackageName?: string) { test(`${packageName} is ${realPackageName ? `${realPackageName}@${version}` : version}`, () => { diff --git a/test/cli/install/migration/migrate.test.ts b/test/cli/install/migration/migrate.test.ts index 9c8b759f18c647..335d9f6eeb95e3 100644 --- a/test/cli/install/migration/migrate.test.ts +++ b/test/cli/install/migration/migrate.test.ts @@ -1,27 +1,10 @@ import fs from "fs"; -import { test, expect, beforeAll, afterAll } from "bun:test"; -import { bunEnv, bunExe } from "harness"; -import { join, sep } from "path"; -import { mkdtempSync } from "js/node/fs/export-star-from"; -import { tmpdir } from "os"; - -const ROOT_TEMP_DIR = join(tmpdir(), "migrate", sep); - -beforeAll(() => { - // if the test was stopped early - fs.rmSync(ROOT_TEMP_DIR, { recursive: true, force: true }); - fs.mkdirSync(ROOT_TEMP_DIR); -}); - -afterAll(() => { - fs.rmSync(ROOT_TEMP_DIR, { - recursive: true, - force: true, - }); -}); +import { test, expect } from "bun:test"; +import { bunEnv, bunExe, tmpdirSync } from "harness"; +import { join } from "path"; function testMigration(lockfile: string) { - const testDir = mkdtempSync(ROOT_TEMP_DIR); + const testDir = tmpdirSync(); fs.writeFileSync( join(testDir, "package.json"), @@ -58,7 +41,7 @@ test("migrate from npm lockfile v2 during `bun add`", () => { // Currently this upgrades svelte :( test.todo("migrate workspace from npm during `bun add`", async () => { - const testDir = mkdtempSync(ROOT_TEMP_DIR); + const testDir = tmpdirSync(); fs.cpSync(join(import.meta.dir, "add-while-migrate-workspace"), testDir, { recursive: true }); @@ -77,7 +60,7 @@ test.todo("migrate workspace from npm during `bun add`", async () => { }); test("migrate from npm lockfile that is missing `resolved` properties", async () => { - const testDir = mkdtempSync(ROOT_TEMP_DIR); + const testDir = tmpdirSync(); fs.cpSync(join(import.meta.dir, "missing-resolved-properties"), testDir, { recursive: true }); diff --git a/test/cli/install/overrides.test.ts b/test/cli/install/overrides.test.ts index db617d8393f14d..9ec599abe7e2b9 100644 --- a/test/cli/install/overrides.test.ts +++ b/test/cli/install/overrides.test.ts @@ -1,8 +1,7 @@ -import { mkdtempSync } from "fs"; import { join } from "path"; -import { tmpdir } from "os"; import { readFileSync, writeFileSync } from "fs"; -import { bunEnv, bunExe } from "harness"; +import { bunEnv, bunExe, tmpdirSync } from "harness"; +import { test, expect } from "bun:test"; function install(cwd: string, args: string[]) { const exec = Bun.spawnSync({ @@ -51,7 +50,7 @@ function ensureLockfileDoesntChangeOnBunI(cwd: string) { } test("overrides affect your own packages", async () => { - const tmp = mkdtempSync(join(tmpdir(), "bun-pm-test")); + const tmp = tmpdirSync(); writeFileSync( join(tmp, "package.json"), JSON.stringify({ @@ -67,7 +66,7 @@ test("overrides affect your own packages", async () => { }); test("overrides affects all dependencies", async () => { - const tmp = mkdtempSync(join(tmpdir(), "bun-pm-test")); + const tmp = tmpdirSync(); writeFileSync( join(tmp, "package.json"), JSON.stringify({ @@ -84,7 +83,7 @@ test("overrides affects all dependencies", async () => { }); test("overrides being set later affects all dependencies", async () => { - const tmp = mkdtempSync(join(tmpdir(), "bun-pm-test")); + const tmp = tmpdirSync(); writeFileSync( join(tmp, "package.json"), JSON.stringify({ @@ -112,7 +111,7 @@ test("overrides being set later affects all dependencies", async () => { }); test("overrides to npm specifier", async () => { - const tmp = mkdtempSync(join(tmpdir(), "bun-pm-test")); + const tmp = tmpdirSync(); writeFileSync( join(tmp, "package.json"), JSON.stringify({ @@ -137,7 +136,7 @@ test("overrides to npm specifier", async () => { }); test("changing overrides makes the lockfile changed, prevent frozen install", async () => { - const tmp = mkdtempSync(join(tmpdir(), "bun-pm-test")); + const tmp = tmpdirSync(); writeFileSync( join(tmp, "package.json"), JSON.stringify({ @@ -163,7 +162,7 @@ test("changing overrides makes the lockfile changed, prevent frozen install", as }); test("overrides reset when removed", async () => { - const tmp = mkdtempSync(join(tmpdir(), "bun-pm-test")); + const tmp = tmpdirSync(); writeFileSync( join(tmp, "package.json"), JSON.stringify({ diff --git a/test/cli/install/registry/bun-install-registry.test.ts b/test/cli/install/registry/bun-install-registry.test.ts index 09092132e939a3..72fa32dfcb9fa0 100644 --- a/test/cli/install/registry/bun-install-registry.test.ts +++ b/test/cli/install/registry/bun-install-registry.test.ts @@ -11,10 +11,8 @@ import { toMatchNodeModulesAt, } from "harness"; import { join, sep } from "path"; -import { mkdtempSync, realpathSync } from "fs"; import { rm, writeFile, mkdir, exists, cp } from "fs/promises"; import { readdirSorted } from "../dummy.registry"; -import { tmpdir } from "os"; import { fork, ChildProcess } from "child_process"; import { beforeAll, afterAll, beforeEach, afterEach, test, expect, describe } from "bun:test"; import { install_test_helpers } from "bun:internal-for-testing"; @@ -53,7 +51,7 @@ afterAll(() => { }); beforeEach(async () => { - packageDir = tmpdirSync("bun-install-registry-" + testCounter++ + "-"); + packageDir = tmpdirSync(); env.BUN_INSTALL_CACHE_DIR = join(packageDir, ".bun-cache"); env.BUN_TMPDIR = env.TMPDIR = env.TEMP = join(packageDir, ".bun-tmp"); await writeFile( diff --git a/test/cli/install/registry/bun-install-windowsshim.test.ts b/test/cli/install/registry/bun-install-windowsshim.test.ts index 50afcda98924a4..48f3de0b22998e 100644 --- a/test/cli/install/registry/bun-install-windowsshim.test.ts +++ b/test/cli/install/registry/bun-install-windowsshim.test.ts @@ -1,9 +1,8 @@ import { spawn } from "bun"; -import { bunExe, bunEnv as env, isWindows, mergeWindowEnvs } from "harness"; +import { bunExe, bunEnv as env, isWindows, mergeWindowEnvs, tmpdirSync } from "harness"; import { join } from "path"; -import { copyFileSync, mkdirSync, mkdtempSync, realpathSync } from "fs"; +import { copyFileSync, mkdirSync } from "fs"; import { writeFile } from "fs/promises"; -import { tmpdir } from "os"; import { test, expect, describe } from "bun:test"; // This test is to verify that BinLinkingShim.zig creates correct shim files as @@ -12,7 +11,7 @@ import { test, expect, describe } from "bun:test"; describe("windows bin linking shim should work", async () => { if (!isWindows) return; - const packageDir = mkdtempSync(join(realpathSync(tmpdir()), "bun-install-windowsshim-")); + const packageDir = tmpdirSync(); const port = 4873; await writeFile( diff --git a/test/cli/run/run-cjs.test.ts b/test/cli/run/run-cjs.test.ts index 4c0d3700256b1a..73584616a465e1 100644 --- a/test/cli/run/run-cjs.test.ts +++ b/test/cli/run/run-cjs.test.ts @@ -1,11 +1,10 @@ import { expect, test } from "bun:test"; -import { mkdirSync, realpathSync } from "fs"; -import { bunEnv, bunExe } from "harness"; -import { tmpdir } from "os"; +import { mkdirSync } from "fs"; +import { bunEnv, bunExe, tmpdirSync } from "harness"; import { join } from "path"; test("running a commonjs module works", async () => { - const dir = join(realpathSync(tmpdir()), "bun-run-test1"); + const dir = tmpdirSync(); mkdirSync(dir, { recursive: true }); await Bun.write(join(dir, "index1.js"), "module.exports = 1; console.log('hello world');"); let { stdout } = Bun.spawnSync({ diff --git a/test/cli/run/run-eval.test.ts b/test/cli/run/run-eval.test.ts index bd210f6c213b04..b4631edce294e0 100644 --- a/test/cli/run/run-eval.test.ts +++ b/test/cli/run/run-eval.test.ts @@ -1,7 +1,7 @@ import { SpawnOptions, Subprocess, SyncSubprocess } from "bun"; import { describe, expect, test } from "bun:test"; -import { mkdtempSync, writeFileSync, rmSync } from "fs"; -import { bunEnv, bunExe } from "harness"; +import { writeFileSync, rmSync } from "fs"; +import { bunEnv, bunExe, tmpdirSync } from "harness"; import { tmpdir } from "os"; import { join, sep, posix } from "path"; @@ -48,7 +48,7 @@ for (const flag of ["-e", "--print"]) { describe("--print for cjs/esm", () => { test("eval result between esm imports", async () => { - let cwd = mkdtempSync(join(tmpdir(), "bun-run-eval-test-")); + let cwd = tmpdirSync(); writeFileSync(join(cwd, "foo.js"), "'foo'"); writeFileSync(join(cwd, "bar.js"), "'bar'"); let { stdout, stderr, exitCode } = Bun.spawnSync({ diff --git a/test/cli/run/run-extensionless.test.ts b/test/cli/run/run-extensionless.test.ts index 5f4fee7c4be20d..4881a583825eaa 100644 --- a/test/cli/run/run-extensionless.test.ts +++ b/test/cli/run/run-extensionless.test.ts @@ -1,12 +1,11 @@ import { expect, test } from "bun:test"; -import { mkdirSync, realpathSync } from "fs"; -import { bunEnv, bunExe, isWindows } from "harness"; +import { mkdirSync } from "fs"; +import { bunEnv, bunExe, isWindows, tmpdirSync } from "harness"; import { writeFileSync } from "fs"; -import { tmpdir } from "os"; import { join } from "path"; test("running extensionless file works", async () => { - const dir = join(realpathSync(tmpdir()), "bun-run-test1"); + const dir = tmpdirSync(); mkdirSync(dir, { recursive: true }); await Bun.write(join(dir, "cool"), "const x: Test = 2; console.log('hello world');"); let { stdout } = Bun.spawnSync({ @@ -18,7 +17,7 @@ test("running extensionless file works", async () => { }); test.skipIf(isWindows)("running shebang typescript file works", async () => { - const dir = join(realpathSync(tmpdir()), "bun-run-test2"); + const dir = tmpdirSync(); mkdirSync(dir, { recursive: true }); writeFileSync(join(dir, "cool"), `#!${bunExe()}\nconst x: Test = 2; console.log('hello world');`, { mode: 0o777 }); diff --git a/test/cli/run/run-shell.test.ts b/test/cli/run/run-shell.test.ts index 2f24433345dde0..42857f01ec3342 100644 --- a/test/cli/run/run-shell.test.ts +++ b/test/cli/run/run-shell.test.ts @@ -1,11 +1,10 @@ import { expect, test } from "bun:test"; -import { mkdirSync, realpathSync } from "fs"; -import { bunEnv, bunExe } from "harness"; -import { tmpdir } from "os"; +import { mkdirSync } from "fs"; +import { bunEnv, bunExe, tmpdirSync } from "harness"; import { join } from "path"; test("running a shell script works", async () => { - const dir = join(realpathSync(tmpdir()), "bun-run-shell"); + const dir = tmpdirSync(); mkdirSync(dir, { recursive: true }); await Bun.write(join(dir, "something.sh"), "echo wah"); let { stdout, stderr } = Bun.spawnSync({ diff --git a/test/cli/run/transpiler-cache.test.ts b/test/cli/run/transpiler-cache.test.ts index 3c4b8f417fe14d..133946b3ff71a3 100644 --- a/test/cli/run/transpiler-cache.test.ts +++ b/test/cli/run/transpiler-cache.test.ts @@ -1,7 +1,7 @@ import { Subprocess } from "bun"; import { beforeEach, describe, expect, test } from "bun:test"; import { realpathSync, chmodSync, existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "fs"; -import { bunEnv, bunExe, bunRun } from "harness"; +import { bunEnv, bunExe, bunRun, tmpdirSync } from "harness"; import { tmpdir } from "os"; import { join } from "path"; @@ -50,7 +50,7 @@ beforeEach(() => { removeCache(); } - temp_dir = join(tmpdir(), `bun-test-transpiler-cache-${Date.now()}-` + (Math.random() * 81023).toString(36).slice(2)); + temp_dir = tmpdirSync(); mkdirSync(temp_dir, { recursive: true }); temp_dir = realpathSync(temp_dir); cache_dir = join(temp_dir, ".cache"); diff --git a/test/cli/test/bun-test.test.ts b/test/cli/test/bun-test.test.ts index 02f45bd170cc4f..c643eb347406f6 100644 --- a/test/cli/test/bun-test.test.ts +++ b/test/cli/test/bun-test.test.ts @@ -1,9 +1,9 @@ import { join, resolve, dirname } from "node:path"; import { tmpdir } from "node:os"; -import { mkdtempSync, writeFileSync, rmSync, mkdirSync } from "node:fs"; +import { writeFileSync, rmSync, mkdirSync } from "node:fs"; import { spawnSync } from "bun"; import { describe, test, expect } from "bun:test"; -import { bunExe, bunEnv } from "harness"; +import { bunExe, bunEnv, tmpdirSync } from "harness"; describe("bun test", () => { test("can provide no arguments", () => { @@ -889,7 +889,7 @@ describe("bun test", () => { }); function createTest(input?: string | (string | { filename: string; contents: string })[], filename?: string): string { - const cwd = mkdtempSync(join(tmpdir(), "bun-test-")); + const cwd = tmpdirSync(); const inputs = Array.isArray(input) ? input : [input ?? ""]; for (const input of inputs) { const contents = typeof input === "string" ? input : input.contents; diff --git a/test/cli/watch/watch.test.ts b/test/cli/watch/watch.test.ts index a4cf98aac5e9ff..64c24a47e94b5a 100644 --- a/test/cli/watch/watch.test.ts +++ b/test/cli/watch/watch.test.ts @@ -2,14 +2,13 @@ import { it, expect, afterEach } from "bun:test"; import type { Subprocess } from "bun"; import { spawn } from "bun"; import { join } from "node:path"; -import { tmpdir } from "node:os"; -import { mkdtempSync, writeFileSync, rmSync } from "node:fs"; -import { bunExe, bunEnv } from "harness"; +import { writeFileSync, rmSync } from "node:fs"; +import { bunExe, bunEnv, tmpdirSync } from "harness"; let watchee: Subprocess; it("should watch files", async () => { - const cwd = mkdtempSync(join(tmpdir(), "bun-test-")); + const cwd = tmpdirSync(); const path = join(cwd, "watchee.js"); const updateFile = (i: number) => { diff --git a/test/harness.ts b/test/harness.ts index 1ad159a19e419f..48f5ad2bed6410 100644 --- a/test/harness.ts +++ b/test/harness.ts @@ -760,6 +760,6 @@ export function mergeWindowEnvs(envs: Record[]) { return flat; } -export function tmpdirSync(pattern: string) { +export function tmpdirSync(pattern: string = "bun.test.") { return fs.mkdtempSync(join(fs.realpathSync(os.tmpdir()), pattern)); } diff --git a/test/integration/esbuild/esbuild.test.ts b/test/integration/esbuild/esbuild.test.ts index e154e6d62db5c0..d1259c1374082e 100644 --- a/test/integration/esbuild/esbuild.test.ts +++ b/test/integration/esbuild/esbuild.test.ts @@ -1,14 +1,12 @@ import { describe, expect, test } from "bun:test"; -import { rm, writeFile, mkdir, exists, cp } from "fs/promises"; -import { bunExe, bunEnv as env } from "harness"; -import { mkdtempSync, realpathSync } from "fs"; -import { tmpdir } from "os"; +import { rm, writeFile, cp } from "fs/promises"; +import { bunExe, bunEnv as env, tmpdirSync } from "harness"; import { join } from "path"; import { spawn } from "bun"; describe("esbuild integration test", () => { test("install and use esbuild", async () => { - const packageDir = mkdtempSync(join(realpathSync(tmpdir()), "bun-esbuild-test-")); + const packageDir = tmpdirSync(); await writeFile( join(packageDir, "package.json"), @@ -50,7 +48,7 @@ describe("esbuild integration test", () => { }); test("install and use estrella", async () => { - const packageDir = mkdtempSync(join(realpathSync(tmpdir()), "bun-ebuild-estrella-test-")); + const packageDir = tmpdirSync(); await writeFile( join(packageDir, "package.json"), diff --git a/test/integration/next-pages/test/dev-server-ssr-100.test.ts b/test/integration/next-pages/test/dev-server-ssr-100.test.ts index ff94f6bf8f4e25..1e186d5a7e74e0 100644 --- a/test/integration/next-pages/test/dev-server-ssr-100.test.ts +++ b/test/integration/next-pages/test/dev-server-ssr-100.test.ts @@ -1,5 +1,5 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; -import { bunEnv, bunExe, toMatchNodeModulesAt } from "../../../harness"; +import { bunEnv, bunExe, tmpdirSync, toMatchNodeModulesAt } from "../../../harness"; import { Subprocess } from "bun"; import { copyFileSync, rmSync } from "fs"; import { join } from "path"; @@ -8,11 +8,9 @@ import { cp, rm } from "fs/promises"; import { install_test_helpers } from "bun:internal-for-testing"; const { parseLockfile } = install_test_helpers; -import { tmpdir } from "node:os"; - expect.extend({ toMatchNodeModulesAt }); -let root = join(tmpdir(), "ssr" + Math.random().toString(36).slice(2, 4) + "-" + Date.now().toString(36).slice(2, 4)); +let root = tmpdirSync(); beforeAll(async () => { await rm(root, { recursive: true, force: true }); diff --git a/test/integration/next-pages/test/dev-server.test.ts b/test/integration/next-pages/test/dev-server.test.ts index bd0b16a4ad5bad..43b3dfe0589079 100644 --- a/test/integration/next-pages/test/dev-server.test.ts +++ b/test/integration/next-pages/test/dev-server.test.ts @@ -1,5 +1,5 @@ import { afterAll, beforeAll, expect, test } from "bun:test"; -import { bunEnv, bunExe, toMatchNodeModulesAt } from "../../../harness"; +import { bunEnv, bunExe, tmpdirSync, toMatchNodeModulesAt } from "../../../harness"; import { Subprocess } from "bun"; import { copyFileSync } from "fs"; import { join } from "path"; @@ -8,11 +8,9 @@ import { cp, rm } from "fs/promises"; import { install_test_helpers } from "bun:internal-for-testing"; const { parseLockfile } = install_test_helpers; -import { tmpdir } from "node:os"; - expect.extend({ toMatchNodeModulesAt }); -let root = join(tmpdir(), "next-pages" + Math.random().toString(36).slice(2) + "-" + Date.now().toString(36)); +let root = tmpdirSync(); beforeAll(async () => { await rm(root, { recursive: true, force: true }); diff --git a/test/integration/next-pages/test/next-build.test.ts b/test/integration/next-pages/test/next-build.test.ts index bdeebdfae76e64..e27f58a9d6ec7d 100644 --- a/test/integration/next-pages/test/next-build.test.ts +++ b/test/integration/next-pages/test/next-build.test.ts @@ -1,7 +1,6 @@ import { expect, test } from "bun:test"; -import { bunEnv, bunExe, isWindows, toMatchNodeModulesAt } from "../../../harness"; -import { copyFileSync, cpSync, mkdtempSync, readFileSync, rmSync, symlinkSync, promises as fs } from "fs"; -import { tmpdir } from "os"; +import { bunEnv, bunExe, tmpdirSync, toMatchNodeModulesAt } from "../../../harness"; +import { copyFileSync, cpSync, readFileSync, rmSync, promises as fs } from "fs"; import { join } from "path"; import { cp } from "fs/promises"; import { install_test_helpers } from "bun:internal-for-testing"; @@ -12,7 +11,7 @@ expect.extend({ toMatchNodeModulesAt }); const root = join(import.meta.dir, "../"); async function tempDirToBuildIn() { - const dir = mkdtempSync(join(tmpdir(), "bun-next-build-")); + const dir = tmpdirSync(); const copy = [ ".eslintrc.json", "bun.lockb", diff --git a/test/js/bun/glob/scan.test.ts b/test/js/bun/glob/scan.test.ts index 5a31016c298dd4..2ba0a18091b99b 100644 --- a/test/js/bun/glob/scan.test.ts +++ b/test/js/bun/glob/scan.test.ts @@ -25,7 +25,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import fg from "fast-glob"; import * as path from "path"; import { tempFixturesDir, createTempDirectoryWithBrokenSymlinks, prepareEntries } from "./util"; -import { tempDirWithFiles } from "harness"; +import { tempDirWithFiles, tmpdirSync } from "harness"; import * as os from "node:os"; import * as fs from "node:fs"; @@ -313,8 +313,7 @@ describe("fast-glob e2e tests", async () => { let absolute_pattern_dir: string = ""; // beforeAll(() => { tempFixturesDir(); - const tmp = os.tmpdir(); - absolute_pattern_dir = fs.mkdtempSync(path.join(tmp, "absolute_patterns")); + absolute_pattern_dir = tmpdirSync(); // add some more directories so patterns like ../**/* don't break absolute_pattern_dir = path.join(absolute_pattern_dir, "ooga/booga"); fs.mkdirSync(absolute_pattern_dir, { recursive: true })!; @@ -590,14 +589,9 @@ describe("literal fast path", async () => { }); }); -function makeTmpdir(): string { - const tmp = os.tmpdir(); - return fs.mkdtempSync(path.join(tmp, "test_builder")); -} - describe("trailing directory separator", async () => { test("matches directories absolute", async () => { - const tmpdir = makeTmpdir(); + const tmpdir = tmpdirSync(); const files = [`${tmpdir}${path.sep}bunx-foo`, `${tmpdir}${path.sep}bunx-bar`, `${tmpdir}${path.sep}bunx-baz`]; await Bun.$`touch ${files[0]}; touch ${files[1]}; mkdir ${files[2]}`; const glob = new Glob(`${path.join(tmpdir, "bunx-*")}${path.sep}`); @@ -606,7 +600,7 @@ describe("trailing directory separator", async () => { }); test("matches directories relative", async () => { - const tmpdir = makeTmpdir(); + const tmpdir = tmpdirSync(); const files = [`bunx-foo`, `bunx-bar`, `bunx-baz`]; await Bun.$`touch ${files[0]}; touch ${files[1]}; mkdir ${files[2]}`.cwd(tmpdir); const glob = new Glob(`bunx-*/`); @@ -617,7 +611,7 @@ describe("trailing directory separator", async () => { describe("absolute path pattern", async () => { test("works *", async () => { - const tmpdir = makeTmpdir(); + const tmpdir = tmpdirSync(); const files = [`${tmpdir}${path.sep}bunx-foo`, `${tmpdir}${path.sep}bunx-bar`, `${tmpdir}${path.sep}bunx-baz`]; await Bun.$`touch ${files[0]}; touch ${files[1]}; mkdir ${files[2]}`; const glob = new Glob(`${path.join(tmpdir, "bunx-*")}`); @@ -626,7 +620,7 @@ describe("absolute path pattern", async () => { }); test("works **/", async () => { - const tmpdir = makeTmpdir(); + const tmpdir = tmpdirSync(); const files = [ `${tmpdir}${path.sep}bunx-foo`, `${tmpdir}${path.sep}bunx-bar`, @@ -642,7 +636,7 @@ describe("absolute path pattern", async () => { }); test("works **", async () => { - const tmpdir = makeTmpdir(); + const tmpdir = tmpdirSync(); const files = [ `${tmpdir}${path.sep}bunx-foo`, `${tmpdir}${path.sep}bunx-bar`, @@ -664,7 +658,7 @@ describe("absolute path pattern", async () => { }); test("doesn't exist, file pattern", async () => { - const tmpdir = makeTmpdir(); + const tmpdir = tmpdirSync(); await Bun.$`mkdir -p hello/friends; touch hello/friends/lol.json; echo ${tmpdir}`.cwd(tmpdir); const glob = new Glob(`${tmpdir}/hello/friends/nice.json`); console.log(Array.from(glob.scanSync({ cwd: tmpdir }))); diff --git a/test/js/bun/glob/util.ts b/test/js/bun/glob/util.ts index 47b65c0e1bf7db..3fac0f7b06a2eb 100644 --- a/test/js/bun/glob/util.ts +++ b/test/js/bun/glob/util.ts @@ -1,10 +1,10 @@ -import { mkdtempSync, symlinkSync } from "fs"; +import { symlinkSync } from "fs"; import path from "path"; -import os from "os"; +import { tmpdirSync } from "harness"; export function createTempDirectoryWithBrokenSymlinks() { // Create a temporary directory - const tempDir = mkdtempSync(path.join(os.tmpdir(), "fixtures_symlink_")); + const tempDir = tmpdirSync(); // Define broken symlink targets (non-existent paths) const brokenTargets = ["non_existent_file.txt", "non_existent_dir"]; diff --git a/test/js/bun/http/serve-listen.test.ts b/test/js/bun/http/serve-listen.test.ts index 7ffad3e2e82f53..b75c6c91ae6b4e 100644 --- a/test/js/bun/http/serve-listen.test.ts +++ b/test/js/bun/http/serve-listen.test.ts @@ -1,15 +1,15 @@ import { describe, test, expect } from "bun:test"; import { file, serve } from "bun"; import type { NetworkInterfaceInfo } from "node:os"; -import { tmpdir, networkInterfaces } from "node:os"; -import { mkdtempSync } from "node:fs"; +import { networkInterfaces } from "node:os"; import { join } from "node:path"; +import { tmpdirSync } from "harness"; const networks = Object.values(networkInterfaces()).flat() as NetworkInterfaceInfo[]; const hasIPv4 = networks.some(({ family }) => family === "IPv4"); const hasIPv6 = networks.some(({ family }) => family === "IPv6"); -const unix = join(mkdtempSync(join(tmpdir(), "bun-serve-")), "unix.sock"); +const unix = join(tmpdirSync(), "unix.sock"); const tls = { cert: file(new URL("./fixtures/cert.pem", import.meta.url)), key: file(new URL("./fixtures/cert.key", import.meta.url)), diff --git a/test/js/bun/shell/bunshell.test.ts b/test/js/bun/shell/bunshell.test.ts index a3f3320da81bff..81af1b17c1f542 100644 --- a/test/js/bun/shell/bunshell.test.ts +++ b/test/js/bun/shell/bunshell.test.ts @@ -6,9 +6,8 @@ */ import { $ } from "bun"; import { afterAll, beforeAll, describe, expect, test } from "bun:test"; -import { mkdir, mkdtemp, realpath, rm, stat } from "fs/promises"; -import { bunEnv, bunExe, runWithErrorPromise, tempDirWithFiles } from "harness"; -import { tmpdir } from "os"; +import { mkdir, rm, stat } from "fs/promises"; +import { bunEnv, bunExe, runWithErrorPromise, tempDirWithFiles, tmpdirSync } from "harness"; import { join, sep } from "path"; import { createTestBuilder, sortedShellOutput } from "./util"; const TestBuilder = createTestBuilder(import.meta.path); @@ -21,7 +20,7 @@ let temp_dir: string; const temp_files = ["foo.txt", "lmao.ts"]; beforeAll(async () => { $.nothrow(); - temp_dir = await mkdtemp(join(await realpath(tmpdir()), "bun-add.test")); + temp_dir = tmpdirSync(); await mkdir(temp_dir, { recursive: true }); for (const file of temp_files) { @@ -742,7 +741,56 @@ ${temp_dir}` /** * */ - describe("escaping", () => {}); + describe("escaping", () => { + // Testing characters that need special handling when not quoted or in different contexts + TestBuilder.command`echo ${"$"}`.stdout("$\n").runAsTest("dollar"); + TestBuilder.command`echo ${">"}`.stdout(">\n").runAsTest("right_arrow"); + TestBuilder.command`echo ${"&"}`.stdout("&\n").runAsTest("ampersand"); + TestBuilder.command`echo ${"|"}`.stdout("|\n").runAsTest("pipe"); + TestBuilder.command`echo ${"="}`.stdout("=\n").runAsTest("equals"); + TestBuilder.command`echo ${";"}`.stdout(";\n").runAsTest("semicolon"); + TestBuilder.command`echo ${"\n"}`.stdout("\n\n").runAsTest("newline"); + TestBuilder.command`echo ${"{"}`.stdout("{\n").runAsTest("left_brace"); + TestBuilder.command`echo ${"}"}`.stdout("}\n").runAsTest("right_brace"); + TestBuilder.command`echo ${","}`.stdout(",\n").runAsTest("comma"); + TestBuilder.command`echo ${"("}`.stdout("(\n").runAsTest("left_parenthesis"); + TestBuilder.command`echo ${")"}`.stdout(")\n").runAsTest("right_parenthesis"); + TestBuilder.command`echo ${"\\"}`.stdout("\\\n").runAsTest("backslash"); + TestBuilder.command`echo ${" "}`.stdout(" \n").runAsTest("space"); + TestBuilder.command`echo ${"'hello'"}`.stdout("'hello'\n").runAsTest("single_quote"); + TestBuilder.command`echo ${'"hello"'}`.stdout('"hello"\n').runAsTest("double_quote"); + TestBuilder.command`echo ${"`hello`"}`.stdout("`hello`\n").runAsTest("backtick"); + + // Testing characters that need to be escaped within double quotes + TestBuilder.command`echo "${"$"}"`.stdout("$\n").runAsTest("dollar_in_dquotes"); + TestBuilder.command`echo "${"`"}"`.stdout("`\n").runAsTest("backtick_in_dquotes"); + TestBuilder.command`echo "${'"'}"`.stdout('"\n').runAsTest("double_quote_in_dquotes"); + TestBuilder.command`echo "${"\\"}"`.stdout("\\\n").runAsTest("backslash_in_dquotes"); + + // Testing characters that need to be escaped within single quotes + TestBuilder.command`echo '${"$"}'`.stdout("$\n").runAsTest("dollar_in_squotes"); + TestBuilder.command`echo '${'"'}'`.stdout('"\n').runAsTest("double_quote_in_squotes"); + TestBuilder.command`echo '${"`"}'`.stdout("`\n").runAsTest("backtick_in_squotes"); + TestBuilder.command`echo '${"\\\\"}'`.stdout("\\\\\n").runAsTest("backslash_in_squotes"); + + // Ensure that backslash escapes within single quotes are treated literally + TestBuilder.command`echo '${"\\"}'`.stdout("\\\n").runAsTest("literal_backslash_single_quote"); + TestBuilder.command`echo '${"\\\\"}'`.stdout("\\\\\n").runAsTest("double_backslash_single_quote"); + + // Edge cases with mixed quotes + TestBuilder.command`echo "'\${"$"}'"`.stdout("'${$}'\n").runAsTest("mixed_quotes_dollar"); + TestBuilder.command`echo '"${"`"}"'`.stdout('"`"\n').runAsTest("mixed_quotes_backtick"); + + // Compound command with special characters + TestBuilder.command`echo ${"hello; echo world"}`.stdout("hello; echo world\n").runAsTest("compound_command"); + TestBuilder.command`echo ${"hello > world"}`.stdout("hello > world\n").runAsTest("redirect_in_echo"); + TestBuilder.command`echo ${"$(echo nested)"}`.stdout("$(echo nested)\n").runAsTest("nested_command_substitution"); + + // Pathological cases involving multiple special characters + TestBuilder.command`echo ${"complex > command; $(execute)"}` + .stdout("complex > command; $(execute)\n") + .runAsTest("complex_mixed_special_chars"); + }); }); describe("deno_task", () => { diff --git a/test/js/bun/shell/env.positionals.test.ts b/test/js/bun/shell/env.positionals.test.ts index 2a87c303612066..93846badab3af9 100644 --- a/test/js/bun/shell/env.positionals.test.ts +++ b/test/js/bun/shell/env.positionals.test.ts @@ -9,7 +9,7 @@ $.nothrow(); describe("$ argv", async () => { for (let i = 0; i < process.argv.length; i++) { const element = process.argv[i]; - TestBuilder.command`echo $${i}` + TestBuilder.command`echo $${{ raw: i }}` .exitCode(0) .stdout(process.argv[i] + "\n") .runAsTest(`$${i} should equal process.argv[${i}]`); diff --git a/test/js/bun/spawn/spawn.test.ts b/test/js/bun/spawn/spawn.test.ts index 7875082c52eeec..f1f949b9c3ff45 100644 --- a/test/js/bun/spawn/spawn.test.ts +++ b/test/js/bun/spawn/spawn.test.ts @@ -1,16 +1,22 @@ import { ArrayBufferSink, readableStreamToText, spawn, spawnSync, write } from "bun"; import { beforeAll, describe, expect, it } from "bun:test"; import { closeSync, fstatSync, openSync } from "fs"; -import { gcTick as _gcTick, bunEnv, bunExe, isLinux, isMacOS, isPosix, isWindows, withoutAggressiveGC } from "harness"; -import { mkdirSync, rmSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; +import { + gcTick as _gcTick, + bunEnv, + bunExe, + isMacOS, + isPosix, + isWindows, + tmpdirSync, + withoutAggressiveGC, +} from "harness"; +import { rmSync, writeFileSync } from "node:fs"; import path from "path"; let tmp; beforeAll(() => { - tmp = path.join(tmpdir(), "bun-spawn-" + Date.now().toString(32)) + path.sep; - rmSync(tmp, { force: true, recursive: true }); - mkdirSync(tmp, { recursive: true }); + tmp = tmpdirSync(); }); function createHugeString() { diff --git a/test/js/bun/test/snapshot-tests/new-snapshot.test.ts b/test/js/bun/test/snapshot-tests/new-snapshot.test.ts index 51aa48a1368aab..1a9fd5cf7a616f 100644 --- a/test/js/bun/test/snapshot-tests/new-snapshot.test.ts +++ b/test/js/bun/test/snapshot-tests/new-snapshot.test.ts @@ -1,11 +1,9 @@ import fs from "fs"; -import { bunExe } from "harness"; -import { tmpdir } from "os"; - +import { bunExe, tmpdirSync } from "harness"; import { it, test, expect, describe } from "bun:test"; test("it will create a snapshot file and directory if they don't exist", () => { - const tempDir = tmpdir() + "/new-snapshot"; + const tempDir = tmpdirSync(); fs.rmSync(tempDir, { force: true, recursive: true }); fs.mkdirSync(tempDir, { recursive: true }); diff --git a/test/js/bun/test/test-test.test.ts b/test/js/bun/test/test-test.test.ts index b91fe56833b39b..0cd48042764ec7 100644 --- a/test/js/bun/test/test-test.test.ts +++ b/test/js/bun/test/test-test.test.ts @@ -2,8 +2,8 @@ import { spawn, spawnSync } from "bun"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, test } from "bun:test"; import { mkdirSync, realpathSync, rmSync, writeFileSync, copyFileSync } from "fs"; -import { mkdtemp, rm, writeFile } from "fs/promises"; -import { bunEnv, bunExe } from "harness"; +import { rm, writeFile } from "fs/promises"; +import { bunEnv, bunExe, tmpdirSync } from "harness"; import { tmpdir } from "os"; import { join, dirname } from "path"; @@ -27,7 +27,7 @@ it("shouldn't crash when async test runner callback throws", async () => { }) `; - const test_dir = await mkdtemp(join(tmp, "test")); + const test_dir = tmpdirSync(); try { await writeFile(join(test_dir, "bad.test.js"), code); const { stdout, stderr, exited } = spawn({ @@ -259,7 +259,7 @@ test("test async exceptions fail tests", () => { }); `; - const dir = join(tmp, "test-throwing-bun"); + const dir = tmpdirSync(); const filepath = join(dir, "test-throwing-eventemitter.test.js"); rmSync(filepath, { force: true, @@ -289,7 +289,7 @@ test("test async exceptions fail tests", () => { }); it("should return non-zero exit code for invalid syntax", async () => { - const test_dir = await mkdtemp(join(tmp, "test")); + const test_dir = tmpdirSync(); try { await writeFile(join(test_dir, "bad.test.js"), "!!!"); const { stdout, stderr, exited } = spawn({ @@ -314,7 +314,7 @@ it("should return non-zero exit code for invalid syntax", async () => { }); it("invalid syntax counts towards bail", async () => { - const test_dir = await mkdtemp(join(tmp, "test")); + const test_dir = tmpdirSync(); try { await writeFile(join(test_dir, "bad1.test.js"), "!!!"); await writeFile(join(test_dir, "bad2.test.js"), "!!!"); @@ -616,7 +616,7 @@ it("skip() and skipIf()", () => { }); it("should run beforeAll() & afterAll() even without tests", async () => { - const test_dir = await mkdtemp(join(tmp, "test-hooks-empty")); + const test_dir = tmpdirSync(); try { await writeFile( join(test_dir, "empty.test.js"), diff --git a/test/js/bun/util/filesink.test.ts b/test/js/bun/util/filesink.test.ts index 1c94107cc85932..f00529ae39fd76 100644 --- a/test/js/bun/util/filesink.test.ts +++ b/test/js/bun/util/filesink.test.ts @@ -1,8 +1,6 @@ -import { ArrayBufferSink } from "bun"; import { describe, expect, it } from "bun:test"; -import { isWindows } from "harness"; +import { isWindows, tmpdirSync } from "harness"; import { mkfifo } from "mkfifo"; -import { tmpdir } from "node:os"; import { join } from "node:path"; describe("FileSink", () => { @@ -45,7 +43,7 @@ describe("FileSink", () => { }); function getPath(label: string) { - const path = join(tmpdir(), `bun-test-${Bun.hash(label).toString(10)}.${(Math.random() * 1_000_000) | 0}.txt`); + const path = join(tmpdirSync(), `${Bun.hash(label).toString(10)}.txt`); try { require("fs").unlinkSync(path); } catch (e) {} @@ -56,7 +54,7 @@ describe("FileSink", () => { var decoder = new TextDecoder(); function getFd(label: string, byteLength = 0) { - const path = join(tmpdir(), `bun-test-${Bun.hash(label).toString(10)}.${(Math.random() * 1_000_000) | 0}.txt`); + const path = join(tmpdirSync(), `${Bun.hash(label).toString(10)}.txt`); try { require("fs").unlinkSync(path); } catch (e) {} diff --git a/test/js/bun/util/filesystem_router.test.ts b/test/js/bun/util/filesystem_router.test.ts index a9aa29bf0f82dc..09a644d2100aad 100644 --- a/test/js/bun/util/filesystem_router.test.ts +++ b/test/js/bun/util/filesystem_router.test.ts @@ -1,9 +1,8 @@ import { FileSystemRouter } from "bun"; import { it, expect } from "bun:test"; -import path, { dirname, resolve } from "path"; -import fs, { mkdirSync, realpathSync, rmSync } from "fs"; -import { tmpdir } from "os"; -const tempdir = realpathSync(tmpdir()) + "/"; +import path, { dirname } from "path"; +import fs, { mkdirSync, rmSync } from "fs"; +import { tmpdirSync } from "harness"; function createTree(basedir: string, paths: string[]) { for (const end of paths) { @@ -17,7 +16,7 @@ function createTree(basedir: string, paths: string[]) { } var count = 0; function make(files: string[]) { - const dir = (tempdir + `fs-router-test-${count++}`).replace(/\\/g, "/"); + const dir = tmpdirSync(); rmSync(dir, { recursive: true, force: true, diff --git a/test/js/bun/util/which.test.ts b/test/js/bun/util/which.test.ts index edb4670e32a2b8..95e770a5a6dce2 100644 --- a/test/js/bun/util/which.test.ts +++ b/test/js/bun/util/which.test.ts @@ -5,7 +5,7 @@ import { rmSync, chmodSync, mkdirSync, realpathSync } from "node:fs"; import { join, basename } from "node:path"; import { tmpdir } from "node:os"; import { rmdirSync } from "js/node/fs/export-star-from"; -import { isIntelMacOS, isWindows, tempDirWithFiles } from "harness"; +import { isIntelMacOS, isWindows, tempDirWithFiles, tmpdirSync } from "harness"; import { w } from "vitest/dist/types-2b1c412e.js"; $.nothrow(); @@ -51,7 +51,7 @@ if (isWindows) { } } - let basedir = join(tmpdir(), "which-test-" + Math.random().toString(36).slice(2)); + let basedir = tmpdirSync(); rmSync(basedir, { recursive: true, force: true }); mkdirSync(basedir, { recursive: true }); diff --git a/test/js/node/child_process/child_process.test.ts b/test/js/node/child_process/child_process.test.ts index 63e74fc7e1b898..da82266615fe84 100644 --- a/test/js/node/child_process/child_process.test.ts +++ b/test/js/node/child_process/child_process.test.ts @@ -382,7 +382,7 @@ it("should call close and exit before process exits", async () => { }); it("it accepts stdio passthrough", async () => { - const package_dir = tmpdirSync("bun-node-child_process"); + const package_dir = tmpdirSync(); await fs.promises.writeFile( path.join(package_dir, "package.json"), diff --git a/test/js/node/crypto/crypto.test.ts b/test/js/node/crypto/crypto.test.ts index c20cb52990014b..7222a7d6b86625 100644 --- a/test/js/node/crypto/crypto.test.ts +++ b/test/js/node/crypto/crypto.test.ts @@ -200,7 +200,7 @@ describe("crypto.createSign()/.verifySign()", () => { }); it("should send cipher events in the right order", async () => { - const package_dir = tmpdirSync("bun-test-node-stream"); + const package_dir = tmpdirSync(); const fixture_path = path.join(package_dir, "fixture.js"); await Bun.write( diff --git a/test/js/node/fs/fs.test.ts b/test/js/node/fs/fs.test.ts index 34dbf03580e6d6..8b0e93600cf460 100644 --- a/test/js/node/fs/fs.test.ts +++ b/test/js/node/fs/fs.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, spyOn } from "bun:test"; import { dirname, resolve, relative } from "node:path"; import { promisify } from "node:util"; -import { bunEnv, bunExe, gc, getMaxFD, isIntelMacOS, isWindows, tempDirWithFiles } from "harness"; +import { bunEnv, bunExe, gc, getMaxFD, isIntelMacOS, isWindows, tempDirWithFiles, tmpdirSync } from "harness"; import { isAscii } from "node:buffer"; import fs, { closeSync, @@ -253,7 +253,7 @@ it("Dirent.name setter", () => { }); it("writeFileSync should correctly resolve ../..", () => { - const base = join(tmpdir(), `fs-test-${Math.random().toString(36).slice(2)}`); + const base = tmpdirSync(); const path = join(base, "foo", "bar"); mkdirSync(path, { recursive: true }); const cwd = process.cwd(); @@ -264,7 +264,7 @@ it("writeFileSync should correctly resolve ../..", () => { }); it("writeFileSync in append should not truncate the file", () => { - const path = join(tmpdir(), "writeFileSync-should-not-append-" + (Date.now() * 10000).toString(16)); + const path = join(tmpdirSync(), "should-not-append.txt"); var str = ""; writeFileSync(path, "---BEGIN---"); str += "---BEGIN---"; @@ -286,7 +286,7 @@ it("await readdir #3931", async () => { }); it("writeFileSync NOT in append SHOULD truncate the file", () => { - const path = join(tmpdir(), "writeFileSync-should-not-append-" + (Date.now() * 10000).toString(16)); + const path = join(tmpdirSync(), "should-not-append.txt"); for (let options of [{ flag: "w" }, { flag: undefined }, {}, undefined]) { writeFileSync(path, "---BEGIN---", options); @@ -482,9 +482,7 @@ it("readdirSync on import.meta.dir", () => { }); it("promises.readdir on a large folder", async () => { - const huge = join(tmpdir(), "huge-folder-" + Math.random().toString(32)); - rmSync(huge, { force: true, recursive: true }); - mkdirSync(huge, { recursive: true }); + const huge = tmpdirSync(); for (let i = 0; i < 128; i++) { writeFileSync(join(huge, "file-" + i), ""); } @@ -819,9 +817,7 @@ it("promises.readFile with buffer as file path", async () => { }); it("promises.readdir on a large folder withFileTypes", async () => { - const huge = join(tmpdir(), "huge-folder-" + Math.random().toString(32)); - rmSync(huge, { force: true, recursive: true }); - mkdirSync(huge, { recursive: true }); + const huge = tmpdirSync(); let withFileTypes = { withFileTypes: true } as const; for (let i = 0; i < 128; i++) { writeFileSync(join(huge, "file-" + i), ""); @@ -954,14 +950,12 @@ it("readdirSync on import.meta.dir with trailing slash", () => { }); it("readdirSync works on empty directories", () => { - const path = `${tmpdir()}/fs-test-empty-dir-${(Math.random() * 100000 + 100).toString(32)}`; - mkdirSync(path, { recursive: true }); + const path = tmpdirSync(); expect(readdirSync(path).length).toBe(0); }); it("readdirSync works on directories with under 32 files", () => { - const path = `${tmpdir()}/fs-test-one-dir-${(Math.random() * 100000 + 100).toString(32)}`; - mkdirSync(path, { recursive: true }); + const path = tmpdirSync(); writeFileSync(`${path}/a`, "a"); const results = readdirSync(path); expect(results.length).toBe(1); @@ -1263,13 +1257,13 @@ describe("readFile", () => { describe("writeFileSync", () => { it("works", () => { - const path = `${tmpdir()}/${Date.now()}.writeFileSync.txt`; + const path = `${tmpdirSync()}/writeFileSync.txt`; writeFileSync(path, "File written successfully", "utf8"); expect(readFileSync(path, "utf8")).toBe("File written successfully"); }); it("write file with mode, issue #3740", () => { - const path = `${tmpdir()}/${Date.now()}.writeFileSyncWithMode.txt`; + const path = `${tmpdirSync()}/writeFileSyncWithMode.txt`; writeFileSync(path, "bun", { mode: 33188 }); const stat = fs.statSync(path); expect(stat.mode).toBe(isWindows ? 33206 : 33188); @@ -1279,7 +1273,7 @@ describe("writeFileSync", () => { 70, 105, 108, 101, 32, 119, 114, 105, 116, 116, 101, 110, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108, 121, ]); - const path = `${tmpdir()}/${Date.now()}.blob.writeFileSync.txt`; + const path = `${tmpdirSync()}/blob.writeFileSync.txt`; writeFileSync(path, buffer); const out = readFileSync(path); @@ -1292,7 +1286,7 @@ describe("writeFileSync", () => { 70, 105, 108, 101, 32, 119, 114, 105, 116, 116, 101, 110, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108, 121, ]); - const path = `${tmpdir()}/${Date.now()}.blob2.writeFileSync.txt`; + const path = `${tmpdirSync()}/blob2.writeFileSync.txt`; writeFileSync(path, buffer); const out = readFileSync(path); @@ -1335,7 +1329,7 @@ describe("lstat", () => { }); it("symlink metadata is correct", () => { - const link = join(tmpdir(), `fs-stream.link${Math.random().toString(32)}.js`); + const link = join(tmpdirSync(), `fs-stream.link.js`); symlinkSync(join(import.meta.dir, "fs-stream.js"), link); const linkStats = lstatSync(link); expect(linkStats.isSymbolicLink()).toBe(true); @@ -1349,7 +1343,7 @@ describe("lstat", () => { }); it("symlink", () => { - const actual = join(tmpdir(), Math.random().toString(32) + "-fs-symlink.txt"); + const actual = join(tmpdirSync(), "fs-symlink.txt"); try { unlinkSync(actual); } catch (e) {} @@ -1360,7 +1354,7 @@ it("symlink", () => { }); it("readlink", () => { - const actual = join(tmpdir(), Math.random().toString(32) + "-fs-readlink.txt"); + const actual = join(tmpdirSync(), "fs-readlink.txt"); try { unlinkSync(actual); } catch (e) {} @@ -1371,7 +1365,7 @@ it("readlink", () => { }); it.if(isWindows)("symlink on windows with forward slashes", async () => { - const r = join(tmpdir(), Math.random().toString(32)); + const r = tmpdirSync(); await fs.promises.rm(join(r, "files/2024"), { recursive: true, force: true }); await fs.promises.mkdir(join(r, "files/2024"), { recursive: true }); await fs.promises.writeFile(join(r, "files/2024/123.txt"), "text"); @@ -1380,7 +1374,7 @@ it.if(isWindows)("symlink on windows with forward slashes", async () => { }); it("realpath async", async () => { - const actual = join(tmpdir(), Math.random().toString(32) + "-fs-realpath.txt"); + const actual = join(tmpdirSync(), "fs-realpath.txt"); try { unlinkSync(actual); } catch (e) {} @@ -1453,7 +1447,7 @@ describe("stat", () => { it("stat returns ENOENT", () => { try { - statSync("${tmpdir()}/doesntexist"); + statSync(`${tmpdir()}/doesntexist`); throw "statSync should throw"; } catch (e: any) { expect(e.code).toBe("ENOENT"); @@ -2619,6 +2613,21 @@ describe("utimesSync", () => { expect(finalStats.mtime).toEqual(prevModifiedTime); expect(finalStats.atime).toEqual(prevAccessTime); }); + + it("works with whole numbers", () => { + const atime = Math.floor(Date.now() / 1000); + const mtime = Math.floor(Date.now() / 1000); + + const tmp = join(tmpdir(), "utimesSync-test-file-" + Math.random().toString(36).slice(2)); + writeFileSync(tmp, "test"); + + fs.utimesSync(tmp, atime, mtime); + + const newStats = fs.statSync(tmp); + + expect(newStats.mtime.getTime() / 1000).toEqual(mtime); + expect(newStats.atime.getTime() / 1000).toEqual(atime); + }); }); it("createReadStream on a large file emits readable event correctly", () => { diff --git a/test/js/node/http/node-http.test.ts b/test/js/node/http/node-http.test.ts index 736fc46af6e170..2ed842f175916d 100644 --- a/test/js/node/http/node-http.test.ts +++ b/test/js/node/http/node-http.test.ts @@ -983,24 +983,6 @@ describe("node:http", () => { }); }); - test("test server internal error, issue#4298", done => { - const server = createServer((req, res) => { - throw Error("throw an error here."); - }); - server.listen({ port: 0 }, async (_err, host, port) => { - try { - await fetch(`http://${host}:${port}`).then(res => { - expect(res.status).toBe(500); - done(); - }); - } catch (err) { - done(err); - } finally { - server.close(); - } - }); - }); - test("test unix socket server", done => { const socketPath = `${tmpdir()}/bun-server-${Math.random().toString(32)}.sock`; const server = createServer((req, res) => { diff --git a/test/js/node/net/node-net.test.ts b/test/js/node/net/node-net.test.ts index 3badca5c55d3a6..6fa7758875301d 100644 --- a/test/js/node/net/node-net.test.ts +++ b/test/js/node/net/node-net.test.ts @@ -1,12 +1,10 @@ import { ServerWebSocket, TCPSocket, Socket as _BunSocket, TCPSocketListener } from "bun"; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "bun:test"; +import { describe, expect, it } from "bun:test"; import { connect, isIP, isIPv4, isIPv6, Socket, createConnection, Server } from "net"; -import { realpathSync, mkdtempSync } from "fs"; -import { tmpdir } from "os"; import { join } from "path"; -import { bunEnv, bunExe } from "harness"; +import { bunEnv, bunExe, tmpdirSync } from "harness"; -const socket_domain = mkdtempSync(join(realpathSync(tmpdir()), "node-net")); +const socket_domain = tmpdirSync(); it("should support net.isIP()", () => { expect(isIP("::1")).toBe(6); diff --git a/test/js/node/stream/node-stream.test.js b/test/js/node/stream/node-stream.test.js index bee68e3b81deb6..54c6f039909dab 100644 --- a/test/js/node/stream/node-stream.test.js +++ b/test/js/node/stream/node-stream.test.js @@ -1,4 +1,4 @@ -import { expect, describe, it } from "bun:test"; +import { expect, describe, it, jest } from "bun:test"; import { Stream, Readable, Writable, Duplex, Transform, PassThrough } from "node:stream"; import { createReadStream } from "node:fs"; import { join } from "path"; @@ -533,7 +533,7 @@ it("#9242.10 PassThrough has constructor", () => { }); it("should send Readable events in the right order", async () => { - const package_dir = tmpdirSync("bun-test-node-stream"); + const package_dir = tmpdirSync(); const fixture_path = join(package_dir, "fixture.js"); await Bun.write( @@ -588,3 +588,13 @@ it("should send Readable events in the right order", async () => { ``, ]); }); + +it("emits newListener event _before_ adding the listener", () => { + const cb = jest.fn(event => { + expect(stream.listenerCount(event)).toBe(0); + }); + const stream = new Stream(); + stream.on("newListener", cb); + stream.on("foo", () => {}); + expect(cb).toHaveBeenCalled(); +}); diff --git a/test/js/node/tls/node-tls-connect.test.ts b/test/js/node/tls/node-tls-connect.test.ts index 3bcf49db01835d..189590314643f0 100644 --- a/test/js/node/tls/node-tls-connect.test.ts +++ b/test/js/node/tls/node-tls-connect.test.ts @@ -2,6 +2,8 @@ import tls, { TLSSocket, connect, checkServerIdentity, createServer, Server } fr import { join } from "path"; import { AddressInfo } from "ws"; +const symbolConnectOptions = Symbol.for("::buntlsconnectoptions::"); + it("should work with alpnProtocols", done => { try { let socket: TLSSocket | null = connect({ @@ -210,3 +212,81 @@ it("should have checkServerIdentity", async () => { expect(checkServerIdentity).toBeFunction(); expect(tls.checkServerIdentity).toBeFunction(); }); + +// Test using only options +it("should process options correctly when connect is called with only options", done => { + let socket = connect({ + port: 443, + host: "bun.sh", + rejectUnauthorized: false, + }); + + socket.on("secureConnect", () => { + expect(socket.remotePort).toBe(443); + expect(socket[symbolConnectOptions].serverName).toBe("bun.sh"); + socket.end(); + done(); + }); + + socket.on("error", err => { + socket.end(); + done(err); + }); +}); + +// Test using port and host +it("should process port and host correctly", done => { + let socket = connect(443, "bun.sh", { + rejectUnauthorized: false, + }); + + socket.on("secureConnect", () => { + expect(socket.remotePort).toBe(443); + expect(socket[symbolConnectOptions].serverName).toBe("bun.sh"); + socket.end(); + done(); + }); + + socket.on("error", err => { + socket.end(); + done(err); + }); +}); + +// Test using port, host, and callback +it("should process port, host, and callback correctly", done => { + let socket = connect( + 443, + "bun.sh", + { + rejectUnauthorized: false, + }, + () => { + expect(socket.remotePort).toBe(443); + expect(socket[symbolConnectOptions].serverName).toBe("bun.sh"); + socket.end(); + done(); + }, + ).on("error", err => { + done(err); + }); +}); + +// Additional tests to ensure the callback is optional and handled correctly +it("should handle the absence of a callback gracefully", done => { + let socket = connect(443, "bun.sh", { + rejectUnauthorized: false, + }); + + socket.on("secureConnect", () => { + expect(socket[symbolConnectOptions].serverName).toBe("bun.sh"); + expect(socket.remotePort).toBe(443); + socket.end(); + done(); + }); + + socket.on("error", err => { + socket.end(); + done(err); + }); +}); diff --git a/test/js/node/zlib/zlib.test.js b/test/js/node/zlib/zlib.test.js index 3276774f8fd7bc..e1dc8d46e14600 100644 --- a/test/js/node/zlib/zlib.test.js +++ b/test/js/node/zlib/zlib.test.js @@ -153,7 +153,7 @@ describe("zlib.brotli", () => { }); it("fully works as a stream.Transform", async () => { - const x_dir = tmpdirSync("bun.test."); + const x_dir = tmpdirSync(); const out_path_c = resolve(x_dir, "this.js.br"); const out_path_d = resolve(x_dir, "this.js"); diff --git a/test/js/third_party/azure-service-bus.test.ts b/test/js/third_party/azure-service-bus.test.ts new file mode 100644 index 00000000000000..54641134e8f010 --- /dev/null +++ b/test/js/third_party/azure-service-bus.test.ts @@ -0,0 +1,58 @@ +import { bunExe } from "bun:harness"; +import { bunEnv, tmpdirSync } from "harness"; +import { expect, it } from "bun:test"; +import * as path from "node:path"; + +// prettier-ignore +it.skipIf(!bunEnv.TEST_INFO_AZURE_SERVICE_BUS)("works", async () => { + const package_dir = tmpdirSync("bun-test-"); + + let { stdout, stderr, exited } = Bun.spawn({ + cmd: [bunExe(), "add", "@azure/service-bus@7.9.4"], + cwd: package_dir, + stdout: "pipe", + stdin: "ignore", + stderr: "pipe", + env: bunEnv, + }); + let err = await new Response(stderr).text(); + expect(err).not.toContain("panic:"); + expect(err).not.toContain("error:"); + expect(err).not.toContain("warn:"); + let out = await new Response(stdout).text(); + expect(await exited).toBe(0); + + const fixture_path = path.join(package_dir, "index.ts"); + const fixture_data = ` + import { ServiceBusClient } from "@azure/service-bus"; + + const connectionString = "${bunEnv.TEST_INFO_AZURE_SERVICE_BUS}"; + const sbClient = new ServiceBusClient(connectionString); + const sender = sbClient.createSender("test"); + + try { + await sender.sendMessages({ body: "Hello, world!" }); + console.log("Message sent"); + await sender.close(); + } finally { + await sbClient.close(); + } + `; + await Bun.write(fixture_path, fixture_data); + + ({ stdout, stderr, exited } = Bun.spawn({ + cmd: [bunExe(), "run", fixture_path], + cwd: package_dir, + stdout: "pipe", + stdin: "ignore", + stderr: "pipe", + env: bunEnv, + })); + err = await new Response(stderr).text(); + expect(err).toBeEmpty(); + out = await new Response(stdout).text(); + expect(out).toEqual("Message sent\n"); + expect(await exited).toBe(0); +}, 10_000); +// this takes ~4s locally so increase the time to try and ensure its +// not flaky in a higher pressure environment diff --git a/test/js/third_party/st.test.ts b/test/js/third_party/st.test.ts index 9b738213859818..f77b4e552eb202 100644 --- a/test/js/third_party/st.test.ts +++ b/test/js/third_party/st.test.ts @@ -1,11 +1,10 @@ import { bunExe } from "bun:harness"; import { bunEnv, tmpdirSync } from "harness"; -import { createTest } from "node-harness"; -const { describe, expect, it, beforeAll, afterAll, createDoneDotAll } = createTest(import.meta.path); +import { expect, it } from "bun:test"; import * as path from "node:path"; it("works", async () => { - const package_dir = tmpdirSync("bun-test-"); + const package_dir = tmpdirSync(); let { stdout, stderr, exited } = Bun.spawn({ cmd: [bunExe(), "add", "st@3.0.0"], @@ -20,10 +19,10 @@ it("works", async () => { expect(err).not.toContain("error:"); expect(err).not.toContain("warn:"); let out = await new Response(stdout).text(); - await exited; + expect(await exited).toBe(0); const fixture_path = path.join(package_dir, "index.ts"); - const fixture_data = String.raw` + const fixture_data = ` import { createServer } from "node:http"; import st from "st"; @@ -36,7 +35,6 @@ it("works", async () => { resolve(new URL("http://"+hostname+":"+port)); } }); - setTimeout(() => reject("Timed out"), 1000); }); } await using server = createServer(st(process.cwd())); @@ -46,7 +44,7 @@ it("works", async () => { `; await Bun.write(fixture_path, fixture_data); - ({ stdout, stderr } = Bun.spawn({ + ({ stdout, stderr, exited } = Bun.spawn({ cmd: [bunExe(), "run", fixture_path], cwd: package_dir, stdout: "pipe", @@ -58,4 +56,5 @@ it("works", async () => { // expect(err).toBeEmpty(); out = await new Response(stdout).text(); expect(out).toEqual(fixture_data + "\n"); + expect(await exited).toBe(0); }); diff --git a/test/js/third_party/stripe.test.ts b/test/js/third_party/stripe.test.ts index d73eff7f58cef8..4c400afe54468e 100644 --- a/test/js/third_party/stripe.test.ts +++ b/test/js/third_party/stripe.test.ts @@ -1,11 +1,10 @@ import { bunExe } from "bun:harness"; import { bunEnv, tmpdirSync } from "harness"; import * as path from "node:path"; -import { createTest } from "node-harness"; -const { describe, expect, it, beforeAll, afterAll, createDoneDotAll } = createTest(import.meta.path); +import { expect, it } from "bun:test"; it.skipIf(!process.env.TEST_INFO_STRIPE)("should be able to query a charge", async () => { - const package_dir = tmpdirSync("bun-test-"); + const package_dir = tmpdirSync(); await Bun.write( path.join(package_dir, "package.json"), @@ -18,6 +17,7 @@ it.skipIf(!process.env.TEST_INFO_STRIPE)("should be able to query a charge", asy let { stdout, stderr } = Bun.spawn({ cmd: [bunExe(), "install"], + cwd: package_dir, stdout: "pipe", stdin: "ignore", stderr: "pipe", @@ -51,6 +51,7 @@ it.skipIf(!process.env.TEST_INFO_STRIPE)("should be able to query a charge", asy ({ stdout, stderr } = Bun.spawn({ cmd: [bunExe(), "run", fixture_path], + cwd: package_dir, stdout: "pipe", stdin: "ignore", stderr: "pipe", diff --git a/test/js/web/abort/abort.test.ts b/test/js/web/abort/abort.test.ts index ef0b07a18d0aa8..46c1f12f3ef946 100644 --- a/test/js/web/abort/abort.test.ts +++ b/test/js/web/abort/abort.test.ts @@ -1,15 +1,15 @@ import { describe, test, expect } from "bun:test"; -import { bunExe, bunEnv } from "harness"; +import { bunExe, bunEnv, tmpdirSync } from "harness"; import { writeFileSync } from "fs"; import { join } from "path"; import { tmpdir } from "os"; describe("AbortSignal", () => { test("spawn test", async () => { - const fileName = `/abort-${Date.now()}.test.ts`; + const fileName = `/abort.test.ts`; const testFileContents = await Bun.file(join(import.meta.dir, "abort.ts")).arrayBuffer(); - writeFileSync(join(tmpdir(), fileName), testFileContents, "utf8"); + writeFileSync(join(tmpdirSync(), fileName), testFileContents, "utf8"); const { stderr } = Bun.spawnSync({ cmd: [bunExe(), "test", fileName], env: bunEnv, diff --git a/test/js/web/fetch/fetch.test.ts b/test/js/web/fetch/fetch.test.ts index e54df89e89cdf5..2b87b097f7598c 100644 --- a/test/js/web/fetch/fetch.test.ts +++ b/test/js/web/fetch/fetch.test.ts @@ -1,14 +1,13 @@ import { AnyFunction, serve, ServeOptions, Server, sleep, TCPSocketListener } from "bun"; import { afterAll, afterEach, beforeAll, describe, expect, it, beforeEach } from "bun:test"; -import { chmodSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "fs"; +import { chmodSync, readFileSync, rmSync, writeFileSync } from "fs"; import { mkfifo } from "mkfifo"; -import { tmpdir } from "os"; import { gzipSync } from "zlib"; import { join } from "path"; -import { gc, withoutAggressiveGC, gcTick, isWindows } from "harness"; +import { gc, withoutAggressiveGC, gcTick, isWindows, tmpdirSync } from "harness"; import net from "net"; -const tmp_dir = mkdtempSync(join(realpathSync(tmpdir()), "fetch.test")); +const tmp_dir = tmpdirSync(); const fixture = readFileSync(join(import.meta.dir, "fetch.js.txt"), "utf8").replaceAll("\r\n", "\n"); diff --git a/test/js/web/fetch/fetch.unix.test.ts b/test/js/web/fetch/fetch.unix.test.ts index be401f512dffee..193e102e736aea 100644 --- a/test/js/web/fetch/fetch.unix.test.ts +++ b/test/js/web/fetch/fetch.unix.test.ts @@ -1,11 +1,10 @@ import { serve, ServeOptions, Server } from "bun"; import { afterAll, afterEach, expect, it } from "bun:test"; -import { mkdtempSync, realpathSync, rmSync } from "fs"; -import { tmpdir } from "os"; +import { rmSync } from "fs"; import { join } from "path"; import { request } from "http"; -import { isWindows } from "harness"; -const tmp_dir = join(realpathSync(tmpdir())); +import { isWindows, tmpdirSync } from "harness"; +const tmp_dir = tmpdirSync(); it("throws ENAMETOOLONG when socket path exceeds platform-specific limit", () => { // this must be the filename specifically, because we add a workaround for the length limit on linux diff --git a/test/node.js/runner.mjs b/test/node.js/runner.mjs index c508f971e47622..2ec6bcde12a0a0 100644 --- a/test/node.js/runner.mjs +++ b/test/node.js/runner.mjs @@ -140,7 +140,7 @@ async function runTests(options, filters) { } const { pathname: filePath } = new URL(filename, testPath); - const tmp = mkdtempSync(join(tmpdir(), "bun-")); + const tmp = tmpdirSync(); const timestamp = Date.now(); const { status: exitCode, diff --git a/test/regression/issue/00631.test.ts b/test/regression/issue/00631.test.ts index 643f5ed98fa592..920911a9083427 100644 --- a/test/regression/issue/00631.test.ts +++ b/test/regression/issue/00631.test.ts @@ -1,11 +1,10 @@ import { expect, it } from "bun:test"; -import { bunExe, bunEnv } from "../../harness.js"; -import { mkdirSync, rmSync, writeFileSync, readFileSync, mkdtempSync } from "fs"; -import { tmpdir } from "os"; +import { bunExe, bunEnv, tmpdirSync } from "../../harness.js"; +import { mkdirSync, rmSync, writeFileSync, readFileSync } from "fs"; import { join } from "path"; it("JSON strings escaped properly", async () => { - const testDir = mkdtempSync(join(tmpdir(), "issue631-")); + const testDir = tmpdirSync(); // Clean up from prior runs if necessary rmSync(testDir, { recursive: true, force: true }); diff --git a/test/regression/issue/03216.test.ts b/test/regression/issue/03216.test.ts index ffa3c53c3cb629..29ed854a983f27 100644 --- a/test/regression/issue/03216.test.ts +++ b/test/regression/issue/03216.test.ts @@ -1,12 +1,11 @@ // https://github.com/oven-sh/bun/issues/3216 import { test, expect } from "bun:test"; -import { tmpdir } from "os"; -import { mkdtempSync, writeFileSync } from "fs"; +import { writeFileSync } from "fs"; import { join } from "path"; -import { bunEnv, bunExe } from "harness"; +import { bunEnv, bunExe, tmpdirSync } from "harness"; test("runtime directory caching gets invalidated", () => { - const tmp = mkdtempSync(join(tmpdir(), "bun-test-")); + const tmp = tmpdirSync(); writeFileSync( join(tmp, "index.ts"), `const file = \`\${import.meta.dir}/temp.mjs\`; diff --git a/test/regression/issue/03830.test.ts b/test/regression/issue/03830.test.ts index 87427b525d9fd7..8da5ffdcfd0c67 100644 --- a/test/regression/issue/03830.test.ts +++ b/test/regression/issue/03830.test.ts @@ -1,14 +1,13 @@ import { it, expect } from "bun:test"; -import { bunEnv, bunExe } from "harness"; -import { mkdirSync, rmSync, writeFileSync, readFileSync, mkdtempSync, realpathSync } from "fs"; -import { tmpdir } from "os"; +import { bunEnv, bunExe, tmpdirSync } from "harness"; +import { mkdirSync, rmSync, writeFileSync, realpathSync } from "fs"; import { join } from "path"; it("macros should not lead to seg faults under any given input", async () => { // this test code follows the same structure as and // is based on the code for testing issue 4893 - let testDir = mkdtempSync(join(tmpdir(), "issue3830-")); + let testDir = tmpdirSync(); // Clean up from prior runs if necessary rmSync(testDir, { recursive: true, force: true }); diff --git a/test/regression/issue/04298/04298.fixture.js b/test/regression/issue/04298/04298.fixture.js new file mode 100644 index 00000000000000..77161560d6f823 --- /dev/null +++ b/test/regression/issue/04298/04298.fixture.js @@ -0,0 +1,15 @@ +import { createServer } from "node:http"; +import { isIPv6 } from "node:net"; + +const server = createServer((req, res) => { + throw new Error("Oops!"); +}); + +server.listen({ port: 0 }, async (err, host, port) => { + if (err) { + console.error(err); + process.exit(1); + } + const hostname = isIPv6(host) ? `[${host}]` : host; + process.send(`http://${hostname}:${port}/`); +}); diff --git a/test/regression/issue/04298/04298.test.ts b/test/regression/issue/04298/04298.test.ts new file mode 100644 index 00000000000000..cbf1598575398a --- /dev/null +++ b/test/regression/issue/04298/04298.test.ts @@ -0,0 +1,24 @@ +import { test, expect } from "bun:test"; +import { spawn } from "bun"; +import { bunExe, bunEnv } from "harness"; + +test("node:http should not crash when server throws", async () => { + const { promise, resolve, reject } = Promise.withResolvers(); + await using server = spawn({ + cwd: import.meta.dirname, + cmd: [bunExe(), "04298.fixture.js"], + env: bunEnv, + stderr: "pipe", + ipc(url) { + resolve(url); + }, + onExit(exitCode, signalCode) { + if (signalCode || exitCode !== 0) { + reject(new Error(`process exited with code ${signalCode || exitCode}`)); + } + }, + }); + const url = await promise; + const response = await fetch(url); + expect(response.status).toBe(500); +}); diff --git a/test/regression/issue/04893.test.ts b/test/regression/issue/04893.test.ts index 43850693bef085..d784f69c7b99a7 100644 --- a/test/regression/issue/04893.test.ts +++ b/test/regression/issue/04893.test.ts @@ -1,10 +1,10 @@ -import { bunEnv, bunExe } from "harness"; -import { mkdirSync, rmSync, writeFileSync, readFileSync, mkdtempSync } from "fs"; -import { tmpdir } from "os"; +import { bunEnv, bunExe, tmpdirSync } from "harness"; +import { mkdirSync, rmSync, writeFileSync } from "fs"; import { join } from "path"; +import { it, expect } from "bun:test"; it("correctly handles CRLF multiline string in CRLF terminated files", async () => { - const testDir = mkdtempSync(join(tmpdir(), "issue4893-")); + const testDir = tmpdirSync(); // Clean up from prior runs if necessary rmSync(testDir, { recursive: true, force: true }); diff --git a/test/regression/issue/07261.test.ts b/test/regression/issue/07261.test.ts index 0d522377aa4f4b..d0c7b3e38721fb 100644 --- a/test/regression/issue/07261.test.ts +++ b/test/regression/issue/07261.test.ts @@ -1,10 +1,10 @@ -import { bunEnv, bunExe } from "harness"; -import { mkdirSync, rmSync, writeFileSync, mkdtempSync } from "fs"; -import { tmpdir } from "os"; +import { bunEnv, bunExe, tmpdirSync } from "harness"; +import { mkdirSync, rmSync, writeFileSync } from "fs"; import { join } from "path"; +import { it, expect } from "bun:test"; it("imports tsconfig.json with abritary keys", async () => { - const testDir = mkdtempSync(join(tmpdir(), "issue7261-")); + const testDir = tmpdirSync(); // Clean up from prior runs if necessary rmSync(testDir, { recursive: true, force: true }); diff --git a/test/regression/issue/07500.test.ts b/test/regression/issue/07500.test.ts index 00095e685a5165..0ba5316e743745 100644 --- a/test/regression/issue/07500.test.ts +++ b/test/regression/issue/07500.test.ts @@ -1,9 +1,9 @@ import { test, expect } from "bun:test"; -import { bunEnv, bunExe, isWindows } from "harness"; +import { bunEnv, bunExe, isWindows, tmpdirSync } from "harness"; import { tmpdir } from "os"; import { join } from "path"; test("7500 - Bun.stdin.text() doesn't read all data", async () => { - const filename = join(tmpdir(), "bun.test.offset." + Date.now() + ".txt"); + const filename = join(tmpdirSync(), "bun.test.offset.txt"); const text = "contents of file to be read with several lines of text and lots and lots and lots and lots of bytes! " .repeat(1000) .repeat(9) diff --git a/test/regression/issue/08757.test.ts b/test/regression/issue/08757.test.ts index f577dddb258d00..ac414c914ebe3a 100644 --- a/test/regression/issue/08757.test.ts +++ b/test/regression/issue/08757.test.ts @@ -1,7 +1,6 @@ import { test, expect } from "bun:test"; -import { mkdtempSync, symlinkSync } from "fs"; -import { tmpdir } from "os"; -import { bunRun } from "../../harness"; +import { symlinkSync } from "fs"; +import { bunRun, tmpdirSync } from "../../harness"; if (process.env.IS_SUBPROCESS) { console.log(process.argv[1]); @@ -14,7 +13,7 @@ if (process.env.IS_SUBPROCESS) { } test("absolute path to a file that is symlinked has import.meta.main", () => { - const root = mkdtempSync(tmpdir() + "/bun-08757-"); + const root = tmpdirSync(); try { symlinkSync(process.argv[1], root + "/main.js"); } catch (e) { diff --git a/test/transpiler/transpiler-stack-overflow.test.ts b/test/transpiler/transpiler-stack-overflow.test.ts index 1501292b2f05f7..7db3d7f3cff448 100644 --- a/test/transpiler/transpiler-stack-overflow.test.ts +++ b/test/transpiler/transpiler-stack-overflow.test.ts @@ -1,15 +1,11 @@ import { test, expect } from "bun:test"; import { writeFileSync, mkdirSync } from "node:fs"; import { join } from "path"; -import { tmpdir } from "node:os"; -import { bunEnv, bunExe } from "harness"; +import { bunEnv, bunExe, tmpdirSync } from "harness"; test("long chain of expressions does not cause stack overflow", () => { const chain = `globalThis.a = {};` + "\n" + `globalThis.a + globalThis.a +`.repeat(1000000) + `globalThis.a` + "\n"; - const temp_dir = join( - tmpdir(), - `bun-test-transpiler-cache-${Date.now()}-` + (Math.random() * 81023).toString(36).slice(2), - ); + const temp_dir = tmpdirSync(); mkdirSync(temp_dir, { recursive: true }); writeFileSync(join(temp_dir, "index.js"), chain, "utf-8"); const { exitCode } = Bun.spawnSync({