From f7e4eb83694aa007a492ef66c28ffbe6a2dae791 Mon Sep 17 00:00:00 2001 From: Ashcon Partovi Date: Tue, 7 Mar 2023 12:22:34 -0800 Subject: Reorganize tests (#2332) --- test/.prettierignore | 1 + test/README.md | 166 +- test/apps/bun-create-next.sh | 57 - test/apps/bun-create-react.sh | 20 - test/apps/bun-dev-index-html.sh | 69 - test/apps/bun-dev.sh | 55 - test/apps/bun-init-check.sh | 26 - test/apps/bun-install-lockfile-status.sh | 65 - test/apps/bun-install-utf8.sh | 14 - test/apps/bun-install.sh | 89 - test/apps/bun-run-check-nameless-package.json | 6 - test/apps/bun-run-check-package.json | 7 - test/apps/bun-run-check.sh | 67 - test/bun.js/.prettierignore | 2 - test/bun.js/FormData.test.ts | 410 --- test/bun.js/abort-signal-timeout.test.js | 12 - test/bun.js/arraybuffersink.test.ts | 64 - test/bun.js/assert-test.test.ts | 11 - test/bun.js/atob.test.js | 77 - test/bun.js/bash-echo.sh | 3 - test/bun.js/baz.js | 2 - test/bun.js/bigint.test.js | 14 - test/bun.js/body-mixin-errors.test.ts | 17 - test/bun.js/body-stream.test.ts | 451 ---- test/bun.js/buffer.test.js | 2563 ------------------ test/bun.js/bufferlist.test.ts | 221 -- test/bun.js/bun-jsc.test.js | 108 - test/bun.js/bun-loader-svelte/bun-loader-svelte.ts | 18 - test/bun.js/bun-loader-svelte/package.json | 4 - test/bun.js/bun-server.test.ts | 171 -- test/bun.js/bun-spawn-test.js | 21 - test/bun.js/bun-streams-test-fifo.sh | 8 - test/bun.js/bun-test.test.ts | 6 - test/bun.js/bun-test/jest-hooks.test.ts | 195 -- test/bun.js/bun-test/nested-describes.test.ts | 34 - test/bun.js/bun-write.test.js | 274 -- test/bun.js/bun.lockb | Bin 9454 -> 0 bytes test/bun.js/bunEnv.ts | 6 - test/bun.js/bunExe.ts | 3 - .../always-bundled-module/always-bundled-module | 1 - test/bun.js/bundled/always-bundled-module/cjs.js | 10 - test/bun.js/bundled/always-bundled-module/esm.js | 5 - .../bundled/always-bundled-module/package.json | 4 - test/bun.js/bundled/entrypoint.ts | 13 - test/bun.js/bundled/package.json | 12 - .../to_bundle_node_modules/i-am-bundled/cjs.js | 10 - .../to_bundle_node_modules/i-am-bundled/esm.js | 5 - .../i-am-bundled/package.json | 4 - test/bun.js/bundled/tsconfig.json | 6 - test/bun.js/capture-stack-trace.test.js | 303 --- test/bun.js/child-process-stdio.test.js | 113 - test/bun.js/child_process-node.test.js | 482 ---- test/bun.js/child_process.test.ts | 347 --- test/bun.js/concat.test.js | 44 - test/bun.js/console/console-iterator-run.js | 3 - test/bun.js/console/console-iterator.test.js | 63 - test/bun.js/console/console-log.expected.txt | 46 - test/bun.js/console/console-log.js | 54 - test/bun.js/console/console-log.test.ts | 20 - test/bun.js/crypto-scrypt.test.js | 232 -- test/bun.js/crypto.test.js | 106 - test/bun.js/decorators.test.ts | 959 ------- test/bun.js/dirname.test.js | 9 - test/bun.js/disabled-module.test.js | 38 - test/bun.js/dns.node.mjs | 0 test/bun.js/empty.js | 1 - test/bun.js/emptyFile | 0 test/bun.js/esbuild-child_process.test.ts | 17 - test/bun.js/esbuild-test.js | 37 - test/bun.js/escapeHTML.test.js | 92 - test/bun.js/esm/first.mjs | 8 - test/bun.js/esm/second-child.mjs | 5 - test/bun.js/esm/second.mjs | 7 - test/bun.js/esm/startEnd.mjs | 6 - test/bun.js/esm/third.mjs | 4 - test/bun.js/event-emitter.test.ts | 169 -- test/bun.js/exit-code-0.js | 1 - test/bun.js/exit-code-1.js | 1 - test/bun.js/exit-code-await-throw-1.js | 3 - test/bun.js/exit-code-unhandled-throw.js | 3 - test/bun.js/exit-code.test.ts | 23 - test/bun.js/exit.js | 2 - test/bun.js/fetch-gzip.test.ts | 181 -- test/bun.js/fetch.js.txt | 46 - test/bun.js/fetch.test.ts | 935 ------- test/bun.js/fetch_headers.test.js | 66 - test/bun.js/ffi-test.c | 143 - test/bun.js/ffi.test.fixture.callback.c | 296 --- test/bun.js/ffi.test.fixture.receiver.c | 295 --- test/bun.js/ffi.test.js | 613 ----- test/bun.js/file-importing-nonexistent-file.js | 1 - test/bun.js/fileUrl.test.js | 16 - test/bun.js/filesink.test.ts | 141 - test/bun.js/filesystem_router.test.ts | 354 --- test/bun.js/fixture.html | 1428 ---------- test/bun.js/fixture.html.gz | Bin 16139 -> 0 bytes test/bun.js/form-data-fixture.txt | 1 - test/bun.js/fs-stream.js | 21 - test/bun.js/fs-stream.link.js | 1 - test/bun.js/fs.test.ts | 1016 ------- test/bun.js/gc.ts | 26 - test/bun.js/globals.test.js | 51 - test/bun.js/google-fixture.html | 632 ----- test/bun.js/hash.test.js | 47 - test/bun.js/hello-wasi.wasm | Bin 131 -> 0 bytes test/bun.js/hello.svelte | 5 - test/bun.js/hello2.svelte | 5 - test/bun.js/hot-runner-imported.js | 3 - test/bun.js/hot-runner.js | 6 - test/bun.js/hot.test.ts | 251 -- test/bun.js/html-rewriter.test.js | 303 --- test/bun.js/import-meta.test.js | 164 -- test/bun.js/import-require-tla.js | 7 - test/bun.js/import.live.decl.js | 4 - test/bun.js/import.live.rexport-require.js | 1 - test/bun.js/import.live.rexport.js | 2 - test/bun.js/index-of-line.test.ts | 37 - test/bun.js/inline.macro.js | 23 - test/bun.js/inspect.test.js | 315 --- test/bun.js/install/bad-workspace.test.ts | 35 - test/bun.js/install/bar-0.0.2.tgz | Bin 192 -> 0 bytes test/bun.js/install/baz-0.0.3.tgz | Bin 283 -> 0 bytes test/bun.js/install/baz-0.0.5.tgz | Bin 286 -> 0 bytes test/bun.js/install/bun-add.test.ts | 981 ------- test/bun.js/install/bun-install.test.ts | 2758 -------------------- test/bun.js/install/bun-link.test.ts | 347 --- test/bun.js/install/bun-pm.test.ts | 248 -- test/bun.js/install/bunx.test.ts | 209 -- test/bun.js/install/dummy.registry.ts | 94 - test/bun.js/install/moo-0.1.0.tgz | Bin 197 -> 0 bytes test/bun.js/jest-doesnt-auto-import.js | 12 - test/bun.js/log-test.test.ts | 54 - test/bun.js/macro-check.js | 7 - test/bun.js/microtask.test.js | 74 - test/bun.js/mkfifo.ts | 22 - test/bun.js/mmap.test.js | 69 - test/bun.js/module-require.snapshot.js | 1 - test/bun.js/node-builtins.test.js | 18 - test/bun.js/node-crypto.test.js | 29 - test/bun.js/node-dns.test.js | 148 -- test/bun.js/node-http.test.ts | 593 ----- test/bun.js/node-module-module.test.js | 5 - test/bun.js/node-stream-uint8array.test.ts | 111 - test/bun.js/node-stream.test.js | 86 - test/bun.js/node-test-helpers.test.js | 169 -- test/bun.js/node-test-helpers.ts | 202 -- test/bun.js/node-timers.test.ts | 17 - test/bun.js/northwind.testdb | Bin 602112 -> 0 bytes test/bun.js/os.test.js | 150 -- test/bun.js/package.json | 10 - test/bun.js/path.test.js | 426 --- test/bun.js/peek.test.ts | 42 - test/bun.js/performance.test.js | 22 - test/bun.js/plugins.d.ts | 11 - test/bun.js/plugins.test.ts | 311 --- test/bun.js/preload-test.test.js | 227 -- test/bun.js/print-process-args.js | 4 - test/bun.js/process-args.test.js | 40 - test/bun.js/process-nexttick.js | 84 - test/bun.js/process-nexttick.test.js | 99 - test/bun.js/process-stdin-echo.js | 11 - test/bun.js/process-stdio.test.ts | 128 - test/bun.js/process.test.js | 184 -- test/bun.js/proxy.test.js | 69 - test/bun.js/react-dom-server.bun.cjs | 2670 ------------------- test/bun.js/react-dom.test.tsx | 285 -- test/bun.js/readFileSync.txt | 1 - test/bun.js/readdir.js | 9 - test/bun.js/readline.node.test.ts | 2001 -------------- test/bun.js/readline_promises.node.test.ts | 51 - test/bun.js/reportError.test.js | 25 - test/bun.js/repro_2005.test.js | 11 - test/bun.js/repro_631.test.js | 36 - test/bun.js/require-js-top-level-await.js | 1 - test/bun.js/require-js.js | 2 - test/bun.js/require-js2.js | 1 - test/bun.js/require-json.json | 3 - test/bun.js/require-referenceerror.snapshot.js | 5 - test/bun.js/resolve-dns.test.ts | 46 - test/bun.js/resolve-error.test.ts | 11 - test/bun.js/resolve-typescript-file.tsx | 1 - test/bun.js/resolve.test.js | 229 -- test/bun.js/serve.test.ts | 981 ------- test/bun.js/setImmediate.test.js | 47 - test/bun.js/setInterval.test.js | 61 - test/bun.js/setTimeout.test.js | 173 -- test/bun.js/shadow.test.js | 10 - test/bun.js/sleep.js | 10 - test/bun.js/sleepSync.test.ts | 32 - test/bun.js/snapshots.debug.js | 7 - test/bun.js/snapshots.js | 7 - test/bun.js/socket/echo.js | 76 - test/bun.js/socket/node-net.test.ts | 268 -- test/bun.js/socket/socket.test.ts | 130 - test/bun.js/some-fs.js | 45 - test/bun.js/spawn-streaming-stdin.test.ts | 59 - test/bun.js/spawn-streaming-stdout-repro.js | 5 - test/bun.js/spawn-streaming-stdout.test.ts | 44 - test/bun.js/spawn.test.ts | 381 --- test/bun.js/spawned-child.js | 29 - test/bun.js/sql-raw.test.js | 55 - test/bun.js/sqlite-cross-process.js | 45 - test/bun.js/sqlite.test.js | 529 ---- test/bun.js/stdin-repro.js | 10 - test/bun.js/stdio-test-instance-a-lot.js | 19 - test/bun.js/stdio-test-instance.js | 5 - test/bun.js/streams.test.js | 630 ----- test/bun.js/string-decoder.test.js | 243 -- test/bun.js/tcp-server.test.ts | 222 -- test/bun.js/test-auto-import-jest-globals.test.js | 24 - test/bun.js/test-png-import.test.js | 7 - test/bun.js/test-png.png | 0 test/bun.js/test-programs/bigpipe-r.c | 26 - test/bun.js/test-programs/bigpipe-w.c | 26 - test/bun.js/test-test.test.ts | 2099 --------------- test/bun.js/test-util-types.test.js | 240 -- test/bun.js/test_scope_debug.ts | 172 -- test/bun.js/text-decoder.test.js | 243 -- test/bun.js/text-encoder.test.js | 281 -- test/bun.js/third-party/body-parser-test/bun.lockb | Bin 19805 -> 0 bytes .../express-body-parser-test.test.ts | 59 - .../third-party/body-parser-test/package.json | 9 - .../third-party/napi_create_external/bun.lockb | Bin 1496 -> 0 bytes .../napi-create-external.test.ts | 194 -- .../third-party/napi_create_external/package.json | 12 - test/bun.js/tiny-typed-emitter.snapshot.js | 7 - test/bun.js/toml-fixture.toml | 39 - test/bun.js/toml.test.js | 26 - test/bun.js/transpiler.test.js | 2294 ---------------- test/bun.js/tsconfig.json | 12 - test/bun.js/undici.test.ts | 140 - test/bun.js/unsafe.test.js | 51 - test/bun.js/url.test.ts | 137 - test/bun.js/utf8-encoding-fixture.bin | Bin 4456448 -> 0 bytes test/bun.js/util/util-promisify.test.js | 304 --- test/bun.js/util/util.test.js | 269 -- test/bun.js/wasi.test.js | 15 - test/bun.js/wasm-return-1-test.zig | 5 - test/bun.js/wasm.js | 1 - test/bun.js/web-crypto.test.ts | 91 - test/bun.js/web-globals.test.js | 156 -- test/bun.js/websocket-server.test.ts | 963 ------- test/bun.js/websocket-subprocess.ts | 13 - test/bun.js/websocket.test.js | 266 -- test/bun.js/which.test.ts | 65 - test/bun.js/writeFileSync.txt | 1 - test/bun.js/zlib.test.js | 39 - test/bun.lockb | Bin 0 -> 31909 bytes test/bundler/decorators.test.ts | 959 +++++++ test/bundler/inline.macro.js | 23 + test/bundler/macro-check.js | 7 + test/bundler/transpiler.test.js | 2294 ++++++++++++++++ test/cli/hot/hot-runner-imported.js | 3 + test/cli/hot/hot-runner.js | 6 + test/cli/hot/hot.test.ts | 250 ++ test/cli/install/bad-workspace.test.ts | 34 + test/cli/install/bar-0.0.2.tgz | Bin 0 -> 192 bytes test/cli/install/baz-0.0.3.tgz | Bin 0 -> 283 bytes test/cli/install/baz-0.0.5.tgz | Bin 0 -> 286 bytes test/cli/install/bun-add.test.ts | 980 +++++++ test/cli/install/bun-install.test.ts | 2757 +++++++++++++++++++ test/cli/install/bun-link.test.ts | 346 +++ test/cli/install/bun-pm.test.ts | 247 ++ test/cli/install/bunx.test.ts | 208 ++ test/cli/install/dummy.registry.ts | 94 + test/cli/install/moo-0.1.0.tgz | Bin 0 -> 197 bytes test/cli/run/log-test.test.ts | 53 + test/cli/run/preload-test.test.js | 226 ++ test/fixtures/bun-link-pkg-fixture/.gitignore | 2 - .../bun-link-pkg-fixture/bun-link-pkg-fixture.js | 12 - test/fixtures/bun-link-pkg-fixture/bun.lockb | Bin 1191 -> 0 bytes .../bun-link-pkg-fixture/package-lock.json | 30 - test/fixtures/bun-link-pkg-fixture/package.json | 11 - test/fixtures/bun-link-to-pkg-fixture/.gitignore | 169 -- test/fixtures/bun-link-to-pkg-fixture/README.md | 15 - test/fixtures/bun-link-to-pkg-fixture/bun.lockb | Bin 1527 -> 0 bytes test/fixtures/bun-link-to-pkg-fixture/index.js | 1 - test/fixtures/bun-link-to-pkg-fixture/package.json | 12 - .../fixtures/bun-link-to-pkg-fixture/tsconfig.json | 14 - .../export-lazy-fs-streams/export-*-from.ts | 1 - .../fixtures/export-lazy-fs-streams/export-from.ts | 1 - test/harness.ts | 37 + test/js/bun/console/console-iterator-run.js | 3 + test/js/bun/console/console-iterator.test.js | 63 + test/js/bun/dns/resolve-dns.test.ts | 46 + test/js/bun/ffi/ffi-test.c | 143 + test/js/bun/ffi/ffi.test.fixture.callback.c | 293 +++ test/js/bun/ffi/ffi.test.fixture.receiver.c | 295 +++ test/js/bun/ffi/ffi.test.js | 618 +++++ test/js/bun/globals.test.js | 51 + test/js/bun/http/bun-server.test.ts | 171 ++ test/js/bun/http/fetch.js.txt | 46 + test/js/bun/http/fixture.html.gz | Bin 0 -> 16139 bytes test/js/bun/http/proxy.test.js | 69 + test/js/bun/http/serve.leak.ts | 29 + test/js/bun/http/serve.test.ts | 981 +++++++ test/js/bun/io/bun-streams-test-fifo.sh | 8 + test/js/bun/io/bun-write.test.js | 274 ++ test/js/bun/io/emptyFile | 0 test/js/bun/io/fetch.js.txt | 46 + test/js/bun/jsc/bun-jsc.test.js | 108 + test/js/bun/jsc/shadow.test.js | 10 + test/js/bun/net/echo.js | 76 + test/js/bun/net/socket.test.ts | 130 + test/js/bun/net/tcp-server.test.ts | 222 ++ test/js/bun/plugin/hello.svelte | 5 + test/js/bun/plugin/hello2.svelte | 5 + test/js/bun/plugin/plugins.d.ts | 11 + test/js/bun/plugin/plugins.test.ts | 311 +++ test/js/bun/resolve/baz.js | 2 + .../bun/resolve/file-importing-nonexistent-file.js | 1 + test/js/bun/resolve/first.mjs | 8 + test/js/bun/resolve/import-meta.test.js | 164 ++ test/js/bun/resolve/import-require-tla.js | 7 + test/js/bun/resolve/import.live.decl.js | 4 + test/js/bun/resolve/import.live.rexport-require.js | 1 + test/js/bun/resolve/import.live.rexport.js | 2 + test/js/bun/resolve/png/test-png-import.test.js | 7 + test/js/bun/resolve/png/test-png.png | 0 test/js/bun/resolve/require-js-top-level-await.js | 1 + test/js/bun/resolve/require-js.js | 2 + test/js/bun/resolve/require-js2.js | 1 + test/js/bun/resolve/require-json.json | 3 + .../bun/resolve/require-referenceerror.snapshot.js | 5 + test/js/bun/resolve/resolve-error.test.ts | 11 + test/js/bun/resolve/resolve-typescript-file.tsx | 1 + test/js/bun/resolve/resolve.test.js | 229 ++ test/js/bun/resolve/second-child.mjs | 5 + test/js/bun/resolve/second.mjs | 7 + test/js/bun/resolve/startEnd.mjs | 6 + test/js/bun/resolve/third.mjs | 4 + test/js/bun/resolve/toml/toml-fixture.toml | 39 + test/js/bun/resolve/toml/toml.test.js | 26 + test/js/bun/spawn/bash-echo.sh | 3 + test/js/bun/spawn/bun-spawn-test.js | 21 + test/js/bun/spawn/exit-code-0.js | 1 + test/js/bun/spawn/exit-code-1.js | 1 + test/js/bun/spawn/exit-code-await-throw-1.js | 3 + test/js/bun/spawn/exit-code-unhandled-throw.js | 3 + test/js/bun/spawn/exit-code.test.ts | 23 + test/js/bun/spawn/exit.js | 2 + test/js/bun/spawn/spawn-streaming-stdin.test.ts | 57 + test/js/bun/spawn/spawn-streaming-stdout-repro.js | 5 + test/js/bun/spawn/spawn-streaming-stdout.test.ts | 42 + test/js/bun/spawn/spawn.test.ts | 380 +++ test/js/bun/spawn/stdin-repro.js | 10 + test/js/bun/spawn/stdio-test-instance-a-lot.js | 19 + test/js/bun/spawn/stdio-test-instance.js | 5 + test/js/bun/sqlite/northwind.testdb | Bin 0 -> 602112 bytes test/js/bun/sqlite/sql-raw.test.js | 55 + test/js/bun/sqlite/sqlite-cross-process.js | 45 + test/js/bun/sqlite/sqlite.test.js | 529 ++++ test/js/bun/test/bigint.test.js | 14 + test/js/bun/test/bun-test.test.ts | 6 + test/js/bun/test/jest-doesnt-auto-import.js | 12 + test/js/bun/test/jest-hooks.test.ts | 195 ++ test/js/bun/test/nested-describes.test.ts | 34 + .../bun/test/test-auto-import-jest-globals.test.js | 24 + test/js/bun/test/test-test.test.ts | 2098 +++++++++++++++ test/js/bun/util/arraybuffersink.test.ts | 64 + test/js/bun/util/concat.test.js | 43 + test/js/bun/util/empty.js | 1 + test/js/bun/util/escapeHTML.test.js | 91 + test/js/bun/util/fileUrl.test.js | 16 + test/js/bun/util/filesink.test.ts | 141 + test/js/bun/util/filesystem_router.test.ts | 354 +++ test/js/bun/util/hash.test.js | 47 + test/js/bun/util/index-of-line.test.ts | 37 + test/js/bun/util/inspect.test.js | 315 +++ test/js/bun/util/mmap.test.js | 69 + test/js/bun/util/peek.test.ts | 42 + test/js/bun/util/reportError.test.js | 25 + test/js/bun/util/sleep.js | 10 + test/js/bun/util/sleepSync.test.ts | 32 + test/js/bun/util/unsafe.test.js | 51 + test/js/bun/util/which.test.ts | 65 + test/js/bun/wasm/hello-wasi.wasm | Bin 0 -> 131 bytes test/js/bun/wasm/wasi.test.js | 14 + test/js/bun/wasm/wasm-return-1-test.zig | 5 + test/js/bun/wasm/wasm.js | 1 + test/js/bun/websocket/websocket-server.test.ts | 954 +++++++ test/js/first_party/undici/undici.test.ts | 140 + test/js/node/assert/assert-test.test.ts | 11 + test/js/node/buffer.test.js | 2563 ++++++++++++++++++ .../node/child_process/child-process-stdio.test.js | 112 + .../node/child_process/child_process-node.test.js | 481 ++++ test/js/node/child_process/child_process.test.ts | 347 +++ test/js/node/child_process/readFileSync.txt | 1 + test/js/node/child_process/spawned-child.js | 29 + test/js/node/crypto/crypto-scrypt.test.js | 232 ++ test/js/node/crypto/crypto.test.js | 106 + test/js/node/crypto/node-crypto.test.js | 29 + test/js/node/dirname.test.js | 9 + test/js/node/disabled-module.test.js | 38 + test/js/node/dns/dns.node.mjs | 0 test/js/node/dns/node-dns.test.js | 148 ++ test/js/node/events/event-emitter.test.ts | 169 ++ test/js/node/events/node-builtins.test.js | 18 + test/js/node/fs/export-*-from.ts | 1 + test/js/node/fs/export-from.ts | 1 + test/js/node/fs/fs-stream.js | 21 + test/js/node/fs/fs-stream.link.js | 1 + test/js/node/fs/fs.test.ts | 1013 +++++++ test/js/node/fs/readFileSync.txt | 1 + test/js/node/fs/test.txt | 0 test/js/node/fs/writeFileSync.txt | 1 + test/js/node/harness.test.js | 169 ++ test/js/node/harness.ts | 201 ++ test/js/node/http/node-http.fixme.ts | 604 +++++ test/js/node/module/node-module-module.test.js | 5 + test/js/node/net/node-net.test.ts | 268 ++ test/js/node/os/os.test.js | 150 ++ test/js/node/path/path.test.js | 426 +++ test/js/node/process/print-process-args.js | 4 + test/js/node/process/process-args.test.js | 40 + test/js/node/process/process-nexttick.js | 84 + test/js/node/process/process-nexttick.test.js | 99 + test/js/node/process/process-stdin-echo.js | 11 + test/js/node/process/process-stdio.test.ts | 128 + test/js/node/process/process.test.js | 185 ++ test/js/node/readline/readline.node.test.ts | 2001 ++++++++++++++ .../node/readline/readline_promises.node.test.ts | 51 + test/js/node/stream/bufferlist.test.ts | 221 ++ test/js/node/stream/node-stream-uint8array.test.ts | 111 + test/js/node/stream/node-stream.test.js | 86 + test/js/node/string_decoder/string-decoder.test.js | 243 ++ test/js/node/timers/node-timers.test.ts | 17 + test/js/node/util/test-util-types.test.js | 240 ++ test/js/node/util/util-promisify.test.js | 304 +++ test/js/node/util/util.test.js | 269 ++ test/js/node/v8/capture-stack-trace.test.js | 303 +++ test/js/node/zlib/fixture.html.gz | Bin 0 -> 16139 bytes test/js/node/zlib/zlib.test.js | 39 + test/js/third_party/body-parser/bun.lockb | Bin 0 -> 19805 bytes .../body-parser/express-body-parser-test.test.ts | 59 + test/js/third_party/body-parser/package.json | 9 + test/js/third_party/esbuild/bun.lockb | Bin 0 -> 8425 bytes .../esbuild/esbuild-child_process.test.ts | 17 + test/js/third_party/esbuild/esbuild-test.js | 37 + test/js/third_party/esbuild/package.json | 6 + test/js/third_party/napi_create_external/bun.lockb | Bin 0 -> 1496 bytes .../napi-create-external.test.ts | 194 ++ .../third_party/napi_create_external/package.json | 13 + .../third_party/react-dom/react-dom-server.bun.cjs | 2670 +++++++++++++++++++ test/js/third_party/react-dom/react-dom.test.tsx | 285 ++ test/js/third_party/svelte/bun-loader-svelte.ts | 18 + test/js/third_party/svelte/hello.svelte | 5 + test/js/third_party/svelte/package.json | 4 + test/js/third_party/svelte/svelte.test.ts | 21 + test/js/web/abort/abort-signal-timeout.test.js | 12 + test/js/web/console/console-log.expected.txt | 46 + test/js/web/console/console-log.js | 54 + test/js/web/console/console-log.test.ts | 20 + test/js/web/crypto/web-crypto.test.ts | 91 + test/js/web/encoding/text-decoder.test.js | 243 ++ test/js/web/encoding/text-encoder.test.js | 281 ++ test/js/web/encoding/utf8-encoding-fixture.bin | Bin 0 -> 4456448 bytes test/js/web/fetch/body-mixin-errors.test.ts | 17 + test/js/web/fetch/body-stream.test.ts | 451 ++++ test/js/web/fetch/fetch-gzip.test.ts | 181 ++ test/js/web/fetch/fetch.js.txt | 46 + test/js/web/fetch/fetch.test.ts | 935 +++++++ test/js/web/fetch/fetch_headers.test.js | 66 + test/js/web/fetch/fixture.html | 1428 ++++++++++ test/js/web/fetch/fixture.html.gz | Bin 0 -> 16139 bytes test/js/web/html/FormData.test.ts | 410 +++ test/js/web/html/form-data-fixture.txt | 1 + test/js/web/streams/bun-streams-test-fifo.sh | 8 + test/js/web/streams/fetch.js.txt | 46 + test/js/web/streams/streams.test.js | 630 +++++ test/js/web/timers/microtask.test.js | 74 + test/js/web/timers/performance.test.js | 22 + test/js/web/timers/setImmediate.test.js | 47 + test/js/web/timers/setInterval.test.js | 61 + test/js/web/timers/setTimeout.test.js | 173 ++ test/js/web/url/url.test.ts | 137 + test/js/web/util/atob.test.js | 77 + test/js/web/web-globals.test.js | 156 ++ test/js/web/websocket/websocket-subprocess.ts | 13 + test/js/web/websocket/websocket.test.js | 263 ++ test/js/workerd/html-rewriter.test.js | 303 +++ test/leaks/http-static-leak.ts | 29 - test/macro/assert.tsx | 4 - test/macro/fetchSync.tsx | 9 - test/macro/hello-fetch-macro.tsx | 5 - test/macro/loadMocks.tsx | 29 - test/macro/macro.d.ts | 1 - test/mkfifo.ts | 22 + test/package.json | 19 + test/regression/issue/00631.test.ts | 38 + test/regression/issue/02005.test.ts | 11 + test/tsconfig.json | 23 + 492 files changed, 40560 insertions(+), 42242 deletions(-) create mode 100644 test/.prettierignore delete mode 100644 test/apps/bun-create-next.sh delete mode 100644 test/apps/bun-create-react.sh delete mode 100644 test/apps/bun-dev-index-html.sh delete mode 100644 test/apps/bun-dev.sh delete mode 100644 test/apps/bun-init-check.sh delete mode 100644 test/apps/bun-install-lockfile-status.sh delete mode 100644 test/apps/bun-install-utf8.sh delete mode 100644 test/apps/bun-install.sh delete mode 100644 test/apps/bun-run-check-nameless-package.json delete mode 100644 test/apps/bun-run-check-package.json delete mode 100644 test/apps/bun-run-check.sh delete mode 100644 test/bun.js/.prettierignore delete mode 100644 test/bun.js/FormData.test.ts delete mode 100644 test/bun.js/abort-signal-timeout.test.js delete mode 100644 test/bun.js/arraybuffersink.test.ts delete mode 100644 test/bun.js/assert-test.test.ts delete mode 100644 test/bun.js/atob.test.js delete mode 100644 test/bun.js/bash-echo.sh delete mode 100644 test/bun.js/baz.js delete mode 100644 test/bun.js/bigint.test.js delete mode 100644 test/bun.js/body-mixin-errors.test.ts delete mode 100644 test/bun.js/body-stream.test.ts delete mode 100644 test/bun.js/buffer.test.js delete mode 100644 test/bun.js/bufferlist.test.ts delete mode 100644 test/bun.js/bun-jsc.test.js delete mode 100644 test/bun.js/bun-loader-svelte/bun-loader-svelte.ts delete mode 100644 test/bun.js/bun-loader-svelte/package.json delete mode 100644 test/bun.js/bun-server.test.ts delete mode 100644 test/bun.js/bun-spawn-test.js delete mode 100644 test/bun.js/bun-streams-test-fifo.sh delete mode 100644 test/bun.js/bun-test.test.ts delete mode 100644 test/bun.js/bun-test/jest-hooks.test.ts delete mode 100644 test/bun.js/bun-test/nested-describes.test.ts delete mode 100644 test/bun.js/bun-write.test.js delete mode 100755 test/bun.js/bun.lockb delete mode 100644 test/bun.js/bunEnv.ts delete mode 100644 test/bun.js/bunExe.ts delete mode 120000 test/bun.js/bundled/always-bundled-module/always-bundled-module delete mode 100644 test/bun.js/bundled/always-bundled-module/cjs.js delete mode 100644 test/bun.js/bundled/always-bundled-module/esm.js delete mode 100644 test/bun.js/bundled/always-bundled-module/package.json delete mode 100644 test/bun.js/bundled/entrypoint.ts delete mode 100644 test/bun.js/bundled/package.json delete mode 100644 test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/cjs.js delete mode 100644 test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/esm.js delete mode 100644 test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/package.json delete mode 100644 test/bun.js/bundled/tsconfig.json delete mode 100644 test/bun.js/capture-stack-trace.test.js delete mode 100644 test/bun.js/child-process-stdio.test.js delete mode 100644 test/bun.js/child_process-node.test.js delete mode 100644 test/bun.js/child_process.test.ts delete mode 100644 test/bun.js/concat.test.js delete mode 100644 test/bun.js/console/console-iterator-run.js delete mode 100644 test/bun.js/console/console-iterator.test.js delete mode 100644 test/bun.js/console/console-log.expected.txt delete mode 100644 test/bun.js/console/console-log.js delete mode 100644 test/bun.js/console/console-log.test.ts delete mode 100644 test/bun.js/crypto-scrypt.test.js delete mode 100644 test/bun.js/crypto.test.js delete mode 100644 test/bun.js/decorators.test.ts delete mode 100644 test/bun.js/dirname.test.js delete mode 100644 test/bun.js/disabled-module.test.js delete mode 100644 test/bun.js/dns.node.mjs delete mode 100644 test/bun.js/empty.js delete mode 100644 test/bun.js/emptyFile delete mode 100644 test/bun.js/esbuild-child_process.test.ts delete mode 100644 test/bun.js/esbuild-test.js delete mode 100644 test/bun.js/escapeHTML.test.js delete mode 100644 test/bun.js/esm/first.mjs delete mode 100644 test/bun.js/esm/second-child.mjs delete mode 100644 test/bun.js/esm/second.mjs delete mode 100644 test/bun.js/esm/startEnd.mjs delete mode 100644 test/bun.js/esm/third.mjs delete mode 100644 test/bun.js/event-emitter.test.ts delete mode 100644 test/bun.js/exit-code-0.js delete mode 100644 test/bun.js/exit-code-1.js delete mode 100644 test/bun.js/exit-code-await-throw-1.js delete mode 100644 test/bun.js/exit-code-unhandled-throw.js delete mode 100644 test/bun.js/exit-code.test.ts delete mode 100644 test/bun.js/exit.js delete mode 100644 test/bun.js/fetch-gzip.test.ts delete mode 100644 test/bun.js/fetch.js.txt delete mode 100644 test/bun.js/fetch.test.ts delete mode 100644 test/bun.js/fetch_headers.test.js delete mode 100644 test/bun.js/ffi-test.c delete mode 100644 test/bun.js/ffi.test.fixture.callback.c delete mode 100644 test/bun.js/ffi.test.fixture.receiver.c delete mode 100644 test/bun.js/ffi.test.js delete mode 100644 test/bun.js/file-importing-nonexistent-file.js delete mode 100644 test/bun.js/fileUrl.test.js delete mode 100644 test/bun.js/filesink.test.ts delete mode 100644 test/bun.js/filesystem_router.test.ts delete mode 100644 test/bun.js/fixture.html delete mode 100644 test/bun.js/fixture.html.gz delete mode 100644 test/bun.js/form-data-fixture.txt delete mode 100644 test/bun.js/fs-stream.js delete mode 120000 test/bun.js/fs-stream.link.js delete mode 100644 test/bun.js/fs.test.ts delete mode 100644 test/bun.js/gc.ts delete mode 100644 test/bun.js/globals.test.js delete mode 100644 test/bun.js/google-fixture.html delete mode 100644 test/bun.js/hash.test.js delete mode 100755 test/bun.js/hello-wasi.wasm delete mode 100644 test/bun.js/hello.svelte delete mode 100644 test/bun.js/hello2.svelte delete mode 100644 test/bun.js/hot-runner-imported.js delete mode 100644 test/bun.js/hot-runner.js delete mode 100644 test/bun.js/hot.test.ts delete mode 100644 test/bun.js/html-rewriter.test.js delete mode 100644 test/bun.js/import-meta.test.js delete mode 100644 test/bun.js/import-require-tla.js delete mode 100644 test/bun.js/import.live.decl.js delete mode 100644 test/bun.js/import.live.rexport-require.js delete mode 100644 test/bun.js/import.live.rexport.js delete mode 100644 test/bun.js/index-of-line.test.ts delete mode 100644 test/bun.js/inline.macro.js delete mode 100644 test/bun.js/inspect.test.js delete mode 100644 test/bun.js/install/bad-workspace.test.ts delete mode 100644 test/bun.js/install/bar-0.0.2.tgz delete mode 100644 test/bun.js/install/baz-0.0.3.tgz delete mode 100644 test/bun.js/install/baz-0.0.5.tgz delete mode 100644 test/bun.js/install/bun-add.test.ts delete mode 100644 test/bun.js/install/bun-install.test.ts delete mode 100644 test/bun.js/install/bun-link.test.ts delete mode 100644 test/bun.js/install/bun-pm.test.ts delete mode 100644 test/bun.js/install/bunx.test.ts delete mode 100644 test/bun.js/install/dummy.registry.ts delete mode 100644 test/bun.js/install/moo-0.1.0.tgz delete mode 100644 test/bun.js/jest-doesnt-auto-import.js delete mode 100644 test/bun.js/log-test.test.ts delete mode 100644 test/bun.js/macro-check.js delete mode 100644 test/bun.js/microtask.test.js delete mode 100644 test/bun.js/mkfifo.ts delete mode 100644 test/bun.js/mmap.test.js delete mode 100644 test/bun.js/module-require.snapshot.js delete mode 100644 test/bun.js/node-builtins.test.js delete mode 100644 test/bun.js/node-crypto.test.js delete mode 100644 test/bun.js/node-dns.test.js delete mode 100644 test/bun.js/node-http.test.ts delete mode 100644 test/bun.js/node-module-module.test.js delete mode 100644 test/bun.js/node-stream-uint8array.test.ts delete mode 100644 test/bun.js/node-stream.test.js delete mode 100644 test/bun.js/node-test-helpers.test.js delete mode 100644 test/bun.js/node-test-helpers.ts delete mode 100644 test/bun.js/node-timers.test.ts delete mode 100644 test/bun.js/northwind.testdb delete mode 100644 test/bun.js/os.test.js delete mode 100644 test/bun.js/package.json delete mode 100644 test/bun.js/path.test.js delete mode 100644 test/bun.js/peek.test.ts delete mode 100644 test/bun.js/performance.test.js delete mode 100644 test/bun.js/plugins.d.ts delete mode 100644 test/bun.js/plugins.test.ts delete mode 100644 test/bun.js/preload-test.test.js delete mode 100644 test/bun.js/print-process-args.js delete mode 100644 test/bun.js/process-args.test.js delete mode 100644 test/bun.js/process-nexttick.js delete mode 100644 test/bun.js/process-nexttick.test.js delete mode 100644 test/bun.js/process-stdin-echo.js delete mode 100644 test/bun.js/process-stdio.test.ts delete mode 100644 test/bun.js/process.test.js delete mode 100644 test/bun.js/proxy.test.js delete mode 100644 test/bun.js/react-dom-server.bun.cjs delete mode 100644 test/bun.js/react-dom.test.tsx delete mode 100644 test/bun.js/readFileSync.txt delete mode 100644 test/bun.js/readdir.js delete mode 100644 test/bun.js/readline.node.test.ts delete mode 100644 test/bun.js/readline_promises.node.test.ts delete mode 100644 test/bun.js/reportError.test.js delete mode 100644 test/bun.js/repro_2005.test.js delete mode 100644 test/bun.js/repro_631.test.js delete mode 100644 test/bun.js/require-js-top-level-await.js delete mode 100644 test/bun.js/require-js.js delete mode 100644 test/bun.js/require-js2.js delete mode 100644 test/bun.js/require-json.json delete mode 100644 test/bun.js/require-referenceerror.snapshot.js delete mode 100644 test/bun.js/resolve-dns.test.ts delete mode 100644 test/bun.js/resolve-error.test.ts delete mode 100644 test/bun.js/resolve-typescript-file.tsx delete mode 100644 test/bun.js/resolve.test.js delete mode 100644 test/bun.js/serve.test.ts delete mode 100644 test/bun.js/setImmediate.test.js delete mode 100644 test/bun.js/setInterval.test.js delete mode 100644 test/bun.js/setTimeout.test.js delete mode 100644 test/bun.js/shadow.test.js delete mode 100644 test/bun.js/sleep.js delete mode 100644 test/bun.js/sleepSync.test.ts delete mode 100644 test/bun.js/snapshots.debug.js delete mode 100644 test/bun.js/snapshots.js delete mode 100644 test/bun.js/socket/echo.js delete mode 100644 test/bun.js/socket/node-net.test.ts delete mode 100644 test/bun.js/socket/socket.test.ts delete mode 100644 test/bun.js/some-fs.js delete mode 100644 test/bun.js/spawn-streaming-stdin.test.ts delete mode 100644 test/bun.js/spawn-streaming-stdout-repro.js delete mode 100644 test/bun.js/spawn-streaming-stdout.test.ts delete mode 100644 test/bun.js/spawn.test.ts delete mode 100644 test/bun.js/spawned-child.js delete mode 100644 test/bun.js/sql-raw.test.js delete mode 100644 test/bun.js/sqlite-cross-process.js delete mode 100644 test/bun.js/sqlite.test.js delete mode 100644 test/bun.js/stdin-repro.js delete mode 100644 test/bun.js/stdio-test-instance-a-lot.js delete mode 100644 test/bun.js/stdio-test-instance.js delete mode 100644 test/bun.js/streams.test.js delete mode 100644 test/bun.js/string-decoder.test.js delete mode 100644 test/bun.js/tcp-server.test.ts delete mode 100644 test/bun.js/test-auto-import-jest-globals.test.js delete mode 100644 test/bun.js/test-png-import.test.js delete mode 100644 test/bun.js/test-png.png delete mode 100644 test/bun.js/test-programs/bigpipe-r.c delete mode 100644 test/bun.js/test-programs/bigpipe-w.c delete mode 100644 test/bun.js/test-test.test.ts delete mode 100644 test/bun.js/test-util-types.test.js delete mode 100644 test/bun.js/test_scope_debug.ts delete mode 100644 test/bun.js/text-decoder.test.js delete mode 100644 test/bun.js/text-encoder.test.js delete mode 100755 test/bun.js/third-party/body-parser-test/bun.lockb delete mode 100644 test/bun.js/third-party/body-parser-test/express-body-parser-test.test.ts delete mode 100644 test/bun.js/third-party/body-parser-test/package.json delete mode 100755 test/bun.js/third-party/napi_create_external/bun.lockb delete mode 100644 test/bun.js/third-party/napi_create_external/napi-create-external.test.ts delete mode 100644 test/bun.js/third-party/napi_create_external/package.json delete mode 100644 test/bun.js/tiny-typed-emitter.snapshot.js delete mode 100644 test/bun.js/toml-fixture.toml delete mode 100644 test/bun.js/toml.test.js delete mode 100644 test/bun.js/transpiler.test.js delete mode 100644 test/bun.js/tsconfig.json delete mode 100644 test/bun.js/undici.test.ts delete mode 100644 test/bun.js/unsafe.test.js delete mode 100644 test/bun.js/url.test.ts delete mode 100644 test/bun.js/utf8-encoding-fixture.bin delete mode 100644 test/bun.js/util/util-promisify.test.js delete mode 100644 test/bun.js/util/util.test.js delete mode 100644 test/bun.js/wasi.test.js delete mode 100644 test/bun.js/wasm-return-1-test.zig delete mode 100644 test/bun.js/wasm.js delete mode 100644 test/bun.js/web-crypto.test.ts delete mode 100644 test/bun.js/web-globals.test.js delete mode 100644 test/bun.js/websocket-server.test.ts delete mode 100644 test/bun.js/websocket-subprocess.ts delete mode 100644 test/bun.js/websocket.test.js delete mode 100644 test/bun.js/which.test.ts delete mode 100644 test/bun.js/writeFileSync.txt delete mode 100644 test/bun.js/zlib.test.js create mode 100755 test/bun.lockb create mode 100644 test/bundler/decorators.test.ts create mode 100644 test/bundler/inline.macro.js create mode 100644 test/bundler/macro-check.js create mode 100644 test/bundler/transpiler.test.js create mode 100644 test/cli/hot/hot-runner-imported.js create mode 100644 test/cli/hot/hot-runner.js create mode 100644 test/cli/hot/hot.test.ts create mode 100644 test/cli/install/bad-workspace.test.ts create mode 100644 test/cli/install/bar-0.0.2.tgz create mode 100644 test/cli/install/baz-0.0.3.tgz create mode 100644 test/cli/install/baz-0.0.5.tgz create mode 100644 test/cli/install/bun-add.test.ts create mode 100644 test/cli/install/bun-install.test.ts create mode 100644 test/cli/install/bun-link.test.ts create mode 100644 test/cli/install/bun-pm.test.ts create mode 100644 test/cli/install/bunx.test.ts create mode 100644 test/cli/install/dummy.registry.ts create mode 100644 test/cli/install/moo-0.1.0.tgz create mode 100644 test/cli/run/log-test.test.ts create mode 100644 test/cli/run/preload-test.test.js delete mode 100644 test/fixtures/bun-link-pkg-fixture/.gitignore delete mode 100755 test/fixtures/bun-link-pkg-fixture/bun-link-pkg-fixture.js delete mode 100755 test/fixtures/bun-link-pkg-fixture/bun.lockb delete mode 100644 test/fixtures/bun-link-pkg-fixture/package-lock.json delete mode 100644 test/fixtures/bun-link-pkg-fixture/package.json delete mode 100644 test/fixtures/bun-link-to-pkg-fixture/.gitignore delete mode 100644 test/fixtures/bun-link-to-pkg-fixture/README.md delete mode 100755 test/fixtures/bun-link-to-pkg-fixture/bun.lockb delete mode 100644 test/fixtures/bun-link-to-pkg-fixture/index.js delete mode 100644 test/fixtures/bun-link-to-pkg-fixture/package.json delete mode 100644 test/fixtures/bun-link-to-pkg-fixture/tsconfig.json delete mode 100644 test/fixtures/export-lazy-fs-streams/export-*-from.ts delete mode 100644 test/fixtures/export-lazy-fs-streams/export-from.ts create mode 100644 test/harness.ts create mode 100644 test/js/bun/console/console-iterator-run.js create mode 100644 test/js/bun/console/console-iterator.test.js create mode 100644 test/js/bun/dns/resolve-dns.test.ts create mode 100644 test/js/bun/ffi/ffi-test.c create mode 100644 test/js/bun/ffi/ffi.test.fixture.callback.c create mode 100644 test/js/bun/ffi/ffi.test.fixture.receiver.c create mode 100644 test/js/bun/ffi/ffi.test.js create mode 100644 test/js/bun/globals.test.js create mode 100644 test/js/bun/http/bun-server.test.ts create mode 100644 test/js/bun/http/fetch.js.txt create mode 100644 test/js/bun/http/fixture.html.gz create mode 100644 test/js/bun/http/proxy.test.js create mode 100644 test/js/bun/http/serve.leak.ts create mode 100644 test/js/bun/http/serve.test.ts create mode 100644 test/js/bun/io/bun-streams-test-fifo.sh create mode 100644 test/js/bun/io/bun-write.test.js create mode 100644 test/js/bun/io/emptyFile create mode 100644 test/js/bun/io/fetch.js.txt create mode 100644 test/js/bun/jsc/bun-jsc.test.js create mode 100644 test/js/bun/jsc/shadow.test.js create mode 100644 test/js/bun/net/echo.js create mode 100644 test/js/bun/net/socket.test.ts create mode 100644 test/js/bun/net/tcp-server.test.ts create mode 100644 test/js/bun/plugin/hello.svelte create mode 100644 test/js/bun/plugin/hello2.svelte create mode 100644 test/js/bun/plugin/plugins.d.ts create mode 100644 test/js/bun/plugin/plugins.test.ts create mode 100644 test/js/bun/resolve/baz.js create mode 100644 test/js/bun/resolve/file-importing-nonexistent-file.js create mode 100644 test/js/bun/resolve/first.mjs create mode 100644 test/js/bun/resolve/import-meta.test.js create mode 100644 test/js/bun/resolve/import-require-tla.js create mode 100644 test/js/bun/resolve/import.live.decl.js create mode 100644 test/js/bun/resolve/import.live.rexport-require.js create mode 100644 test/js/bun/resolve/import.live.rexport.js create mode 100644 test/js/bun/resolve/png/test-png-import.test.js create mode 100644 test/js/bun/resolve/png/test-png.png create mode 100644 test/js/bun/resolve/require-js-top-level-await.js create mode 100644 test/js/bun/resolve/require-js.js create mode 100644 test/js/bun/resolve/require-js2.js create mode 100644 test/js/bun/resolve/require-json.json create mode 100644 test/js/bun/resolve/require-referenceerror.snapshot.js create mode 100644 test/js/bun/resolve/resolve-error.test.ts create mode 100644 test/js/bun/resolve/resolve-typescript-file.tsx create mode 100644 test/js/bun/resolve/resolve.test.js create mode 100644 test/js/bun/resolve/second-child.mjs create mode 100644 test/js/bun/resolve/second.mjs create mode 100644 test/js/bun/resolve/startEnd.mjs create mode 100644 test/js/bun/resolve/third.mjs create mode 100644 test/js/bun/resolve/toml/toml-fixture.toml create mode 100644 test/js/bun/resolve/toml/toml.test.js create mode 100644 test/js/bun/spawn/bash-echo.sh create mode 100644 test/js/bun/spawn/bun-spawn-test.js create mode 100644 test/js/bun/spawn/exit-code-0.js create mode 100644 test/js/bun/spawn/exit-code-1.js create mode 100644 test/js/bun/spawn/exit-code-await-throw-1.js create mode 100644 test/js/bun/spawn/exit-code-unhandled-throw.js create mode 100644 test/js/bun/spawn/exit-code.test.ts create mode 100644 test/js/bun/spawn/exit.js create mode 100644 test/js/bun/spawn/spawn-streaming-stdin.test.ts create mode 100644 test/js/bun/spawn/spawn-streaming-stdout-repro.js create mode 100644 test/js/bun/spawn/spawn-streaming-stdout.test.ts create mode 100644 test/js/bun/spawn/spawn.test.ts create mode 100644 test/js/bun/spawn/stdin-repro.js create mode 100644 test/js/bun/spawn/stdio-test-instance-a-lot.js create mode 100644 test/js/bun/spawn/stdio-test-instance.js create mode 100644 test/js/bun/sqlite/northwind.testdb create mode 100644 test/js/bun/sqlite/sql-raw.test.js create mode 100644 test/js/bun/sqlite/sqlite-cross-process.js create mode 100644 test/js/bun/sqlite/sqlite.test.js create mode 100644 test/js/bun/test/bigint.test.js create mode 100644 test/js/bun/test/bun-test.test.ts create mode 100644 test/js/bun/test/jest-doesnt-auto-import.js create mode 100644 test/js/bun/test/jest-hooks.test.ts create mode 100644 test/js/bun/test/nested-describes.test.ts create mode 100644 test/js/bun/test/test-auto-import-jest-globals.test.js create mode 100644 test/js/bun/test/test-test.test.ts create mode 100644 test/js/bun/util/arraybuffersink.test.ts create mode 100644 test/js/bun/util/concat.test.js create mode 100644 test/js/bun/util/empty.js create mode 100644 test/js/bun/util/escapeHTML.test.js create mode 100644 test/js/bun/util/fileUrl.test.js create mode 100644 test/js/bun/util/filesink.test.ts create mode 100644 test/js/bun/util/filesystem_router.test.ts create mode 100644 test/js/bun/util/hash.test.js create mode 100644 test/js/bun/util/index-of-line.test.ts create mode 100644 test/js/bun/util/inspect.test.js create mode 100644 test/js/bun/util/mmap.test.js create mode 100644 test/js/bun/util/peek.test.ts create mode 100644 test/js/bun/util/reportError.test.js create mode 100644 test/js/bun/util/sleep.js create mode 100644 test/js/bun/util/sleepSync.test.ts create mode 100644 test/js/bun/util/unsafe.test.js create mode 100644 test/js/bun/util/which.test.ts create mode 100755 test/js/bun/wasm/hello-wasi.wasm create mode 100644 test/js/bun/wasm/wasi.test.js create mode 100644 test/js/bun/wasm/wasm-return-1-test.zig create mode 100644 test/js/bun/wasm/wasm.js create mode 100644 test/js/bun/websocket/websocket-server.test.ts create mode 100644 test/js/first_party/undici/undici.test.ts create mode 100644 test/js/node/assert/assert-test.test.ts create mode 100644 test/js/node/buffer.test.js create mode 100644 test/js/node/child_process/child-process-stdio.test.js create mode 100644 test/js/node/child_process/child_process-node.test.js create mode 100644 test/js/node/child_process/child_process.test.ts create mode 100644 test/js/node/child_process/readFileSync.txt create mode 100644 test/js/node/child_process/spawned-child.js create mode 100644 test/js/node/crypto/crypto-scrypt.test.js create mode 100644 test/js/node/crypto/crypto.test.js create mode 100644 test/js/node/crypto/node-crypto.test.js create mode 100644 test/js/node/dirname.test.js create mode 100644 test/js/node/disabled-module.test.js create mode 100644 test/js/node/dns/dns.node.mjs create mode 100644 test/js/node/dns/node-dns.test.js create mode 100644 test/js/node/events/event-emitter.test.ts create mode 100644 test/js/node/events/node-builtins.test.js create mode 100644 test/js/node/fs/export-*-from.ts create mode 100644 test/js/node/fs/export-from.ts create mode 100644 test/js/node/fs/fs-stream.js create mode 120000 test/js/node/fs/fs-stream.link.js create mode 100644 test/js/node/fs/fs.test.ts create mode 100644 test/js/node/fs/readFileSync.txt create mode 100644 test/js/node/fs/test.txt create mode 100644 test/js/node/fs/writeFileSync.txt create mode 100644 test/js/node/harness.test.js create mode 100644 test/js/node/harness.ts create mode 100644 test/js/node/http/node-http.fixme.ts create mode 100644 test/js/node/module/node-module-module.test.js create mode 100644 test/js/node/net/node-net.test.ts create mode 100644 test/js/node/os/os.test.js create mode 100644 test/js/node/path/path.test.js create mode 100644 test/js/node/process/print-process-args.js create mode 100644 test/js/node/process/process-args.test.js create mode 100644 test/js/node/process/process-nexttick.js create mode 100644 test/js/node/process/process-nexttick.test.js create mode 100644 test/js/node/process/process-stdin-echo.js create mode 100644 test/js/node/process/process-stdio.test.ts create mode 100644 test/js/node/process/process.test.js create mode 100644 test/js/node/readline/readline.node.test.ts create mode 100644 test/js/node/readline/readline_promises.node.test.ts create mode 100644 test/js/node/stream/bufferlist.test.ts create mode 100644 test/js/node/stream/node-stream-uint8array.test.ts create mode 100644 test/js/node/stream/node-stream.test.js create mode 100644 test/js/node/string_decoder/string-decoder.test.js create mode 100644 test/js/node/timers/node-timers.test.ts create mode 100644 test/js/node/util/test-util-types.test.js create mode 100644 test/js/node/util/util-promisify.test.js create mode 100644 test/js/node/util/util.test.js create mode 100644 test/js/node/v8/capture-stack-trace.test.js create mode 100644 test/js/node/zlib/fixture.html.gz create mode 100644 test/js/node/zlib/zlib.test.js create mode 100755 test/js/third_party/body-parser/bun.lockb create mode 100644 test/js/third_party/body-parser/express-body-parser-test.test.ts create mode 100644 test/js/third_party/body-parser/package.json create mode 100755 test/js/third_party/esbuild/bun.lockb create mode 100644 test/js/third_party/esbuild/esbuild-child_process.test.ts create mode 100644 test/js/third_party/esbuild/esbuild-test.js create mode 100644 test/js/third_party/esbuild/package.json create mode 100755 test/js/third_party/napi_create_external/bun.lockb create mode 100644 test/js/third_party/napi_create_external/napi-create-external.test.ts create mode 100644 test/js/third_party/napi_create_external/package.json create mode 100644 test/js/third_party/react-dom/react-dom-server.bun.cjs create mode 100644 test/js/third_party/react-dom/react-dom.test.tsx create mode 100644 test/js/third_party/svelte/bun-loader-svelte.ts create mode 100644 test/js/third_party/svelte/hello.svelte create mode 100644 test/js/third_party/svelte/package.json create mode 100644 test/js/third_party/svelte/svelte.test.ts create mode 100644 test/js/web/abort/abort-signal-timeout.test.js create mode 100644 test/js/web/console/console-log.expected.txt create mode 100644 test/js/web/console/console-log.js create mode 100644 test/js/web/console/console-log.test.ts create mode 100644 test/js/web/crypto/web-crypto.test.ts create mode 100644 test/js/web/encoding/text-decoder.test.js create mode 100644 test/js/web/encoding/text-encoder.test.js create mode 100644 test/js/web/encoding/utf8-encoding-fixture.bin create mode 100644 test/js/web/fetch/body-mixin-errors.test.ts create mode 100644 test/js/web/fetch/body-stream.test.ts create mode 100644 test/js/web/fetch/fetch-gzip.test.ts create mode 100644 test/js/web/fetch/fetch.js.txt create mode 100644 test/js/web/fetch/fetch.test.ts create mode 100644 test/js/web/fetch/fetch_headers.test.js create mode 100644 test/js/web/fetch/fixture.html create mode 100644 test/js/web/fetch/fixture.html.gz create mode 100644 test/js/web/html/FormData.test.ts create mode 100644 test/js/web/html/form-data-fixture.txt create mode 100644 test/js/web/streams/bun-streams-test-fifo.sh create mode 100644 test/js/web/streams/fetch.js.txt create mode 100644 test/js/web/streams/streams.test.js create mode 100644 test/js/web/timers/microtask.test.js create mode 100644 test/js/web/timers/performance.test.js create mode 100644 test/js/web/timers/setImmediate.test.js create mode 100644 test/js/web/timers/setInterval.test.js create mode 100644 test/js/web/timers/setTimeout.test.js create mode 100644 test/js/web/url/url.test.ts create mode 100644 test/js/web/util/atob.test.js create mode 100644 test/js/web/web-globals.test.js create mode 100644 test/js/web/websocket/websocket-subprocess.ts create mode 100644 test/js/web/websocket/websocket.test.js create mode 100644 test/js/workerd/html-rewriter.test.js delete mode 100644 test/leaks/http-static-leak.ts delete mode 100644 test/macro/assert.tsx delete mode 100644 test/macro/fetchSync.tsx delete mode 100644 test/macro/hello-fetch-macro.tsx delete mode 100644 test/macro/loadMocks.tsx delete mode 100644 test/macro/macro.d.ts create mode 100644 test/mkfifo.ts create mode 100644 test/package.json create mode 100644 test/regression/issue/00631.test.ts create mode 100644 test/regression/issue/02005.test.ts create mode 100644 test/tsconfig.json (limited to 'test') diff --git a/test/.prettierignore b/test/.prettierignore new file mode 100644 index 000000000..3c3629e64 --- /dev/null +++ b/test/.prettierignore @@ -0,0 +1 @@ +node_modules diff --git a/test/README.md b/test/README.md index 274b19fbf..36fe3b005 100644 --- a/test/README.md +++ b/test/README.md @@ -1,145 +1,73 @@ -# Tests in Bun +# Tests -Bun currently has four different kinds of tests +## Finding tests -To run all the tests: +Tests are located in the [`test/`](test/) directory and are organized using the following structure: -```bash -make test-all -bun --cwd=test/bun.js wiptest -``` - -### Runtime tests - -To run the runtime tests: +* `test/` + * `js/` - tests for JavaScript APIs. + * `cli/` - tests for commands, configs, and stdout. + * `bundler/` - tests for the transpiler/bundler. + * `regression/` - tests that reproduce a specific issue. + * `harness.ts` - utility functions that can be imported from any test. -```bash -cd test/bun.js -bun wiptest -``` +The tests in [`test/js/`](test/js/) directory are further categorized by the type of API. -These tests are in [./bun.js](./bun.js) and are files with `.test.js` or `.test.ts` in the filename. +* `test/js/` + * `bun/` - tests for `Bun`-specific APIs. + * `node/` - tests for Node.js APIs. + * `web/` - tests for Web APIs, like `fetch()`. + * `first_party/` - tests for npm packages that are built-in, like `undici`. + * `third_party/` - tests for npm packages that are not built-in, but are popular, like `esbuild`. -These test that the runtime behaves as expected. These also test the transpiler, both because test files are transpiled and directly by running the transpiler via `Bun.Transpiler`. +## Running tests -#### Adding a new test +To run a test, use Bun's built-in test command: `bun test`. -1. Create a new file in [./bun.js](./bun.js/) with `.test` in the name. +```sh +bun test # Run all tests +bun test js/bun # Only run tests in a directory +bun test sqlite.test.ts # Only run a specific test +``` -These test use `bun:test` as the import (though you can also import from `vitest` or jest and it will work). +If you encounter lots of errors, try running `bun install`, then trying again. -This will eventually be a public test runner for bun, but the reporter isn't very good yet and it doesn't run in parallel. +## Writing tests -The syntax intends for Jest compatibility. +Tests are written in TypeScript (preferred) or JavaScript using Jest's `describe()`, `test()`, and `expect()` APIs. ```ts -import { describe, expect, it } from "bun:test"; - -describe("Example", () => { - it("should work", () => { - expect(1).toBe(1); +import { describe, test, expect } from "bun:test"; +import { gcTick } from "harness"; + +describe("TextEncoder", () => { + test("can encode a string", async () => { + const encoder = new TextEncoder(); + const actual = encoder.encode("bun"); + await gcTick(); + expect(actual).toBe(new Uint8Array([0x62, 0x75, 0x6E])); }); }); ``` -### Browser tests - -Browser tests run end-to-end inside of Puppeteer and execute code transpiled by `bun dev`. These tests are in [./snippets](./snippets). - -The interface is: - -```js -// this function is called after import() -// if testDone() is never called, the test fails -export function test() { - return testDone(import.meta.url); -} -``` - -On success, it saves a snapshot to [./snapshots](./snapshots) which is checked into git. - -#### Adding a new test - -1. Create a new file in the `snippets` directory. -2. Append the filename to [./scripts/snippets.json](./scripts/snippets.json) -3. Run `bun dev` inside this folder in one terminal window -4. Run `make integration-test-dev` - -These tests are run twice. Once with HMR enabled and once with HMR disabled. HMR changes the output enough to warrant it's own special treatment. - -#### Running the tests - -To run the browser tests with HMR on a production build: - -```bash -make test-with-hmr -``` - -To run the browser tests without HMR on a production build: - -```bash -make test-with-no-hmr -``` - -To run the browser tests with HMR on a debug build: - -```bash -make test-dev-with-hmr -``` - -To run the browser tests without HMR on a debug build: +If you are fixing a bug that was reported from a GitHub issue, remember to add a test in the `test/regression/` directory. -```bash -make test-dev-no-hmr -``` +```ts +// test/regression/issue/02005.test.ts -To run the browser tests on whatever version of bun is running on port 3000: +import { it, expect } from "bun:test"; -```bash -make integration-test-dev +it("regex literal should work with non-latin1", () => { + const text = "这是一段要替换的文字"; + expect(text.replace(new RegExp("要替换"), "")).toBe("这是一段的文字"); + expect(text.replace(/要替换/, "")).toBe("这是一段的文字"); +}); ``` -These were the first tests bun started with - -#### Running the tests - -Run `bun wiptest ${part-of-file-name}` - -If you run the test in the top-level bun repo directory, it will take an extra couple seconds because `bun wiptest` will scan through all of WebKit recursively. Consider running it in the `bun.js` directory instead. - -### CLI tests +In the future, a bot will automatically close or re-open issues when a regression is detected or resolved. -These run the bash files in the `apps` directory. - -They check end-to-end that the CLI works as expected. - -```bash -# Install dependencies for running tests -# Does not run tests -make test-install - -# Check a Create React App created via `bun create react ./foo` returns HTML -make test-create-react - -# Check a Next.js app created via `bun create next ./foo` SSRs successfully -make test-create-next - -# Check that bun run works for the same CLI args passed to npm run -make test-bun-run - -# Check that "react" installed via bun install loads successfully -# and that deleting/adding updates the lockfile as expected -make test-bun-install - -# Check that serving public paths works correctly -# and that files which should be transpiled are transpiled and files which shouldn't be aren't -make test-bun-dev -``` - -### Zig tests +## Zig tests These tests live in various `.zig` files throughout Bun's codebase, leveraging Zig's builtin `test` keyword. -Currently, they're not run automatically nor is there a simple way to run all of them. - -This is an area bun needs to improve in. +Currently, they're not run automatically nor is there a simple way to run all of them. We will make this better soon. diff --git a/test/apps/bun-create-next.sh b/test/apps/bun-create-next.sh deleted file mode 100644 index d8e148282..000000000 --- a/test/apps/bun-create-next.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# The important part of this test: make sure that bun.js successfully loads -# The most likely reason for this test to fail is that something broke in the JavaScriptCore <> bun integration -killall -9 $(basename $BUN_BIN) || echo "" - -rm -rf /tmp/next-app -mkdir -p /tmp/next-app -$BUN_BIN create next /tmp/next-app - -if (($?)); then - echo "bun create failed" - exit 1 -fi - -echo "hi!" >/tmp/next-app/public/file.txt -echo "export default 'string';" >/tmp/next-app/file.js - -cd /tmp/next-app -BUN_CRASH_WITHOUT_JIT=1 $BUN_BIN dev --port 8087 & -sleep 0.1 -curl --fail -Ss http://localhost:8087/ - -if [[ "$(curl --fail -sS http://localhost:8087/file.txt)" != "hi!" ]]; then - echo "" - echo "" - echo "" - echo "ERR: Expected 'hi!', got '$(curl --fail -sS http://localhost:8087/file.txt)'" - killall -9 $(basename $BUN_BIN) || echo "" - exit 1 -fi - -if [[ "$(curl --fail -sS http://localhost:8087/file.js)" != *"string"* ]]; then - echo "" - echo "" - echo "" - echo "ERR: Expected file to contain string got '$(curl --fail -sS http://localhost:8087/file.js)'" - killall -9 $(basename $BUN_BIN) || echo "" - exit 1 -fi - -# very simple HMR test -echo "export default 'string';" >/tmp/next-app/file2.js -sleep 0.1 - -if [[ "$(curl --fail -sS http://localhost:8087/file2.js)" != *"string"* ]]; then - echo "" - echo "" - echo "" - echo "ERR: Expected file to contain string got '$(curl --fail -sS http://localhost:8087/file2.js)'" - killall -9 $(basename $BUN_BIN) || echo "" - exit 1 -fi - -killall -9 $(basename $BUN_BIN) || echo "" diff --git a/test/apps/bun-create-react.sh b/test/apps/bun-create-react.sh deleted file mode 100644 index 564a59d35..000000000 --- a/test/apps/bun-create-react.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash - -killall -9 $(basename $BUN_BIN) || echo "" - -rm -rf /tmp/react-app -mkdir -p /tmp/react-app -$BUN_BIN create react /tmp/react-app - - -if (($?)); then - echo "bun create failed" - exit 1 -fi - -cd /tmp/react-app -BUN_CRASH_WITHOUT_JIT=1 $BUN_BIN dev --port 8087 & -sleep 0.005 - -curl --fail http://localhost:8087/ && curl --fail http://localhost:8087/src/index.jsx && killall -9 $(basename $BUN_BIN) && echo "✅ bun create react passed." -exit $? diff --git a/test/apps/bun-dev-index-html.sh b/test/apps/bun-dev-index-html.sh deleted file mode 100644 index f22f8df51..000000000 --- a/test/apps/bun-dev-index-html.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -killall -9 $(basename $BUN_BIN) || echo "" - -dir=$(mktemp -d) - -index_content="index.html" -bacon_content="bacon.html" -js_content="if(0) { var foo = 'TEST FAILED'; }" -static_content="PASS" -css_not_transpiled_content="@import url(/index.js); @import url(/i-dont-exist.css); @import url('/i-dont-exist.css'); @import url(\"/i-dont-exist.css\");" -css_is_transpiled_import="*{background-color:red;}" -css_is_transpiled="@import url(./css_is_transpiled_import.css);" - -echo $index_content >"$dir/index.html" -echo $js_content >"$dir/index.js" -echo $bacon_content >"$dir/bacon.html" -echo $static_content >"$dir/static.txt" -echo $css_not_transpiled_content >"$dir/css_not_transpiled_content.css" - -cd $dir -$BUN_BIN dev --port 8087 & -sleep 0.005 - -if [[ "$(curl --fail -sS http://localhost:8087/)" != "$index_content" ]]; then - echo "ERR: Expected '$index_content', got '$(curl --fail -sS http://localhost:8087/)'" - exit 1 -fi - -if [[ "$(curl --fail -sS http://localhost:8087/index)" != "$index_content" ]]; then - echo "ERR: Expected '$index_content', got '$(curl --fail -sS http://localhost:8087/index)'" - exit 1 -fi - -if [[ "$(curl --fail -sS http://localhost:8087/static.txt)" != "PASS" ]]; then - echo "ERR: Expected static file, got '$(curl --fail -sS http://localhost:8087/static.txt)'" - exit 1 -fi - -# Check that the file is actually transpiled -if [[ "$(curl --fail -sS http://localhost:8087/index.js)" == *"TEST FAILED"* ]]; then - echo "ERR: Expected file to be transpiled, got '$(curl --fail -sS http://localhost:8087/index.js)'" - exit 1 -fi - -if [[ "$(curl --fail -sS http://localhost:8087/index.html)" != "$index_content" ]]; then - echo "ERR: Expected '$index_content', got '$(curl --fail -sS http://localhost:8087/index.html)'" - exit 1 -fi - -if [[ "$(curl --fail -sS http://localhost:8087/foo/foo)" != "$index_content" ]]; then - echo "ERR: Expected '$index_content', got '$(curl --fail -sS http://localhost:8087/index.html)'" - exit 1 -fi - -if [[ "$(curl --fail -sS http://localhost:8087/bacon)" != "$bacon_content" ]]; then - echo "ERR: Expected '$bacon_content', got '$(curl --fail -sS http://localhost:8087/bacon)'" - exit 1 -fi - -if [[ "$(curl --fail -sS http://localhost:8087/bacon.html)" != "$bacon_content" ]]; then - echo "ERR: Expected '$bacon_content', got '$(curl --fail -sS http://localhost:8087/bacon.html)'" - exit 1 -fi - -killall -9 $(basename $BUN_BIN) || echo "" -echo "✅ bun dev index html check passed." diff --git a/test/apps/bun-dev.sh b/test/apps/bun-dev.sh deleted file mode 100644 index ea15c1d21..000000000 --- a/test/apps/bun-dev.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -killall -9 $(basename $BUN_BIN) || echo "" - -dir=$(mktemp -d) - -index_content="index.html" -bacon_content="bacon.html" -js_content="console.log('hi')" - -mkdir -p $dir/public - -echo $index_content >"$dir/public/index.html" -echo $js_content >"$dir/index.js" -echo $bacon_content >"$dir/public/bacon.html" - -cd $dir - -$BUN_BIN dev --port 8087 & -sleep 0.005 - -if [ "$(curl --fail -sS http://localhost:8087/)" != "$index_content" ]; then - echo "ERR: Expected '$index_content', got '$(curl --fail -sS http://localhost:8087/)'" - exit 1 -fi - -if [ "$(curl --fail -sS http://localhost:8087/index)" != "$index_content" ]; then - echo "ERR: Expected '$index_content', got '$(curl --fail -sS http://localhost:8087/index)'" - exit 1 -fi - -if [ "$(curl --fail -sS http://localhost:8087/index.html)" != "$index_content" ]; then - echo "ERR: Expected '$index_content', got '$(curl --fail -sS http://localhost:8087/index.html)'" - exit 1 -fi - -if [ "$(curl --fail -sS http://localhost:8087/foo/foo)" != "$index_content" ]; then - echo "ERR: Expected '$index_content', got '$(curl --fail -sS http://localhost:8087/index.html)'" - exit 1 -fi - -if [ "$(curl --fail -sS http://localhost:8087/bacon)" != "$bacon_content" ]; then - echo "ERR: Expected '$index_content', got '$(curl --fail -sS http://localhost:8087/bacon)'" - exit 1 -fi - -if [ "$(curl --fail -sS http://localhost:8087/bacon.html)" != "$bacon_content" ]; then - echo "ERR: Expected '$index_content', got '$(curl --fail -sS http://localhost:8087/bacon.html)'" - exit 1 -fi - -killall -9 $(basename $BUN_BIN) || echo "" -echo "✅ bun dev index html check passed." diff --git a/test/apps/bun-init-check.sh b/test/apps/bun-init-check.sh deleted file mode 100644 index d0077cb60..000000000 --- a/test/apps/bun-init-check.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -# TODO: move this test to bun once we have a child_process equivalent. -(killall -9 $(basename $BUN_BIN) || echo "") >/dev/null 2>&1 - -rm -rf /tmp/bun-init-check -mkdir -p /tmp/bun-init-check - -cd /tmp/bun-init-check - -$BUN_BIN init -y - -if (($?)); then - echo "Bun init failed" - exit 1 -fi - -SHASUM_RESULT=$(cat index.ts .gitignore tsconfig.json package.json | shasum) - -# This test will fail when the minor version of Bun changes. -if [[ "${SHASUM_RESULT}" != "10eabf5101a3ef999bd67232a7af33542c525ec6 -" ]]; then - echo -e "Bun init shasum mismatch\n expected: b1548bb4e806f0506fd1b27ae8901d2e84926774\n actual: ${SHASUM_RESULT}" - exit 1 -fi - -exit 0 diff --git a/test/apps/bun-install-lockfile-status.sh b/test/apps/bun-install-lockfile-status.sh deleted file mode 100644 index fc39a6dd6..000000000 --- a/test/apps/bun-install-lockfile-status.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -killall -9 $(basename $BUN_BIN) || echo "" - -dir=$(mktemp -d) - -cd $dir - -$BUN_BIN add react - -echo "node_modules" >.gitignore - -git init && git add . && git commit -am "Initial commit" - -$BUN_BIN install - -ORIG_LOCKFILE="$($BUN_BIN pm hash-string)" - -[[ -z $(git status --untracked-files=no --porcelain) ]] || { - echo "ERR: Expected empty git status, got '$(git status --untracked-files=no --porcelain)'" - exit 1 -} - -$BUN_BIN add react - -NEW_LOCKFILE="$($BUN_BIN pm hash-string)" - -diff <(echo "$ORIG_LOCKFILE") <(echo "$NEW_LOCKFILE") || { - echo "ERR: Expected lockfile to be unchanged, got '$NEW_LOCKFILE'" - exit 1 -} - -ORIG_HASH=$($BUN_BIN bun.lockb --hash) - -$BUN_BIN remove react -$BUN_BIN add react - -NEW_HASH=$($BUN_BIN bun.lockb --hash) - -diff <(echo "$ORIG_HASH") <(echo "$NEW_HASH") || { - echo "ERR: Expected hash to be unchanged, got '$NEW_HASH'" - exit 1 -} - -echo '{ "dependencies": { "react": "17.0.2", "react-dom": "17.0.2" } }' >package.json - -$BUN_BIN install - -echo "var {version} = JSON.parse(require(\"fs\").readFileSync('./node_modules/react-dom/package.json', 'utf8')); if (version !== '17.0.2') {throw new Error('Unexpected react-dom version');}; " >index.js -$BUN_BIN run ./index.js - -echo "var {version} = JSON.parse(require(\"fs\").readFileSync('./node_modules/react/package.json', 'utf8')); if (version !== '17.0.2') {throw new Error('Unexpected react version');}; " >index.js -$BUN_BIN run ./index.js - -# This is just making sure that the JS was executed -realpath -e node_modules/react-dom >/dev/null || { - echo "ERR: Expected react-dom to be installed" - exit 1 -} -realpath -e node_modules/react >/dev/null || { - echo "ERR: Expected react to be installed" - exit 1 -} diff --git a/test/apps/bun-install-utf8.sh b/test/apps/bun-install-utf8.sh deleted file mode 100644 index ac6b684ed..000000000 --- a/test/apps/bun-install-utf8.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -killall -9 $(basename $BUN_BIN) || echo "" - -dir=$(mktemp -d) - -cd $dir - -# https://github.com/oven-sh/bun/issues/115 -echo '{ "author": "Arnaud Barré (https://github.com/ArnaudBarre)" }' >package.json - -$BUN_BIN add react diff --git a/test/apps/bun-install.sh b/test/apps/bun-install.sh deleted file mode 100644 index 18813b2d2..000000000 --- a/test/apps/bun-install.sh +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -dir=$(mktemp -d) - -cd $dir -${NPM_CLIENT:-$(which bun)} add react react-dom @types/react @babel/parser esbuild vite@3.0.0 - -echo "console.log(typeof require(\"react\").createElement);" >index.js -chmod +x index.js - -JS_RUNTIME=${JS_RUNTIME:-"$(which bun)"} - -if [ "$JS_RUNTIME" == "node" ]; then - result="$(node ./index.js)" -fi - -if [ "$JS_RUNTIME" != "node" ]; then - result="$($JS_RUNTIME run ./index.js)" -fi - -echo "console.log(typeof require(\"react-dom\").render);" >index.js -chmod +x index.js - -JS_RUNTIME=${JS_RUNTIME:-"$(which bun)"} - -# If this fails to run, it means we didn't link @babel/parser correctly -$(which grealpath || which realpath) -e ./node_modules/.bin/parser >/dev/null - -# If this fails to run, it means we didn't link esbuild correctly or esbuild's install script broke -# - https://github.com/evanw/esbuild/issues/2558 -./node_modules/.bin/esbuild --version >/dev/null - -VITE_ESBUILD="$(echo node_modules/vite/node_modules/esbuild-*)" -$VITE_ESBUILD/bin/esbuild --version >/dev/null - -if [ "$JS_RUNTIME" == "node" ]; then - result="$(node ./index.js)" -fi - -if [ "$JS_RUNTIME" != "node" ]; then - result="$($JS_RUNTIME run ./index.js)" -fi - -if [ "$result" != "function" ]; then - echo "ERR: Expected 'function', got '$result'" - exit 1 -fi - -${NPM_CLIENT:-$(which bun)} remove react-dom - -if [ -d "node_modules/react-dom" ]; then - echo "ERR: react-dom module still exists in $dir" - exit 1 -fi - -yarn_dot_lock=$(${NPM_CLIENT:-$(which bun)} bun.lockb) - -if echo "$yarn_dot_lock" | grep -q "react-dom"; then - echo "ERR: react-dom module still exists in lockfile" - exit 1 -fi - -${NPM_CLIENT:-$(which bun)} remove @types/react - -yarn_dot_lock=$(${NPM_CLIENT:-$(which bun)} bun.lockb) - -if echo "$yarn_dot_lock" | grep -q "@types/react"; then - echo "ERR: @types/react module still exists in lockfile" - exit 1 -fi - -if echo "$yarn_dot_lock" | grep -q "@types/react"; then - echo "ERR: @types/react module still exists in $dir" - exit 1 -fi - -${NPM_CLIENT:-$(which bun)} remove react - -if [ -d "node_modules/react" ]; then - echo "ERR: react module still exists in $dir" - exit 1 -fi - -if [ -d "bun.lockb" ]; then - echo "ERR: empty bun.lockb should be deleted" - exit 1 -fi diff --git a/test/apps/bun-run-check-nameless-package.json b/test/apps/bun-run-check-nameless-package.json deleted file mode 100644 index 8ace03613..000000000 --- a/test/apps/bun-run-check-nameless-package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "scripts": { - "this-should-work": "echo \"✅ bun run test passed!\"", - "argv": "node -e 'console.log(process.argv)'" - } -} diff --git a/test/apps/bun-run-check-package.json b/test/apps/bun-run-check-package.json deleted file mode 100644 index b6eeda046..000000000 --- a/test/apps/bun-run-check-package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "check", - "scripts": { - "this-should-work": "echo \"✅ bun run test passed!\"", - "argv": "node -e 'console.log(process.argv)'" - } -} diff --git a/test/apps/bun-run-check.sh b/test/apps/bun-run-check.sh deleted file mode 100644 index 25432c422..000000000 --- a/test/apps/bun-run-check.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env bash - -# TODO: move this test to bun once we have a child_process equivalent. -(killall -9 $(basename $BUN_BIN) || echo "") >/dev/null 2>&1 - -# https://github.com/oven-sh/bun/issues/40 -# Define a function (details aren't important) -fn() { :; } -# The important bit: export the function -export -f fn - -rm -rf /tmp/bun-run-check -mkdir -p /tmp/bun-run-check - -cp ./bun-run-check-package.json /tmp/bun-run-check/package.json -cd /tmp/bun-run-check - -$BUN_BIN run bash -- -c "" - -if (($?)); then - echo "Bash exported functions are broken" - exit 1 -fi - -# We need to run these tests for two variations: -# bun run foo "bar" -# bun run foo -- "bar" -# the "--" should be ignored -# in earlier versions of bun, it was required to be present - -$BUN_BIN run bash -c "" -if (($?)); then - echo "Bash exported functions are broken" - exit 1 -fi - -# https://github.com/oven-sh/bun/issues/53 -rm -f /tmp/bun-run-out.expected.txt /tmp/bun-run-out.txt >/dev/null 2>&1 - -$BUN_BIN run --silent argv -- foo bar baz >/tmp/bun-run-out.txt -npm run --silent argv -- foo bar baz >/tmp/bun-run-out.expected.txt - -cmp -s /tmp/bun-run-out.expected.txt /tmp/bun-run-out.txt -if (($?)); then - echo "argv failed" - exit 1 -fi - -rm -f /tmp/bun-run-out.expected.txt /tmp/bun-run-out.txt >/dev/null 2>&1 - -$BUN_BIN run --silent argv foo bar baz >/tmp/bun-run-out.txt -npm run --silent argv -- foo bar baz >/tmp/bun-run-out.expected.txt - -cmp -s /tmp/bun-run-out.expected.txt /tmp/bun-run-out.txt -if (($?)); then - echo "argv failed" - exit 1 -fi - -$BUN_BIN run --silent this-should-work - -if (($?)); then - echo "this-should work failed" - exit 1 -fi - -exit 0 diff --git a/test/bun.js/.prettierignore b/test/bun.js/.prettierignore deleted file mode 100644 index 91b589eb2..000000000 --- a/test/bun.js/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -third-party diff --git a/test/bun.js/FormData.test.ts b/test/bun.js/FormData.test.ts deleted file mode 100644 index 25c0d5d54..000000000 --- a/test/bun.js/FormData.test.ts +++ /dev/null @@ -1,410 +0,0 @@ -import { afterAll, beforeAll, describe, expect, it, test } from "bun:test"; -import fs, { chmodSync, unlinkSync } from "fs"; -import { mkfifo } from "mkfifo"; -import { gc, withoutAggressiveGC } from "./gc"; - -describe("FormData", () => { - it("should be able to append a string", () => { - const formData = new FormData(); - formData.append("foo", "bar"); - expect(formData.get("foo")).toBe("bar"); - expect(formData.getAll("foo")[0]).toBe("bar"); - }); - - it("should be able to append a Blob", async () => { - const formData = new FormData(); - formData.append("foo", new Blob(["bar"])); - expect(await formData.get("foo").text()).toBe("bar"); - expect(formData.getAll("foo")[0] instanceof Blob).toBe(true); - }); - - it("should be able to set a Blob", async () => { - const formData = new FormData(); - formData.set("foo", new Blob(["bar"])); - expect(await formData.get("foo").text()).toBe("bar"); - expect(formData.getAll("foo")[0] instanceof Blob).toBe(true); - }); - - it("should be able to set a string", async () => { - const formData = new FormData(); - formData.set("foo", "bar"); - expect(formData.get("foo")).toBe("bar"); - expect(formData.getAll("foo")[0]).toBe("bar"); - }); - - const multipartFormDataFixturesRawBody = [ - { - name: "simple", - body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo--\r\n', - headers: { - "Content-Type": "multipart/form-data; boundary=foo", - }, - expected: { - foo: "bar", - }, - }, - { - name: "simple with trailing CRLF", - body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo--\r\n\r\n', - headers: { - "Content-Type": "multipart/form-data; boundary=foo", - }, - expected: { - foo: "bar", - }, - }, - { - name: "simple with trailing CRLF and extra CRLF", - body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo--\r\n\r\n\r\n', - headers: { - "Content-Type": "multipart/form-data; boundary=foo", - }, - expected: { - foo: "bar", - }, - }, - { - name: "advanced", - body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo\r\nContent-Disposition: form-data; name="baz"\r\n\r\nqux\r\n--foo--\r\n', - headers: { - "Content-Type": "multipart/form-data; boundary=foo", - }, - expected: { - foo: "bar", - baz: "qux", - }, - }, - { - name: "advanced with multiple values", - body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbaz\r\n--foo--\r\n', - headers: { - "Content-Type": "multipart/form-data; boundary=foo", - }, - expected: { - foo: ["bar", "baz"], - }, - }, - { - name: "advanced with multiple values and trailing CRLF", - body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbaz\r\n--foo--\r\n\r\n', - headers: { - "Content-Type": "multipart/form-data; boundary=foo", - }, - expected: { - foo: ["bar", "baz"], - }, - }, - { - name: "extremely advanced", - body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo\r\nContent-Disposition: form-data; name="baz"\r\n\r\nqux\r\n--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbaz\r\n--foo--\r\n', - headers: { - "Content-Type": "multipart/form-data; boundary=foo", - }, - expected: { - foo: ["bar", "baz"], - baz: "qux", - }, - }, - { - name: "with name and filename", - body: '--foo\r\nContent-Disposition: form-data; name="foo"; filename="bar"\r\n\r\nbaz\r\n--foo--\r\n', - headers: { - "Content-Type": "multipart/form-data; boundary=foo", - }, - expected: { - foo: new Blob(["baz"]), - }, - }, - { - name: "with name and filename and trailing CRLF", - body: '--foo\r\nContent-Disposition: form-data; name="foo"; filename="bar"\r\n\r\nbaz\r\n--foo--\r\n\r\n', - headers: { - "Content-Type": "multipart/form-data; boundary=foo", - }, - expected: { - foo: new Blob(["baz"]), - }, - }, - ]; - - for (const { name, body, headers, expected: expected_ } of multipartFormDataFixturesRawBody) { - const Class = [Response, Request] as const; - for (const C of Class) { - it(`should parse multipart/form-data (${name}) with ${C.name}`, async () => { - const response = C === Response ? new Response(body, { headers }) : new Request({ headers, body }); - const formData = await response.formData(); - expect(formData instanceof FormData).toBe(true); - const entry = {}; - const expected = Object.assign({}, expected_); - - for (const key of formData.keys()) { - const values = formData.getAll(key); - if (values.length > 1) { - entry[key] = values; - } else { - entry[key] = values[0]; - if (entry[key] instanceof Blob) { - expect(expected[key] instanceof Blob).toBe(true); - - entry[key] = await entry[key].text(); - expected[key] = await expected[key].text(); - } else { - expect(typeof entry[key]).toBe(typeof expected[key]); - expect(expected[key] instanceof Blob).toBe(false); - } - } - } - - expect(entry).toEqual(expected); - }); - - it(`should roundtrip multipart/form-data (${name}) with ${C.name}`, async () => { - const response = C === Response ? new Response(body, { headers }) : new Request({ headers, body }); - const formData = await response.formData(); - expect(formData instanceof FormData).toBe(true); - - const request = await new Response(formData).formData(); - expect(request instanceof FormData).toBe(true); - - const aKeys = Array.from(formData.keys()); - const bKeys = Array.from(request.keys()); - expect(aKeys).toEqual(bKeys); - - for (const key of aKeys) { - const aValues = formData.getAll(key); - const bValues = request.getAll(key); - for (let i = 0; i < aValues.length; i++) { - const a = aValues[i]; - const b = bValues[i]; - if (a instanceof Blob) { - expect(b instanceof Blob).toBe(true); - expect(await a.text()).toBe(await b.text()); - } else { - expect(a).toBe(b); - } - } - } - - // Test that it also works with Blob. - const c = await new Blob([body], { type: headers["Content-Type"] }).formData(); - expect(c instanceof FormData).toBe(true); - const cKeys = Array.from(c.keys()); - expect(cKeys).toEqual(bKeys); - for (const key of cKeys) { - const cValues = c.getAll(key); - const bValues = request.getAll(key); - for (let i = 0; i < cValues.length; i++) { - const c = cValues[i]; - const b = bValues[i]; - if (c instanceof Blob) { - expect(b instanceof Blob).toBe(true); - expect(await c.text()).toBe(await b.text()); - } else { - expect(c).toBe(b); - } - } - } - }); - } - } - - it("should throw on missing final boundary", async () => { - const response = new Response('-foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n', { - headers: { - "Content-Type": "multipart/form-data; boundary=foo", - }, - }); - try { - await response.formData(); - throw "should have thrown"; - } catch (e) { - expect(typeof e.message).toBe("string"); - } - }); - - it("should throw on bad boundary", async () => { - const response = new Response('foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n', { - headers: { - "Content-Type": "multipart/form-data; boundary=foo", - }, - }); - try { - await response.formData(); - throw "should have thrown"; - } catch (e) { - expect(typeof e.message).toBe("string"); - } - }); - - it("should throw on bad header", async () => { - const response = new Response('foo\r\nContent-Disposition: form-data; name"foo"\r\n\r\nbar\r\n', { - headers: { - "Content-Type": "multipart/form-data; boundary=foo", - }, - }); - try { - await response.formData(); - throw "should have thrown"; - } catch (e) { - expect(typeof e.message).toBe("string"); - } - }); - - it("file upload on HTTP server (receive)", async () => { - const server = Bun.serve({ - port: 0, - development: false, - async fetch(req) { - const formData = await req.formData(); - return new Response(formData.get("foo")); - }, - }); - - const reqBody = new Request(`http://${server.hostname}:${server.port}`, { - body: '--foo\r\nContent-Disposition: form-data; name="foo"; filename="bar"\r\n\r\nbaz\r\n--foo--\r\n\r\n', - headers: { - "Content-Type": "multipart/form-data; boundary=foo", - }, - method: "POST", - }); - - const res = await fetch(reqBody); - const body = await res.text(); - expect(body).toBe("baz"); - server.stop(true); - }); - - it("file send on HTTP server (receive)", async () => { - const server = Bun.serve({ - port: 0, - development: false, - async fetch(req) { - const formData = await req.formData(); - return new Response(formData); - }, - }); - - const reqBody = new Request(`http://${server.hostname}:${server.port}`, { - body: '--foo\r\nContent-Disposition: form-data; name="foo"; filename="bar"\r\n\r\nbaz\r\n--foo--\r\n\r\n', - headers: { - "Content-Type": "multipart/form-data; boundary=foo", - }, - method: "POST", - }); - - const res = await fetch(reqBody); - const body = await res.formData(); - expect(await (body.get("foo") as Blob).text()).toBe("baz"); - server.stop(true); - }); - - describe("Bun.file support", () => { - describe("roundtrip", () => { - const path = import.meta.dir + "/form-data-fixture.txt"; - for (const C of [Request, Response]) { - it(`with ${C.name}`, async () => { - await Bun.write(path, "foo!"); - const formData = new FormData(); - formData.append("foo", Bun.file(path)); - const response = C === Response ? new Response(formData) : new Request({ body: formData }); - expect(response.headers.get("content-type")?.startsWith("multipart/form-data;")).toBe(true); - - const formData2 = await response.formData(); - expect(formData2 instanceof FormData).toBe(true); - expect(formData2.get("foo") instanceof Blob).toBe(true); - expect(await (formData2.get("foo") as Blob).text()).toBe("foo!"); - }); - } - }); - - it("doesnt crash when file is missing", async () => { - const formData = new FormData(); - formData.append("foo", Bun.file("missing")); - expect(() => new Response(formData)).toThrow(); - }); - }); - - it("Bun.inspect", () => { - const formData = new FormData(); - formData.append("foo", "bar"); - formData.append("foo", new Blob(["bar"])); - formData.append("bar", "baz"); - formData.append("boop", Bun.file("missing")); - expect(Bun.inspect(formData).length > 0).toBe(true); - }); - - describe("URLEncoded", () => { - test("should parse URL encoded", async () => { - const response = new Response("foo=bar&baz=qux", { - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - }); - const formData = await response.formData(); - expect(formData instanceof FormData).toBe(true); - expect(formData.get("foo")).toBe("bar"); - expect(formData.get("baz")).toBe("qux"); - }); - - test("should parse URLSearchParams", async () => { - const searchParams = new URLSearchParams("foo=bar&baz=qux"); - const response = new Response(searchParams); - expect(response.headers.get("Content-Type")).toBe("application/x-www-form-urlencoded;charset=UTF-8"); - - expect(searchParams instanceof URLSearchParams).toBe(true); - expect(searchParams.get("foo")).toBe("bar"); - - const formData = await response.formData(); - expect(formData instanceof FormData).toBe(true); - expect(formData.get("foo")).toBe("bar"); - expect(formData.get("baz")).toBe("qux"); - }); - - test("should parse URL encoded with charset", async () => { - const response = new Response("foo=bar&baz=qux", { - headers: { - "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", - }, - }); - const formData = await response.formData(); - expect(formData instanceof FormData).toBe(true); - expect(formData.get("foo")).toBe("bar"); - expect(formData.get("baz")).toBe("qux"); - }); - - test("should parse URL encoded with charset and space", async () => { - const response = new Response("foo=bar&baz=qux+quux", { - headers: { - "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", - }, - }); - const formData = await response.formData(); - expect(formData instanceof FormData).toBe(true); - expect(formData.get("foo")).toBe("bar"); - expect(formData.get("baz")).toBe("qux quux"); - }); - - test("should parse URL encoded with charset and plus", async () => { - const response = new Response("foo=bar&baz=qux+quux", { - headers: { - "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", - }, - }); - const formData = await response.formData(); - expect(formData instanceof FormData).toBe(true); - expect(formData.get("foo")).toBe("bar"); - expect(formData.get("baz")).toBe("qux quux"); - }); - - it("should handle multiple values", async () => { - const response = new Response("foo=bar&foo=baz", { - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - }); - const formData = await response.formData(); - expect(formData instanceof FormData).toBe(true); - expect(formData.getAll("foo")).toEqual(["bar", "baz"]); - }); - }); -}); diff --git a/test/bun.js/abort-signal-timeout.test.js b/test/bun.js/abort-signal-timeout.test.js deleted file mode 100644 index 7d741b2ad..000000000 --- a/test/bun.js/abort-signal-timeout.test.js +++ /dev/null @@ -1,12 +0,0 @@ -import { expect, test } from "bun:test"; - -test.skip("AbortSignal.timeout", done => { - const abort = AbortSignal.timeout(10); - abort.addEventListener("abort", event => { - done(); - }); - - // AbortSignal.timeout doesn't keep the event loop / process alive - // so we set a no-op timeout - setTimeout(() => {}, 11); -}); diff --git a/test/bun.js/arraybuffersink.test.ts b/test/bun.js/arraybuffersink.test.ts deleted file mode 100644 index f0df03d90..000000000 --- a/test/bun.js/arraybuffersink.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { ArrayBufferSink } from "bun"; -import { describe, expect, it } from "bun:test"; -import { withoutAggressiveGC } from "gc"; - -describe("ArrayBufferSink", () => { - const fixtures = [ - [ - ["abcdefghijklmnopqrstuvwxyz"], - new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz"), - "abcdefghijklmnopqrstuvwxyz", - ], - [ - ["abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"], - new TextEncoder().encode("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", - ], - [ - ["😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"], - new TextEncoder().encode("😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"), - "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌", - ], - [ - ["abcdefghijklmnopqrstuvwxyz", "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"], - new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"), - "abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌", - ], - [ - ["abcdefghijklmnopqrstuvwxyz", "😋", " Get Emoji — All Emojis", " to ✂️ Copy and 📋 Paste 👌"], - new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"), - "(rope) " + "abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌", - ], - [ - [ - new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz"), - "😋", - " Get Emoji — All Emojis", - " to ✂️ Copy and 📋 Paste 👌", - ], - new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"), - "(array) " + "abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌", - ], - ] as const; - - for (const [input, expected, label] of fixtures) { - it(`${JSON.stringify(label)}`, () => { - const sink = new ArrayBufferSink(); - withoutAggressiveGC(() => { - for (let i = 0; i < input.length; i++) { - const el = input[i]; - if (typeof el !== "number") { - sink.write(el); - } - } - }); - const output = new Uint8Array(sink.end()); - withoutAggressiveGC(() => { - for (let i = 0; i < expected.length; i++) { - expect(output[i]).toBe(expected[i]); - } - }); - expect(output.byteLength).toBe(expected.byteLength); - }); - } -}); diff --git a/test/bun.js/assert-test.test.ts b/test/bun.js/assert-test.test.ts deleted file mode 100644 index 1723b7d47..000000000 --- a/test/bun.js/assert-test.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import assert from "assert"; -import { expect, test } from "bun:test"; - -// https://github.com/oven-sh/bun/issues/941 -test("assert as a function does not throw", () => assert(true)); -test("assert as a function does throw", () => { - try { - assert(false); - expect(false).toBe(true); - } catch (e) {} -}); diff --git a/test/bun.js/atob.test.js b/test/bun.js/atob.test.js deleted file mode 100644 index 4945829e1..000000000 --- a/test/bun.js/atob.test.js +++ /dev/null @@ -1,77 +0,0 @@ -import { expect, it } from "bun:test"; - -function expectInvalidCharacters(val) { - try { - atob(val); - throw new Error("Expected error"); - } catch (error) { - expect(error.message).toBe("The string contains invalid characters."); - } -} - -it("atob", () => { - expect(atob("YQ==")).toBe("a"); - expect(atob("YWI=")).toBe("ab"); - expect(atob("YWJj")).toBe("abc"); - expect(atob("YWJjZA==")).toBe("abcd"); - expect(atob("YWJjZGU=")).toBe("abcde"); - expect(atob("YWJjZGVm")).toBe("abcdef"); - expect(atob("zzzz")).toBe("Ï<ó"); - expect(atob("")).toBe(""); - expect(atob(null)).toBe("žée"); - expect(atob("6ek=")).toBe("éé"); - expect(atob("6ek")).toBe("éé"); - expect(atob("gIE=")).toBe("€"); - expect(atob("zz")).toBe("Ï"); - expect(atob("zzz")).toBe("Ï<"); - expect(atob("zzz=")).toBe("Ï<"); - expect(atob(" YQ==")).toBe("a"); - expect(atob("YQ==\u000a")).toBe("a"); - - try { - atob(); - } catch (error) { - expect(error.name).toBe("TypeError"); - } - expectInvalidCharacters(undefined); - expectInvalidCharacters(" abcd==="); - expectInvalidCharacters("abcd=== "); - expectInvalidCharacters("abcd ==="); - expectInvalidCharacters("тест"); - expectInvalidCharacters("z"); - expectInvalidCharacters("zzz=="); - expectInvalidCharacters("zzz==="); - expectInvalidCharacters("zzz===="); - expectInvalidCharacters("zzz====="); - expectInvalidCharacters("zzzzz"); - expectInvalidCharacters("z=zz"); - expectInvalidCharacters("="); - expectInvalidCharacters("=="); - expectInvalidCharacters("==="); - expectInvalidCharacters("===="); - expectInvalidCharacters("====="); -}); - -it("btoa", () => { - expect(btoa("a")).toBe("YQ=="); - expect(btoa("ab")).toBe("YWI="); - expect(btoa("abc")).toBe("YWJj"); - expect(btoa("abcd")).toBe("YWJjZA=="); - expect(btoa("abcde")).toBe("YWJjZGU="); - expect(btoa("abcdef")).toBe("YWJjZGVm"); - expect(typeof btoa).toBe("function"); - try { - btoa(); - throw new Error("Expected error"); - } catch (error) { - expect(error.name).toBe("TypeError"); - } - var window = "[object Window]"; - expect(btoa("")).toBe(""); - expect(btoa(null)).toBe("bnVsbA=="); - expect(btoa(undefined)).toBe("dW5kZWZpbmVk"); - expect(btoa(window)).toBe("W29iamVjdCBXaW5kb3dd"); - expect(btoa("éé")).toBe("6ek="); - expect(btoa("\u0080\u0081")).toBe("gIE="); - expect(btoa(Bun)).toBe(btoa("[object Bun]")); -}); diff --git a/test/bun.js/bash-echo.sh b/test/bun.js/bash-echo.sh deleted file mode 100644 index 57bca4b01..000000000 --- a/test/bun.js/bash-echo.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -myvar=$(cat /dev/stdin) -echo -e "$myvar" diff --git a/test/bun.js/baz.js b/test/bun.js/baz.js deleted file mode 100644 index 5837bb3bb..000000000 --- a/test/bun.js/baz.js +++ /dev/null @@ -1,2 +0,0 @@ -// this file is used in resolve.test.js -export default {}; diff --git a/test/bun.js/bigint.test.js b/test/bun.js/bigint.test.js deleted file mode 100644 index 46ad50b84..000000000 --- a/test/bun.js/bigint.test.js +++ /dev/null @@ -1,14 +0,0 @@ -import { describe, expect, it } from "bun:test"; - -describe("BigInt", () => { - it("compares correctly (literal)", () => { - expect(42n).toBe(42n); - }); - - it("compares correctly (object)", () => { - expect(BigInt(42n)).toBe(BigInt(42n)); - expect(42n).toBe(BigInt(42n)); - expect(BigInt(Bun.inspect(42n).substring(0, 2))).toBe(BigInt(42n)); - expect(BigInt(42n).valueOf()).toBe(BigInt(42n)); - }); -}); diff --git a/test/bun.js/body-mixin-errors.test.ts b/test/bun.js/body-mixin-errors.test.ts deleted file mode 100644 index f57bbc56c..000000000 --- a/test/bun.js/body-mixin-errors.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { it, describe, expect } from "bun:test"; - -describe("body-mixin-errors", () => { - it("should fail when bodyUsed", async () => { - var res = new Response("a"); - expect(res.bodyUsed).toBe(false); - await res.text(); - expect(res.bodyUsed).toBe(true); - - try { - await res.text(); - throw new Error("should not get here"); - } catch (e: any) { - expect(e.message).toBe("Body already used"); - } - }); -}); diff --git a/test/bun.js/body-stream.test.ts b/test/bun.js/body-stream.test.ts deleted file mode 100644 index 1cd932ed9..000000000 --- a/test/bun.js/body-stream.test.ts +++ /dev/null @@ -1,451 +0,0 @@ -// @ts-nocheck -import { file, gc, serve, ServeOptions } from "bun"; -import { afterAll, afterEach, describe, expect, it, test } from "bun:test"; -import { readFileSync } from "fs"; - -var port = 0; - -{ - const BodyMixin = [ - Request.prototype.arrayBuffer, - Request.prototype.blob, - Request.prototype.text, - Request.prototype.json, - ]; - const useRequestObjectValues = [true, false]; - - for (let RequestPrototypeMixin of BodyMixin) { - for (let useRequestObject of useRequestObjectValues) { - describe(`Request.prototoype.${RequestPrototypeMixin.name}() ${ - useRequestObject ? "fetch(req)" : "fetch(url)" - }`, () => { - const inputFixture = [ - [JSON.stringify("Hello World"), JSON.stringify("Hello World")], - [JSON.stringify("Hello World 123"), Buffer.from(JSON.stringify("Hello World 123")).buffer], - [JSON.stringify("Hello World 456"), Buffer.from(JSON.stringify("Hello World 456"))], - [ - JSON.stringify("EXTREMELY LONG VERY LONG STRING WOW SO LONG YOU WONT BELIEVE IT! ".repeat(100)), - Buffer.from( - JSON.stringify("EXTREMELY LONG VERY LONG STRING WOW SO LONG YOU WONT BELIEVE IT! ".repeat(100)), - ), - ], - [ - JSON.stringify("EXTREMELY LONG 🔥 UTF16 🔥 VERY LONG STRING WOW SO LONG YOU WONT BELIEVE IT! ".repeat(100)), - Buffer.from( - JSON.stringify( - "EXTREMELY LONG 🔥 UTF16 🔥 VERY LONG STRING WOW SO LONG YOU WONT BELIEVE IT! ".repeat(100), - ), - ), - ], - ]; - - for (const [name, input] of inputFixture) { - test(`${name.slice(0, Math.min(name.length ?? name.byteLength, 64))}`, async () => { - await runInServer( - { - async fetch(req) { - var result = await RequestPrototypeMixin.call(req); - if (RequestPrototypeMixin === Request.prototype.json) { - result = JSON.stringify(result); - } - if (typeof result === "string") { - expect(result.length).toBe(name.length); - expect(result).toBe(name); - } else if (result && result instanceof Blob) { - expect(result.size).toBe(new TextEncoder().encode(name).byteLength); - expect(await result.text()).toBe(name); - } else { - expect(result.byteLength).toBe(Buffer.from(input).byteLength); - expect(Bun.SHA1.hash(result, "base64")).toBe(Bun.SHA1.hash(input, "base64")); - } - return new Response(result, { - headers: req.headers, - }); - }, - }, - async url => { - var response; - - // once, then batch of 5 - - if (useRequestObject) { - response = await fetch( - new Request({ - body: input, - method: "POST", - url: url, - headers: { - "content-type": "text/plain", - }, - }), - ); - } else { - response = await fetch(url, { - body: input, - method: "POST", - headers: { - "content-type": "text/plain", - }, - }); - } - - expect(response.status).toBe(200); - expect(response.headers.get("content-length")).toBe(String(Buffer.from(input).byteLength)); - expect(response.headers.get("content-type")).toBe("text/plain"); - expect(await response.text()).toBe(name); - - var promises = new Array(5); - for (let i = 0; i < 5; i++) { - if (useRequestObject) { - promises[i] = await fetch( - new Request({ - body: input, - method: "POST", - url: url, - headers: { - "content-type": "text/plain", - "x-counter": i, - }, - }), - ); - } else { - promises[i] = await fetch(url, { - body: input, - method: "POST", - headers: { - "content-type": "text/plain", - "x-counter": i, - }, - }); - } - } - - const results = await Promise.all(promises); - for (let i = 0; i < 5; i++) { - const response = results[i]; - expect(response.status).toBe(200); - expect(response.headers.get("content-length")).toBe(String(Buffer.from(input).byteLength)); - expect(response.headers.get("content-type")).toBe("text/plain"); - expect(response.headers.get("x-counter")).toBe(String(i)); - expect(await response.text()).toBe(name); - } - }, - ); - }); - } - }); - } - } -} - -var existingServer; -async function runInServer(opts: ServeOptions, cb: (url: string) => void | Promise) { - var server; - const handler = { - ...opts, - port: port++, - fetch(req) { - try { - return opts.fetch(req); - } catch (e) { - console.error(e.message); - console.log(e.stack); - throw e; - } - }, - error(err) { - console.log(err.message); - console.log(err.stack); - throw err; - }, - }; - - if (!existingServer) { - existingServer = server = Bun.serve(handler); - } else { - server = existingServer; - server.reload(handler); - } - - try { - await cb(`http://${server.hostname}:${server.port}`); - } catch (e) { - throw e; - } finally { - } -} - -afterAll(() => { - existingServer && existingServer.close(); - existingServer = null; -}); - -function fillRepeating(dstBuffer, start, end) { - let len = dstBuffer.length, - sLen = end - start, - p = sLen; - while (p < len) { - if (p + sLen > len) sLen = len - p; - dstBuffer.copyWithin(p, start, sLen); - p += sLen; - sLen <<= 1; - } -} - -function gc() { - Bun.gc(true); -} - -describe("reader", function () { - try { - // - empty - // - 1 byte - // - less than the InlineBlob limit - // - multiple chunks - // - backpressure - for (let inputLength of [0, 1, 2, 12, 95, 1024, 1024 * 1024, 1024 * 1024 * 2]) { - var bytes = new Uint8Array(inputLength); - { - const chunk = Math.min(bytes.length, 256); - for (var i = 0; i < chunk; i++) { - bytes[i] = 255 - i; - } - } - - if (bytes.length > 255) fillRepeating(bytes, 0, bytes.length); - - for (const huge_ of [ - bytes, - bytes.buffer, - new DataView(bytes.buffer), - new Int8Array(bytes), - new Blob([bytes]), - - new Uint16Array(bytes), - new Uint32Array(bytes), - new Float64Array(bytes), - - new Int16Array(bytes), - new Int32Array(bytes), - new Float32Array(bytes), - - // make sure we handle subarray() as expected when reading - // typed arrays from native code - new Int16Array(bytes).subarray(1), - new Int16Array(bytes).subarray(0, new Int16Array(bytes).byteLength - 1), - new Int32Array(bytes).subarray(1), - new Int32Array(bytes).subarray(0, new Int32Array(bytes).byteLength - 1), - new Float32Array(bytes).subarray(1), - new Float32Array(bytes).subarray(0, new Float32Array(bytes).byteLength - 1), - new Int16Array(bytes).subarray(0, 1), - new Int32Array(bytes).subarray(0, 1), - new Float32Array(bytes).subarray(0, 1), - ]) { - gc(); - const thisArray = huge_; - it(`works with ${thisArray.constructor.name}(${ - thisArray.byteLength ?? thisArray.size - }:${inputLength}) via req.body.getReader() in chunks`, async () => { - var huge = thisArray; - var called = false; - gc(); - - const expectedHash = - huge instanceof Blob - ? Bun.SHA1.hash(new Uint8Array(await huge.arrayBuffer()), "base64") - : Bun.SHA1.hash(huge, "base64"); - const expectedSize = huge instanceof Blob ? huge.size : huge.byteLength; - - const out = await runInServer( - { - async fetch(req) { - try { - expect(req.headers.get("x-custom")).toBe("hello"); - expect(req.headers.get("content-type")).toBe("text/plain"); - expect(req.headers.get("user-agent")).toBe(navigator.userAgent); - - gc(); - expect(req.headers.get("x-custom")).toBe("hello"); - expect(req.headers.get("content-type")).toBe("text/plain"); - expect(req.headers.get("user-agent")).toBe(navigator.userAgent); - - var reader = req.body.getReader(); - called = true; - var buffers = []; - while (true) { - var { done, value } = await reader.read(); - if (done) break; - buffers.push(value); - } - const out = new Blob(buffers); - gc(); - expect(out.size).toBe(expectedSize); - expect(Bun.SHA1.hash(await out.arrayBuffer(), "base64")).toBe(expectedHash); - expect(req.headers.get("x-custom")).toBe("hello"); - expect(req.headers.get("content-type")).toBe("text/plain"); - expect(req.headers.get("user-agent")).toBe(navigator.userAgent); - gc(); - return new Response(out, { - headers: req.headers, - }); - } catch (e) { - console.error(e); - throw e; - } - }, - }, - async url => { - gc(); - const response = await fetch(url, { - body: huge, - method: "POST", - headers: { - "content-type": "text/plain", - "x-custom": "hello", - "x-typed-array": thisArray.constructor.name, - }, - }); - huge = undefined; - expect(response.status).toBe(200); - const response_body = new Uint8Array(await response.arrayBuffer()); - - expect(response_body.byteLength).toBe(expectedSize); - expect(Bun.SHA1.hash(response_body, "base64")).toBe(expectedHash); - - gc(); - expect(response.headers.get("content-type")).toBe("text/plain"); - gc(); - }, - ); - expect(called).toBe(true); - gc(); - return out; - }); - - for (let isDirectStream of [true, false]) { - const positions = ["begin", "end"]; - const inner = thisArray => { - for (let position of positions) { - it(`streaming back ${thisArray.constructor.name}(${ - thisArray.byteLength ?? thisArray.size - }:${inputLength}) starting request.body.getReader() at ${position}`, async () => { - var huge = thisArray; - var called = false; - gc(); - - const expectedHash = - huge instanceof Blob - ? Bun.SHA1.hash(new Uint8Array(await huge.arrayBuffer()), "base64") - : Bun.SHA1.hash(huge, "base64"); - const expectedSize = huge instanceof Blob ? huge.size : huge.byteLength; - - const out = await runInServer( - { - async fetch(req) { - try { - var reader; - - if (position === "begin") { - reader = req.body.getReader(); - } - - if (position === "end") { - await 1; - reader = req.body.getReader(); - } - - expect(req.headers.get("x-custom")).toBe("hello"); - expect(req.headers.get("content-type")).toBe("text/plain"); - expect(req.headers.get("user-agent")).toBe(navigator.userAgent); - - gc(); - expect(req.headers.get("x-custom")).toBe("hello"); - expect(req.headers.get("content-type")).toBe("text/plain"); - expect(req.headers.get("user-agent")).toBe(navigator.userAgent); - - const direct = { - type: "direct", - async pull(controller) { - while (true) { - const { done, value } = await reader.read(); - if (done) { - called = true; - controller.end(); - - return; - } - controller.write(value); - } - }, - }; - - const web = { - async pull(controller) { - while (true) { - const { done, value } = await reader.read(); - if (done) { - called = true; - controller.close(); - return; - } - controller.enqueue(value); - } - }, - }; - - return new Response(new ReadableStream(isDirectStream ? direct : web), { - headers: req.headers, - }); - } catch (e) { - console.error(e); - throw e; - } - }, - }, - async url => { - gc(); - const response = await fetch(url, { - body: huge, - method: "POST", - headers: { - "content-type": "text/plain", - "x-custom": "hello", - "x-typed-array": thisArray.constructor.name, - }, - }); - huge = undefined; - expect(response.status).toBe(200); - const response_body = new Uint8Array(await response.arrayBuffer()); - - expect(response_body.byteLength).toBe(expectedSize); - expect(Bun.SHA1.hash(response_body, "base64")).toBe(expectedHash); - - gc(); - if (!response.headers.has("content-type")) { - console.error(Object.fromEntries(response.headers.entries())); - } - - expect(response.headers.get("content-type")).toBe("text/plain"); - gc(); - }, - ); - expect(called).toBe(true); - gc(); - return out; - }); - } - }; - - if (isDirectStream) { - describe(" direct stream", () => inner(thisArray)); - } else { - describe("default stream", () => inner(thisArray)); - } - } - } - } - } catch (e) { - console.error(e); - throw e; - } -}); diff --git a/test/bun.js/buffer.test.js b/test/bun.js/buffer.test.js deleted file mode 100644 index b8fade4d2..000000000 --- a/test/bun.js/buffer.test.js +++ /dev/null @@ -1,2563 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, test } from "bun:test"; -import { gc } from "./gc"; - -const BufferModule = await import("buffer"); - -beforeEach(() => gc()); -afterEach(() => gc()); - -function assert(a) { - expect(a).toBeTruthy(); -} - -Object.assign(assert, { - ok(a) { - expect(a).toBeTruthy(); - }, - deepStrictEqual(a, b) { - expect(b).toStrictEqual(a); - }, - strictEqual(a, b) { - expect(a).toBe(b); - }, - throws(a, b) { - expect(a).toThrow(); - }, -}); - -// https://github.com/oven-sh/bun/issues/2052 -it("Buffer global is settable", () => { - var prevBuffer = globalThis.Buffer; - globalThis.Buffer = 42; - expect(globalThis.Buffer).toBe(42); - globalThis.Buffer = prevBuffer; - expect(globalThis.Buffer).toBe(BufferModule.Buffer); - expect(globalThis.Buffer).toBe(prevBuffer); -}); - -it("Buffer.alloc", () => { - // Verify the maximum Uint8Array size. There is no concrete limit by spec. The - // internal limits should be updated if this fails. - assert.throws(() => new Uint8Array(2 ** 32 + 1), { - message: "Invalid typed array length: 4294967297", - }); - - const b = Buffer.allocUnsafe(1024); - assert.strictEqual(b.length, 1024); - - b[0] = -1; - assert.strictEqual(b[0], 255); - - for (let i = 0; i < 1024; i++) { - b[i] = i % 256; - } - - for (let i = 0; i < 1024; i++) { - assert.strictEqual(i % 256, b[i]); - } - - const c = Buffer.allocUnsafe(512); - assert.strictEqual(c.length, 512); - - const d = Buffer.from([]); - assert.strictEqual(d.length, 0); - - // Test offset properties - { - const b = Buffer.alloc(128); - assert.strictEqual(b.length, 128); - assert.strictEqual(b.byteOffset, 0); - assert.strictEqual(b.offset, 0); - } - - // Test creating a Buffer from a Uint32Array - { - const ui32 = new Uint32Array(4).fill(42); - const e = Buffer.from(ui32); - for (const [index, value] of e.entries()) { - assert.strictEqual(value, ui32[index]); - } - } - // Test creating a Buffer from a Uint32Array (old constructor) - { - const ui32 = new Uint32Array(4).fill(42); - const e = Buffer(ui32); - for (const [key, value] of e.entries()) { - assert.deepStrictEqual(value, ui32[key]); - } - } - - // Test invalid encoding for Buffer.toString - assert.throws(() => b.toString("invalid"), /Unknown encoding: invalid/); - // Invalid encoding for Buffer.write - assert.throws(() => b.write("test string", 0, 5, "invalid"), /Unknown encoding: invalid/); - // Unsupported arguments for Buffer.write - // assert.throws(() => b.write("test", "utf8", 0), { - // code: "ERR_INVALID_ARG_TYPE", - // }); - - // Try to create 0-length buffers. Should not throw. - Buffer.from(""); - Buffer.from("", "ascii"); - Buffer.from("", "latin1"); - Buffer.alloc(0); - Buffer.allocUnsafe(0); - new Buffer(""); - new Buffer("", "ascii"); - new Buffer("", "latin1"); - new Buffer("", "binary"); - Buffer(0); - - const outOfRangeError = { - code: "ERR_OUT_OF_RANGE", - name: "RangeError", - }; - - // Try to write a 0-length string beyond the end of b - // assert.throws(() => b.write("", 2048), outOfRangeError); - - // // Throw when writing to negative offset - // assert.throws(() => b.write("a", -1), outOfRangeError); - - // // Throw when writing past bounds from the pool - // assert.throws(() => b.write("a", 2048), outOfRangeError); - - // // Throw when writing to negative offset - // assert.throws(() => b.write("a", -1), outOfRangeError); - - // Try to copy 0 bytes worth of data into an empty buffer - b.copy(Buffer.alloc(0), 0, 0, 0); - - // Try to copy 0 bytes past the end of the target buffer - b.copy(Buffer.alloc(0), 1, 1, 1); - b.copy(Buffer.alloc(1), 1, 1, 1); - - // Try to copy 0 bytes from past the end of the source buffer - b.copy(Buffer.alloc(1), 0, 2048, 2048); - - // Testing for smart defaults and ability to pass string values as offset - { - const writeTest = Buffer.from("abcdes"); - writeTest.write("n", "ascii"); - assert.throws(() => writeTest.write("o", "1", "ascii"), { - code: "ERR_INVALID_ARG_TYPE", - }); - writeTest.write("o", 1, "ascii"); - writeTest.write("d", 2, "ascii"); - writeTest.write("e", 3, "ascii"); - writeTest.write("j", 4, "ascii"); - assert.strictEqual(writeTest.toString(), "nodejs"); - } - - // Offset points to the end of the buffer and does not throw. - // (see https://github.com/nodejs/node/issues/8127). - Buffer.alloc(1).write("", 1, 0); - - // ASCII slice test - { - const asciiString = "hello world"; - - for (let i = 0; i < asciiString.length; i++) { - b[i] = asciiString.charCodeAt(i); - } - const asciiSlice = b.toString("ascii", 0, asciiString.length); - assert.strictEqual(asciiString, asciiSlice); - } - - { - const asciiString = "hello world"; - const offset = 100; - - assert.strictEqual(asciiString.length, b.write(asciiString, offset, "ascii")); - const asciiSlice = b.toString("ascii", offset, offset + asciiString.length); - assert.strictEqual(asciiString, asciiSlice); - } - - { - const asciiString = "hello world"; - const offset = 100; - - const sliceA = b.slice(offset, offset + asciiString.length); - const sliceB = b.slice(offset, offset + asciiString.length); - for (let i = 0; i < asciiString.length; i++) { - assert.strictEqual(sliceA[i], sliceB[i]); - } - } - - // UTF-8 slice test - { - const utf8String = "¡hέlló wôrld!"; - const offset = 100; - - b.write(utf8String, 0, Buffer.byteLength(utf8String), "utf8"); - let utf8Slice = b.toString("utf8", 0, Buffer.byteLength(utf8String)); - assert.strictEqual(utf8String, utf8Slice); - - assert.strictEqual(Buffer.byteLength(utf8String), b.write(utf8String, offset, "utf8")); - utf8Slice = b.toString("utf8", offset, offset + Buffer.byteLength(utf8String)); - assert.strictEqual(utf8String, utf8Slice); - - const sliceA = b.slice(offset, offset + Buffer.byteLength(utf8String)); - const sliceB = b.slice(offset, offset + Buffer.byteLength(utf8String)); - for (let i = 0; i < Buffer.byteLength(utf8String); i++) { - assert.strictEqual(sliceA[i], sliceB[i]); - } - } - - { - const slice = b.slice(100, 150); - assert.strictEqual(slice.length, 50); - for (let i = 0; i < 50; i++) { - assert.strictEqual(b[100 + i], slice[i]); - } - } - - { - // Make sure only top level parent propagates from allocPool - const b = Buffer.allocUnsafe(5); - const c = b.slice(0, 4); - const d = c.slice(0, 2); - assert.strictEqual(b.parent, c.parent); - assert.strictEqual(b.parent, d.parent); - } - - { - // Also from a non-pooled instance - const b = Buffer.allocUnsafeSlow(5); - const c = b.slice(0, 4); - const d = c.slice(0, 2); - assert.strictEqual(c.parent, d.parent); - } - - { - // Bug regression test - const testValue = "\u00F6\u65E5\u672C\u8A9E"; // ö日本語 - const buffer = Buffer.allocUnsafe(32); - const size = buffer.write(testValue, 0, "utf8"); - const slice = buffer.toString("utf8", 0, size); - assert.strictEqual(slice, testValue); - } - - { - // Test triple slice - const a = Buffer.allocUnsafe(8); - for (let i = 0; i < 8; i++) a[i] = i; - const b = a.slice(4, 8); - assert.strictEqual(b[0], 4); - assert.strictEqual(b[1], 5); - assert.strictEqual(b[2], 6); - assert.strictEqual(b[3], 7); - const c = b.slice(2, 4); - assert.strictEqual(c[0], 6); - assert.strictEqual(c[1], 7); - } - - { - const d = Buffer.from([23, 42, 255]); - assert.strictEqual(d.length, 3); - assert.strictEqual(d[0], 23); - assert.strictEqual(d[1], 42); - assert.strictEqual(d[2], 255); - assert.deepStrictEqual(d, Buffer.from(d)); - } - - { - // Test for proper UTF-8 Encoding - const e = Buffer.from("über"); - assert.deepStrictEqual(e, Buffer.from([195, 188, 98, 101, 114])); - } - - { - // Test for proper ascii Encoding, length should be 4 - const f = Buffer.from("über", "ascii"); - assert.deepStrictEqual(f, Buffer.from([252, 98, 101, 114])); - } - - ["ucs2", "ucs-2", "utf16le", "utf-16le"].forEach(encoding => { - { - // Test for proper UTF16LE encoding, length should be 8 - const f = Buffer.from("über", encoding); - assert.deepStrictEqual(f, Buffer.from([252, 0, 98, 0, 101, 0, 114, 0])); - } - - { - // Length should be 12 - const f = Buffer.from("привет", encoding); - assert.deepStrictEqual(f, Buffer.from([63, 4, 64, 4, 56, 4, 50, 4, 53, 4, 66, 4])); - assert.strictEqual(f.toString(encoding), "привет"); - } - - { - const f = Buffer.from([0, 0, 0, 0, 0]); - assert.strictEqual(f.length, 5); - const size = f.write("あいうえお", encoding); - assert.strictEqual(size, 4); - assert.deepStrictEqual(f, Buffer.from([0x42, 0x30, 0x44, 0x30, 0x00])); - } - }); - - { - const f = Buffer.from("\uD83D\uDC4D", "utf-16le"); // THUMBS UP SIGN (U+1F44D) - assert.strictEqual(f.length, 4); - assert.deepStrictEqual(f, Buffer.from("3DD84DDC", "hex")); - } - - // Test construction from arrayish object - { - const arrayIsh = { 0: 0, 1: 1, 2: 2, 3: 3, length: 4 }; - let g = Buffer.from(arrayIsh); - assert.deepStrictEqual(g, Buffer.from([0, 1, 2, 3])); - const strArrayIsh = { 0: "0", 1: "1", 2: "2", 3: "3", length: 4 }; - g = Buffer.from(strArrayIsh); - assert.deepStrictEqual(g, Buffer.from([0, 1, 2, 3])); - } - - // - // Test toString('base64') - // - assert.strictEqual(Buffer.from("Man").toString("base64"), "TWFu"); - assert.strictEqual(Buffer.from("Woman").toString("base64"), "V29tYW4="); - - // - // Test toString('base64url') - // - assert.strictEqual(Buffer.from("Man").toString("base64url"), "TWFu"); - assert.strictEqual(Buffer.from("Woman").toString("base64url"), "V29tYW4"); - - { - // Test that regular and URL-safe base64 both work both ways - const expected = [0xff, 0xff, 0xbe, 0xff, 0xef, 0xbf, 0xfb, 0xef, 0xff]; - assert.deepStrictEqual(Buffer.from("//++/++/++//", "base64"), Buffer.from(expected)); - assert.deepStrictEqual(Buffer.from("__--_--_--__", "base64"), Buffer.from(expected)); - assert.deepStrictEqual(Buffer.from("//++/++/++//", "base64url"), Buffer.from(expected)); - assert.deepStrictEqual(Buffer.from("__--_--_--__", "base64url"), Buffer.from(expected)); - } - - const base64flavors = ["base64", "base64url"]; - - { - // Test that regular and URL-safe base64 both work both ways with padding - const expected = [0xff, 0xff, 0xbe, 0xff, 0xef, 0xbf, 0xfb, 0xef, 0xff, 0xfb]; - assert.deepStrictEqual(Buffer.from("//++/++/++//+w==", "base64"), Buffer.from(expected)); - assert.deepStrictEqual(Buffer.from("//++/++/++//+w==", "base64"), Buffer.from(expected)); - assert.deepStrictEqual(Buffer.from("//++/++/++//+w==", "base64url"), Buffer.from(expected)); - assert.deepStrictEqual(Buffer.from("//++/++/++//+w==", "base64url"), Buffer.from(expected)); - } - - { - // big example - const quote = - "Man is distinguished, not only by his reason, but by this " + - "singular passion from other animals, which is a lust " + - "of the mind, that by a perseverance of delight in the " + - "continued and indefatigable generation of knowledge, " + - "exceeds the short vehemence of any carnal pleasure."; - const expected = - "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb" + - "24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlci" + - "BhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQ" + - "gYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu" + - "dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZ" + - "GdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm" + - "5hbCBwbGVhc3VyZS4="; - assert.strictEqual(Buffer.from(quote).toString("base64"), expected); - assert.strictEqual( - Buffer.from(quote).toString("base64url"), - expected.replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", ""), - ); - - base64flavors.forEach(encoding => { - let b = Buffer.allocUnsafe(1024); - let bytesWritten = b.write(expected, 0, encoding); - assert.strictEqual(quote.length, bytesWritten); - assert.strictEqual(quote, b.toString("ascii", 0, quote.length)); - - // Check that the base64 decoder ignores whitespace - const expectedWhite = - `${expected.slice(0, 60)} \n` + - `${expected.slice(60, 120)} \n` + - `${expected.slice(120, 180)} \n` + - `${expected.slice(180, 240)} \n` + - `${expected.slice(240, 300)}\n` + - `${expected.slice(300, 360)}\n`; - b = Buffer.allocUnsafe(1024); - bytesWritten = b.write(expectedWhite, 0, encoding); - assert.strictEqual(quote.length, bytesWritten); - assert.strictEqual(quote, b.toString("ascii", 0, quote.length)); - - // Check that the base64 decoder on the constructor works - // even in the presence of whitespace. - b = Buffer.from(expectedWhite, encoding); - assert.strictEqual(quote.length, b.length); - assert.strictEqual(quote, b.toString("ascii", 0, quote.length)); - - // Check that the base64 decoder ignores illegal chars - const expectedIllegal = - expected.slice(0, 60) + - " \x80" + - expected.slice(60, 120) + - " \xff" + - expected.slice(120, 180) + - " \x00" + - expected.slice(180, 240) + - " \x98" + - expected.slice(240, 300) + - "\x03" + - expected.slice(300, 360); - b = Buffer.from(expectedIllegal, encoding); - assert.strictEqual(quote.length, b.length); - assert.strictEqual(quote, b.toString("ascii", 0, quote.length)); - }); - } - - base64flavors.forEach(encoding => { - assert.strictEqual(Buffer.from("", encoding).toString(), ""); - assert.strictEqual(Buffer.from("K", encoding).toString(), ""); - - // multiple-of-4 with padding - assert.strictEqual(Buffer.from("Kg==", encoding).toString(), "*"); - assert.strictEqual(Buffer.from("Kio=", encoding).toString(), "*".repeat(2)); - assert.strictEqual(Buffer.from("Kioq", encoding).toString(), "*".repeat(3)); - assert.strictEqual(Buffer.from("KioqKg==", encoding).toString(), "*".repeat(4)); - assert.strictEqual(Buffer.from("KioqKio=", encoding).toString(), "*".repeat(5)); - assert.strictEqual(Buffer.from("KioqKioq", encoding).toString(), "*".repeat(6)); - assert.strictEqual(Buffer.from("KioqKioqKg==", encoding).toString(), "*".repeat(7)); - assert.strictEqual(Buffer.from("KioqKioqKio=", encoding).toString(), "*".repeat(8)); - assert.strictEqual(Buffer.from("KioqKioqKioq", encoding).toString(), "*".repeat(9)); - assert.strictEqual(Buffer.from("KioqKioqKioqKg==", encoding).toString(), "*".repeat(10)); - assert.strictEqual(Buffer.from("KioqKioqKioqKio=", encoding).toString(), "*".repeat(11)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioq", encoding).toString(), "*".repeat(12)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKg==", encoding).toString(), "*".repeat(13)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKio=", encoding).toString(), "*".repeat(14)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioq", encoding).toString(), "*".repeat(15)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKg==", encoding).toString(), "*".repeat(16)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKio=", encoding).toString(), "*".repeat(17)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKioq", encoding).toString(), "*".repeat(18)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKioqKg==", encoding).toString(), "*".repeat(19)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKioqKio=", encoding).toString(), "*".repeat(20)); - - // No padding, not a multiple of 4 - assert.strictEqual(Buffer.from("Kg", encoding).toString(), "*"); - assert.strictEqual(Buffer.from("Kio", encoding).toString(), "*".repeat(2)); - assert.strictEqual(Buffer.from("KioqKg", encoding).toString(), "*".repeat(4)); - assert.strictEqual(Buffer.from("KioqKio", encoding).toString(), "*".repeat(5)); - assert.strictEqual(Buffer.from("KioqKioqKg", encoding).toString(), "*".repeat(7)); - assert.strictEqual(Buffer.from("KioqKioqKio", encoding).toString(), "*".repeat(8)); - assert.strictEqual(Buffer.from("KioqKioqKioqKg", encoding).toString(), "*".repeat(10)); - assert.strictEqual(Buffer.from("KioqKioqKioqKio", encoding).toString(), "*".repeat(11)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKg", encoding).toString(), "*".repeat(13)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKio", encoding).toString(), "*".repeat(14)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKg", encoding).toString(), "*".repeat(16)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKio", encoding).toString(), "*".repeat(17)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKioqKg", encoding).toString(), "*".repeat(19)); - assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKioqKio", encoding).toString(), "*".repeat(20)); - }); - - // Handle padding graciously, multiple-of-4 or not - assert.strictEqual(Buffer.from("72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw==", "base64").length, 32); - assert.strictEqual(Buffer.from("72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw==", "base64url").length, 32); - assert.strictEqual(Buffer.from("72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw=", "base64").length, 32); - assert.strictEqual(Buffer.from("72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw=", "base64url").length, 32); - assert.strictEqual(Buffer.from("72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw", "base64").length, 32); - assert.strictEqual(Buffer.from("72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw", "base64url").length, 32); - assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg==", "base64").length, 31); - assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg==", "base64url").length, 31); - assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg=", "base64").length, 31); - assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg=", "base64url").length, 31); - assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg", "base64").length, 31); - assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg", "base64url").length, 31); - - { - // This string encodes single '.' character in UTF-16 - const dot = Buffer.from("//4uAA==", "base64"); - assert.strictEqual(dot[0], 0xff); - assert.strictEqual(dot[1], 0xfe); - assert.strictEqual(dot[2], 0x2e); - assert.strictEqual(dot[3], 0x00); - assert.strictEqual(dot.toString("base64"), "//4uAA=="); - } - - { - // This string encodes single '.' character in UTF-16 - const dot = Buffer.from("//4uAA", "base64url"); - assert.strictEqual(dot[0], 0xff); - assert.strictEqual(dot[1], 0xfe); - assert.strictEqual(dot[2], 0x2e); - assert.strictEqual(dot[3], 0x00); - assert.strictEqual(dot.toString("base64url"), "__4uAA"); - } - - { - // Writing base64 at a position > 0 should not mangle the result. - // - // https://github.com/joyent/node/issues/402 - const segments = ["TWFkbmVzcz8h", "IFRoaXM=", "IGlz", "IG5vZGUuanMh"]; - const b = Buffer.allocUnsafe(64); - let pos = 0; - - for (let i = 0; i < segments.length; ++i) { - pos += b.write(segments[i], pos, "base64"); - } - assert.strictEqual(b.toString("latin1", 0, pos), "Madness?! This is node.js!"); - } - - { - // Writing base64url at a position > 0 should not mangle the result. - // - // https://github.com/joyent/node/issues/402 - const segments = ["TWFkbmVzcz8h", "IFRoaXM", "IGlz", "IG5vZGUuanMh"]; - const b = Buffer.allocUnsafe(64); - let pos = 0; - - for (let i = 0; i < segments.length; ++i) { - pos += b.write(segments[i], pos, "base64url"); - } - assert.strictEqual(b.toString("latin1", 0, pos), "Madness?! This is node.js!"); - } - - // Regression test for https://github.com/nodejs/node/issues/3496. - assert.strictEqual(Buffer.from("=bad".repeat(1e4), "base64").length, 0); - - // Regression test for https://github.com/nodejs/node/issues/11987. - assert.deepStrictEqual(Buffer.from("w0 ", "base64"), Buffer.from("w0", "base64")); - - // Regression test for https://github.com/nodejs/node/issues/13657. - assert.deepStrictEqual(Buffer.from(" YWJvcnVtLg", "base64"), Buffer.from("YWJvcnVtLg", "base64")); - - { - // Creating buffers larger than pool size. - const l = Buffer.poolSize + 5; - const s = "h".repeat(l); - const b = Buffer.from(s); - - for (let i = 0; i < l; i++) { - assert.strictEqual(b[i], "h".charCodeAt(0)); - } - - const sb = b.toString(); - assert.strictEqual(sb.length, s.length); - assert.strictEqual(sb, s); - } - - { - // test hex toString - const hexb = Buffer.allocUnsafe(256); - for (let i = 0; i < 256; i++) { - hexb[i] = i; - } - const hexStr = hexb.toString("hex"); - assert.strictEqual( - hexStr, - "000102030405060708090a0b0c0d0e0f" + - "101112131415161718191a1b1c1d1e1f" + - "202122232425262728292a2b2c2d2e2f" + - "303132333435363738393a3b3c3d3e3f" + - "404142434445464748494a4b4c4d4e4f" + - "505152535455565758595a5b5c5d5e5f" + - "606162636465666768696a6b6c6d6e6f" + - "707172737475767778797a7b7c7d7e7f" + - "808182838485868788898a8b8c8d8e8f" + - "909192939495969798999a9b9c9d9e9f" + - "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" + - "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" + - "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" + - "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" + - "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" + - "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", - ); - - const hexb2 = Buffer.from(hexStr, "hex"); - for (let i = 0; i < 256; i++) { - assert.strictEqual(hexb2[i], hexb[i]); - } - } - - // Test single hex character is discarded. - assert.strictEqual(Buffer.from("A", "hex").length, 0); - - // Test that if a trailing character is discarded, rest of string is processed. - assert.deepStrictEqual(Buffer.from("Abx", "hex"), Buffer.from("Ab", "hex")); - - // Test single base64 char encodes as 0. - assert.strictEqual(Buffer.from("A", "base64").length, 0); - - { - // Test an invalid slice end. - const b = Buffer.from([1, 2, 3, 4, 5]); - const b2 = b.toString("hex", 1, 10000); - const b3 = b.toString("hex", 1, 5); - const b4 = b.toString("hex", 1); - assert.strictEqual(b2, b3); - assert.strictEqual(b2, b4); - } - - function buildBuffer(data) { - if (Array.isArray(data)) { - const buffer = Buffer.allocUnsafe(data.length); - data.forEach((v, k) => (buffer[k] = v)); - return buffer; - } - return null; - } - - const x = buildBuffer([0x81, 0xa3, 0x66, 0x6f, 0x6f, 0xa3, 0x62, 0x61, 0x72]); - - // assert.strictEqual(x.inspect(), ""); - - { - const z = x.slice(4); - assert.strictEqual(z.length, 5); - assert.strictEqual(z[0], 0x6f); - assert.strictEqual(z[1], 0xa3); - assert.strictEqual(z[2], 0x62); - assert.strictEqual(z[3], 0x61); - assert.strictEqual(z[4], 0x72); - } - - { - const z = x.slice(0); - assert.strictEqual(z.length, x.length); - } - - { - const z = x.slice(0, 4); - assert.strictEqual(z.length, 4); - assert.strictEqual(z[0], 0x81); - assert.strictEqual(z[1], 0xa3); - } - - { - const z = x.slice(0, 9); - assert.strictEqual(z.length, 9); - } - - { - const z = x.slice(1, 4); - assert.strictEqual(z.length, 3); - assert.strictEqual(z[0], 0xa3); - } - - { - const z = x.slice(2, 4); - assert.strictEqual(z.length, 2); - assert.strictEqual(z[0], 0x66); - assert.strictEqual(z[1], 0x6f); - } - - ["ucs2", "ucs-2", "utf16le", "utf-16le"].forEach(encoding => { - const b = Buffer.allocUnsafe(10); - b.write("あいうえお", encoding); - assert.strictEqual(b.toString(encoding), "あいうえお"); - }); - - ["ucs2", "ucs-2", "utf16le", "utf-16le"].forEach(encoding => { - const b = Buffer.allocUnsafe(11); - b.write("あいうえお", 1, encoding); - assert.strictEqual(b.toString(encoding, 1), "あいうえお"); - }); - - { - // latin1 encoding should write only one byte per character. - const b = Buffer.from([0xde, 0xad, 0xbe, 0xef]); - let s = String.fromCharCode(0xffff); - b.write(s, 0, "latin1"); - assert.strictEqual(b[0], 0xff); - assert.strictEqual(b[1], 0xad); - assert.strictEqual(b[2], 0xbe); - assert.strictEqual(b[3], 0xef); - s = String.fromCharCode(0xaaee); - b.write(s, 0, "latin1"); - assert.strictEqual(b[0], 0xee); - assert.strictEqual(b[1], 0xad); - assert.strictEqual(b[2], 0xbe); - assert.strictEqual(b[3], 0xef); - } - - { - // Binary encoding should write only one byte per character. - const b = Buffer.from([0xde, 0xad, 0xbe, 0xef]); - let s = String.fromCharCode(0xffff); - b.write(s, 0, "latin1"); - assert.strictEqual(b[0], 0xff); - assert.strictEqual(b[1], 0xad); - assert.strictEqual(b[2], 0xbe); - assert.strictEqual(b[3], 0xef); - s = String.fromCharCode(0xaaee); - b.write(s, 0, "latin1"); - assert.strictEqual(b[0], 0xee); - assert.strictEqual(b[1], 0xad); - assert.strictEqual(b[2], 0xbe); - assert.strictEqual(b[3], 0xef); - } - - { - // https://github.com/nodejs/node-v0.x-archive/pull/1210 - // Test UTF-8 string includes null character - let buf = Buffer.from("\0"); - assert.strictEqual(buf.length, 1); - buf = Buffer.from("\0\0"); - assert.strictEqual(buf.length, 2); - } - - { - const buf = Buffer.allocUnsafe(2); - assert.strictEqual(buf.write(""), 0); // 0bytes - assert.strictEqual(buf.write("\0"), 1); // 1byte (v8 adds null terminator) - assert.strictEqual(buf.write("a\0"), 2); // 1byte * 2 - assert.strictEqual(buf.write("あ"), 0); // 3bytes - assert.strictEqual(buf.write("\0あ"), 1); // 1byte + 3bytes - assert.strictEqual(buf.write("\0\0あ"), 2); // 1byte * 2 + 3bytes - } - - { - const buf = Buffer.allocUnsafe(10); - assert.strictEqual(buf.write("あいう"), 9); // 3bytes * 3 (v8 adds null term.) - assert.strictEqual(buf.write("あいう\0"), 10); // 3bytes * 3 + 1byte - } - - { - // https://github.com/nodejs/node-v0.x-archive/issues/243 - // Test write() with maxLength - const buf = Buffer.allocUnsafe(4); - buf.fill(0xff); - assert.strictEqual(buf.write("abcd", 1, 2, "utf8"), 2); - assert.strictEqual(buf[0], 0xff); - assert.strictEqual(buf[1], 0x61); - assert.strictEqual(buf[2], 0x62); - assert.strictEqual(buf[3], 0xff); - - buf.fill(0xff); - assert.strictEqual(buf.write("abcd", 1, 4), 3); - assert.strictEqual(buf[0], 0xff); - assert.strictEqual(buf[1], 0x61); - assert.strictEqual(buf[2], 0x62); - assert.strictEqual(buf[3], 0x63); - - buf.fill(0xff); - assert.strictEqual(buf.write("abcd", 1, 2, "utf8"), 2); - assert.strictEqual(buf[0], 0xff); - assert.strictEqual(buf[1], 0x61); - assert.strictEqual(buf[2], 0x62); - assert.strictEqual(buf[3], 0xff); - - buf.fill(0xff); - assert.strictEqual(buf.write("abcdef", 1, 2, "hex"), 2); - assert.strictEqual(buf[0], 0xff); - assert.strictEqual(buf[1], 0xab); - assert.strictEqual(buf[2], 0xcd); - assert.strictEqual(buf[3], 0xff); - - ["ucs2", "ucs-2", "utf16le", "utf-16le"].forEach(encoding => { - buf.fill(0xff); - assert.strictEqual(buf.write("abcd", 0, 2, encoding), 2); - assert.strictEqual(buf[0], 0x61); - assert.strictEqual(buf[1], 0x00); - assert.strictEqual(buf[2], 0xff); - assert.strictEqual(buf[3], 0xff); - }); - } - - { - // Test offset returns are correct - const b = Buffer.allocUnsafe(16); - assert.strictEqual(b.writeUInt32LE(0, 0), 4); - assert.strictEqual(b.writeUInt16LE(0, 4), 6); - assert.strictEqual(b.writeUInt8(0, 6), 7); - assert.strictEqual(b.writeInt8(0, 7), 8); - assert.strictEqual(b.writeDoubleLE(0, 8), 16); - } - - { - // Test unmatched surrogates not producing invalid utf8 output - // ef bf bd = utf-8 representation of unicode replacement character - // see https://codereview.chromium.org/121173009/ - let buf = Buffer.from("ab\ud800cd", "utf8"); - assert.strictEqual(buf[0], 0x61); - assert.strictEqual(buf[1], 0x62); - assert.strictEqual(buf[2], 0xef); - assert.strictEqual(buf[3], 0xbf); - assert.strictEqual(buf[4], 0xbd); - assert.strictEqual(buf[5], 0x63); - assert.strictEqual(buf[6], 0x64); - - buf = Buffer.from("abcd\ud800", "utf8"); - expect(buf[0]).toBe(0x61); - expect(buf[1]).toBe(0x62); - expect(buf[2]).toBe(0x63); - expect(buf[3]).toBe(0x64); - expect(buf[4]).toBe(0xef); - expect(buf[5]).toBe(0xbf); - expect(buf[6]).toBe(0xbd); - - buf = Buffer.from("\ud800abcd", "utf8"); - expect(buf[0]).toBe(0xef); - expect(buf[1]).toBe(0xbf); - expect(buf[2]).toBe(0xbd); - expect(buf[3]).toBe(0x61); - expect(buf[4]).toBe(0x62); - expect(buf[5]).toBe(0x63); - expect(buf[6]).toBe(0x64); - } - - { - // Test for buffer overrun - const buf = Buffer.from([0, 0, 0, 0, 0]); // length: 5 - const sub = buf.slice(0, 4); // length: 4 - assert.strictEqual(sub.write("12345", "latin1"), 4); - assert.strictEqual(buf[4], 0); - assert.strictEqual(sub.write("12345", "binary"), 4); - assert.strictEqual(buf[4], 0); - } - - { - // Test alloc with fill option - const buf = Buffer.alloc(5, "800A", "hex"); - assert.strictEqual(buf[0], 128); - assert.strictEqual(buf[1], 10); - assert.strictEqual(buf[2], 128); - assert.strictEqual(buf[3], 10); - assert.strictEqual(buf[4], 128); - } - - // Check for fractional length args, junk length args, etc. - // https://github.com/joyent/node/issues/1758 - - // Call .fill() first, stops valgrind warning about uninitialized memory reads. - Buffer.allocUnsafe(3.3).fill().toString(); - // Throws bad argument error in commit 43cb4ec - Buffer.alloc(3.3).fill().toString(); - assert.strictEqual(Buffer.allocUnsafe(3.3).length, 3); - assert.strictEqual(Buffer.from({ length: 3.3 }).length, 3); - assert.strictEqual(Buffer.from({ length: "BAM" }).length, 0); - - // Make sure that strings are not coerced to numbers. - assert.strictEqual(Buffer.from("99").length, 2); - assert.strictEqual(Buffer.from("13.37").length, 5); - - // Ensure that the length argument is respected. - ["ascii", "utf8", "hex", "base64", "latin1", "binary"].forEach(enc => { - assert.strictEqual(Buffer.allocUnsafe(1).write("aaaaaa", 0, 1, enc), 1); - }); - - { - // Regression test, guard against buffer overrun in the base64 decoder. - const a = Buffer.allocUnsafe(3); - const b = Buffer.from("xxx"); - a.write("aaaaaaaa", "base64"); - assert.strictEqual(b.toString(), "xxx"); - } - - // issue GH-3416 - Buffer.from(Buffer.allocUnsafe(0), 0, 0); - - // issue GH-5587 - assert.throws(() => Buffer.alloc(8).writeFloatLE(0, 5), outOfRangeError); - assert.throws(() => Buffer.alloc(16).writeDoubleLE(0, 9), outOfRangeError); - - // Attempt to overflow buffers, similar to previous bug in array buffers - assert.throws(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, 0xffffffff), outOfRangeError); - assert.throws(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, 0xffffffff), outOfRangeError); - - // Ensure negative values can't get past offset - assert.throws(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, -1), outOfRangeError); - assert.throws(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, -1), outOfRangeError); - - // Test for common write(U)IntLE/BE - { - let buf = Buffer.allocUnsafe(3); - buf.writeUIntLE(0x123456, 0, 3); - assert.deepStrictEqual(buf.toJSON().data, [0x56, 0x34, 0x12]); - assert.strictEqual(buf.readUIntLE(0, 3), 0x123456); - - buf.fill(0xff); - buf.writeUIntBE(0x123456, 0, 3); - assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56]); - assert.strictEqual(buf.readUIntBE(0, 3), 0x123456); - - buf.fill(0xff); - buf.writeIntLE(0x123456, 0, 3); - assert.deepStrictEqual(buf.toJSON().data, [0x56, 0x34, 0x12]); - assert.strictEqual(buf.readIntLE(0, 3), 0x123456); - - buf.fill(0xff); - buf.writeIntBE(0x123456, 0, 3); - assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56]); - assert.strictEqual(buf.readIntBE(0, 3), 0x123456); - - buf.fill(0xff); - buf.writeIntLE(-0x123456, 0, 3); - assert.deepStrictEqual(buf.toJSON().data, [0xaa, 0xcb, 0xed]); - assert.strictEqual(buf.readIntLE(0, 3), -0x123456); - - buf.fill(0xff); - buf.writeIntBE(-0x123456, 0, 3); - assert.deepStrictEqual(buf.toJSON().data, [0xed, 0xcb, 0xaa]); - assert.strictEqual(buf.readIntBE(0, 3), -0x123456); - - buf.fill(0xff); - buf.writeIntLE(-0x123400, 0, 3); - assert.deepStrictEqual(buf.toJSON().data, [0x00, 0xcc, 0xed]); - assert.strictEqual(buf.readIntLE(0, 3), -0x123400); - - buf.fill(0xff); - buf.writeIntBE(-0x123400, 0, 3); - assert.deepStrictEqual(buf.toJSON().data, [0xed, 0xcc, 0x00]); - assert.strictEqual(buf.readIntBE(0, 3), -0x123400); - - buf.fill(0xff); - buf.writeIntLE(-0x120000, 0, 3); - assert.deepStrictEqual(buf.toJSON().data, [0x00, 0x00, 0xee]); - assert.strictEqual(buf.readIntLE(0, 3), -0x120000); - - buf.fill(0xff); - buf.writeIntBE(-0x120000, 0, 3); - assert.deepStrictEqual(buf.toJSON().data, [0xee, 0x00, 0x00]); - assert.strictEqual(buf.readIntBE(0, 3), -0x120000); - - buf = Buffer.allocUnsafe(5); - buf.writeUIntLE(0x1234567890, 0, 5); - assert.deepStrictEqual(buf.toJSON().data, [0x90, 0x78, 0x56, 0x34, 0x12]); - assert.strictEqual(buf.readUIntLE(0, 5), 0x1234567890); - - buf.fill(0xff); - buf.writeUIntBE(0x1234567890, 0, 5); - assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56, 0x78, 0x90]); - assert.strictEqual(buf.readUIntBE(0, 5), 0x1234567890); - - buf.fill(0xff); - buf.writeIntLE(0x1234567890, 0, 5); - assert.deepStrictEqual(buf.toJSON().data, [0x90, 0x78, 0x56, 0x34, 0x12]); - assert.strictEqual(buf.readIntLE(0, 5), 0x1234567890); - - buf.fill(0xff); - buf.writeIntBE(0x1234567890, 0, 5); - assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56, 0x78, 0x90]); - assert.strictEqual(buf.readIntBE(0, 5), 0x1234567890); - - buf.fill(0xff); - buf.writeIntLE(-0x1234567890, 0, 5); - assert.deepStrictEqual(buf.toJSON().data, [0x70, 0x87, 0xa9, 0xcb, 0xed]); - assert.strictEqual(buf.readIntLE(0, 5), -0x1234567890); - - buf.fill(0xff); - buf.writeIntBE(-0x1234567890, 0, 5); - assert.deepStrictEqual(buf.toJSON().data, [0xed, 0xcb, 0xa9, 0x87, 0x70]); - assert.strictEqual(buf.readIntBE(0, 5), -0x1234567890); - - buf.fill(0xff); - buf.writeIntLE(-0x0012000000, 0, 5); - assert.deepStrictEqual(buf.toJSON().data, [0x00, 0x00, 0x00, 0xee, 0xff]); - assert.strictEqual(buf.readIntLE(0, 5), -0x0012000000); - - buf.fill(0xff); - buf.writeIntBE(-0x0012000000, 0, 5); - assert.deepStrictEqual(buf.toJSON().data, [0xff, 0xee, 0x00, 0x00, 0x00]); - assert.strictEqual(buf.readIntBE(0, 5), -0x0012000000); - } - - // Regression test for https://github.com/nodejs/node-v0.x-archive/issues/5482: - // should throw but not assert in C++ land. - assert.throws(() => Buffer.from("", "buffer"), { - code: "ERR_UNKNOWN_ENCODING", - name: "TypeError", - message: "Unknown encoding: buffer", - }); - - // Regression test for https://github.com/nodejs/node-v0.x-archive/issues/6111. - // Constructing a buffer from another buffer should a) work, and b) not corrupt - // the source buffer. - { - const a = [...Array(128).keys()]; // [0, 1, 2, 3, ... 126, 127] - const b = Buffer.from(a); - const c = Buffer.from(b); - assert.strictEqual(b.length, a.length); - assert.strictEqual(c.length, a.length); - for (let i = 0, k = a.length; i < k; ++i) { - assert.strictEqual(a[i], i); - assert.strictEqual(b[i], i); - assert.strictEqual(c[i], i); - } - } - - // if (common.hasCrypto) { - // eslint-disable-line node-core/crypto-check - // Test truncation after decode - const crypto = require("crypto"); - - const b1 = Buffer.from("YW55=======", "base64"); - const b2 = Buffer.from("YW55", "base64"); - - assert.strictEqual( - crypto.createHash("sha1").update(b1).digest("hex"), - crypto.createHash("sha1").update(b2).digest("hex"), - ); - // } else { - // common.printSkipMessage("missing crypto"); - // } - - const ps = Buffer.poolSize; - Buffer.poolSize = 0; - assert(Buffer.allocUnsafe(1).parent instanceof ArrayBuffer); - Buffer.poolSize = ps; - - assert.throws(() => Buffer.allocUnsafe(10).copy(), { - code: "ERR_INVALID_ARG_TYPE", - name: "TypeError", - message: 'The "target" argument must be an instance of Buffer or ' + "Uint8Array. Received undefined", - }); - - assert.throws(() => Buffer.from(), { - name: "TypeError", - message: - "The first argument must be of type string or an instance of " + - "Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined", - }); - assert.throws(() => Buffer.from(null), { - name: "TypeError", - message: - "The first argument must be of type string or an instance of " + - "Buffer, ArrayBuffer, or Array or an Array-like Object. Received null", - }); - - // Test prototype getters don't throw - assert.strictEqual(Buffer.prototype.parent, undefined); - assert.strictEqual(Buffer.prototype.offset, undefined); - assert.strictEqual(SlowBuffer.prototype.parent, undefined); - assert.strictEqual(SlowBuffer.prototype.offset, undefined); - - { - // Test that large negative Buffer length inputs don't affect the pool offset. - // Use the fromArrayLike() variant here because it's more lenient - // about its input and passes the length directly to allocate(). - assert.deepStrictEqual(Buffer.from({ length: -Buffer.poolSize }), Buffer.from("")); - assert.deepStrictEqual(Buffer.from({ length: -100 }), Buffer.from("")); - - // Check pool offset after that by trying to write string into the pool. - Buffer.from("abc"); - } - - // Test that ParseArrayIndex handles full uint32 - { - const errMsg = common.expectsError({ - code: "ERR_BUFFER_OUT_OF_BOUNDS", - name: "RangeError", - message: '"offset" is outside of buffer bounds', - }); - assert.throws(() => Buffer.from(new ArrayBuffer(0), -1 >>> 0), errMsg); - } - - // ParseArrayIndex() should reject values that don't fit in a 32 bits size_t. - assert.throws(() => { - const a = Buffer.alloc(1); - const b = Buffer.alloc(1); - a.copy(b, 0, 0x100000000, 0x100000001); - }, outOfRangeError); - - // Unpooled buffer (replaces SlowBuffer) - { - const ubuf = Buffer.allocUnsafeSlow(10); - assert(ubuf); - assert(ubuf.buffer); - assert.strictEqual(ubuf.buffer.byteLength, 10); - } - - // Regression test to verify that an empty ArrayBuffer does not throw. - Buffer.from(new ArrayBuffer()); - - // Test that ArrayBuffer from a different context is detected correctly. - // const arrayBuf = vm.runInNewContext("new ArrayBuffer()"); - // Buffer.from(arrayBuf); - // Buffer.from({ buffer: arrayBuf }); - - assert.throws(() => Buffer.alloc({ valueOf: () => 1 }), /"size" argument must be of type number/); - assert.throws(() => Buffer.alloc({ valueOf: () => -1 }), /"size" argument must be of type number/); - - assert.strictEqual(Buffer.prototype.toLocaleString, Buffer.prototype.toString); - { - const buf = Buffer.from("test"); - assert.strictEqual(buf.toLocaleString(), buf.toString()); - } - - assert.throws( - () => { - Buffer.alloc(0x1000, "This is not correctly encoded", "hex"); - }, - { - code: "ERR_INVALID_ARG_VALUE", - name: "TypeError", - }, - ); - - assert.throws( - () => { - Buffer.alloc(0x1000, "c", "hex"); - }, - { - code: "ERR_INVALID_ARG_VALUE", - name: "TypeError", - }, - ); - - assert.throws( - () => { - Buffer.alloc(1, Buffer.alloc(0)); - }, - { - code: "ERR_INVALID_ARG_VALUE", - name: "TypeError", - }, - ); - - assert.throws( - () => { - Buffer.alloc(40, "x", 20); - }, - { - code: "ERR_INVALID_ARG_TYPE", - name: "TypeError", - }, - ); -}); - -it("Buffer.toJSON()", () => { - expect(JSON.stringify(Buffer.from("hello"))).toBe( - JSON.stringify({ - type: "Buffer", - data: [104, 101, 108, 108, 111], - }), - ); -}); - -it("buffer", () => { - var buf = new Buffer(20); - gc(); - // if this fails or infinitely loops, it means there is a memory issue with the JSC::Structure object - expect(Object.keys(buf).length > 0).toBe(true); - gc(); - expect(buf.write("hello world ")).toBe(12); - expect(buf.write("hello world ", "utf8")).toBe(12); - - gc(); - expect(buf.toString("utf8", 0, "hello world ".length)).toBe("hello world "); - gc(); - expect(buf.toString("base64url", 0, "hello world ".length)).toBe(btoa("hello world ")); - gc(); - expect(buf instanceof Uint8Array).toBe(true); - gc(); - expect(buf instanceof Buffer).toBe(true); - gc(); - expect(buf.slice() instanceof Uint8Array).toBe(true); - gc(); - expect(buf.slice(0, 1) instanceof Buffer).toBe(true); - gc(); - expect(buf.slice(0, 1) instanceof Uint8Array).toBe(true); - gc(); - expect(buf.slice(0, 1) instanceof Buffer).toBe(true); - gc(); - expect(buf.slice(0, 0).length).toBe(0); -}); - -it("Buffer", () => { - var inputs = ["hello world", "hello world".repeat(100), `😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌`]; - var good = inputs.map(a => new TextEncoder().encode(a)); - for (let i = 0; i < inputs.length; i++) { - var input = inputs[i]; - expect(new Buffer(input).toString("utf8")).toBe(inputs[i]); - gc(); - expect(Array.from(new Buffer(input)).join(",")).toBe(good[i].join(",")); - gc(); - expect(Buffer.byteLength(input)).toBe(good[i].length); - gc(); - expect(Buffer.from(input).byteLength).toBe(Buffer.byteLength(input)); - } -}); - -it("Buffer.byteLength", () => { - expect(Buffer.byteLength("😀😃😄😁😆😅😂🤣☺️😊😊😇")).toBe( - new TextEncoder().encode("😀😃😄😁😆😅😂🤣☺️😊😊😇").byteLength, - ); -}); - -it("Buffer.isBuffer", () => { - expect(Buffer.isBuffer(new Buffer(1))).toBe(true); - gc(); - expect(Buffer.isBuffer(new Buffer(0))).toBe(true); - gc(); - expect(Buffer.isBuffer(new Uint8Array(0))).toBe(false); - gc(); - expect(Buffer.isBuffer(new Uint8Array(1))).toBe(false); - gc(); - var a = new Uint8Array(1); - gc(); - expect(Buffer.isBuffer(a)).toBe(false); - gc(); - a = new Buffer(a.buffer); - gc(); - expect(Buffer.isBuffer(a)).toBe(true); - gc(); - expect(a instanceof Buffer).toBe(true); - expect(a instanceof Uint8Array).toBe(true); - expect(new Uint8Array(0) instanceof Buffer).toBe(false); - - // DOMJIT - for (let i = 0; i < 9000; i++) { - if (!Buffer.isBuffer(a)) { - throw new Error("Buffer.isBuffer failed"); - } - - if (Buffer.isBuffer("wat")) { - throw new Error("Buffer.isBuffer failed"); - } - } -}); - -it("writeInt", () => { - var buf = new Buffer(1024); - var data = new DataView(buf.buffer); - buf.writeInt32BE(100); - expect(data.getInt32(0, false)).toBe(100); - buf.writeInt32BE(100); - expect(data.getInt32(0, false)).toBe(100); - var childBuf = buf.subarray(0, 4); - expect(data.getInt32(0, false)).toBe(100); - expect(childBuf.readInt32BE(0, false)).toBe(100); -}); - -it("Buffer.from", () => { - expect(Buffer.from("hello world").toString("utf8")).toBe("hello world"); - expect(Buffer.from("hello world", "ascii").toString("utf8")).toBe("hello world"); - expect(Buffer.from("hello world", "latin1").toString("utf8")).toBe("hello world"); - gc(); - expect(Buffer.from([254]).join(",")).toBe("254"); - - expect(Buffer.from([254], "utf8").join(",")).toBe("254"); - expect(Buffer.from([254], "utf-8").join(",")).toBe("254"); - expect(Buffer.from([254], "latin").join(",")).toBe("254"); - expect(Buffer.from([254], "uc2").join(",")).toBe("254"); - expect(Buffer.from([254], "utf16").join(",")).toBe("254"); - expect(Buffer.isBuffer(Buffer.from([254], "utf16"))).toBe(true); - - expect(() => Buffer.from(123).join(",")).toThrow(); - - expect(Buffer.from({ length: 124 }).join(",")).toBe(Uint8Array.from({ length: 124 }).join(",")); - - expect(Buffer.from(new ArrayBuffer(1024), 0, 512).join(",")).toBe(new Uint8Array(512).join(",")); - - expect(Buffer.from(new Buffer(new ArrayBuffer(1024), 0, 512)).join(",")).toBe(new Uint8Array(512).join(",")); - gc(); -}); - -it("Buffer.from latin1 vs ascii", () => { - const simpleBuffer = Buffer.from("\xa4", "binary"); - expect(simpleBuffer.toString("latin1")).toBe("¤"); - expect(simpleBuffer.toString("ascii")).toBe("$"); - gc(); - const asciiBuffer = Buffer.from("\xa4", "ascii"); - expect(asciiBuffer.toString("latin1")).toBe("¤"); - expect(asciiBuffer.toString("ascii")).toBe("$"); - gc(); -}); - -it("Buffer.equals", () => { - var a = new Uint8Array(10); - a[2] = 1; - var b = new Uint8Array(10); - b[2] = 1; - a = new Buffer(a.buffer); - b = new Buffer(b.buffer); - expect(a.equals(b)).toBe(true); - b[2] = 0; - expect(a.equals(b)).toBe(false); -}); - -it("Buffer.compare", () => { - var a = new Uint8Array(10); - a[2] = 1; - var b = new Uint8Array(10); - b[2] = 1; - a = new Buffer(a.buffer); - b = new Buffer(b.buffer); - expect(a.compare(b)).toBe(0); - b[2] = 0; - expect(a.compare(b)).toBe(1); - expect(b.compare(a)).toBe(-1); - - const buf = Buffer.from("0123456789", "utf8"); - const expectedSameBufs = [ - [buf.slice(-10, 10), Buffer.from("0123456789", "utf8")], - [buf.slice(-20, 10), Buffer.from("0123456789", "utf8")], - [buf.slice(-20, -10), Buffer.from("", "utf8")], - [buf.slice(), Buffer.from("0123456789", "utf8")], - [buf.slice(0), Buffer.from("0123456789", "utf8")], - [buf.slice(0, 0), Buffer.from("", "utf8")], - [buf.slice(undefined), Buffer.from("0123456789", "utf8")], - [buf.slice("foobar"), Buffer.from("0123456789", "utf8")], - [buf.slice(undefined, undefined), Buffer.from("0123456789", "utf8")], - [buf.slice(2), Buffer.from("23456789", "utf8")], - [buf.slice(5), Buffer.from("56789", "utf8")], - [buf.slice(10), Buffer.from("", "utf8")], - [buf.slice(5, 8), Buffer.from("567", "utf8")], - [buf.slice(8, -1), Buffer.from("8", "utf8")], - [buf.slice(-10), Buffer.from("0123456789", "utf8")], - [buf.slice(0, -9), Buffer.from("0", "utf8")], - [buf.slice(0, -10), Buffer.from("", "utf8")], - [buf.slice(0, -1), Buffer.from("012345678", "utf8")], - [buf.slice(2, -2), Buffer.from("234567", "utf8")], - [buf.slice(0, 65536), Buffer.from("0123456789", "utf8")], - [buf.slice(65536, 0), Buffer.from("", "utf8")], - [buf.slice(-5, -8), Buffer.from("", "utf8")], - [buf.slice(-5, -3), Buffer.from("56", "utf8")], - [buf.slice(-10, 10), Buffer.from("0123456789", "utf8")], - [buf.slice("0", "1"), Buffer.from("0", "utf8")], - [buf.slice("-5", "10"), Buffer.from("56789", "utf8")], - [buf.slice("-10", "10"), Buffer.from("0123456789", "utf8")], - [buf.slice("-10", "-5"), Buffer.from("01234", "utf8")], - [buf.slice("-10", "-0"), Buffer.from("", "utf8")], - [buf.slice("111"), Buffer.from("", "utf8")], - [buf.slice("0", "-111"), Buffer.from("", "utf8")], - ]; - - for (let i = 0, s = buf.toString(); i < buf.length; ++i) { - expectedSameBufs.push( - [buf.slice(i), Buffer.from(s.slice(i))], - [buf.slice(0, i), Buffer.from(s.slice(0, i))], - [buf.slice(-i), Buffer.from(s.slice(-i))], - [buf.slice(0, -i), Buffer.from(s.slice(0, -i))], - ); - } - - expectedSameBufs.forEach(([buf1, buf2]) => { - expect(Buffer.compare(buf1, buf2)).toBe(0); - }); - - { - const buf = Buffer.from([ - 1, 29, 0, 0, 1, 143, 216, 162, 92, 254, 248, 63, 0, 0, 0, 18, 184, 6, 0, 175, 29, 0, 8, 11, 1, 0, 0, - ]); - const chunk1 = Buffer.from([1, 29, 0, 0, 1, 143, 216, 162, 92, 254, 248, 63, 0]); - const chunk2 = Buffer.from([0, 0, 18, 184, 6, 0, 175, 29, 0, 8, 11, 1, 0, 0]); - const middle = buf.length / 2; - - expect(JSON.stringify(buf.slice(0, middle))).toBe(JSON.stringify(chunk1)); - expect(JSON.stringify(buf.slice(middle))).toBe(JSON.stringify(chunk2)); - } -}); - -it("Buffer.copy", () => { - var array1 = new Uint8Array(128); - array1.fill(100); - array1 = new Buffer(array1.buffer); - var array2 = new Uint8Array(128); - array2.fill(200); - array2 = new Buffer(array2.buffer); - var array3 = new Uint8Array(128); - array3 = new Buffer(array3.buffer); - gc(); - expect(array1.copy(array2)).toBe(128); - expect(array1.join("")).toBe(array2.join("")); - - { - // Create two `Buffer` instances. - const buf1 = Buffer.allocUnsafe(26); - const buf2 = Buffer.allocUnsafe(26).fill("!"); - - for (let i = 0; i < 26; i++) { - // 97 is the decimal ASCII value for 'a'. - buf1[i] = i + 97; - } - - // Copy `buf1` bytes 16 through 19 into `buf2` starting at byte 8 of `buf2`. - buf1.copy(buf2, 8, 16, 20); - expect(buf2.toString("ascii", 0, 25)).toBe("!!!!!!!!qrst!!!!!!!!!!!!!"); - } - - { - const buf = Buffer.allocUnsafe(26); - - for (let i = 0; i < 26; i++) { - // 97 is the decimal ASCII value for 'a'. - buf[i] = i + 97; - } - - buf.copy(buf, 0, 4, 10); - expect(buf.toString()).toBe("efghijghijklmnopqrstuvwxyz"); - } -}); - -export function fillRepeating(dstBuffer, start, end) { - let len = dstBuffer.length, // important: use indices length, not byte-length - sLen = end - start, - p = sLen; // set initial position = source sequence length - - // step 2: copy existing data doubling segment length per iteration - while (p < len) { - if (p + sLen > len) sLen = len - p; // if not power of 2, truncate last segment - dstBuffer.copyWithin(p, start, sLen); // internal copy - p += sLen; // add current length to offset - sLen <<= 1; // double length for next segment - } -} - -describe("Buffer.fill string", () => { - for (let text of ["hello world", "1234567890", "\uD83D\uDE00", "😀😃😄😁😆😅😂🤣☺️😊😊😇"]) { - it(text, () => { - var input = new Buffer(1024); - input.fill(text); - var demo = new Uint8Array(1024); - var encoded = new TextEncoder().encode(text); - - demo.set(encoded); - fillRepeating(demo, 0, encoded.length); - expect(input.join("")).toBe(demo.join("")); - }); - } -}); - -it("Buffer.fill 1 char string", () => { - var input = new Buffer(1024); - input.fill("h"); - var demo = new Uint8Array(1024); - var encoded = new TextEncoder().encode("h"); - - demo.set(encoded); - fillRepeating(demo, 0, encoded.length); - expect(input.join("")).toBe(demo.join("")); -}); - -it("Buffer.concat", () => { - var array1 = new Uint8Array(128); - array1.fill(100); - var array2 = new Uint8Array(128); - array2.fill(200); - var array3 = new Uint8Array(128); - array3.fill(300); - gc(); - expect(Buffer.concat([array1, array2, array3]).join("")).toBe(array1.join("") + array2.join("") + array3.join("")); - expect(Buffer.concat([array1, array2, array3], 222).length).toBe(222); - expect(Buffer.concat([array1, array2, array3], 222).subarray(0, 128).join("")).toBe("100".repeat(128)); - expect(Buffer.concat([array1, array2, array3], 222).subarray(129, 222).join("")).toBe("200".repeat(222 - 129)); -}); - -it("read", () => { - var buf = new Buffer(1024); - var data = new DataView(buf.buffer); - function reset() { - new Uint8Array(buf.buffer).fill(0); - } - data.setBigInt64(0, BigInt(1000), false); - expect(buf.readBigInt64BE(0)).toBe(BigInt(1000)); - reset(); - - data.setBigInt64(0, BigInt(1000), true); - expect(buf.readBigInt64LE(0)).toBe(BigInt(1000)); - reset(); - - data.setBigUint64(0, BigInt(1000), false); - expect(buf.readBigUInt64BE(0)).toBe(BigInt(1000)); - reset(); - - data.setBigUint64(0, BigInt(1000), true); - expect(buf.readBigUInt64LE(0)).toBe(BigInt(1000)); - reset(); - - data.setFloat64(0, 1000, false); - expect(buf.readDoubleBE(0)).toBe(1000); - reset(); - - data.setFloat64(0, 1000, true); - expect(buf.readDoubleLE(0)).toBe(1000); - reset(); - - data.setFloat32(0, 1000, false); - expect(buf.readFloatBE(0)).toBe(1000); - reset(); - - data.setFloat32(0, 1000, true); - expect(buf.readFloatLE(0)).toBe(1000); - reset(); - - data.setInt16(0, 1000, false); - expect(buf.readInt16BE(0)).toBe(1000); - reset(); - - data.setInt16(0, 1000, true); - expect(buf.readInt16LE(0)).toBe(1000); - reset(); - - data.setInt32(0, 1000, false); - expect(buf.readInt32BE(0)).toBe(1000); - reset(); - - data.setInt32(0, 1000, true); - expect(buf.readInt32LE(0)).toBe(1000); - reset(); - - data.setInt8(0, 100, false); - expect(buf.readInt8(0)).toBe(100); - reset(); - - data.setUint16(0, 1000, false); - expect(buf.readUInt16BE(0)).toBe(1000); - reset(); - - data.setUint16(0, 1000, true); - expect(buf.readUInt16LE(0)).toBe(1000); - reset(); - - data.setUint32(0, 1000, false); - expect(buf.readUInt32BE(0)).toBe(1000); - reset(); - - data.setUint32(0, 1000, true); - expect(buf.readUInt32LE(0)).toBe(1000); - reset(); - - data.setUint8(0, 255, false); - expect(buf.readUInt8(0)).toBe(255); - reset(); - - data.setUint8(0, 255, false); - expect(buf.readUInt8(0)).toBe(255); - reset(); -}); - -// this is for checking the simd code path -it("write long utf16 string works", () => { - const long = "😀😃😄😁😆😅😂🤣☺️😊😊😇".repeat(200); - const buf = Buffer.alloc(long.length * 2); - buf.write(long, 0, "utf16le"); - expect(buf.toString("utf16le")).toBe(long); - for (let offset = 0; offset < long.length; offset += 48) { - expect(buf.toString("utf16le", offset, offset + 4)).toBe("😀"); - expect(buf.toString("utf16le", offset, offset + 8)).toBe("😀😃"); - expect(buf.toString("utf16le", offset, offset + 12)).toBe("😀😃😄"); - expect(buf.toString("utf16le", offset, offset + 16)).toBe("😀😃😄😁"); - expect(buf.toString("utf16le", offset, offset + 20)).toBe("😀😃😄😁😆"); - expect(buf.toString("utf16le", offset, offset + 24)).toBe("😀😃😄😁😆😅"); - expect(buf.toString("utf16le", offset, offset + 28)).toBe("😀😃😄😁😆😅😂"); - expect(buf.toString("utf16le", offset, offset + 32)).toBe("😀😃😄😁😆😅😂🤣"); - expect(buf.toString("utf16le", offset, offset + 36)).toBe("😀😃😄😁😆😅😂🤣☺️"); - expect(buf.toString("utf16le", offset, offset + 40)).toBe("😀😃😄😁😆😅😂🤣☺️😊"); - expect(buf.toString("utf16le", offset, offset + 44)).toBe("😀😃😄😁😆😅😂🤣☺️😊😊"); - expect(buf.toString("utf16le", offset, offset + 48)).toBe("😀😃😄😁😆😅😂🤣☺️😊😊😇"); - } -}); - -it("write", () => { - const resultMap = new Map([ - ["utf8", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], - ["ucs2", Buffer.from([102, 0, 111, 0, 111, 0, 0, 0, 0])], - ["ascii", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], - ["latin1", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], - ["binary", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], - ["utf16le", Buffer.from([102, 0, 111, 0, 111, 0, 0, 0, 0])], - ["base64", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], - ["base64url", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], - ["hex", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], - ]); - - let buf = Buffer.alloc(9); - function reset() { - new Uint8Array(buf.buffer).fill(0); - } - - // utf8, ucs2, ascii, latin1, utf16le - const encodings = ["utf8", "utf-8", "ucs2", "ucs-2", "ascii", "latin1", "binary", "utf16le", "utf-16le"]; - - encodings - .reduce((es, e) => es.concat(e, e.toUpperCase()), []) - .forEach(encoding => { - reset(); - - const len = Buffer.byteLength("foo", encoding); - expect(buf.write("foo", 0, len, encoding)).toBe(len); - - if (encoding.includes("-")) encoding = encoding.replace("-", ""); - - expect(buf).toStrictEqual(resultMap.get(encoding.toLowerCase())); - }); - - // base64 - ["base64", "BASE64", "base64url", "BASE64URL"].forEach(encoding => { - reset(); - - const len = Buffer.byteLength("Zm9v", encoding); - - expect(buf.write("Zm9v", 0, len, encoding)).toBe(len); - expect(buf).toStrictEqual(resultMap.get(encoding.toLowerCase())); - }); - - // hex - ["hex", "HEX"].forEach(encoding => { - reset(); - const len = Buffer.byteLength("666f6f", encoding); - - expect(buf.write("666f6f", 0, len, encoding)).toBe(len); - expect(buf).toStrictEqual(resultMap.get(encoding.toLowerCase())); - }); - - // UCS-2 overflow CVE-2018-12115 - for (let i = 1; i < 4; i++) { - // Allocate two Buffers sequentially off the pool. Run more than once in case - // we hit the end of the pool and don't get sequential allocations - const x = Buffer.allocUnsafe(4).fill(0); - const y = Buffer.allocUnsafe(4).fill(1); - // Should not write anything, pos 3 doesn't have enough room for a 16-bit char - expect(x.write("ыыыыыы", 3, "ucs2")).toBe(0); - // CVE-2018-12115 experienced via buffer overrun to next block in the pool - expect(Buffer.compare(y, Buffer.alloc(4, 1))).toBe(0); - } - - // // Should not write any data when there is no space for 16-bit chars - const z = Buffer.alloc(4, 0); - expect(z.write("\u0001", 3, "ucs2")).toBe(0); - expect(Buffer.compare(z, Buffer.alloc(4, 0))).toBe(0); - // Make sure longer strings are written up to the buffer end. - expect(z.write("abcd", 2)).toBe(2); - expect([...z]).toStrictEqual([0, 0, 0x61, 0x62]); - - //Large overrun could corrupt the process with utf8 - expect(Buffer.alloc(4).write("a".repeat(100), 3, "utf8")).toBe(1); - - // Large overrun could corrupt the process - expect(Buffer.alloc(4).write("ыыыыыы".repeat(100), 3, "utf16le")).toBe(0); - - { - // .write() does not affect the byte after the written-to slice of the Buffer. - // Refs: https://github.com/nodejs/node/issues/26422 - const buf = Buffer.alloc(8); - expect(buf.write("ыы", 1, "utf16le")).toBe(4); - expect([...buf]).toStrictEqual([0, 0x4b, 0x04, 0x4b, 0x04, 0, 0, 0]); - } -}); - -it("includes", () => { - const buf = Buffer.from("this is a buffer"); - - expect(buf.includes("this")).toBe(true); - expect(buf.includes("is")).toBe(true); - expect(buf.includes(Buffer.from("a buffer"))).toBe(true); - expect(buf.includes(97)).toBe(true); - expect(buf.includes(Buffer.from("a buffer example"))).toBe(false); - expect(buf.includes(Buffer.from("a buffer example").slice(0, 8))).toBe(true); - expect(buf.includes("this", 4)).toBe(false); -}); - -it("indexOf", () => { - const buf = Buffer.from("this is a buffer"); - - expect(buf.indexOf("this")).toBe(0); - expect(buf.indexOf("is")).toBe(2); - expect(buf.indexOf(Buffer.from("a buffer"))).toBe(8); - expect(buf.indexOf(97)).toBe(8); - expect(buf.indexOf(Buffer.from("a buffer example"))).toBe(-1); - expect(buf.indexOf(Buffer.from("a buffer example").slice(0, 8))).toBe(8); - - const utf16Buffer = Buffer.from("\u039a\u0391\u03a3\u03a3\u0395", "utf16le"); - - expect(utf16Buffer.indexOf("\u03a3", 0, "utf16le")).toBe(4); - expect(utf16Buffer.indexOf("\u03a3", -4, "utf16le")).toBe(6); - - const b = Buffer.from("abcdef"); - - // Passing a value that's a number, but not a valid byte. - // Prints: 2, equivalent to searching for 99 or 'c'. - expect(b.indexOf(99.9)).toBe(2); - expect(b.indexOf(256 + 99)).toBe(2); - - // Passing a byteOffset that coerces to NaN or 0. - // Prints: 1, searching the whole buffer. - expect(b.indexOf("b", undefined)).toBe(1); - expect(b.indexOf("b", {})).toBe(1); - expect(b.indexOf("b", null)).toBe(1); - expect(b.indexOf("b", [])).toBe(1); -}); - -it("lastIndexOf", () => { - const buf = Buffer.from("this buffer is a buffer"); - - expect(buf.lastIndexOf("this")).toBe(0); - expect(buf.lastIndexOf("this", 0)).toBe(0); - expect(buf.lastIndexOf("this", -1000)).toBe(-1); - expect(buf.lastIndexOf("buffer")).toBe(17); - expect(buf.lastIndexOf(Buffer.from("buffer"))).toBe(17); - expect(buf.lastIndexOf(97)).toBe(15); - expect(buf.lastIndexOf(Buffer.from("yolo"))).toBe(-1); - expect(buf.lastIndexOf("buffer", 5)).toBe(5); - expect(buf.lastIndexOf("buffer", 4)).toBe(-1); - - const utf16Buffer = Buffer.from("\u039a\u0391\u03a3\u03a3\u0395", "utf16le"); - - expect(utf16Buffer.lastIndexOf("\u03a3", undefined, "utf16le")).toBe(6); - expect(utf16Buffer.lastIndexOf("\u03a3", -5, "utf16le")).toBe(4); - - const b = Buffer.from("abcdef"); - - // Passing a value that's a number, but not a valid byte. - // Prints: 2, equivalent to searching for 99 or 'c'. - expect(b.lastIndexOf(99.9)).toBe(2); - expect(b.lastIndexOf(256 + 99)).toBe(2); - - // Passing a byteOffset that coerces to NaN or 0. - // Prints: 1, searching the whole buffer. - expect(b.lastIndexOf("b", undefined)).toBe(1); - expect(b.lastIndexOf("b", {})).toBe(1); - - // Passing a byteOffset that coerces to 0. - // Prints: -1, equivalent to passing 0. - expect(b.lastIndexOf("b", null)).toBe(-1); - expect(b.lastIndexOf("b", [])).toBe(-1); -}); - -for (let fn of [Buffer.prototype.slice, Buffer.prototype.subarray]) { - it(`Buffer.${fn.name}`, () => { - const buf = new Buffer("buffer"); - const slice = fn.call(buf, 1, 3); - expect(slice.toString()).toBe("uf"); - const slice2 = fn.call(slice, 100); - expect(slice2.toString()).toBe(""); - - const slice3 = fn.call(slice, -1); - expect(slice3.toString()).toBe("f"); - }); -} - -it("Buffer.from(base64)", () => { - const buf = Buffer.from("aGVsbG8gd29ybGQ=", "base64"); - expect(buf.toString()).toBe("hello world"); - - expect(Buffer.from(btoa('console.log("hello world")\n'), "base64").toString()).toBe('console.log("hello world")\n'); -}); - -it("Buffer.swap16", () => { - const examples = [ - ["", ""], - ["a1", "1a"], - ["a1b2", "1a2b"], - ]; - - for (let i = 0; i < examples.length; i++) { - const input = examples[i][0]; - const output = examples[i][1]; - const buf = Buffer.from(input, "utf-8"); - - const ref = buf.swap16(); - expect(ref instanceof Buffer).toBe(true); - expect(buf.toString()).toBe(output); - } - - const buf = Buffer.from("123", "utf-8"); - try { - buf.swap16(); - expect(false).toBe(true); - } catch (exception) { - expect(exception.message).toBe("Buffer size must be a multiple of 16-bits"); - } -}); - -it("Buffer.swap32", () => { - const examples = [ - ["", ""], - ["a1b2", "2b1a"], - ["a1b2c3d4", "2b1a4d3c"], - ]; - - for (let i = 0; i < examples.length; i++) { - const input = examples[i][0]; - const output = examples[i][1]; - const buf = Buffer.from(input, "utf-8"); - - const ref = buf.swap32(); - expect(ref instanceof Buffer).toBe(true); - expect(buf.toString()).toBe(output); - } - - const buf = Buffer.from("12345", "utf-8"); - try { - buf.swap32(); - expect(false).toBe(true); - } catch (exception) { - expect(exception.message).toBe("Buffer size must be a multiple of 32-bits"); - } -}); - -it("Buffer.swap64", () => { - const examples = [ - ["", ""], - ["a1b2c3d4", "4d3c2b1a"], - ["a1b2c3d4e5f6g7h8", "4d3c2b1a8h7g6f5e"], - ]; - - for (let i = 0; i < examples.length; i++) { - const input = examples[i][0]; - const output = examples[i][1]; - const buf = Buffer.from(input, "utf-8"); - - const ref = buf.swap64(); - expect(ref instanceof Buffer).toBe(true); - expect(buf.toString()).toBe(output); - } - - const buf = Buffer.from("123456789", "utf-8"); - try { - buf.swap64(); - expect(false).toBe(true); - } catch (exception) { - expect(exception.message).toBe("Buffer size must be a multiple of 64-bits"); - } -}); - -it("Buffer.toString regessions", () => { - expect( - Buffer.from([65, 0]) - .toString("utf16le") - .split("") - .map(x => x.charCodeAt(0)), - ).toEqual([65]); - expect(Buffer.from([65, 0]).toString("base64")).toBe("QQA="); - expect(Buffer.from('{"alg":"RS256","typ":"JWT"}', "latin1").toString("latin1")).toBe('{"alg":"RS256","typ":"JWT"}'); - expect(Buffer.from('{"alg":"RS256","typ":"JWT"}', "utf8").toString("utf8")).toBe('{"alg":"RS256","typ":"JWT"}'); -}); - -it("Buffer.toString(utf16le)", () => { - const buf = Buffer.from("hello world", "utf16le"); - expect(buf.toString("utf16le")).toBe("hello world"); - expect(buf.toString("utf16le", 0, 5)).toBe("he"); -}); - -it("Buffer.toString(binary)", () => { - var x = Buffer.from(" { - { - const buf = Buffer.from("hello world"); - expect(buf.toString("base64")).toBe("aGVsbG8gd29ybGQ="); - } - - { - expect(Buffer.from(`console.log("hello world")\n`).toString("base64")).toBe(btoa('console.log("hello world")\n')); - } -}); - -it("Buffer can be mocked", () => { - function MockBuffer() { - const noop = function () {}; - const res = Buffer.alloc(0); - for (const op in Buffer.prototype) { - if (typeof res[op] === "function") { - res[op] = noop; - } - } - return res; - } - - const buf = MockBuffer(); - - expect(() => { - buf.write("hello world"); - buf.writeUint16BE(0); - buf.writeUint32BE(0); - buf.writeBigInt64BE(0); - buf.writeBigUInt64BE(0); - buf.writeBigInt64LE(0); - buf.writeBigUInt64LE(0); - }).not.toThrow(); -}); - -it("constants", () => { - expect(BufferModule.constants.MAX_LENGTH).toBe(4294967296); - expect(BufferModule.constants.MAX_STRING_LENGTH).toBe(536870888); - expect(BufferModule.default.constants.MAX_LENGTH).toBe(4294967296); - expect(BufferModule.default.constants.MAX_STRING_LENGTH).toBe(536870888); -}); - -it("File", () => { - expect(BufferModule.File).toBe(Blob); -}); - -it("transcode", () => { - expect(typeof BufferModule.transcode).toBe("undefined"); - - // This is a masqueradesAsUndefined function - expect(() => BufferModule.transcode()).toThrow("Not implemented"); -}); - -it("Buffer.from (Node.js test/test-buffer-from.js)", () => { - const checkString = "test"; - - const check = Buffer.from(checkString); - - class MyString extends String { - constructor() { - super(checkString); - } - } - - class MyPrimitive { - [Symbol.toPrimitive]() { - return checkString; - } - } - - class MyBadPrimitive { - [Symbol.toPrimitive]() { - return 1; - } - } - - expect(Buffer.from(new String(checkString))).toStrictEqual(check); - expect(Buffer.from(new MyString())).toStrictEqual(check); - expect(Buffer.from(new MyPrimitive())).toStrictEqual(check); - - [ - {}, - new Boolean(true), - { - valueOf() { - return null; - }, - }, - { - valueOf() { - return undefined; - }, - }, - { valueOf: null }, - Object.create(null), - new Number(true), - new MyBadPrimitive(), - Symbol(), - 5n, - (one, two, three) => {}, - undefined, - null, - ].forEach(input => { - expect(() => Buffer.from(input)).toThrow(); - expect(() => Buffer.from(input, "hex")).toThrow(); - }); - - expect(() => Buffer.allocUnsafe(10)).not.toThrow(); // Should not throw. - expect(() => Buffer.from("deadbeaf", "hex")).not.toThrow(); // Should not throw. -}); - -it("new Buffer() (Node.js test/test-buffer-new.js)", () => { - const LENGTH = 16; - - const ab = new ArrayBuffer(LENGTH); - const dv = new DataView(ab); - const ui = new Uint8Array(ab); - const buf = Buffer.from(ab); - - expect(buf instanceof Buffer).toBe(true); - // expect(buf.parent, buf.buffer); - expect(buf.buffer).toBe(ab); - expect(buf.length).toBe(ab.byteLength); - - buf.fill(0xc); - for (let i = 0; i < LENGTH; i++) { - expect(ui[i]).toBe(0xc); - ui[i] = 0xf; - expect(buf[i]).toBe(0xf); - } - - buf.writeUInt32LE(0xf00, 0); - buf.writeUInt32BE(0xb47, 4); - buf.writeDoubleLE(3.1415, 8); - expect(dv.getUint32(0, true)).toBe(0xf00); - expect(dv.getUint32(4)).toBe(0xb47); - expect(dv.getFloat64(8, true)).toBe(3.1415); - - // Now test protecting users from doing stupid things - - // expect(function () { - // function AB() {} - // Object.setPrototypeOf(AB, ArrayBuffer); - // Object.setPrototypeOf(AB.prototype, ArrayBuffer.prototype); - // // Buffer.from(new AB()); - // }).toThrow(); - // console.log(origAB !== ab); - - // Test the byteOffset and length arguments - { - const ab = new Uint8Array(5); - ab[0] = 1; - ab[1] = 2; - ab[2] = 3; - ab[3] = 4; - ab[4] = 5; - const buf = Buffer.from(ab.buffer, 1, 3); - expect(buf.length).toBe(3); - expect(buf[0]).toBe(2); - expect(buf[1]).toBe(3); - expect(buf[2]).toBe(4); - buf[0] = 9; - expect(ab[1]).toBe(9); - - expect(() => Buffer.from(ab.buffer, 6)).toThrow(); - expect(() => Buffer.from(ab.buffer, 3, 6)).toThrow(); - } - - // Test the deprecated Buffer() version also - { - const ab = new Uint8Array(5); - ab[0] = 1; - ab[1] = 2; - ab[2] = 3; - ab[3] = 4; - ab[4] = 5; - const buf = Buffer(ab.buffer, 1, 3); - expect(buf.length).toBe(3); - expect(buf[0]).toBe(2); - expect(buf[1]).toBe(3); - expect(buf[2]).toBe(4); - buf[0] = 9; - expect(ab[1]).toBe(9); - - expect(() => Buffer(ab.buffer, 6)).toThrow(); - expect(() => Buffer(ab.buffer, 3, 6)).toThrow(); - } - - { - // If byteOffset is not numeric, it defaults to 0. - const ab = new ArrayBuffer(10); - const expected = Buffer.from(ab, 0); - expect(Buffer.from(ab, "fhqwhgads")).toStrictEqual(expected); - expect(Buffer.from(ab, NaN)).toStrictEqual(expected); - expect(Buffer.from(ab, {})).toStrictEqual(expected); - expect(Buffer.from(ab, [])).toStrictEqual(expected); - - // If byteOffset can be converted to a number, it will be. - expect(Buffer.from(ab, [1])).toStrictEqual(Buffer.from(ab, 1)); - - // If byteOffset is Infinity, throw. - expect(() => { - Buffer.from(ab, Infinity); - }).toThrow(); - } - - { - // If length is not numeric, it defaults to 0. - const ab = new ArrayBuffer(10); - const expected = Buffer.from(ab, 0, 0); - expect(Buffer.from(ab, 0, "fhqwhgads")).toStrictEqual(expected); - expect(Buffer.from(ab, 0, NaN)).toStrictEqual(expected); - expect(Buffer.from(ab, 0, {})).toStrictEqual(expected); - expect(Buffer.from(ab, 0, [])).toStrictEqual(expected); - - // If length can be converted to a number, it will be. - expect(Buffer.from(ab, 0, [1])).toStrictEqual(Buffer.from(ab, 0, 1)); - - // If length is Infinity, throw. - expect(() => Buffer.from(ab, 0, Infinity)).toThrow(); - } - - // Test an array like entry with the length set to NaN. - expect(Buffer.from({ length: NaN })).toStrictEqual(Buffer.alloc(0)); -}); - -it("Buffer.fill (Node.js tests)", () => { - "use strict"; - const SIZE = 28; - - const buf1 = Buffer.allocUnsafe(SIZE); - const buf2 = Buffer.allocUnsafe(SIZE); - - function assertEqual(a, b) { - expect(a).toEqual(b); - } - - // Default encoding - testBufs("abc"); - testBufs("\u0222aa"); - testBufs("a\u0234b\u0235c\u0236"); - testBufs("abc", 4); - testBufs("abc", 5); - testBufs("abc", SIZE); - testBufs("\u0222aa", 2); - testBufs("\u0222aa", 8); - testBufs("a\u0234b\u0235c\u0236", 4); - testBufs("a\u0234b\u0235c\u0236", 12); - testBufs("abc", 4, 1); - testBufs("abc", 5, 1); - testBufs("\u0222aa", 8, 1); - testBufs("a\u0234b\u0235c\u0236", 4, 1); - testBufs("a\u0234b\u0235c\u0236", 12, 1); - - // UTF8 - testBufs("abc", "utf8"); - testBufs("\u0222aa", "utf8"); - testBufs("a\u0234b\u0235c\u0236", "utf8"); - testBufs("abc", 4, "utf8"); - testBufs("abc", 5, "utf8"); - testBufs("abc", SIZE, "utf8"); - testBufs("\u0222aa", 2, "utf8"); - testBufs("\u0222aa", 8, "utf8"); - testBufs("a\u0234b\u0235c\u0236", 4, "utf8"); - testBufs("a\u0234b\u0235c\u0236", 12, "utf8"); - testBufs("abc", 4, 1, "utf8"); - testBufs("abc", 5, 1, "utf8"); - testBufs("\u0222aa", 8, 1, "utf8"); - testBufs("a\u0234b\u0235c\u0236", 4, 1, "utf8"); - testBufs("a\u0234b\u0235c\u0236", 12, 1, "utf8"); - assertEqual(Buffer.allocUnsafe(1).fill(0).fill("\u0222")[0], 0xc8); - - // BINARY - testBufs("abc", "binary"); - testBufs("\u0222aa", "binary"); - testBufs("a\u0234b\u0235c\u0236", "binary"); - testBufs("abc", 4, "binary"); - testBufs("abc", 5, "binary"); - testBufs("abc", SIZE, "binary"); - testBufs("\u0222aa", 2, "binary"); - testBufs("\u0222aa", 8, "binary"); - testBufs("a\u0234b\u0235c\u0236", 4, "binary"); - testBufs("a\u0234b\u0235c\u0236", 12, "binary"); - testBufs("abc", 4, 1, "binary"); - testBufs("abc", 5, 1, "binary"); - testBufs("\u0222aa", 8, 1, "binary"); - testBufs("a\u0234b\u0235c\u0236", 4, 1, "binary"); - testBufs("a\u0234b\u0235c\u0236", 12, 1, "binary"); - - // LATIN1 - testBufs("abc", "latin1"); - testBufs("\u0222aa", "latin1"); - testBufs("a\u0234b\u0235c\u0236", "latin1"); - testBufs("abc", 4, "latin1"); - testBufs("abc", 5, "latin1"); - testBufs("abc", SIZE, "latin1"); - testBufs("\u0222aa", 2, "latin1"); - testBufs("\u0222aa", 8, "latin1"); - testBufs("a\u0234b\u0235c\u0236", 4, "latin1"); - testBufs("a\u0234b\u0235c\u0236", 12, "latin1"); - testBufs("abc", 4, 1, "latin1"); - testBufs("abc", 5, 1, "latin1"); - testBufs("\u0222aa", 8, 1, "latin1"); - testBufs("a\u0234b\u0235c\u0236", 4, 1, "latin1"); - testBufs("a\u0234b\u0235c\u0236", 12, 1, "latin1"); - - // UCS2 - testBufs("abc", "ucs2"); - testBufs("\u0222aa", "ucs2"); - testBufs("a\u0234b\u0235c\u0236", "ucs2"); - testBufs("abc", 4, "ucs2"); - testBufs("abc", SIZE, "ucs2"); - testBufs("\u0222aa", 2, "ucs2"); - testBufs("\u0222aa", 8, "ucs2"); - testBufs("a\u0234b\u0235c\u0236", 4, "ucs2"); - testBufs("a\u0234b\u0235c\u0236", 12, "ucs2"); - testBufs("abc", 4, 1, "ucs2"); - testBufs("abc", 5, 1, "ucs2"); - testBufs("\u0222aa", 8, 1, "ucs2"); - testBufs("a\u0234b\u0235c\u0236", 4, 1, "ucs2"); - testBufs("a\u0234b\u0235c\u0236", 12, 1, "ucs2"); - assertEqual(Buffer.allocUnsafe(1).fill("\u0222", "ucs2")[0], 0x22); - - // HEX - testBufs("616263", "hex"); - testBufs("c8a26161", "hex"); - testBufs("61c8b462c8b563c8b6", "hex"); - testBufs("616263", 4, "hex"); - testBufs("616263", 5, "hex"); - testBufs("616263", SIZE, "hex"); - testBufs("c8a26161", 2, "hex"); - testBufs("c8a26161", 8, "hex"); - testBufs("61c8b462c8b563c8b6", 4, "hex"); - testBufs("61c8b462c8b563c8b6", 12, "hex"); - testBufs("616263", 4, 1, "hex"); - testBufs("616263", 5, 1, "hex"); - testBufs("c8a26161", 8, 1, "hex"); - testBufs("61c8b462c8b563c8b6", 4, 1, "hex"); - testBufs("61c8b462c8b563c8b6", 12, 1, "hex"); - - expect(() => { - const buf = Buffer.allocUnsafe(SIZE); - - buf.fill("yKJh", "hex"); - }).toThrow(); - - expect(() => { - const buf = Buffer.allocUnsafe(SIZE); - - buf.fill("\u0222", "hex"); - }).toThrow(); - - // BASE64 - testBufs("YWJj", "base64"); - testBufs("yKJhYQ==", "base64"); - testBufs("Yci0Ysi1Y8i2", "base64"); - testBufs("YWJj", 4, "base64"); - testBufs("YWJj", SIZE, "base64"); - testBufs("yKJhYQ==", 2, "base64"); - testBufs("yKJhYQ==", 8, "base64"); - testBufs("Yci0Ysi1Y8i2", 4, "base64"); - testBufs("Yci0Ysi1Y8i2", 12, "base64"); - testBufs("YWJj", 4, 1, "base64"); - testBufs("YWJj", 5, 1, "base64"); - testBufs("yKJhYQ==", 8, 1, "base64"); - testBufs("Yci0Ysi1Y8i2", 4, 1, "base64"); - testBufs("Yci0Ysi1Y8i2", 12, 1, "base64"); - - // BASE64URL - testBufs("YWJj", "base64url"); - testBufs("yKJhYQ", "base64url"); - testBufs("Yci0Ysi1Y8i2", "base64url"); - testBufs("YWJj", 4, "base64url"); - testBufs("YWJj", SIZE, "base64url"); - testBufs("yKJhYQ", 2, "base64url"); - testBufs("yKJhYQ", 8, "base64url"); - testBufs("Yci0Ysi1Y8i2", 4, "base64url"); - testBufs("Yci0Ysi1Y8i2", 12, "base64url"); - testBufs("YWJj", 4, 1, "base64url"); - testBufs("YWJj", 5, 1, "base64url"); - testBufs("yKJhYQ", 8, 1, "base64url"); - testBufs("Yci0Ysi1Y8i2", 4, 1, "base64url"); - testBufs("Yci0Ysi1Y8i2", 12, 1, "base64url"); - - // Buffer - function deepStrictEqualValues(buf, arr) { - for (const [index, value] of buf.entries()) { - expect(value).toStrictEqual(arr[index]); - } - } - - const buf2Fill = Buffer.allocUnsafe(1).fill(2); - deepStrictEqualValues(genBuffer(4, [buf2Fill]), [2, 2, 2, 2]); - deepStrictEqualValues(genBuffer(4, [buf2Fill, 1]), [0, 2, 2, 2]); - deepStrictEqualValues(genBuffer(4, [buf2Fill, 1, 3]), [0, 2, 2, 0]); - deepStrictEqualValues(genBuffer(4, [buf2Fill, 1, 1]), [0, 0, 0, 0]); - const hexBufFill = Buffer.allocUnsafe(2).fill(0).fill("0102", "hex"); - deepStrictEqualValues(genBuffer(4, [hexBufFill]), [1, 2, 1, 2]); - deepStrictEqualValues(genBuffer(4, [hexBufFill, 1]), [0, 1, 2, 1]); - deepStrictEqualValues(genBuffer(4, [hexBufFill, 1, 3]), [0, 1, 2, 0]); - deepStrictEqualValues(genBuffer(4, [hexBufFill, 1, 1]), [0, 0, 0, 0]); - - // Check exceptions - [ - [0, -1], - [0, 0, buf1.length + 1], - ["", -1], - ["", 0, buf1.length + 1], - ["", 1, -1], - ].forEach(args => { - expect(() => buf1.fill(...args)).toThrow(); - }); - - expect(() => buf1.fill("a", 0, buf1.length, "node rocks!")).toThrow(); - - [ - ["a", 0, 0, NaN], - ["a", 0, 0, false], - ].forEach(args => { - expect(() => buf1.fill(...args)).toThrow(); - }); - - expect(() => buf1.fill("a", 0, 0, "foo")).toThrow(); - - function genBuffer(size, args) { - const b = Buffer.allocUnsafe(size); - return b.fill(0).fill.apply(b, args); - } - - function bufReset() { - buf1.fill(0); - buf2.fill(0); - } - - // This is mostly accurate. Except write() won't write partial bytes to the - // string while fill() blindly copies bytes into memory. To account for that an - // error will be thrown if not all the data can be written, and the SIZE has - // been massaged to work with the input characters. - function writeToFill(string, offset, end, encoding) { - if (typeof offset === "string") { - encoding = offset; - offset = 0; - end = buf2.length; - } else if (typeof end === "string") { - encoding = end; - end = buf2.length; - } else if (end === undefined) { - end = buf2.length; - } - - // Should never be reached. - if (offset < 0 || end > buf2.length) throw new ERR_OUT_OF_RANGE(); - - if (end <= offset) return buf2; - - offset >>>= 0; - end >>>= 0; - expect(offset <= buf2.length).toBe(true); - - // Convert "end" to "length" (which write understands). - const length = end - offset < 0 ? 0 : end - offset; - - let wasZero = false; - do { - const written = buf2.write(string, offset, length, encoding); - offset += written; - // Safety check in case write falls into infinite loop. - if (written === 0) { - if (wasZero) throw new Error("Could not write all data to Buffer at " + offset); - else wasZero = true; - } - } while (offset < buf2.length); - - return buf2; - } - - function testBufs(string, offset, length, encoding) { - bufReset(); - buf1.fill.apply(buf1, arguments); - // Swap bytes on BE archs for ucs2 encoding. - expect(buf1.fill.apply(buf1, arguments)).toStrictEqual(writeToFill.apply(null, arguments)); - } - - // Make sure these throw. - expect(() => Buffer.allocUnsafe(8).fill("a", -1)).toThrow(); - expect(() => Buffer.allocUnsafe(8).fill("a", 0, 9)).toThrow(); - - // Make sure this doesn't hang indefinitely. - Buffer.allocUnsafe(8).fill(""); - Buffer.alloc(8, ""); - - { - const buf = Buffer.alloc(64, 10); - for (let i = 0; i < buf.length; i++) assertEqual(buf[i], 10); - - buf.fill(11, 0, buf.length >> 1); - for (let i = 0; i < buf.length >> 1; i++) assertEqual(buf[i], 11); - for (let i = (buf.length >> 1) + 1; i < buf.length; i++) assertEqual(buf[i], 10); - - buf.fill("h"); - for (let i = 0; i < buf.length; i++) assertEqual(buf[i], "h".charCodeAt(0)); - - buf.fill(0); - for (let i = 0; i < buf.length; i++) assertEqual(buf[i], 0); - - buf.fill(null); - for (let i = 0; i < buf.length; i++) assertEqual(buf[i], 0); - - buf.fill(1, 16, 32); - for (let i = 0; i < 16; i++) assertEqual(buf[i], 0); - for (let i = 16; i < 32; i++) assertEqual(buf[i], 1); - for (let i = 32; i < buf.length; i++) assertEqual(buf[i], 0); - } - - { - const buf = Buffer.alloc(10, "abc"); - assertEqual(buf.toString(), "abcabcabca"); - buf.fill("է"); - assertEqual(buf.toString(), "էէէէէ"); - } - - // // Testing process.binding. Make sure "start" is properly checked for range - // // errors. - // assert.throws( - // () => { - // internalBinding("buffer").fill(Buffer.alloc(1), 1, -1, 0, 1); - // }, - // { code: "ERR_OUT_OF_RANGE" }, - // ); - - // Make sure "end" is properly checked, even if it's magically mangled using - // Symbol.toPrimitive. - { - expect(() => { - const end = { - [Symbol.toPrimitive]() { - return 1; - }, - }; - Buffer.alloc(1).fill(Buffer.alloc(1), 0, end); - }).toThrow(); - } - - // Testing process.binding. Make sure "end" is properly checked for range - // errors. - // assert.throws( - // () => { - // internalBinding("buffer").fill(Buffer.alloc(1), 1, 1, -2, 1); - // }, - // { code: "ERR_OUT_OF_RANGE" }, - // ); - - // Test that bypassing 'length' won't cause an abort. - expect(() => { - const buf = Buffer.from("w00t"); - Object.defineProperty(buf, "length", { - value: 1337, - enumerable: true, - }); - buf.fill(""); - }).toThrow(); - - assertEqual(Buffer.allocUnsafeSlow(16).fill("ab", "utf16le"), Buffer.from("61006200610062006100620061006200", "hex")); - - assertEqual(Buffer.allocUnsafeSlow(15).fill("ab", "utf16le"), Buffer.from("610062006100620061006200610062", "hex")); - - assertEqual(Buffer.allocUnsafeSlow(16).fill("ab", "utf16le"), Buffer.from("61006200610062006100620061006200", "hex")); - assertEqual(Buffer.allocUnsafeSlow(16).fill("a", "utf16le"), Buffer.from("61006100610061006100610061006100", "hex")); - - assertEqual(Buffer.allocUnsafeSlow(16).fill("a", "utf16le").toString("utf16le"), "a".repeat(8)); - assertEqual(Buffer.allocUnsafeSlow(16).fill("a", "latin1").toString("latin1"), "a".repeat(16)); - assertEqual(Buffer.allocUnsafeSlow(16).fill("a", "utf8").toString("utf8"), "a".repeat(16)); - - assertEqual(Buffer.allocUnsafeSlow(16).fill("Љ", "utf16le").toString("utf16le"), "Љ".repeat(8)); - assertEqual(Buffer.allocUnsafeSlow(16).fill("Љ", "latin1").toString("latin1"), "\t".repeat(16)); - assertEqual(Buffer.allocUnsafeSlow(16).fill("Љ", "utf8").toString("utf8"), "Љ".repeat(8)); - - expect(() => { - const buf = Buffer.from("a".repeat(1000)); - - buf.fill("This is not correctly encoded", "hex"); - }).toThrow(); -}); - -test("Buffer.byteLength", () => { - const SlowBuffer = require("buffer").SlowBuffer; - - [[32, "latin1"], [NaN, "utf8"], [{}, "latin1"], []].forEach(args => { - assert.throws(() => Buffer.byteLength(...args)); - }); - - assert.strictEqual(Buffer.byteLength("", undefined, true), 0); - - assert(ArrayBuffer.isView(new Buffer(10))); - assert(ArrayBuffer.isView(new SlowBuffer(10))); - assert(ArrayBuffer.isView(Buffer.alloc(10))); - assert(ArrayBuffer.isView(Buffer.allocUnsafe(10))); - assert(ArrayBuffer.isView(Buffer.allocUnsafeSlow(10))); - assert(ArrayBuffer.isView(Buffer.from(""))); - - // buffer - const incomplete = Buffer.from([0xe4, 0xb8, 0xad, 0xe6, 0x96]); - assert.strictEqual(Buffer.byteLength(incomplete), 5); - const ascii = Buffer.from("abc"); - assert.strictEqual(Buffer.byteLength(ascii), 3); - - // ArrayBuffer - const buffer = new ArrayBuffer(8); - assert.strictEqual(Buffer.byteLength(buffer), 8); - - // TypedArray - const int8 = new Int8Array(8); - assert.strictEqual(Buffer.byteLength(int8), 8); - const uint8 = new Uint8Array(8); - assert.strictEqual(Buffer.byteLength(uint8), 8); - const uintc8 = new Uint8ClampedArray(2); - assert.strictEqual(Buffer.byteLength(uintc8), 2); - const int16 = new Int16Array(8); - assert.strictEqual(Buffer.byteLength(int16), 16); - const uint16 = new Uint16Array(8); - assert.strictEqual(Buffer.byteLength(uint16), 16); - const int32 = new Int32Array(8); - assert.strictEqual(Buffer.byteLength(int32), 32); - const uint32 = new Uint32Array(8); - assert.strictEqual(Buffer.byteLength(uint32), 32); - const float32 = new Float32Array(8); - assert.strictEqual(Buffer.byteLength(float32), 32); - const float64 = new Float64Array(8); - assert.strictEqual(Buffer.byteLength(float64), 64); - - // DataView - const dv = new DataView(new ArrayBuffer(2)); - assert.strictEqual(Buffer.byteLength(dv), 2); - - // Special case: zero length string - assert.strictEqual(Buffer.byteLength("", "ascii"), 0); - assert.strictEqual(Buffer.byteLength("", "HeX"), 0); - - // utf8 - assert.strictEqual(Buffer.byteLength("∑éllö wørl∂!", "utf-8"), 19); - assert.strictEqual(Buffer.byteLength("κλμνξο", "utf8"), 12); - assert.strictEqual(Buffer.byteLength("挵挶挷挸挹", "utf-8"), 15); - assert.strictEqual(Buffer.byteLength("𠝹𠱓𠱸", "UTF8"), 12); - // Without an encoding, utf8 should be assumed - assert.strictEqual(Buffer.byteLength("hey there"), 9); - assert.strictEqual(Buffer.byteLength("𠱸挶νξ#xx :)"), 17); - assert.strictEqual(Buffer.byteLength("hello world", ""), 11); - // It should also be assumed with unrecognized encoding - assert.strictEqual(Buffer.byteLength("hello world", "abc"), 11); - assert.strictEqual(Buffer.byteLength("ßœ∑≈", "unkn0wn enc0ding"), 10); - - // base64 - assert.strictEqual(Buffer.byteLength("aGVsbG8gd29ybGQ=", "base64"), 11); - assert.strictEqual(Buffer.byteLength("aGVsbG8gd29ybGQ=", "BASE64"), 11); - assert.strictEqual(Buffer.byteLength("bm9kZS5qcyByb2NrcyE=", "base64"), 14); - assert.strictEqual(Buffer.byteLength("aGkk", "base64"), 3); - assert.strictEqual(Buffer.byteLength("bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw==", "base64"), 25); - // base64url - assert.strictEqual(Buffer.byteLength("aGVsbG8gd29ybGQ", "base64url"), 11); - assert.strictEqual(Buffer.byteLength("aGVsbG8gd29ybGQ", "BASE64URL"), 11); - assert.strictEqual(Buffer.byteLength("bm9kZS5qcyByb2NrcyE", "base64url"), 14); - assert.strictEqual(Buffer.byteLength("aGkk", "base64url"), 3); - assert.strictEqual(Buffer.byteLength("bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw", "base64url"), 25); - // special padding - assert.strictEqual(Buffer.byteLength("aaa=", "base64"), 2); - assert.strictEqual(Buffer.byteLength("aaaa==", "base64"), 3); - assert.strictEqual(Buffer.byteLength("aaa=", "base64url"), 2); - assert.strictEqual(Buffer.byteLength("aaaa==", "base64url"), 3); - assert.strictEqual(Buffer.byteLength("Il était tué", "utf8"), 14); - assert.strictEqual(Buffer.byteLength("Il était tué"), 14); - - ["ascii", "latin1", "binary"] - .reduce((es, e) => es.concat(e, e.toUpperCase()), []) - .forEach(encoding => { - assert.strictEqual(Buffer.byteLength("Il était tué", encoding), 12); - }); - - ["ucs2", "ucs-2", "utf16le", "utf-16le"] - .reduce((es, e) => es.concat(e, e.toUpperCase()), []) - .forEach(encoding => { - assert.strictEqual(Buffer.byteLength("Il était tué", encoding), 24); - }); - - // Test that ArrayBuffer from a different context is detected correctly - // const arrayBuf = vm.runInNewContext("new ArrayBuffer()"); - // assert.strictEqual(Buffer.byteLength(arrayBuf), 0); - - // Verify that invalid encodings are treated as utf8 - for (let i = 1; i < 10; i++) { - const encoding = String(i).repeat(i); - - assert.ok(!Buffer.isEncoding(encoding)); - assert.strictEqual(Buffer.byteLength("foo", encoding), Buffer.byteLength("foo", "utf8")); - } -}); - -it("should not crash on invalid UTF-8 byte sequence", () => { - const buf = Buffer.from([0xc0, 0xfd]); - expect(buf.length).toBe(2); - const str = buf.toString(); - expect(str.length).toBe(2); - expect(str).toBe("\uFFFD\uFFFD"); -}); - -it("should not crash on invalid UTF-8 byte sequence with ASCII head", () => { - const buf = Buffer.from([0x42, 0xc0, 0xfd]); - expect(buf.length).toBe(3); - const str = buf.toString(); - expect(str.length).toBe(3); - expect(str).toBe("B\uFFFD\uFFFD"); -}); - -it("should not perform out-of-bound access on invalid UTF-8 byte sequence", () => { - const buf = Buffer.from([0x01, 0x9a, 0x84, 0x13, 0x12, 0x11, 0x10, 0x09]).subarray(2); - expect(buf.length).toBe(6); - const str = buf.toString(); - expect(str.length).toBe(6); - expect(str).toBe("\uFFFD\x13\x12\x11\x10\x09"); -}); - -it("repro #2063", () => { - const buf = Buffer.from( - "eyJlbWFpbCI6Ijg3MTg4NDYxN0BxcS5jb20iLCJpZCI6OCwicm9sZSI6Im5vcm1hbCIsImlhdCI6MTY3NjI4NDQyMSwiZXhwIjoxNjc2ODg5MjIxfQ", - "base64", - ); - expect(buf.length).toBe(85); - expect(buf[82]).toBe(50); - expect(buf[83]).toBe(49); - expect(buf[84]).toBe(125); -}); diff --git a/test/bun.js/bufferlist.test.ts b/test/bun.js/bufferlist.test.ts deleted file mode 100644 index b8a5443ea..000000000 --- a/test/bun.js/bufferlist.test.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { Readable } from "stream"; -import { it, expect } from "bun:test"; - -function makeUint8Array(str) { - return new Uint8Array( - [].map.call(str, function (ch) { - return ch.charCodeAt(0); - }), - ); -} - -it("should work with .clear()", () => { - const list = new Readable().readableBuffer; - expect(list.length).toBe(0); - expect(list.push({})).toBeUndefined(); - expect(list.length).toBe(1); - expect(list.push({})).toBeUndefined(); - expect(list.length).toBe(2); - expect(list.clear()).toBeUndefined(); - expect(list.length).toBe(0); -}); - -it("should work with .concat()", () => { - const list = new Readable().readableBuffer; - expect(list.length).toBe(0); - expect(list.push(makeUint8Array("foo"))).toBeUndefined(); - expect(list.length).toBe(1); - expect(list.concat(3)).toEqual(new Uint8Array([102, 111, 111])); - expect(list.push(makeUint8Array("bar"))).toBeUndefined(); - expect(list.length).toBe(2); - expect(list.concat(10)).toEqual(new Uint8Array([102, 111, 111, 98, 97, 114, 0, 0, 0, 0])); -}); - -it("should fail on .concat() with invalid items", () => { - const list = new Readable().readableBuffer; - expect(list.length).toBe(0); - expect(list.push("foo")).toBeUndefined(); - expect(() => { - list.concat(42); - }).toThrow(TypeError); -}); - -it("should fail on .concat() buffer overflow", () => { - const list = new Readable().readableBuffer; - expect(list.length).toBe(0); - expect(list.push(makeUint8Array("foo"))).toBeUndefined(); - expect(list.length).toBe(1); - expect(() => { - list.concat(2); - }).toThrow(RangeError); - expect(list.push(makeUint8Array("bar"))).toBeUndefined(); - expect(list.length).toBe(2); - expect(() => { - list.concat(5); - }).toThrow(RangeError); -}); - -it("should work with .consume() on strings", () => { - const list = new Readable().readableBuffer; - expect(list.length).toBe(0); - expect(list.consume(42, true)).toBe(""); - expect(list.push("foo")).toBeUndefined(); - expect(list.push("bar")).toBeUndefined(); - expect(list.push("baz")).toBeUndefined(); - expect(list.push("moo")).toBeUndefined(); - expect(list.push("moz")).toBeUndefined(); - expect(list.length).toBe(5); - expect(list.consume(3, true)).toBe("foo"); - expect(list.length).toBe(4); - expect(list.consume(4, true)).toBe("barb"); - expect(list.length).toBe(3); - expect(list.consume(256, true)).toBe("azmoomoz"); - expect(list.length).toBe(0); -}); - -it("should work with .consume() on buffers", () => { - const list = new Readable().readableBuffer; - expect(list.length).toBe(0); - expect(list.consume(42, false)).toEqual(new Uint8Array()); - expect(list.push(makeUint8Array("foo"))).toBeUndefined(); - expect(list.push(makeUint8Array("bar"))).toBeUndefined(); - expect(list.push(makeUint8Array("baz"))).toBeUndefined(); - expect(list.push(makeUint8Array("moo"))).toBeUndefined(); - expect(list.push(makeUint8Array("moz"))).toBeUndefined(); - expect(list.length).toBe(5); - expect(list.consume(3, false)).toEqual(makeUint8Array("foo")); - expect(list.length).toBe(4); - expect(list.consume(2, false)).toEqual(makeUint8Array("ba")); - expect(list.length).toBe(4); - expect(list.consume(4, false)).toEqual(makeUint8Array("rbaz")); - expect(list.length).toBe(2); - expect(list.consume(10, false)).toEqual(new Uint8Array([109, 111, 111, 109, 111, 122, 0, 0, 0, 0])); - expect(list.length).toBe(0); -}); - -it("should fail on .consume() with invalid items", () => { - const list = new Readable().readableBuffer; - expect(list.length).toBe(0); - expect(list.push("foo")).toBeUndefined(); - expect(list.length).toBe(1); - expect(list.consume(0, false)).toEqual(new Uint8Array([])); - expect(() => { - list.consume(1, false); - }).toThrow(TypeError); - expect(list.consume(3, true)).toBe("foo"); - expect(list.length).toBe(0); - expect(list.push(makeUint8Array("bar"))).toBeUndefined(); - expect(list.length).toBe(1); - expect(list.consume(0, true)).toEqual(""); - expect(() => { - list.consume(1, true); - }).toThrow(TypeError); - expect(list.consume(3, false)).toEqual(new Uint8Array([98, 97, 114])); -}); - -it("should work with .first()", () => { - const list = new Readable().readableBuffer; - expect(list.length).toBe(0); - expect(list.first()).toBeUndefined(); - const item = {}; - expect(list.push(item)).toBeUndefined(); - expect(list.length).toBe(1); - expect(list.first()).toBe(item); -}); - -it("should work with .join()", () => { - const list = new Readable().readableBuffer; - expect(list.length).toBe(0); - expect(list.push(42)).toBeUndefined(); - expect(list.push(null)).toBeUndefined(); - expect(list.push("foo")).toBeUndefined(); - expect(list.push(makeUint8Array("bar"))).toBeUndefined(); - expect(list.length).toBe(4); - expect(list.join("")).toBe("42nullfoo98,97,114"); - expect(list.join(",")).toBe("42,null,foo,98,97,114"); - expect(list.join(" baz ")).toBe("42 baz null baz foo baz 98,97,114"); -}); - -it("should work with .push()", () => { - const list = new Readable().readableBuffer; - expect(list.length).toBe(0); - const item1 = {}; - expect(list.push(item1)).toBeUndefined(); - expect(list.length).toBe(1); - expect(list.first()).toBe(item1); - const item2 = {}; - expect(list.push(item2)).toBeUndefined(); - expect(list.length).toBe(2); - expect(list.shift()).toBe(item1); - expect(list.shift()).toBe(item2); - expect(list.shift()).toBeUndefined(); -}); - -it("should work with .shift()", () => { - const list = new Readable().readableBuffer; - expect(list.length).toBe(0); - expect(list.shift()).toBeUndefined(); - const item = {}; - expect(list.push(item)).toBeUndefined(); - expect(list.length).toBe(1); - expect(list.shift()).toBe(item); - expect(list.shift()).toBeUndefined(); -}); - -it("should work with .unshift()", () => { - const list = new Readable().readableBuffer; - expect(list.length).toBe(0); - const item1 = {}; - expect(list.unshift(item1)).toBeUndefined(); - expect(list.length).toBe(1); - expect(list.first()).toBe(item1); - const item2 = {}; - expect(list.push(item2)).toBeUndefined(); - expect(list.length).toBe(2); - expect(list.first()).toBe(item1); - const item3 = {}; - expect(list.unshift(item3)).toBeUndefined(); - expect(list.length).toBe(3); - expect(list.shift()).toBe(item3); - expect(list.shift()).toBe(item1); - expect(list.shift()).toBe(item2); - expect(list.shift()).toBeUndefined(); -}); - -it("should work with partial .consume() followed by .first()", () => { - const list = new Readable().readableBuffer; - expect(list.length).toBe(0); - expect(list.push("foo")).toBeUndefined(); - expect(list.push("bar")).toBeUndefined(); - expect(list.length).toBe(2); - expect(list.consume(4, true)).toEqual("foob"); - expect(list.length).toBe(1); - expect(list.first()).toEqual("ar"); - expect(list.length).toBe(1); -}); - -it("should work with partial .consume() followed by .shift()", () => { - const list = new Readable().readableBuffer; - expect(list.length).toBe(0); - expect(list.push(makeUint8Array("foo"))).toBeUndefined(); - expect(list.push(makeUint8Array("bar"))).toBeUndefined(); - expect(list.length).toBe(2); - expect(list.consume(4, false)).toEqual(makeUint8Array("foob")); - expect(list.length).toBe(1); - expect(list.shift()).toEqual(makeUint8Array("ar")); - expect(list.length).toBe(0); -}); - -it("should work with partial .consume() followed by .unshift()", () => { - const list = new Readable().readableBuffer; - expect(list.length).toBe(0); - expect(list.push(makeUint8Array("😋😋😋"))).toBeUndefined(); - expect(list.push(makeUint8Array("📋📋📋"))).toBeUndefined(); - expect(list.length).toBe(2); - expect(list.consume(7, false)).toEqual(new Uint8Array([61, 11, 61, 11, 61, 11, 61])); - expect(list.length).toBe(1); - expect(list.unshift(makeUint8Array("👌👌👌"))).toBeUndefined(); - expect(list.length).toBe(2); - expect(list.consume(12, false)).toEqual(new Uint8Array([61, 76, 61, 76, 61, 76, 203, 61, 203, 61, 203, 0])); - expect(list.length).toBe(0); -}); diff --git a/test/bun.js/bun-jsc.test.js b/test/bun.js/bun-jsc.test.js deleted file mode 100644 index 6e6897eb3..000000000 --- a/test/bun.js/bun-jsc.test.js +++ /dev/null @@ -1,108 +0,0 @@ -import { describe, expect, it } from "bun:test"; -import { - describe as jscDescribe, - describeArray, - gcAndSweep, - fullGC, - edenGC, - heapSize, - heapStats, - memoryUsage, - getRandomSeed, - setRandomSeed, - isRope, - callerSourceOrigin, - noFTL, - noOSRExitFuzzing, - optimizeNextInvocation, - numberOfDFGCompiles, - releaseWeakRefs, - totalCompileTime, - getProtectedObjects, - reoptimizationRetryCount, - drainMicrotasks, - startRemoteDebugger, -} from "bun:jsc"; - -describe("bun:jsc", () => { - function count() { - var j = 0; - for (var i = 0; i < 999999; i++) { - j += i + 2; - } - - return j; - } - - it("describe", () => { - jscDescribe([]); - }); - it("describeArray", () => { - describeArray([1, 2, 3]); - }); - it("gcAndSweep", () => { - gcAndSweep(); - }); - it("fullGC", () => { - fullGC(); - }); - it("edenGC", () => { - edenGC(); - }); - it("heapSize", () => { - expect(heapSize() > 0).toBe(true); - }); - it("heapStats", () => { - heapStats(); - }); - it("memoryUsage", () => { - memoryUsage(); - }); - it("getRandomSeed", () => { - getRandomSeed(2); - }); - it("setRandomSeed", () => { - setRandomSeed(2); - }); - it("isRope", () => { - expect(isRope("a" + 123 + "b")).toBe(true); - expect(isRope("abcdefgh")).toBe(false); - }); - it("callerSourceOrigin", () => { - expect(callerSourceOrigin()).toBe(import.meta.url); - }); - it("noFTL", () => {}); - it("noOSRExitFuzzing", () => {}); - it("optimizeNextInvocation", () => { - count(); - optimizeNextInvocation(count); - count(); - }); - it("numberOfDFGCompiles", () => { - expect(numberOfDFGCompiles(count) > 0).toBe(true); - }); - it("releaseWeakRefs", () => { - releaseWeakRefs(); - }); - it("totalCompileTime", () => { - totalCompileTime(count); - }); - it("reoptimizationRetryCount", () => { - reoptimizationRetryCount(count); - }); - it("drainMicrotasks", () => { - drainMicrotasks(); - }); - it("startRemoteDebugger", () => { - // try { - // startRemoteDebugger(""); - // } catch (e) { - // if (process.platform !== "darwin") { - // throw e; - // } - // } - }); - it("getProtectedObjects", () => { - expect(getProtectedObjects().length > 0).toBe(true); - }); -}); diff --git a/test/bun.js/bun-loader-svelte/bun-loader-svelte.ts b/test/bun.js/bun-loader-svelte/bun-loader-svelte.ts deleted file mode 100644 index f0a6e3419..000000000 --- a/test/bun.js/bun-loader-svelte/bun-loader-svelte.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { plugin } from "bun"; - -await plugin({ - name: "svelte loader", - async setup(builder) { - var { compile } = await import("svelte/compiler"); - var { readFileSync } = await import("fs"); - await 2; - builder.onLoad({ filter: /\.svelte$/ }, ({ path }) => ({ - contents: compile(readFileSync(path, "utf8"), { - filename: path, - generate: "ssr", - }).js.code, - loader: "js", - })); - await 1; - }, -}); diff --git a/test/bun.js/bun-loader-svelte/package.json b/test/bun.js/bun-loader-svelte/package.json deleted file mode 100644 index ae4958ccc..000000000 --- a/test/bun.js/bun-loader-svelte/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "bun-loader-svelte", - "module": "./bun-loader-svelte.ts" -} diff --git a/test/bun.js/bun-server.test.ts b/test/bun.js/bun-server.test.ts deleted file mode 100644 index 52574d2a3..000000000 --- a/test/bun.js/bun-server.test.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { describe, expect, test } from "bun:test"; - -describe("Server", () => { - test("returns active port when initializing server with 0 port", () => { - const server = Bun.serve({ - fetch() { - return new Response("Hello"); - }, - port: 0, - }); - - expect(server.port).not.toBe(0); - expect(server.port).toBeDefined(); - server.stop(true); - }); - - test("allows connecting to server", async () => { - const server = Bun.serve({ - fetch() { - return new Response("Hello"); - }, - port: 0, - }); - - const response = await fetch(`http://${server.hostname}:${server.port}`); - expect(await response.text()).toBe("Hello"); - server.stop(true); - }); - - test("abort signal on server", async () => { - { - let signalOnServer = false; - const server = Bun.serve({ - async fetch(req) { - req.signal.addEventListener("abort", () => { - signalOnServer = true; - }); - await Bun.sleep(15); - return new Response("Hello"); - }, - port: 0, - }); - - try { - await fetch(`http://${server.hostname}:${server.port}`, { signal: AbortSignal.timeout(10) }); - } catch {} - expect(signalOnServer).toBe(true); - server.stop(true); - } - }); - - test("abort signal on server should only fire if aborted", async () => { - { - const abortController = new AbortController(); - - let signalOnServer = false; - const server = Bun.serve({ - async fetch(req) { - req.signal.addEventListener("abort", () => { - signalOnServer = true; - }); - return new Response("Hello"); - }, - port: 0, - }); - - try { - await fetch(`http://${server.hostname}:${server.port}`, { signal: abortController.signal }); - } catch {} - expect(signalOnServer).toBe(false); - server.stop(true); - } - }); - - test("abort signal on server with direct stream", async () => { - { - let signalOnServer = false; - const abortController = new AbortController(); - - const server = Bun.serve({ - async fetch(req) { - req.signal.addEventListener("abort", () => { - signalOnServer = true; - }); - return new Response( - new ReadableStream({ - type: "direct", - async pull(controller) { - abortController.abort(); - - const buffer = await Bun.file(import.meta.dir + "/fixture.html.gz").arrayBuffer(); - controller.write(buffer); - - //wait to detect the connection abortion - await Bun.sleep(15); - - controller.close(); - }, - }), - { - headers: { - "Content-Encoding": "gzip", - "Content-Type": "text/html; charset=utf-8", - "Content-Length": "1", - }, - }, - ); - }, - port: 0, - }); - - try { - await fetch(`http://${server.hostname}:${server.port}`, { signal: abortController.signal }); - } catch {} - await Bun.sleep(10); - expect(signalOnServer).toBe(true); - server.stop(true); - } - }); - - test("abort signal on server with stream", async () => { - { - let signalOnServer = false; - const abortController = new AbortController(); - - const server = Bun.serve({ - async fetch(req) { - req.signal.addEventListener("abort", () => { - signalOnServer = true; - }); - return new Response( - new ReadableStream({ - async pull(controller) { - console.trace("here"); - abortController.abort(); - - const buffer = await Bun.file(import.meta.dir + "/fixture.html.gz").arrayBuffer(); - console.trace("here"); - controller.enqueue(buffer); - console.trace("here"); - - //wait to detect the connection abortion - await Bun.sleep(15); - controller.close(); - }, - }), - { - headers: { - "Content-Encoding": "gzip", - "Content-Type": "text/html; charset=utf-8", - "Content-Length": "1", - }, - }, - ); - }, - port: 0, - }); - - try { - console.trace("here"); - await fetch(`http://${server.hostname}:${server.port}`, { signal: abortController.signal }); - } catch {} - await Bun.sleep(10); - console.trace("here"); - expect(signalOnServer).toBe(true); - console.trace("here"); - server.stop(true); - console.trace("here"); - } - }); -}); diff --git a/test/bun.js/bun-spawn-test.js b/test/bun.js/bun-spawn-test.js deleted file mode 100644 index 1617a8588..000000000 --- a/test/bun.js/bun-spawn-test.js +++ /dev/null @@ -1,21 +0,0 @@ -const EventEmitter = import.meta.require("events"); -class TestClass extends EventEmitter { - #handle = null; - spawn() { - this.#handle = Bun.spawn(["pwd"], { - cwd: "/tmp", - onExit: this.#handleOnExit.bind(this), - }); - } - #handleOnExit(code) { - console.log(code); - this.emit("exit"); - } -} - -const testClass = new TestClass(); -testClass.spawn(); -testClass.on("exit", () => { - console.log("exiting"); - process.exit(0); -}); diff --git a/test/bun.js/bun-streams-test-fifo.sh b/test/bun.js/bun-streams-test-fifo.sh deleted file mode 100644 index 57650ba1d..000000000 --- a/test/bun.js/bun-streams-test-fifo.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -echoerr() { echo "$@" 1>&2; } - -echoerr "bun-streams-test-fifo.sh: starting" -echo -e "$FIFO_TEST" >>${@: -1} -echoerr "bun-streams-test-fifo.sh: ending" -exit 0 diff --git a/test/bun.js/bun-test.test.ts b/test/bun.js/bun-test.test.ts deleted file mode 100644 index cc6bf644a..000000000 --- a/test/bun.js/bun-test.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { expect, test } from "bun:test"; - -test("Bun.version", () => { - expect(process.versions.bun).toBe(Bun.version); - expect(process.revision).toBe(Bun.revision); -}); diff --git a/test/bun.js/bun-test/jest-hooks.test.ts b/test/bun.js/bun-test/jest-hooks.test.ts deleted file mode 100644 index c99dc7759..000000000 --- a/test/bun.js/bun-test/jest-hooks.test.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "bun:test"; - -describe("test jest hooks in bun-test", () => { - describe("test beforeAll hook", () => { - let animal = "tiger"; - - beforeAll(() => { - animal = "lion"; - }); - - it("string should be set by hook", () => { - expect(animal).toEqual("lion"); - }); - }); - - describe("test beforeEach hook", () => { - let animal = "tiger"; - - beforeEach(() => { - animal = "lion"; - }); - - it("string should be set by hook", () => { - expect(animal).toEqual("lion"); - animal = "dog"; - }); - - it("string should be re-set by hook", () => { - expect(animal).toEqual("lion"); - }); - }); - - describe("test afterEach hook", () => { - let animal = "tiger"; - - afterEach(() => { - animal = "lion"; - }); - - it("string should not be set by hook", () => { - expect(animal).toEqual("tiger"); - animal = "dog"; - }); - - it("string should be set by hook", () => { - expect(animal).toEqual("lion"); - }); - }); - - describe("test afterAll hook", () => { - let animal = "tiger"; - - describe("test afterAll hook", () => { - afterAll(() => { - animal = "lion"; - }); - - it("string should not be set by hook", () => { - expect(animal).toEqual("tiger"); - animal = "dog"; - }); - }); - - it("string should be set by hook", () => { - expect(animal).toEqual("lion"); - }); - }); - - describe("test async hooks", async () => { - let beforeAllCalled = 0; - let beforeEachCalled = 0; - let afterAllCalled = 0; - let afterEachCalled = 0; - - beforeAll(async () => { - beforeAllCalled += await 1; - }); - - beforeEach(async () => { - beforeEachCalled += await 1; - }); - - afterAll(async () => { - afterAllCalled += await 1; - }); - - afterEach(async () => { - afterEachCalled += await 1; - }); - - it("should run after beforeAll()", () => { - expect(beforeAllCalled).toBe(1); - expect(beforeEachCalled).toBe(1); - expect(afterAllCalled).toBe(0); - expect(afterEachCalled).toBe(0); - }); - - it("should run after beforeEach()", () => { - expect(beforeAllCalled).toBe(1); - expect(beforeEachCalled).toBe(2); - expect(afterAllCalled).toBe(0); - expect(afterEachCalled).toBe(1); - }); - }); - - describe("test done callback in hooks", () => { - let beforeAllCalled = 0; - let beforeEachCalled = 0; - let afterAllCalled = 0; - let afterEachCalled = 0; - - beforeAll(done => { - setImmediate(() => { - beforeAllCalled++; - done(); - }); - }); - - beforeEach(done => { - setImmediate(() => { - beforeEachCalled++; - done(); - }); - }); - - afterAll(done => { - setImmediate(() => { - afterAllCalled++; - done(); - }); - }); - - afterEach(done => { - setImmediate(() => { - afterEachCalled++; - done(); - }); - }); - - it("should run after beforeAll()", () => { - expect(beforeAllCalled).toBe(1); - expect(beforeEachCalled).toBe(1); - expect(afterAllCalled).toBe(0); - expect(afterEachCalled).toBe(0); - }); - - it("should run after beforeEach()", () => { - expect(beforeAllCalled).toBe(1); - expect(beforeEachCalled).toBe(2); - expect(afterAllCalled).toBe(0); - expect(afterEachCalled).toBe(1); - }); - }); - - describe("test async hooks with done()", () => { - let beforeAllCalled = 0; - let beforeEachCalled = 0; - let afterAllCalled = 0; - let afterEachCalled = 0; - - beforeAll(async done => { - beforeAllCalled += await 1; - setTimeout(done, 1); - }); - - beforeEach(async done => { - beforeEachCalled += await 1; - setTimeout(done, 1); - }); - - afterAll(async done => { - afterAllCalled += await 1; - setTimeout(done, 1); - }); - - afterEach(async done => { - afterEachCalled += await 1; - setTimeout(done, 1); - }); - - it("should run after beforeAll()", () => { - expect(beforeAllCalled).toBe(1); - expect(beforeEachCalled).toBe(1); - expect(afterAllCalled).toBe(0); - expect(afterEachCalled).toBe(0); - }); - - it("should run after beforeEach()", () => { - expect(beforeAllCalled).toBe(1); - expect(beforeEachCalled).toBe(2); - expect(afterAllCalled).toBe(0); - expect(afterEachCalled).toBe(1); - }); - }); -}); diff --git a/test/bun.js/bun-test/nested-describes.test.ts b/test/bun.js/bun-test/nested-describes.test.ts deleted file mode 100644 index 636714fdc..000000000 --- a/test/bun.js/bun-test/nested-describes.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { describe, expect, test } from "bun:test"; - -/* -In this test we want the tests to print out the following on a success. -Each success / fail should show the path of describe and test scopes - -✓ outer most describe > mid describe 1 > inner most describe 1 > first -✓ outer most describe > mid describe 1 > inner most describe 2 > second -✓ outer most describe > mid describe 2 > inner most describe 3 > first - -@TODO add testing for this, would require to read the test console output -*/ - -describe("outer most describe", () => { - describe("mid describe 1", () => { - describe("inner most describe 1", () => { - test("first", () => { - expect(5).toEqual(5); - }); - }); - describe("inner most describe 2", () => { - test("second", () => { - expect(5).toEqual(5); - }); - }); - }); - describe("mid describe 2", () => { - describe("inner most describe 3", () => { - test("third", () => { - expect(5).toEqual(5); - }); - }); - }); -}); diff --git a/test/bun.js/bun-write.test.js b/test/bun.js/bun-write.test.js deleted file mode 100644 index c324d36a0..000000000 --- a/test/bun.js/bun-write.test.js +++ /dev/null @@ -1,274 +0,0 @@ -import fs from "fs"; -import { it, expect, describe } from "bun:test"; -import path from "path"; -import { gcTick, withoutAggressiveGC } from "./gc"; - -it("Bun.write blob", async () => { - await Bun.write(Bun.file("/tmp/response-file.test.txt"), Bun.file(path.join(import.meta.dir, "fetch.js.txt"))); - await gcTick(); - await Bun.write(Bun.file("/tmp/response-file.test.txt"), "blah blah blha"); - await gcTick(); - await Bun.write(Bun.file("/tmp/response-file.test.txt"), new Uint32Array(1024)); - await gcTick(); - await Bun.write("/tmp/response-file.test.txt", new Uint32Array(1024)); - await gcTick(); - expect(await Bun.write(new TextEncoder().encode("/tmp/response-file.test.txt"), new Uint32Array(1024))).toBe( - new Uint32Array(1024).byteLength, - ); - await gcTick(); -}); - -describe("large file", () => { - const fixtures = [ - [ - `/tmp/bun-test-large-file-${Date.now()}.txt`, - "https://www.iana.org/assignments/media-types/media-types.xhtml,".repeat(10000), - ], - ]; - - for (const [filename, content] of fixtures) { - it(`write ${filename} ${content.length} (text)`, async () => { - try { - unlinkSync(filename); - } catch (e) {} - await Bun.write(filename, content); - expect(await Bun.file(filename).text()).toBe(content); - - try { - unlinkSync(filename); - } catch (e) {} - }); - - it(`write ${filename}.bytes ${content.length} (bytes)`, async () => { - try { - unlinkSync(filename + ".bytes"); - } catch (e) {} - var bytes = new TextEncoder().encode(content); - const written = await Bun.write(filename + ".bytes", bytes); - expect(written).toBe(bytes.byteLength); - expect(new Buffer(await Bun.file(filename + ".bytes").arrayBuffer()).equals(bytes)).toBe(true); - - try { - unlinkSync(filename + ".bytes"); - } catch (e) {} - }); - - it(`write ${filename}.blob ${content.length} (Blob)`, async () => { - try { - unlinkSync(filename + ".blob"); - } catch (e) {} - var bytes = new Blob([content]); - await Bun.write(filename + ".blob", bytes); - expect(await Bun.file(filename + ".blob").text()).toBe(content); - - try { - unlinkSync(filename + ".blob"); - } catch (e) {} - }); - } -}); - -it("Bun.file not found returns ENOENT", async () => { - try { - await gcTick(); - await Bun.file("/does/not/exist.txt").text(); - await gcTick(); - } catch (exception) { - expect(exception.code).toBe("ENOENT"); - } - await gcTick(); -}); - -it("Bun.write('out.txt', 'string')", async () => { - for (let erase of [true, false]) { - if (erase) { - try { - fs.unlinkSync(path.join("/tmp", "out.txt")); - } catch (e) {} - } - await gcTick(); - expect(await Bun.write("/tmp/out.txt", "string")).toBe("string".length); - await gcTick(); - const out = Bun.file("/tmp/out.txt"); - await gcTick(); - expect(await out.text()).toBe("string"); - await gcTick(); - expect(await out.text()).toBe(fs.readFileSync("/tmp/out.txt", "utf8")); - await gcTick(); - } -}); - -it("Bun.file -> Bun.file", async () => { - try { - fs.unlinkSync(path.join("/tmp", "fetch.js.in")); - } catch (e) {} - await gcTick(); - try { - fs.unlinkSync(path.join("/tmp", "fetch.js.out")); - } catch (e) {} - await gcTick(); - const file = path.join(import.meta.dir, "fetch.js.txt"); - await gcTick(); - const text = fs.readFileSync(file, "utf8"); - fs.writeFileSync("/tmp/fetch.js.in", text); - await gcTick(); - { - const result = await Bun.write(Bun.file("/tmp/fetch.js.out"), Bun.file("/tmp/fetch.js.in")); - await gcTick(); - expect(await Bun.file("/tmp/fetch.js.out").text()).toBe(text); - await gcTick(); - } - - { - await Bun.write(Bun.file("/tmp/fetch.js.in").slice(0, (text.length / 2) | 0), Bun.file("/tmp/fetch.js.out")); - expect(await Bun.file("/tmp/fetch.js.in").text()).toBe(text.substring(0, (text.length / 2) | 0)); - } - - { - await gcTick(); - await Bun.write("/tmp/fetch.js.in", Bun.file("/tmp/fetch.js.out")); - await gcTick(); - expect(await Bun.file("/tmp/fetch.js.in").text()).toBe(text); - } -}); - -it("Bun.file", async () => { - const file = path.join(import.meta.dir, "fetch.js.txt"); - await gcTick(); - expect(await Bun.file(file).text()).toBe(fs.readFileSync(file, "utf8")); - await gcTick(); -}); - -it("Bun.file empty file", async () => { - const file = path.join(import.meta.dir, "emptyFile"); - await gcTick(); - const buffer = await Bun.file(file).arrayBuffer(); - expect(buffer.byteLength).toBe(0); - await gcTick(); -}); - -it("Bun.file as a Blob", async () => { - const filePath = path.join(import.meta.path, "../fetch.js.txt"); - const fixture = fs.readFileSync(filePath, "utf8"); - // this is a Blob object with the same interface as the one returned by fetch - // internally, instead of a byte array, it stores the file path! - // this enables several performance optimizations - var blob = Bun.file(filePath); - await gcTick(); - - // now it reads "./fetch.js.txt" from the filesystem - // it's lazy, only loads once we ask for it - // if it fails, the promise will reject at this point - expect(await blob.text()).toBe(fixture); - await gcTick(); - // BEHAVIOR CHANGE IN BUN V0.3.0 - size is never set - // now that it's loaded, the size updates - // expect(blob.size).toBe(fixture.length); - // await gcTick(); - // and it only loads once for _all_ blobs pointing to that file path - // until all references are released - expect((await blob.arrayBuffer()).byteLength).toBe(fixture.length); - await gcTick(); - - const array = new Uint8Array(await blob.arrayBuffer()); - await gcTick(); - const text = fixture; - withoutAggressiveGC(() => { - for (let i = 0; i < text.length; i++) { - expect(array[i]).toBe(text.charCodeAt(i)); - } - }); - await gcTick(); - expect(blob.size).toBe(fixture.length); - blob = null; - await gcTick(); - await new Promise(resolve => setTimeout(resolve, 1)); - var blob = Bun.file(filePath); - expect(blob.size).toBe(fixture.length); -}); - -it("Response -> Bun.file", async () => { - const file = path.join(import.meta.dir, "fetch.js.txt"); - await gcTick(); - const text = fs.readFileSync(file, "utf8"); - await gcTick(); - const response = new Response(Bun.file(file)); - await gcTick(); - expect(await response.text()).toBe(text); - await gcTick(); -}); - -it("Bun.file -> Response", async () => { - // ensure the file doesn't already exist - try { - fs.unlinkSync("/tmp/fetch.js.out"); - } catch {} - await gcTick(); - const file = path.join(import.meta.dir, "fetch.js.txt"); - await gcTick(); - const text = fs.readFileSync(file, "utf8"); - await gcTick(); - const resp = await fetch("https://example.com"); - await gcTick(); - - expect(await Bun.write("/tmp/fetch.js.out", resp)).toBe(text.length); - await gcTick(); - expect(await Bun.file("/tmp/fetch.js.out").text()).toBe(text); - await gcTick(); -}); - -it("Response -> Bun.file -> Response -> text", async () => { - await gcTick(); - const file = path.join(import.meta.dir, "fetch.js.txt"); - await gcTick(); - const text = fs.readFileSync(file, "utf8"); - await gcTick(); - const response = new Response(Bun.file(file)); - await gcTick(); - const response2 = response.clone(); - await gcTick(); - expect(await response2.text()).toBe(text); - await gcTick(); -}); - -it("Bun.write('output.html', '')", async () => { - await Bun.write("/tmp/output.html", "lalalala"); - expect(await Bun.write("/tmp/output.html", "")).toBe(0); - await Bun.write("/tmp/output.html", "lalalala"); - expect(await Bun.file("/tmp/output.html").text()).toBe("lalalala"); -}); - -it("Bun.write(Bun.stdout, 'Bun.write STDOUT TEST')", async () => { - expect(await Bun.write(Bun.stdout, "\nBun.write STDOUT TEST\n\n")).toBe(24); -}); - -it("Bun.write(Bun.stderr, 'Bun.write STDERR TEST')", async () => { - expect(await Bun.write(Bun.stderr, "\nBun.write STDERR TEST\n\n")).toBe(24); -}); - -it("Bun.write(Bun.stdout, new TextEncoder().encode('Bun.write STDOUT TEST'))", async () => { - expect(await Bun.write(Bun.stdout, new TextEncoder().encode("\nBun.write STDOUT TEST\n\n"))).toBe(24); -}); - -it("Bun.write(Bun.stderr, 'new TextEncoder().encode(Bun.write STDERR TEST'))", async () => { - expect(await Bun.write(Bun.stderr, new TextEncoder().encode("\nBun.write STDERR TEST\n\n"))).toBe(24); -}); - -// FLAKY TEST -// Since Bun.file is resolved lazily, this needs to specifically be checked -it.skip("Bun.write('output.html', HTMLRewriter.transform(Bun.file)))", async done => { - var rewriter = new HTMLRewriter(); - - rewriter.on("div", { - element(element) { - element.setInnerContent("it worked!", { html: true }); - }, - }); - await Bun.write("/tmp/html-rewriter.txt.js", "
hello
"); - var input = new Response(Bun.file("/tmp/html-rewriter.txt.js")); - var output = rewriter.transform(input); - const outpath = `/tmp/html-rewriter.${Date.now()}.html`; - await Bun.write(outpath, output); - expect(await Bun.file(outpath).text()).toBe("
it worked!
"); - done(); -}); diff --git a/test/bun.js/bun.lockb b/test/bun.js/bun.lockb deleted file mode 100755 index 0c6a33d3d..000000000 Binary files a/test/bun.js/bun.lockb and /dev/null differ diff --git a/test/bun.js/bunEnv.ts b/test/bun.js/bunEnv.ts deleted file mode 100644 index 2fe33a4ea..000000000 --- a/test/bun.js/bunEnv.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const bunEnv: any = { - ...process.env, - BUN_DEBUG_QUIET_LOGS: "1", - NO_COLOR: "1", - FORCE_COLOR: undefined, -}; diff --git a/test/bun.js/bunExe.ts b/test/bun.js/bunExe.ts deleted file mode 100644 index a66f702de..000000000 --- a/test/bun.js/bunExe.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function bunExe() { - return process.execPath; -} diff --git a/test/bun.js/bundled/always-bundled-module/always-bundled-module b/test/bun.js/bundled/always-bundled-module/always-bundled-module deleted file mode 120000 index f9a91ac4d..000000000 --- a/test/bun.js/bundled/always-bundled-module/always-bundled-module +++ /dev/null @@ -1 +0,0 @@ -node_modules/always-bundled-module \ No newline at end of file diff --git a/test/bun.js/bundled/always-bundled-module/cjs.js b/test/bun.js/bundled/always-bundled-module/cjs.js deleted file mode 100644 index 087697589..000000000 --- a/test/bun.js/bundled/always-bundled-module/cjs.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - default: 0xdeadbeef, - default() { - return "ok"; - }, - default: true, - ok() { - return true; - }, -}; diff --git a/test/bun.js/bundled/always-bundled-module/esm.js b/test/bun.js/bundled/always-bundled-module/esm.js deleted file mode 100644 index 28e702881..000000000 --- a/test/bun.js/bundled/always-bundled-module/esm.js +++ /dev/null @@ -1,5 +0,0 @@ -const __esModule = true; - -export const foo = () => __esModule; - -export { __esModule, foo as default }; diff --git a/test/bun.js/bundled/always-bundled-module/package.json b/test/bun.js/bundled/always-bundled-module/package.json deleted file mode 100644 index 5029c1695..000000000 --- a/test/bun.js/bundled/always-bundled-module/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "always-bundled-module", - "version": "1.0.0" -} diff --git a/test/bun.js/bundled/entrypoint.ts b/test/bun.js/bundled/entrypoint.ts deleted file mode 100644 index b9a17b538..000000000 --- a/test/bun.js/bundled/entrypoint.ts +++ /dev/null @@ -1,13 +0,0 @@ -import "i-am-bundled/cjs"; -import "i-am-bundled/esm"; -import "always-bundled-module/esm"; -import "always-bundled-module/cjs"; -import { foo } from "i-am-bundled/esm"; -import { foo as foo2 } from "always-bundled-module/esm"; -import cJS from "always-bundled-module/cjs"; - -foo(); -foo2(); -cJS(); - -export default cJS(); diff --git a/test/bun.js/bundled/package.json b/test/bun.js/bundled/package.json deleted file mode 100644 index cce72af9c..000000000 --- a/test/bun.js/bundled/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "to-bundle", - "scripts": { - "prebundle": "rm -rf node_modules; cp -r to_bundle_node_modules node_modules; ln -s always-bundled-module node_modules/always-bundled-module", - "bundle": "${BUN_BIN:-$(which bun)} bun ./entrypoint.ts" - }, - "bun": { - "alwaysBundle": [ - "always-bundled-module" - ] - } -} diff --git a/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/cjs.js b/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/cjs.js deleted file mode 100644 index 087697589..000000000 --- a/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/cjs.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - default: 0xdeadbeef, - default() { - return "ok"; - }, - default: true, - ok() { - return true; - }, -}; diff --git a/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/esm.js b/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/esm.js deleted file mode 100644 index 28e702881..000000000 --- a/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/esm.js +++ /dev/null @@ -1,5 +0,0 @@ -const __esModule = true; - -export const foo = () => __esModule; - -export { __esModule, foo as default }; diff --git a/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/package.json b/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/package.json deleted file mode 100644 index 661a80b2d..000000000 --- a/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "i-am-bundled", - "version": "1.0.0" -} diff --git a/test/bun.js/bundled/tsconfig.json b/test/bun.js/bundled/tsconfig.json deleted file mode 100644 index 3ccb11ae4..000000000 --- a/test/bun.js/bundled/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "compilerOptions": { - "paths": {}, - "baseUrl": ".", - } -} \ No newline at end of file diff --git a/test/bun.js/capture-stack-trace.test.js b/test/bun.js/capture-stack-trace.test.js deleted file mode 100644 index 789503960..000000000 --- a/test/bun.js/capture-stack-trace.test.js +++ /dev/null @@ -1,303 +0,0 @@ -import { test, expect } from "bun:test"; - -test("capture stack trace", () => { - function f1() { - f2(); - } - - function f2() { - f3(); - } - - function f3() { - logErrorStackTrace(); - } - - function logErrorStackTrace() { - let error = {}; - Error.captureStackTrace(error); - expect(error.stack !== undefined).toBe(true); - } - - f1(); -}); - -test("capture stack trace with message", () => { - function f1() { - f2(); - } - - function f2() { - f3(); - } - - function f3() { - logErrorStackTrace(); - } - - function logErrorStackTrace() { - let e1 = { message: "bad error!" }; - Error.captureStackTrace(e1); - expect(e1.message === "bad error!").toBe(true); - - let e2 = new Error("bad error!"); - Error.captureStackTrace(e2); - expect(e2.message === "bad error!").toBe(true); - } - - f1(); -}); - -test("capture stack trace with constructor", () => { - class S { - constructor() { - captureStackTrace(); - } - } - - function captureStackTrace() { - let e1 = {}; - Error.captureStackTrace(e1); - expect(e1.stack.split("\n")[2].includes("new S")).toBe(true); - } - - let s = new S(); -}); - -test("capture stack trace limit", () => { - function f1() { - f2(); - } - - function f2() { - f3(); - } - - function f3() { - f4(); - } - - function f4() { - f5(); - } - - function f5() { - f6(); - } - - function f6() { - f7(); - } - - function f7() { - f8(); - } - - function f8() { - f9(); - } - - function f9() { - f10(); - } - - function f10() { - captureStackTrace(); - } - - function captureStackTrace() { - let e1 = {}; - Error.captureStackTrace(e1); - expect(e1.stack.split("\n").length).toBe(11); - - let e2 = new Error(); - Error.captureStackTrace(e2); - expect(e2.stack.split("\n").length).toBe(11); - - let e3 = {}; - Error.stackTraceLimit = 4; - Error.captureStackTrace(e3); - expect(e3.stack.split("\n").length).toBe(5); - - let e4 = new Error(); - Error.captureStackTrace(e4); - expect(e4.stack.split("\n").length).toBe(5); - - let e5 = { stackTraceLimit: 2 }; - Error.captureStackTrace(e5); - expect(e5.stack.split("\n").length).toBe(5); - - let e6 = {}; - Error.stackTraceLimit = Infinity; - Error.captureStackTrace(e6); - expect(e6.stack.split("\n").length).toBe(13); - } - - f1(); -}); - -test("prepare stack trace", () => { - function f1() { - f2(); - } - - function f2() { - let e = {}; - let prevPrepareStackTrace = Error.prepareStackTrace; - Error.prepareStackTrace = (e, stack) => { - return "custom stack trace"; - }; - Error.captureStackTrace(e); - expect(e.stack).toBe("custom stack trace"); - Error.prepareStackTrace = prevPrepareStackTrace; - f3(); - } - - function f3() { - let e = { message: "bad error!" }; - let prevPrepareStackTrace = Error.prepareStackTrace; - Error.prepareStackTrace = (e, s) => { - expect(e.message === "bad error!").toBe(true); - expect(s.length).toBe(4); - }; - Error.stackTraceLimit = 10; - Error.captureStackTrace(e); - expect(e.stack === undefined).toBe(true); - Error.prepareStackTrace = prevPrepareStackTrace; - } - - f1(); -}); - -test("capture stack trace second argument", () => { - function f0() { - let s = new S(); - } - - class S { - constructor() { - f1(); - } - } - - function f1() { - f2(); - } - - function f2() { - f3(); - } - - function f3() { - f4(); - } - - function f4() { - f5(); - } - - function f5() { - f6(); - } - - function f6() { - let e = { message: "bad error!" }; - Error.captureStackTrace(e); - expect(e.stack.split("\n")[1].includes("at f6")).toBe(true); - expect(e.stack.split("\n")[2].includes("at f5")).toBe(true); - - let e2 = {}; - Error.captureStackTrace(e2, f3); - expect(e2.stack.split("\n")[1].includes("at f2")).toBe(true); - expect(e2.stack.split("\n")[2].includes("at f1")).toBe(true); - - let e3 = {}; - Error.captureStackTrace(e3, f9); - expect(e3.stack.split("\n").length).toBe(1); - - let e4 = { message: "exclude constructor!" }; - Error.captureStackTrace(e4, S.constructor); - expect(e4.stack.split("\n").length).toBe(1); - - let e5 = { message: "actually exclude constructor!" }; - Error.captureStackTrace(e5, S); - expect(e5.stack.split("\n")[1].includes("at f0")).toBe(true); - } - - function f9() { - // nothing - } - - f0(); -}); - -test("capture stack trace edge cases", () => { - let e1 = {}; - Error.captureStackTrace(e1, null); - expect(e1.stack !== undefined).toBe(true); - - let e2 = {}; - Error.captureStackTrace(e2, undefined); - expect(e2.stack !== undefined).toBe(true); - - let e3 = {}; - Error.captureStackTrace(e3, 1); - expect(e3.stack !== undefined).toBe(true); - - let e4 = {}; - Error.captureStackTrace(e4, "foo"); - expect(e4.stack !== undefined).toBe(true); - - let e5 = {}; - Error.captureStackTrace(e5, {}); - expect(e5.stack !== undefined).toBe(true); - - expect(Error.captureStackTrace({})).toBe(undefined); - expect(Error.captureStackTrace({}, () => {})).toBe(undefined); - expect(Error.captureStackTrace({}, undefined)).toBe(undefined); - expect(Error.captureStackTrace({}, null)).toBe(undefined); - expect(Error.captureStackTrace({}, 1)).toBe(undefined); - expect(Error.captureStackTrace({}, "foo")).toBe(undefined); - expect(Error.captureStackTrace({}, {})).toBe(undefined); - expect(Error.captureStackTrace({}, [])).toBe(undefined); - expect(Error.captureStackTrace({}, true)).toBe(undefined); -}); - -test("prepare stack trace call sites", () => { - function f1() { - f2(); - } - - function f2() { - f3(); - } - - function f3() { - let e = { message: "bad error!" }; - // let e = new Error("bad error!"); - let prevPrepareStackTrace = Error.prepareStackTrace; - Error.prepareStackTrace = (e, s) => { - expect(s[0].getThis !== undefined).toBe(true); - expect(s[0].getTypeName !== undefined).toBe(true); - expect(s[0].getFunction !== undefined).toBe(true); - expect(s[0].getFunctionName !== undefined).toBe(true); - expect(s[0].getMethodName !== undefined).toBe(true); - expect(s[0].getFileName !== undefined).toBe(true); - expect(s[0].getLineNumber !== undefined).toBe(true); - expect(s[0].getColumnNumber !== undefined).toBe(true); - expect(s[0].getEvalOrigin !== undefined).toBe(true); - expect(s[0].isToplevel !== undefined).toBe(true); - expect(s[0].isEval !== undefined).toBe(true); - expect(s[0].isNative !== undefined).toBe(true); - expect(s[0].isConstructor !== undefined).toBe(true); - expect(s[0].isAsync !== undefined).toBe(true); - expect(s[0].isPromiseAll !== undefined).toBe(true); - expect(s[0].getPromiseIndex !== undefined).toBe(true); - }; - Error.captureStackTrace(e); - expect(e.stack === undefined).toBe(true); - Error.prepareStackTrace = prevPrepareStackTrace; - } - - f1(); -}); diff --git a/test/bun.js/child-process-stdio.test.js b/test/bun.js/child-process-stdio.test.js deleted file mode 100644 index 43bcc771a..000000000 --- a/test/bun.js/child-process-stdio.test.js +++ /dev/null @@ -1,113 +0,0 @@ -import { describe, it, expect, beforeAll } from "bun:test"; -import { spawn, execSync } from "node:child_process"; -import { bunExe } from "bunExe"; -import { bunEnv } from "bunEnv"; - -const CHILD_PROCESS_FILE = import.meta.dir + "/spawned-child.js"; -const OUT_FILE = import.meta.dir + "/stdio-test-out.txt"; - -describe("process.stdout", () => { - it("should allow us to write to it", done => { - const child = spawn(bunExe(), [CHILD_PROCESS_FILE, "STDOUT"], { - env: bunEnv, - }); - child.stdout.setEncoding("utf8"); - child.stdout.on("data", data => { - try { - expect(data).toBe("stdout_test"); - done(); - } catch (err) { - done(err); - } - }); - }); -}); - -describe("process.stdin", () => { - it("should allow us to read from stdin in readable mode", done => { - const input = "hello\n"; - // Child should read from stdin and write it back - const child = spawn(bunExe(), [CHILD_PROCESS_FILE, "STDIN", "READABLE"], { - env: bunEnv, - }); - let data = ""; - child.stdout.setEncoding("utf8"); - child.stdout - .on("data", chunk => { - data += chunk; - }) - .on("end", function () { - try { - expect(data).toBe(`data: ${input}`); - done(); - } catch (err) { - done(err); - } - }); - child.stdin.write(input); - child.stdin.end(); - }); - - it("should allow us to read from stdin via flowing mode", done => { - const input = "hello\n"; - // Child should read from stdin and write it back - const child = spawn(bunExe(), [CHILD_PROCESS_FILE, "STDIN", "FLOWING"], { - env: bunEnv, - }); - let data = ""; - child.stdout.setEncoding("utf8"); - child.stdout - .on("readable", () => { - let chunk; - while ((chunk = child.stdout.read()) !== null) { - data += chunk; - } - }) - .on("end", function () { - try { - expect(data).toBe(`data: ${input}`); - done(); - } catch (err) { - done(err); - } - }); - child.stdin.write(input); - child.stdin.end(); - }); - - it("should allow us to read > 65kb from stdin", done => { - const numReps = Math.ceil((66 * 1024) / 5); - const input = "hello".repeat(numReps); - // Child should read from stdin and write it back - const child = spawn(bunExe(), [CHILD_PROCESS_FILE, "STDIN", "FLOWING"], { - env: bunEnv, - }); - let data = ""; - child.stdout.setEncoding("utf8"); - child.stdout - .on("readable", () => { - let chunk; - while ((chunk = child.stdout.read()) !== null) { - data += chunk; - } - }) - .on("end", function () { - try { - expect(data).toBe(`data: ${input}`); - done(); - } catch (err) { - done(err); - } - }); - child.stdin.write(input); - child.stdin.end(); - }); - - it("should allow us to read from a file", () => { - const result = execSync(`${bunExe()} ${CHILD_PROCESS_FILE} STDIN FLOWING < ${import.meta.dir}/readFileSync.txt`, { - encoding: "utf8", - env: bunEnv, - }); - expect(result).toEqual("data: File read successfully"); - }); -}); diff --git a/test/bun.js/child_process-node.test.js b/test/bun.js/child_process-node.test.js deleted file mode 100644 index f2c18c67c..000000000 --- a/test/bun.js/child_process-node.test.js +++ /dev/null @@ -1,482 +0,0 @@ -import { beforeAll, describe, expect, it } from "bun:test"; -import { ChildProcess, spawn, exec } from "node:child_process"; -import { throws, assert, createCallCheckCtx, createDoneDotAll } from "node-test-helpers"; -import { tmpdir } from "node:os"; -import { gcTick } from "gc"; -const strictEqual = (a, b) => expect(a).toStrictEqual(b); -const debug = process.env.DEBUG ? console.log : () => {}; - -const platformTmpDir = require("fs").realpathSync(tmpdir()); - -const TYPE_ERR_NAME = "TypeError"; - -console.log(process.cwd()); - -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -const common = { - pwdCommand: ["pwd", []], -}; - -describe("ChildProcess.constructor", () => { - it("should be a function", () => { - strictEqual(typeof ChildProcess, "function"); - }); -}); - -describe("ChildProcess.spawn()", () => { - it("should throw on invalid options", () => { - // Verify that invalid options to spawn() throw. - const child = new ChildProcess(); - - [undefined, null, "foo", 0, 1, NaN, true, false].forEach(options => { - throws( - () => { - child.spawn(options); - }, - { - code: "ERR_INVALID_ARG_TYPE", - name: TYPE_ERR_NAME, - // message: - // 'The "options" argument must be of type object.' + - // `${common.invalidArgTypeHelper(options)}`, - }, - ); - }); - }); - - it("should throw if file is not a string", () => { - // Verify that spawn throws if file is not a string. - const child = new ChildProcess(); - [undefined, null, 0, 1, NaN, true, false, {}].forEach(file => { - throws( - () => { - child.spawn({ file }); - }, - { - code: "ERR_INVALID_ARG_TYPE", - name: TYPE_ERR_NAME, - // message: - // 'The "options.file" property must be of type string.' + - // `${common.invalidArgTypeHelper(file)}`, - }, - ); - }); - }); - - it("should throw if envPairs is not an array or undefined", () => { - // Verify that spawn throws if envPairs is not an array or undefined. - const child = new ChildProcess(); - - [null, 0, 1, NaN, true, false, {}, "foo"].forEach(envPairs => { - throws( - () => { - child.spawn({ - envPairs, - stdio: ["ignore", "ignore", "ignore", "ipc"], - }); - }, - { - code: "ERR_INVALID_ARG_TYPE", - name: TYPE_ERR_NAME, - // message: - // 'The "options.envPairs" property must be an instance of Array.' + - // common.invalidArgTypeHelper(envPairs), - }, - ); - }); - }); - - it("should throw if stdio is not an array or undefined", () => { - // Verify that spawn throws if args is not an array or undefined. - const child = new ChildProcess(); - - [null, 0, 1, NaN, true, false, {}, "foo"].forEach(args => { - throws( - () => { - child.spawn({ file: "foo", args }); - }, - { - code: "ERR_INVALID_ARG_TYPE", - name: TYPE_ERR_NAME, - // message: - // 'The "options.args" property must be an instance of Array.' + - // common.invalidArgTypeHelper(args), - }, - ); - }); - }); -}); - -describe("ChildProcess.spawn", () => { - function getChild() { - const child = new ChildProcess(); - child.spawn({ - file: "node", - // file: process.execPath, - args: ["node", "--interactive"], - cwd: process.cwd(), - stdio: ["ignore", "ignore", "ignore"], - }); - return child; - } - - it("should spawn a process", () => { - const child = getChild(); - // Test that we can call spawn - - strictEqual(Object.hasOwn(child, "pid"), true); - assert(Number.isInteger(child.pid)); - child.kill(); - }); - - it("should throw error on invalid signal", () => { - const child = getChild(); - // Try killing with invalid signal - throws( - () => { - child.kill("foo"); - }, - { code: "ERR_UNKNOWN_SIGNAL", name: TYPE_ERR_NAME }, - ); - }); -}); - -describe("ChildProcess spawn bad stdio", () => { - // Monkey patch spawn() to create a child process normally, but destroy the - // stdout and stderr streams. This replicates the conditions where the streams - // cannot be properly created. - function createChild(options, callback, done, target) { - var __originalSpawn = ChildProcess.prototype.spawn; - ChildProcess.prototype.spawn = function () { - const err = __originalSpawn.apply(this, arguments); - - this.stdout.destroy(); - this.stderr.destroy(); - - return err; - }; - - const { mustCall } = createCallCheckCtx(done); - let cmd = `bun ${import.meta.dir}/spawned-child.js`; - if (target) cmd += " " + target; - const child = exec(cmd, options, mustCall(callback)); - ChildProcess.prototype.spawn = __originalSpawn; - return child; - } - - it("should handle normal execution of child process", done => { - createChild( - {}, - (err, stdout, stderr) => { - strictEqual(err, null); - strictEqual(stdout, ""); - strictEqual(stderr, ""); - }, - done, - ); - }); - - it("should handle error event of child process", done => { - const error = new Error(`Command failed: bun ${import.meta.dir}/spawned-child.js ERROR`); - createChild( - {}, - (err, stdout, stderr) => { - strictEqual(err.message, error.message); - strictEqual(stdout, ""); - strictEqual(stderr, ""); - }, - done, - "ERROR", - ); - }); - - it("should handle killed process", done => { - createChild( - { timeout: 1 }, - (err, stdout, stderr) => { - strictEqual(err.killed, true); - strictEqual(stdout, ""); - strictEqual(stderr, ""); - }, - done, - ); - }); -}); - -describe("child_process cwd", () => { - // Spawns 'pwd' with given options, then test - // - whether the child pid is undefined or number, - // - whether the exit code equals expectCode, - // - optionally whether the trimmed stdout result matches expectData - function testCwd(options, { expectPidType, expectCode = 0, expectData }, done = () => {}) { - const createDone = createDoneDotAll(done); - const { mustCall } = createCallCheckCtx(createDone(1500)); - const exitDone = createDone(5000); - - const child = spawn(...common.pwdCommand, options); - - strictEqual(typeof child.pid, expectPidType); - - child.stdout.setEncoding("utf8"); - - // No need to assert callback since `data` is asserted. - let data = ""; - child.stdout.on("data", chunk => { - data += chunk; - }); - - // TODO: Test exit events - // // Can't assert callback, as stayed in to API: - // // _The 'exit' event may or may not fire after an error has occurred._ - child.on("exit", (code, signal) => { - try { - strictEqual(code, expectCode); - exitDone(); - } catch (err) { - exitDone(err); - } - }); - - child.on( - "close", - mustCall(() => { - expectData && strictEqual(data.trim(), expectData); - }), - ); - - return child; - } - - // TODO: Make sure this isn't important - // Currently Bun.spawn will still spawn even though cwd doesn't exist - // // Assume does-not-exist doesn't exist, expect exitCode=-1 and errno=ENOENT - // it("should throw an error when given cwd doesn't exist", () => { - // testCwd({ cwd: "does-not-exist" }, "undefined", -1).on( - // "error", - // mustCall(function (e) { - // console.log(e); - // strictEqual(e.code, "ENOENT"); - // }) - // ); - // }); - - // TODO: Make sure this isn't an important test - // it("should throw when cwd is a non-file url", () => { - // throws(() => { - // testCwd( - // { - // cwd: new URL("http://example.com/"), - // }, - // "number", - // 0, - // tmpdir.path - // ); - // }, /The URL must be of scheme file/); - - // // if (process.platform !== "win32") { - // // throws(() => { - // // testCwd( - // // { - // // cwd: new URL("file://host/dev/null"), - // // }, - // // "number", - // // 0, - // // tmpdir.path - // // ); - // // }, /File URL host must be "localhost" or empty on/); - // // } - // }); - - it("should work for valid given cwd", done => { - const tmpdir = { path: platformTmpDir }; - const createDone = createDoneDotAll(done); - - // Assume these exist, and 'pwd' gives us the right directory back - testCwd( - { cwd: tmpdir.path }, - { - expectPidType: "number", - expectCode: 0, - expectData: platformTmpDir, - }, - createDone(1500), - ); - const shouldExistDir = "/dev"; - testCwd( - { cwd: shouldExistDir }, - { - expectPidType: "number", - expectCode: 0, - expectData: shouldExistDir, - }, - createDone(1500), - ); - testCwd( - { cwd: Bun.pathToFileURL(tmpdir.path) }, - { - expectPidType: "number", - expectCode: 0, - expectData: platformTmpDir, - }, - createDone(1500), - ); - }); - - it.skip("shouldn't try to chdir to an invalid cwd", done => { - const createDone = createDoneDotAll(done); - // Spawn() shouldn't try to chdir() to invalid arg, so this should just work - testCwd({ cwd: "" }, { expectPidType: "number" }, createDone(1500)); - testCwd({ cwd: undefined }, { expectPidType: "number" }, createDone(1500)); - testCwd({ cwd: null }, { expectPidType: "number" }, createDone(1500)); - }); -}); - -describe("child_process default options", () => { - it("should use process.env as default env", done => { - globalThis.process.env.TMPDIR = platformTmpDir; - - let child = spawn("printenv", [], {}); - let response = ""; - - child.stdout.setEncoding("utf8"); - child.stdout.on("data", chunk => { - debug(`stdout: ${chunk}`); - response += chunk; - }); - - // NOTE: Original test used child.on("exit"), but this is unreliable - // because the process can exit before the stream is closed and the data is read - child.stdout.on("close", () => { - expect(response.includes(`TMPDIR=${platformTmpDir}`)).toBe(true); - done(); - }); - }); -}); - -describe("child_process double pipe", () => { - it("should allow two pipes to be used at once", done => { - // const { mustCallAtLeast, mustCall } = createCallCheckCtx(done); - const mustCallAtLeast = fn => fn; - const mustCall = fn => fn; - let grep, sed, echo; - grep = spawn("grep", ["o"], { stdio: ["pipe", "pipe", "pipe"] }); - sed = spawn("sed", ["s/o/O/"]); - echo = spawn("echo", ["hello\nnode\nand\nworld\n"]); - - // pipe grep | sed - grep.stdout.on( - "data", - mustCallAtLeast(data => { - debug(`grep stdout ${data.length}`); - if (!sed.stdin.write(data)) { - grep.stdout.pause(); - } - }), - ); - - // print sed's output - sed.stdout.on( - "data", - mustCallAtLeast(data => { - result += data.toString("utf8"); - debug(data); - }), - ); - - echo.stdout.on( - "data", - mustCallAtLeast(data => { - debug(`grep stdin write ${data.length}`); - if (!grep.stdin.write(data)) { - debug("echo stdout pause"); - echo.stdout.pause(); - } - }), - ); - - // TODO(Derrick): We don't implement the full API for this yet, - // So stdin has no 'drain' event. - // TODO(@jasnell): This does not appear to ever be - // emitted. It's not clear if it is necessary. - grep.stdin.on("drain", () => { - debug("echo stdout resume"); - echo.stdout.resume(); - }); - - // Propagate end from echo to grep - echo.stdout.on( - "end", - mustCall(() => { - debug("echo stdout end"); - grep.stdin.end(); - }), - ); - - echo.on( - "exit", - mustCall(() => { - debug("echo exit"); - }), - ); - - grep.on( - "exit", - mustCall(() => { - debug("grep exit"); - }), - ); - - sed.on( - "exit", - mustCall(() => { - debug("sed exit"); - }), - ); - - // TODO(@jasnell): This does not appear to ever be - // emitted. It's not clear if it is necessary. - sed.stdin.on("drain", () => { - grep.stdout.resume(); - }); - - // Propagate end from grep to sed - grep.stdout.on( - "end", - mustCall(() => { - debug("grep stdout end"); - sed.stdin.end(); - }), - ); - - let result = ""; - - sed.stdout.on( - "end", - mustCall(() => { - debug("result: " + result); - strictEqual(result, `hellO\nnOde\nwOrld\n`); - done(); - }), - ); - }); -}); diff --git a/test/bun.js/child_process.test.ts b/test/bun.js/child_process.test.ts deleted file mode 100644 index 7f2c6b383..000000000 --- a/test/bun.js/child_process.test.ts +++ /dev/null @@ -1,347 +0,0 @@ -import { describe, it as it_, expect as expect_ } from "bun:test"; -import { gcTick } from "gc"; -import { ChildProcess, spawn, execFile, exec, fork, spawnSync, execFileSync, execSync } from "node:child_process"; -import { tmpdir } from "node:os"; -import { promisify } from "node:util"; - -const expect: typeof expect_ = (actual: unknown) => { - gcTick(); - const ret = expect_(actual); - gcTick(); - return ret; -}; - -const it: typeof it_ = (label, fn) => { - const hasDone = fn.length === 1; - if (fn.constructor.name === "AsyncFunction" && hasDone) { - return it_(label, async done => { - gcTick(); - await fn(done); - gcTick(); - }); - } else if (hasDone) { - return it_(label, done => { - gcTick(); - fn(done); - gcTick(); - }); - } else if (fn.constructor.name === "AsyncFunction") { - return it_(label, async () => { - gcTick(); - await fn(() => {}); - gcTick(); - }); - } else { - return it_(label, () => { - gcTick(); - fn(() => {}); - gcTick(); - }); - } -}; - -const debug = process.env.DEBUG ? console.log : () => {}; - -const platformTmpDir = require("fs").realpathSync(tmpdir()); - -// Semver regex: https://gist.github.com/jhorsman/62eeea161a13b80e39f5249281e17c39?permalink_comment_id=2896416#gistcomment-2896416 -// Not 100% accurate, but good enough for this test -const SEMVER_REGEX = - /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[a-zA-Z\d][-a-zA-Z.\d]*)?(\+[a-zA-Z\d][-a-zA-Z.\d]*)?$/; - -describe("ChildProcess.spawn()", () => { - it("should emit `spawn` on spawn", async () => { - const proc = new ChildProcess(); - const result = await new Promise(resolve => { - proc.on("spawn", () => { - resolve(true); - }); - proc.spawn({ file: "bun", args: ["bun", "-v"] }); - }); - expect(result).toBe(true); - }); - - it("should emit `exit` when killed", async () => { - const proc = new ChildProcess(); - const result = await new Promise(resolve => { - proc.on("exit", () => { - resolve(true); - }); - - proc.spawn({ file: "bun", args: ["bun", "-v"] }); - proc.kill(); - }); - expect(result).toBe(true); - }); -}); - -describe("spawn()", () => { - it("should spawn a process", () => { - const child = spawn("echo", ["hello"]); - expect(!!child).toBe(true); - }); - - it("should disallow invalid filename", () => { - let child; - let child2; - try { - // @ts-ignore - child = spawn(123); - // @ts-ignore - child2 = spawn(["echo", "hello"]); - } catch (e) {} - expect(!!child).toBe(false); - expect(!!child2).toBe(false); - }); - - it("should allow stdout to be read via Node stream.Readable `data` events", async () => { - const child = spawn("bun", ["-v"]); - const result: string = await new Promise(resolve => { - child.stdout.on("error", e => { - console.error(e); - }); - child.stdout.on("data", data => { - debug(`stdout: ${data}`); - resolve(data.toString()); - }); - child.stderr.on("data", data => { - debug(`stderr: ${data}`); - }); - }); - expect(SEMVER_REGEX.test(result.trim())).toBe(true); - }); - - it("should allow stdout to be read via .read() API", async done => { - const child = spawn("bun", ["-v"]); - const result: string = await new Promise(resolve => { - let finalData = ""; - child.stdout.on("error", e => { - done(e); - }); - child.stdout.on("readable", () => { - let data; - - while ((data = child.stdout.read()) !== null) { - finalData += data.toString(); - } - resolve(finalData); - }); - }); - expect(SEMVER_REGEX.test(result.trim())).toBe(true); - done(); - }); - - it("should accept stdio option with 'ignore' for no stdio fds", async () => { - const child1 = spawn("bun", ["-v"], { - stdio: "ignore", - }); - const child2 = spawn("bun", ["-v"], { - stdio: ["ignore", "ignore", "ignore"], - }); - - expect(!!child1).toBe(true); - expect(child1.stdin).toBe(null); - expect(child1.stdout).toBe(null); - expect(child1.stderr).toBe(null); - - expect(!!child2).toBe(true); - expect(child2.stdin).toBe(null); - expect(child2.stdout).toBe(null); - expect(child2.stderr).toBe(null); - }); - - it("should allow us to set cwd", async () => { - const child = spawn("pwd", { cwd: platformTmpDir }); - const result: string = await new Promise(resolve => { - child.stdout.on("data", data => { - resolve(data.toString()); - }); - }); - expect(result.trim()).toBe(platformTmpDir); - }); - - it("should allow us to write to stdin", async () => { - const child = spawn("tee"); - const result: string = await new Promise(resolve => { - child.stdin.write("hello"); - child.stdout.on("data", data => { - resolve(data.toString()); - }); - }); - expect(result.trim()).toBe("hello"); - }); - - it("should allow us to timeout hanging processes", async () => { - const child = spawn("sleep", ["2"], { timeout: 3 }); - const start = performance.now(); - let end; - await new Promise(resolve => { - child.on("exit", () => { - end = performance.now(); - resolve(true); - }); - }); - expect(end - start < 2000).toBe(true); - }); - - it("should allow us to set env", async () => { - const child = spawn("env", { env: { TEST: "test" } }); - const result: string = await new Promise(resolve => { - child.stdout.on("data", data => { - resolve(data.toString()); - }); - }); - expect(/TEST\=test/.test(result)).toBe(true); - }); - - it("should allow explicit setting of argv0", async () => { - var resolve; - const promise = new Promise(resolve1 => { - resolve = resolve1; - }); - process.env.NO_COLOR = "1"; - const child = spawn("node", ["--help"], { argv0: "bun" }); - delete process.env.NO_COLOR; - let msg = ""; - - child.stdout.on("data", data => { - msg += data.toString(); - }); - - child.stdout.on("close", () => { - resolve(msg); - }); - - const result = await promise; - expect(/Open bun's Discord server/.test(result)).toBe(true); - }); - - it("should allow us to spawn in a shell", async () => { - const result1: string = await new Promise(resolve => { - const child1 = spawn("echo", ["$0"], { shell: true }); - child1.stdout.on("data", data => { - resolve(data.toString()); - }); - }); - const result2: string = await new Promise(resolve => { - const child2 = spawn("echo", ["$0"], { shell: "bash" }); - child2.stdout.on("data", data => { - resolve(data.toString()); - }); - }); - expect(result1.trim()).toBe(Bun.which("sh")); - expect(result2.trim()).toBe(Bun.which("bash")); - }); - it("should spawn a process synchronously", () => { - const { stdout } = spawnSync("echo", ["hello"], { encoding: "utf8" }); - expect(stdout.trim()).toBe("hello"); - }); -}); - -describe("execFile()", () => { - it("should execute a file", async () => { - const result: Buffer = await new Promise((resolve, reject) => { - execFile("bun", ["-v"], { encoding: "buffer" }, (error, stdout, stderr) => { - if (error) { - reject(error); - } - resolve(stdout); - }); - }); - expect(SEMVER_REGEX.test(result.toString().trim())).toBe(true); - }); -}); - -describe("exec()", () => { - it("should execute a command in a shell", async () => { - const result: Buffer = await new Promise((resolve, reject) => { - exec("bun -v", { encoding: "buffer" }, (error, stdout, stderr) => { - if (error) { - reject(error); - } - resolve(stdout); - }); - }); - expect(SEMVER_REGEX.test(result.toString().trim())).toBe(true); - }); - - it("should return an object w/ stdout and stderr when promisified", async () => { - const result = await promisify(exec)("bun -v"); - expect(typeof result).toBe("object"); - expect(typeof result.stdout).toBe("string"); - expect(typeof result.stderr).toBe("string"); - - const { stdout, stderr } = result; - expect(SEMVER_REGEX.test(stdout.trim())).toBe(true); - expect(stderr.trim()).toBe(""); - }); -}); - -describe("fork()", () => { - it("should throw an error when used", () => { - let err; - try { - fork("index.js"); - } catch (e) { - err = e; - } - expect(err instanceof Error).toBe(true); - }); -}); - -describe("spawnSync()", () => { - it("should spawn a process synchronously", () => { - const { stdout } = spawnSync("echo", ["hello"], { encoding: "utf8" }); - expect(stdout.trim()).toBe("hello"); - }); -}); - -describe("execFileSync()", () => { - it("should execute a file synchronously", () => { - const result = execFileSync("bun", ["-v"], { encoding: "utf8" }); - expect(SEMVER_REGEX.test(result.trim())).toBe(true); - }); - - it("should allow us to pass input to the command", () => { - const result = execFileSync("node", [import.meta.dir + "/spawned-child.js", "STDIN"], { - input: "hello world!", - encoding: "utf8", - }); - expect(result.trim()).toBe("data: hello world!"); - }); -}); - -describe("execSync()", () => { - it("should execute a command in the shell synchronously", () => { - const result = execSync("bun -v", { encoding: "utf8" }); - expect(SEMVER_REGEX.test(result.trim())).toBe(true); - }); -}); - -describe("Bun.spawn()", () => { - it("should return exit code 0 on successful execution", async () => { - const proc = Bun.spawn({ - cmd: ["echo", "hello"], - stdout: "pipe", - }); - - for await (const chunk of proc.stdout!) { - const text = new TextDecoder().decode(chunk); - expect(text.trim()).toBe("hello"); - } - - const result = await new Promise(resolve => { - const maybeExited = Bun.peek(proc.exited); - if (maybeExited === proc.exited) { - proc.exited.then(code => resolve(code)); - } else { - resolve(maybeExited); - } - }); - expect(result).toBe(0); - }); - // it("should fail when given an invalid cwd", () => { - // const child = Bun.spawn({ cmd: ["echo", "hello"], cwd: "/invalid" }); - // expect(child.pid).toBe(undefined); - // }); -}); diff --git a/test/bun.js/concat.test.js b/test/bun.js/concat.test.js deleted file mode 100644 index 36421cab6..000000000 --- a/test/bun.js/concat.test.js +++ /dev/null @@ -1,44 +0,0 @@ -import { describe, it, expect } from "bun:test"; -import { gcTick } from "./gc"; -import { concatArrayBuffers } from "bun"; - -describe("concat", () => { - function polyfill(chunks) { - var size = 0; - for (const chunk of chunks) { - size += chunk.byteLength; - } - var buffer = new ArrayBuffer(size); - var view = new Uint8Array(buffer); - var offset = 0; - for (const chunk of chunks) { - view.set(chunk, offset); - offset += chunk.byteLength; - } - return buffer; - } - - function concatToString(chunks) { - return Array.from(new Uint8Array(concatArrayBuffers(chunks))).join(""); - } - - function polyfillToString(chunks) { - return Array.from(new Uint8Array(polyfill(chunks))).join(""); - } - - it("works with one element", () => { - expect(concatToString([new Uint8Array([123])])).toBe(polyfillToString([new Uint8Array([123])])); - }); - - it("works with two elements", () => { - expect(concatToString([Uint8Array.from([123]), Uint8Array.from([456])])).toBe( - polyfillToString([Uint8Array.from([123]), Uint8Array.from([456])]), - ); - }); - - it("works with mix of ArrayBuffer and TypedArray elements", () => { - expect(concatToString([Uint8Array.from([123]).buffer, Uint8Array.from([456])])).toBe( - polyfillToString([Uint8Array.from([123]), Uint8Array.from([456])]), - ); - }); -}); diff --git a/test/bun.js/console/console-iterator-run.js b/test/bun.js/console/console-iterator-run.js deleted file mode 100644 index 7664c85a1..000000000 --- a/test/bun.js/console/console-iterator-run.js +++ /dev/null @@ -1,3 +0,0 @@ -for await (const line of console) { - console.write(line); -} diff --git a/test/bun.js/console/console-iterator.test.js b/test/bun.js/console/console-iterator.test.js deleted file mode 100644 index 053e4382d..000000000 --- a/test/bun.js/console/console-iterator.test.js +++ /dev/null @@ -1,63 +0,0 @@ -import { spawnSync, spawn } from "bun"; -import { describe, expect, it } from "bun:test"; -import { bunExe } from "bunExe"; - -describe("should work for static input", () => { - const inputs = [ - "hello world", - "hello world\n", - "hello world\n\n", - "hello world\n\n\n", - "Hello\nWorld\n", - "1", - "💕 Red Heart ✨ Sparkles 🔥 Fire\n💕 Red Heart ✨ Sparkles\n💕 Red Heart\n💕\n\nnormal", - ]; - - for (let input of inputs) { - it(input.replaceAll("\n", "\\n"), () => { - const { stdout } = spawnSync({ - cmd: [bunExe(), import.meta.dir + "/" + "console-iterator-run.js"], - stdin: Buffer.from(input), - env: { - BUN_DEBUG_QUIET_LOGS: "1", - }, - }); - expect(stdout.toString()).toBe(input.replaceAll("\n", "")); - }); - } -}); - -describe("should work for streaming input", () => { - const inputs = [ - "hello world", - "hello world\n", - "hello world\n\n", - "hello world\n\n\n", - "Hello\nWorld\n", - "1", - "💕 Red Heart ✨ Sparkles 🔥 Fire\n 💕 Red Heart ✨ Sparkles\n 💕 Red Heart\n 💕 \n\nnormal", - ]; - - for (let input of inputs) { - it(input.replaceAll("\n", "\\n"), async () => { - const proc = spawn({ - cmd: [bunExe(), import.meta.dir + "/" + "console-iterator-run.js"], - stdin: "pipe", - stdout: "pipe", - env: { - BUN_DEBUG_QUIET_LOGS: "1", - }, - }); - const { stdout, stdin } = proc; - stdin.write(input.slice(0, (input.length / 2) | 0)); - stdin.flush(); - await new Promise(resolve => setTimeout(resolve, 1)); - stdin.write(input.slice((input.length / 2) | 0)); - stdin.flush(); - stdin.end(); - - expect(await new Response(stdout).text()).toBe(input.replaceAll("\n", "")); - proc.kill(0); - }); - } -}); diff --git a/test/bun.js/console/console-log.expected.txt b/test/bun.js/console/console-log.expected.txt deleted file mode 100644 index 97191c8be..000000000 --- a/test/bun.js/console/console-log.expected.txt +++ /dev/null @@ -1,46 +0,0 @@ -Hello World! -123 --123 -123.567 --123.567 -true -false -null -undefined -Symbol(Symbol Description) -2000-06-27T02:24:34.304Z -[ 123, 456, 789 ] -{ - name: "foo" -} -{ - a: 123, - b: 456, - c: 789 -} -{ - a: { - b: { - c: 123 - }, - bacon: true - }, - name: "bar" -} -Promise { } -[Function] -[Function: Foo] -{} -[Function: foooo] -/FooRegex/ -Is it a bug or a feature that formatting numbers like 123 is colored -String 123 should be 2nd word, 456 == 456 and percent s %s == What okay -{ - foo: { - name: "baz" - }, - bar: [Circular] -} am -[ - {}, {}, {}, {} -] diff --git a/test/bun.js/console/console-log.js b/test/bun.js/console/console-log.js deleted file mode 100644 index e23a3e9cb..000000000 --- a/test/bun.js/console/console-log.js +++ /dev/null @@ -1,54 +0,0 @@ -console.log("Hello World!"); -console.log(123); -console.log(-123); -console.log(123.567); -console.log(-123.567); -console.log(true); -console.log(false); -console.log(null); -console.log(undefined); -console.log(Symbol("Symbol Description")); -console.log(new Date(Math.pow(2, 34) * 56)); -console.log([123, 456, 789]); -console.log({ name: "foo" }); -console.log({ a: 123, b: 456, c: 789 }); -console.log({ - a: { - b: { - c: 123, - }, - bacon: true, - }, - name: "bar", -}); - -console.log(new Promise(() => {})); - -class Foo {} - -console.log(() => {}); -console.log(Foo); -console.log(new Foo()); -console.log(function foooo() {}); - -console.log(/FooRegex/); - -console.error("uh oh"); -console.time("Check"); - -console.log("Is it a bug or a feature that formatting numbers like %d is colored", 123); -//console.log(globalThis); - -console.log("String %s should be 2nd word, 456 == %s and percent s %s == %s", "123", "456", "%s", "What", "okay"); - -const infinteLoop = { - foo: { - name: "baz", - }, - bar: {}, -}; - -infinteLoop.bar = infinteLoop; -console.log(infinteLoop, "am"); - -console.log(new Array(4).fill({})); diff --git a/test/bun.js/console/console-log.test.ts b/test/bun.js/console/console-log.test.ts deleted file mode 100644 index 7fd9c79e5..000000000 --- a/test/bun.js/console/console-log.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { file, spawn } from "bun"; -import { expect, it } from "bun:test"; -import { bunExe } from "bunExe"; - -it("should log to console correctly", async () => { - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), import.meta.dir + "/console-log.js"], - stdin: null, - stdout: "pipe", - stderr: "pipe", - env: { - BUN_DEBUG_QUIET_LOGS: "1", - }, - }); - expect(await exited).toBe(0); - expect(await new Response(stderr).text()).toBe("uh oh\n"); - expect(await new Response(stdout).text()).toBe( - await new Response(file(import.meta.dir + "/console-log.expected.txt")).text(), - ); -}); diff --git a/test/bun.js/crypto-scrypt.test.js b/test/bun.js/crypto-scrypt.test.js deleted file mode 100644 index 4b7412251..000000000 --- a/test/bun.js/crypto-scrypt.test.js +++ /dev/null @@ -1,232 +0,0 @@ -// most of these tests are taken from Node.js -// thank you Node.js team for the tests -import { expect, it } from "bun:test"; -const crypto = require("crypto"); - -const good = [ - // Zero-length key is legal, functions as a parameter validation check. - { - pass: "", - salt: "", - keylen: 0, - N: 16, - p: 1, - r: 1, - expected: "", - }, - // Test vectors from https://tools.ietf.org/html/rfc7914#page-13 that - // should pass. Note that the test vector with N=1048576 is omitted - // because it takes too long to complete and uses over 1 GB of memory. - { - pass: "", - salt: "", - keylen: 64, - N: 16, - p: 1, - r: 1, - expected: - "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442" + - "fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906", - }, - { - pass: "password", - salt: "NaCl", - keylen: 64, - N: 1024, - p: 16, - r: 8, - expected: - "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162" + - "2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640", - }, - { - pass: "pleaseletmein", - salt: "SodiumChloride", - keylen: 64, - N: 16384, - p: 1, - r: 8, - expected: - "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2" + - "d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887", - }, - { - pass: "", - salt: "", - keylen: 64, - cost: 16, - parallelization: 1, - blockSize: 1, - expected: - "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442" + - "fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906", - }, - { - pass: "password", - salt: "NaCl", - keylen: 64, - cost: 1024, - parallelization: 16, - blockSize: 8, - expected: - "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162" + - "2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640", - }, - { - pass: "pleaseletmein", - salt: "SodiumChloride", - keylen: 64, - cost: 16384, - parallelization: 1, - blockSize: 8, - expected: - "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2" + - "d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887", - }, -]; - -// Test vectors that should fail. -const bad = [ - { N: 1, p: 1, r: 1 }, // N < 2 - { N: 3, p: 1, r: 1 }, // Not power of 2. - { N: 1, cost: 1 }, // Both N and cost - // TODO: these should error, but I don't quite understand why. - // { p: 1, parallelization: 1 }, // Both p and parallelization - // { r: 1, blockSize: 1 }, // Both r and blocksize -]; - -// Test vectors where 128*N*r exceeds maxmem. -const toobig = [ - { N: 2 ** 16, p: 1, r: 1 }, // N >= 2**(r*16) - { N: 2, p: 2 ** 30, r: 1 }, // p > (2**30-1)/r - { N: 2 ** 20, p: 1, r: 8 }, - { N: 2 ** 10, p: 1, r: 8, maxmem: 2 ** 20 }, -]; - -const badargs = [ - { - args: [], - expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"password"/ */ }, - }, - { - args: [null], - expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"password"/ */ }, - }, - { - args: [""], - expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"salt"/ */ }, - }, - { - args: ["", null], - expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"salt"/ */ }, - }, - { - args: ["", ""], - expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"keylen"/ */ }, - }, - { - args: ["", "", null], - expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"keylen"/ */ }, - }, - { - args: ["", "", 42, null], - expected: { code: "ERR_INVALID_ARG_TYPE" }, - }, - // TODO: throw on these - // { - // args: ["", "", 42, {}], - // expected: { code: "ERR_INVALID_ARG_TYPE" }, - // }, - // { - // args: ["", "", 42, {}, {}], - // expected: { code: "ERR_INVALID_ARG_TYPE" }, - // }, - // { - // args: ["", "", 42, {}, null], - // expected: { code: "ERR_INVALID_ARG_TYPE" }, - // }, - // { - // args: ["", "", 0.42], - // expected: { code: "ERR_OUT_OF_RANGE" /*message: /"keylen"/ */ }, - // }, - // { - // args: ["", "", -42], - // expected: { code: "ERR_OUT_OF_RANGE" /*message: /"keylen"/ */ }, - // }, - // { - // args: ["", "", 2147485780], - // expected: { code: "ERR_OUT_OF_RANGE" /*message: /"keylen"/ */ }, - // }, - // { - // args: ["", "", 0, { maxmem: 2 ** 53 }], - // expected: { code: "ERR_OUT_OF_RANGE" /*message: /"keylen"/ */ }, - // }, -]; - -it("scrypt good", () => { - for (const options of good) { - const { pass, salt, keylen, expected } = options; - const actual = crypto.scryptSync(pass, salt, keylen, options); - expect(actual.toString("hex")).toBe(expected); - } -}); - -it("scrypt bad", () => { - for (const options of bad) { - expect(() => crypto.scryptSync("pass", "salt", 1, options)).toThrow(/Invalid scrypt param/); - } -}); - -it("scrypt toobig", () => { - for (const options of toobig) { - expect(() => crypto.scryptSync("pass", "salt", 1, options)).toThrow(/Invalid scrypt param/); - } -}); - -it("scrypt defaults eql", () => { - const defaults = { N: 16384, p: 1, r: 8 }; - const expected = crypto.scryptSync("pass", "salt", 1, defaults); - const actual = crypto.scryptSync("pass", "salt", 1); - expect(actual.toString("hex")).toBe(expected.toString("hex")); -}); - -// TODO: DEFAULT_ENCODING is read-only -// it("scrypt defaults encoding", () => { -// { -// const defaultEncoding = crypto.DEFAULT_ENCODING; -// const defaults = { N: 16384, p: 1, r: 8 }; -// const expected = crypto.scryptSync("pass", "salt", 1, defaults); - -// const testEncoding = "latin1"; -// crypto.DEFAULT_ENCODING = testEncoding; -// const actual = crypto.scryptSync("pass", "salt", 1); -// expect(actual).toBe(expected.toString(testEncoding)); - -// crypto.DEFAULT_ENCODING = defaultEncoding; -// } -// }); - -it("scrypt badargs", () => { - for (const { args, expected } of badargs) { - try { - crypto.scryptSync(...args); - expect(() => {}).toThrow(); - } catch (e) { - if (!("code" in e)) throw e; - expect(e.code).toBe(expected.code); - } - } - - // { - // // Values for maxmem that do not fit in 32 bits but that are still safe - // // integers should be allowed. - // crypto.scrypt( - // "", - // "", - // 4, - // { maxmem: 2 ** 52 }, - // common.mustSucceed((actual) => { - // expect(actual.toString("hex")).toBe("d72c87d0"); - // }), - // ); -}); diff --git a/test/bun.js/crypto.test.js b/test/bun.js/crypto.test.js deleted file mode 100644 index b5b8e9286..000000000 --- a/test/bun.js/crypto.test.js +++ /dev/null @@ -1,106 +0,0 @@ -import { sha, MD5, MD4, SHA1, SHA224, SHA256, SHA384, SHA512, SHA512_256, gc, CryptoHasher } from "bun"; -import { it, expect, describe } from "bun:test"; -import { readFileSync } from "fs"; - -const HashClasses = [MD5, MD4, SHA1, SHA224, SHA256, SHA384, SHA512, SHA512_256]; - -describe("CryptoHasher", () => { - it("CryptoHasher.algorithms", () => { - expect(CryptoHasher.algorithms).toEqual([ - "blake2b256", - "md4", - "md5", - "ripemd160", - "sha1", - "sha224", - "sha256", - "sha384", - "sha512", - "sha512-256", - ]); - }); - - it("CryptoHasher md5", () => { - var hasher = new CryptoHasher("md5"); - hasher.update("hello world"); - expect(hasher.digest("hex")).toBe("5eb63bbbe01eeed093cb22bb8f5acdc3"); - expect(hasher.algorithm).toBe("md5"); - }); - - it("CryptoHasher blake2b256", () => { - var hasher = new CryptoHasher("blake2b256"); - hasher.update("hello world"); - expect(hasher.algorithm).toBe("blake2b256"); - - expect(hasher.digest("hex")).toBe( - // b2sum --length=256 - "256c83b297114d201b30179f3f0ef0cace9783622da5974326b436178aeef610", - ); - }); - - it("CryptoHasher sha512", () => { - var hasher = new CryptoHasher("sha512"); - hasher.update("hello world"); - expect(hasher.digest("hex")).toBe( - "309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f989dd35bc5ff499670da34255b45b0cfd830e81f605dcf7dc5542e93ae9cd76f", - ); - expect(hasher.algorithm).toBe("sha512"); - }); -}); - -describe("crypto", () => { - for (let Hash of HashClasses) { - for (let [input, label] of [ - ["hello world", '"hello world"'], - ["hello world".repeat(20).slice(), '"hello world" x 20'], - ["", "empty string"], - ["a", '"a"'], - ]) { - describe(label, () => { - gc(true); - - it(`${Hash.name} base64`, () => { - gc(true); - const result = new Hash(); - result.update(input); - expect(typeof result.digest("base64")).toBe("string"); - gc(true); - }); - - it(`${Hash.name} hash base64`, () => { - Hash.hash(input, "base64"); - gc(true); - }); - - it(`${Hash.name} hex`, () => { - const result = new Hash(); - result.update(input); - expect(typeof result.digest("hex")).toBe("string"); - gc(true); - }); - - it(`${Hash.name} hash hex`, () => { - expect(typeof Hash.hash(input, "hex")).toBe("string"); - gc(true); - }); - - it(`${Hash.name} buffer`, () => { - var buf = new Uint8Array(256); - const result = new Hash(); - - result.update(input); - expect(result.digest(buf)).toBe(buf); - expect(buf[0] != 0).toBe(true); - gc(true); - }); - - it(`${Hash.name} buffer`, () => { - var buf = new Uint8Array(256); - - expect(Hash.hash(input, buf) instanceof Uint8Array).toBe(true); - gc(true); - }); - }); - } - } -}); diff --git a/test/bun.js/decorators.test.ts b/test/bun.js/decorators.test.ts deleted file mode 100644 index 134ae73ac..000000000 --- a/test/bun.js/decorators.test.ts +++ /dev/null @@ -1,959 +0,0 @@ -// @ts-nocheck -import { test, expect, describe } from "bun:test"; - -test("decorator order of evaluation", () => { - let counter = 0; - const computedProp: unique symbol = Symbol("computedProp"); - - @decorator1 - @decorator2 - class BugReport { - @decorator7 - type: string; - - @decorator3 - x: number = 20; - - @decorator5 - private _y: number = 12; - - @decorator10 - get y() { - return this._y; - } - @decorator11 - set y(newY: number) { - this._y = newY; - } - - @decorator9 - [computedProp]: string = "yes"; - - constructor(@decorator8 type: string) { - this.type = type; - } - - @decorator6 - move(newX: number, @decorator12 newY: number) { - this.x = newX; - this._y = newY; - } - - @decorator4 - jump() { - this._y += 30; - } - } - - function decorator1(target, propertyKey) { - expect(counter++).toBe(11); - expect(target === BugReport).toBe(true); - expect(propertyKey).toBe(undefined); - } - - function decorator2(target, propertyKey) { - expect(counter++).toBe(10); - expect(target === BugReport).toBe(true); - expect(propertyKey).toBe(undefined); - } - - function decorator3(target, propertyKey) { - expect(counter++).toBe(1); - expect(target === BugReport.prototype).toBe(true); - expect(propertyKey).toBe("x"); - } - - function decorator4(target, propertyKey) { - expect(counter++).toBe(8); - expect(target === BugReport.prototype).toBe(true); - expect(propertyKey).toBe("jump"); - } - - function decorator5(target, propertyKey) { - expect(counter++).toBe(2); - expect(target === BugReport.prototype).toBe(true); - expect(propertyKey).toBe("_y"); - } - - function decorator6(target, propertyKey) { - expect(counter++).toBe(7); - expect(target === BugReport.prototype).toBe(true); - expect(propertyKey).toBe("move"); - } - - function decorator7(target, propertyKey) { - expect(counter++).toBe(0); - expect(target === BugReport.prototype).toBe(true); - expect(propertyKey).toBe("type"); - } - - function decorator8(target, propertyKey) { - expect(counter++).toBe(9); - expect(target === BugReport).toBe(true); - expect(propertyKey).toBe(undefined); - } - - function decorator9(target, propertyKey) { - expect(counter++).toBe(5); - expect(target === BugReport.prototype).toBe(true); - expect(propertyKey).toBe(computedProp); - } - - function decorator10(target, propertyKey) { - expect(counter++).toBe(3); - expect(target === BugReport.prototype).toBe(true); - expect(propertyKey).toBe("y"); - } - - function decorator11(target, propertyKey) { - expect(counter++).toBe(4); - expect(target === BugReport.prototype).toBe(true); - expect(propertyKey).toBe("y"); - } - - function decorator12(target, propertyKey) { - expect(counter++).toBe(6); - expect(target === BugReport.prototype).toBe(true); - expect(propertyKey).toBe("move"); - } -}); - -test("decorator factories order of evaluation", () => { - let counter = 0; - const computedProp: unique symbol = Symbol("computedProp"); - - @decorator1() - @decorator2() - class BugReport { - @decorator7() - type: string; - - @decorator3() - x: number = 20; - - @decorator5() - private _y: number = 12; - - @decorator10() - get y() { - return this._y; - } - @decorator11() - set y(newY: number) { - this._y = newY; - } - - @decorator9() - [computedProp]: string = "yes"; - - constructor(@decorator8() type: string) { - this.type = type; - } - - @decorator6() - move(newX: number, @decorator12() newY: number) { - this.x = newX; - this._y = newY; - } - - @decorator4() - jump() { - this._y += 30; - } - } - - function decorator1() { - expect(counter++).toBe(18); - return function (target, descriptorKey) { - expect(counter++).toBe(23); - }; - } - - function decorator2() { - expect(counter++).toBe(19); - return function (target, descriptorKey) { - expect(counter++).toBe(22); - }; - } - - function decorator3() { - expect(counter++).toBe(2); - return function (target, descriptorKey) { - expect(counter++).toBe(3); - }; - } - - function decorator4() { - expect(counter++).toBe(16); - return function (target, descriptorKey) { - expect(counter++).toBe(17); - }; - } - - function decorator5() { - expect(counter++).toBe(4); - return function (target, descriptorKey) { - expect(counter++).toBe(5); - }; - } - - function decorator6() { - expect(counter++).toBe(12); - return function (target, descriptorKey) { - expect(counter++).toBe(15); - }; - } - - function decorator7() { - expect(counter++).toBe(0); - return function (target, descriptorKey) { - expect(counter++).toBe(1); - }; - } - - function decorator8() { - expect(counter++).toBe(20); - return function (target, descriptorKey) { - expect(counter++).toBe(21); - }; - } - - function decorator9() { - expect(counter++).toBe(10); - return function (target, descriptorKey) { - expect(counter++).toBe(11); - }; - } - - function decorator10() { - expect(counter++).toBe(6); - return function (target, descriptorKey) { - expect(counter++).toBe(7); - }; - } - - function decorator11() { - expect(counter++).toBe(8); - return function (target, descriptorKey) { - expect(counter++).toBe(9); - }; - } - - function decorator12() { - expect(counter++).toBe(13); - return function (target, descriptorKey) { - expect(counter++).toBe(14); - }; - } -}); - -test("parameter decorators", () => { - let counter = 0; - class HappyDecorator { - width: number; - height: number; - x: number; - y: number; - - move(@d4 x: number, @d5 @d6 y: number) { - this.x = x; - this.y = y; - } - - constructor(one: number, two: string, three: boolean, @d1 @d2 width: number, @d3 height: number) { - this.width = width; - this.height = height; - } - - dance(@d7 @d8 intensity: number) { - this.width *= intensity; - this.height *= intensity; - } - } - - function d1(target, propertyKey, parameterIndex) { - expect(counter++).toBe(7); - expect(target === HappyDecorator).toBe(true); - expect(propertyKey).toBe(undefined); - expect(parameterIndex).toBe(3); - } - - function d2(target, propertyKey, parameterIndex) { - expect(counter++).toBe(6); - expect(target === HappyDecorator).toBe(true); - expect(propertyKey).toBe(undefined); - expect(parameterIndex).toBe(3); - } - - function d3(target, propertyKey, parameterIndex) { - expect(counter++).toBe(5); - expect(target === HappyDecorator).toBe(true); - expect(propertyKey).toBe(undefined); - expect(parameterIndex).toBe(4); - } - - function d4(target, propertyKey, parameterIndex) { - expect(counter++).toBe(2); - expect(target === HappyDecorator.prototype).toBe(true); - expect(propertyKey).toBe("move"); - expect(parameterIndex).toBe(0); - } - - function d5(target, propertyKey, parameterIndex) { - expect(counter++).toBe(1); - expect(target === HappyDecorator.prototype).toBe(true); - expect(propertyKey).toBe("move"); - expect(parameterIndex).toBe(1); - } - - function d6(target, propertyKey, parameterIndex) { - expect(counter++).toBe(0); - expect(target === HappyDecorator.prototype).toBe(true); - expect(propertyKey).toBe("move"); - expect(parameterIndex).toBe(1); - } - - function d7(target, propertyKey, parameterIndex) { - expect(counter++).toBe(4); - expect(target === HappyDecorator.prototype).toBe(true); - expect(propertyKey).toBe("dance"); - expect(parameterIndex).toBe(0); - } - - function d8(target, propertyKey, parameterIndex) { - expect(counter++).toBe(3); - expect(target === HappyDecorator.prototype).toBe(true); - expect(propertyKey).toBe("dance"); - expect(parameterIndex).toBe(0); - } - - class Maybe { - constructor(@m1 private x: number, @m2 public y: boolean, @m3 protected z: string) {} - } - - function m1(target, propertyKey, index) { - expect(target === Maybe).toBe(true); - expect(propertyKey).toBe(undefined); - expect(index).toBe(0); - } - - function m2(target, propertyKey, index) { - expect(target === Maybe).toBe(true); - expect(propertyKey).toBe(undefined); - expect(index).toBe(1); - } - - function m3(target, propertyKey, index) { - expect(target === Maybe).toBe(true); - expect(propertyKey).toBe(undefined); - expect(index).toBe(2); - } -}); - -test("decorators random", () => { - @Frozen - class IceCream {} - - function Frozen(constructor: Function) { - Object.freeze(constructor); - Object.freeze(constructor.prototype); - } - - expect(Object.isFrozen(IceCream)).toBe(true); - - class IceCreamComponent { - @Emoji() - flavor = "vanilla"; - } - - // Property Decorator - function Emoji() { - return function (target: Object, key: string | symbol) { - let val = target[key]; - - const getter = () => { - return val; - }; - const setter = next => { - val = `🍦 ${next} 🍦`; - }; - - Object.defineProperty(target, key, { - get: getter, - set: setter, - enumerable: true, - configurable: true, - }); - }; - } - - const iceCream = new IceCreamComponent(); - expect(iceCream.flavor === "🍦 vanilla 🍦").toBe(true); - iceCream.flavor = "chocolate"; - expect(iceCream.flavor === "🍦 chocolate 🍦").toBe(true); - - const i: unique symbol = Symbol.for("i"); - const h: unique symbol = Symbol.for("h"); - const t: unique symbol = Symbol.for("t"); - const q: unique symbol = Symbol.for("q"); - const p: unique symbol = Symbol.for("p"); - const u3: unique symbol = Symbol.for("u3"); - const u5: unique symbol = Symbol.for("u5"); - const u6: unique symbol = Symbol.for("u6"); - const u8: unique symbol = Symbol.for("u8"); - - class S { - @StringAppender("😛") k = 35; - @StringAppender("🤠") static j = 4; - @StringAppender("😵‍💫") private static [h] = 30; - @StringAppender("🤯") private static u = 60; - @StringAppender("🤪") private [t] = 32; - @StringAppender("🤑") [i] = 8; - @StringAppender("🎃") private e = 10; - @StringAppender("👻") static [q] = 202; - @StringAppender("😇") r = S[h]; - _y: number; - @StringAppender("🤡") get y() { - return this._y; - } - set y(next) { - this._y = next; - } - #o = 100; - - @StringAppender("😍") u1: number; - @StringAppender("🥳") static u2: number; - @StringAppender("🤓") private static [u3]: number; - @StringAppender("🥺") private static u4: number; - @StringAppender("🤯") private [u5]: number; - @StringAppender("🤩") [u6]: number; - @StringAppender("☹️") private u7: number; - @StringAppender("🙃") static [u8]: number; - - @StringAppender("🤔") u9 = this.u1; - @StringAppender("🤨") u10 = this.u2; - @StringAppender("🙂") u11 = S[u3]; - @StringAppender("🙁") u12 = S.u4; - @StringAppender("😐") u13 = this[u5]; - @StringAppender("😑") u14 = this[u6]; - @StringAppender("😶") u15 = this.u7; - @StringAppender("😏") u16 = S[u8]; - - constructor() { - this.k = 3; - expect(this.k).toBe("3 😛"); - expect(S.j).toBe(4); - expect(this[i]).toBe("8 🤑"); - expect(this.e).toBe("10 🎃"); - expect(S[h]).toBe(30); - expect(S.u).toBe(60); - expect(this[t]).toBe("32 🤪"); - expect(S[q]).toBe(202); - expect(this.#o).toBe(100); - expect(this.r).toBe("30 😇"); - expect(this.y).toBe(undefined); - this.y = 100; - expect(this.y).toBe(100); - - expect(this.u1).toBe(undefined); - expect(S.u2).toBe(undefined); - expect(S[u3]).toBe(undefined); - expect(S.u4).toBe(undefined); - expect(this[u5]).toBe(undefined); - expect(this[u6]).toBe(undefined); - expect(this.u7).toBe(undefined); - expect(S[u8]).toBe(undefined); - - expect(this.u9).toBe("undefined 🤔"); - expect(this.u10).toBe("undefined 🤨"); - expect(this.u11).toBe("undefined 🙂"); - expect(this.u12).toBe("undefined 🙁"); - expect(this.u13).toBe("undefined 😐"); - expect(this.u14).toBe("undefined 😑"); - expect(this.u15).toBe("undefined 😶"); - expect(this.u16).toBe("undefined 😏"); - - this.u1 = 100; - expect(this.u1).toBe("100 😍"); - S.u2 = 100; - expect(S.u2).toBe("100 🥳"); - S[u3] = 100; - expect(S[u3]).toBe("100 🤓"); - S.u4 = 100; - expect(S.u4).toBe("100 🥺"); - this[u5] = 100; - expect(this[u5]).toBe("100 🤯"); - this[u6] = 100; - expect(this[u6]).toBe("100 🤩"); - this.u7 = 100; - expect(this.u7).toBe("100 ☹️"); - S[u8] = 100; - expect(S[u8]).toBe("100 🙃"); - - expect(this.u9).toBe("undefined 🤔"); - expect(this.u10).toBe("undefined 🤨"); - expect(this.u11).toBe("undefined 🙂"); - expect(this.u12).toBe("undefined 🙁"); - expect(this.u13).toBe("undefined 😐"); - expect(this.u14).toBe("undefined 😑"); - expect(this.u15).toBe("undefined 😶"); - expect(this.u16).toBe("undefined 😏"); - } - } - - let s = new S(); - expect(s.u9).toBe("undefined 🤔"); - expect(s.u10).toBe("undefined 🤨"); - expect(s.u11).toBe("undefined 🙂"); - expect(s.u12).toBe("undefined 🙁"); - expect(s.u13).toBe("undefined 😐"); - expect(s.u14).toBe("undefined 😑"); - expect(s.u15).toBe("undefined 😶"); - expect(s.u16).toBe("undefined 😏"); - - s.u9 = 35; - expect(s.u9).toBe("35 🤔"); - s.u10 = 36; - expect(s.u10).toBe("36 🤨"); - s.u11 = 37; - expect(s.u11).toBe("37 🙂"); - s.u12 = 38; - expect(s.u12).toBe("38 🙁"); - s.u13 = 39; - expect(s.u13).toBe("39 😐"); - s.u14 = 40; - expect(s.u14).toBe("40 😑"); - s.u15 = 41; - expect(s.u15).toBe("41 😶"); - s.u16 = 42; - expect(s.u16).toBe("42 😏"); - - function StringAppender(emoji: string) { - return function (target: Object, key: string | symbol) { - let val = target[key]; - - const getter = () => { - return val; - }; - const setter = value => { - val = `${value} ${emoji}`; - }; - - Object.defineProperty(target, key, { - get: getter, - set: setter, - enumerable: true, - configurable: true, - }); - }; - } -}); - -test("class field order", () => { - class N { - l = 455; - } - class M { - u = 4; - @d1 w = 9; - constructor() { - // this.w = 9 should be moved here - expect(this.u).toBe(4); - expect(this.w).toBe(9); - this.u = 3; - this.w = 6; - expect(this.u).toBe(3); - expect(this.w).toBe(6); - } - } - - function d1(target, propertyKey) { - expect(target === M.prototype).toBe(true); - expect(propertyKey).toBe("w"); - } - - let m = new M(); - expect(m.u).toBe(3); - expect(m.w).toBe(6); -}); - -test("changing static method", () => { - class A { - static bar() { - return 1; - } - } - - @changeMethodReturn("bar", 5) - class A_2 { - static bar() { - return 7; - } - } - - function changeMethodReturn(method, value) { - return function (target) { - target[method] = function () { - return value; - }; - return target; - }; - } - - @changeMethodReturn("bar", 2) - class B extends A {} - - @changeMethodReturn("bar", 9) - class C extends B {} - - expect(A_2.bar()).toBe(5); - expect(A.bar()).toBe(1); - expect(B.bar()).toBe(2); - expect(C.bar()).toBe(9); -}); - -test("class extending from another class", () => { - class A { - a: number; - constructor() { - this.a = 3; - } - } - - class B extends A { - a: number = 9; - } - - expect(new A().a).toBe(3); - expect(new B().a).toBe(9); - - class C { - a: number = 80; - } - - class D extends C { - a: number = 32; - constructor() { - super(); - } - } - - expect(new C().a).toBe(80); - expect(new D().a).toBe(32); - - class E { - a: number = 40; - constructor() { - expect(this.a).toBe(40); - } - } - - class F extends E { - @d1 a: number = 50; - constructor() { - super(); - expect(this.a).toBe(50); - this.a = 60; - expect(this.a).toBe(60); - } - } - - function d1(target) { - target.a = 100; - } -}); - -test("decorated fields moving to constructor", () => { - class A { - @d1 a = 3; - @d2 b = 4; - @d3 c = 5; - } - - function d1(target, propertyKey) { - expect(target === A.prototype).toBe(true); - expect(propertyKey).toBe("a"); - } - - function d2(target, propertyKey) { - expect(target === A.prototype).toBe(true); - expect(propertyKey).toBe("b"); - } - - function d3(target, propertyKey) { - expect(target === A.prototype).toBe(true); - expect(propertyKey).toBe("c"); - } - - let a = new A(); - expect(a.a).toBe(3); - expect(a.b).toBe(4); - expect(a.c).toBe(5); -}); - -test("only class decorator", () => { - let a = 0; - @d1 - class A {} - - let aa = new A(); - - function d1(target) { - a = 1; - expect(target).toBe(A); - } - - expect(a).toBe(1); -}); - -test("only property decorators", () => { - let a = 0; - class A { - @d1 a() {} - } - - let b = 0; - class B { - @d2 b = 3; - } - - let c = 0; - class C { - @d3 get c() { - return 3; - } - } - - function d1(target, propertyKey) { - a = 1; - expect(target === A.prototype).toBe(true); - expect(propertyKey).toBe("a"); - } - expect(a).toBe(1); - - function d2(target, propertyKey) { - b = 1; - expect(target === B.prototype).toBe(true); - expect(propertyKey).toBe("b"); - } - expect(b).toBe(1); - - function d3(target, propertyKey) { - c = 1; - expect(target === C.prototype).toBe(true); - expect(propertyKey).toBe("c"); - } - expect(c).toBe(1); -}); - -test("only argument decorators", () => { - let a = 0; - class A { - a(@d1 a: string) {} - } - - function d1(target, propertyKey, parameterIndex) { - a = 1; - expect(target === A.prototype).toBe(true); - expect(propertyKey).toBe("a"); - expect(parameterIndex).toBe(0); - } - - expect(a).toBe(1); -}); - -test("no decorators", () => { - let a = 0; - class A { - b: number; - constructor() { - a = 1; - this.b = 300000; - } - } - - let aa = new A(); - expect(a).toBe(1); - expect(aa.b).toBe(300000); -}); - -describe("constructor statements", () => { - test("with parameter properties", () => { - class A { - constructor(readonly d: string = "default") { - expect(d).toBe(d); - expect(this.d).toBe(d); - } - } - - const a = new A("c"); - expect(a.d).toBe("c"); - - class B extends A {} - - const b = new B(); - expect(b.d).toBe("default"); - - class C extends A { - constructor(public f: number) { - super(); - expect(this.d).toBe("default"); - expect(f).toBe(f); - expect(this.f).toBe(f); - } - } - - const c = new C(5); - expect(c.d).toBe("default"); - expect(c.f).toBe(5); - }); - - test("class expressions (no decorators)", () => { - const A = class a { - constructor(readonly b: string = "default") { - expect(b).toBe(b); - expect(this.b).toBe(b); - } - }; - - const a = new A("hello class expression"); - expect(a.b).toBe("hello class expression"); - - const B = class b extends A {}; - const b = new B(); - expect(b.b).toBe("default"); - - const C = class c extends A { - constructor(public f: number) { - super(); - expect(this.b).toBe("default"); - expect(this.f).toBe(f); - expect(f).toBe(f); - } - }; - - const c = new C(5); - expect(c.b).toBe("default"); - expect(c.f).toBe(5); - }); - - test("with parameter properties and statements", () => { - class B { - value: number; - v2: number; - constructor(value: number) { - this.value = value; - this.v2 = 0; - } - } - - class A extends B { - constructor(value: number, public v: string = "test") { - const newValue = value * 10; - super(newValue); - } - } - - const a = new A(10); - expect(a.value).toBe(100); - expect(a.v).toBe("test"); - expect(a.v2).toBe(0); - }); - - test("with parameter properties, statements, and decorators", () => { - class B { - value: number; - v2: number; - constructor(value: number) { - this.value = value; - this.v2 = 0; - } - } - - function d1() {} - - class A extends B { - b: number; - constructor(value: number, @d1 b: number, public v: string = "test") { - const newValue = value * 10; - super(newValue); - expect(this.v).toBe("test"); - this.b = b; - expect(this.b).toBe(b); - } - } - - const a = new A(10, 1); - expect(a.b).toBe(1); - expect(a.value).toBe(100); - expect(a.v).toBe("test"); - expect(a.v2).toBe(0); - }); - - test("with more parameter properties, statements, and decorators", () => { - let decoratorCounter = 0; - function d1() { - expect(decoratorCounter).toBe(1); - decoratorCounter += 1; - } - function d2() { - expect(decoratorCounter).toBe(0); - decoratorCounter += 1; - } - function d3() { - expect(decoratorCounter).toBe(2); - decoratorCounter += 1; - } - function d4() { - expect(decoratorCounter).toBe(3); - decoratorCounter += 1; - } - - class A { - l: number; - constructor(protected u: string, @d1 l: number = 3, @d2 public k: number = 4) { - this.l = l; - } - } - - class B extends A { - @d3 e: string = "hello test"; - - constructor(private i: number) { - super("protected"); - expect(this.i).toBe(i); - expect(this.u).toBe("protected"); - } - - @d4 f() {} - } - - let b = new B(9); - expect(b.k).toBe(4); - expect(b.l).toBe(3); - expect(b.e).toBe("hello test"); - }); - - test("expression with parameter properties and statements", () => { - const B = class b { - value: number; - v2: number; - constructor(value: number) { - this.value = value; - this.v2 = 0; - } - }; - - const A = class a extends B { - constructor(value: number, public v: string = "test") { - const newValue = value * 10; - super(newValue); - } - }; - - const a = new A(10); - expect(a.value).toBe(100); - expect(a.v).toBe("test"); - expect(a.v2).toBe(0); - }); -}); diff --git a/test/bun.js/dirname.test.js b/test/bun.js/dirname.test.js deleted file mode 100644 index 98292dc49..000000000 --- a/test/bun.js/dirname.test.js +++ /dev/null @@ -1,9 +0,0 @@ -import { expect, it } from "bun:test"; - -it("__dirname should work", () => { - expect(import.meta.dir).toBe(__dirname); -}); - -it("__filename should work", () => { - expect(import.meta.path).toBe(__filename); -}); diff --git a/test/bun.js/disabled-module.test.js b/test/bun.js/disabled-module.test.js deleted file mode 100644 index c12676959..000000000 --- a/test/bun.js/disabled-module.test.js +++ /dev/null @@ -1,38 +0,0 @@ -import { expect, test } from "bun:test"; - -test("not implemented yet module masquerades as undefined and throws an error", () => { - const worker_threads = import.meta.require("worker_threads"); - - expect(typeof worker_threads).toBe("undefined"); - expect(typeof worker_threads.getEnvironmentData).toBe("undefined"); -}); - -test("AsyncLocalStorage polyfill", () => { - const { AsyncLocalStorage } = import.meta.require("async_hooks"); - - const store = new AsyncLocalStorage(); - var called = false; - expect(store.getStore()).toBe(null); - store.run({ foo: "bar" }, () => { - expect(store.getStore()).toEqual({ foo: "bar" }); - called = true; - }); - expect(store.getStore()).toBe(null); - expect(called).toBe(true); -}); - -test("AsyncResource polyfill", () => { - const { AsyncResource } = import.meta.require("async_hooks"); - - const resource = new AsyncResource("test"); - var called = false; - resource.runInAsyncScope( - () => { - called = true; - }, - null, - "foo", - "bar", - ); - expect(called).toBe(true); -}); diff --git a/test/bun.js/dns.node.mjs b/test/bun.js/dns.node.mjs deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/bun.js/empty.js b/test/bun.js/empty.js deleted file mode 100644 index cb0ff5c3b..000000000 --- a/test/bun.js/empty.js +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/test/bun.js/emptyFile b/test/bun.js/emptyFile deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/bun.js/esbuild-child_process.test.ts b/test/bun.js/esbuild-child_process.test.ts deleted file mode 100644 index 9c0daa523..000000000 --- a/test/bun.js/esbuild-child_process.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { spawnSync } from "bun"; -import { describe, it, expect, test } from "bun:test"; -import { bunExe } from "bunExe"; - -test("esbuild", () => { - const { exitCode, stderr, stdout } = spawnSync([bunExe(), import.meta.dir + "/esbuild-test.js"], { - env: { - BUN_DEBUG_QUIET_LOGS: "1", - }, - }); - const out = "" + stderr?.toString() + stdout?.toString(); - if (exitCode !== 0 && out?.length) { - throw new Error(out); - } - - expect(exitCode).toBe(0); -}); diff --git a/test/bun.js/esbuild-test.js b/test/bun.js/esbuild-test.js deleted file mode 100644 index beb34b283..000000000 --- a/test/bun.js/esbuild-test.js +++ /dev/null @@ -1,37 +0,0 @@ -import { transform, transformSync } from "esbuild"; - -{ - const result = await transform("console.log('hello world')", { - loader: "js", - target: "node12", - }); - if (result.code !== 'console.log("hello world");\n') { - throw new Error("Test failed."); - } -} - -{ - const hugeString = `console.log(${JSON.stringify("a".repeat(1000000))});`; - - for (let i = 0; i < 2; i++) { - const result = await transform(hugeString, { - loader: "js", - target: "node12", - }); - if (result.code !== hugeString + "\n") { - throw new Error("Test failed."); - } - } -} - -{ - const result = transformSync("console.log('hello world')", { - loader: "js", - target: "node12", - }); - if (result.code !== 'console.log("hello world");\n') { - throw new Error("Test failed."); - } -} - -process.exit(0); diff --git a/test/bun.js/escapeHTML.test.js b/test/bun.js/escapeHTML.test.js deleted file mode 100644 index f96849a84..000000000 --- a/test/bun.js/escapeHTML.test.js +++ /dev/null @@ -1,92 +0,0 @@ -import { describe, it, expect } from "bun:test"; -import { gcTick } from "./gc"; -import { escapeHTML } from "bun"; - -describe("escapeHTML", () => { - // The matrix of cases we need to test for: - // 1. Works with short strings - // 2. Works with long strings - // 3. Works with latin1 strings - // 4. Works with utf16 strings - // 5. Works when the text to escape is somewhere in the middle - // 6. Works when the text to escape is in the beginning - // 7. Works when the text to escape is in the end - // 8. Returns the same string when there's no need to escape - it("works", () => { - expect(escapeHTML("absolutely nothing to do here")).toBe("absolutely nothing to do here"); - expect(escapeHTML("")).toBe("<script>alert(1)</script>"); - expect(escapeHTML("<")).toBe("<"); - expect(escapeHTML(">")).toBe(">"); - expect(escapeHTML("&")).toBe("&"); - expect(escapeHTML("'")).toBe("'"); - expect(escapeHTML('"')).toBe("""); - expect(escapeHTML("\n")).toBe("\n"); - expect(escapeHTML("\r")).toBe("\r"); - expect(escapeHTML("\t")).toBe("\t"); - expect(escapeHTML("\f")).toBe("\f"); - expect(escapeHTML("\v")).toBe("\v"); - expect(escapeHTML("\b")).toBe("\b"); - expect(escapeHTML("\u00A0")).toBe("\u00A0"); - expect(escapeHTML("" + "lalala")).toBe( - "lalala<script>alert(1)</script>lalala", - ); - - expect(escapeHTML("" + "lalala")).toBe("<script>alert(1)</script>lalala"); - expect(escapeHTML("lalala" + "")).toBe("lalala" + "<script>alert(1)</script>"); - - expect(escapeHTML("What does 😊 mean?")).toBe("What does 😊 mean?"); - const output = escapeHTML("What does 😊 mean in text?")).toBe("<div>What does 😊 mean in text?"); - - expect(escapeHTML(("lalala" + "" + "lalala").repeat(900))).toBe( - "lalala<script>alert(1)</script>lalala".repeat(900), - ); - expect(escapeHTML(("" + "lalala").repeat(900))).toBe( - "<script>alert(1)</script>lalala".repeat(900), - ); - expect(escapeHTML(("lalala" + "").repeat(900))).toBe( - ("lalala" + "<script>alert(1)</script>").repeat(900), - ); - - // the positions of the unicode codepoint are important - // our simd code for U16 is at 8 bytes, so we need to especially check the boundaries - expect(escapeHTML("😊lalala" + "" + "lalala")).toBe( - "😊lalala<script>alert(1)</script>lalala", - ); - expect(escapeHTML("" + "lalala")).toBe("<script>😊alert(1)</script>lalala"); - expect(escapeHTML("" + "lalala")).toBe("<script>alert(1)😊</script>lalala"); - expect(escapeHTML("" + "😊lalala")).toBe("<script>alert(1)</script>😊lalala"); - expect(escapeHTML("" + "lal😊ala")).toBe("<script>alert(1)</script>lal😊ala"); - expect(escapeHTML("" + "lal😊ala".repeat(10))).toBe( - "<script>alert(1)</script>" + "lal😊ala".repeat(10), - ); - - for (let i = 1; i < 10; i++) - expect(escapeHTML("" + "la😊".repeat(i))).toBe( - "<script>alert(1)</script>" + "la😊".repeat(i), - ); - - expect(escapeHTML("la😊" + "")).toBe("la😊" + "<script>alert(1)</script>"); - expect(escapeHTML(("lalala" + "😊").repeat(1))).toBe( - ("lalala" + "<script>alert(1)</script>😊").repeat(1), - ); - - expect(escapeHTML("😊".repeat(100))).toBe("😊".repeat(100)); - expect(escapeHTML("😊<".repeat(100))).toBe("😊<".repeat(100)); - expect(escapeHTML("<😊>".repeat(100))).toBe("<😊>".repeat(100)); - expect(escapeHTML("😊")).toBe("😊"); - expect(escapeHTML("😊😊")).toBe("😊😊"); - expect(escapeHTML("😊lo")).toBe("😊lo"); - expect(escapeHTML("lo😊")).toBe("lo😊"); - - expect(escapeHTML(" ".repeat(32) + "😊")).toBe(" ".repeat(32) + "😊"); - expect(escapeHTML(" ".repeat(32) + "😊😊")).toBe(" ".repeat(32) + "😊😊"); - expect(escapeHTML(" ".repeat(32) + "😊lo")).toBe(" ".repeat(32) + "😊lo"); - expect(escapeHTML(" ".repeat(32) + "lo😊")).toBe(" ".repeat(32) + "lo😊"); - }); -}); diff --git a/test/bun.js/esm/first.mjs b/test/bun.js/esm/first.mjs deleted file mode 100644 index 17021c623..000000000 --- a/test/bun.js/esm/first.mjs +++ /dev/null @@ -1,8 +0,0 @@ -import { end, start } from "./startEnd.mjs"; - -start("First"); - -import "./second.mjs"; -import "./third.mjs"; - -end("First"); diff --git a/test/bun.js/esm/second-child.mjs b/test/bun.js/esm/second-child.mjs deleted file mode 100644 index 5fb06ed45..000000000 --- a/test/bun.js/esm/second-child.mjs +++ /dev/null @@ -1,5 +0,0 @@ -import { start, end } from "./startEnd.mjs"; - -start("Second (nested import)"); - -end("Second (nested import)"); diff --git a/test/bun.js/esm/second.mjs b/test/bun.js/esm/second.mjs deleted file mode 100644 index 888eb11b9..000000000 --- a/test/bun.js/esm/second.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import { start, end } from "./startEnd.mjs"; - -start("Second"); - -import "./second-child.mjs"; - -end("Second"); diff --git a/test/bun.js/esm/startEnd.mjs b/test/bun.js/esm/startEnd.mjs deleted file mode 100644 index 8b5549802..000000000 --- a/test/bun.js/esm/startEnd.mjs +++ /dev/null @@ -1,6 +0,0 @@ -export function start(name) { - console.log(`[start] ${name}`); -} -export function end(name) { - console.log(`[end] ${name}`); -} diff --git a/test/bun.js/esm/third.mjs b/test/bun.js/esm/third.mjs deleted file mode 100644 index f5ba5cc84..000000000 --- a/test/bun.js/esm/third.mjs +++ /dev/null @@ -1,4 +0,0 @@ -import { end, start } from "./startEnd.mjs"; - -start("Third"); -end("Third"); diff --git a/test/bun.js/event-emitter.test.ts b/test/bun.js/event-emitter.test.ts deleted file mode 100644 index 2bb891778..000000000 --- a/test/bun.js/event-emitter.test.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { test, describe, expect, it } from "bun:test"; -import fs from "node:fs"; - -// this is also testing that imports with default and named imports in the same statement work -// our transpiler transform changes this to a var with import.meta.require -import EventEmitter, { getEventListeners, captureRejectionSymbol } from "node:events"; -import { heapStats } from "bun:jsc"; - -describe("EventEmitter", () => { - it("captureRejectionSymbol", () => { - expect(EventEmitter.captureRejectionSymbol).toBeDefined(); - expect(captureRejectionSymbol).toBeDefined(); - }); - test("getEventListeners", () => { - expect(getEventListeners(new EventEmitter(), "hey").length).toBe(0); - }); - test("EventEmitter constructor", () => { - var emitter = new EventEmitter(); - emitter.setMaxListeners(100); - expect(emitter.getMaxListeners()).toBe(100); - }); - - test("EventEmitter.removeAllListeners()", () => { - var emitter = new EventEmitter(); - var ran = false; - emitter.on("hey", () => { - ran = true; - }); - emitter.removeAllListeners(); - expect(emitter.listenerCount("hey")).toBe(0); - emitter.emit("hey"); - expect(ran).toBe(false); - emitter.on("hey", () => { - ran = true; - }); - emitter.emit("hey"); - expect(ran).toBe(true); - expect(emitter.listenerCount("hey")).toBe(1); - }); - - // These are also tests for the done() function in the test runner. - test("EventEmitter emit (different tick)", done => { - var emitter = new EventEmitter(); - emitter.on("wow", () => done()); - queueMicrotask(() => { - emitter.emit("wow"); - }); - }); - - // Unlike Jest, bun supports async and done - test("async EventEmitter emit (microtask)", async done => { - await 1; - var emitter = new EventEmitter(); - emitter.on("wow", () => done()); - emitter.emit("wow"); - }); - - test("async EventEmitter emit (microtask) after", async done => { - var emitter = new EventEmitter(); - emitter.on("wow", () => done()); - await 1; - emitter.emit("wow"); - }); - - test("EventEmitter emit (same tick)", done => { - var emitter = new EventEmitter(); - - emitter.on("wow", () => done()); - - emitter.emit("wow"); - }); - - test("EventEmitter emit (setTimeout task)", done => { - var emitter = new EventEmitter(); - emitter.on("wow", () => done()); - setTimeout(() => emitter.emit("wow"), 1); - }); -}); - -const waysOfCreating = [ - () => Object.create(EventEmitter.prototype), - () => new EventEmitter(), - () => new (class extends EventEmitter {})(), - () => { - class MyEmitter extends EventEmitter {} - return new MyEmitter(); - }, - () => { - var foo = {}; - Object.setPrototypeOf(foo, EventEmitter.prototype); - return foo; - }, - () => { - const FakeEmitter = function FakeEmitter() { - return EventEmitter.call(this); - }; - Object.setPrototypeOf(FakeEmitter.prototype, EventEmitter.prototype); - Object.setPrototypeOf(FakeEmitter, EventEmitter); - return new FakeEmitter(); - }, - () => { - const FakeEmitter = function FakeEmitter() { - EventEmitter.call(this); - }; - Object.assign(FakeEmitter.prototype, EventEmitter.prototype); - Object.assign(FakeEmitter, EventEmitter); - return new FakeEmitter(); - }, - () => { - var foo = {}; - Object.assign(foo, EventEmitter.prototype); - return foo; - }, -]; - -for (let create of waysOfCreating) { - it(`${create.toString().slice(10, 40).replaceAll("\n", "\\n").trim()} should work`, () => { - var myEmitter = create(); - var called = false; - myEmitter.once("event", function () { - called = true; - expect(this as any).toBe(myEmitter); - }); - var firstEvents = myEmitter._events; - expect(myEmitter.listenerCount("event")).toBe(1); - - expect(myEmitter.emit("event")).toBe(true); - expect(myEmitter.listenerCount("event")).toBe(0); - - expect(firstEvents).toBe(myEmitter._events); - expect(called).toBe(true); - }); -} - -test("EventEmitter.on", () => { - var myEmitter = new EventEmitter(); - expect(myEmitter.on("foo", () => {})).toBe(myEmitter); -}); - -test("EventEmitter.off", () => { - var myEmitter = new EventEmitter(); - expect(myEmitter.off("foo", () => {})).toBe(myEmitter); -}); - -// Internally, EventEmitter has a JSC::Weak with the thisValue of the listener -test("EventEmitter GCs", () => { - Bun.gc(true); - - const startCount = heapStats().objectTypeCounts["EventEmitter"] || 0; - (function () { - Bun.gc(true); - - function EventEmitterSubclass(this: any) { - EventEmitter.call(this); - } - - Object.setPrototypeOf(EventEmitterSubclass.prototype, EventEmitter.prototype); - Object.setPrototypeOf(EventEmitterSubclass, EventEmitter); - - var myEmitter = new EventEmitterSubclass(); - myEmitter.on("foo", () => {}); - myEmitter.emit("foo"); - Bun.gc(true); - })(); - Bun.gc(true); - - const endCount = heapStats().objectTypeCounts["EventEmitter"] || 0; - expect(endCount).toBe(startCount); -}); diff --git a/test/bun.js/exit-code-0.js b/test/bun.js/exit-code-0.js deleted file mode 100644 index dcbbff6c9..000000000 --- a/test/bun.js/exit-code-0.js +++ /dev/null @@ -1 +0,0 @@ -process.exit(0); diff --git a/test/bun.js/exit-code-1.js b/test/bun.js/exit-code-1.js deleted file mode 100644 index 6cee2e1e7..000000000 --- a/test/bun.js/exit-code-1.js +++ /dev/null @@ -1 +0,0 @@ -process.exit(1); diff --git a/test/bun.js/exit-code-await-throw-1.js b/test/bun.js/exit-code-await-throw-1.js deleted file mode 100644 index 6b8c42eab..000000000 --- a/test/bun.js/exit-code-await-throw-1.js +++ /dev/null @@ -1,3 +0,0 @@ -await (async function () { - throw 42; -})(); diff --git a/test/bun.js/exit-code-unhandled-throw.js b/test/bun.js/exit-code-unhandled-throw.js deleted file mode 100644 index e8f5ca4cb..000000000 --- a/test/bun.js/exit-code-unhandled-throw.js +++ /dev/null @@ -1,3 +0,0 @@ -(async function () { - throw 42; -})(); diff --git a/test/bun.js/exit-code.test.ts b/test/bun.js/exit-code.test.ts deleted file mode 100644 index 5f1aab904..000000000 --- a/test/bun.js/exit-code.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { describe, expect, it, test } from "bun:test"; -import { bunExe } from "bunExe"; -import { spawnSync } from "bun"; - -it("process.exit(1) works", () => { - const { exitCode } = spawnSync([bunExe(), import.meta.dir + "/exit-code-1.js"]); - expect(exitCode).toBe(1); -}); - -it("await on a thrown value reports exit code 1", () => { - const { exitCode } = spawnSync([bunExe(), import.meta.dir + "/exit-code-await-throw-1.js"]); - expect(exitCode).toBe(1); -}); - -it("unhandled promise rejection reports exit code 1", () => { - const { exitCode } = spawnSync([bunExe(), import.meta.dir + "/exit-code-unhandled-throw.js"]); - expect(exitCode).toBe(1); -}); - -it("process.exit(0) works", () => { - const { exitCode } = spawnSync([bunExe(), import.meta.dir + "/exit-code-0.js"]); - expect(exitCode).toBe(0); -}); diff --git a/test/bun.js/exit.js b/test/bun.js/exit.js deleted file mode 100644 index fb28b1fb4..000000000 --- a/test/bun.js/exit.js +++ /dev/null @@ -1,2 +0,0 @@ -process.exit(0); -throw new Error("Well that didn't work"); diff --git a/test/bun.js/fetch-gzip.test.ts b/test/bun.js/fetch-gzip.test.ts deleted file mode 100644 index eee841375..000000000 --- a/test/bun.js/fetch-gzip.test.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { concatArrayBuffers } from "bun"; -import { it, describe, expect } from "bun:test"; -import fs from "fs"; -import { gc, gcTick } from "./gc"; - -it("fetch() with a buffered gzip response works (one chunk)", async () => { - var server = Bun.serve({ - port: 6025, - - async fetch(req) { - gcTick(true); - return new Response(require("fs").readFileSync(import.meta.dir + "/fixture.html.gz"), { - headers: { - "Content-Encoding": "gzip", - "Content-Type": "text/html; charset=utf-8", - }, - }); - }, - }); - gcTick(true); - - const res = await fetch(`http://${server.hostname}:${server.port}`, { verbose: true }); - gcTick(true); - const arrayBuffer = await res.arrayBuffer(); - const clone = new Buffer(arrayBuffer); - gcTick(true); - await (async function () { - const second = new Buffer(await Bun.file(import.meta.dir + "/fixture.html").arrayBuffer()); - gcTick(true); - expect(second.equals(clone)).toBe(true); - })(); - gcTick(true); - server.stop(); -}); - -it("fetch() with a redirect that returns a buffered gzip response works (one chunk)", async () => { - var server = Bun.serve({ - port: 6020, - - async fetch(req) { - if (req.url.endsWith("/redirect")) - return new Response(await Bun.file(import.meta.dir + "/fixture.html.gz").arrayBuffer(), { - headers: { - "Content-Encoding": "gzip", - "Content-Type": "text/html; charset=utf-8", - }, - }); - - return Response.redirect("/redirect"); - }, - }); - - const res = await fetch(`http://${server.hostname}:${server.port}/hey`, { verbose: true }); - const arrayBuffer = await res.arrayBuffer(); - expect( - new Buffer(arrayBuffer).equals(new Buffer(await Bun.file(import.meta.dir + "/fixture.html").arrayBuffer())), - ).toBe(true); - server.stop(); -}); - -it("fetch() with a protocol-relative redirect that returns a buffered gzip response works (one chunk)", async () => { - const server = Bun.serve({ - port: 5018, - - async fetch(req, server) { - if (req.url.endsWith("/redirect")) - return new Response(await Bun.file(import.meta.dir + "/fixture.html.gz").arrayBuffer(), { - headers: { - "Content-Encoding": "gzip", - "Content-Type": "text/html; charset=utf-8", - }, - }); - - return Response.redirect(`://${server.hostname}:${server.port}/redirect`); - }, - }); - - const res = await fetch(`http://${server.hostname}:${server.port}/hey`, { verbose: true }); - expect(res.url).toBe(`http://${server.hostname}:${server.port}/redirect`); - expect(res.redirected).toBe(true); - expect(res.status).toBe(200); - const arrayBuffer = await res.arrayBuffer(); - expect( - new Buffer(arrayBuffer).equals(new Buffer(await Bun.file(import.meta.dir + "/fixture.html").arrayBuffer())), - ).toBe(true); - - server.stop(); -}); - -it("fetch() with a gzip response works (one chunk, streamed, with a delay", async () => { - var server = Bun.serve({ - port: 6081, - - fetch(req) { - return new Response( - new ReadableStream({ - type: "direct", - async pull(controller) { - await 2; - - const buffer = await Bun.file(import.meta.dir + "/fixture.html.gz").arrayBuffer(); - controller.write(buffer); - controller.close(); - }, - }), - { - headers: { - "Content-Encoding": "gzip", - "Content-Type": "text/html; charset=utf-8", - "Content-Length": "1", - }, - }, - ); - }, - }); - - const res = await fetch(`http://${server.hostname}:${server.port}`, {}); - const arrayBuffer = await res.arrayBuffer(); - expect( - new Buffer(arrayBuffer).equals(new Buffer(await Bun.file(import.meta.dir + "/fixture.html").arrayBuffer())), - ).toBe(true); - server.stop(); -}); - -it("fetch() with a gzip response works (multiple chunks, TCP server", async done => { - const compressed = await Bun.file(import.meta.dir + "/fixture.html.gz").arrayBuffer(); - var socketToClose; - const server = Bun.listen({ - port: 4024, - hostname: "0.0.0.0", - socket: { - async open(socket) { - socketToClose = socket; - - var corked: any[] = []; - var cork = true; - async function write(chunk) { - await new Promise((resolve, reject) => { - if (cork) { - corked.push(chunk); - } - - if (!cork && corked.length) { - socket.write(corked.join("")); - corked.length = 0; - } - - if (!cork) { - socket.write(chunk); - } - - resolve(); - }); - } - await write("HTTP/1.1 200 OK\r\n"); - await write("Content-Encoding: gzip\r\n"); - await write("Content-Type: text/html; charset=utf-8\r\n"); - await write("Content-Length: " + compressed.byteLength + "\r\n"); - await write("X-WTF: " + "lol".repeat(1000) + "\r\n"); - await write("\r\n"); - for (var i = 100; i < compressed.byteLength; i += 100) { - cork = false; - await write(compressed.slice(i - 100, i)); - } - await write(compressed.slice(i - 100)); - socket.flush(); - }, - drain(socket) {}, - }, - }); - await 1; - - const res = await fetch(`http://${server.hostname}:${server.port}`, {}); - const arrayBuffer = await res.arrayBuffer(); - expect( - new Buffer(arrayBuffer).equals(new Buffer(await Bun.file(import.meta.dir + "/fixture.html").arrayBuffer())), - ).toBe(true); - socketToClose.end(); - server.stop(); - done(); -}); diff --git a/test/bun.js/fetch.js.txt b/test/bun.js/fetch.js.txt deleted file mode 100644 index 5a9b52fcf..000000000 --- a/test/bun.js/fetch.js.txt +++ /dev/null @@ -1,46 +0,0 @@ - - - - Example Domain - - - - - - - - -
-

Example Domain

-

This domain is for use in illustrative examples in documents. You may use this - domain in literature without prior coordination or asking for permission.

-

More information...

-
- - diff --git a/test/bun.js/fetch.test.ts b/test/bun.js/fetch.test.ts deleted file mode 100644 index f5f264dd7..000000000 --- a/test/bun.js/fetch.test.ts +++ /dev/null @@ -1,935 +0,0 @@ -import { serve, sleep } from "bun"; -import { afterAll, afterEach, beforeAll, describe, expect, it, beforeEach } from "bun:test"; -import { chmodSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "fs"; -import { mkfifo } from "mkfifo"; -import { tmpdir } from "os"; -import { join } from "path"; -import { gc, withoutAggressiveGC } from "./gc"; - -const tmp_dir = mkdtempSync(join(realpathSync(tmpdir()), "fetch.test")); - -const fixture = readFileSync(join(import.meta.dir, "fetch.js.txt"), "utf8"); - -let server; -function startServer({ fetch, ...options }) { - server = serve({ - ...options, - fetch, - port: 0, - }); -} - -afterEach(() => { - server?.stop?.(true); -}); - -afterAll(() => { - rmSync(tmp_dir, { force: true, recursive: true }); -}); - -const payload = new Uint8Array(1024 * 1024 * 2); -crypto.getRandomValues(payload); - -describe("AbortSignal", () => { - beforeEach(() => { - startServer({ - async fetch(request) { - if (request.url.endsWith("/nodelay")) { - return new Response("Hello"); - } - if (request.url.endsWith("/stream")) { - const reader = request.body.getReader(); - const body = new ReadableStream({ - async pull(controller) { - if (!reader) controller.close(); - const { done, value } = await reader.read(); - // When no more data needs to be consumed, close the stream - if (done) { - controller.close(); - return; - } - // Enqueue the next data chunk into our target stream - controller.enqueue(value); - }, - }); - return new Response(body); - } - if (request.method.toUpperCase() === "POST") { - const body = await request.text(); - return new Response(body); - } - await sleep(15); - return new Response("Hello"); - }, - }); - }); - afterEach(() => { - server?.stop?.(true); - }); - - it("AbortError", async () => { - const controller = new AbortController(); - const signal = controller.signal; - - expect(async () => { - async function manualAbort() { - await sleep(1); - controller.abort(); - } - await Promise.all([ - fetch(`http://127.0.0.1:${server.port}`, { signal: signal }).then(res => res.text()), - manualAbort(), - ]); - }).toThrow(new DOMException("The operation was aborted.")); - }); - - it("AbortAfterFinish", async () => { - const controller = new AbortController(); - const signal = controller.signal; - - await fetch(`http://127.0.0.1:${server.port}/nodelay`, { signal: signal }).then(async res => - expect(await res.text()).toBe("Hello"), - ); - controller.abort(); - }); - - it("AbortErrorWithReason", async () => { - const controller = new AbortController(); - const signal = controller.signal; - - expect(async () => { - async function manualAbort() { - await sleep(10); - controller.abort(new Error("My Reason")); - } - await Promise.all([ - fetch(`http://127.0.0.1:${server.port}`, { signal: signal }).then(res => res.text()), - manualAbort(), - ]); - }).toThrow("My Reason"); - }); - - it("AbortErrorEventListener", async () => { - const controller = new AbortController(); - const signal = controller.signal; - signal.addEventListener("abort", ev => { - const target = ev.currentTarget; - expect(target).toBeDefined(); - expect(target.aborted).toBe(true); - expect(target.reason).toBeDefined(); - expect(target.reason.name).toBe("AbortError"); - }); - - expect(async () => { - async function manualAbort() { - await sleep(10); - controller.abort(); - } - await Promise.all([ - fetch(`http://127.0.0.1:${server.port}`, { signal: signal }).then(res => res.text()), - manualAbort(), - ]); - }).toThrow(new DOMException("The operation was aborted.")); - }); - - it("AbortErrorWhileUploading", async () => { - const controller = new AbortController(); - - expect(async () => { - await fetch(`http://localhost:${server.port}`, { - method: "POST", - body: new ReadableStream({ - pull(event_controller) { - event_controller.enqueue(new Uint8Array([1, 2, 3, 4])); - //this will abort immediately should abort before connected - controller.abort(); - }, - }), - signal: controller.signal, - }); - }).toThrow(new DOMException("The operation was aborted.")); - }); - - it("TimeoutError", async () => { - const signal = AbortSignal.timeout(10); - - try { - await fetch(`http://127.0.0.1:${server.port}`, { signal: signal }).then(res => res.text()); - expect(() => {}).toThrow(); - } catch (ex: any) { - expect(ex.name).toBe("TimeoutError"); - } - }); - - it("Request", async () => { - const controller = new AbortController(); - const signal = controller.signal; - async function manualAbort() { - await sleep(10); - controller.abort(); - } - - try { - const request = new Request(`http://127.0.0.1:${server.port}`, { signal }); - await Promise.all([fetch(request).then(res => res.text()), manualAbort()]); - expect(() => {}).toThrow(); - } catch (ex: any) { - expect(ex.name).toBe("AbortError"); - } - }); -}); - -describe("Headers", () => { - it(".toJSON", () => { - const headers = new Headers({ - "content-length": "123", - "content-type": "text/plain", - "x-another-custom-header": "Hello World", - "x-custom-header": "Hello World", - }); - expect(JSON.stringify(headers.toJSON(), null, 2)).toBe( - JSON.stringify(Object.fromEntries(headers.entries()), null, 2), - ); - }); - - it(".getSetCookie() with object", () => { - const headers = new Headers({ - "content-length": "123", - "content-type": "text/plain", - "x-another-custom-header": "Hello World", - "x-custom-header": "Hello World", - "Set-Cookie": "foo=bar; Path=/; HttpOnly", - }); - expect(headers.count).toBe(5); - expect(headers.getAll("set-cookie")).toEqual(["foo=bar; Path=/; HttpOnly"]); - }); - - it(".getSetCookie() with array", () => { - const headers = new Headers([ - ["content-length", "123"], - ["content-type", "text/plain"], - ["x-another-custom-header", "Hello World"], - ["x-custom-header", "Hello World"], - ["Set-Cookie", "foo=bar; Path=/; HttpOnly"], - ["Set-Cookie", "foo2=bar2; Path=/; HttpOnly"], - ]); - expect(headers.count).toBe(6); - expect(headers.getAll("set-cookie")).toEqual(["foo=bar; Path=/; HttpOnly", "foo2=bar2; Path=/; HttpOnly"]); - }); - - it("Set-Cookies init", () => { - const headers = new Headers([ - ["Set-Cookie", "foo=bar"], - ["Set-Cookie", "bar=baz"], - ["X-bun", "abc"], - ["X-bun", "def"], - ]); - const actual = [...headers]; - expect(actual).toEqual([ - ["set-cookie", "foo=bar"], - ["set-cookie", "bar=baz"], - ["x-bun", "abc, def"], - ]); - expect([...headers.values()]).toEqual(["foo=bar", "bar=baz", "abc, def"]); - }); - - it("Headers append multiple", () => { - const headers = new Headers([ - ["Set-Cookie", "foo=bar"], - ["X-bun", "foo"], - ]); - headers.append("Set-Cookie", "bar=baz"); - headers.append("x-bun", "bar"); - const actual = [...headers]; - - // we do not preserve the order - // which is kind of bad - expect(actual).toEqual([ - ["set-cookie", "foo=bar"], - ["set-cookie", "bar=baz"], - ["x-bun", "foo, bar"], - ]); - }); - - it("append duplicate set cookie key", () => { - const headers = new Headers([["Set-Cookie", "foo=bar"]]); - headers.append("set-Cookie", "foo=baz"); - headers.append("Set-cookie", "baz=bar"); - const actual = [...headers]; - expect(actual).toEqual([ - ["set-cookie", "foo=baz"], - ["set-cookie", "baz=bar"], - ]); - }); - - it("set duplicate cookie key", () => { - const headers = new Headers([["Set-Cookie", "foo=bar"]]); - headers.set("set-Cookie", "foo=baz"); - headers.set("set-cookie", "bar=qat"); - const actual = [...headers]; - expect(actual).toEqual([ - ["set-cookie", "foo=baz"], - ["set-cookie", "bar=qat"], - ]); - }); -}); - -describe("fetch", () => { - const urls = [ - "https://example.com", - "http://example.com", - new URL("https://example.com"), - new Request({ url: "https://example.com" }), - { toString: () => "https://example.com" }, - ]; - for (let url of urls) { - gc(); - let name; - if (url instanceof URL) { - name = "URL: " + url; - } else if (url instanceof Request) { - name = "Request: " + url.url; - } else if (url.hasOwnProperty("toString")) { - name = "Object: " + url.toString(); - } else { - name = url; - } - it(name, async () => { - gc(); - const response = await fetch(url, { verbose: true }); - gc(); - const text = await response.text(); - gc(); - expect(fixture).toBe(text); - }); - } - - it('redirect: "manual"', async () => { - startServer({ - fetch(req) { - return new Response(null, { - status: 302, - headers: { - Location: "https://example.com", - }, - }); - }, - }); - const response = await fetch(`http://${server.hostname}:${server.port}`, { - redirect: "manual", - }); - expect(response.status).toBe(302); - expect(response.headers.get("location")).toBe("https://example.com"); - expect(response.redirected).toBe(true); - }); - - it('redirect: "follow"', async () => { - startServer({ - fetch(req) { - return new Response(null, { - status: 302, - headers: { - Location: "https://example.com", - }, - }); - }, - }); - const response = await fetch(`http://${server.hostname}:${server.port}`, { - redirect: "follow", - }); - expect(response.status).toBe(200); - expect(response.headers.get("location")).toBe(null); - expect(response.redirected).toBe(true); - }); - - it("provide body", async () => { - startServer({ - fetch(req) { - return new Response(req.body); - }, - host: "localhost", - }); - - // POST with body - const url = `http://${server.hostname}:${server.port}`; - const response = await fetch(url, { method: "POST", body: "buntastic" }); - expect(response.status).toBe(200); - expect(await response.text()).toBe("buntastic"); - }); - - ["GET", "HEAD", "OPTIONS"].forEach(method => - it(`fail on ${method} with body`, async () => { - const url = `http://${server.hostname}:${server.port}`; - expect(async () => { - await fetch(url, { body: "buntastic" }); - }).toThrow("fetch() request with GET/HEAD/OPTIONS method cannot have body."); - }), - ); -}); - -it("simultaneous HTTPS fetch", async () => { - const urls = ["https://example.com", "https://www.example.com"]; - for (let batch = 0; batch < 4; batch++) { - const promises = new Array(20); - for (let i = 0; i < 20; i++) { - promises[i] = fetch(urls[i % 2]); - } - const result = await Promise.all(promises); - expect(result.length).toBe(20); - for (let i = 0; i < 20; i++) { - expect(result[i].status).toBe(200); - expect(await result[i].text()).toBe(fixture); - } - } -}); - -it("website with tlsextname", async () => { - // irony - await fetch("https://bun.sh", { method: "HEAD" }); -}); - -function testBlobInterface(blobbyConstructor, hasBlobFn?) { - for (let withGC of [false, true]) { - for (let jsonObject of [ - { hello: true }, - { - hello: "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳", - }, - ]) { - it(`${jsonObject.hello === true ? "latin1" : "utf16"} json${withGC ? " (with gc) " : ""}`, async () => { - if (withGC) gc(); - var response = blobbyConstructor(JSON.stringify(jsonObject)); - if (withGC) gc(); - expect(JSON.stringify(await response.json())).toBe(JSON.stringify(jsonObject)); - if (withGC) gc(); - }); - - it(`${jsonObject.hello === true ? "latin1" : "utf16"} arrayBuffer -> json${ - withGC ? " (with gc) " : "" - }`, async () => { - if (withGC) gc(); - var response = blobbyConstructor(new TextEncoder().encode(JSON.stringify(jsonObject))); - if (withGC) gc(); - expect(JSON.stringify(await response.json())).toBe(JSON.stringify(jsonObject)); - if (withGC) gc(); - }); - - it(`${jsonObject.hello === true ? "latin1" : "utf16"} arrayBuffer -> invalid json${ - withGC ? " (with gc) " : "" - }`, async () => { - if (withGC) gc(); - var response = blobbyConstructor( - new TextEncoder().encode(JSON.stringify(jsonObject) + " NOW WE ARE INVALID JSON"), - ); - if (withGC) gc(); - var failed = false; - try { - await response.json(); - } catch (e) { - failed = true; - } - expect(failed).toBe(true); - if (withGC) gc(); - }); - - it(`${jsonObject.hello === true ? "latin1" : "utf16"} text${withGC ? " (with gc) " : ""}`, async () => { - if (withGC) gc(); - var response = blobbyConstructor(JSON.stringify(jsonObject)); - if (withGC) gc(); - expect(await response.text()).toBe(JSON.stringify(jsonObject)); - if (withGC) gc(); - }); - - it(`${jsonObject.hello === true ? "latin1" : "utf16"} arrayBuffer -> text${ - withGC ? " (with gc) " : "" - }`, async () => { - if (withGC) gc(); - var response = blobbyConstructor(new TextEncoder().encode(JSON.stringify(jsonObject))); - if (withGC) gc(); - expect(await response.text()).toBe(JSON.stringify(jsonObject)); - if (withGC) gc(); - }); - - it(`${jsonObject.hello === true ? "latin1" : "utf16"} arrayBuffer${withGC ? " (with gc) " : ""}`, async () => { - if (withGC) gc(); - - var response = blobbyConstructor(JSON.stringify(jsonObject)); - if (withGC) gc(); - - const bytes = new TextEncoder().encode(JSON.stringify(jsonObject)); - if (withGC) gc(); - - const compare = new Uint8Array(await response.arrayBuffer()); - if (withGC) gc(); - - withoutAggressiveGC(() => { - for (let i = 0; i < compare.length; i++) { - if (withGC) gc(); - - expect(compare[i]).toBe(bytes[i]); - if (withGC) gc(); - } - }); - if (withGC) gc(); - }); - - it(`${jsonObject.hello === true ? "latin1" : "utf16"} arrayBuffer -> arrayBuffer${ - withGC ? " (with gc) " : "" - }`, async () => { - if (withGC) gc(); - - var response = blobbyConstructor(new TextEncoder().encode(JSON.stringify(jsonObject))); - if (withGC) gc(); - - const bytes = new TextEncoder().encode(JSON.stringify(jsonObject)); - if (withGC) gc(); - - const compare = new Uint8Array(await response.arrayBuffer()); - if (withGC) gc(); - - withoutAggressiveGC(() => { - for (let i = 0; i < compare.length; i++) { - if (withGC) gc(); - - expect(compare[i]).toBe(bytes[i]); - if (withGC) gc(); - } - }); - if (withGC) gc(); - }); - - hasBlobFn && - it(`${jsonObject.hello === true ? "latin1" : "utf16"} blob${withGC ? " (with gc) " : ""}`, async () => { - if (withGC) gc(); - const text = JSON.stringify(jsonObject); - var response = blobbyConstructor(text); - if (withGC) gc(); - const size = new TextEncoder().encode(text).byteLength; - if (withGC) gc(); - const blobed = await response.blob(); - if (withGC) gc(); - expect(blobed instanceof Blob).toBe(true); - if (withGC) gc(); - expect(blobed.size).toBe(size); - if (withGC) gc(); - blobed.type = ""; - if (withGC) gc(); - expect(blobed.type).toBe(""); - if (withGC) gc(); - blobed.type = "application/json"; - if (withGC) gc(); - expect(blobed.type).toBe("application/json"); - if (withGC) gc(); - const out = await blobed.text(); - expect(out).toBe(text); - if (withGC) gc(); - await new Promise(resolve => setTimeout(resolve, 1)); - if (withGC) gc(); - expect(out).toBe(text); - const first = await blobed.arrayBuffer(); - const initial = first[0]; - first[0] = 254; - const second = await blobed.arrayBuffer(); - expect(second[0]).toBe(initial); - expect(first[0]).toBe(254); - }); - } - } -} - -describe("Bun.file", () => { - let count = 0; - testBlobInterface(data => { - const blob = new Blob([data]); - const buffer = Bun.peek(blob.arrayBuffer()); - const path = join(tmp_dir, `tmp-${count++}.bytes`); - writeFileSync(path, buffer); - const file = Bun.file(path); - expect(blob.size).toBe(file.size); - return file; - }); - - it("size is Infinity on a fifo", () => { - const path = join(tmp_dir, "test-fifo"); - mkfifo(path); - const { size } = Bun.file(path); - expect(size).toBe(Infinity); - }); - - function forEachMethod(fn, skip?) { - const method = ["arrayBuffer", "text", "json"]; - for (const m of method) { - (skip ? it.skip : it)(m, fn(m)); - } - } - - describe("bad permissions throws", () => { - const path = join(tmp_dir, "my-new-file"); - beforeAll(async () => { - await Bun.write(path, "hey"); - chmodSync(path, 0o000); - }); - - forEachMethod( - m => () => { - const file = Bun.file(path); - expect(async () => await file[m]()).toThrow("Permission denied"); - }, - () => { - try { - readFileSync(path); - } catch { - return false; - } - return true; - }, - ); - }); - - describe("non-existent file throws", () => { - const path = join(tmp_dir, "does-not-exist"); - - forEachMethod(m => async () => { - const file = Bun.file(path); - expect(async () => await file[m]()).toThrow("No such file or directory"); - }); - }); -}); - -describe("Blob", () => { - testBlobInterface(data => new Blob([data])); - - var blobConstructorValues = [ - ["123", "456"], - ["123", 456], - ["123", "456", "789"], - ["123", 456, 789], - [1, 2, 3, 4, 5, 6, 7, 8, 9], - [Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 9])], - [Uint8Array.from([1, 2, 3, 4]), "5678", 9], - [new Blob([Uint8Array.from([1, 2, 3, 4])]), "5678", 9], - [ - new Blob([ - new TextEncoder().encode( - "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳", - ), - ]), - ], - [ - new TextEncoder().encode( - "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳", - ), - ], - ]; - - var expected = [ - "123456", - "123456", - "123456789", - "123456789", - "123456789", - "\x01\x02\x03\x04\x05\x06\x07\t", - "\x01\x02\x03\x0456789", - "\x01\x02\x03\x0456789", - "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳", - "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳", - ]; - - it(`blobConstructorValues`, async () => { - for (let i = 0; i < blobConstructorValues.length; i++) { - var response = new Blob(blobConstructorValues[i]); - const res = await response.text(); - if (res !== expected[i]) { - throw new Error( - `Failed: ${expected[i].split("").map(a => a.charCodeAt(0))}, received: ${res - .split("") - .map(a => a.charCodeAt(0))}`, - ); - } - - expect(res).toBe(expected[i]); - } - }); - - for (let withGC of [false, true]) { - it(`Blob.slice() ${withGC ? " with gc" : ""}`, async () => { - var parts = ["hello", " ", "world"]; - if (withGC) gc(); - var str = parts.join(""); - if (withGC) gc(); - var combined = new Blob(parts); - if (withGC) gc(); - for (let part of parts) { - if (withGC) gc(); - expect(await combined.slice(str.indexOf(part), str.indexOf(part) + part.length).text()).toBe(part); - if (withGC) gc(); - } - if (withGC) gc(); - for (let part of parts) { - if (withGC) gc(); - expect(await combined.slice(str.indexOf(part), str.indexOf(part) + part.length).text()).toBe(part); - if (withGC) gc(); - } - }); - } -}); - -{ - const sample = new TextEncoder().encode("Hello World!"); - const typedArrays = [ - Uint8Array, - Uint8ClampedArray, - Int8Array, - Uint16Array, - Int16Array, - Uint32Array, - Int32Array, - Float32Array, - Float64Array, - ]; - const Constructors = [Blob, Response, Request]; - - for (let withGC of [false, true]) { - for (let TypedArray of typedArrays) { - for (let Constructor of Constructors) { - it(`${Constructor.name} arrayBuffer() with ${TypedArray.name}${withGC ? " with gc" : ""}`, async () => { - const data = new TypedArray(sample); - if (withGC) gc(); - const input = Constructor === Blob ? [data] : Constructor === Request ? { body: data } : data; - if (withGC) gc(); - const blob = new Constructor(input); - if (withGC) gc(); - const out = await blob.arrayBuffer(); - if (withGC) gc(); - expect(out instanceof ArrayBuffer).toBe(true); - if (withGC) gc(); - expect(out.byteLength).toBe(data.byteLength); - if (withGC) gc(); - }); - } - } - } -} - -describe("Response", () => { - describe("Response.json", () => { - it("works", async () => { - const inputs = ["hellooo", [[123], 456, 789], { hello: "world" }, { ok: "😉 😌 😍 🥰 😘 " }]; - for (let input of inputs) { - const output = JSON.stringify(input); - expect(await Response.json(input).text()).toBe(output); - } - // JSON.stringify() returns undefined - expect(await Response.json().text()).toBe(""); - // JSON.stringify("") returns '""' - expect(await Response.json("").text()).toBe('""'); - }); - it("sets the content-type header", () => { - let response = Response.json("hello"); - expect(response.type).toBe("basic"); - expect(response.headers.get("content-type")).toBe("application/json;charset=utf-8"); - expect(response.status).toBe(200); - }); - it("supports number status code", () => { - let response = Response.json("hello", 407); - expect(response.type).toBe("basic"); - expect(response.headers.get("content-type")).toBe("application/json;charset=utf-8"); - expect(response.status).toBe(407); - }); - - it("supports headers", () => { - var response = Response.json("hello", { - headers: { - "content-type": "potato", - "x-hello": "world", - }, - status: 408, - }); - - expect(response.headers.get("x-hello")).toBe("world"); - expect(response.status).toBe(408); - }); - }); - describe("Response.redirect", () => { - it("works", () => { - const inputs = [ - "http://example.com", - "http://example.com/", - "http://example.com/hello", - "http://example.com/hello/", - "http://example.com/hello/world", - "http://example.com/hello/world/", - ]; - for (let input of inputs) { - expect(Response.redirect(input).headers.get("Location")).toBe(input); - } - }); - - it("supports headers", () => { - var response = Response.redirect("https://example.com", { - headers: { - "content-type": "potato", - "x-hello": "world", - Location: "https://wrong.com", - }, - status: 408, - }); - expect(response.headers.get("x-hello")).toBe("world"); - expect(response.headers.get("Location")).toBe("https://example.com"); - expect(response.status).toBe(302); - expect(response.type).toBe("basic"); - expect(response.ok).toBe(false); - }); - }); - describe("Response.error", () => { - it("works", () => { - expect(Response.error().type).toBe("error"); - expect(Response.error().ok).toBe(false); - expect(Response.error().status).toBe(0); - }); - }); - it("clone", async () => { - gc(); - var body = new Response("
hello
", { - headers: { - "content-type": "text/html; charset=utf-8", - }, - }); - gc(); - var clone = body.clone(); - gc(); - body.headers.set("content-type", "text/plain"); - gc(); - expect(clone.headers.get("content-type")).toBe("text/html; charset=utf-8"); - gc(); - expect(body.headers.get("content-type")).toBe("text/plain"); - gc(); - expect(await clone.text()).toBe("
hello
"); - gc(); - }); - it("invalid json", async () => { - gc(); - var body = new Response("
hello
", { - headers: { - "content-type": "text/html; charset=utf-8", - }, - }); - try { - await body.json(); - expect(false).toBe(true); - } catch (exception) { - expect(exception instanceof SyntaxError).toBe(true); - } - }); - - testBlobInterface(data => new Response(data), true); -}); - -describe("Request", () => { - it("clone", async () => { - gc(); - var body = new Request("https://hello.com", { - headers: { - "content-type": "text/html; charset=utf-8", - }, - body: "
hello
", - }); - gc(); - expect(body.signal).toBeDefined(); - gc(); - expect(body.headers.get("content-type")).toBe("text/html; charset=utf-8"); - gc(); - var clone = body.clone(); - gc(); - expect(clone.signal).toBeDefined(); - gc(); - body.headers.set("content-type", "text/plain"); - gc(); - expect(clone.headers.get("content-type")).toBe("text/html; charset=utf-8"); - gc(); - expect(body.headers.get("content-type")).toBe("text/plain"); - gc(); - expect(await clone.text()).toBe("
hello
"); - }); - - it("signal", async () => { - gc(); - const controller = new AbortController(); - const req = new Request("https://hello.com", { signal: controller.signal }); - expect(req.signal.aborted).toBe(false); - gc(); - controller.abort(); - gc(); - expect(req.signal.aborted).toBe(true); - }); - - it("cloned signal", async () => { - gc(); - const controller = new AbortController(); - const req = new Request("https://hello.com", { signal: controller.signal }); - expect(req.signal.aborted).toBe(false); - gc(); - controller.abort(); - gc(); - expect(req.signal.aborted).toBe(true); - gc(); - const cloned = req.clone(); - expect(cloned.signal.aborted).toBe(true); - }); - - testBlobInterface(data => new Request("https://hello.com", { body: data }), true); -}); - -describe("Headers", () => { - it("writes", async () => { - var headers = new Headers({ - "content-type": "text/html; charset=utf-8", - }); - gc(); - expect(headers.get("content-type")).toBe("text/html; charset=utf-8"); - gc(); - headers.delete("content-type"); - gc(); - expect(headers.get("content-type")).toBe(null); - gc(); - headers.append("content-type", "text/plain"); - gc(); - expect(headers.get("content-type")).toBe("text/plain"); - gc(); - headers.append("content-type", "text/plain"); - gc(); - expect(headers.get("content-type")).toBe("text/plain, text/plain"); - gc(); - headers.set("content-type", "text/html; charset=utf-8"); - gc(); - expect(headers.get("content-type")).toBe("text/html; charset=utf-8"); - - headers.delete("content-type"); - gc(); - expect(headers.get("content-type")).toBe(null); - gc(); - }); -}); - -it("body nullable", async () => { - gc(); - { - const req = new Request("https://hello.com", { body: null }); - expect(req.body).toBeNull(); - } - gc(); - { - const req = new Request("https://hello.com", { body: undefined }); - expect(req.body).toBeNull(); - } - gc(); - { - const req = new Request("https://hello.com"); - expect(req.body).toBeNull(); - } - gc(); - { - const req = new Request("https://hello.com", { body: "" }); - expect(req.body).not.toBeNull(); - } -}); diff --git a/test/bun.js/fetch_headers.test.js b/test/bun.js/fetch_headers.test.js deleted file mode 100644 index cd2786c08..000000000 --- a/test/bun.js/fetch_headers.test.js +++ /dev/null @@ -1,66 +0,0 @@ -import { describe, it, expect, beforeAll, afterAll } from "bun:test"; -const port = 3009; -const url = `http://localhost:${port}`; -let server; - -describe("Headers", async () => { - // Start up a single server and reuse it between tests - beforeAll(() => { - server = Bun.serve({ - fetch(req) { - const hdr = req.headers.get("x-test"); - return new Response(hdr); - }, - port: port, - }); - }); - afterAll(() => { - server.stop(); - }); - - it("Headers should work", async () => { - expect(await fetchContent({ "x-test": "header 1" })).toBe("header 1"); - }); - - it("Header names must be valid", async () => { - expect(() => fetch(url, { headers: { "a\tb:c": "foo" } })).toThrow("Invalid header name: 'a\tb:c'"); - expect(() => fetch(url, { headers: { "❤️": "foo" } })).toThrow("Invalid header name: '❤️'"); - }); - - it("Header values must be valid", async () => { - expect(() => fetch(url, { headers: { "x-test": "\0" } })).toThrow("Header 'x-test' has invalid value: '\0'"); - expect(() => fetch(url, { headers: { "x-test": "❤️" } })).toThrow("Header 'x-test' has invalid value: '❤️'"); - }); - - it("repro 1602", async () => { - const origString = "😂1234".slice(3); - - var encoder = new TextEncoder(); - var decoder = new TextDecoder(); - const roundTripString = decoder.decode(encoder.encode(origString)); - - expect(roundTripString).toBe(origString); - - // This one will pass - expect(await fetchContent({ "x-test": roundTripString })).toBe(roundTripString); - // This would hang - expect(await fetchContent({ "x-test": origString })).toBe(origString); - }); - - describe("toJSON()", () => { - it("should provide lowercase header names", () => { - const headers1 = new Headers({ "X-Test": "yep", "Content-Type": "application/json" }); - expect(headers1.toJSON()).toEqual({ "x-test": "yep", "content-type": "application/json" }); - - const headers2 = new Headers(); - headers2.append("X-Test", "yep"); - headers2.append("Content-Type", "application/json"); - expect(headers2.toJSON()).toEqual({ "x-test": "yep", "content-type": "application/json" }); - }); - }); -}); - -async function fetchContent(headers) { - const res = await fetch(url, { headers: headers }, { verbose: true }); - return await res.text(); -} diff --git a/test/bun.js/ffi-test.c b/test/bun.js/ffi-test.c deleted file mode 100644 index 0fe227385..000000000 --- a/test/bun.js/ffi-test.c +++ /dev/null @@ -1,143 +0,0 @@ -#include -#include -#include -#include - -bool returns_true(); -bool returns_false(); -char returns_42_char(); -float returns_42_float(); -double returns_42_double(); -uint8_t returns_42_uint8_t(); -int8_t returns_neg_42_int8_t(); -uint16_t returns_42_uint16_t(); -uint32_t returns_42_uint32_t(); -uint64_t returns_42_uint64_t(); -int16_t returns_neg_42_int16_t(); -int32_t returns_neg_42_int32_t(); -int64_t returns_neg_42_int64_t(); - -bool cb_identity_true(bool (*cb)()); -bool cb_identity_false(bool (*cb)()); -char cb_identity_42_char(char (*cb)()); -float cb_identity_42_float(float (*cb)()); -double cb_identity_42_double(double (*cb)()); -uint8_t cb_identity_42_uint8_t(uint8_t (*cb)()); -int8_t cb_identity_neg_42_int8_t(int8_t (*cb)()); -uint16_t cb_identity_42_uint16_t(uint16_t (*cb)()); -uint32_t cb_identity_42_uint32_t(uint32_t (*cb)()); -uint64_t cb_identity_42_uint64_t(uint64_t (*cb)()); -int16_t cb_identity_neg_42_int16_t(int16_t (*cb)()); -int32_t cb_identity_neg_42_int32_t(int32_t (*cb)()); -int64_t cb_identity_neg_42_int64_t(int64_t (*cb)()); - -bool identity_bool_true(); -bool identity_bool_false(); -char identity_char(char a); -float identity_float(float a); -bool identity_bool(bool ident); -double identity_double(double a); -int8_t identity_int8_t(int8_t a); -int16_t identity_int16_t(int16_t a); -int32_t identity_int32_t(int32_t a); -int64_t identity_int64_t(int64_t a); -uint8_t identity_uint8_t(uint8_t a); -uint16_t identity_uint16_t(uint16_t a); -uint32_t identity_uint32_t(uint32_t a); -uint64_t identity_uint64_t(uint64_t a); - -char add_char(char a, char b); -float add_float(float a, float b); -double add_double(double a, double b); -int8_t add_int8_t(int8_t a, int8_t b); -int16_t add_int16_t(int16_t a, int16_t b); -int32_t add_int32_t(int32_t a, int32_t b); -int64_t add_int64_t(int64_t a, int64_t b); -uint8_t add_uint8_t(uint8_t a, uint8_t b); -uint16_t add_uint16_t(uint16_t a, uint16_t b); -uint32_t add_uint32_t(uint32_t a, uint32_t b); -uint64_t add_uint64_t(uint64_t a, uint64_t b); - -bool returns_false() { return false; } -bool returns_true() { return true; } -char returns_42_char() { return '*'; } -double returns_42_double() { return (double)42.42; } -float returns_42_float() { return 42.42f; } -int16_t returns_neg_42_int16_t() { return -42; } -int32_t returns_neg_42_int32_t() { return -42; } -int64_t returns_neg_42_int64_t() { return -42; } -int8_t returns_neg_42_int8_t() { return -42; } -uint16_t returns_42_uint16_t() { return 42; } -uint32_t returns_42_uint32_t() { return 42; } -uint64_t returns_42_uint64_t() { return 42; } -uint8_t returns_42_uint8_t() { return (uint8_t)42; } - -char identity_char(char a) { return a; } -float identity_float(float a) { return a; } -double identity_double(double a) { return a; } -int8_t identity_int8_t(int8_t a) { return a; } -int16_t identity_int16_t(int16_t a) { return a; } -int32_t identity_int32_t(int32_t a) { return a; } -int64_t identity_int64_t(int64_t a) { return a; } -uint8_t identity_uint8_t(uint8_t a) { return a; } -uint16_t identity_uint16_t(uint16_t a) { return a; } -uint32_t identity_uint32_t(uint32_t a) { return a; } -uint64_t identity_uint64_t(uint64_t a) { return a; } -bool identity_bool(bool ident) { return ident; } -void *identity_ptr(void *ident) { return ident; } - -char add_char(char a, char b) { return a + b; } -float add_float(float a, float b) { return a + b; } -double add_double(double a, double b) { return a + b; } -int8_t add_int8_t(int8_t a, int8_t b) { return a + b; } -int16_t add_int16_t(int16_t a, int16_t b) { return a + b; } -int32_t add_int32_t(int32_t a, int32_t b) { return a + b; } -int64_t add_int64_t(int64_t a, int64_t b) { return a + b; } -uint8_t add_uint8_t(uint8_t a, uint8_t b) { return a + b; } -uint16_t add_uint16_t(uint16_t a, uint16_t b) { return a + b; } -uint32_t add_uint32_t(uint32_t a, uint32_t b) { return a + b; } -uint64_t add_uint64_t(uint64_t a, uint64_t b) { return a + b; } - -void *ptr_should_point_to_42_as_int32_t(); -void *ptr_should_point_to_42_as_int32_t() { - int32_t *ptr = malloc(sizeof(int32_t)); - *ptr = 42; - return ptr; -} - -static uint8_t buffer_with_deallocator[128]; -static int deallocatorCalled; -void deallocator(void *ptr, void *userData) { deallocatorCalled++; } -void *getDeallocatorCallback() { - deallocatorCalled = 0; - return &deallocator; -} -void *getDeallocatorBuffer() { - deallocatorCalled = 0; - return &buffer_with_deallocator; -} -int getDeallocatorCalledCount() { return deallocatorCalled; } - -bool is_null(int32_t *ptr) { return ptr == NULL; } -bool does_pointer_equal_42_as_int32_t(int32_t *ptr); -bool does_pointer_equal_42_as_int32_t(int32_t *ptr) { return *ptr == 42; } - -void *return_a_function_ptr_to_function_that_returns_true(); -void *return_a_function_ptr_to_function_that_returns_true() { - return (void *)&returns_true; -} - -bool cb_identity_true(bool (*cb)()) { return cb(); } - -bool cb_identity_false(bool (*cb)()) { return cb(); } -char cb_identity_42_char(char (*cb)()) { return cb(); } -float cb_identity_42_float(float (*cb)()) { return cb(); } -double cb_identity_42_double(double (*cb)()) { return cb(); } -uint8_t cb_identity_42_uint8_t(uint8_t (*cb)()) { return cb(); } -int8_t cb_identity_neg_42_int8_t(int8_t (*cb)()) { return cb(); } -uint16_t cb_identity_42_uint16_t(uint16_t (*cb)()) { return cb(); } -uint32_t cb_identity_42_uint32_t(uint32_t (*cb)()) { return cb(); } -uint64_t cb_identity_42_uint64_t(uint64_t (*cb)()) { return cb(); } -int16_t cb_identity_neg_42_int16_t(int16_t (*cb)()) { return cb(); } -int32_t cb_identity_neg_42_int32_t(int32_t (*cb)()) { return cb(); } -int64_t cb_identity_neg_42_int64_t(int64_t (*cb)()) { return cb(); } \ No newline at end of file diff --git a/test/bun.js/ffi.test.fixture.callback.c b/test/bun.js/ffi.test.fixture.callback.c deleted file mode 100644 index 3b9a46577..000000000 --- a/test/bun.js/ffi.test.fixture.callback.c +++ /dev/null @@ -1,296 +0,0 @@ -#define JS_GLOBAL_OBJECT (void*)0x0000000000000000ULL -#define IS_CALLBACK 1 -// This file is part of Bun! -// You can find the original source: -// https://github.com/oven-sh/bun/blob/main/src/bun.js/api/FFI.h#L2 -// -// clang-format off -// This file is only compatible with 64 bit CPUs -// It must be kept in sync with JSCJSValue.h -// https://github.com/oven-sh/WebKit/blob/72c2052b781cbfd4af867ae79ac9de460e392fba/Source/JavaScriptCore/runtime/JSCJSValue.h#L455-L458 -#ifdef IS_CALLBACK -#define INJECT_BEFORE int c = 500; // This is a callback, so we need to inject code before the call -#endif -#define IS_BIG_ENDIAN 0 -#define USE_JSVALUE64 1 -#define USE_JSVALUE32_64 0 - -#define ZIG_REPR_TYPE int64_t - - -// /* 7.18.1.1 Exact-width integer types */ -typedef unsigned char uint8_t; -typedef signed char int8_t; -typedef short int16_t; -typedef unsigned short uint16_t; -typedef int int32_t; -typedef unsigned int uint32_t; -typedef long long int64_t; -typedef unsigned long long uint64_t; -typedef unsigned long long size_t; -typedef long intptr_t; -typedef uint64_t uintptr_t; -typedef _Bool bool; - -#define true 1 -#define false 0 - - -#ifdef INJECT_BEFORE -// #include -#endif -// #include - -// This value is 2^49, used to encode doubles such that the encoded value will -// begin with a 15-bit pattern within the range 0x0002..0xFFFC. -#define DoubleEncodeOffsetBit 49 -#define DoubleEncodeOffset (1ll << DoubleEncodeOffsetBit) -#define OtherTag 0x2 -#define BoolTag 0x4 -#define UndefinedTag 0x8 -#define TagValueFalse (OtherTag | BoolTag | false) -#define TagValueTrue (OtherTag | BoolTag | true) -#define TagValueUndefined (OtherTag | UndefinedTag) -#define TagValueNull (OtherTag) -#define NotCellMask NumberTag | OtherTag - -#define MAX_INT32 2147483648 -#define MAX_INT52 9007199254740991 - -// If all bits in the mask are set, this indicates an integer number, -// if any but not all are set this value is a double precision number. -#define NumberTag 0xfffe000000000000ll - -typedef void* JSCell; - -typedef union EncodedJSValue { - int64_t asInt64; - -#if USE_JSVALUE64 - JSCell *ptr; -#endif - -#if IS_BIG_ENDIAN - struct { - int32_t tag; - int32_t payload; - } asBits; -#else - struct { - int32_t payload; - int32_t tag; - } asBits; -#endif - - void* asPtr; - double asDouble; - - ZIG_REPR_TYPE asZigRepr; -} EncodedJSValue; - -EncodedJSValue ValueUndefined = { TagValueUndefined }; -EncodedJSValue ValueTrue = { TagValueTrue }; - -typedef void* JSContext; - -// Bun_FFI_PointerOffsetToArgumentsList is injected into the build -// The value is generated in `make sizegen` -// The value is 6. -// On ARM64_32, the value is something else but it really doesn't matter for our case -// However, I don't want this to subtly break amidst future upgrades to JavaScriptCore -#define LOAD_ARGUMENTS_FROM_CALL_FRAME \ - int64_t *argsPtr = (int64_t*)((size_t*)callFrame + Bun_FFI_PointerOffsetToArgumentsList) - - -#ifdef IS_CALLBACK -void* callback_ctx; -ZIG_REPR_TYPE FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args); -// We wrap -static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) __attribute__((__always_inline__)); -static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) { - EncodedJSValue return_value; - return_value.asZigRepr = FFI_Callback_call(ctx, argCount, args); - return return_value; -} -#endif - -static bool JSVALUE_IS_CELL(EncodedJSValue val) __attribute__((__always_inline__)); -static bool JSVALUE_IS_INT32(EncodedJSValue val) __attribute__((__always_inline__)); -static bool JSVALUE_IS_NUMBER(EncodedJSValue val) __attribute__((__always_inline__)); - -static uint64_t JSVALUE_TO_UINT64(EncodedJSValue value) __attribute__((__always_inline__)); -static int64_t JSVALUE_TO_INT64(EncodedJSValue value) __attribute__((__always_inline__)); -uint64_t JSVALUE_TO_UINT64_SLOW(EncodedJSValue value); -int64_t JSVALUE_TO_INT64_SLOW(EncodedJSValue value); - -EncodedJSValue UINT64_TO_JSVALUE_SLOW(void* jsGlobalObject, uint64_t val); -EncodedJSValue INT64_TO_JSVALUE_SLOW(void* jsGlobalObject, int64_t val); -static EncodedJSValue UINT64_TO_JSVALUE(void* jsGlobalObject, uint64_t val) __attribute__((__always_inline__)); -static EncodedJSValue INT64_TO_JSVALUE(void* jsGlobalObject, int64_t val) __attribute__((__always_inline__)); - - -static EncodedJSValue INT32_TO_JSVALUE(int32_t val) __attribute__((__always_inline__)); -static EncodedJSValue DOUBLE_TO_JSVALUE(double val) __attribute__((__always_inline__)); -static EncodedJSValue FLOAT_TO_JSVALUE(float val) __attribute__((__always_inline__)); -static EncodedJSValue BOOLEAN_TO_JSVALUE(bool val) __attribute__((__always_inline__)); -static EncodedJSValue PTR_TO_JSVALUE(void* ptr) __attribute__((__always_inline__)); - -static void* JSVALUE_TO_PTR(EncodedJSValue val) __attribute__((__always_inline__)); -static int32_t JSVALUE_TO_INT32(EncodedJSValue val) __attribute__((__always_inline__)); -static float JSVALUE_TO_FLOAT(EncodedJSValue val) __attribute__((__always_inline__)); -static double JSVALUE_TO_DOUBLE(EncodedJSValue val) __attribute__((__always_inline__)); -static bool JSVALUE_TO_BOOL(EncodedJSValue val) __attribute__((__always_inline__)); - -static bool JSVALUE_IS_CELL(EncodedJSValue val) { - return !(val.asInt64 & NotCellMask); -} - -static bool JSVALUE_IS_INT32(EncodedJSValue val) { - return (val.asInt64 & NumberTag) == NumberTag; -} - -static bool JSVALUE_IS_NUMBER(EncodedJSValue val) { - return val.asInt64 & NumberTag; -} - - -// JSValue numbers-as-pointers are represented as a 52-bit integer -// Previously, the pointer was stored at the end of the 64-bit value -// Now, they're stored at the beginning of the 64-bit value -// This behavior change enables the JIT to handle it better -// It also is better readability when console.log(myPtr) -static void* JSVALUE_TO_PTR(EncodedJSValue val) { - if (val.asInt64 == TagValueNull) - return 0; - val.asInt64 -= DoubleEncodeOffset; - size_t ptr = (size_t)val.asDouble; - return (void*)ptr; -} - -static EncodedJSValue PTR_TO_JSVALUE(void* ptr) { - EncodedJSValue val; - if (ptr == 0) - { - val.asInt64 = TagValueNull; - return val; - } - - val.asDouble = (double)(size_t)ptr; - val.asInt64 += DoubleEncodeOffset; - return val; -} - -static EncodedJSValue DOUBLE_TO_JSVALUE(double val) { - EncodedJSValue res; - res.asDouble = val; - res.asInt64 += DoubleEncodeOffset; - return res; -} - -static int32_t JSVALUE_TO_INT32(EncodedJSValue val) { - return val.asInt64; -} - -static EncodedJSValue INT32_TO_JSVALUE(int32_t val) { - EncodedJSValue res; - res.asInt64 = NumberTag | (uint32_t)val; - return res; -} - - - - -static EncodedJSValue FLOAT_TO_JSVALUE(float val) { - return DOUBLE_TO_JSVALUE((double)val); -} - -static EncodedJSValue BOOLEAN_TO_JSVALUE(bool val) { - EncodedJSValue res; - res.asInt64 = val ? TagValueTrue : TagValueFalse; - return res; -} - - -static double JSVALUE_TO_DOUBLE(EncodedJSValue val) { - val.asInt64 -= DoubleEncodeOffset; - return val.asDouble; -} - -static float JSVALUE_TO_FLOAT(EncodedJSValue val) { - return (float)JSVALUE_TO_DOUBLE(val); -} - -static bool JSVALUE_TO_BOOL(EncodedJSValue val) { - return val.asInt64 == TagValueTrue; -} - - -static uint64_t JSVALUE_TO_UINT64(EncodedJSValue value) { - if (JSVALUE_IS_INT32(value)) { - return (uint64_t)JSVALUE_TO_INT32(value); - } - - if (JSVALUE_IS_NUMBER(value)) { - return (uint64_t)JSVALUE_TO_DOUBLE(value); - } - - return JSVALUE_TO_UINT64_SLOW(value); -} -static int64_t JSVALUE_TO_INT64(EncodedJSValue value) { - if (JSVALUE_IS_INT32(value)) { - return (int64_t)JSVALUE_TO_INT32(value); - } - - if (JSVALUE_IS_NUMBER(value)) { - return (int64_t)JSVALUE_TO_DOUBLE(value); - } - - return JSVALUE_TO_INT64_SLOW(value); -} - -static EncodedJSValue UINT64_TO_JSVALUE(void* jsGlobalObject, uint64_t val) { - if (val < MAX_INT32) { - return INT32_TO_JSVALUE((int32_t)val); - } - - if (val < MAX_INT52) { - return DOUBLE_TO_JSVALUE((double)val); - } - - return UINT64_TO_JSVALUE_SLOW(jsGlobalObject, val); -} - -static EncodedJSValue INT64_TO_JSVALUE(void* jsGlobalObject, int64_t val) { - if (val >= -MAX_INT32 && val <= MAX_INT32) { - return INT32_TO_JSVALUE((int32_t)val); - } - - if (val >= -MAX_INT52 && val <= MAX_INT52) { - return DOUBLE_TO_JSVALUE((double)val); - } - - return INT64_TO_JSVALUE_SLOW(jsGlobalObject, val); -} - -#ifndef IS_CALLBACK -ZIG_REPR_TYPE JSFunctionCall(void* jsGlobalObject, void* callFrame); - -#endif - - -// --- Generated Code --- - - -/* --- The Callback Function */ -/* --- The Callback Function */ -bool my_callback_function(void* arg0); - -bool my_callback_function(void* arg0) { -#ifdef INJECT_BEFORE -INJECT_BEFORE; -#endif - ZIG_REPR_TYPE arguments[1]; -arguments[0] = PTR_TO_JSVALUE(arg0).asZigRepr; - return (bool)JSVALUE_TO_BOOL(_FFI_Callback_call((void*)0x0000000000000000ULL, 1, arguments)); -} - diff --git a/test/bun.js/ffi.test.fixture.receiver.c b/test/bun.js/ffi.test.fixture.receiver.c deleted file mode 100644 index 0299a961b..000000000 --- a/test/bun.js/ffi.test.fixture.receiver.c +++ /dev/null @@ -1,295 +0,0 @@ -#define HAS_ARGUMENTS -#define USES_FLOAT 1 -// This file is part of Bun! -// You can find the original source: -// https://github.com/oven-sh/bun/blob/main/src/bun.js/api/FFI.h#L2 -// -// clang-format off -// This file is only compatible with 64 bit CPUs -// It must be kept in sync with JSCJSValue.h -// https://github.com/oven-sh/WebKit/blob/72c2052b781cbfd4af867ae79ac9de460e392fba/Source/JavaScriptCore/runtime/JSCJSValue.h#L455-L458 -#ifdef IS_CALLBACK -#define INJECT_BEFORE int c = 500; // This is a callback, so we need to inject code before the call -#endif -#define IS_BIG_ENDIAN 0 -#define USE_JSVALUE64 1 -#define USE_JSVALUE32_64 0 - -#define ZIG_REPR_TYPE int64_t - - -// /* 7.18.1.1 Exact-width integer types */ -typedef unsigned char uint8_t; -typedef signed char int8_t; -typedef short int16_t; -typedef unsigned short uint16_t; -typedef int int32_t; -typedef unsigned int uint32_t; -typedef long long int64_t; -typedef unsigned long long uint64_t; -typedef unsigned long long size_t; -typedef long intptr_t; -typedef uint64_t uintptr_t; -typedef _Bool bool; - -#define true 1 -#define false 0 - - -#ifdef INJECT_BEFORE -// #include -#endif -// #include - -// This value is 2^49, used to encode doubles such that the encoded value will -// begin with a 15-bit pattern within the range 0x0002..0xFFFC. -#define DoubleEncodeOffsetBit 49 -#define DoubleEncodeOffset (1ll << DoubleEncodeOffsetBit) -#define OtherTag 0x2 -#define BoolTag 0x4 -#define UndefinedTag 0x8 -#define TagValueFalse (OtherTag | BoolTag | false) -#define TagValueTrue (OtherTag | BoolTag | true) -#define TagValueUndefined (OtherTag | UndefinedTag) -#define TagValueNull (OtherTag) -#define NotCellMask NumberTag | OtherTag - -#define MAX_INT32 2147483648 -#define MAX_INT52 9007199254740991 - -// If all bits in the mask are set, this indicates an integer number, -// if any but not all are set this value is a double precision number. -#define NumberTag 0xfffe000000000000ll - -typedef void* JSCell; - -typedef union EncodedJSValue { - int64_t asInt64; - -#if USE_JSVALUE64 - JSCell *ptr; -#endif - -#if IS_BIG_ENDIAN - struct { - int32_t tag; - int32_t payload; - } asBits; -#else - struct { - int32_t payload; - int32_t tag; - } asBits; -#endif - - void* asPtr; - double asDouble; - - ZIG_REPR_TYPE asZigRepr; -} EncodedJSValue; - -EncodedJSValue ValueUndefined = { TagValueUndefined }; -EncodedJSValue ValueTrue = { TagValueTrue }; - -typedef void* JSContext; - -// Bun_FFI_PointerOffsetToArgumentsList is injected into the build -// The value is generated in `make sizegen` -// The value is 6. -// On ARM64_32, the value is something else but it really doesn't matter for our case -// However, I don't want this to subtly break amidst future upgrades to JavaScriptCore -#define LOAD_ARGUMENTS_FROM_CALL_FRAME \ - int64_t *argsPtr = (int64_t*)((size_t*)callFrame + Bun_FFI_PointerOffsetToArgumentsList) - - -#ifdef IS_CALLBACK -void* callback_ctx; -ZIG_REPR_TYPE FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args); -// We wrap -static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) __attribute__((__always_inline__)); -static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) { - EncodedJSValue return_value; - return_value.asZigRepr = FFI_Callback_call(ctx, argCount, args); - return return_value; -} -#endif - -static bool JSVALUE_IS_CELL(EncodedJSValue val) __attribute__((__always_inline__)); -static bool JSVALUE_IS_INT32(EncodedJSValue val) __attribute__((__always_inline__)); -static bool JSVALUE_IS_NUMBER(EncodedJSValue val) __attribute__((__always_inline__)); - -static uint64_t JSVALUE_TO_UINT64(EncodedJSValue value) __attribute__((__always_inline__)); -static int64_t JSVALUE_TO_INT64(EncodedJSValue value) __attribute__((__always_inline__)); -uint64_t JSVALUE_TO_UINT64_SLOW(EncodedJSValue value); -int64_t JSVALUE_TO_INT64_SLOW(EncodedJSValue value); - -EncodedJSValue UINT64_TO_JSVALUE_SLOW(void* jsGlobalObject, uint64_t val); -EncodedJSValue INT64_TO_JSVALUE_SLOW(void* jsGlobalObject, int64_t val); -static EncodedJSValue UINT64_TO_JSVALUE(void* jsGlobalObject, uint64_t val) __attribute__((__always_inline__)); -static EncodedJSValue INT64_TO_JSVALUE(void* jsGlobalObject, int64_t val) __attribute__((__always_inline__)); - - -static EncodedJSValue INT32_TO_JSVALUE(int32_t val) __attribute__((__always_inline__)); -static EncodedJSValue DOUBLE_TO_JSVALUE(double val) __attribute__((__always_inline__)); -static EncodedJSValue FLOAT_TO_JSVALUE(float val) __attribute__((__always_inline__)); -static EncodedJSValue BOOLEAN_TO_JSVALUE(bool val) __attribute__((__always_inline__)); -static EncodedJSValue PTR_TO_JSVALUE(void* ptr) __attribute__((__always_inline__)); - -static void* JSVALUE_TO_PTR(EncodedJSValue val) __attribute__((__always_inline__)); -static int32_t JSVALUE_TO_INT32(EncodedJSValue val) __attribute__((__always_inline__)); -static float JSVALUE_TO_FLOAT(EncodedJSValue val) __attribute__((__always_inline__)); -static double JSVALUE_TO_DOUBLE(EncodedJSValue val) __attribute__((__always_inline__)); -static bool JSVALUE_TO_BOOL(EncodedJSValue val) __attribute__((__always_inline__)); - -static bool JSVALUE_IS_CELL(EncodedJSValue val) { - return !(val.asInt64 & NotCellMask); -} - -static bool JSVALUE_IS_INT32(EncodedJSValue val) { - return (val.asInt64 & NumberTag) == NumberTag; -} - -static bool JSVALUE_IS_NUMBER(EncodedJSValue val) { - return val.asInt64 & NumberTag; -} - - -// JSValue numbers-as-pointers are represented as a 52-bit integer -// Previously, the pointer was stored at the end of the 64-bit value -// Now, they're stored at the beginning of the 64-bit value -// This behavior change enables the JIT to handle it better -// It also is better readability when console.log(myPtr) -static void* JSVALUE_TO_PTR(EncodedJSValue val) { - if (val.asInt64 == TagValueNull) - return 0; - val.asInt64 -= DoubleEncodeOffset; - size_t ptr = (size_t)val.asDouble; - return (void*)ptr; -} - -static EncodedJSValue PTR_TO_JSVALUE(void* ptr) { - EncodedJSValue val; - if (ptr == 0) - { - val.asInt64 = TagValueNull; - return val; - } - - val.asDouble = (double)(size_t)ptr; - val.asInt64 += DoubleEncodeOffset; - return val; -} - -static EncodedJSValue DOUBLE_TO_JSVALUE(double val) { - EncodedJSValue res; - res.asDouble = val; - res.asInt64 += DoubleEncodeOffset; - return res; -} - -static int32_t JSVALUE_TO_INT32(EncodedJSValue val) { - return val.asInt64; -} - -static EncodedJSValue INT32_TO_JSVALUE(int32_t val) { - EncodedJSValue res; - res.asInt64 = NumberTag | (uint32_t)val; - return res; -} - - - - -static EncodedJSValue FLOAT_TO_JSVALUE(float val) { - return DOUBLE_TO_JSVALUE((double)val); -} - -static EncodedJSValue BOOLEAN_TO_JSVALUE(bool val) { - EncodedJSValue res; - res.asInt64 = val ? TagValueTrue : TagValueFalse; - return res; -} - - -static double JSVALUE_TO_DOUBLE(EncodedJSValue val) { - val.asInt64 -= DoubleEncodeOffset; - return val.asDouble; -} - -static float JSVALUE_TO_FLOAT(EncodedJSValue val) { - return (float)JSVALUE_TO_DOUBLE(val); -} - -static bool JSVALUE_TO_BOOL(EncodedJSValue val) { - return val.asInt64 == TagValueTrue; -} - - -static uint64_t JSVALUE_TO_UINT64(EncodedJSValue value) { - if (JSVALUE_IS_INT32(value)) { - return (uint64_t)JSVALUE_TO_INT32(value); - } - - if (JSVALUE_IS_NUMBER(value)) { - return (uint64_t)JSVALUE_TO_DOUBLE(value); - } - - return JSVALUE_TO_UINT64_SLOW(value); -} -static int64_t JSVALUE_TO_INT64(EncodedJSValue value) { - if (JSVALUE_IS_INT32(value)) { - return (int64_t)JSVALUE_TO_INT32(value); - } - - if (JSVALUE_IS_NUMBER(value)) { - return (int64_t)JSVALUE_TO_DOUBLE(value); - } - - return JSVALUE_TO_INT64_SLOW(value); -} - -static EncodedJSValue UINT64_TO_JSVALUE(void* jsGlobalObject, uint64_t val) { - if (val < MAX_INT32) { - return INT32_TO_JSVALUE((int32_t)val); - } - - if (val < MAX_INT52) { - return DOUBLE_TO_JSVALUE((double)val); - } - - return UINT64_TO_JSVALUE_SLOW(jsGlobalObject, val); -} - -static EncodedJSValue INT64_TO_JSVALUE(void* jsGlobalObject, int64_t val) { - if (val >= -MAX_INT32 && val <= MAX_INT32) { - return INT32_TO_JSVALUE((int32_t)val); - } - - if (val >= -MAX_INT52 && val <= MAX_INT52) { - return DOUBLE_TO_JSVALUE((double)val); - } - - return INT64_TO_JSVALUE_SLOW(jsGlobalObject, val); -} - -#ifndef IS_CALLBACK -ZIG_REPR_TYPE JSFunctionCall(void* jsGlobalObject, void* callFrame); - -#endif - - -// --- Generated Code --- -/* --- The Function To Call */ -float not_a_callback(float arg0); - - -/* ---- Your Wrapper Function ---- */ -ZIG_REPR_TYPE JSFunctionCall(void* JS_GLOBAL_OBJECT, void* callFrame) { - LOAD_ARGUMENTS_FROM_CALL_FRAME; - EncodedJSValue arg0; - arg0.asInt64 = *argsPtr; - float return_value = not_a_callback( JSVALUE_TO_FLOAT(arg0)); - - return FLOAT_TO_JSVALUE(return_value).asZigRepr; -} - diff --git a/test/bun.js/ffi.test.js b/test/bun.js/ffi.test.js deleted file mode 100644 index 102737782..000000000 --- a/test/bun.js/ffi.test.js +++ /dev/null @@ -1,613 +0,0 @@ -import { afterAll, describe, expect, it } from "bun:test"; -// -import { - CFunction, - CString, - dlopen as _dlopen, - JSCallback, - ptr, - read, - toArrayBuffer, - toBuffer, - viewSource, -} from "bun:ffi"; - -const dlopen = (...args) => { - try { - return _dlopen(...args); - } catch (err) { - console.error("To enable this test, run `make compile-ffi-test`."); - throw err; - } -}; - -it("ffi print", async () => { - await Bun.write( - import.meta.dir + "/ffi.test.fixture.callback.c", - viewSource( - { - returns: "bool", - args: ["ptr"], - }, - true, - ), - ); - await Bun.write( - import.meta.dir + "/ffi.test.fixture.receiver.c", - viewSource( - { - not_a_callback: { - returns: "float", - args: ["float"], - }, - }, - false, - )[0], - ); - expect( - viewSource( - { - returns: "int8_t", - args: [], - }, - true, - ).length > 0, - ).toBe(true); - expect( - viewSource( - { - a: { - returns: "int8_t", - args: [], - }, - }, - false, - ).length > 0, - ).toBe(true); -}); - -function getTypes(fast) { - const int64_t = fast ? "i64_fast" : "int64_t"; - const uint64_t = fast ? "u64_fast" : "uint64_t"; - return { - returns_true: { - returns: "bool", - args: [], - }, - returns_false: { - returns: "bool", - args: [], - }, - returns_42_char: { - returns: "char", - args: [], - }, - returns_42_float: { - returns: "float", - args: [], - }, - returns_42_double: { - returns: "double", - args: [], - }, - returns_42_uint8_t: { - returns: "uint8_t", - args: [], - }, - returns_neg_42_int8_t: { - returns: "int8_t", - args: [], - }, - returns_42_uint16_t: { - returns: "uint16_t", - args: [], - }, - returns_42_uint32_t: { - returns: "uint32_t", - args: [], - }, - returns_42_uint64_t: { - returns: uint64_t, - args: [], - }, - returns_neg_42_int16_t: { - returns: "int16_t", - args: [], - }, - returns_neg_42_int32_t: { - returns: "int32_t", - args: [], - }, - returns_neg_42_int64_t: { - returns: int64_t, - args: [], - }, - - identity_char: { - returns: "char", - args: ["char"], - }, - identity_float: { - returns: "float", - args: ["float"], - }, - identity_bool: { - returns: "bool", - args: ["bool"], - }, - identity_double: { - returns: "double", - args: ["double"], - }, - identity_int8_t: { - returns: "int8_t", - args: ["int8_t"], - }, - identity_int16_t: { - returns: "int16_t", - args: ["int16_t"], - }, - identity_int32_t: { - returns: "int32_t", - args: ["int32_t"], - }, - identity_int64_t: { - returns: int64_t, - args: [int64_t], - }, - identity_uint8_t: { - returns: "uint8_t", - args: ["uint8_t"], - }, - identity_uint16_t: { - returns: "uint16_t", - args: ["uint16_t"], - }, - identity_uint32_t: { - returns: "uint32_t", - args: ["uint32_t"], - }, - identity_uint64_t: { - returns: uint64_t, - args: [uint64_t], - }, - - add_char: { - returns: "char", - args: ["char", "char"], - }, - add_float: { - returns: "float", - args: ["float", "float"], - }, - add_double: { - returns: "double", - args: ["double", "double"], - }, - add_int8_t: { - returns: "int8_t", - args: ["int8_t", "int8_t"], - }, - add_int16_t: { - returns: "int16_t", - args: ["int16_t", "int16_t"], - }, - add_int32_t: { - returns: "int32_t", - args: ["int32_t", "int32_t"], - }, - add_int64_t: { - returns: int64_t, - args: [int64_t, int64_t], - }, - add_uint8_t: { - returns: "uint8_t", - args: ["uint8_t", "uint8_t"], - }, - add_uint16_t: { - returns: "uint16_t", - args: ["uint16_t", "uint16_t"], - }, - add_uint32_t: { - returns: "uint32_t", - args: ["uint32_t", "uint32_t"], - }, - - is_null: { - returns: "bool", - args: ["ptr"], - }, - - does_pointer_equal_42_as_int32_t: { - returns: "bool", - args: ["ptr"], - }, - - ptr_should_point_to_42_as_int32_t: { - returns: "ptr", - args: [], - }, - identity_ptr: { - returns: "ptr", - args: ["ptr"], - }, - add_uint64_t: { - returns: uint64_t, - args: [uint64_t, uint64_t], - }, - - cb_identity_true: { - returns: "bool", - args: ["ptr"], - }, - cb_identity_false: { - returns: "bool", - args: ["ptr"], - }, - cb_identity_42_char: { - returns: "char", - args: ["ptr"], - }, - cb_identity_42_float: { - returns: "float", - args: ["ptr"], - }, - cb_identity_42_double: { - returns: "double", - args: ["ptr"], - }, - cb_identity_42_uint8_t: { - returns: "uint8_t", - args: ["ptr"], - }, - cb_identity_neg_42_int8_t: { - returns: "int8_t", - args: ["ptr"], - }, - cb_identity_42_uint16_t: { - returns: "uint16_t", - args: ["ptr"], - }, - cb_identity_42_uint32_t: { - returns: "uint32_t", - args: ["ptr"], - }, - cb_identity_42_uint64_t: { - returns: uint64_t, - args: ["ptr"], - }, - cb_identity_neg_42_int16_t: { - returns: "int16_t", - args: ["ptr"], - }, - cb_identity_neg_42_int32_t: { - returns: "int32_t", - args: ["ptr"], - }, - cb_identity_neg_42_int64_t: { - returns: int64_t, - args: ["ptr"], - }, - - return_a_function_ptr_to_function_that_returns_true: { - returns: "ptr", - args: [], - }, - - getDeallocatorCalledCount: { - returns: "int32_t", - args: [], - }, - getDeallocatorCallback: { - returns: "ptr", - args: [], - }, - getDeallocatorBuffer: { - returns: "ptr", - args: [], - }, - }; -} - -function ffiRunner(fast) { - describe("FFI runner" + (fast ? " (fast int)" : ""), () => { - const types = getTypes(fast); - const { - symbols: { - returns_true, - returns_false, - return_a_function_ptr_to_function_that_returns_true, - returns_42_char, - returns_42_float, - returns_42_double, - returns_42_uint8_t, - returns_neg_42_int8_t, - returns_42_uint16_t, - returns_42_uint32_t, - returns_42_uint64_t, - returns_neg_42_int16_t, - returns_neg_42_int32_t, - returns_neg_42_int64_t, - identity_char, - identity_float, - identity_bool, - identity_double, - identity_int8_t, - identity_int16_t, - identity_int32_t, - identity_int64_t, - identity_uint8_t, - identity_uint16_t, - identity_uint32_t, - identity_uint64_t, - add_char, - add_float, - add_double, - add_int8_t, - add_int16_t, - add_int32_t, - add_int64_t, - add_uint8_t, - add_uint16_t, - identity_ptr, - add_uint32_t, - add_uint64_t, - is_null, - does_pointer_equal_42_as_int32_t, - ptr_should_point_to_42_as_int32_t, - cb_identity_true, - cb_identity_false, - cb_identity_42_char, - cb_identity_42_float, - cb_identity_42_double, - cb_identity_42_uint8_t, - cb_identity_neg_42_int8_t, - cb_identity_42_uint16_t, - cb_identity_42_uint32_t, - cb_identity_42_uint64_t, - cb_identity_neg_42_int16_t, - cb_identity_neg_42_int32_t, - cb_identity_neg_42_int64_t, - getDeallocatorCalledCount, - getDeallocatorCallback, - getDeallocatorBuffer, - }, - close, - } = dlopen("/tmp/bun-ffi-test.dylib", types); - it("primitives", () => { - Bun.gc(true); - expect(returns_true()).toBe(true); - Bun.gc(true); - expect(returns_false()).toBe(false); - - expect(returns_42_char()).toBe(42); - if (fast) expect(returns_42_uint64_t().valueOf()).toBe(42); - else expect(returns_42_uint64_t().valueOf()).toBe(42n); - Bun.gc(true); - expect(Math.fround(returns_42_float())).toBe(Math.fround(42.41999804973602)); - expect(returns_42_double()).toBe(42.42); - expect(returns_42_uint8_t()).toBe(42); - expect(returns_neg_42_int8_t()).toBe(-42); - expect(returns_42_uint16_t()).toBe(42); - expect(returns_42_uint32_t()).toBe(42); - if (fast) expect(returns_42_uint64_t()).toBe(42); - else expect(returns_42_uint64_t()).toBe(42n); - expect(returns_neg_42_int16_t()).toBe(-42); - expect(returns_neg_42_int32_t()).toBe(-42); - expect(identity_int32_t(10)).toBe(10); - Bun.gc(true); - if (fast) expect(returns_neg_42_int64_t()).toBe(-42); - else expect(returns_neg_42_int64_t()).toBe(-42n); - - expect(identity_char(10)).toBe(10); - - expect(identity_float(10.199999809265137)).toBe(10.199999809265137); - - expect(identity_bool(true)).toBe(true); - - expect(identity_bool(false)).toBe(false); - expect(identity_double(10.100000000000364)).toBe(10.100000000000364); - - expect(identity_int8_t(10)).toBe(10); - expect(identity_int16_t(10)).toBe(10); - - if (fast) expect(identity_int64_t(10)).toBe(10); - else expect(identity_int64_t(10)).toBe(10n); - expect(identity_uint8_t(10)).toBe(10); - expect(identity_uint16_t(10)).toBe(10); - expect(identity_uint32_t(10)).toBe(10); - if (fast) expect(identity_uint64_t(10)).toBe(10); - else expect(identity_uint64_t(10)).toBe(10n); - Bun.gc(true); - var bigArray = new BigUint64Array(8); - new Uint8Array(bigArray.buffer).fill(255); - var bigIntArray = new BigInt64Array(bigArray.buffer); - expect(identity_uint64_t(bigArray[0])).toBe(bigArray[0]); - expect(identity_uint64_t(bigArray[0] - BigInt(1))).toBe(bigArray[0] - BigInt(1)); - if (fast) { - expect(add_uint64_t(BigInt(-1) * bigArray[0], bigArray[0])).toBe(0); - expect(add_uint64_t(BigInt(-1) * bigArray[0] + BigInt(10), bigArray[0])).toBe(10); - } else { - expect(add_uint64_t(BigInt(-1) * bigArray[0], bigArray[0])).toBe(0n); - expect(add_uint64_t(BigInt(-1) * bigArray[0] + BigInt(10), bigArray[0])).toBe(10n); - } - if (fast) { - expect(identity_uint64_t(0)).toBe(0); - expect(identity_uint64_t(100)).toBe(100); - expect(identity_uint64_t(BigInt(100))).toBe(100); - - expect(identity_int64_t(bigIntArray[0])).toBe(-1); - expect(identity_int64_t(bigIntArray[0] - BigInt(1))).toBe(-2); - } else { - expect(identity_uint64_t(0)).toBe(0n); - expect(identity_uint64_t(100)).toBe(100n); - expect(identity_uint64_t(BigInt(100))).toBe(100n); - - expect(identity_int64_t(bigIntArray[0])).toBe(bigIntArray[0]); - expect(identity_int64_t(bigIntArray[0] - BigInt(1))).toBe(bigIntArray[0] - BigInt(1)); - } - Bun.gc(true); - expect(add_char.native(1, 1)).toBe(2); - - expect(add_float(2.4, 2.8)).toBe(Math.fround(5.2)); - expect(add_double(4.2, 0.1)).toBe(4.3); - expect(add_int8_t(1, 1)).toBe(2); - expect(add_int16_t(1, 1)).toBe(2); - expect(add_int32_t(1, 1)).toBe(2); - if (fast) expect(add_int64_t(1, 1)).toBe(2); - else expect(add_int64_t(1n, 1n)).toBe(2n); - expect(add_uint8_t(1, 1)).toBe(2); - expect(add_uint16_t(1, 1)).toBe(2); - expect(add_uint32_t(1, 1)).toBe(2); - Bun.gc(true); - expect(is_null(null)).toBe(true); - const cptr = ptr_should_point_to_42_as_int32_t(); - expect(cptr != 0).toBe(true); - expect(typeof cptr === "number").toBe(true); - expect(does_pointer_equal_42_as_int32_t(cptr)).toBe(true); - const buffer = toBuffer(cptr, 0, 4); - expect(buffer.readInt32(0)).toBe(42); - expect(new DataView(toArrayBuffer(cptr, 0, 4), 0, 4).getInt32(0, true)).toBe(42); - expect(ptr(buffer)).toBe(cptr); - expect(new CString(cptr, 0, 1).toString()).toBe("*"); - expect(identity_ptr(cptr)).toBe(cptr); - const second_ptr = ptr(new Buffer(8)); - expect(identity_ptr(second_ptr)).toBe(second_ptr); - }); - - it("CFunction", () => { - var myCFunction = new CFunction({ - ptr: return_a_function_ptr_to_function_that_returns_true(), - returns: "bool", - }); - expect(myCFunction()).toBe(true); - }); - - const typeMap = { - int8_t: -8, - int16_t: -16, - int32_t: -32, - int64_t: -64n, - uint8_t: 8, - uint16_t: 16, - uint32_t: 32, - uint64_t: 64n, - float: 32.5, - double: 64.5, - ptr: 0xdeadbeef, - "void*": null, - }; - - it("JSCallback", () => { - var toClose = new JSCallback( - input => { - return input; - }, - { - returns: "bool", - args: ["bool"], - }, - ); - expect(toClose.ptr > 0).toBe(true); - toClose.close(); - expect(toClose.ptr === null).toBe(true); - }); - - describe("callbacks", () => { - // Return types, 1 argument - for (let [returnName, returnValue] of Object.entries(typeMap)) { - it("fn(" + returnName + ") " + returnName, () => { - var roundtripFunction = new CFunction({ - ptr: new JSCallback( - input => { - return input; - }, - { - returns: returnName, - args: [returnName], - }, - ).ptr, - returns: returnName, - args: [returnName], - }); - expect(roundtripFunction(returnValue)).toBe(returnValue); - }); - } - // Return types, no args - for (let [name, value] of Object.entries(typeMap)) { - it("fn() " + name, () => { - var roundtripFunction = new CFunction({ - ptr: new JSCallback(() => value, { - returns: name, - }).ptr, - returns: name, - }); - expect(roundtripFunction()).toBe(value); - }); - } - }); - - describe("threadsafe callback", done => { - // 1 arg, threadsafe - for (let [name, value] of Object.entries(typeMap)) { - it("fn(" + name + ") " + name, async () => { - const cb = new JSCallback( - arg1 => { - expect(arg1).toBe(value); - }, - { - args: [name], - threadsafe: true, - }, - ); - var roundtripFunction = new CFunction({ - ptr: cb.ptr, - returns: "void", - args: [name], - }); - roundtripFunction(value); - await 1; - }); - } - }); - - afterAll(() => { - close(); - }); - }); -} - -it("read", () => { - const buffer = new BigInt64Array(16); - const dataView = new DataView(buffer.buffer); - const addr = ptr(buffer); - - for (let i = 0; i < buffer.length; i++) { - buffer[i] = BigInt(i); - expect(read.intptr(addr, i * 8)).toBe(Number(dataView.getBigInt64(i * 8, true))); - expect(read.ptr(addr, i * 8)).toBe(Number(dataView.getBigUint64(i * 8, true))); - expect(read.f64(addr, i + 8)).toBe(dataView.getFloat64(i + 8, true)); - expect(read.i64(addr, i * 8)).toBe(dataView.getBigInt64(i * 8, true)); - expect(read.u64(addr, i * 8)).toBe(dataView.getBigUint64(i * 8, true)); - } - - for (let i = 0; i < buffer.byteLength - 4; i++) { - // read is intended to behave like DataView - // but instead of doing - // new DataView(toArrayBuffer(myPtr)).getInt8(0, true) - // you can do - // read.i8(myPtr, 0) - expect(read.i8(addr, i)).toBe(dataView.getInt8(i, true)); - expect(read.i16(addr, i)).toBe(dataView.getInt16(i, true)); - expect(read.i32(addr, i)).toBe(dataView.getInt32(i, true)); - expect(read.u8(addr, i)).toBe(dataView.getUint8(i, true)); - expect(read.u16(addr, i)).toBe(dataView.getUint16(i, true)); - expect(read.u32(addr, i)).toBe(dataView.getUint32(i, true)); - expect(read.f32(addr, i)).toBe(dataView.getFloat32(i, true)); - } -}); - -describe("run ffi", () => { - ffiRunner(false); - ffiRunner(true); -}); diff --git a/test/bun.js/file-importing-nonexistent-file.js b/test/bun.js/file-importing-nonexistent-file.js deleted file mode 100644 index 1136a7d8c..000000000 --- a/test/bun.js/file-importing-nonexistent-file.js +++ /dev/null @@ -1 +0,0 @@ -import "./does-not-exist.js"; diff --git a/test/bun.js/fileUrl.test.js b/test/bun.js/fileUrl.test.js deleted file mode 100644 index ebae570f8..000000000 --- a/test/bun.js/fileUrl.test.js +++ /dev/null @@ -1,16 +0,0 @@ -import { expect, it, describe } from "bun:test"; -import { pathToFileURL, fileURLToPath } from "bun"; -describe("pathToFileURL", () => { - it("should convert a path to a file url", () => { - expect(pathToFileURL("/path/to/file.js").href).toBe("file:///path/to/file.js"); - }); -}); - -describe("fileURLToPath", () => { - it("should convert a file url to a path", () => { - expect(fileURLToPath("file:///path/to/file.js")).toBe("/path/to/file.js"); - }); - it("should convert a URL to a path", () => { - expect(fileURLToPath(new URL("file:///path/to/file.js"))).toBe("/path/to/file.js"); - }); -}); diff --git a/test/bun.js/filesink.test.ts b/test/bun.js/filesink.test.ts deleted file mode 100644 index 31fd70e54..000000000 --- a/test/bun.js/filesink.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { ArrayBufferSink } from "bun"; -import { describe, expect, it } from "bun:test"; -import { mkfifo } from "mkfifo"; - -describe("FileSink", () => { - const fixtures = [ - [ - ["abcdefghijklmnopqrstuvwxyz"], - new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz"), - "abcdefghijklmnopqrstuvwxyz", - ], - [ - ["abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"], - new TextEncoder().encode("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", - ], - [ - ["😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"], - new TextEncoder().encode("😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"), - "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌", - ], - [ - ["abcdefghijklmnopqrstuvwxyz", "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"], - new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"), - "abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌", - ], - [ - ["abcdefghijklmnopqrstuvwxyz", "😋", " Get Emoji — All Emojis", " to ✂️ Copy and 📋 Paste 👌"], - new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"), - "(rope) " + "abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌", - ], - [ - [ - new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz"), - "😋", - " Get Emoji — All Emojis", - " to ✂️ Copy and 📋 Paste 👌", - ], - new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"), - "(array) " + "abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌", - ], - ] as const; - - function getPath(label) { - const path = `/tmp/bun-test-${Bun.hash(label).toString(10)}.txt`; - try { - require("fs").unlinkSync(path); - } catch (e) {} - return path; - } - - var activeFIFO: Promise; - var decoder = new TextDecoder(); - - function getFd(label) { - const path = `/tmp/bun-test-${Bun.hash(label).toString(10)}.txt`; - try { - require("fs").unlinkSync(path); - } catch (e) {} - mkfifo(path, 0o666); - activeFIFO = (async function (stream: ReadableStream) { - var chunks: Uint8Array[] = []; - for await (const chunk of stream) { - chunks.push(chunk); - } - return Buffer.concat(chunks).toString(); - // test it on a small chunk size - })(Bun.file(path).stream(64)); - return path; - } - - for (let isPipe of [true, false] as const) { - describe(isPipe ? "pipe" : "file", () => { - for (const [input, expected, label] of fixtures) { - var getPathOrFd = () => (isPipe ? getFd(label) : getPath(label)); - - it(`${JSON.stringify(label)}`, async () => { - const path = getPathOrFd(); - const sink = Bun.file(path).writer(); - for (let i = 0; i < input.length; i++) { - sink.write(input[i]); - } - await sink.end(); - - if (!isPipe) { - const output = new Uint8Array(await Bun.file(path).arrayBuffer()); - for (let i = 0; i < expected.length; i++) { - expect(output[i]).toBe(expected[i]); - } - expect(output.byteLength).toBe(expected.byteLength); - } else { - console.log("reading"); - const output = await activeFIFO; - expect(output).toBe(decoder.decode(expected)); - } - }); - - it(`flushing -> ${JSON.stringify(label)}`, async () => { - const path = getPathOrFd(); - const sink = Bun.file(path).writer(); - for (let i = 0; i < input.length; i++) { - sink.write(input[i]); - await sink.flush(); - } - await sink.end(); - if (!isPipe) { - const output = new Uint8Array(await Bun.file(path).arrayBuffer()); - for (let i = 0; i < expected.length; i++) { - expect(output[i]).toBe(expected[i]); - } - expect(output.byteLength).toBe(expected.byteLength); - } else { - const output = await activeFIFO; - expect(output).toBe(decoder.decode(expected)); - } - }); - - it(`highWaterMark -> ${JSON.stringify(label)}`, async () => { - const path = getPathOrFd(); - const sink = Bun.file(path).writer({ highWaterMark: 1 }); - for (let i = 0; i < input.length; i++) { - sink.write(input[i]); - await sink.flush(); - } - await sink.end(); - - if (!isPipe) { - const output = new Uint8Array(await Bun.file(path).arrayBuffer()); - for (let i = 0; i < expected.length; i++) { - expect(output[i]).toBe(expected[i]); - } - expect(output.byteLength).toBe(expected.byteLength); - } else { - const output = await activeFIFO; - expect(output).toBe(decoder.decode(expected)); - } - }); - } - }); - } -}); diff --git a/test/bun.js/filesystem_router.test.ts b/test/bun.js/filesystem_router.test.ts deleted file mode 100644 index b55e716c0..000000000 --- a/test/bun.js/filesystem_router.test.ts +++ /dev/null @@ -1,354 +0,0 @@ -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()) + "/"; - -function createTree(basedir, paths) { - for (const end of paths) { - const abs = path.join(basedir, end); - try { - const dir = dirname(abs); - if (dir.length > 0 && dir !== "/") fs.mkdirSync(dir, { recursive: true }); - } catch (e) {} - fs.writeFileSync(abs, "export default " + JSON.stringify(end) + ";\n"); - } -} -var count = 0; -function make(files) { - const dir = tempdir + `fs-router-test-${count++}`; - rmSync(dir, { - recursive: true, - force: true, - }); - - createTree(dir, files); - if (files.length === 0) mkdirSync(dir, { recursive: true }); - return { - dir, - }; -} - -it("should find files", () => { - const { dir } = make([ - `index.tsx`, - `[id].tsx`, - `a.tsx`, - `abc/index.tsx`, - `abc/[id].tsx`, - `abc/def/[id].tsx`, - `abc/def/ghi/index.tsx`, - `abc/def/ghi/[id].tsx`, - `abc/def/ghi/jkl/index.tsx`, - `abc/def/ghi/jkl/[id].tsx`, - `abc/def/index.tsx`, - `b.tsx`, - `foo/[id].tsx`, - `catch-all/[[...id]].tsx`, - ]); - - const router = new FileSystemRouter({ - dir, - fileExtensions: [".tsx"], - style: "nextjs", - }); - - const routes = router.routes; - const fixture = { - "/": `${dir}/index.tsx`, - "/[id]": `${dir}/[id].tsx`, - "/a": `${dir}/a.tsx`, - "/abc": `${dir}/abc/index.tsx`, - "/abc/[id]": `${dir}/abc/[id].tsx`, - "/abc/def/[id]": `${dir}/abc/def/[id].tsx`, - "/abc/def/ghi": `${dir}/abc/def/ghi/index.tsx`, - "/abc/def/ghi/[id]": `${dir}/abc/def/ghi/[id].tsx`, - "/abc/def/ghi/jkl": `${dir}/abc/def/ghi/jkl/index.tsx`, - "/abc/def/ghi/jkl/[id]": `${dir}/abc/def/ghi/jkl/[id].tsx`, - "/abc/def": `${dir}/abc/def/index.tsx`, - "/b": `${dir}/b.tsx`, - "/foo/[id]": `${dir}/foo/[id].tsx`, - "/catch-all/[[...id]]": `${dir}/catch-all/[[...id]].tsx`, - }; - - for (const route in fixture) { - if (!(route in routes)) { - throw new Error(`Route ${route} not found`); - } - - expect(routes[route]).toBe(fixture[route]); - } - - expect(Object.keys(routes).length).toBe(Object.keys(fixture).length); - expect(Object.values(routes).length).toBe(Object.values(fixture).length); -}); - -it("should handle empty dirs", () => { - const { dir } = make([]); - - const router = new FileSystemRouter({ - dir, - fileExtensions: [".tsx"], - style: "nextjs", - }); - - // assert this doesn't crash - expect(router.bar).toBeUndefined(); - - const routes = router.routes; - expect(Object.keys(routes).length).toBe(0); - expect(Object.values(routes).length).toBe(0); -}); - -it("should match dynamic routes", () => { - // set up the test - const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx"]); - - const router = new Bun.FileSystemRouter({ - dir, - style: "nextjs", - }); - - const { name, filePath } = router.match("/posts/hello-world"); - - expect(name).toBe("/posts/[id]"); - expect(filePath).toBe(`${dir}/posts/[id].tsx`); -}); - -it(".params works on dynamic routes", () => { - // set up the test - const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx"]); - - const router = new Bun.FileSystemRouter({ - dir, - style: "nextjs", - }); - - const { - params: { id }, - } = router.match("/posts/hello-world"); - - expect(id).toBe("hello-world"); -}); - -it("should support static routes", () => { - // set up the test - const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx", "posts/hey.tsx"]); - - const router = new Bun.FileSystemRouter({ - dir, - style: "nextjs", - }); - - const { name, params, filePath } = router.match("/posts/hey"); - - expect(name).toBe("/posts/hey"); - expect(filePath).toBe(`${dir}/posts/hey.tsx`); -}); - -it("should support optional catch-all routes", () => { - // set up the test - const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx", "posts/hey.tsx", "posts/[[...id]].tsx"]); - - const router = new Bun.FileSystemRouter({ - dir, - style: "nextjs", - }); - - for (let fixture of ["/posts/123", "/posts/hey", "/posts/zorp", "/posts", "/index", "/posts/"]) { - expect(router.match(fixture)?.name).not.toBe("/posts/[[...id]]"); - } - - for (let fixture of ["/posts/hey/there", "/posts/hey/there/you", "/posts/zorp/123"]) { - const { name, params, filePath } = router.match(fixture); - - expect(name).toBe("/posts/[[...id]]"); - expect(filePath).toBe(`${dir}/posts/[[...id]].tsx`); - expect(params.id).toBe(fixture.split("/").slice(2).join("/")); - } -}); - -it("should support catch-all routes", () => { - // set up the test - const { dir } = make([ - "index.tsx", - "posts/[id].tsx", - "posts.tsx", - "posts/hey.tsx", - "posts/[...id].tsx", - "posts/wow/[[...id]].tsx", - ]); - - const router = new Bun.FileSystemRouter({ - dir, - style: "nextjs", - }); - - for (let fixture of ["/posts/123", "/posts/hey", "/posts/zorp", "/posts", "/index", "/posts/"]) { - expect(router.match(fixture)?.name).not.toBe("/posts/[...id]"); - } - - for (let fixture of ["/posts/hey/there", "/posts/hey/there/you", "/posts/zorp/123", "/posts/wow/hey/there"]) { - const { name, params, filePath } = router.match(fixture); - - expect(name).toBe("/posts/[...id]"); - expect(filePath).toBe(`${dir}/posts/[...id].tsx`); - expect(params.id).toBe(fixture.split("/").slice(2).join("/")); - } -}); - -it("should support index routes", () => { - // set up the test - const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx", "posts/hey.tsx"]); - - const router = new Bun.FileSystemRouter({ - dir, - style: "nextjs", - }); - - for (let route of ["/", "/index"]) { - const { name, params, filePath } = router.match(route); - - expect(name).toBe("/"); - expect(filePath).toBe(`${dir}/index.tsx`); - expect(Object.keys(params).length).toBe(0); - } - - for (let route of ["/posts", "/posts/index", "/posts/"]) { - const { name, params, filePath } = router.match(route); - - expect(name).toBe("/posts"); - expect(filePath).toBe(`${dir}/posts.tsx`); - expect(Object.keys(params).length).toBe(0); - } -}); - -it("should support Request", async () => { - // set up the test - const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx"]); - - const router = new Bun.FileSystemRouter({ - dir, - style: "nextjs", - }); - - for (let current of [ - new Request({ url: "/posts/hello-world" }), - new Request({ url: "http://example.com/posts/hello-world" }), - ]) { - const { - name, - params: { id }, - filePath, - } = router.match(current); - expect(name).toBe("/posts/[id]"); - expect(filePath).toBe(`${dir}/posts/[id].tsx`); - expect(id).toBe("hello-world"); - } -}); - -it("assetPrefix, src, and origin", async () => { - // set up the test - const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx"]); - - const router = new Bun.FileSystemRouter({ - dir, - style: "nextjs", - assetPrefix: "/_next/static/", - origin: "https://nextjs.org", - }); - - for (let current of [ - // Reuqest - new Request({ url: "/posts/hello-world" }), - new Request({ url: "https://nextjs.org/posts/hello-world" }), - ]) { - const { name, src, filePath, checkThisDoesntCrash } = router.match(current); - expect(name).toBe("/posts/[id]"); - - // check nothing is weird on the MatchedRoute object - expect(checkThisDoesntCrash).toBeUndefined(); - - expect(src).toBe("https://nextjs.org/_next/static/posts/[id].tsx"); - expect(filePath).toBe(`${dir}/posts/[id].tsx`); - } -}); - -it(".query works", () => { - // set up the test - const { dir } = make(["posts.tsx"]); - - const router = new Bun.FileSystemRouter({ - dir, - style: "nextjs", - assetPrefix: "/_next/static/", - origin: "https://nextjs.org", - }); - - for (let [current, object] of [ - [new URL("https://example.com/posts?hello=world").href, { hello: "world" }], - [new URL("https://example.com/posts?hello=world&second=2").href, { hello: "world", second: "2" }], - [ - new URL("https://example.com/posts?hello=world&second=2&third=3").href, - { hello: "world", second: "2", third: "3" }, - ], - [new URL("https://example.com/posts").href, {}], - ]) { - const { name, src, filePath, checkThisDoesntCrash, query } = router.match(current); - expect(name).toBe("/posts"); - - // check nothing is weird on the MatchedRoute object - expect(checkThisDoesntCrash).toBeUndefined(); - - expect(JSON.stringify(query)).toBe(JSON.stringify(object)); - expect(filePath).toBe(`${dir}/posts.tsx`); - } -}); - -it("reload() works", () => { - // set up the test - const { dir } = make(["posts.tsx"]); - - const router = new Bun.FileSystemRouter({ - dir, - style: "nextjs", - assetPrefix: "/_next/static/", - origin: "https://nextjs.org", - }); - - expect(router.match("/posts").name).toBe("/posts"); - router.reload(); - expect(router.match("/posts").name).toBe("/posts"); -}); - -it(".query works with dynamic routes, including params", () => { - // set up the test - const { dir } = make(["posts/[id].tsx"]); - - const router = new Bun.FileSystemRouter({ - dir, - style: "nextjs", - assetPrefix: "/_next/static/", - origin: "https://nextjs.org", - }); - - for (let [current, object] of [ - [new URL("https://example.com/posts/123?hello=world").href, { id: "123", hello: "world" }], - [new URL("https://example.com/posts/123?hello=world&second=2").href, { id: "123", hello: "world", second: "2" }], - [ - new URL("https://example.com/posts/123?hello=world&second=2&third=3").href, - { id: "123", hello: "world", second: "2", third: "3" }, - ], - [new URL("https://example.com/posts/123").href, { id: "123" }], - ]) { - const { name, src, filePath, checkThisDoesntCrash, query } = router.match(current); - expect(name).toBe("/posts/[id]"); - - // check nothing is weird on the MatchedRoute object - expect(checkThisDoesntCrash).toBeUndefined(); - - expect(JSON.stringify(query)).toBe(JSON.stringify(object)); - expect(filePath).toBe(`${dir}/posts/[id].tsx`); - } -}); diff --git a/test/bun.js/fixture.html b/test/bun.js/fixture.html deleted file mode 100644 index 081040506..000000000 --- a/test/bun.js/fixture.html +++ /dev/null @@ -1,1428 +0,0 @@ - - - - - - - Bun is a fast all-in-one JavaScript runtime - - - - - - - - - - - - - - -
-
- Bun - -
-
-
-
-
-

Bun is a fast all-in-one JavaScript runtime

-

- Bundle, transpile, install and run JavaScript & TypeScript projects — all in Bun. Bun is a new - JavaScript runtime with a native bundler, transpiler, task runner and npm client built-in. -

-
-
-
- Install Bun CLI - 0.2.1 - (beta) -
-
- macOS x64 & Silicon, Linux x64, Windows Subsystem for Linux -
-
-
-
curl https://bun.sh/install | bash
- -
- Show script source -
-
-
-
- -
-
-
-

Server-side rendering React

-

HTTP requests per second (Linux x64)

-
    -
  • -
    bun: 69,845 requests per second
    - -
  • -
  • -
    node: 16,288 requests per second
    - -
  • -
  • -
    deno: 12,926 requests per second
    - -
  • -
- -
-
-

WebSocket server chat

-

Messages sent per second (Linux x64, 16 clients)

-
    -
  • -
    bun: 737,280 messages sent per second
    - -
  • -
  • -
    node: 107,457 messages sent per second
    - -
  • -
  • -
    deno: 82,097 messages sent per second
    - -
  • -
- -
-
-

Load a huge table

-

Average queries per second

-
    -
  • -
    bun: 70.32 queries per second
    - -
  • -
  • -
    deno: 36.54 queries per second
    - -
  • -
  • -
    better-sqlite3: 23.28 queries per second
    - -
  • -
- -
-
-
-
-
-
- Install Bun CLI - 0.2.1 - (beta) -
-
macOS x64 & Silicon, Linux x64, Windows Subsystem for Linux
-
-
-
curl https://bun.sh/install | bash
- -
- Show script source -
-
-
-
-
-

Tell me more about Bun

-

- Bun is a modern JavaScript runtime like Node or Deno. It was built from scratch to focus on three main things: -

-
    -
  • Start fast (it has the edge in mind).
  • -
  • New levels of performance (extending JavaScriptCore, the engine).
  • -
  • Being a great and complete tool (bundler, transpiler, package manager).
  • -
-

- Bun is designed as a drop-in replacement for your current JavaScript & TypeScript apps or scripts — on - your local computer, server or on the edge. Bun natively implements hundreds of Node.js and Web APIs, - including ~90% of - Node-API - functions (native modules), fs, path, Buffer and more. -

-

- The goal of Bun is to run most of the world's JavaScript outside of browsers, bringing performance and - complexity enhancements to your future infrastructure, as well as developer productivity through better, - simpler tooling. -

-

Batteries included

-
    -
  • - Web APIs like - fetch, - WebSocket, and - ReadableStream - are built-in -
  • -
  • - node_modules - bun implements Node.js' module resolution algorithm, so you can use npm packages in Bun. ESM and - CommonJS are supported, but Bun internally uses ESM -
  • -
  • - In Bun, every file is transpiled. - TypeScript - & JSX just work -
  • -
  • - Bun supports "paths", "jsxImportSource"and more from - tsconfig.json - files -
  • -
  • - Bun.Transpiler - Bun's JSX & TypeScript transpiler is available as an API in Bun -
  • -
  • - use the fastest system calls available with - Bun.write - to write, copy, pipe, send and clone files -
  • -
  • - Bun automatically loads environment variables from - .env - files. No more - require("dotenv").config() -
  • -
  • - Bun ships with a fast SQLite3 client built-in - bun:sqlite -
  • -
  • - Node-API - Bun implements most of - Node-API (N-API). Many Node.js - native modules just work -
  • -
  • - bun:ffi call native code from JavaScript with Bun's - low-overhead foreign function interface -
  • -
  • - node:fs - node:path Bun natively supports a growing list of - Node.js core modules along with globals like Buffer and process -
  • -
-

How does Bun work?

-

- Bun uses the - JavaScriptCore - engine, which tends - to start - and perform a little faster than more traditional choices like V8. Bun is written in - - Zig - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - , a low-level programming language with manual memory management.

Most of Bun is written from - scratch including the JSX/TypeScript transpiler, npm client, bundler, SQLite client, HTTP client, WebSocket - client and more. -

-

Why is Bun fast?

-

- An enormous amount of time spent profiling, benchmarking and optimizing things. The answer is different for - every part of Bun, but one general theme: - - Zig - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 's low-level control over memory and lack of hidden control flow makes it much simpler to write fast - software. - Sponsor the Zig Software Foundation. -

-

Getting started

-

- To install Bun, run this - install script - in your terminal. It downloads Bun from GitHub. -

-
-
curl https://bun.sh/install | bash
-
-

- Bun's HTTP server is built on web standards like - Request - and - Response -

-
-
// http.js
-export default {
-  port: 3000,
-  fetch(request) {
-    return new Response("Welcome to Bun!");
-  },
-};
-
-

Run it with Bun:

-
-
bun run http.js
-
-

- Then open - http://localhost:3000 - in your browser.

See - more examples - and check out - the docs. If you have any questions or want help, join - Bun's Discord. -

-

Bun CLI

-
- bun run -

- The same command for running JavaScript & TypeScript files with bun's JavaScript runtime also runs - package.json - "scripts". -

- Replace npm run with - bun run and save 160ms on every run.
-
- Bun runs package.json scripts - 30x faster than npm run -
-
-
- bun install -

- bun install is an npm-compatible package manager. You probably will be - surprised by how much faster copying files can get. -

- Replace yarn with - bun install and get 20x faster package installs.
-
bun install uses the fastest system calls available to copy files.
-
-
- bun wiptest -

A Jest-like test runner for JavaScript & TypeScript projects built-in to Bun.

- -
-

What is the license?

-

MIT License, excluding dependencies which have various licenses.

-

How do I see the source code?

-

Bun is on GitHub.

-
-
-
- -
- Built with Bun - 0.2.1 -
- - diff --git a/test/bun.js/fixture.html.gz b/test/bun.js/fixture.html.gz deleted file mode 100644 index 0bb85d4cb..000000000 Binary files a/test/bun.js/fixture.html.gz and /dev/null differ diff --git a/test/bun.js/form-data-fixture.txt b/test/bun.js/form-data-fixture.txt deleted file mode 100644 index a4d20dd78..000000000 --- a/test/bun.js/form-data-fixture.txt +++ /dev/null @@ -1 +0,0 @@ -foo! \ No newline at end of file diff --git a/test/bun.js/fs-stream.js b/test/bun.js/fs-stream.js deleted file mode 100644 index db56fdfaa..000000000 --- a/test/bun.js/fs-stream.js +++ /dev/null @@ -1,21 +0,0 @@ -import { createReadStream, createWriteStream, readFileSync } from "fs"; - -await new Promise((resolve, reject) => { - createReadStream("fs-stream.js") - .pipe(createWriteStream("/tmp/fs-stream.copy.js")) - .once("error", err => reject(err)) - .once("finish", () => { - try { - const copied = readFileSync("/tmp/fs-stream.copy.js", "utf8"); - const real = readFileSync("/tmp/fs-stream.js", "utf8"); - if (copied !== real) { - reject(new Error("fs-stream.js is not the same as fs-stream.copy.js")); - return; - } - - resolve(true); - } catch (err) { - reject(err); - } - }); -}); diff --git a/test/bun.js/fs-stream.link.js b/test/bun.js/fs-stream.link.js deleted file mode 120000 index 0cadae0e5..000000000 --- a/test/bun.js/fs-stream.link.js +++ /dev/null @@ -1 +0,0 @@ -./test/bun.js/fs-stream.js \ No newline at end of file diff --git a/test/bun.js/fs.test.ts b/test/bun.js/fs.test.ts deleted file mode 100644 index 4c847d25a..000000000 --- a/test/bun.js/fs.test.ts +++ /dev/null @@ -1,1016 +0,0 @@ -import { beforeEach, describe, expect, it } from "bun:test"; -import { gc, gcTick } from "./gc"; -import fs, { - closeSync, - existsSync, - mkdirSync, - openSync, - readdirSync, - readFile, - readFileSync, - readSync, - writeFileSync, - writeSync, - statSync, - lstatSync, - copyFileSync, - rmSync, - rmdir, - rmdirSync, - createReadStream, - createWriteStream, - promises, - unlinkSync, - mkdtempSync, - constants, - Dirent, - Stats, -} from "node:fs"; - -import _promises from "node:fs/promises"; - -import { tmpdir } from "node:os"; -import { join } from "node:path"; - -import { ReadStream as ReadStream_, WriteStream as WriteStream_ } from "../fixtures/export-lazy-fs-streams/export-from"; -import { - ReadStream as ReadStreamStar_, - WriteStream as WriteStreamStar_, -} from "../fixtures/export-lazy-fs-streams/export-*-from"; - -const Buffer = globalThis.Buffer || Uint8Array; - -if (!import.meta.dir) { - import.meta.dir = "."; -} - -function mkdirForce(path) { - if (!existsSync(path)) mkdirSync(path, { recursive: true }); -} - -describe("copyFileSync", () => { - it("should work for files < 128 KB", () => { - const tempdir = `/tmp/fs.test.js/${Date.now()}/1234/hi`; - expect(existsSync(tempdir)).toBe(false); - expect(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true); - - // that don't exist - copyFileSync(import.meta.path, tempdir + "/copyFileSync.js"); - expect(existsSync(tempdir + "/copyFileSync.js")).toBe(true); - expect(readFileSync(tempdir + "/copyFileSync.js", "utf-8")).toBe(readFileSync(import.meta.path, "utf-8")); - - // that do exist - copyFileSync(tempdir + "/copyFileSync.js", tempdir + "/copyFileSync.js1"); - writeFileSync(tempdir + "/copyFileSync.js1", "hello"); - copyFileSync(tempdir + "/copyFileSync.js1", tempdir + "/copyFileSync.js"); - - expect(readFileSync(tempdir + "/copyFileSync.js", "utf-8")).toBe("hello"); - }); - - it("should work for files > 128 KB ", () => { - const tempdir = `/tmp/fs.test.js/${Date.now()}-1/1234/hi`; - expect(existsSync(tempdir)).toBe(false); - expect(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true); - var buffer = new Int32Array(128 * 1024); - for (let i = 0; i < buffer.length; i++) { - buffer[i] = i % 256; - } - - const hash = Bun.hash(buffer.buffer); - writeFileSync(tempdir + "/copyFileSync.src.blob", buffer.buffer); - - expect(existsSync(tempdir + "/copyFileSync.dest.blob")).toBe(false); - expect(existsSync(tempdir + "/copyFileSync.src.blob")).toBe(true); - copyFileSync(tempdir + "/copyFileSync.src.blob", tempdir + "/copyFileSync.dest.blob"); - - expect(Bun.hash(readFileSync(tempdir + "/copyFileSync.dest.blob"))).toBe(hash); - buffer[0] = 255; - writeFileSync(tempdir + "/copyFileSync.src.blob", buffer.buffer); - copyFileSync(tempdir + "/copyFileSync.src.blob", tempdir + "/copyFileSync.dest.blob"); - expect(Bun.hash(readFileSync(tempdir + "/copyFileSync.dest.blob"))).toBe(Bun.hash(buffer.buffer)); - }); -}); - -describe("mkdirSync", () => { - it("should create a directory", () => { - const tempdir = `/tmp/fs.test.js/${Date.now()}/1234/hi`; - expect(existsSync(tempdir)).toBe(false); - expect(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true); - expect(existsSync(tempdir)).toBe(true); - }); -}); - -it("readdirSync on import.meta.dir", () => { - const dirs = readdirSync(import.meta.dir); - expect(dirs.length > 0).toBe(true); - var match = false; - gc(true); - for (let i = 0; i < dirs.length; i++) { - if (dirs[i] === import.meta.file) { - match = true; - } - } - gc(true); - expect(match).toBe(true); -}); - -// https://github.com/oven-sh/bun/issues/1887 -it("mkdtempSync, readdirSync, rmdirSync and unlinkSync with non-ascii", () => { - const tempdir = mkdtempSync(`/tmp/emoji-fruit-🍇 🍈 🍉 🍊 🍋`); - expect(existsSync(tempdir)).toBe(true); - writeFileSync(tempdir + "/non-ascii-👍.txt", "hello"); - const dirs = readdirSync(tempdir); - expect(dirs.length > 0).toBe(true); - var match = false; - gc(true); - for (let i = 0; i < dirs.length; i++) { - if (dirs[i].endsWith("non-ascii-👍.txt")) { - match = true; - break; - } - } - gc(true); - expect(match).toBe(true); - unlinkSync(tempdir + "/non-ascii-👍.txt"); - expect(existsSync(tempdir + "/non-ascii-👍.txt")).toBe(false); - rmdirSync(tempdir); - expect(existsSync(tempdir)).toBe(false); -}); - -it("mkdtempSync() empty name", () => { - // @ts-ignore-next-line - const tempdir = mkdtempSync(); - expect(existsSync(tempdir)).toBe(true); - writeFileSync(tempdir + "/non-ascii-👍.txt", "hello"); - const dirs = readdirSync(tempdir); - expect(dirs.length > 0).toBe(true); - var match = false; - gc(true); - for (let i = 0; i < dirs.length; i++) { - if (dirs[i].endsWith("non-ascii-👍.txt")) { - match = true; - break; - } - } - gc(true); - expect(match).toBe(true); - unlinkSync(tempdir + "/non-ascii-👍.txt"); - expect(existsSync(tempdir + "/non-ascii-👍.txt")).toBe(false); - rmdirSync(tempdir); - expect(existsSync(tempdir)).toBe(false); -}); - -it("readdirSync on import.meta.dir with trailing slash", () => { - const dirs = readdirSync(import.meta.dir + "/"); - expect(dirs.length > 0).toBe(true); - // this file should exist in it - var match = false; - for (let i = 0; i < dirs.length; i++) { - if (dirs[i] === import.meta.file) { - match = true; - } - } - expect(match).toBe(true); -}); - -it("readdirSync works on empty directories", () => { - const path = `/tmp/fs-test-empty-dir-${(Math.random() * 100000 + 100).toString(32)}`; - mkdirSync(path, { recursive: true }); - expect(readdirSync(path).length).toBe(0); -}); - -it("readdirSync works on directories with under 32 files", () => { - const path = `/tmp/fs-test-one-dir-${(Math.random() * 100000 + 100).toString(32)}`; - mkdirSync(path, { recursive: true }); - writeFileSync(`${path}/a`, "a"); - const results = readdirSync(path); - expect(results.length).toBe(1); - expect(results[0]).toBe("a"); -}); - -it("readdirSync throws when given a file path", () => { - try { - readdirSync(import.meta.path); - throw new Error("should not get here"); - } catch (exception: any) { - expect(exception.name).toBe("ENOTDIR"); - } -}); - -it("readdirSync throws when given a path that doesn't exist", () => { - try { - readdirSync(import.meta.path + "/does-not-exist/really"); - throw new Error("should not get here"); - } catch (exception: any) { - expect(exception.name).toBe("ENOTDIR"); - } -}); - -it("readdirSync throws when given a file path with trailing slash", () => { - try { - readdirSync(import.meta.path + "/"); - throw new Error("should not get here"); - } catch (exception: any) { - expect(exception.name).toBe("ENOTDIR"); - } -}); - -describe("readSync", () => { - const firstFourBytes = new Uint32Array(new TextEncoder().encode("File").buffer)[0]; - it("works with a position set to 0", () => { - const fd = openSync(import.meta.dir + "/readFileSync.txt", "r"); - const four = new Uint8Array(4); - - { - const count = readSync(fd, four, 0, 4, 0); - const u32 = new Uint32Array(four.buffer)[0]; - expect(u32).toBe(firstFourBytes); - expect(count).toBe(4); - } - closeSync(fd); - }); - it("works without position set", () => { - const fd = openSync(import.meta.dir + "/readFileSync.txt", "r"); - const four = new Uint8Array(4); - { - const count = readSync(fd, four); - const u32 = new Uint32Array(four.buffer)[0]; - expect(u32).toBe(firstFourBytes); - expect(count).toBe(4); - } - closeSync(fd); - }); -}); - -describe("writeSync", () => { - it("works with a position set to 0", () => { - const fd = openSync(import.meta.dir + "/writeFileSync.txt", "w+"); - const four = new Uint8Array(4); - - { - const count = writeSync(fd, new TextEncoder().encode("File"), 0, 4, 0); - expect(count).toBe(4); - } - closeSync(fd); - }); - it("works without position set", () => { - const fd = openSync(import.meta.dir + "/writeFileSync.txt", "w+"); - const four = new Uint8Array(4); - { - const count = writeSync(fd, new TextEncoder().encode("File")); - expect(count).toBe(4); - } - closeSync(fd); - }); -}); - -describe("readFileSync", () => { - it("works", () => { - gc(); - const text = readFileSync(import.meta.dir + "/readFileSync.txt", "utf8"); - gc(); - expect(text).toBe("File read successfully"); - gc(); - }); - - it("works with a file url", () => { - gc(); - const text = readFileSync(new URL("file://" + import.meta.dir + "/readFileSync.txt"), "utf8"); - gc(); - expect(text).toBe("File read successfully"); - }); - - it("works with special files in the filesystem", () => { - { - const text = readFileSync("/dev/null", "utf8"); - gc(); - expect(text).toBe(""); - } - - if (process.platform === "linux") { - const text = readFileSync("/proc/filesystems"); - gc(); - expect(text.length > 0).toBe(true); - } - }); - - it("returning Buffer works", () => { - const text = readFileSync(import.meta.dir + "/readFileSync.txt"); - const encoded = [ - 70, 105, 108, 101, 32, 114, 101, 97, 100, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108, 121, - ]; - for (let i = 0; i < encoded.length; i++) { - expect(text[i]).toBe(encoded[i]); - } - }); -}); - -describe("readFile", () => { - it("works", async () => { - gc(); - await new Promise((resolve, reject) => { - readFile(import.meta.dir + "/readFileSync.txt", "utf8", (err, text) => { - gc(); - expect(text).toBe("File read successfully"); - resolve(true); - }); - }); - }); - - it("returning Buffer works", async () => { - gc(); - await new Promise((resolve, reject) => { - gc(); - readFile(import.meta.dir + "/readFileSync.txt", (err, text) => { - const encoded = [ - 70, 105, 108, 101, 32, 114, 101, 97, 100, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108, 121, - ]; - gc(); - for (let i = 0; i < encoded.length; i++) { - expect(text[i]).toBe(encoded[i]); - } - resolve(true); - }); - }); - }); -}); - -describe("writeFileSync", () => { - it("works", () => { - const path = `/tmp/${Date.now()}.writeFileSync.txt`; - writeFileSync(path, "File written successfully", "utf8"); - - expect(readFileSync(path, "utf8")).toBe("File written successfully"); - }); - - it("returning Buffer works", () => { - const buffer = new Buffer([ - 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 = `/tmp/${Date.now()}.blob.writeFileSync.txt`; - writeFileSync(path, buffer); - const out = readFileSync(path); - - for (let i = 0; i < buffer.length; i++) { - expect(buffer[i]).toBe(out[i]); - } - }); - it("returning ArrayBuffer works", () => { - const buffer = new Buffer([ - 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 = `/tmp/${Date.now()}.blob2.writeFileSync.txt`; - writeFileSync(path, buffer); - const out = readFileSync(path); - - for (let i = 0; i < buffer.length; i++) { - expect(buffer[i]).toBe(out[i]); - } - }); -}); - -function triggerDOMJIT(target, fn, result) { - for (let i = 0; i < 9999; i++) { - if (fn.apply(target) !== result) { - throw new Error("DOMJIT failed"); - } - } -} - -describe("lstat", () => { - it("file metadata is correct", () => { - const fileStats = lstatSync(new URL("./fs-stream.js", import.meta.url).toString().slice("file://".length - 1)); - expect(fileStats.isSymbolicLink()).toBe(false); - expect(fileStats.isFile()).toBe(true); - expect(fileStats.isDirectory()).toBe(false); - - triggerDOMJIT(fileStats, fileStats.isFile, true); - triggerDOMJIT(fileStats, fileStats.isDirectory, false); - triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false); - }); - - it("folder metadata is correct", () => { - const fileStats = lstatSync(new URL("../../test", import.meta.url).toString().slice("file://".length - 1)); - expect(fileStats.isSymbolicLink()).toBe(false); - expect(fileStats.isFile()).toBe(false); - expect(fileStats.isDirectory()).toBe(true); - - triggerDOMJIT(fileStats, fileStats.isFile, false); - triggerDOMJIT(fileStats, fileStats.isDirectory, true); - triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false); - }); - - it("symlink metadata is correct", () => { - const linkStats = lstatSync(new URL("./fs-stream.link.js", import.meta.url).toString().slice("file://".length - 1)); - expect(linkStats.isSymbolicLink()).toBe(true); - expect(linkStats.isFile()).toBe(false); - expect(linkStats.isDirectory()).toBe(false); - - triggerDOMJIT(linkStats, linkStats.isFile, false); - triggerDOMJIT(linkStats, linkStats.isDirectory, false); - triggerDOMJIT(linkStats, linkStats.isSymbolicLink, true); - }); -}); - -describe("stat", () => { - it("file metadata is correct", () => { - const fileStats = statSync(new URL("./fs-stream.js", import.meta.url).toString().slice("file://".length - 1)); - expect(fileStats.isSymbolicLink()).toBe(false); - expect(fileStats.isFile()).toBe(true); - expect(fileStats.isDirectory()).toBe(false); - - triggerDOMJIT(fileStats, fileStats.isFile, true); - triggerDOMJIT(fileStats, fileStats.isDirectory, false); - triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false); - }); - - it("folder metadata is correct", () => { - const fileStats = statSync(new URL("../../test", import.meta.url).toString().slice("file://".length - 1)); - expect(fileStats.isSymbolicLink()).toBe(false); - expect(fileStats.isFile()).toBe(false); - expect(fileStats.isDirectory()).toBe(true); - expect(typeof fileStats.dev).toBe("number"); - expect(typeof fileStats.ino).toBe("number"); - expect(typeof fileStats.mode).toBe("number"); - expect(typeof fileStats.nlink).toBe("number"); - expect(typeof fileStats.uid).toBe("number"); - expect(typeof fileStats.gid).toBe("number"); - expect(typeof fileStats.rdev).toBe("number"); - expect(typeof fileStats.size).toBe("number"); - expect(typeof fileStats.blksize).toBe("number"); - expect(typeof fileStats.blocks).toBe("number"); - expect(typeof fileStats.atimeMs).toBe("number"); - expect(typeof fileStats.mtimeMs).toBe("number"); - expect(typeof fileStats.ctimeMs).toBe("number"); - expect(typeof fileStats.birthtimeMs).toBe("number"); - expect(typeof fileStats.atime).toBe("object"); - expect(typeof fileStats.mtime).toBe("object"); - expect(typeof fileStats.ctime).toBe("object"); - expect(typeof fileStats.birthtime).toBe("object"); - - triggerDOMJIT(fileStats, fileStats.isFile, false); - triggerDOMJIT(fileStats, fileStats.isDirectory, true); - triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false); - }); - - it("stat returns ENOENT", () => { - try { - statSync("/tmp/doesntexist"); - throw "statSync should throw"; - } catch (e: any) { - expect(e.code).toBe("ENOENT"); - } - }); -}); - -describe("rm", () => { - it("removes a file", () => { - const path = `/tmp/${Date.now()}.rm.txt`; - writeFileSync(path, "File written successfully", "utf8"); - expect(existsSync(path)).toBe(true); - rmSync(path); - expect(existsSync(path)).toBe(false); - }); - - it("removes a dir", () => { - const path = `/tmp/${Date.now()}.rm.dir`; - try { - mkdirSync(path); - } catch (e) {} - expect(existsSync(path)).toBe(true); - rmSync(path); - expect(existsSync(path)).toBe(false); - }); - - it("removes a dir recursively", () => { - const path = `/tmp/${Date.now()}.rm.dir/foo/bar`; - try { - mkdirSync(path, { recursive: true }); - } catch (e) {} - expect(existsSync(path)).toBe(true); - rmSync(join(path, "../../"), { recursive: true }); - expect(existsSync(path)).toBe(false); - }); -}); - -describe("rmdir", () => { - it("removes a file", done => { - const path = `/tmp/${Date.now()}.rm.txt`; - writeFileSync(path, "File written successfully", "utf8"); - expect(existsSync(path)).toBe(true); - rmdir(path, err => { - try { - expect(err).toBeDefined(); - expect(err!.code).toBe("EPERM"); - expect(err!.message).toBe("Operation not permitted"); - expect(existsSync(path)).toBe(true); - } catch (e) { - return done(e); - } finally { - done(); - } - }); - }); - - it("removes a dir", done => { - const path = `/tmp/${Date.now()}.rm.dir`; - try { - mkdirSync(path); - } catch (e) {} - expect(existsSync(path)).toBe(true); - rmdir(path, err => { - if (err) return done(err); - expect(existsSync(path)).toBe(false); - done(); - }); - }); - // TODO support `recursive: true` - it("removes a dir recursively", done => { - const path = `/tmp/${Date.now()}.rm.dir/foo/bar`; - try { - mkdirSync(path, { recursive: true }); - } catch (e) {} - expect(existsSync(path)).toBe(true); - rmdir(join(path, "../../"), { recursive: true }, err => { - try { - expect(existsSync(path)).toBe(false); - done(err); - } catch (e) { - return done(e); - } finally { - done(); - } - }); - }); -}); - -describe("rmdirSync", () => { - it("removes a file", () => { - const path = `/tmp/${Date.now()}.rm.txt`; - writeFileSync(path, "File written successfully", "utf8"); - expect(existsSync(path)).toBe(true); - expect(() => { - rmdirSync(path); - }).toThrow("Operation not permitted"); - expect(existsSync(path)).toBe(true); - }); - it("removes a dir", () => { - const path = `/tmp/${Date.now()}.rm.dir`; - try { - mkdirSync(path); - } catch (e) {} - expect(existsSync(path)).toBe(true); - rmdirSync(path); - expect(existsSync(path)).toBe(false); - }); - // TODO support `recursive: true` - it("removes a dir recursively", () => { - const path = `/tmp/${Date.now()}.rm.dir/foo/bar`; - try { - mkdirSync(path, { recursive: true }); - } catch (e) {} - expect(existsSync(path)).toBe(true); - rmdirSync(join(path, "../../"), { recursive: true }); - expect(existsSync(path)).toBe(false); - }); -}); - -describe("createReadStream", () => { - it("works (1 chunk)", async () => { - return await new Promise((resolve, reject) => { - var stream = createReadStream(import.meta.dir + "/readFileSync.txt", {}); - - stream.on("error", e => { - reject(e); - }); - - stream.on("data", chunk => { - expect(chunk instanceof Buffer).toBe(true); - expect(chunk.length).toBe("File read successfully".length); - expect(chunk.toString()).toBe("File read successfully"); - }); - - stream.on("close", () => { - resolve(true); - }); - }); - }); - - it("works (22 chunk)", async () => { - var stream = createReadStream(import.meta.dir + "/readFileSync.txt", { - highWaterMark: 1, - }); - - var data = readFileSync(import.meta.dir + "/readFileSync.txt", "utf8"); - var i = 0; - return await new Promise(resolve => { - stream.on("data", chunk => { - expect(chunk instanceof Buffer).toBe(true); - expect(chunk.length).toBe(1); - expect(chunk.toString()).toBe(data[i++]); - }); - - stream.on("end", () => { - resolve(true); - }); - }); - }); -}); - -describe("fs.WriteStream", () => { - it("should be exported", () => { - expect(fs.WriteStream).toBeDefined(); - }); - - it("should be constructable", () => { - // @ts-ignore-next-line - const stream = new fs.WriteStream("test.txt"); - expect(stream instanceof fs.WriteStream).toBe(true); - }); - - it("should be able to write to a file", done => { - const pathToDir = `${tmpdir()}/${Date.now()}`; - mkdirForce(pathToDir); - const path = join(pathToDir, `fs-writestream-test.txt`); - - // @ts-ignore-next-line - const stream = new fs.WriteStream(path, { flags: "w+" }); - stream.write("Test file written successfully"); - stream.end(); - - stream.on("error", e => { - done(e instanceof Error ? e : new Error(e)); - }); - - stream.on("finish", () => { - expect(readFileSync(path, "utf8")).toBe("Test file written successfully"); - done(); - }); - }); - - it("should work if re-exported by name", () => { - // @ts-ignore-next-line - const stream = new WriteStream_("test.txt"); - expect(stream instanceof WriteStream_).toBe(true); - expect(stream instanceof WriteStreamStar_).toBe(true); - expect(stream instanceof fs.WriteStream).toBe(true); - }); - - it("should work if re-exported by name, called without new", () => { - // @ts-ignore-next-line - const stream = WriteStream_("test.txt"); - expect(stream instanceof WriteStream_).toBe(true); - expect(stream instanceof WriteStreamStar_).toBe(true); - expect(stream instanceof fs.WriteStream).toBe(true); - }); - - it("should work if re-exported, as export * from ...", () => { - // @ts-ignore-next-line - const stream = new WriteStreamStar_("test.txt"); - expect(stream instanceof WriteStream_).toBe(true); - expect(stream instanceof WriteStreamStar_).toBe(true); - expect(stream instanceof fs.WriteStream).toBe(true); - }); - - it("should work if re-exported, as export * from..., called without new", () => { - // @ts-ignore-next-line - const stream = WriteStreamStar_("test.txt"); - expect(stream instanceof WriteStream_).toBe(true); - expect(stream instanceof WriteStreamStar_).toBe(true); - expect(stream instanceof fs.WriteStream).toBe(true); - }); - - it("should be able to write to a file with re-exported WriteStream", done => { - const pathToDir = `${tmpdir()}/${Date.now()}`; - mkdirForce(pathToDir); - const path = join(pathToDir, `fs-writestream-re-exported-test.txt`); - // @ts-ignore-next-line - const stream = new WriteStream_(path, { flags: "w+" }); - stream.write("Test file written successfully"); - stream.end(); - - stream.on("error", e => { - done(e instanceof Error ? e : new Error(e)); - }); - - stream.on("finish", () => { - expect(readFileSync(path, "utf8")).toBe("Test file written successfully"); - done(); - }); - }); -}); - -describe("fs.ReadStream", () => { - it("should be exported", () => { - expect(fs.ReadStream).toBeDefined(); - }); - - it("should be constructable", () => { - // @ts-ignore-next-line - const stream = new fs.ReadStream("test.txt"); - expect(stream instanceof fs.ReadStream).toBe(true); - }); - - it("should be able to read from a file", done => { - const pathToDir = `${tmpdir()}/${Date.now()}`; - mkdirForce(pathToDir); - const path = join(pathToDir, `fs-readstream-test.txt`); - - writeFileSync(path, "Test file written successfully", { - encoding: "utf8", - flag: "w+", - }); - // @ts-ignore-next-line - const stream = new fs.ReadStream(path); - stream.setEncoding("utf8"); - stream.on("error", e => { - done(e instanceof Error ? e : new Error(e)); - }); - - let data = ""; - - stream.on("data", chunk => { - data += chunk; - }); - - stream.on("end", () => { - expect(data).toBe("Test file written successfully"); - done(); - }); - }); - - it("should work if re-exported by name", () => { - // @ts-ignore-next-line - const stream = new ReadStream_("test.txt"); - expect(stream instanceof ReadStream_).toBe(true); - expect(stream instanceof ReadStreamStar_).toBe(true); - expect(stream instanceof fs.ReadStream).toBe(true); - }); - - it("should work if re-exported by name, called without new", () => { - // @ts-ignore-next-line - const stream = ReadStream_("test.txt"); - expect(stream instanceof ReadStream_).toBe(true); - expect(stream instanceof ReadStreamStar_).toBe(true); - expect(stream instanceof fs.ReadStream).toBe(true); - }); - - it("should work if re-exported as export * from ...", () => { - // @ts-ignore-next-line - const stream = new ReadStreamStar_("test.txt"); - expect(stream instanceof ReadStreamStar_).toBe(true); - expect(stream instanceof ReadStream_).toBe(true); - expect(stream instanceof fs.ReadStream).toBe(true); - }); - - it("should work if re-exported as export * from ..., called without new", () => { - // @ts-ignore-next-line - const stream = ReadStreamStar_("test.txt"); - expect(stream instanceof ReadStreamStar_).toBe(true); - expect(stream instanceof ReadStream_).toBe(true); - expect(stream instanceof fs.ReadStream).toBe(true); - }); - - it("should be able to read from a file, with re-exported ReadStream", done => { - const pathToDir = `${tmpdir()}/${Date.now()}`; - mkdirForce(pathToDir); - const path = join(pathToDir, `fs-readstream-re-exported-test.txt`); - - writeFileSync(path, "Test file written successfully", { - encoding: "utf8", - flag: "w+", - }); - - // @ts-ignore-next-line - const stream = new ReadStream_(path); - stream.setEncoding("utf8"); - stream.on("error", e => { - done(e instanceof Error ? e : new Error(e)); - }); - - let data = ""; - - stream.on("data", chunk => { - data += chunk; - }); - - stream.on("end", () => { - expect(data).toBe("Test file written successfully"); - done(); - }); - }); -}); - -describe("createWriteStream", () => { - it("simple write stream finishes", async () => { - const path = `/tmp/fs.test.js/${Date.now()}.createWriteStream.txt`; - const stream = createWriteStream(path); - stream.write("Test file written successfully"); - stream.end(); - - return await new Promise((resolve, reject) => { - stream.on("error", e => { - reject(e); - }); - - stream.on("finish", () => { - expect(readFileSync(path, "utf8")).toBe("Test file written successfully"); - resolve(true); - }); - }); - }); - - it("writing null throws ERR_STREAM_NULL_VALUES", async () => { - const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamNulls.txt`; - const stream = createWriteStream(path); - try { - stream.write(null); - expect(() => {}).toThrow(Error); - } catch (exception: any) { - expect(exception.code).toBe("ERR_STREAM_NULL_VALUES"); - } - }); - - it("writing null throws ERR_STREAM_NULL_VALUES (objectMode: true)", async () => { - const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamNulls.txt`; - const stream = createWriteStream(path, { - // @ts-ignore-next-line - objectMode: true, - }); - try { - stream.write(null); - expect(() => {}).toThrow(Error); - } catch (exception: any) { - expect(exception.code).toBe("ERR_STREAM_NULL_VALUES"); - } - }); - - it("writing false throws ERR_INVALID_ARG_TYPE", async () => { - const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamFalse.txt`; - const stream = createWriteStream(path); - try { - stream.write(false); - expect(() => {}).toThrow(Error); - } catch (exception: any) { - expect(exception.code).toBe("ERR_INVALID_ARG_TYPE"); - } - }); - - it("writing false throws ERR_INVALID_ARG_TYPE (objectMode: true)", async () => { - const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamFalse.txt`; - const stream = createWriteStream(path, { - // @ts-ignore-next-line - objectMode: true, - }); - try { - stream.write(false); - expect(() => {}).toThrow(Error); - } catch (exception: any) { - expect(exception.code).toBe("ERR_INVALID_ARG_TYPE"); - } - }); -}); - -describe("fs/promises", () => { - const { exists, mkdir, readFile, rmdir, stat, writeFile } = promises; - - it("should not segfault on exception", async () => { - try { - await stat("foo/bar"); - } catch (e) {} - }); - - it("readFile", async () => { - const data = await readFile(import.meta.dir + "/readFileSync.txt", "utf8"); - expect(data).toBe("File read successfully"); - }); - - it("writeFile", async () => { - const path = `/tmp/fs.test.js/${Date.now()}.writeFile.txt`; - await writeFile(path, "File written successfully"); - expect(readFileSync(path, "utf8")).toBe("File written successfully"); - }); - - it("readdir()", async () => { - const files = await promises.readdir(import.meta.dir); - expect(files.length).toBeGreaterThan(0); - }); - - it("readdir() no args doesnt segfault", async () => { - const fizz = [ - [], - [Symbol("ok")], - [Symbol("ok"), Symbol("ok")], - [Symbol("ok"), Symbol("ok"), Symbol("ok")], - [Infinity, -NaN, -Infinity], - "\0\0\0\0", - "\r\n", - ]; - for (const args of fizz) { - try { - // check it doens't segfault when called with invalid arguments - await promises.readdir(...(args as [any, ...any[]])); - } catch (e) { - // check that producing the error doesn't cause any crashes - Bun.inspect(e); - } - } - }); - - describe("rmdir", () => { - it("removes a file", async () => { - const path = `/tmp/${Date.now()}.rm.txt`; - await writeFile(path, "File written successfully", "utf8"); - expect(await exists(path)).toBe(true); - try { - await rmdir(path); - expect(() => {}).toThrow(); - } catch (err: any) { - expect(err.code).toBe("ENOTDIR"); - // expect(err.message).toBe("Operation not permitted"); - expect(await exists(path)).toBe(true); - } - }); - - it("removes a dir", async () => { - const path = `/tmp/${Date.now()}.rm.dir`; - try { - await mkdir(path); - } catch (e) {} - expect(await exists(path)).toBe(true); - await rmdir(path); - expect(await exists(path)).toBe(false); - }); - // TODO support `recursive: true` - // it("removes a dir recursively", async () => { - // const path = `/tmp/${Date.now()}.rm.dir/foo/bar`; - // try { - // await mkdir(path, { recursive: true }); - // } catch (e) {} - // expect(await exists(path)).toBe(true); - // await rmdir(join(path, "../../"), { recursive: true }); - // expect(await exists(path)).toBe(false); - // }); - }); -}); - -it("fs.constants", () => { - expect(constants).toBeDefined(); - expect(constants.F_OK).toBeDefined(); - expect(constants.R_OK).toBeDefined(); - expect(constants.W_OK).toBeDefined(); - expect(constants.X_OK).toBeDefined(); - expect(constants.O_RDONLY).toBeDefined(); - expect(constants.O_WRONLY).toBeDefined(); - expect(constants.O_RDWR).toBeDefined(); - expect(constants.O_CREAT).toBeDefined(); - expect(constants.O_EXCL).toBeDefined(); - expect(constants.O_NOCTTY).toBeDefined(); - expect(constants.O_TRUNC).toBeDefined(); - expect(constants.O_APPEND).toBeDefined(); - expect(constants.O_DIRECTORY).toBeDefined(); - expect(constants.O_NOATIME).toBeDefined(); - expect(constants.O_NOFOLLOW).toBeDefined(); - expect(constants.O_SYNC).toBeDefined(); - expect(constants.O_DSYNC).toBeDefined(); - expect(constants.O_SYMLINK).toBeDefined(); - expect(constants.O_DIRECT).toBeDefined(); - expect(constants.O_NONBLOCK).toBeDefined(); - expect(constants.S_IFMT).toBeDefined(); - expect(constants.S_IFREG).toBeDefined(); - expect(constants.S_IFDIR).toBeDefined(); - expect(constants.S_IFCHR).toBeDefined(); - expect(constants.S_IFBLK).toBeDefined(); - expect(constants.S_IFIFO).toBeDefined(); - expect(constants.S_IFLNK).toBeDefined(); - expect(constants.S_IFSOCK).toBeDefined(); - expect(constants.S_IRWXU).toBeDefined(); - expect(constants.S_IRUSR).toBeDefined(); - expect(constants.S_IWUSR).toBeDefined(); - expect(constants.S_IXUSR).toBeDefined(); - expect(constants.S_IRWXG).toBeDefined(); - expect(constants.S_IRGRP).toBeDefined(); - expect(constants.S_IWGRP).toBeDefined(); - expect(constants.S_IXGRP).toBeDefined(); - expect(constants.S_IRWXO).toBeDefined(); - expect(constants.S_IROTH).toBeDefined(); - expect(constants.S_IWOTH).toBeDefined(); -}); - -it("fs.Dirent", () => { - expect(Dirent).toBeDefined(); -}); - -it("fs.Stats", () => { - expect(Stats).toBeDefined(); -}); - -it("repro 1516: can use undefined/null to specify default flag", () => { - const path = "/tmp/repro_1516.txt"; - writeFileSync(path, "b", { flag: undefined }); - // @ts-ignore-next-line - expect(readFileSync(path, { encoding: "utf8", flag: null })).toBe("b"); - rmSync(path); -}); diff --git a/test/bun.js/gc.ts b/test/bun.js/gc.ts deleted file mode 100644 index b9d80116d..000000000 --- a/test/bun.js/gc.ts +++ /dev/null @@ -1,26 +0,0 @@ -export function gc(force: boolean = true) { - Bun.gc(force); -} - -// we must ensure that finalizers are run -// so that the reference-counting logic is exercised -export function gcTick(trace = false) { - trace && console.trace(""); - // console.trace("hello"); - gc(); - return new Promise(resolve => { - setTimeout(resolve, 0); - }); -} - -export function withoutAggressiveGC(block) { - if (!Bun.unsafe.gcAggressionLevel) return block(); - - const origGC = Bun.unsafe.gcAggressionLevel(); - Bun.unsafe.gcAggressionLevel(0); - try { - return block(); - } finally { - Bun.unsafe.gcAggressionLevel(origGC); - } -} diff --git a/test/bun.js/globals.test.js b/test/bun.js/globals.test.js deleted file mode 100644 index 5d491cde7..000000000 --- a/test/bun.js/globals.test.js +++ /dev/null @@ -1,51 +0,0 @@ -import { it, describe, expect } from "bun:test"; - -it("extendable", () => { - const classes = [Blob, TextDecoder, TextEncoder, Request, Response, Headers, HTMLRewriter, Bun.Transpiler, Buffer]; - // None of these should error - for (let Class of classes) { - var Foo = class extends Class {}; - var bar = new Foo(); - expect(bar instanceof Class).toBe(true); - expect(!!Class.prototype).toBe(true); - expect(typeof Class.prototype).toBe("object"); - } - expect(true).toBe(true); -}); - -it("writable", () => { - const classes = [ - // ["Blob", Blob], - ["TextDecoder", TextDecoder], - // ["TextEncoder", TextEncoder], - ["Request", Request], - ["Response", Response], - ["Headers", Headers], - ["Buffer", Buffer], - // ["HTMLRewriter", HTMLRewriter], - // ["Transpiler", Bun.Transpiler], - ]; - for (let [name, Class] of classes) { - globalThis[name] = 123; - expect(globalThis[name]).toBe(123); - globalThis[name] = Class; - expect(globalThis[name]).toBe(Class); - } -}); - -it("name", () => { - const classes = [ - ["Blob", Blob], - ["TextDecoder", TextDecoder], - ["TextEncoder", TextEncoder], - ["Request", Request], - ["Response", Response], - ["Headers", Headers], - ["HTMLRewriter", HTMLRewriter], - ["Transpiler", Bun.Transpiler], - ["Buffer", Buffer], - ]; - for (let [name, Class] of classes) { - expect(Class.name).toBe(name); - } -}); diff --git a/test/bun.js/google-fixture.html b/test/bun.js/google-fixture.html deleted file mode 100644 index a5042789e..000000000 --- a/test/bun.js/google-fixture.html +++ /dev/null @@ -1,632 +0,0 @@ - - - - - - - - Google - - - - - - - -
-
- Search Images - Maps - Play - YouTube - News - Gmail - Drive - More » -
- -
-
-
-
-
-
-

-
-
- - - - - - -
  - -
- -
-
- - -
- Advanced search -
- - -
-
-

- -

- © 2022 - Privacy - Terms -

-
- - - - - diff --git a/test/bun.js/hash.test.js b/test/bun.js/hash.test.js deleted file mode 100644 index 0c1baaf14..000000000 --- a/test/bun.js/hash.test.js +++ /dev/null @@ -1,47 +0,0 @@ -import fs from "fs"; -import { it, expect } from "bun:test"; -import path from "path"; -import { gcTick } from "gc"; - -it(`Bun.hash()`, () => { - gcTick(); - Bun.hash("hello world"); - Bun.hash(new TextEncoder().encode("hello world")); -}); -it(`Bun.hash.wyhash()`, () => { - Bun.hash.wyhash("hello world"); - gcTick(); - Bun.hash.wyhash(new TextEncoder().encode("hello world")); -}); -it(`Bun.hash.adler32()`, () => { - Bun.hash.adler32("hello world"); - gcTick(); - Bun.hash.adler32(new TextEncoder().encode("hello world")); -}); -it(`Bun.hash.crc32()`, () => { - Bun.hash.crc32("hello world"); - gcTick(); - Bun.hash.crc32(new TextEncoder().encode("hello world")); -}); -it(`Bun.hash.cityHash32()`, () => { - Bun.hash.cityHash32("hello world"); - gcTick(); - Bun.hash.cityHash32(new TextEncoder().encode("hello world")); - gcTick(); -}); -it(`Bun.hash.cityHash64()`, () => { - Bun.hash.cityHash64("hello world"); - gcTick(); - Bun.hash.cityHash64(new TextEncoder().encode("hello world")); - gcTick(); -}); -it(`Bun.hash.murmur32v3()`, () => { - Bun.hash.murmur32v3("hello world"); - gcTick(); - Bun.hash.murmur32v3(new TextEncoder().encode("hello world")); -}); -it(`Bun.hash.murmur64v2()`, () => { - Bun.hash.murmur64v2("hello world"); - gcTick(); - Bun.hash.murmur64v2(new TextEncoder().encode("hello world")); -}); diff --git a/test/bun.js/hello-wasi.wasm b/test/bun.js/hello-wasi.wasm deleted file mode 100755 index e134bcee4..000000000 Binary files a/test/bun.js/hello-wasi.wasm and /dev/null differ diff --git a/test/bun.js/hello.svelte b/test/bun.js/hello.svelte deleted file mode 100644 index 05dac4294..000000000 --- a/test/bun.js/hello.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - -

Hello {name}!

diff --git a/test/bun.js/hello2.svelte b/test/bun.js/hello2.svelte deleted file mode 100644 index 05dac4294..000000000 --- a/test/bun.js/hello2.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - -

Hello {name}!

diff --git a/test/bun.js/hot-runner-imported.js b/test/bun.js/hot-runner-imported.js deleted file mode 100644 index a763a17d6..000000000 --- a/test/bun.js/hot-runner-imported.js +++ /dev/null @@ -1,3 +0,0 @@ -globalThis.importedCounter ??= 0; - -console.log(`[${Date.now()}] [#!imported] Reloaded: ${++globalThis.importedCounter}`); diff --git a/test/bun.js/hot-runner.js b/test/bun.js/hot-runner.js deleted file mode 100644 index eda65f0b9..000000000 --- a/test/bun.js/hot-runner.js +++ /dev/null @@ -1,6 +0,0 @@ -import "./hot-runner-imported"; - -globalThis.counter ??= 0; - -console.log(`[${Date.now()}] [#!root] Reloaded: ${++globalThis.counter}`); -!setTimeout(() => {}, 9999999); diff --git a/test/bun.js/hot.test.ts b/test/bun.js/hot.test.ts deleted file mode 100644 index 174515507..000000000 --- a/test/bun.js/hot.test.ts +++ /dev/null @@ -1,251 +0,0 @@ -import { spawn } from "bun"; -import { expect, it } from "bun:test"; -import { bunEnv } from "bunEnv"; -import { bunExe } from "bunExe"; -import { readFileSync, renameSync, rmSync, unlinkSync, writeFileSync } from "fs"; - -it("should hot reload when file is overwritten", async () => { - const root = import.meta.dir + "/hot-runner.js"; - const runner = spawn({ - cmd: [bunExe(), "--hot", "run", root], - env: bunEnv, - stdout: "pipe", - stderr: "inherit", - stdin: "ignore", - }); - - var reloadCounter = 0; - - async function onReload() { - writeFileSync(root, readFileSync(root, "utf-8")); - } - - for await (const line of runner.stdout!) { - var str = new TextDecoder().decode(line); - var any = false; - for (let line of str.split("\n")) { - if (!line.includes("[#!root]")) continue; - reloadCounter++; - - if (reloadCounter === 3) { - runner.unref(); - runner.kill(); - break; - } - - expect(line).toContain(`[#!root] Reloaded: ${reloadCounter}`); - any = true; - } - - if (any) await onReload(); - } - - expect(reloadCounter).toBe(3); -}); - -it("should recover from errors", async () => { - const root = import.meta.dir + "/hot-runner.js"; - const runner = spawn({ - cmd: [bunExe(), "--hot", "run", root], - env: bunEnv, - stdout: "pipe", - stderr: "pipe", - stdin: "ignore", - }); - - let reloadCounter = 0; - const input = readFileSync(root, "utf-8"); - function onReloadGood() { - writeFileSync(root, input); - } - - function onReloadError() { - writeFileSync(root, "throw new Error('error');\n"); - } - - var queue = [onReloadError, onReloadGood, onReloadError, onReloadGood]; - var errors: string[] = []; - var onError; - (async () => { - for await (let line of runner.stderr!) { - var str = new TextDecoder().decode(line); - errors.push(str); - onError && onError(str); - } - })(); - - for await (const line of runner.stdout!) { - var str = new TextDecoder().decode(line); - var any = false; - for (let line of str.split("\n")) { - if (!line.includes("[#!root]")) continue; - reloadCounter++; - - if (reloadCounter === 3) { - runner.unref(); - runner.kill(); - break; - } - - expect(line).toContain(`[#!root] Reloaded: ${reloadCounter}`); - any = true; - } - - if (any) { - queue.shift()!(); - await new Promise((resolve, reject) => { - if (errors.length > 0) { - errors.length = 0; - resolve(); - return; - } - - onError = resolve; - }); - - queue.shift()!(); - } - } - - expect(reloadCounter).toBe(3); -}); - -it("should not hot reload when a random file is written", async () => { - const root = import.meta.dir + "/hot-runner.js"; - const runner = spawn({ - cmd: [bunExe(), "--hot", "run", root], - env: bunEnv, - stdout: "pipe", - stderr: "inherit", - stdin: "ignore", - }); - - let reloadCounter = 0; - const code = readFileSync(root, "utf-8"); - async function onReload() { - writeFileSync(root + ".another.yet.js", code); - unlinkSync(root + ".another.yet.js"); - } - var waiter = new Promise((resolve, reject) => { - setTimeout(async () => { - resolve(); - }, 50); - }); - var finished = false; - await Promise.race([ - waiter, - (async () => { - if (finished) { - return; - } - for await (const line of runner.stdout!) { - if (finished) { - return; - } - - var str = new TextDecoder().decode(line); - for (let line of str.split("\n")) { - if (!line.includes("[#!root]")) continue; - if (finished) { - return; - } - await onReload(); - - reloadCounter++; - expect(line).toContain(`[#!root] Reloaded: ${reloadCounter}`); - } - } - })(), - ]); - finished = true; - runner.kill(0); - runner.unref(); - - expect(reloadCounter).toBe(1); -}); - -it("should hot reload when a file is deleted and rewritten", async () => { - const root = import.meta.dir + "/hot-runner.js"; - const runner = spawn({ - cmd: [bunExe(), "--hot", "run", root], - env: bunEnv, - stdout: "pipe", - stderr: "inherit", - stdin: "ignore", - }); - - var reloadCounter = 0; - - async function onReload() { - const contents = readFileSync(root, "utf-8"); - unlinkSync(root); - writeFileSync(root, contents); - } - - for await (const line of runner.stdout!) { - var str = new TextDecoder().decode(line); - var any = false; - for (let line of str.split("\n")) { - if (!line.includes("[#!root]")) continue; - reloadCounter++; - - if (reloadCounter === 3) { - runner.unref(); - runner.kill(); - break; - } - - expect(line).toContain(`[#!root] Reloaded: ${reloadCounter}`); - any = true; - } - - if (any) await onReload(); - } - - expect(reloadCounter).toBe(3); -}); - -it("should hot reload when a file is renamed() into place", async () => { - const root = import.meta.dir + "/hot-runner.js"; - const runner = spawn({ - cmd: [bunExe(), "--hot", "run", root], - env: bunEnv, - stdout: "pipe", - stderr: "inherit", - stdin: "ignore", - }); - - var reloadCounter = 0; - - async function onReload() { - const contents = readFileSync(root, "utf-8"); - rmSync(root + ".tmpfile", { force: true }); - await 1; - writeFileSync(root + ".tmpfile", contents); - await 1; - renameSync(root + ".tmpfile", root); - await 1; - } - - for await (const line of runner.stdout!) { - var str = new TextDecoder().decode(line); - var any = false; - for (let line of str.split("\n")) { - if (!line.includes("[#!root]")) continue; - reloadCounter++; - - if (reloadCounter === 3) { - runner.unref(); - runner.kill(); - break; - } - - expect(line).toContain(`[#!root] Reloaded: ${reloadCounter}`); - any = true; - } - - if (any) await onReload(); - } - - expect(reloadCounter).toBe(3); -}); diff --git a/test/bun.js/html-rewriter.test.js b/test/bun.js/html-rewriter.test.js deleted file mode 100644 index e33710f28..000000000 --- a/test/bun.js/html-rewriter.test.js +++ /dev/null @@ -1,303 +0,0 @@ -import { describe, it, expect } from "bun:test"; -import { gcTick } from "./gc"; - -var setTimeoutAsync = (fn, delay) => { - return new Promise((resolve, reject) => { - setTimeout(() => { - try { - resolve(fn()); - } catch (e) { - reject(e); - } - }, delay); - }); -}; - -describe("HTMLRewriter", () => { - it("HTMLRewriter: async replacement", async () => { - await gcTick(); - const res = new HTMLRewriter() - .on("div", { - async element(element) { - await setTimeoutAsync(() => { - element.setInnerContent("replace", { html: true }); - }, 5); - }, - }) - .transform(new Response("
example.com
")); - await gcTick(); - expect(await res.text()).toBe("
replace
"); - await gcTick(); - }); - - it("supports element handlers", async () => { - var rewriter = new HTMLRewriter(); - rewriter.on("div", { - element(element) { - element.setInnerContent("it worked!", { html: true }); - }, - }); - var input = new Response("
hello
"); - var output = rewriter.transform(input); - expect(await output.text()).toBe("
it worked!
"); - }); - - it("(from file) supports element handlers", async () => { - var rewriter = new HTMLRewriter(); - rewriter.on("div", { - element(element) { - element.setInnerContent("it worked!", { html: true }); - }, - }); - await Bun.write("/tmp/html-rewriter.txt.js", "
hello
"); - var input = new Response(Bun.file("/tmp/html-rewriter.txt.js")); - var output = rewriter.transform(input); - expect(await output.text()).toBe("
it worked!
"); - }); - - it("supports attribute iterator", async () => { - var rewriter = new HTMLRewriter(); - var expected = [ - ["first", ""], - ["second", "alrihgt"], - ["third", "123"], - ["fourth", "5"], - ["fifth", "helloooo"], - ]; - rewriter.on("div", { - element(element2) { - for (let attr of element2.attributes) { - const stack = expected.shift(); - expect(stack[0]).toBe(attr[0]); - expect(stack[1]).toBe(attr[1]); - } - }, - }); - var input = new Response('
hello
'); - var output = rewriter.transform(input); - expect(await output.text()).toBe('
hello
'); - expect(expected.length).toBe(0); - }); - - it("handles element specific mutations", async () => { - // prepend/append - let res = new HTMLRewriter() - .on("p", { - element(element) { - element.prepend("prepend"); - element.prepend("prepend html", { html: true }); - element.append("append"); - element.append("append html", { html: true }); - }, - }) - .transform(new Response("

test

")); - expect(await res.text()).toBe( - [ - "

", - "prepend html", - "<span>prepend</span>", - "test", - "<span>append</span>", - "append html", - "

", - ].join(""), - ); - - // setInnerContent - res = new HTMLRewriter() - .on("p", { - element(element) { - element.setInnerContent("replace"); - }, - }) - .transform(new Response("

test

")); - expect(await res.text()).toBe("

<span>replace</span>

"); - res = new HTMLRewriter() - .on("p", { - element(element) { - element.setInnerContent("replace", { html: true }); - }, - }) - .transform(new Response("

test

")); - expect(await res.text()).toBe("

replace

"); - - // removeAndKeepContent - res = new HTMLRewriter() - .on("p", { - element(element) { - element.removeAndKeepContent(); - }, - }) - .transform(new Response("

test

")); - expect(await res.text()).toBe("test"); - }); - - it("handles element class properties", async () => { - class Handler { - constructor(content) { - this.content = content; - } - - // noinspection JSUnusedGlobalSymbols - element(element) { - element.setInnerContent(this.content); - } - } - const res = new HTMLRewriter().on("p", new Handler("new")).transform(new Response("

test

")); - expect(await res.text()).toBe("

new

"); - }); - - const commentsMutationsInput = "

"; - const commentsMutationsExpected = { - beforeAfter: [ - "

", - "<span>before</span>", - "before html", - "", - "after html", - "<span>after</span>", - "

", - ].join(""), - replace: "

<span>replace</span>

", - replaceHtml: "

replace

", - remove: "

", - }; - - const commentPropertiesMacro = async func => { - const res = func(new HTMLRewriter(), comment => { - expect(comment.removed).toBe(false); - expect(comment.text).toBe("test"); - comment.text = "new"; - expect(comment.text).toBe("new"); - }).transform(new Response("

")); - expect(await res.text()).toBe("

"); - }; - - it("HTMLRewriter: handles comment properties", () => - commentPropertiesMacro((rw, comments) => { - rw.on("p", { comments }); - return rw; - })); - - it("selector tests", async () => { - const checkSelector = async (selector, input, expected) => { - const res = new HTMLRewriter() - .on(selector, { - element(element) { - element.setInnerContent("new"); - }, - }) - .transform(new Response(input)); - expect(await res.text()).toBe(expected); - }; - - await checkSelector("*", "

1

2

", "

new

new

"); - await checkSelector("p", "

1

2

", "

1

new

"); - await checkSelector( - "p:nth-child(2)", - "

1

2

3

", - "

1

new

3

", - ); - await checkSelector( - "p:first-child", - "

1

2

3

", - "

new

2

3

", - ); - await checkSelector( - "p:nth-of-type(2)", - "

1

2

3

4

5

", - "

1

2

new

4

5

", - ); - await checkSelector( - "p:first-of-type", - "

1

2

3

", - "

1

new

3

", - ); - await checkSelector( - "p:not(:first-child)", - "

1

2

3

", - "

1

new

new

", - ); - await checkSelector("p.red", '

1

2

', '

new

2

'); - await checkSelector("h1#header", '

1

2

', '

new

2

'); - await checkSelector("p[data-test]", "

1

2

", "

new

2

"); - await checkSelector( - 'p[data-test="one"]', - '

1

2

', - '

new

2

', - ); - await checkSelector( - 'p[data-test="one" i]', - '

1

2

3

', - '

new

new

3

', - ); - await checkSelector( - 'p[data-test="one" s]', - '

1

2

3

', - '

new

2

3

', - ); - await checkSelector( - 'p[data-test~="two"]', - '

1

2

3

', - '

new

new

3

', - ); - await checkSelector( - 'p[data-test^="a"]', - '

1

2

3

', - '

new

new

3

', - ); - await checkSelector( - 'p[data-test$="1"]', - '

1

2

3

', - '

new

2

new

', - ); - await checkSelector( - 'p[data-test*="b"]', - '

1

2

3

', - '

new

new

3

', - ); - await checkSelector( - 'p[data-test|="a"]', - '

1

2

3

', - '

new

new

3

', - ); - await checkSelector( - "div span", - "

1

23
", - "

new

new3
", - ); - await checkSelector( - "div > span", - "

1

23
", - "

1

new3
", - ); - }); - - it("supports deleting innerContent", async () => { - expect( - await new HTMLRewriter() - .on("div", { - element(elem) { - // https://github.com/oven-sh/bun/issues/2323 - elem.setInnerContent(""); - }, - }) - .transform(new Response("
content
")) - .text(), - ).toEqual("
"); - }); - - it("supports deleting innerHTML", async () => { - expect( - await new HTMLRewriter() - .on("div", { - element(elem) { - // https://github.com/oven-sh/bun/issues/2323 - elem.setInnerContent("", { html: true }); - }, - }) - .transform(new Response("
content
")) - .text(), - ).toEqual("
"); - }); -}); diff --git a/test/bun.js/import-meta.test.js b/test/bun.js/import-meta.test.js deleted file mode 100644 index 3f89c09f0..000000000 --- a/test/bun.js/import-meta.test.js +++ /dev/null @@ -1,164 +0,0 @@ -import { it, expect } from "bun:test"; -import { mkdirSync, rmSync, writeFileSync } from "node:fs"; -import * as Module from "node:module"; -import sync from "./require-json.json"; - -const { path, dir } = import.meta; - -it("primordials are not here!", () => { - expect(import.meta.primordials === undefined).toBe(true); -}); - -it("import.meta.resolveSync", () => { - expect(import.meta.resolveSync("./" + import.meta.file, import.meta.path)).toBe(path); - const require = Module.createRequire(import.meta.path); - expect(require.resolve(import.meta.path)).toBe(path); - expect(require.resolve("./" + import.meta.file)).toBe(path); - - // check it works with URL objects - expect(Module.createRequire(new URL(import.meta.url)).resolve(import.meta.path)).toBe(import.meta.path); -}); - -it("require with a query string works on dynamically created content", () => { - rmSync("/tmp/bun-test-import-meta-dynamic-dir", { - recursive: true, - force: true, - }); - try { - const require = Module.createRequire("/tmp/bun-test-import-meta-dynamic-dir/foo.js"); - try { - require("./bar.js?query=123"); - } catch (e) { - expect(e.name).toBe("ResolveError"); - } - - mkdirSync("/tmp/bun-test-import-meta-dynamic-dir", { recursive: true }); - - writeFileSync("/tmp/bun-test-import-meta-dynamic-dir/bar.js", "export default 'hello';", "utf8"); - - expect(require("./bar.js?query=123").default).toBe("hello"); - } catch (e) { - throw e; - } finally { - rmSync("/tmp/bun-test-import-meta-dynamic-dir", { - recursive: true, - force: true, - }); - } -}); - -it("import.meta.require (json)", () => { - expect(import.meta.require("./require-json.json").hello).toBe(sync.hello); - const require = Module.createRequire(import.meta.path); - expect(require("./require-json.json").hello).toBe(sync.hello); -}); - -it("const f = require;require(json)", () => { - const f = require; - console.log(f); - expect(f("./require-json.json").hello).toBe(sync.hello); -}); - -it("Module.createRequire().resolve", () => { - const expected = Bun.resolveSync("./require-json.json", import.meta.dir); - - const createdRequire = Module.createRequire(import.meta.path); - const result = createdRequire.resolve("./require-json.json"); - - expect(result).toBe(expected); -}); - -// this is stubbed out -it("Module._nodeModulePaths()", () => { - const expected = Module._nodeModulePaths(); - expect(!!expected).toBe(true); -}); - -// this isn't used in bun but exists anyway -// we just want it to not be undefined -it("Module._cache", () => { - const expected = typeof Module._cache === "object" && Module._cache; - expect(!!expected).toBe(true); -}); - -it("Module._resolveFileName()", () => { - const expected = Bun.resolveSync(import.meta.path, "/"); - const result = Module._resolveFileName(import.meta.path, "/", true); - expect(result).toBe(expected); -}); - -it("Module.createRequire(file://url).resolve(file://url)", () => { - const expected = Bun.resolveSync("./require-json.json", import.meta.dir); - - const createdRequire = Module.createRequire(import.meta.url); - const result1 = createdRequire.resolve("./require-json.json"); - const result2 = createdRequire.resolve("file://./require-json.json"); - - expect(result1).toBe(expected); - expect(result2).toBe(expected); -}); - -it("import.meta.require.resolve", () => { - const expected = Bun.resolveSync("./require-json.json", import.meta.dir); - var { resolve } = import.meta.require; - const result = resolve("./require-json.json"); - expect(result).toBe(expected); -}); - -it("import.meta.require (javascript)", () => { - expect(import.meta.require("./require-js.js").hello).toBe(sync.hello); - const require = Module.createRequire(import.meta.path); - expect(require("./require-js.js").hello).toBe(sync.hello); -}); - -it("import() require + TLA", async () => { - expect((await import("./import-require-tla.js")).foo).toBe("bar"); -}); - -it("import.meta.require (javascript, live bindings)", () => { - var Source = import.meta.require("./import.live.decl.js"); - - // require transpiles to import.meta.require - var ReExport = require("./import.live.rexport.js"); - - // dynamic require (string interpolation that way forces it to be dynamic) - var ReExportDynamic = require(`./import.live.${"rexport".split("").join("")}.js`); - - expect(Source.foo).toBe(1); - Source.setFoo(Source.foo + 1); - - expect(ReExport.foo).toBe(2); - expect(Source.foo).toBe(2); - expect(ReExportDynamic.foo).toBe(2); - - Source.setFoo(Source.foo + 1); - - var { Namespace } = require("./import.live.rexport-require.js"); - - expect(Namespace).toBe(Source); - expect(ReExport.foo).toBe(3); - expect(Source.foo).toBe(3); - expect(Namespace.foo).toBe(3); - - ReExport.setFoo(ReExport.foo + 1); - - expect(ReExport.foo).toBe(4); - expect(Source.foo).toBe(4); - expect(Namespace.foo).toBe(4); -}); - -it("import.meta.dir", () => { - expect(dir.endsWith("/bun/test/bun.js")).toBe(true); -}); - -it("import.meta.path", () => { - expect(path.endsWith("/bun/test/bun.js/import-meta.test.js")).toBe(true); -}); - -it('require("bun") works', () => { - expect(require("bun")).toBe(Bun); -}); - -it('import("bun") works', async () => { - expect(await import("bun")).toBe(Bun); -}); diff --git a/test/bun.js/import-require-tla.js b/test/bun.js/import-require-tla.js deleted file mode 100644 index 732fc34dd..000000000 --- a/test/bun.js/import-require-tla.js +++ /dev/null @@ -1,7 +0,0 @@ -const Fs = require("fs"); - -const DirEnt = Fs.Dirent; - -await Promise.resolve(123); - -export const foo = "bar"; diff --git a/test/bun.js/import.live.decl.js b/test/bun.js/import.live.decl.js deleted file mode 100644 index 46e67c9bd..000000000 --- a/test/bun.js/import.live.decl.js +++ /dev/null @@ -1,4 +0,0 @@ -export var foo = 1; -export function setFoo(val) { - foo = val; -} diff --git a/test/bun.js/import.live.rexport-require.js b/test/bun.js/import.live.rexport-require.js deleted file mode 100644 index 10c993e08..000000000 --- a/test/bun.js/import.live.rexport-require.js +++ /dev/null @@ -1 +0,0 @@ -export const Namespace = import.meta.require("./import.live.decl.js"); diff --git a/test/bun.js/import.live.rexport.js b/test/bun.js/import.live.rexport.js deleted file mode 100644 index e4accd2ba..000000000 --- a/test/bun.js/import.live.rexport.js +++ /dev/null @@ -1,2 +0,0 @@ -export { foo, setFoo } from "./import.live.decl"; -import { foo as bar } from "./import.live.decl"; diff --git a/test/bun.js/index-of-line.test.ts b/test/bun.js/index-of-line.test.ts deleted file mode 100644 index 1c6cbaea2..000000000 --- a/test/bun.js/index-of-line.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { expect, test } from "bun:test"; -import { indexOfLine } from "bun"; - -test("indexOfLine", () => { - const source = ` - const a = 1; - - const b = 2; - - 😋const c = 3; // handles unicode - - 😋 Get Emoji — All Emojis to ✂️ - - const b = 2; - - const c = 3; -`; - var i = 0; - var j = 0; - const buffer = Buffer.from(source); - var nonEmptyLineCount = 0; - while (i < buffer.length) { - const prev = j; - j = source.indexOf("\n", j); - i = indexOfLine(buffer, i); - - const delta = Buffer.byteLength(source.slice(0, j), "utf8") - j; - console.log(source.slice(prev + 1, j)); - if (i === -1) { - expect(j).toBe(-1); - expect(nonEmptyLineCount).toBe(6); - break; - } - expect(i++ - delta).toBe(j++); - nonEmptyLineCount++; - } -}); diff --git a/test/bun.js/inline.macro.js b/test/bun.js/inline.macro.js deleted file mode 100644 index 8b157aa38..000000000 --- a/test/bun.js/inline.macro.js +++ /dev/null @@ -1,23 +0,0 @@ -export function whatDidIPass(expr, ctx) { - return ctx; -} - -export function promiseReturningFunction(expr, ctx) { - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve(1); - }, 1); - }); -} - -export function promiseReturningCtx(expr, ctx) { - return new Promise((resolve, reject) => { - setTimeout( - ctx => { - resolve(ctx); - }, - 1, - ctx, - ); - }); -} diff --git a/test/bun.js/inspect.test.js b/test/bun.js/inspect.test.js deleted file mode 100644 index 024a976a3..000000000 --- a/test/bun.js/inspect.test.js +++ /dev/null @@ -1,315 +0,0 @@ -import { it, expect, describe } from "bun:test"; - -it("Timeout", () => { - const id = setTimeout(() => {}, 0); - expect(Bun.inspect(id)).toBe(`Timeout (#${+id})`); - - const id2 = setInterval(() => {}, 1); - id2.unref(); - expect(Bun.inspect(id2)).toBe(`Timeout (#${+id2}, repeats)`); -}); - -it("when prototype defines the same property, don't print the same property twice", () => { - var base = { - foo: "123", - }; - var obj = Object.create(base); - obj.foo = "456"; - expect(Bun.inspect(obj).trim()).toBe('{\n foo: "456"\n}'.trim()); -}); - -it("Blob inspect", () => { - expect(Bun.inspect(new Blob(["123"]))).toBe(`Blob (3 bytes)`); - expect(Bun.inspect(new Blob(["123".repeat(900)]))).toBe(`Blob (2.70 KB)`); - expect(Bun.inspect(Bun.file("/tmp/file.txt"))).toBe(`FileRef ("/tmp/file.txt") { - type: "text/plain;charset=utf-8" -}`); - expect(Bun.inspect(Bun.file(123))).toBe(`FileRef (fd: 123) { - type: "application/octet-stream" -}`); - expect(Bun.inspect(new Response(new Blob()))).toBe(`Response (0 KB) { - ok: true, - url: "", - statusText: "", - redirected: false, - bodyUsed: false, - status: 200, - [Blob detached] -}`); - expect(Bun.inspect(new Response("Hello"))).toBe(`Response (5 bytes) { - ok: true, - url: "", - statusText: "", - redirected: false, - bodyUsed: false, - status: 200, - Blob (5 bytes) -}`); -}); - -it.skip("utf16 property name", () => { - var { Database } = require("bun:sqlite"); - const db = Database.open(":memory:"); - expect("笑".codePointAt(0)).toBe(31505); - - // latin1 escaping identifier issue - expect(Object.keys({ 笑: "hey" })[0].codePointAt(0)).toBe(31505); - - const output = JSON.stringify( - [ - { - 笑: "😀", - }, - ], - null, - 2, - ); - expect(Bun.inspect(db.prepare("select '😀' as 笑").all())).toBe(output); -}); - -it("latin1", () => { - expect(Bun.inspect("English")).toBe("English"); - expect(Bun.inspect("Français")).toBe("Français"); - expect(Bun.inspect("Ελληνική")).toBe("Ελληνική"); - expect(Bun.inspect("日本語")).toBe("日本語"); - expect(Bun.inspect("Emoji😎")).toBe("Emoji😎"); - expect(Bun.inspect("Français / Ελληνική")).toBe("Français / Ελληνική"); -}); - -it("Request object", () => { - expect(Bun.inspect(new Request({ url: "https://example.com" })).trim()).toBe( - ` -Request (0 KB) { - method: "GET", - url: "https://example.com" -}`.trim(), - ); -}); - -it("MessageEvent", () => { - expect(Bun.inspect(new MessageEvent("message", { data: 123 }))).toBe( - `MessageEvent { - type: "message", - data: 123 -}`, - ); -}); - -// https://github.com/oven-sh/bun/issues/561 -it("TypedArray prints", () => { - for (let TypedArray of [ - Uint8Array, - Uint16Array, - Uint32Array, - Uint8ClampedArray, - Int8Array, - Int16Array, - Int32Array, - Float32Array, - Float64Array, - ]) { - const buffer = new TypedArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - const input = Bun.inspect(buffer); - - expect(input).toBe(`${TypedArray.name}(${buffer.length}) [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]`); - for (let i = 1; i < buffer.length + 1; i++) { - expect(Bun.inspect(buffer.subarray(i))).toBe( - `${TypedArray.name}(${buffer.length - i}) [ ` + [...buffer.subarray(i)].join(", ") + " ]", - ); - } - } -}); - -it("BigIntArray", () => { - for (let TypedArray of [BigInt64Array, BigUint64Array]) { - const buffer = new TypedArray([1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 10n]); - const input = Bun.inspect(buffer); - - expect(input).toBe(`${TypedArray.name}(${buffer.length}) [ 1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 10n ]`); - for (let i = 1; i < buffer.length + 1; i++) { - expect(Bun.inspect(buffer.subarray(i))).toBe( - `${TypedArray.name}(${buffer.length - i}) [ ` + - [...buffer.subarray(i)].map(a => a.toString(10) + "n").join(", ") + - " ]", - ); - } - } -}); - -it("FloatArray", () => { - for (let TypedArray of [Float32Array, Float64Array]) { - const buffer = new TypedArray([Math.fround(42.68)]); - const input = Bun.inspect(buffer); - - expect(input).toBe(`${TypedArray.name}(${buffer.length}) [ ${[Math.fround(42.68)].join(", ")} ]`); - for (let i = 1; i < buffer.length + 1; i++) { - expect(Bun.inspect(buffer.subarray(i))).toBe( - `${TypedArray.name}(${buffer.length - i}) [ ` + [...buffer.subarray(i)].join(", ") + " ]", - ); - } - } -}); - -it("jsx with two elements", () => { - const input = Bun.inspect( -
- - string inside child -
, - ); - - const output = `
- - string inside child -
`; - - expect(input).toBe(output); -}); - -const Foo = () =>
foo
; - -it("jsx with anon component", () => { - const input = Bun.inspect(); - - const output = ``; - - expect(input).toBe(output); -}); - -it("jsx with fragment", () => { - const input = Bun.inspect(<>foo bar); - - const output = `<>foo bar`; - - expect(input).toBe(output); -}); - -it("inspect", () => { - expect(Bun.inspect(new TypeError("what")).includes("TypeError: what")).toBe(true); - expect("hi").toBe("hi"); - expect(Bun.inspect(1)).toBe("1"); - expect(Bun.inspect(NaN)).toBe("NaN"); - expect(Bun.inspect(Infinity)).toBe("Infinity"); - expect(Bun.inspect(-Infinity)).toBe("-Infinity"); - expect(Bun.inspect(1, "hi")).toBe("1 hi"); - expect(Bun.inspect([])).toBe("[]"); - expect(Bun.inspect({})).toBe("{}"); - expect(Bun.inspect({ hello: 1 })).toBe("{\n hello: 1\n}"); - expect(Bun.inspect({ hello: 1, there: 2 })).toBe("{\n hello: 1,\n there: 2\n}"); - expect(Bun.inspect({ hello: "1", there: 2 })).toBe('{\n hello: "1",\n there: 2\n}'); - expect(Bun.inspect({ 'hello-"there': "1", there: 2 })).toBe('{\n "hello-\\"there": "1",\n there: 2\n}'); - var str = "123"; - while (str.length < 4096) { - str += "123"; - } - expect(Bun.inspect(str)).toBe(str); - // expect(Bun.inspect(new Headers())).toBe("Headers (0 KB) {}"); - expect(Bun.inspect(new Response()).length > 0).toBe(true); - // expect( - // JSON.stringify( - // new Headers({ - // hi: "ok", - // }) - // ) - // ).toBe('{"hi":"ok"}'); - expect(Bun.inspect(new Set())).toBe("Set {}"); - expect(Bun.inspect(new Map())).toBe("Map {}"); - expect(Bun.inspect(new Map([["foo", "bar"]]))).toBe('Map(1) {\n "foo": "bar",\n}'); - expect(Bun.inspect(new Set(["bar"]))).toBe('Set(1) {\n "bar",\n}'); - expect(Bun.inspect(
foo
)).toBe("
foo
"); - expect(Bun.inspect(
foo
)).toBe("
foo
"); - expect(Bun.inspect(
foo
)).toBe("
foo
"); - expect(Bun.inspect(
hi
)).toBe("
hi
"); - expect(Bun.inspect(
quoted
)).toBe('
quoted
'); - expect( - Bun.inspect( -
- -
, - ), - ).toBe( - ` -
- -
`.trim(), - ); - expect(Bun.inspect(BigInt(32))).toBe("32n"); -}); - -describe("latin1 supplemental", () => { - const fixture = [ - [["äbc"], '[ "äbc" ]'], - [["cbä"], '[ "cbä" ]'], - [["cäb"], '[ "cäb" ]'], - [["äbc äbc"], '[ "äbc äbc" ]'], - [["cbä cbä"], '[ "cbä cbä" ]'], - [["cäb cäb"], '[ "cäb cäb" ]'], - ]; - - for (let [input, output] of fixture) { - it(`latin1 (input) \"${input}\" ${output}`, () => { - expect(Bun.inspect(input)).toBe(output); - }); - it(`latin1 (output) \"${output}\"`, () => { - expect(Bun.inspect(output)).toBe(output); - }); - // this test is failing: - // it(`latin1 (property key)`, () => { - // expect( - // Object.keys({ - // ä: 1, - // })[0].codePointAt(0), - // ).toBe(228); - // }); - } -}); - -const fixture = [ - () => globalThis, - () => Bun.file("/tmp/log.txt").stream(), - () => Bun.file("/tmp/log.1.txt").stream().getReader(), - () => Bun.file("/tmp/log.2.txt").writer(), - () => - new WritableStream({ - write(chunk) {}, - }), - () => require("events"), - () => { - return new (import.meta.require("events").EventEmitter)(); - }, - async () => await import("node:assert"), - async () => await import("./empty.js"), - () => import.meta.require("./empty.js"), - () => new Proxy({ yolo: 1 }, {}), - () => - new Proxy( - { yolo: 1 }, - { - get(target, prop) { - return prop + "!"; - }, - has(target, prop) { - return true; - }, - ownKeys() { - return ["foo"]; - }, - }, - ), -]; - -describe("crash testing", () => { - for (let input of fixture) { - it(`inspecting "${input.toString().slice(0, 20).replaceAll("\n", "\\n")}" doesn't crash`, async () => { - try { - Bun.inspect(await input()); - } catch (e) { - // this can throw its fine - } - }); - } -}); - -it("possibly formatted emojis log", () => { - expect(Bun.inspect("✔", "hey")).toBe("✔ hey"); -}); diff --git a/test/bun.js/install/bad-workspace.test.ts b/test/bun.js/install/bad-workspace.test.ts deleted file mode 100644 index bb3f03a86..000000000 --- a/test/bun.js/install/bad-workspace.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { spawnSync } from "bun"; -import { test, describe, expect } from "bun:test"; -import { bunEnv } from "bunEnv"; -import { bunExe } from "bunExe"; -import { mkdirSync, rmSync, writeFileSync } from "fs"; - -test("bad workspace path", () => { - const cwd = "/tmp/bun-bad-workspace"; - rmSync(cwd, { recursive: true, force: true }); - mkdirSync(cwd); - writeFileSync( - `${cwd}/package.json`, - JSON.stringify( - { - name: "hey", - workspaces: ["i-dont-exist", "*/i-have-a-star-and-i-dont-exist"], - }, - null, - 2, - ), - ); - const { stderr, exitCode } = spawnSync({ - cmd: [bunExe(), "install"], - cwd, - env: bunEnv, - stderr: "pipe", - stdout: "pipe", - }); - const text = stderr!.toString(); - - expect(text).toContain('Workspace not found "i-dont-exist"'); - expect(text).toContain('Workspace not found "*/i-have-a-star-and-i-dont-exist"'); - expect(exitCode).toBe(1); - rmSync(cwd, { recursive: true, force: true }); -}); diff --git a/test/bun.js/install/bar-0.0.2.tgz b/test/bun.js/install/bar-0.0.2.tgz deleted file mode 100644 index 1983142d7..000000000 Binary files a/test/bun.js/install/bar-0.0.2.tgz and /dev/null differ diff --git a/test/bun.js/install/baz-0.0.3.tgz b/test/bun.js/install/baz-0.0.3.tgz deleted file mode 100644 index 375a5e31f..000000000 Binary files a/test/bun.js/install/baz-0.0.3.tgz and /dev/null differ diff --git a/test/bun.js/install/baz-0.0.5.tgz b/test/bun.js/install/baz-0.0.5.tgz deleted file mode 100644 index 7e708fb1b..000000000 Binary files a/test/bun.js/install/baz-0.0.5.tgz and /dev/null differ diff --git a/test/bun.js/install/bun-add.test.ts b/test/bun.js/install/bun-add.test.ts deleted file mode 100644 index 9ac54bbc5..000000000 --- a/test/bun.js/install/bun-add.test.ts +++ /dev/null @@ -1,981 +0,0 @@ -import { file, spawn } from "bun"; -import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from "bun:test"; -import { bunExe } from "bunExe"; -import { bunEnv as env } from "bunEnv"; -import { access, mkdir, mkdtemp, readlink, realpath, rm, writeFile } from "fs/promises"; -import { join, relative } from "path"; -import { tmpdir } from "os"; -import { - dummyAfterAll, - dummyAfterEach, - dummyBeforeAll, - dummyBeforeEach, - dummyRegistry, - package_dir, - readdirSorted, - requested, - root_url, - setHandler, -} from "./dummy.registry"; - -beforeAll(dummyBeforeAll); -afterAll(dummyAfterAll); - -let add_dir; - -beforeEach(async () => { - add_dir = await mkdtemp(join(await realpath(tmpdir()), "bun-add.test")); - await dummyBeforeEach(); -}); -afterEach(async () => { - await rm(add_dir, { force: true, recursive: true }); - await dummyAfterEach(); -}); - -it("should add existing package", async () => { - await writeFile( - join(add_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - }), - ); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "bar", - version: "0.0.2", - }), - ); - const add_path = relative(package_dir, add_dir); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "add", `file:${add_path}`], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual(["bun add", " Saved lockfile", ""]); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - "", - ` installed foo@${add_path}`, - "", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(await file(join(package_dir, "package.json")).json()).toEqual({ - name: "bar", - version: "0.0.2", - dependencies: { - foo: `file:${add_path}`, - }, - }); -}); - -it("should reject missing package", async () => { - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "bar", - version: "0.0.2", - }), - ); - const add_path = relative(package_dir, add_dir); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "add", `file:${add_path}`], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual([ - "bun add", - `error: file:${add_path} failed to resolve`, - "", - ]); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out).toBe(""); - expect(await exited).toBe(1); - expect(await file(join(package_dir, "package.json")).json()).toEqual({ - name: "bar", - version: "0.0.2", - }); -}); - -it("should reject invalid path without segfault", async () => { - await writeFile( - join(add_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - }), - ); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "bar", - version: "0.0.2", - }), - ); - const add_path = relative(package_dir, add_dir); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "add", `file://${add_path}`], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual([ - "bun add", - `error: file://${add_path} failed to resolve`, - "", - ]); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out).toBe(""); - expect(await exited).toBe(1); - expect(await file(join(package_dir, "package.json")).json()).toEqual({ - name: "bar", - version: "0.0.2", - }); -}); - -it("should handle semver-like names", async () => { - const urls: string[] = []; - setHandler(async request => { - expect(request.method).toBe("GET"); - expect(request.headers.get("accept")).toBe( - "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*", - ); - expect(request.headers.get("npm-auth-type")).toBe(null); - expect(await request.text()).toBe(""); - urls.push(request.url); - return new Response("not to be found", { status: 404 }); - }); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "add", "1.2.3"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err.split(/\r?\n/)).toContain('error: package "1.2.3" not found localhost/1.2.3 404'); - expect(stdout).toBeDefined(); - expect(await new Response(stdout).text()).toBe(""); - expect(await exited).toBe(1); - expect(urls.sort()).toEqual([`${root_url}/1.2.3`]); - expect(requested).toBe(1); - try { - await access(join(package_dir, "bun.lockb")); - expect(() => {}).toThrow(); - } catch (err: any) { - expect(err.code).toBe("ENOENT"); - } -}); - -it("should handle @scoped names", async () => { - const urls: string[] = []; - setHandler(async request => { - expect(request.method).toBe("GET"); - expect(request.headers.get("accept")).toBe( - "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*", - ); - expect(request.headers.get("npm-auth-type")).toBe(null); - expect(await request.text()).toBe(""); - urls.push(request.url); - return new Response("not to be found", { status: 404 }); - }); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "add", "@bar/baz"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err.split(/\r?\n/)).toContain('error: package "@bar/baz" not found localhost/@bar/baz 404'); - expect(stdout).toBeDefined(); - expect(await new Response(stdout).text()).toBe(""); - expect(await exited).toBe(1); - expect(urls.sort()).toEqual([`${root_url}/@bar/baz`]); - expect(requested).toBe(1); - try { - await access(join(package_dir, "bun.lockb")); - expect(() => {}).toThrow(); - } catch (err: any) { - expect(err.code).toBe("ENOENT"); - } -}); - -it("should add dependency with capital letters", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "add", "BaR"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - "", - " installed BaR@0.0.2", - "", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/BaR`, `${root_url}/BaR-0.0.2.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "BaR"]); - expect(await readdirSorted(join(package_dir, "node_modules", "BaR"))).toEqual(["package.json"]); - expect(await file(join(package_dir, "node_modules", "BaR", "package.json")).json()).toEqual({ - name: "bar", - version: "0.0.2", - }); - expect(await file(join(package_dir, "package.json")).json()).toEqual({ - name: "foo", - version: "0.0.1", - dependencies: { - BaR: "^0.0.2", - }, - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should add dependency with specified semver", async () => { - const urls: string[] = []; - setHandler( - dummyRegistry(urls, { - "0.0.3": { - bin: { - "baz-run": "index.js", - }, - }, - }), - ); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "add", "baz@~0.0.2"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - "", - " installed baz@0.0.3 with binaries:", - " - baz-run", - "", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "baz"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]); - expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "baz", "index.js")); - expect(await readdirSorted(join(package_dir, "node_modules", "baz"))).toEqual(["index.js", "package.json"]); - expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.3", - bin: { - "baz-run": "index.js", - }, - }); - expect(await file(join(package_dir, "package.json")).json()).toEqual({ - name: "foo", - version: "0.0.1", - dependencies: { - baz: "~0.0.2", - }, - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should add dependency alongside workspaces", async () => { - const urls: string[] = []; - setHandler( - dummyRegistry(urls, { - "0.0.3": { - bin: { - "baz-run": "index.js", - }, - }, - }), - ); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - workspaces: ["packages/bar"], - }), - ); - await mkdir(join(package_dir, "packages", "bar"), { recursive: true }); - await writeFile( - join(package_dir, "packages", "bar", "package.json"), - JSON.stringify({ - name: "bar", - version: "0.0.2", - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "add", "baz"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + bar@workspace:packages/bar", - "", - " installed baz@0.0.3 with binaries:", - " - baz-run", - "", - "", - " 2 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "bar", "baz"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]); - expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "baz", "index.js")); - expect(await readlink(join(package_dir, "node_modules", "bar"))).toBe(join("..", "packages", "bar")); - expect(await readdirSorted(join(package_dir, "node_modules", "baz"))).toEqual(["index.js", "package.json"]); - expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.3", - bin: { - "baz-run": "index.js", - }, - }); - expect(await file(join(package_dir, "package.json")).json()).toEqual({ - name: "foo", - version: "0.0.1", - workspaces: ["packages/bar"], - dependencies: { - baz: "^0.0.3", - }, - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should add aliased dependency (npm)", async () => { - const urls: string[] = []; - setHandler( - dummyRegistry(urls, { - "0.0.3": { - bin: { - "baz-run": "index.js", - }, - }, - }), - ); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "add", "bar@npm:baz@~0.0.2"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - "", - " installed bar@0.0.3 with binaries:", - " - baz-run", - "", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "bar"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]); - expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "bar", "index.js")); - expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["index.js", "package.json"]); - expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.3", - bin: { - "baz-run": "index.js", - }, - }); - expect(await file(join(package_dir, "package.json")).json()).toEqual({ - name: "foo", - version: "0.0.1", - dependencies: { - bar: "npm:baz@~0.0.2", - }, - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should add aliased dependency (GitHub)", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "add", "uglify@mishoo/UglifyJS#v3.14.1"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - "", - " installed uglify@github:mishoo/UglifyJS#e219a9a with binaries:", - " - uglifyjs", - "", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".cache"))).toEqual([ - "@GH@mishoo-UglifyJS-e219a9a", - "uglify", - ]); - expect(await readdirSorted(join(package_dir, "node_modules", ".cache", "uglify"))).toEqual([ - "mishoo-UglifyJS-e219a9a", - ]); - expect(await readlink(join(package_dir, "node_modules", ".cache", "uglify", "mishoo-UglifyJS-e219a9a"))).toBe( - join(package_dir, "node_modules", ".cache", "@GH@mishoo-UglifyJS-e219a9a"), - ); - expect(await readdirSorted(join(package_dir, "node_modules", "uglify"))).toEqual([ - ".bun-tag", - ".gitattributes", - ".github", - ".gitignore", - "CONTRIBUTING.md", - "LICENSE", - "README.md", - "bin", - "lib", - "package.json", - "test", - "tools", - ]); - const package_json = await file(join(package_dir, "node_modules", "uglify", "package.json")).json(); - expect(package_json.name).toBe("uglify-js"); - expect(package_json.version).toBe("3.14.1"); - expect(await file(join(package_dir, "package.json")).json()).toEqual({ - name: "foo", - version: "0.0.1", - dependencies: { - uglify: "mishoo/UglifyJS#v3.14.1", - }, - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should let you add the same package twice", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls, { "0.0.3": {} })); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - dependencies: {}, - }), - ); - // add as non-dev - const { - stdout: stdout1, - stderr: stderr1, - exited: exited1, - } = spawn({ - cmd: [bunExe(), "add", "baz@0.0.3"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr1).toBeDefined(); - const err1 = await new Response(stderr1).text(); - expect(err1).toContain("Saved lockfile"); - expect(stdout1).toBeDefined(); - const out1 = await new Response(stdout1).text(); - expect(out1.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - "", - " installed baz@0.0.3", - "", - "", - " 1 packages installed", - ]); - expect(await exited1).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "baz"]); - expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.3", - bin: { - "baz-run": "index.js", - }, - }); - expect(await file(join(package_dir, "package.json")).json()).toEqual({ - name: "Foo", - version: "0.0.1", - dependencies: { - baz: "0.0.3", - }, - }); - await access(join(package_dir, "bun.lockb")); - // re-add as dev - urls.length = 0; - const { - stdout: stdout2, - stderr: stderr2, - exited: exited2, - } = spawn({ - cmd: [bunExe(), "add", "baz", "-d"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr2).toBeDefined(); - const err2 = await new Response(stderr2).text(); - expect(err2).toContain("Saved lockfile"); - expect(stdout2).toBeDefined(); - const out2 = await new Response(stdout2).text(); - expect(out2.replace(/\[[0-9\.]+m?s\]/, "[]").split(/\r?\n/)).toEqual(["", " installed baz@0.0.3", "", "[] done", ""]); - expect(await exited2).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/baz`]); - expect(requested).toBe(3); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "baz"]); - expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.3", - bin: { - "baz-run": "index.js", - }, - }); - expect(await file(join(package_dir, "package.json")).json()).toEqual({ - name: "Foo", - version: "0.0.1", - dependencies: { - baz: "^0.0.3", - }, - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should install version tagged with `latest` by default", async () => { - const urls: string[] = []; - setHandler( - dummyRegistry(urls, { - "0.0.3": {}, - "0.0.5": {}, - latest: "0.0.3", - }), - ); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - }), - ); - // add `latest` version - const { - stdout: stdout1, - stderr: stderr1, - exited: exited1, - } = spawn({ - cmd: [bunExe(), "add", "baz"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr1).toBeDefined(); - const err1 = await new Response(stderr1).text(); - expect(err1).toContain("Saved lockfile"); - expect(stdout1).toBeDefined(); - const out1 = await new Response(stdout1).text(); - expect(out1.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - "", - " installed baz@0.0.3", - "", - "", - " 1 packages installed", - ]); - expect(await exited1).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "baz"]); - expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.3", - bin: { - "baz-run": "index.js", - }, - }); - expect(await file(join(package_dir, "package.json")).json()).toEqual({ - name: "foo", - version: "0.0.1", - dependencies: { - baz: "^0.0.3", - }, - }); - await access(join(package_dir, "bun.lockb")); - // re-install with updated `package.json` - await rm(join(package_dir, "node_modules"), { force: true, recursive: true }); - urls.length = 0; - const { - stdout: stdout2, - stderr: stderr2, - exited: exited2, - } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr2).toBeDefined(); - const err2 = await new Response(stderr2).text(); - expect(err2).toContain("Saved lockfile"); - expect(stdout2).toBeDefined(); - const out2 = await new Response(stdout2).text(); - expect(out2.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + baz@0.0.3", - "", - " 1 packages installed", - ]); - expect(await exited2).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]); - expect(requested).toBe(4); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "baz"]); - expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.3", - bin: { - "baz-run": "index.js", - }, - }); - expect(await file(join(package_dir, "package.json")).json()).toEqual({ - name: "foo", - version: "0.0.1", - dependencies: { - baz: "^0.0.3", - }, - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle Git URL in dependencies (SCP-style)", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - }), - ); - const { - stdout: stdout1, - stderr: stderr1, - exited: exited1, - } = spawn({ - cmd: [bunExe(), "add", "bun@github.com:mishoo/UglifyJS.git"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr1).toBeDefined(); - const err1 = await new Response(stderr1).text(); - expect(err1).toContain("Saved lockfile"); - expect(stdout1).toBeDefined(); - let out1 = await new Response(stdout1).text(); - out1 = out1.replace(/\s*\[[0-9\.]+m?s\]\s*$/, ""); - out1 = out1.replace(/(\.git)#[a-f0-9]+/, "$1"); - expect(out1.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - "", - " installed uglify-js@git+ssh://bun@github.com:mishoo/UglifyJS.git with binaries:", - " - uglifyjs", - "", - "", - " 1 packages installed", - ]); - expect(await exited1).toBe(0); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(0); - expect(await file(join(package_dir, "package.json")).json()).toEqual({ - name: "foo", - version: "0.0.1", - dependencies: { - "uglify-js": "bun@github.com:mishoo/UglifyJS.git", - }, - }); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify-js"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]); - expect(await readlink(join(package_dir, "node_modules", ".bin", "uglifyjs"))).toBe( - join("..", "uglify-js", "bin", "uglifyjs"), - ); - expect((await readdirSorted(join(package_dir, "node_modules", ".cache")))[0]).toBe("9d05c118f06c3b4c.git"); - expect(await readdirSorted(join(package_dir, "node_modules", "uglify-js"))).toEqual([ - ".bun-tag", - ".gitattributes", - ".github", - ".gitignore", - "CONTRIBUTING.md", - "LICENSE", - "README.md", - "bin", - "lib", - "package.json", - "test", - "tools", - ]); - const package_json = await file(join(package_dir, "node_modules", "uglify-js", "package.json")).json(); - expect(package_json.name).toBe("uglify-js"); - await access(join(package_dir, "bun.lockb")); - const { - stdout: stdout2, - stderr: stderr2, - exited: exited2, - } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr2).toBeDefined(); - const err2 = await new Response(stderr2).text(); - expect(err2).not.toContain("Saved lockfile"); - expect(stdout2).toBeDefined(); - const out2 = await new Response(stdout2).text(); - expect(out2.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - "", - "Checked 1 installs across 2 packages (no changes)", - ]); - expect(await exited2).toBe(0); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(0); -}); - -it("should prefer optionalDependencies over dependencies of the same name", async () => { - const urls = []; - setHandler( - dummyRegistry(urls, { - "0.0.2": { - dependencies: { - baz: "0.0.3", - }, - optionalDependencies: { - baz: "0.0.5", - }, - }, - "0.0.3": {}, - "0.0.5": {}, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "add", "bar@0.0.2"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - "", - " installed bar@0.0.2", - "", - "", - " 2 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([ - `${root_url}/bar`, - `${root_url}/bar-0.0.2.tgz`, - `${root_url}/baz`, - `${root_url}/baz-0.0.5.tgz`, - ]); - expect(requested).toBe(4); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "bar", "baz"]); - expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["package.json"]); - expect(await readdirSorted(join(package_dir, "node_modules", "baz"))).toEqual(["index.js", "package.json"]); - expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({ - name: "bar", - version: "0.0.2", - }); - expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.5", - bin: { - "baz-exec": "index.js", - }, - }); -}); - -it("should prefer dependencies over peerDependencies of the same name", async () => { - const urls = []; - setHandler( - dummyRegistry(urls, { - "0.0.2": { - dependencies: { - baz: "0.0.3", - }, - peerDependencies: { - baz: "0.0.5", - }, - }, - "0.0.3": {}, - "0.0.5": {}, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "add", "bar@0.0.2"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - "", - " installed bar@0.0.2", - "", - "", - " 2 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([ - `${root_url}/bar`, - `${root_url}/bar-0.0.2.tgz`, - `${root_url}/baz`, - `${root_url}/baz-0.0.3.tgz`, - ]); - expect(requested).toBe(4); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "bar", "baz"]); - expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["package.json"]); - expect(await readdirSorted(join(package_dir, "node_modules", "baz"))).toEqual(["index.js", "package.json"]); - expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({ - name: "bar", - version: "0.0.2", - }); - expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.3", - bin: { - "baz-run": "index.js", - }, - }); -}); diff --git a/test/bun.js/install/bun-install.test.ts b/test/bun.js/install/bun-install.test.ts deleted file mode 100644 index 3ea14f27c..000000000 --- a/test/bun.js/install/bun-install.test.ts +++ /dev/null @@ -1,2758 +0,0 @@ -import { file, listen, spawn } from "bun"; -import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from "bun:test"; -import { bunExe } from "bunExe"; -import { bunEnv as env } from "bunEnv"; -import { access, mkdir, readlink, writeFile } from "fs/promises"; -import { join } from "path"; -import { - dummyAfterAll, - dummyAfterEach, - dummyBeforeAll, - dummyBeforeEach, - dummyRegistry, - package_dir, - readdirSorted, - requested, - root_url, - setHandler, -} from "./dummy.registry"; - -beforeAll(dummyBeforeAll); -afterAll(dummyAfterAll); -beforeEach(dummyBeforeEach); -afterEach(dummyAfterEach); - -it("should report connection errors", async () => { - function end(socket) { - socket.end(); - } - const server = listen({ - socket: { - data: end, - drain: end, - open: end, - }, - hostname: "localhost", - port: 0, - }); - await writeFile( - join(package_dir, "bunfig.toml"), - ` -[install] -cache = false -registry = "http://localhost:${server.port}/" -`, - ); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - bar: "0.0.2", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err.split(/\r?\n/)).toContain("error: ConnectionRefused downloading package manifest bar"); - expect(stdout).toBeDefined(); - expect(await new Response(stdout).text()).toBe(""); - expect(await exited).toBe(1); - try { - await access(join(package_dir, "bun.lockb")); - expect(() => {}).toThrow(); - } catch (err: any) { - expect(err.code).toBe("ENOENT"); - } -}); - -it("should handle missing package", async () => { - const urls: string[] = []; - setHandler(async request => { - expect(request.method).toBe("GET"); - expect(request.headers.get("accept")).toBe( - "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*", - ); - expect(request.headers.get("npm-auth-type")).toBe(null); - expect(await request.text()).toBe(""); - urls.push(request.url); - return new Response("bar", { status: 404 }); - }); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install", "foo"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err.split(/\r?\n/)).toContain('error: package "foo" not found localhost/foo 404'); - expect(stdout).toBeDefined(); - expect(await new Response(stdout).text()).toBe(""); - expect(await exited).toBe(1); - expect(urls.sort()).toEqual([`${root_url}/foo`]); - expect(requested).toBe(1); - try { - await access(join(package_dir, "bun.lockb")); - expect(() => {}).toThrow(); - } catch (err: any) { - expect(err.code).toBe("ENOENT"); - } -}); - -it("should handle @scoped authentication", async () => { - let seen_token = false; - const url = `${root_url}/@foo/bar`; - const urls: string[] = []; - setHandler(async request => { - expect(request.method).toBe("GET"); - expect(request.headers.get("accept")).toBe( - "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*", - ); - if (request.url === url) { - expect(request.headers.get("authorization")).toBe("Bearer bar"); - expect(request.headers.get("npm-auth-type")).toBe("legacy"); - seen_token = true; - } else { - expect(request.headers.get("npm-auth-type")).toBe(null); - } - expect(await request.text()).toBe(""); - urls.push(request.url); - return new Response("Feeling lucky?", { status: 555 }); - }); - // workaround against `writeFile(..., { flag: "a" })` - await writeFile( - join(package_dir, "bunfig.toml"), - `${await file(join(package_dir, "bunfig.toml")).text()} -[install.scopes] -foo = { token = "bar" } -`, - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install", "@foo/bar"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err.split(/\r?\n/)).toContain(`GET ${url} - 555`); - expect(stdout).toBeDefined(); - expect(await new Response(stdout).text()).toBe(""); - expect(await exited).toBe(1); - expect(urls.sort()).toEqual([url]); - expect(seen_token).toBe(true); - expect(requested).toBe(1); - try { - await access(join(package_dir, "bun.lockb")); - expect(() => {}).toThrow(); - } catch (err: any) { - expect(err.code).toBe("ENOENT"); - } -}); - -it("should handle empty string in dependencies", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - bar: "", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + bar@0.0.2", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "bar"]); - expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["package.json"]); - expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({ - name: "bar", - version: "0.0.2", - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle workspaces", async () => { - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - workspaces: ["bar"], - }), - ); - await mkdir(join(package_dir, "bar")); - await writeFile( - join(package_dir, "bar", "package.json"), - JSON.stringify({ - name: "Bar", - version: "0.0.2", - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + Bar@workspace:bar", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(requested).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "Bar"]); - expect(await readlink(join(package_dir, "node_modules", "Bar"))).toBe(join("..", "bar")); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle workspaces with packages array", async () => { - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - workspaces: { packages: ["bar"] }, - }), - ); - await mkdir(join(package_dir, "bar")); - await writeFile( - join(package_dir, "bar", "package.json"), - JSON.stringify({ - name: "Bar", - version: "0.0.2", - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + Bar@workspace:bar", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(requested).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "Bar"]); - expect(await readlink(join(package_dir, "node_modules", "Bar"))).toBe(join("..", "bar")); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle inter-dependency between workspaces", async () => { - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - workspaces: ["bar", "packages/baz"], - }), - ); - await mkdir(join(package_dir, "bar")); - await writeFile( - join(package_dir, "bar", "package.json"), - JSON.stringify({ - name: "Bar", - version: "0.0.2", - dependencies: { - Baz: "0.0.3", - }, - }), - ); - await mkdir(join(package_dir, "packages", "baz"), { recursive: true }); - await writeFile( - join(package_dir, "packages", "baz", "package.json"), - JSON.stringify({ - name: "Baz", - version: "0.0.3", - dependencies: { - Bar: "0.0.2", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + Bar@workspace:bar", - " + Baz@workspace:packages/baz", - "", - " 2 packages installed", - ]); - expect(await exited).toBe(0); - expect(requested).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "Bar", "Baz"]); - expect(await readlink(join(package_dir, "node_modules", "Bar"))).toBe(join("..", "bar")); - expect(await readlink(join(package_dir, "node_modules", "Baz"))).toBe(join("..", "packages", "baz")); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle inter-dependency between workspaces (devDependencies)", async () => { - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - workspaces: ["bar", "packages/baz"], - }), - ); - await mkdir(join(package_dir, "bar")); - await writeFile( - join(package_dir, "bar", "package.json"), - JSON.stringify({ - name: "Bar", - version: "0.0.2", - devDependencies: { - Baz: "0.0.3", - }, - }), - ); - await mkdir(join(package_dir, "packages", "baz"), { recursive: true }); - await writeFile( - join(package_dir, "packages", "baz", "package.json"), - JSON.stringify({ - name: "Baz", - version: "0.0.3", - devDependencies: { - Bar: "0.0.2", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + Bar@workspace:bar", - " + Baz@workspace:packages/baz", - "", - " 2 packages installed", - ]); - expect(await exited).toBe(0); - expect(requested).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "Bar", "Baz"]); - expect(await readlink(join(package_dir, "node_modules", "Bar"))).toBe(join("..", "bar")); - expect(await readlink(join(package_dir, "node_modules", "Baz"))).toBe(join("..", "packages", "baz")); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle inter-dependency between workspaces (optionalDependencies)", async () => { - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - workspaces: ["bar", "packages/baz"], - }), - ); - await mkdir(join(package_dir, "bar")); - await writeFile( - join(package_dir, "bar", "package.json"), - JSON.stringify({ - name: "Bar", - version: "0.0.2", - optionalDependencies: { - Baz: "0.0.3", - }, - }), - ); - await mkdir(join(package_dir, "packages", "baz"), { recursive: true }); - await writeFile( - join(package_dir, "packages", "baz", "package.json"), - JSON.stringify({ - name: "Baz", - version: "0.0.3", - optionalDependencies: { - Bar: "0.0.2", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + Bar@workspace:bar", - " + Baz@workspace:packages/baz", - "", - " 2 packages installed", - ]); - expect(await exited).toBe(0); - expect(requested).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "Bar", "Baz"]); - expect(await readlink(join(package_dir, "node_modules", "Bar"))).toBe(join("..", "bar")); - expect(await readlink(join(package_dir, "node_modules", "Baz"))).toBe(join("..", "packages", "baz")); - await access(join(package_dir, "bun.lockb")); -}); - -it("should ignore peerDependencies within workspaces", async () => { - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - workspaces: ["packages/baz"], - peerDependencies: { - Bar: ">=0.0.2", - }, - }), - ); - await mkdir(join(package_dir, "packages", "baz"), { recursive: true }); - await writeFile( - join(package_dir, "packages", "baz", "package.json"), - JSON.stringify({ - name: "Baz", - version: "0.0.3", - peerDependencies: { - Moo: ">=0.0.4", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + Baz@workspace:packages/baz", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(requested).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "Baz"]); - expect(await readlink(join(package_dir, "node_modules", "Baz"))).toBe(join("..", "packages", "baz")); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle life-cycle scripts within workspaces", async () => { - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - scripts: { - install: [bunExe(), "index.js"].join(" "), - }, - workspaces: ["bar"], - }), - ); - await writeFile(join(package_dir, "index.js"), 'console.log("[scripts:run] Foo");'); - await mkdir(join(package_dir, "bar")); - await writeFile( - join(package_dir, "bar", "package.json"), - JSON.stringify({ - name: "Bar", - version: "0.0.2", - scripts: { - preinstall: [bunExe(), "index.js"].join(" "), - }, - }), - ); - await writeFile(join(package_dir, "bar", "index.js"), 'console.log("[scripts:run] Bar");'); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - "[scripts:run] Bar", - " + Bar@workspace:bar", - "[scripts:run] Foo", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(requested).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "Bar"]); - expect(await readlink(join(package_dir, "node_modules", "Bar"))).toBe(join("..", "bar")); - await access(join(package_dir, "bun.lockb")); -}); - -it("should ignore workspaces within workspaces", async () => { - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - workspaces: ["bar"], - }), - ); - await mkdir(join(package_dir, "bar")); - await writeFile( - join(package_dir, "bar", "package.json"), - JSON.stringify({ - name: "bar", - version: "0.0.2", - workspaces: ["baz"], - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + bar@workspace:bar", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(requested).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "bar"]); - expect(await readlink(join(package_dir, "node_modules", "bar"))).toBe(join("..", "bar")); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle ^0 in dependencies", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - bar: "^0", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + bar@0.0.2", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "bar"]); - expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["package.json"]); - expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({ - name: "bar", - version: "0.0.2", - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle ^1 in dependencies", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - bar: "^1", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain('error: No version matching "^1" found for specifier "bar" (but package exists)'); - expect(stdout).toBeDefined(); - expect(await new Response(stdout).text()).toBe(""); - expect(await exited).toBe(1); - expect(urls.sort()).toEqual([`${root_url}/bar`]); - expect(requested).toBe(1); - try { - await access(join(package_dir, "bun.lockb")); - expect(() => {}).toThrow(); - } catch (err: any) { - expect(err.code).toBe("ENOENT"); - } -}); - -it("should handle ^0.0 in dependencies", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - bar: "^0.0", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + bar@0.0.2", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "bar"]); - expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["package.json"]); - expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({ - name: "bar", - version: "0.0.2", - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle ^0.1 in dependencies", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - bar: "^0.1", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain('error: No version matching "^0.1" found for specifier "bar" (but package exists)'); - expect(stdout).toBeDefined(); - expect(await new Response(stdout).text()).toBe(""); - expect(await exited).toBe(1); - expect(urls.sort()).toEqual([`${root_url}/bar`]); - expect(requested).toBe(1); - try { - await access(join(package_dir, "bun.lockb")); - expect(() => {}).toThrow(); - } catch (err: any) { - expect(err.code).toBe("ENOENT"); - } -}); - -it("should handle ^0.0.0 in dependencies", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - bar: "^0.0.0", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain('error: No version matching "^0.0.0" found for specifier "bar" (but package exists)'); - expect(stdout).toBeDefined(); - expect(await new Response(stdout).text()).toBe(""); - expect(await exited).toBe(1); - expect(urls.sort()).toEqual([`${root_url}/bar`]); - expect(requested).toBe(1); - try { - await access(join(package_dir, "bun.lockb")); - expect(() => {}).toThrow(); - } catch (err: any) { - expect(err.code).toBe("ENOENT"); - } -}); - -it("should handle ^0.0.2 in dependencies", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - bar: "^0.0.2", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + bar@0.0.2", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "bar"]); - expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["package.json"]); - expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({ - name: "bar", - version: "0.0.2", - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle ^0.0.2-rc in dependencies", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls, { "0.0.2-rc": { as: "0.0.2" } })); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - bar: "^0.0.2-rc", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + bar@0.0.2-rc", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "bar"]); - expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["package.json"]); - expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({ - name: "bar", - version: "0.0.2", - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle ^0.0.2-alpha.3+b4d in dependencies", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls, { "0.0.2-alpha.3": { as: "0.0.2" } })); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - bar: "^0.0.2-alpha.3+b4d", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + bar@0.0.2-alpha.3", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "bar"]); - expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["package.json"]); - expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({ - name: "bar", - version: "0.0.2", - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should prefer latest-tagged dependency", async () => { - const urls: string[] = []; - setHandler( - dummyRegistry(urls, { - "0.0.3": { - bin: { - "baz-run": "index.js", - }, - }, - "0.0.5": { - bin: { - "baz-exec": "index.js", - }, - }, - latest: "0.0.3", - }), - ); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - baz: "~0.0.2", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + baz@0.0.3", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "baz"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]); - expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "baz", "index.js")); - expect(await readdirSorted(join(package_dir, "node_modules", "baz"))).toEqual(["index.js", "package.json"]); - expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.3", - bin: { - "baz-run": "index.js", - }, - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle dependency aliasing", async () => { - const urls = []; - setHandler( - dummyRegistry(urls, { - "0.0.3": { - bin: { - "baz-run": "index.js", - }, - }, - }), - ); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - dependencies: { - Bar: "npm:baz", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + Bar@0.0.3", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "Bar"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]); - expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "Bar", "index.js")); - expect(await readdirSorted(join(package_dir, "node_modules", "Bar"))).toEqual(["index.js", "package.json"]); - expect(await file(join(package_dir, "node_modules", "Bar", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.3", - bin: { - "baz-run": "index.js", - }, - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle dependency aliasing (versioned)", async () => { - const urls: string[] = []; - setHandler( - dummyRegistry(urls, { - "0.0.3": { - bin: { - "baz-run": "index.js", - }, - }, - }), - ); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - dependencies: { - Bar: "npm:baz@0.0.3", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + Bar@0.0.3", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "Bar"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]); - expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "Bar", "index.js")); - expect(await readdirSorted(join(package_dir, "node_modules", "Bar"))).toEqual(["index.js", "package.json"]); - expect(await file(join(package_dir, "node_modules", "Bar", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.3", - bin: { - "baz-run": "index.js", - }, - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle dependency aliasing (dist-tagged)", async () => { - const urls: string[] = []; - setHandler( - dummyRegistry(urls, { - "0.0.3": { - bin: { - "baz-run": "index.js", - }, - }, - }), - ); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - dependencies: { - Bar: "npm:baz@latest", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + Bar@0.0.3", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "Bar"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]); - expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "Bar", "index.js")); - expect(await readdirSorted(join(package_dir, "node_modules", "Bar"))).toEqual(["index.js", "package.json"]); - expect(await file(join(package_dir, "node_modules", "Bar", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.3", - bin: { - "baz-run": "index.js", - }, - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should not reinstall aliased dependencies", async () => { - const urls = []; - setHandler( - dummyRegistry(urls, { - "0.0.3": { - bin: { - "baz-run": "index.js", - }, - }, - }), - ); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - dependencies: { - Bar: "npm:baz", - }, - }), - ); - const { - stdout: stdout1, - stderr: stderr1, - exited: exited1, - } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr1).toBeDefined(); - const err1 = await new Response(stderr1).text(); - expect(err1).toContain("Saved lockfile"); - expect(stdout1).toBeDefined(); - const out1 = await new Response(stdout1).text(); - expect(out1.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + Bar@0.0.3", - "", - " 1 packages installed", - ]); - expect(await exited1).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "Bar"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]); - expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "Bar", "index.js")); - expect(await readdirSorted(join(package_dir, "node_modules", "Bar"))).toEqual(["index.js", "package.json"]); - expect(await file(join(package_dir, "node_modules", "Bar", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.3", - bin: { - "baz-run": "index.js", - }, - }); - await access(join(package_dir, "bun.lockb")); - // Performs `bun install` again, expects no-op - urls.length = 0; - const { - stdout: stdout2, - stderr: stderr2, - exited: exited2, - } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr2).toBeDefined(); - const err2 = await new Response(stderr2).text(); - expect(err2).not.toContain("Saved lockfile"); - expect(stdout2).toBeDefined(); - const out2 = await new Response(stdout2).text(); - expect(out2.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - "", - "Checked 1 installs across 2 packages (no changes)", - ]); - expect(await exited2).toBe(0); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "Bar"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]); - expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "Bar", "index.js")); - expect(await readdirSorted(join(package_dir, "node_modules", "Bar"))).toEqual(["index.js", "package.json"]); - expect(await file(join(package_dir, "node_modules", "Bar", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.3", - bin: { - "baz-run": "index.js", - }, - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle aliased & direct dependency references", async () => { - const urls = []; - setHandler( - dummyRegistry(urls, { - "0.0.3": { - bin: { - "baz-run": "index.js", - }, - }, - }), - ); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - baz: "~0.0.2", - }, - workspaces: ["bar"], - }), - ); - await mkdir(join(package_dir, "bar")); - await writeFile( - join(package_dir, "bar", "package.json"), - JSON.stringify({ - name: "bar", - version: "0.0.4", - dependencies: { - moo: "npm:baz", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + bar@workspace:bar", - " + baz@0.0.3", - "", - " 2 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "bar", "baz"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]); - expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "baz", "index.js")); - expect(await readlink(join(package_dir, "node_modules", "bar"))).toBe(join("..", "bar")); - expect(await readdirSorted(join(package_dir, "node_modules", "baz"))).toEqual(["index.js", "package.json"]); - expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.3", - bin: { - "baz-run": "index.js", - }, - }); - expect(await readdirSorted(join(package_dir, "bar"))).toEqual(["node_modules", "package.json"]); - expect(await readdirSorted(join(package_dir, "bar", "node_modules"))).toEqual(["moo"]); - expect(await readdirSorted(join(package_dir, "bar", "node_modules", "moo"))).toEqual(["index.js", "package.json"]); - expect(await file(join(package_dir, "bar", "node_modules", "moo", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.3", - bin: { - "baz-run": "index.js", - }, - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should not hoist if name collides with alias", async () => { - const urls = []; - setHandler( - dummyRegistry(urls, { - "0.0.2": {}, - "0.0.3": { - bin: { - "baz-run": "index.js", - }, - }, - }), - ); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - bar: "npm:baz", - }, - workspaces: ["moo"], - }), - ); - await mkdir(join(package_dir, "moo")); - await writeFile( - join(package_dir, "moo", "package.json"), - JSON.stringify({ - name: "moo", - version: "0.0.4", - dependencies: { - bar: "0.0.2", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + moo@workspace:moo", - " + bar@0.0.3", - "", - " 3 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([ - `${root_url}/bar`, - `${root_url}/bar-0.0.2.tgz`, - `${root_url}/baz`, - `${root_url}/baz-0.0.3.tgz`, - ]); - expect(requested).toBe(4); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "bar", "moo"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]); - expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "bar", "index.js")); - expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["index.js", "package.json"]); - expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.3", - bin: { - "baz-run": "index.js", - }, - }); - expect(await readlink(join(package_dir, "node_modules", "moo"))).toBe(join("..", "moo")); - expect(await readdirSorted(join(package_dir, "moo"))).toEqual(["node_modules", "package.json"]); - expect(await readdirSorted(join(package_dir, "moo", "node_modules"))).toEqual(["bar"]); - expect(await readdirSorted(join(package_dir, "moo", "node_modules", "bar"))).toEqual(["package.json"]); - expect(await file(join(package_dir, "moo", "node_modules", "bar", "package.json")).json()).toEqual({ - name: "bar", - version: "0.0.2", - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle unscoped alias on scoped dependency", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls, { "0.1.0": {} })); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - "@barn/moo": "latest", - moo: "npm:@barn/moo", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + @barn/moo@0.1.0", - " + moo@0.1.0", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/@barn/moo`, `${root_url}/@barn/moo-0.1.0.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "@barn", "moo"]); - expect(await readdirSorted(join(package_dir, "node_modules", "@barn"))).toEqual(["moo"]); - expect(await readdirSorted(join(package_dir, "node_modules", "@barn", "moo"))).toEqual(["package.json"]); - expect(await file(join(package_dir, "node_modules", "moo", "package.json")).json()).toEqual({ - name: "@barn/moo", - version: "0.1.0", - }); - expect(await readdirSorted(join(package_dir, "node_modules", "moo"))).toEqual(["package.json"]); - expect(await file(join(package_dir, "node_modules", "moo", "package.json")).json()).toEqual({ - name: "@barn/moo", - version: "0.1.0", - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle scoped alias on unscoped dependency", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - "@baz/bar": "npm:bar", - bar: "latest", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + @baz/bar@0.0.2", - " + bar@0.0.2", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "@baz", "bar"]); - expect(await readdirSorted(join(package_dir, "node_modules", "@baz"))).toEqual(["bar"]); - expect(await readdirSorted(join(package_dir, "node_modules", "@baz", "bar"))).toEqual(["package.json"]); - expect(await file(join(package_dir, "node_modules", "@baz", "bar", "package.json")).json()).toEqual({ - name: "bar", - version: "0.0.2", - }); - expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["package.json"]); - expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({ - name: "bar", - version: "0.0.2", - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle GitHub URL in dependencies (user/repo)", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - dependencies: { - uglify: "mishoo/UglifyJS", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - let out = await new Response(stdout).text(); - out = out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, ""); - out = out.replace(/(github:[^#]+)#[a-f0-9]+/, "$1"); - expect(out.split(/\r?\n/)).toEqual([" + uglify@github:mishoo/UglifyJS", "", " 1 packages installed"]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]); - expect(await readdirSorted(join(package_dir, "node_modules", "uglify"))).toEqual([ - ".bun-tag", - ".gitattributes", - ".github", - ".gitignore", - "CONTRIBUTING.md", - "LICENSE", - "README.md", - "bin", - "lib", - "package.json", - "test", - "tools", - ]); - const package_json = await file(join(package_dir, "node_modules", "uglify", "package.json")).json(); - expect(package_json.name).toBe("uglify-js"); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle GitHub URL in dependencies (user/repo#commit-id)", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - dependencies: { - uglify: "mishoo/UglifyJS#e219a9a", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + uglify@github:mishoo/UglifyJS#e219a9a", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".cache"))).toEqual([ - "@GH@mishoo-UglifyJS-e219a9a", - "uglify", - ]); - expect(await readdirSorted(join(package_dir, "node_modules", ".cache", "uglify"))).toEqual([ - "mishoo-UglifyJS-e219a9a", - ]); - expect(await readlink(join(package_dir, "node_modules", ".cache", "uglify", "mishoo-UglifyJS-e219a9a"))).toBe( - join(package_dir, "node_modules", ".cache", "@GH@mishoo-UglifyJS-e219a9a"), - ); - expect(await readdirSorted(join(package_dir, "node_modules", "uglify"))).toEqual([ - ".bun-tag", - ".gitattributes", - ".github", - ".gitignore", - "CONTRIBUTING.md", - "LICENSE", - "README.md", - "bin", - "lib", - "package.json", - "test", - "tools", - ]); - const package_json = await file(join(package_dir, "node_modules", "uglify", "package.json")).json(); - expect(package_json.name).toBe("uglify-js"); - expect(package_json.version).toBe("3.14.1"); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle GitHub URL in dependencies (user/repo#tag)", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - dependencies: { - uglify: "mishoo/UglifyJS#v3.14.1", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + uglify@github:mishoo/UglifyJS#e219a9a", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".cache"))).toEqual([ - "@GH@mishoo-UglifyJS-e219a9a", - "uglify", - ]); - expect(await readdirSorted(join(package_dir, "node_modules", ".cache", "uglify"))).toEqual([ - "mishoo-UglifyJS-e219a9a", - ]); - expect(await readlink(join(package_dir, "node_modules", ".cache", "uglify", "mishoo-UglifyJS-e219a9a"))).toBe( - join(package_dir, "node_modules", ".cache", "@GH@mishoo-UglifyJS-e219a9a"), - ); - expect(await readdirSorted(join(package_dir, "node_modules", "uglify"))).toEqual([ - ".bun-tag", - ".gitattributes", - ".github", - ".gitignore", - "CONTRIBUTING.md", - "LICENSE", - "README.md", - "bin", - "lib", - "package.json", - "test", - "tools", - ]); - const package_json = await file(join(package_dir, "node_modules", "uglify", "package.json")).json(); - expect(package_json.name).toBe("uglify-js"); - expect(package_json.version).toBe("3.14.1"); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle GitHub URL in dependencies (github:user/repo#tag)", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - dependencies: { - uglify: "github:mishoo/UglifyJS#v3.14.1", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + uglify@github:mishoo/UglifyJS#e219a9a", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]); - expect(await readlink(join(package_dir, "node_modules", ".bin", "uglifyjs"))).toBe( - join("..", "uglify", "bin", "uglifyjs"), - ); - expect(await readdirSorted(join(package_dir, "node_modules", ".cache"))).toEqual([ - "@GH@mishoo-UglifyJS-e219a9a", - "uglify", - ]); - expect(await readdirSorted(join(package_dir, "node_modules", ".cache", "uglify"))).toEqual([ - "mishoo-UglifyJS-e219a9a", - ]); - expect(await readlink(join(package_dir, "node_modules", ".cache", "uglify", "mishoo-UglifyJS-e219a9a"))).toBe( - join(package_dir, "node_modules", ".cache", "@GH@mishoo-UglifyJS-e219a9a"), - ); - expect(await readdirSorted(join(package_dir, "node_modules", "uglify"))).toEqual([ - ".bun-tag", - ".gitattributes", - ".github", - ".gitignore", - "CONTRIBUTING.md", - "LICENSE", - "README.md", - "bin", - "lib", - "package.json", - "test", - "tools", - ]); - const package_json = await file(join(package_dir, "node_modules", "uglify", "package.json")).json(); - expect(package_json.name).toBe("uglify-js"); - expect(package_json.version).toBe("3.14.1"); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle GitHub URL in dependencies (https://github.com/user/repo.git)", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - dependencies: { - uglify: "https://github.com/mishoo/UglifyJS.git", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - let out = await new Response(stdout).text(); - out = out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, ""); - out = out.replace(/(github:[^#]+)#[a-f0-9]+/, "$1"); - expect(out.split(/\r?\n/)).toEqual([" + uglify@github:mishoo/UglifyJS", "", " 1 packages installed"]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]); - expect(await readdirSorted(join(package_dir, "node_modules", "uglify"))).toEqual([ - ".bun-tag", - ".gitattributes", - ".github", - ".gitignore", - "CONTRIBUTING.md", - "LICENSE", - "README.md", - "bin", - "lib", - "package.json", - "test", - "tools", - ]); - const package_json = await file(join(package_dir, "node_modules", "uglify", "package.json")).json(); - expect(package_json.name).toBe("uglify-js"); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle GitHub URL in dependencies (git://github.com/user/repo.git#commit)", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - dependencies: { - uglify: "git://github.com/mishoo/UglifyJS.git#e219a9a", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + uglify@github:mishoo/UglifyJS#e219a9a", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]); - expect(await readlink(join(package_dir, "node_modules", ".bin", "uglifyjs"))).toBe( - join("..", "uglify", "bin", "uglifyjs"), - ); - expect(await readdirSorted(join(package_dir, "node_modules", ".cache"))).toEqual([ - "@GH@mishoo-UglifyJS-e219a9a", - "uglify", - ]); - expect(await readdirSorted(join(package_dir, "node_modules", ".cache", "uglify"))).toEqual([ - "mishoo-UglifyJS-e219a9a", - ]); - expect(await readlink(join(package_dir, "node_modules", ".cache", "uglify", "mishoo-UglifyJS-e219a9a"))).toBe( - join(package_dir, "node_modules", ".cache", "@GH@mishoo-UglifyJS-e219a9a"), - ); - expect(await readdirSorted(join(package_dir, "node_modules", "uglify"))).toEqual([ - ".bun-tag", - ".gitattributes", - ".github", - ".gitignore", - "CONTRIBUTING.md", - "LICENSE", - "README.md", - "bin", - "lib", - "package.json", - "test", - "tools", - ]); - const package_json = await file(join(package_dir, "node_modules", "uglify", "package.json")).json(); - expect(package_json.name).toBe("uglify-js"); - expect(package_json.version).toBe("3.14.1"); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle GitHub URL in dependencies (git+https://github.com/user/repo.git)", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - dependencies: { - uglify: "git+https://github.com/mishoo/UglifyJS.git", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - let out = await new Response(stdout).text(); - out = out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, ""); - out = out.replace(/(github:[^#]+)#[a-f0-9]+/, "$1"); - expect(out.split(/\r?\n/)).toEqual([" + uglify@github:mishoo/UglifyJS", "", " 1 packages installed"]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]); - expect(await readdirSorted(join(package_dir, "node_modules", "uglify"))).toEqual([ - ".bun-tag", - ".gitattributes", - ".github", - ".gitignore", - "CONTRIBUTING.md", - "LICENSE", - "README.md", - "bin", - "lib", - "package.json", - "test", - "tools", - ]); - const package_json = await file(join(package_dir, "node_modules", "uglify", "package.json")).json(); - expect(package_json.name).toBe("uglify-js"); - await access(join(package_dir, "bun.lockb")); -}); - -it("should consider peerDependencies during hoisting", async () => { - const urls = []; - setHandler( - dummyRegistry(urls, { - "0.0.3": { - bin: { - "baz-run": "index.js", - }, - }, - "0.0.5": { - bin: { - "baz-exec": "index.js", - }, - }, - }), - ); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - peerDependencies: { - baz: ">0.0.3", - }, - workspaces: ["bar", "moo"], - }), - ); - await mkdir(join(package_dir, "bar")); - await writeFile( - join(package_dir, "bar", "package.json"), - JSON.stringify({ - name: "bar", - version: "0.0.2", - dependencies: { - baz: "0.0.3", - }, - }), - ); - await mkdir(join(package_dir, "moo")); - await writeFile( - join(package_dir, "moo", "package.json"), - JSON.stringify({ - name: "moo", - version: "0.0.4", - dependencies: { - baz: "0.0.5", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install", "--peer"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + bar@workspace:bar", - " + moo@workspace:moo", - "", - " 4 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`, `${root_url}/baz-0.0.5.tgz`]); - expect(requested).toBe(3); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "bar", "baz", "moo"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-exec", "baz-run"]); - expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-exec"))).toBe(join("..", "baz", "index.js")); - expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe( - join("..", "..", "bar", "node_modules", "baz", "index.js"), - ); - expect(await readlink(join(package_dir, "node_modules", "bar"))).toBe(join("..", "bar")); - expect(await readdirSorted(join(package_dir, "bar"))).toEqual(["node_modules", "package.json"]); - expect(await readdirSorted(join(package_dir, "bar", "node_modules"))).toEqual(["baz"]); - expect(await readdirSorted(join(package_dir, "bar", "node_modules", "baz"))).toEqual(["index.js", "package.json"]); - expect(await file(join(package_dir, "bar", "node_modules", "baz", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.3", - bin: { - "baz-run": "index.js", - }, - }); - expect(await readdirSorted(join(package_dir, "node_modules", "baz"))).toEqual(["index.js", "package.json"]); - expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.5", - bin: { - "baz-exec": "index.js", - }, - }); - expect(await readlink(join(package_dir, "node_modules", "moo"))).toBe(join("..", "moo")); - expect(await readdirSorted(join(package_dir, "moo"))).toEqual(["package.json"]); - await access(join(package_dir, "bun.lockb")); -}); - -it("should not regard peerDependencies declarations as duplicates", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - bar: "*", - }, - peerDependencies: { - bar: "^0.0.2", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + bar@0.0.2", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "bar"]); - expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["package.json"]); - expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({ - name: "bar", - version: "0.0.2", - }); - await access(join(package_dir, "bun.lockb")); -}); - -it("should report error on invalid format for package.json", async () => { - await writeFile(join(package_dir, "package.json"), "foo"); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err.replace(/^bun install v.+\n/, "bun install\n").split(/\r?\n/)).toEqual([ - "bun install", - "", - "", - "error: Unexpected foo", - "foo", - "^", - `${package_dir}/package.json:1:1 0`, - `ParserError parsing package.json in "${package_dir}/"`, - "", - ]); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out).toEqual(""); - expect(await exited).toBe(1); -}); - -it("should report error on invalid format for dependencies", async () => { - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: [], - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err.replace(/^bun install v.+\n/, "bun install\n").split(/\r?\n/)).toEqual([ - "bun install", - "", - "", - "error: dependencies expects a map of specifiers, e.g.", - '"dependencies": {', - ' "bun": "latest"', - "}", - '{"name":"foo","version":"0.0.1","dependencies":[]}', - " ^", - `${package_dir}/package.json:1:33 32`, - "", - ]); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out).toEqual(""); - expect(await exited).toBe(1); -}); - -it("should report error on invalid format for optionalDependencies", async () => { - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - optionalDependencies: "bar", - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err.replace(/^bun install v.+\n/, "bun install\n").split(/\r?\n/)).toEqual([ - "bun install", - "", - "", - "error: optionalDependencies expects a map of specifiers, e.g.", - '"optionalDependencies": {', - ' "bun": "latest"', - "}", - '{"name":"foo","version":"0.0.1","optionalDependencies":"bar"}', - " ^", - `${package_dir}/package.json:1:33 32`, - "", - ]); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out).toEqual(""); - expect(await exited).toBe(1); -}); - -it("should report error on invalid format for workspaces", async () => { - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - workspaces: { - packages: { bar: true }, - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err.replace(/^bun install v.+\n/, "bun install\n").split(/\r?\n/)).toEqual([ - "bun install", - "", - "", - "error: Workspaces expects an array of strings, e.g.", - '"workspaces": [', - ' "path/to/package"', - "]", - '{"name":"foo","version":"0.0.1","workspaces":{"packages":{"bar":true}}}', - " ^", - `${package_dir}/package.json:1:33 32`, - "", - ]); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out).toEqual(""); - expect(await exited).toBe(1); -}); - -it("should report error on duplicated workspace packages", async () => { - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - workspaces: ["bar", "baz"], - }), - ); - await mkdir(join(package_dir, "bar")); - await writeFile( - join(package_dir, "bar", "package.json"), - JSON.stringify({ - name: "moo", - version: "0.0.2", - }), - ); - await mkdir(join(package_dir, "baz")); - await writeFile( - join(package_dir, "baz", "package.json"), - JSON.stringify({ - name: "moo", - version: "0.0.3", - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err.replace(/^bun install v.+\n/, "bun install\n").split(/\r?\n/)).toEqual([ - "bun install", - "", - "", - 'error: Workspace name "moo" already exists', - '{"name":"foo","version":"0.0.1","workspaces":["bar","baz"]}', - " ^", - `${package_dir}/package.json:1:53 52`, - "", - ]); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out).toEqual(""); - expect(await exited).toBe(1); -}); - -it("should handle Git URL in dependencies", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - dependencies: { - "uglify-js": "git+https://git@github.com/mishoo/UglifyJS.git", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - let out = await new Response(stdout).text(); - out = out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, ""); - out = out.replace(/(\.git)#[a-f0-9]+/, "$1"); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + uglify-js@git+https://git@github.com/mishoo/UglifyJS.git", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify-js"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]); - expect(await readlink(join(package_dir, "node_modules", ".bin", "uglifyjs"))).toBe( - join("..", "uglify-js", "bin", "uglifyjs"), - ); - expect((await readdirSorted(join(package_dir, "node_modules", ".cache")))[0]).toBe("9694c5fe9c41ad51.git"); - expect(await readdirSorted(join(package_dir, "node_modules", "uglify-js"))).toEqual([ - ".bun-tag", - ".gitattributes", - ".github", - ".gitignore", - "CONTRIBUTING.md", - "LICENSE", - "README.md", - "bin", - "lib", - "package.json", - "test", - "tools", - ]); - const package_json = await file(join(package_dir, "node_modules", "uglify-js", "package.json")).json(); - expect(package_json.name).toBe("uglify-js"); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle Git URL in dependencies (SCP-style)", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - uglify: "github.com:mishoo/UglifyJS.git", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - let out = await new Response(stdout).text(); - out = out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, ""); - out = out.replace(/(\.git)#[a-f0-9]+/, "$1"); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + uglify@git+ssh://github.com:mishoo/UglifyJS.git", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]); - expect(await readlink(join(package_dir, "node_modules", ".bin", "uglifyjs"))).toBe( - join("..", "uglify", "bin", "uglifyjs"), - ); - expect((await readdirSorted(join(package_dir, "node_modules", ".cache")))[0]).toBe("87d55589eb4217d2.git"); - expect(await readdirSorted(join(package_dir, "node_modules", "uglify"))).toEqual([ - ".bun-tag", - ".gitattributes", - ".github", - ".gitignore", - "CONTRIBUTING.md", - "LICENSE", - "README.md", - "bin", - "lib", - "package.json", - "test", - "tools", - ]); - const package_json = await file(join(package_dir, "node_modules", "uglify", "package.json")).json(); - expect(package_json.name).toBe("uglify-js"); - await access(join(package_dir, "bun.lockb")); -}); - -it("should handle Git URL with committish in dependencies", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - dependencies: { - uglify: "git+https://git@github.com/mishoo/UglifyJS.git#v3.14.1", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + uglify@git+https://git@github.com/mishoo/UglifyJS.git#e219a9a78a0d2251e4dcbd4bb9034207eb484fe8", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify"]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]); - expect(await readlink(join(package_dir, "node_modules", ".bin", "uglifyjs"))).toBe( - join("..", "uglify", "bin", "uglifyjs"), - ); - expect(await readdirSorted(join(package_dir, "node_modules", ".cache"))).toEqual([ - "9694c5fe9c41ad51.git", - "@G@e219a9a78a0d2251e4dcbd4bb9034207eb484fe8", - ]); - expect(await readdirSorted(join(package_dir, "node_modules", "uglify"))).toEqual([ - ".bun-tag", - ".gitattributes", - ".github", - ".gitignore", - "CONTRIBUTING.md", - "LICENSE", - "README.md", - "bin", - "lib", - "package.json", - "test", - "tools", - ]); - const package_json = await file(join(package_dir, "node_modules", "uglify", "package.json")).json(); - expect(package_json.name).toBe("uglify-js"); - expect(package_json.version).toBe("3.14.1"); - await access(join(package_dir, "bun.lockb")); -}); - -it("should fail on invalid Git URL", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - dependencies: { - uglify: "git+http://bun.sh/no_such_repo", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err.split(/\r?\n/)).toContain('error: "git clone" for "uglify" failed'); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out).toBe(""); - expect(await exited).toBe(1); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(0); - try { - await access(join(package_dir, "bun.lockb")); - expect(() => {}).toThrow(); - } catch (err: any) { - expect(err.code).toBe("ENOENT"); - } -}); - -it("should fail on Git URL with invalid committish", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - dependencies: { - uglify: "git+https://git@github.com/mishoo/UglifyJS.git#404-no_such_tag", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err.split(/\r?\n/)).toContain( - 'error: no commit matching "404-no_such_tag" found for "uglify" (but repository exists)', - ); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out).toBe(""); - expect(await exited).toBe(1); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(0); - try { - await access(join(package_dir, "bun.lockb")); - expect(() => {}).toThrow(); - } catch (err: any) { - expect(err.code).toBe("ENOENT"); - } -}); - -it("should de-duplicate committish in Git URLs", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "Foo", - version: "0.0.1", - dependencies: { - "uglify-ver": "git+https://git@github.com/mishoo/UglifyJS.git#v3.14.1", - "uglify-hash": "git+https://git@github.com/mishoo/UglifyJS.git#e219a9a", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + uglify-hash@git+https://git@github.com/mishoo/UglifyJS.git#e219a9a78a0d2251e4dcbd4bb9034207eb484fe8", - " + uglify-ver@git+https://git@github.com/mishoo/UglifyJS.git#e219a9a78a0d2251e4dcbd4bb9034207eb484fe8", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([ - ".bin", - ".cache", - "uglify-hash", - "uglify-ver", - ]); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]); - expect(await readlink(join(package_dir, "node_modules", ".bin", "uglifyjs"))).toBe( - join("..", "uglify-hash", "bin", "uglifyjs"), - ); - expect(await readdirSorted(join(package_dir, "node_modules", ".cache"))).toEqual([ - "9694c5fe9c41ad51.git", - "@G@e219a9a78a0d2251e4dcbd4bb9034207eb484fe8", - ]); - expect(await readdirSorted(join(package_dir, "node_modules", "uglify-hash"))).toEqual([ - ".bun-tag", - ".gitattributes", - ".github", - ".gitignore", - "CONTRIBUTING.md", - "LICENSE", - "README.md", - "bin", - "lib", - "package.json", - "test", - "tools", - ]); - const hash_json = await file(join(package_dir, "node_modules", "uglify-hash", "package.json")).json(); - expect(hash_json.name).toBe("uglify-js"); - expect(hash_json.version).toBe("3.14.1"); - expect(await readdirSorted(join(package_dir, "node_modules", "uglify-ver"))).toEqual([ - ".bun-tag", - ".gitattributes", - ".github", - ".gitignore", - "CONTRIBUTING.md", - "LICENSE", - "README.md", - "bin", - "lib", - "package.json", - "test", - "tools", - ]); - const ver_json = await file(join(package_dir, "node_modules", "uglify-ver", "package.json")).json(); - expect(ver_json.name).toBe("uglify-js"); - expect(ver_json.version).toBe("3.14.1"); - await access(join(package_dir, "bun.lockb")); -}); - -it("should prefer optionalDependencies over dependencies of the same name", async () => { - const urls = []; - setHandler( - dummyRegistry(urls, { - "0.0.3": {}, - "0.0.5": {}, - }), - ); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - baz: "0.0.5", - }, - optionalDependencies: { - baz: "0.0.3", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + baz@0.0.3", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "baz"]); - expect(await readdirSorted(join(package_dir, "node_modules", "baz"))).toEqual(["index.js", "package.json"]); - expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.3", - bin: { - "baz-run": "index.js", - }, - }); -}); - -it("should prefer dependencies over peerDependencies of the same name", async () => { - const urls = []; - setHandler( - dummyRegistry(urls, { - "0.0.3": {}, - "0.0.5": {}, - }), - ); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - baz: "0.0.5", - }, - peerDependencies: { - baz: "0.0.3", - }, - }), - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("Saved lockfile"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ - " + baz@0.0.5", - "", - " 1 packages installed", - ]); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.5.tgz`]); - expect(requested).toBe(2); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "baz"]); - expect(await readdirSorted(join(package_dir, "node_modules", "baz"))).toEqual(["index.js", "package.json"]); - expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({ - name: "baz", - version: "0.0.5", - bin: { - "baz-exec": "index.js", - }, - }); -}); diff --git a/test/bun.js/install/bun-link.test.ts b/test/bun.js/install/bun-link.test.ts deleted file mode 100644 index c1e1d1890..000000000 --- a/test/bun.js/install/bun-link.test.ts +++ /dev/null @@ -1,347 +0,0 @@ -import { spawn } from "bun"; -import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from "bun:test"; -import { bunExe } from "bunExe"; -import { bunEnv as env } from "bunEnv"; -import { access, mkdtemp, readlink, realpath, rm, writeFile } from "fs/promises"; -import { basename, join } from "path"; -import { tmpdir } from "os"; -import { - dummyAfterAll, - dummyAfterEach, - dummyBeforeAll, - dummyBeforeEach, - package_dir, - readdirSorted, -} from "./dummy.registry"; - -beforeAll(dummyBeforeAll); -afterAll(dummyAfterAll); - -let link_dir; - -beforeEach(async () => { - link_dir = await mkdtemp(join(await realpath(tmpdir()), "bun-link.test")); - await dummyBeforeEach(); -}); -afterEach(async () => { - await rm(link_dir, { force: true, recursive: true }); - await dummyAfterEach(); -}); - -it("should link package", async () => { - const link_name = basename(link_dir).slice("bun-link.".length); - await writeFile( - join(link_dir, "package.json"), - JSON.stringify({ - name: link_name, - version: "0.0.1", - }), - ); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.2", - }), - ); - - const { - stdout: stdout1, - stderr: stderr1, - exited: exited1, - } = spawn({ - cmd: [bunExe(), "link"], - cwd: link_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr1).toBeDefined(); - const err1 = await new Response(stderr1).text(); - expect(err1.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual(["bun link", ""]); - expect(stdout1).toBeDefined(); - expect(await new Response(stdout1).text()).toContain(`Success! Registered \\"${link_name}\\"`); - expect(await exited1).toBe(0); - - const { - stdout: stdout2, - stderr: stderr2, - exited: exited2, - } = spawn({ - cmd: [bunExe(), "link", link_name], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr2).toBeDefined(); - const err2 = await new Response(stderr2).text(); - expect(err2.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual(["bun link", ""]); - expect(stdout2).toBeDefined(); - const out2 = await new Response(stdout2).text(); - expect(out2.replace(/\s*\[[0-9\.]+ms\]\s*$/, "").split(/\r?\n/)).toEqual([ - "", - ` installed ${link_name}@link:${link_name}`, - "", - "", - " 1 packages installed", - ]); - expect(await exited2).toBe(0); - - const { - stdout: stdout3, - stderr: stderr3, - exited: exited3, - } = spawn({ - cmd: [bunExe(), "unlink"], - cwd: link_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr3).toBeDefined(); - const err3 = await new Response(stderr3).text(); - expect(err3.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual(["bun unlink", ""]); - expect(stdout3).toBeDefined(); - expect(await new Response(stdout3).text()).toContain(`success: unlinked package "${link_name}"`); - expect(await exited3).toBe(0); - - const { - stdout: stdout4, - stderr: stderr4, - exited: exited4, - } = spawn({ - cmd: [bunExe(), "link", link_name], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr4).toBeDefined(); - const err4 = await new Response(stderr4).text(); - expect(err4).toContain(`error: package "${link_name}" is not linked`); - expect(stdout4).toBeDefined(); - expect(await new Response(stdout4).text()).toBe(""); - expect(await exited4).toBe(1); -}); - -it("should link scoped package", async () => { - const link_name = `@${basename(link_dir).slice("bun-link.".length)}/foo`; - await writeFile( - join(link_dir, "package.json"), - JSON.stringify({ - name: link_name, - version: "0.0.1", - }), - ); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "bar", - version: "0.0.2", - }), - ); - - const { - stdout: stdout1, - stderr: stderr1, - exited: exited1, - } = spawn({ - cmd: [bunExe(), "link"], - cwd: link_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr1).toBeDefined(); - const err1 = await new Response(stderr1).text(); - expect(err1.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual(["bun link", ""]); - expect(stdout1).toBeDefined(); - expect(await new Response(stdout1).text()).toContain(`Success! Registered \\"${link_name}\\"`); - expect(await exited1).toBe(0); - - const { - stdout: stdout2, - stderr: stderr2, - exited: exited2, - } = spawn({ - cmd: [bunExe(), "link", link_name], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr2).toBeDefined(); - const err2 = await new Response(stderr2).text(); - expect(err2.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual(["bun link", ""]); - expect(stdout2).toBeDefined(); - const out2 = await new Response(stdout2).text(); - expect(out2.replace(/\s*\[[0-9\.]+ms\]\s*$/, "").split(/\r?\n/)).toEqual([ - "", - ` installed ${link_name}@link:${link_name}`, - "", - "", - " 1 packages installed", - ]); - expect(await exited2).toBe(0); - - const { - stdout: stdout3, - stderr: stderr3, - exited: exited3, - } = spawn({ - cmd: [bunExe(), "unlink"], - cwd: link_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr3).toBeDefined(); - const err3 = await new Response(stderr3).text(); - expect(err3.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual(["bun unlink", ""]); - expect(stdout3).toBeDefined(); - expect(await new Response(stdout3).text()).toContain(`success: unlinked package "${link_name}"`); - expect(await exited3).toBe(0); - - const { - stdout: stdout4, - stderr: stderr4, - exited: exited4, - } = spawn({ - cmd: [bunExe(), "link", link_name], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr4).toBeDefined(); - const err4 = await new Response(stderr4).text(); - expect(err4).toContain(`error: package "${link_name}" is not linked`); - expect(stdout4).toBeDefined(); - expect(await new Response(stdout4).text()).toBe(""); - expect(await exited4).toBe(1); -}); - -it("should link dependency without crashing", async () => { - const link_name = basename(link_dir).slice("bun-link.".length) + "-really-long-name"; - await writeFile( - join(link_dir, "package.json"), - JSON.stringify({ - name: link_name, - version: "0.0.1", - bin: { - [link_name]: `${link_name}.js`, - }, - }), - ); - await writeFile(join(link_dir, `${link_name}.js`), "console.log(42);"); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.2", - dependencies: { - [link_name]: `link:${link_name}`, - }, - }), - ); - - const { - stdout: stdout1, - stderr: stderr1, - exited: exited1, - } = spawn({ - cmd: [bunExe(), "link"], - cwd: link_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr1).toBeDefined(); - const err1 = await new Response(stderr1).text(); - expect(err1.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual(["bun link", ""]); - expect(stdout1).toBeDefined(); - expect(await new Response(stdout1).text()).toContain(`Success! Registered \\"${link_name}\\"`); - expect(await exited1).toBe(0); - - const { - stdout: stdout2, - stderr: stderr2, - exited: exited2, - } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr2).toBeDefined(); - const err2 = await new Response(stderr2).text(); - expect(err2.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual(["bun install", " Saved lockfile", ""]); - expect(stdout2).toBeDefined(); - const out2 = await new Response(stdout2).text(); - expect(out2.replace(/\s*\[[0-9\.]+ms\]\s*$/, "").split(/\r?\n/)).toEqual([ - ` + ${link_name}@link:${link_name}`, - "", - " 1 packages installed", - ]); - expect(await exited2).toBe(0); - expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", link_name].sort()); - expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual([link_name]); - expect(await readlink(join(package_dir, "node_modules", ".bin", link_name))).toBe( - join("..", link_name, `${link_name}.js`), - ); - expect(await readdirSorted(join(package_dir, "node_modules", link_name))).toEqual( - ["package.json", `${link_name}.js`].sort(), - ); - await access(join(package_dir, "bun.lockb")); - - const { - stdout: stdout3, - stderr: stderr3, - exited: exited3, - } = spawn({ - cmd: [bunExe(), "unlink"], - cwd: link_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr3).toBeDefined(); - const err3 = await new Response(stderr3).text(); - expect(err3.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual(["bun unlink", ""]); - expect(stdout3).toBeDefined(); - expect(await new Response(stdout3).text()).toContain(`success: unlinked package "${link_name}"`); - expect(await exited3).toBe(0); - - const { - stdout: stdout4, - stderr: stderr4, - exited: exited4, - } = spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr4).toBeDefined(); - const err4 = await new Response(stderr4).text(); - expect(err4).toContain(`error: FileNotFound installing ${link_name}`); - expect(stdout4).toBeDefined(); - const out4 = await new Response(stdout4).text(); - expect(out4.replace(/\[[0-9\.]+m?s\]/, "[]").split(/\r?\n/)).toEqual(["Failed to install 1 packages", "[] done", ""]); - expect(await exited4).toBe(0); -}); diff --git a/test/bun.js/install/bun-pm.test.ts b/test/bun.js/install/bun-pm.test.ts deleted file mode 100644 index 3db2a4ad0..000000000 --- a/test/bun.js/install/bun-pm.test.ts +++ /dev/null @@ -1,248 +0,0 @@ -import { spawn } from "bun"; -import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from "bun:test"; -import { bunExe } from "bunExe"; -import { bunEnv as env } from "bunEnv"; -import { mkdir, writeFile } from "fs/promises"; -import { join } from "path"; -import { - dummyAfterAll, - dummyAfterEach, - dummyBeforeAll, - dummyBeforeEach, - dummyRegistry, - package_dir, - requested, - root_url, - setHandler, -} from "./dummy.registry"; - -beforeAll(dummyBeforeAll); -afterAll(dummyAfterAll); -beforeEach(dummyBeforeEach); -afterEach(dummyAfterEach); - -it("should list top-level dependency", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - moo: "./moo", - }, - }), - ); - await mkdir(join(package_dir, "moo")); - await writeFile( - join(package_dir, "moo", "package.json"), - JSON.stringify({ - name: "moo", - version: "0.1.0", - dependencies: { - bar: "latest", - }, - }), - ); - expect( - await spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }).exited, - ).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]); - expect(requested).toBe(2); - urls.length = 0; - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "pm", "ls"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - expect(await new Response(stderr).text()).toBe(""); - expect(stdout).toBeDefined(); - expect(await new Response(stdout).text()).toBe(`${package_dir} node_modules (2) -└── moo@moo -`); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(2); -}); - -it("should list all dependencies", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - moo: "./moo", - }, - }), - ); - await mkdir(join(package_dir, "moo")); - await writeFile( - join(package_dir, "moo", "package.json"), - JSON.stringify({ - name: "moo", - version: "0.1.0", - dependencies: { - bar: "latest", - }, - }), - ); - expect( - await spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }).exited, - ).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]); - expect(requested).toBe(2); - urls.length = 0; - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "pm", "ls", "--all"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - expect(await new Response(stderr).text()).toBe(""); - expect(stdout).toBeDefined(); - expect(await new Response(stdout).text()).toBe(`${package_dir} node_modules -├── bar@0.0.2 -└── moo@moo -`); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(2); -}); - -it("should list top-level aliased dependency", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - "moo-1": "./moo", - }, - }), - ); - await mkdir(join(package_dir, "moo")); - await writeFile( - join(package_dir, "moo", "package.json"), - JSON.stringify({ - name: "moo", - version: "0.1.0", - dependencies: { - "bar-1": "npm:bar", - }, - }), - ); - expect( - await spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }).exited, - ).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]); - expect(requested).toBe(2); - urls.length = 0; - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "pm", "ls"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - expect(await new Response(stderr).text()).toBe(""); - expect(stdout).toBeDefined(); - expect(await new Response(stdout).text()).toBe(`${package_dir} node_modules (2) -└── moo-1@moo -`); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(2); -}); - -it("should list aliased dependencies", async () => { - const urls: string[] = []; - setHandler(dummyRegistry(urls)); - await writeFile( - join(package_dir, "package.json"), - JSON.stringify({ - name: "foo", - version: "0.0.1", - dependencies: { - "moo-1": "./moo", - }, - }), - ); - await mkdir(join(package_dir, "moo")); - await writeFile( - join(package_dir, "moo", "package.json"), - JSON.stringify({ - name: "moo", - version: "0.1.0", - dependencies: { - "bar-1": "npm:bar", - }, - }), - ); - expect( - await spawn({ - cmd: [bunExe(), "install"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }).exited, - ).toBe(0); - expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]); - expect(requested).toBe(2); - urls.length = 0; - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "pm", "ls", "--all"], - cwd: package_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - expect(await new Response(stderr).text()).toBe(""); - expect(stdout).toBeDefined(); - expect(await new Response(stdout).text()).toBe(`${package_dir} node_modules -└── moo-1@moo - └── bar-1@0.0.2 -`); - expect(await exited).toBe(0); - expect(urls.sort()).toEqual([]); - expect(requested).toBe(2); -}); diff --git a/test/bun.js/install/bunx.test.ts b/test/bun.js/install/bunx.test.ts deleted file mode 100644 index 06266fe94..000000000 --- a/test/bun.js/install/bunx.test.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { spawn } from "bun"; -import { afterEach, beforeEach, expect, it } from "bun:test"; -import { bunExe } from "bunExe"; -import { bunEnv as env } from "bunEnv"; -import { realpathSync } from "fs"; -import { mkdtemp, realpath, rm, writeFile } from "fs/promises"; -import { tmpdir } from "os"; -import { join } from "path"; -import { readdirSorted } from "./dummy.registry"; - -let x_dir; - -beforeEach(async () => { - x_dir = realpathSync(await mkdtemp(join(tmpdir(), "bun-x.test"))); -}); -afterEach(async () => { - await rm(x_dir, { force: true, recursive: true }); -}); - -it("should install and run default (latest) version", async () => { - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "x", "uglify-js", "--compress"], - cwd: x_dir, - stdout: null, - stdin: new TextEncoder().encode("console.log(6 * 7);"), - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).not.toContain("error"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.split(/\r?\n/)).toEqual(["console.log(42);", ""]); - expect(await exited).toBe(0); -}); - -it("should install and run specified version", async () => { - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "x", "uglify-js@3.14.1", "-v"], - cwd: x_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).not.toContain("error"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.split(/\r?\n/)).toEqual(["uglify-js 3.14.1", ""]); - expect(await exited).toBe(0); -}); - -it("should output usage if no arguments are passed", async () => { - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "x"], - cwd: x_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toContain("usage: "); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out).toHaveLength(0); - expect(await exited).toBe(1); -}); - -it("should work for @scoped packages", async () => { - await rm(join(await realpath(tmpdir()), "@withfig"), { force: true, recursive: true }); - // without cache - const withoutCache = spawn({ - cmd: [bunExe(), "x", "@withfig/autocomplete-tools", "--help"], - cwd: x_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - - expect(withoutCache.stderr).toBeDefined(); - let err = await new Response(withoutCache.stderr).text(); - expect(err).not.toContain("error"); - expect(withoutCache.stdout).toBeDefined(); - let out = await new Response(withoutCache.stdout).text(); - expect(out.trim()).toContain("Usage: @withfig/autocomplete-tool"); - expect(await withoutCache.exited).toBe(0); - - // cached - const cached = spawn({ - cmd: [bunExe(), "x", "@withfig/autocomplete-tools", "--help"], - cwd: x_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - - expect(cached.stderr).toBeDefined(); - err = await new Response(cached.stderr).text(); - expect(err).not.toContain("error"); - expect(cached.stdout).toBeDefined(); - out = await new Response(cached.stdout).text(); - expect(out.trim()).toContain("Usage: @withfig/autocomplete-tool"); - expect(await cached.exited).toBe(0); -}); - -it("should download dependency to run local file", async () => { - await writeFile( - join(x_dir, "test.js"), - ` -const { minify } = require("uglify-js@3.17.4"); - -console.log(minify("print(6 * 7)").code); -`, - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "test.js"], - cwd: x_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env: { - ...env, - BUN_INSTALL_CACHE_DIR: join(x_dir, ".cache"), - }, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toBe(""); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.split(/\r?\n/)).toEqual(["print(42);", ""]); - expect(await exited).toBe(0); - expect(await readdirSorted(x_dir)).toEqual([".cache", "test.js"]); -}); - -it("should download dependencies to run local file", async () => { - await writeFile( - join(x_dir, "test.js"), - ` -import { file } from "bun"; -import decompress from "decompress@4.2.1"; - -const buffer = await file("${join(import.meta.dir, "baz-0.0.3.tgz")}").arrayBuffer(); -for (const entry of await decompress(Buffer.from(buffer))) { - console.log(\`\${entry.type}: \${entry.path}\`); -} -`, - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "test.js"], - cwd: x_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env: { - ...env, - BUN_INSTALL_CACHE_DIR: join(x_dir, ".cache"), - }, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).toBe(""); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.split(/\r?\n/)).toEqual([ - "directory: package/", - "file: package/index.js", - "file: package/package.json", - "", - ]); - expect(await exited).toBe(0); - expect(await readdirSorted(x_dir)).toEqual([".cache", "test.js"]); -}); - -it("should execute from current working directory", async () => { - await writeFile( - join(x_dir, "test.js"), - ` -console.log( -6 -* -7 -)`, - ); - const { stdout, stderr, exited } = spawn({ - cmd: [bunExe(), "--bun", "x", "uglify-js", "test.js", "--compress"], - cwd: x_dir, - stdout: null, - stdin: "pipe", - stderr: "pipe", - env, - }); - expect(stderr).toBeDefined(); - const err = await new Response(stderr).text(); - expect(err).not.toContain("error"); - expect(stdout).toBeDefined(); - const out = await new Response(stdout).text(); - expect(out.split(/\r?\n/)).toEqual(["console.log(42);", ""]); - expect(await exited).toBe(0); - expect(await readdirSorted(x_dir)).toEqual(["test.js"]); -}); diff --git a/test/bun.js/install/dummy.registry.ts b/test/bun.js/install/dummy.registry.ts deleted file mode 100644 index d3fdc2d7f..000000000 --- a/test/bun.js/install/dummy.registry.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { file } from "bun"; -import { expect } from "bun:test"; -import { mkdtemp, readdir, realpath, rm, writeFile } from "fs/promises"; -import { tmpdir } from "os"; -import { basename, join } from "path"; - -let handler, server; -export let package_dir, requested, root_url; - -export function dummyRegistry(urls, info: any = { "0.0.2": {} }) { - return async request => { - urls.push(request.url); - expect(request.method).toBe("GET"); - if (request.url.endsWith(".tgz")) { - return new Response(file(join(import.meta.dir, basename(request.url).toLowerCase()))); - } - expect(request.headers.get("accept")).toBe( - "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*", - ); - expect(request.headers.get("npm-auth-type")).toBe(null); - expect(await request.text()).toBe(""); - const name = request.url.slice(request.url.indexOf("/", root_url.length) + 1); - const versions = {}; - let version; - for (version in info) { - if (!/^[0-9]/.test(version)) continue; - versions[version] = { - name, - version, - dist: { - tarball: `${request.url}-${info[version].as ?? version}.tgz`, - }, - ...info[version], - }; - } - return new Response( - JSON.stringify({ - name, - versions, - "dist-tags": { - latest: info.latest ?? version, - }, - }), - ); - }; -} - -export async function readdirSorted(path: PathLike): Promise { - const results = await readdir(path); - results.sort(); - return results; -} - -export function setHandler(newHandler) { - handler = newHandler; -} - -function resetHanlder() { - setHandler(() => new Response("Tea Break~", { status: 418 })); -} - -export function dummyBeforeAll() { - server = Bun.serve({ - async fetch(request) { - requested++; - return await handler(request); - }, - port: 0, - }); - root_url = `http://localhost:${server.port}`; -} - -export function dummyAfterAll() { - server.stop(); -} - -export async function dummyBeforeEach() { - resetHanlder(); - requested = 0; - package_dir = await mkdtemp(join(await realpath(tmpdir()), "bun-install.test")); - await writeFile( - join(package_dir, "bunfig.toml"), - ` -[install] -cache = false -registry = "http://localhost:${server.port}/" -`, - ); -} - -export async function dummyAfterEach() { - resetHanlder(); - await rm(package_dir, { force: true, recursive: true }); -} diff --git a/test/bun.js/install/moo-0.1.0.tgz b/test/bun.js/install/moo-0.1.0.tgz deleted file mode 100644 index 72efff27b..000000000 Binary files a/test/bun.js/install/moo-0.1.0.tgz and /dev/null differ diff --git a/test/bun.js/jest-doesnt-auto-import.js b/test/bun.js/jest-doesnt-auto-import.js deleted file mode 100644 index 4d4a02b37..000000000 --- a/test/bun.js/jest-doesnt-auto-import.js +++ /dev/null @@ -1,12 +0,0 @@ -export function getJestGlobals() { - return { - describe: typeof describe === "function" ? describe : undefined, - it: typeof it === "function" ? it : undefined, - test: typeof test === "function" ? test : undefined, - expect: typeof expect === "function" ? expect : undefined, - beforeAll: typeof beforeAll === "function" ? beforeAll : undefined, - beforeEach: typeof beforeEach === "function" ? beforeEach : undefined, - afterAll: typeof afterAll === "function" ? afterAll : undefined, - afterEach: typeof afterEach === "function" ? afterEach : undefined, - }; -} diff --git a/test/bun.js/log-test.test.ts b/test/bun.js/log-test.test.ts deleted file mode 100644 index 29f541e32..000000000 --- a/test/bun.js/log-test.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { it, expect } from "bun:test"; -import { basename, dirname, join } from "path"; -import * as fs from "fs"; -import { readableStreamToText, spawnSync } from "bun"; -import { bunExe } from "bunExe"; -import { bunEnv } from "bunEnv"; - -it("should not log .env when quiet", async () => { - writeDirectoryTree("/tmp/log-test-silent", { - ".env": "FOO=bar", - "bunfig.toml": `logLevel = "error"`, - "index.ts": "export default console.log('Here');", - }); - const { stderr } = spawnSync({ - cmd: [bunExe(), "index.ts"], - cwd: "/tmp/log-test-silent", - env: bunEnv, - }); - - expect(stderr!.toString()).toBe(""); -}); - -it("should log .env by default", async () => { - writeDirectoryTree("/tmp/log-test-silent", { - ".env": "FOO=bar", - "bunfig.toml": ``, - "index.ts": "export default console.log('Here');", - }); - - const { stderr } = spawnSync({ - cmd: [bunExe(), "index.ts"], - cwd: "/tmp/log-test-silent", - env: bunEnv, - }); - - expect(stderr?.toString().includes(".env")).toBe(true); -}); - -function writeDirectoryTree(base, paths) { - for (const path of Object.keys(paths)) { - const content = paths[path]; - const joined = join(base, path); - - try { - fs.mkdirSync(join(base, dirname(path)), { recursive: true }); - } catch (e) {} - - try { - fs.unlinkSync(joined); - } catch (e) {} - - fs.writeFileSync(joined, content); - } -} diff --git a/test/bun.js/macro-check.js b/test/bun.js/macro-check.js deleted file mode 100644 index 0f494a4e7..000000000 --- a/test/bun.js/macro-check.js +++ /dev/null @@ -1,7 +0,0 @@ -export function keepSecondArgument(bacon1234) { - return bacon1234.arguments[1]; -} - -export function bacon(heloooo) { - return heloooo.arguments[1]; -} diff --git a/test/bun.js/microtask.test.js b/test/bun.js/microtask.test.js deleted file mode 100644 index f41159cfa..000000000 --- a/test/bun.js/microtask.test.js +++ /dev/null @@ -1,74 +0,0 @@ -import { it } from "bun:test"; - -it("queueMicrotask", async () => { - // You can verify this test is correct by copy pasting this into a browser's console and checking it doesn't throw an error. - var run = 0; - - await new Promise((resolve, reject) => { - queueMicrotask(() => { - if (run++ != 0) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - queueMicrotask(() => { - if (run++ != 3) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - }); - }); - queueMicrotask(() => { - if (run++ != 1) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - queueMicrotask(() => { - if (run++ != 4) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - - queueMicrotask(() => { - if (run++ != 6) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - }); - }); - }); - queueMicrotask(() => { - if (run++ != 2) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - queueMicrotask(() => { - if (run++ != 5) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - - queueMicrotask(() => { - if (run++ != 7) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - resolve(true); - }); - }); - }); - }); - - { - var passed = false; - try { - queueMicrotask(1234); - } catch (exception) { - passed = exception instanceof TypeError; - } - - if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is not a function"); - } - - { - var passed = false; - try { - queueMicrotask(); - } catch (exception) { - passed = exception instanceof TypeError; - } - - if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is empty"); - } -}); diff --git a/test/bun.js/mkfifo.ts b/test/bun.js/mkfifo.ts deleted file mode 100644 index 48471cbf5..000000000 --- a/test/bun.js/mkfifo.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { dlopen, ptr } from "bun:ffi"; - -var lazyMkfifo; -export function mkfifo(path: string, permissions: number = 0o666): void { - if (!lazyMkfifo) { - const suffix = process.platform === "darwin" ? "dylib" : "so.6"; - lazyMkfifo = dlopen(`libc.${suffix}`, { - mkfifo: { - args: ["ptr", "i32"], - returns: "i32", - }, - }).symbols.mkfifo; - } - - const buf = new Uint8Array(Buffer.byteLength(path) + 1); - new TextEncoder().encodeInto(path, buf); - const rc = lazyMkfifo(ptr(buf), permissions); - - if (rc < 0) { - throw new Error(`mkfifo failed`); - } -} diff --git a/test/bun.js/mmap.test.js b/test/bun.js/mmap.test.js deleted file mode 100644 index 2b15a4000..000000000 --- a/test/bun.js/mmap.test.js +++ /dev/null @@ -1,69 +0,0 @@ -import { describe, it, expect } from "bun:test"; -import { gcTick } from "./gc"; - -describe("Bun.mmap", async () => { - await gcTick(); - const path = `/tmp/bun-mmap-test_${Math.random()}.txt`; - await gcTick(); - await Bun.write(path, "hello"); - await gcTick(); - - it("mmap finalizer", async () => { - let map = Bun.mmap(path); - await gcTick(); - const map2 = Bun.mmap(path); - - map = null; - await gcTick(); - }); - - it("mmap passed to other syscalls", async () => { - const map = Bun.mmap(path); - await gcTick(); - await Bun.write(path + "1", map); - await gcTick(); - const text = await (await Bun.file(path + "1")).text(); - await gcTick(); - - expect(text).toBe(new TextDecoder().decode(map)); - }); - - it("mmap sync", async () => { - const map = Bun.mmap(path); - await gcTick(); - const map2 = Bun.mmap(path); - await gcTick(); - - const old = map[0]; - await gcTick(); - map[0] = 0; - await gcTick(); - expect(map2[0]).toBe(0); - - map2[0] = old; - await gcTick(); - expect(map[0]).toBe(old); - await gcTick(); - await Bun.write(path, "olleh"); - await gcTick(); - expect(new TextDecoder().decode(map)).toBe("olleh"); - await gcTick(); - }); - - it("mmap private", async () => { - await gcTick(); - const map = Bun.mmap(path, { shared: true }); - await gcTick(); - const map2 = Bun.mmap(path, { shared: false }); - await gcTick(); - const old = map[0]; - - await gcTick(); - map2[0] = 0; - await gcTick(); - expect(map2[0]).toBe(0); - await gcTick(); - expect(map[0]).toBe(old); - await gcTick(); - }); -}); diff --git a/test/bun.js/module-require.snapshot.js b/test/bun.js/module-require.snapshot.js deleted file mode 100644 index d0270bb4c..000000000 --- a/test/bun.js/module-require.snapshot.js +++ /dev/null @@ -1 +0,0 @@ -console.log(module.require("react")); diff --git a/test/bun.js/node-builtins.test.js b/test/bun.js/node-builtins.test.js deleted file mode 100644 index 67050f31a..000000000 --- a/test/bun.js/node-builtins.test.js +++ /dev/null @@ -1,18 +0,0 @@ -import { describe, it, expect } from "bun:test"; - -import { EventEmitter } from "events"; -var emitters = [EventEmitter, require("events")]; -describe("EventEmitter", () => { - it("should emit events", () => { - for (let Emitter of emitters) { - const emitter = new Emitter(); - var called = false; - const listener = () => { - called = true; - }; - emitter.on("test", listener); - emitter.emit("test"); - expect(called).toBe(true); - } - }); -}); diff --git a/test/bun.js/node-crypto.test.js b/test/bun.js/node-crypto.test.js deleted file mode 100644 index f148f4fe9..000000000 --- a/test/bun.js/node-crypto.test.js +++ /dev/null @@ -1,29 +0,0 @@ -import { it, expect } from "bun:test"; - -import crypto from "node:crypto"; - -it("crypto.randomBytes should return a Buffer", () => { - expect(crypto.randomBytes(1) instanceof Buffer).toBe(true); - expect(Buffer.isBuffer(crypto.randomBytes(1))).toBe(true); -}); - -// https://github.com/oven-sh/bun/issues/1839 -it("crypto.createHash ", () => { - function fn() { - crypto.createHash("sha1").update(Math.random(), "ascii").digest("base64"); - } - - for (let i = 0; i < 10; i++) fn(); -}); - -it("crypto.createHmac", () => { - const result = crypto.createHmac("sha256", "key").update("message").digest("base64"); - - expect(result).toBe("bp7ym3X//Ft6uuUn1Y/a2y/kLnIZARl2kXNDBl9Y7Uo="); -}); - -it("web crypto", async () => { - let bytes = new Uint8Array(32); - crypto.getRandomValues(bytes); - await crypto.subtle.digest("SHA-256", bytes); -}); diff --git a/test/bun.js/node-dns.test.js b/test/bun.js/node-dns.test.js deleted file mode 100644 index 6f4cac22f..000000000 --- a/test/bun.js/node-dns.test.js +++ /dev/null @@ -1,148 +0,0 @@ -import { expect, test } from "bun:test"; -import * as dns from "node:dns"; - -// TODO: -test("it exists", () => { - expect(dns).toBeDefined(); - expect(dns.lookup).toBeDefined(); - expect(dns.lookupService).toBeDefined(); - expect(dns.resolve).toBeDefined(); - expect(dns.resolve4).toBeDefined(); - expect(dns.resolve6).toBeDefined(); - expect(dns.resolveSrv).toBeDefined(); - expect(dns.resolveTxt).toBeDefined(); - expect(dns.resolveSoa).toBeDefined(); - expect(dns.resolveNaptr).toBeDefined(); - expect(dns.resolveMx).toBeDefined(); - expect(dns.resolveCaa).toBeDefined(); - expect(dns.resolveNs).toBeDefined(); - expect(dns.resolvePtr).toBeDefined(); - expect(dns.resolveCname).toBeDefined(); -}); - -// //TODO: use a bun.sh SRV for testing -test("dns.resolveSrv (_test._tcp.test.socketify.dev)", done => { - dns.resolveSrv("_test._tcp.test.socketify.dev", (err, results) => { - expect(err).toBeNull(); - expect(results instanceof Array).toBe(true); - expect(results[0].name).toBe("_dc-srv.130c90ab9de1._test._tcp.test.socketify.dev"); - expect(results[0].priority).toBe(50); - expect(results[0].weight).toBe(50); - expect(results[0].port).toBe(80); - done(err); - }); -}); - -test("dns.resolveSrv (_test._tcp.invalid.localhost)", done => { - dns.resolveSrv("_test._tcp.invalid.localhost", (err, results) => { - expect(err).toBeTruthy(); - expect(results).toBeUndefined(true); - done(); - }); -}); - -test("dns.resolveTxt (txt.socketify.dev)", done => { - dns.resolveTxt("txt.socketify.dev", (err, results) => { - expect(err).toBeNull(); - expect(results instanceof Array).toBe(true); - expect(results[0][0]).toBe("bun_test;test"); - done(err); - }); -}); - -test("dns.resolveSoa (bun.sh)", done => { - dns.resolveSoa("bun.sh", (err, result) => { - expect(err).toBeNull(); - expect(typeof result.serial).toBe("number"); - expect(result.refresh).toBe(10000); - expect(result.retry).toBe(2400); - expect(result.expire).toBe(604800); - expect(result.minttl).toBe(3600); - expect(result.nsname).toBe("hans.ns.cloudflare.com"); - expect(result.hostmaster).toBe("dns.cloudflare.com"); - done(err); - }); -}); - -test("dns.resolveNaptr (naptr.socketify.dev)", done => { - dns.resolveNaptr("naptr.socketify.dev", (err, results) => { - expect(err).toBeNull(); - expect(results instanceof Array).toBe(true); - expect(results[0].flags).toBe("S"); - expect(results[0].service).toBe("test"); - expect(results[0].regexp).toBe(""); - expect(results[0].replacement).toBe(""); - expect(results[0].order).toBe(1); - expect(results[0].preference).toBe(12); - done(err); - }); -}); - -test("dns.resolveCaa (caa.socketify.dev)", done => { - dns.resolveCaa("caa.socketify.dev", (err, results) => { - expect(err).toBeNull(); - expect(results instanceof Array).toBe(true); - expect(results[0].critical).toBe(0); - expect(results[0].issue).toBe("bun.sh"); - done(err); - }); -}); - -test("dns.resolveMx (bun.sh)", done => { - dns.resolveMx("bun.sh", (err, results) => { - expect(err).toBeNull(); - expect(results instanceof Array).toBe(true); - const priority = results[0].priority; - expect(priority >= 0 && priority < 65535).toBe(true); - expect(results[0].exchange.includes(".registrar-servers.com")).toBe(true); - done(err); - }); -}); - -test("dns.resolveNs (bun.sh) ", done => { - dns.resolveNs("bun.sh", (err, results) => { - expect(err).toBeNull(); - expect(results instanceof Array).toBe(true); - expect(results[0].includes(".ns.cloudflare.com")).toBe(true); - done(err); - }); -}); - -test("dns.resolvePtr (ptr.socketify.dev)", done => { - dns.resolvePtr("ptr.socketify.dev", (err, results) => { - expect(err).toBeNull(); - expect(results instanceof Array).toBe(true); - expect(results[0]).toBe("bun.sh"); - done(err); - }); -}); - -test("dns.resolveCname (cname.socketify.dev)", done => { - dns.resolveCname("cname.socketify.dev", (err, results) => { - expect(err).toBeNull(); - expect(results instanceof Array).toBe(true); - expect(results[0]).toBe("bun.sh"); - done(err); - }); -}); - -test("dns.lookup (example.com)", done => { - dns.lookup("example.com", (err, address, family) => { - expect(err).toBeNull(); - expect(typeof address).toBe("string"); - done(err); - }); -}); - -test("dns.lookup (localhost)", done => { - dns.lookup("localhost", (err, address, family) => { - expect(err).toBeNull(); - if (family === 6) { - expect(address).toBe("::1"); - } else { - expect(address).toBe("127.0.0.1"); - } - - done(err); - }); -}); diff --git a/test/bun.js/node-http.test.ts b/test/bun.js/node-http.test.ts deleted file mode 100644 index 619d8cb35..000000000 --- a/test/bun.js/node-http.test.ts +++ /dev/null @@ -1,593 +0,0 @@ -import { describe, expect, it, beforeAll, afterAll } from "bun:test"; -import { createServer, request, get, Agent, globalAgent, Server } from "node:http"; -import { createDoneDotAll } from "node-test-helpers"; - -describe("node:http", () => { - describe("createServer", async () => { - it("hello world", async () => { - const server = createServer((req, res) => { - expect(req.url).toBe("/hello?world"); - res.writeHead(200, { "Content-Type": "text/plain" }); - res.end("Hello World"); - }); - server.listen(8123); - - const res = await fetch("http://localhost:8123/hello?world"); - expect(await res.text()).toBe("Hello World"); - server.close(); - }); - - it("request & response body streaming (large)", async () => { - const bodyBlob = new Blob(["hello world", "hello world".repeat(9000)]); - - const input = await bodyBlob.text(); - - const server = createServer((req, res) => { - res.writeHead(200, { "Content-Type": "text/plain" }); - req.on("data", chunk => { - res.write(chunk); - }); - - req.on("end", () => { - res.end(); - }); - }); - server.listen(8124); - - const res = await fetch("http://localhost:8124", { - method: "POST", - body: bodyBlob, - }); - - const out = await res.text(); - expect(out).toBe(input); - server.close(); - }); - - it("request & response body streaming (small)", async () => { - const bodyBlob = new Blob(["hello world", "hello world".repeat(4)]); - - const input = await bodyBlob.text(); - - const server = createServer((req, res) => { - res.writeHead(200, { "Content-Type": "text/plain" }); - req.on("data", chunk => { - res.write(chunk); - }); - - req.on("end", () => { - res.end(); - }); - }); - server.listen(8125); - - const res = await fetch("http://localhost:8125", { - method: "POST", - body: bodyBlob, - }); - - const out = await res.text(); - expect(out).toBe(input); - server.close(); - }); - - it("listen should return server", async () => { - const server = createServer(); - const listenResponse = server.listen(8129); - expect(listenResponse instanceof Server).toBe(true); - expect(listenResponse).toBe(server); - listenResponse.close(); - }); - }); - - describe("request", () => { - let server; - let serverPort; - let timer: Timer | null = null; - beforeAll(() => { - server = createServer((req, res) => { - const reqUrl = new URL(req.url!, `http://${req.headers.host}`); - if (reqUrl.pathname) { - if (reqUrl.pathname === "/redirect") { - // Temporary redirect - res.writeHead(301, { - Location: `http://localhost:${serverPort}/redirected`, - }); - res.end("Got redirect!\n"); - return; - } - if (reqUrl.pathname === "/redirected") { - res.writeHead(404, { "Content-Type": "text/plain" }); - res.end("Not Found"); - return; - } - if (reqUrl.pathname === "/lowerCaseHeaders") { - res.writeHead(200, { "content-type": "text/plain", "X-Custom-Header": "custom_value" }); - res.end("Hello World"); - return; - } - if (reqUrl.pathname.includes("timeout")) { - if (timer) clearTimeout(timer); - timer = setTimeout(() => { - res.end("Hello World"); - timer = null; - }, 3000); - return; - } - if (reqUrl.pathname === "/pathTest") { - res.end("Path correct!\n"); - return; - } - } - - res.writeHead(200, { "Content-Type": "text/plain" }); - - if (req.headers["x-test"]) { - res.write(`x-test: ${req.headers["x-test"]}\n`); - } - - // Check for body - if (req.method === "POST") { - req.on("data", chunk => { - res.write(chunk); - }); - - req.on("end", () => { - res.write("POST\n"); - res.end("Hello World"); - }); - } else { - if (req.headers["X-Test"] !== undefined) { - res.write(`X-Test: test\n`); - } - res.write("Maybe GET maybe not\n"); - res.end("Hello World"); - } - }); - server.listen({ port: 0 }, (_, __, port) => { - serverPort = port; - }); - }); - afterAll(() => { - server.close(); - if (timer) clearTimeout(timer); - }); - - it("check for expected fields", done => { - const req = request({ host: "localhost", port: serverPort, method: "GET" }, res => { - res.on("end", () => { - done(); - }); - res.on("error", err => done(err)); - }); - expect(req.path).toEqual("/"); - expect(req.method).toEqual("GET"); - expect(req.host).toEqual("localhost"); - expect(req.protocol).toEqual("http:"); - req.end(); - }); - - it("should make a standard GET request when passed string as first arg", done => { - const req = request(`http://localhost:${serverPort}`, res => { - let data = ""; - res.setEncoding("utf8"); - res.on("data", chunk => { - data += chunk; - }); - res.on("end", () => { - expect(data).toBe("Maybe GET maybe not\nHello World"); - done(); - }); - res.on("error", err => done(err)); - }); - req.end(); - }); - - it("should make a https:// GET request when passed string as first arg", done => { - const req = request("https://example.com", res => { - let data = ""; - res.setEncoding("utf8"); - res.on("data", chunk => { - data += chunk; - }); - res.on("end", () => { - expect(data).toContain("This domain is for use in illustrative examples in documents"); - done(); - }); - res.on("error", err => done(err)); - }); - req.end(); - }); - - it("should make a POST request when provided POST method, even without a body", done => { - const req = request({ host: "localhost", port: serverPort, method: "POST" }, res => { - let data = ""; - res.setEncoding("utf8"); - res.on("data", chunk => { - data += chunk; - }); - res.on("end", () => { - expect(data).toBe("POST\nHello World"); - done(); - }); - res.on("error", err => done(err)); - }); - req.end(); - }); - - it("should correctly handle a POST request with a body", done => { - const req = request({ host: "localhost", port: serverPort, method: "POST" }, res => { - let data = ""; - res.setEncoding("utf8"); - res.on("data", chunk => { - data += chunk; - }); - res.on("end", () => { - expect(data).toBe("Posting\nPOST\nHello World"); - done(); - }); - res.on("error", err => done(err)); - }); - req.write("Posting\n"); - req.end(); - }); - - it("should noop request.setSocketKeepAlive without error", () => { - const req = request(`http://localhost:${serverPort}`); - req.setSocketKeepAlive(true, 1000); - req.end(); - expect(true).toBe(true); - }); - - it("should allow us to set timeout with request.setTimeout or `timeout` in options", done => { - const createDone = createDoneDotAll(done); - const req1Done = createDone(); - const req2Done = createDone(); - - // const start = Date.now(); - const req1 = request( - { - host: "localhost", - port: serverPort, - path: "/timeout", - timeout: 500, - }, - res => { - req1Done(new Error("Should not have received response")); - }, - ); - req1.on("timeout", () => req1Done()); - - const req2 = request( - { - host: "localhost", - port: serverPort, - path: "/timeout", - }, - res => { - req2Done(new Error("Should not have received response")); - }, - ); - - req2.setTimeout(500, () => { - req2Done(); - }); - req1.end(); - req2.end(); - }); - - it("should correctly set path when path provided", done => { - const createDone = createDoneDotAll(done); - const req1Done = createDone(); - const req2Done = createDone(); - - const req1 = request(`http://localhost:${serverPort}/pathTest`, res => { - let data = ""; - res.setEncoding("utf8"); - res.on("data", chunk => { - data += chunk; - }); - res.on("end", () => { - expect(data).toBe("Path correct!\n"); - req1Done(); - }); - res.on("error", err => req1Done(err)); - }); - - const req2 = request(`http://localhost:${serverPort}`, { path: "/pathTest" }, res => { - let data = ""; - res.setEncoding("utf8"); - res.on("data", chunk => { - data += chunk; - }); - res.on("end", () => { - expect(data).toBe("Path correct!\n"); - req2Done(); - }); - res.on("error", err => req2Done(err)); - }); - - req1.end(); - req2.end(); - - expect(req1.path).toBe("/pathTest"); - expect(req2.path).toBe("/pathTest"); - }); - - it("should emit response when response received", done => { - const req = request(`http://localhost:${serverPort}`); - - req.on("response", res => { - expect(res.statusCode).toBe(200); - done(); - }); - req.end(); - }); - - // NOTE: Node http.request doesn't follow redirects by default - it("should handle redirects properly", done => { - const req = request(`http://localhost:${serverPort}/redirect`, res => { - let data = ""; - res.setEncoding("utf8"); - res.on("data", chunk => { - data += chunk; - }); - res.on("end", () => { - expect(data).toBe("Got redirect!\n"); - done(); - }); - res.on("error", err => done(err)); - }); - req.end(); - }); - - it("should correctly attach headers to request", done => { - const req = request({ host: "localhost", port: serverPort, headers: { "X-Test": "test" } }, res => { - let data = ""; - res.setEncoding("utf8"); - res.on("data", chunk => { - data += chunk; - }); - res.on("end", () => { - expect(data).toBe("x-test: test\nMaybe GET maybe not\nHello World"); - done(); - }); - res.on("error", err => done(err)); - }); - req.end(); - expect(req.getHeader("X-Test")).toBe("test"); - }); - - it("should correct casing of method param", done => { - const req = request({ host: "localhost", port: serverPort, method: "get" }, res => { - let data = ""; - res.setEncoding("utf8"); - res.on("data", chunk => { - data += chunk; - }); - res.on("end", () => { - expect(data).toBe("Maybe GET maybe not\nHello World"); - done(); - }); - res.on("error", err => done(err)); - }); - req.end(); - }); - - it("should allow for port as a string", done => { - const req = request({ host: "localhost", port: `${serverPort}`, method: "GET" }, res => { - let data = ""; - res.setEncoding("utf8"); - res.on("data", chunk => { - data += chunk; - }); - res.on("end", () => { - expect(data).toBe("Maybe GET maybe not\nHello World"); - done(); - }); - res.on("error", err => done(err)); - }); - req.end(); - }); - - it("should allow us to pass a URL object", done => { - const req = request(new URL(`http://localhost:${serverPort}`), { method: "POST" }, res => { - let data = ""; - res.setEncoding("utf8"); - res.on("data", chunk => { - data += chunk; - }); - res.on("end", () => { - expect(data).toBe("Hello WorldPOST\nHello World"); - done(); - }); - res.on("error", err => done(err)); - }); - req.write("Hello World"); - req.end(); - }); - - it("should ignore body when method is GET/HEAD/OPTIONS", done => { - const createDone = createDoneDotAll(done); - const methods = ["GET", "HEAD", "OPTIONS"]; - const dones = {}; - for (const method of methods) { - dones[method] = createDone(); - } - for (const method of methods) { - const req = request(`http://localhost:${serverPort}`, { method }, res => { - let data = ""; - res.setEncoding("utf8"); - res.on("data", chunk => { - data += chunk; - }); - res.on("end", () => { - expect(data).toBe(method === "GET" ? "Maybe GET maybe not\nHello World" : ""); - dones[method](); - }); - res.on("error", err => dones[method](err)); - }); - req.write("BODY"); - req.end(); - } - }); - - it("should return response with lowercase headers", done => { - const req = request(`http://localhost:${serverPort}/lowerCaseHeaders`, res => { - console.log(res.headers); - expect(res.headers["content-type"]).toBe("text/plain"); - expect(res.headers["x-custom-header"]).toBe("custom_value"); - done(); - }); - req.end(); - }); - }); - - describe("signal", () => { - it("should abort and close the server", done => { - const server = createServer((req, res) => { - res.writeHead(200, { "Content-Type": "text/plain" }); - res.end("Hello World"); - }); - - //force timeout to not hang tests - const interval = setTimeout(() => { - expect(false).toBe(true); - server.close(); - done(); - }, 100); - - const signal = AbortSignal.timeout(30); - signal.addEventListener("abort", () => { - clearTimeout(interval); - expect(true).toBe(true); - done(); - }); - - server.listen({ signal, port: 8130 }); - }); - }); - - describe("get", () => { - let server; - beforeAll(() => { - server = createServer((req, res) => { - res.writeHead(200, { "Content-Type": "text/plain" }); - res.end("Hello World"); - }); - server.listen(8127); - }); - afterAll(() => { - server.close(); - }); - it("should make a standard GET request, like request", done => { - get("http://127.0.0.1:8127", res => { - let data = ""; - res.setEncoding("utf8"); - res.on("data", chunk => { - data += chunk; - }); - res.on("end", () => { - expect(data).toBe("Hello World"); - done(); - }); - res.on("error", err => done(err)); - }); - }); - }); - - describe("Agent", () => { - let server; - let dummyReq; - let dummyAgent; - beforeAll(() => { - dummyAgent = new Agent(); - server = createServer((req, res) => { - res.writeHead(200, { "Content-Type": "text/plain" }); - res.end("Hello World"); - }); - server.listen(8128, () => { - // Setup request after server is listening - dummyReq = request( - { - host: "localhost", - port: 8128, - agent: dummyAgent, - }, - res => {}, - ); - dummyReq.on("error", () => {}); - }); - }); - - afterAll(() => { - dummyReq.end(); - server.close(); - }); - - it("should be a class", () => { - expect(Agent instanceof Function).toBe(true); - }); - - it("should have a default maxSockets of Infinity", () => { - expect(dummyAgent.maxSockets).toBe(Infinity); - }); - - it("should have a keepAlive value", () => { - expect(dummyAgent.keepAlive).toBe(false); - }); - - it("should noop keepSocketAlive", () => { - const agent = new Agent({ keepAlive: true }); - // @ts-ignore - expect(agent.keepAlive).toBe(true); - - agent.keepSocketAlive(dummyReq.socket); - }); - - it("should provide globalAgent", () => { - expect(globalAgent instanceof Agent).toBe(true); - }); - }); - - describe("ClientRequest.signal", () => { - let server; - let server_port; - let server_host; - beforeAll(() => { - server = createServer((req, res) => { - Bun.sleep(10).then(() => { - res.writeHead(200, { "Content-Type": "text/plain" }); - res.end("Hello World"); - }); - }); - server.listen({ port: 0 }, (_err, host, port) => { - server_port = port; - server_host = host; - }); - }); - afterAll(() => { - server.close(); - }); - it("should attempt to make a standard GET request and abort", done => { - get(`http://127.0.0.1:${server_port}`, { signal: AbortSignal.timeout(5) }, res => { - let data = ""; - res.setEncoding("utf8"); - res.on("data", chunk => { - data += chunk; - }); - res.on("end", () => { - expect(true).toBeFalsy(); - done(); - }); - res.on("error", _ => { - expect(true).toBeFalsy(); - done(); - }); - }).on("error", err => { - expect(err?.name).toBe("AbortError"); - done(); - }); - }); - }); -}); diff --git a/test/bun.js/node-module-module.test.js b/test/bun.js/node-module-module.test.js deleted file mode 100644 index 549b5e085..000000000 --- a/test/bun.js/node-module-module.test.js +++ /dev/null @@ -1,5 +0,0 @@ -import { expect, test } from "bun:test"; - -test("module.globalPaths exists", () => { - expect(Array.isArray(require("module").globalPaths)).toBe(true); -}); diff --git a/test/bun.js/node-stream-uint8array.test.ts b/test/bun.js/node-stream-uint8array.test.ts deleted file mode 100644 index ec2e95d34..000000000 --- a/test/bun.js/node-stream-uint8array.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { beforeEach, describe, expect, it } from "bun:test"; -import { Readable, Writable } from "stream"; - -const ABC = new Uint8Array([0x41, 0x42, 0x43]); -const DEF = new Uint8Array([0x44, 0x45, 0x46]); -const GHI = new Uint8Array([0x47, 0x48, 0x49]); - -describe("Writable", () => { - let called; - - function logCall(fn, id) { - return function () { - called[id] = (called[id] || 0) + 1; - return fn.apply(this, arguments); - }; - } - - beforeEach(() => { - called = []; - }); - - it("should perform simple operations", () => { - let n = 0; - const writable = new Writable({ - write: logCall((chunk, encoding, cb) => { - expect(chunk instanceof Buffer).toBe(true); - if (n++ === 0) { - expect(String(chunk)).toBe("ABC"); - } else { - expect(String(chunk)).toBe("DEF"); - } - - cb(); - }, 0), - }); - - writable.write(ABC); - writable.end(DEF); - expect(called).toEqual([2]); - }); - - it("should pass in Uint8Array in object mode", () => { - const writable = new Writable({ - objectMode: true, - write: logCall((chunk, encoding, cb) => { - expect(chunk instanceof Buffer).toBe(false); - expect(chunk instanceof Uint8Array).toBe(true); - expect(chunk).toStrictEqual(ABC); - expect(encoding).toBe("utf8"); - cb(); - }, 0), - }); - - writable.end(ABC); - expect(called).toEqual([1]); - }); - - it("should handle multiple writes carried out via writev()", () => { - let callback; - - const writable = new Writable({ - write: logCall((chunk, encoding, cb) => { - expect(chunk instanceof Buffer).toBe(true); - expect(encoding).toBe("buffer"); - expect(String(chunk)).toBe("ABC"); - callback = cb; - }, 0), - writev: logCall((chunks, cb) => { - expect(chunks.length).toBe(2); - expect(chunks[0].encoding).toBe("buffer"); - expect(chunks[1].encoding).toBe("buffer"); - expect(chunks[0].chunk + chunks[1].chunk).toBe("DEFGHI"); - }, 1), - }); - - writable.write(ABC); - writable.write(DEF); - writable.end(GHI); - callback(); - expect(called).toEqual([1, 1]); - }); -}); - -describe("Readable", () => { - it("should perform simple operations", () => { - const readable = new Readable({ - read() {}, - }); - - readable.push(DEF); - readable.unshift(ABC); - - const buf = readable.read(); - expect(buf instanceof Buffer).toBe(true); - expect([...buf]).toEqual([...ABC, ...DEF]); - }); - - it("should work with setEncoding()", () => { - const readable = new Readable({ - read() {}, - }); - - readable.setEncoding("utf8"); - - readable.push(DEF); - readable.unshift(ABC); - - const out = readable.read(); - expect(out).toBe("ABCDEF"); - }); -}); diff --git a/test/bun.js/node-stream.test.js b/test/bun.js/node-stream.test.js deleted file mode 100644 index 6bff28b94..000000000 --- a/test/bun.js/node-stream.test.js +++ /dev/null @@ -1,86 +0,0 @@ -import { expect, describe, it } from "bun:test"; -import { Readable, Writable, Duplex, Transform, PassThrough } from "node:stream"; - -describe("Readable", () => { - it("should be able to be created without _construct method defined", done => { - const readable = new Readable({ - read() { - this.push("Hello World!\n"); - this.push(null); - }, - }); - expect(readable instanceof Readable).toBe(true); - let data = ""; - readable.on("data", chunk => { - data += chunk.toString(); - }); - readable.on("end", () => { - expect(data).toBe("Hello World!\n"); - done(); - }); - }); - - it("should be able to be piped via .pipe", done => { - const readable = new Readable({ - read() { - this.push("Hello World!"); - this.push(null); - }, - }); - - const writable = new Writable({ - write(chunk, encoding, callback) { - expect(chunk.toString()).toBe("Hello World!"); - callback(); - done(); - }, - }); - - readable.pipe(writable); - }); -}); - -describe("Duplex", () => { - it("should allow subclasses to be derived via .call() on class", () => { - function Subclass(opts) { - if (!(this instanceof Subclass)) return new Subclass(opts); - Duplex.call(this, opts); - } - - Object.setPrototypeOf(Subclass.prototype, Duplex.prototype); - Object.setPrototypeOf(Subclass, Duplex); - - const subclass = new Subclass(); - expect(subclass instanceof Duplex).toBe(true); - }); -}); - -describe("Transform", () => { - it("should allow subclasses to be derived via .call() on class", () => { - function Subclass(opts) { - if (!(this instanceof Subclass)) return new Subclass(opts); - Transform.call(this, opts); - } - - Object.setPrototypeOf(Subclass.prototype, Transform.prototype); - Object.setPrototypeOf(Subclass, Transform); - - const subclass = new Subclass(); - expect(subclass instanceof Transform).toBe(true); - }); -}); - -describe("PassThrough", () => { - it("should allow subclasses to be derived via .call() on class", () => { - function Subclass(opts) { - if (!(this instanceof Subclass)) return new Subclass(opts); - PassThrough.call(this, opts); - } - - Object.setPrototypeOf(Subclass.prototype, PassThrough.prototype); - Object.setPrototypeOf(Subclass, PassThrough); - - const subclass = new Subclass(); - expect(subclass instanceof PassThrough).toBe(true); - }); -}); diff --git a/test/bun.js/node-test-helpers.test.js b/test/bun.js/node-test-helpers.test.js deleted file mode 100644 index de00e30ca..000000000 --- a/test/bun.js/node-test-helpers.test.js +++ /dev/null @@ -1,169 +0,0 @@ -// import { describe, it, expect } from "bun:test"; -// import { -// throws, -// assert, -// strictEqual, -// createCallCheckCtx, -// createDoneDotAll, -// } from "./node-test-helpers"; - -// describe("NodeTestHelpers.throws()", () => { -// it("should pass when the function throws", () => { -// throws(() => { -// throw new Error("THROWN!"); -// }); -// }); - -// it("should fail when the function doesn't throw", () => { -// let err; -// try { -// throws(() => {}, Error); -// } catch (e) { -// err = e; -// } - -// expect(err instanceof Error).toBe(true); -// }); -// }); - -// describe("NodeTestHelpers.assert()", () => { -// it("should pass when the provided value is true", () => { -// assert(true); -// }); - -// it("should fail when the provided value is false", () => { -// let err; -// try { -// assert(false); -// } catch (e) { -// err = e; -// } -// expect(err instanceof Error).toBe(true); -// }); -// }); - -// describe("NodeTestHelpers.strictEqual()", () => { -// it("should pass when the provided values are deeply equal", () => { -// strictEqual(1, 1); -// strictEqual("hello", "hello"); -// const testing = { hello: "world" }; -// const testing2 = testing; -// testing2.hello = "bla"; -// strictEqual(testing, testing2); -// strictEqual(NaN, NaN); -// strictEqual(Infinity, Infinity); -// strictEqual(-Infinity, -Infinity); -// strictEqual(null, null); -// strictEqual(undefined, undefined); -// }); - -// it("should fail when the provided values are not deeply equal", () => { -// let err = null; -// try { -// strictEqual(1, 5); -// } catch (e) { -// err = e; -// } -// expect(err instanceof Error).toBe(true); -// err = null; -// try { -// strictEqual({ foo: "bar" }, { foo: "bar" }); -// } catch (e) { -// err = e; -// } -// expect(err instanceof Error).toBe(true); -// err = null; -// try { -// strictEqual("1", 1); -// } catch (e) { -// err = e; -// } -// expect(err instanceof Error).toBe(true); -// err = null; -// const obj1 = { foo: "bar" }; -// const obj2 = JSON.parse(JSON.stringify(obj1)); -// try { -// strictEqual(obj1, obj2); -// } catch (e) { -// err = e; -// } -// expect(err instanceof Error).toBe(true); -// }); -// }); - -// describe("NodeTestHelpers.createCallCheckCtx", () => { -// it("should pass when all mustCall marked callbacks have been called", (done) => { -// const { mustCall } = createCallCheckCtx(done); -// const fn1 = mustCall(() => {}); -// const fn2 = mustCall(() => {}); -// fn1(); -// fn2(); -// }); - -// it("should fail when all mustCall marked callbacks have NOT been called", (done) => { -// const mockDone = (result) => { -// expect(result instanceof Error).toBe(true); -// done(); -// }; -// const { mustCall } = createCallCheckCtx(mockDone, 600); -// const fn1 = mustCall(() => {}); -// mustCall(() => {}); -// fn1(); -// }); - -// it("should allow us to get the args of the wrapped callback from mustCall", (done) => { -// const { mustCall } = createCallCheckCtx(done); -// const fn1 = mustCall((arg1, arg2) => { -// expect(arg1).toBe("hello"); -// expect(arg2).toBe("world"); -// }); -// fn1("hello", "world"); -// }); -// }); - -// describe("NodeTestHelpers.createDoneDotAll()", () => { -// it("should pass when all dones have been called", (done) => { -// const createDone = createDoneDotAll(done); -// const done1 = createDone(600); -// const done2 = createDone(600); -// setTimeout(() => done1(), 300); -// setTimeout(() => done2(), 450); -// }); - -// it("should fail when all dones have NOT been called before timeout", (done) => { -// const mockDone = (result) => { -// expect(result instanceof Error).toBe(true); -// done(); -// }; -// const createDone = createDoneDotAll(mockDone); -// const done1 = createDone(400); -// createDone(400); -// setTimeout(() => done1(), 200); -// }); - -// it("should allow us to combine mustCall and multiple dones", (done) => { -// const createDone = createDoneDotAll(done); -// const { mustCall } = createCallCheckCtx(createDone(600)); -// const done1 = createDone(600); -// const done2 = createDone(600); -// const fn1 = mustCall(() => {}); -// const fn2 = mustCall(() => {}); -// setTimeout(() => done1(), 300); -// setTimeout(() => done2(), 450); -// setTimeout(() => fn1(), 200); -// setTimeout(() => fn2(), 200); -// }); - -// it("should fail if a done is called with an error", (done) => { -// const mockDone = (result) => { -// expect(result instanceof Error).toBe(true); -// done(); -// }; -// const createDone = createDoneDotAll(mockDone); - -// const done1 = createDone(600); -// const done2 = createDone(600); -// setTimeout(() => done1(), 300); -// setTimeout(() => done2(new Error("ERROR!")), 450); -// }); -// }); diff --git a/test/bun.js/node-test-helpers.ts b/test/bun.js/node-test-helpers.ts deleted file mode 100644 index 0eaa7d07b..000000000 --- a/test/bun.js/node-test-helpers.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { expect as expect_ } from "bun:test"; -// @ts-ignore -import { gcTick } from "gc"; -import assertNode from "node:assert"; - -type DoneCb = (err?: Error) => any; -function noop() {} - -const expect = actual => { - gcTick(); - const ret = expect_(actual); - gcTick(); - return ret; -}; - -// Assert -export const strictEqual = (...args: Parameters) => { - assertNode.strictEqual.apply(this, args); - expect(true).toBe(true); -}; - -export const notStrictEqual = (...args: Parameters) => { - assertNode.notStrictEqual.apply(this, args); - expect(true).toBe(true); -}; - -export const deepStrictEqual = (...args: Parameters) => { - assertNode.deepStrictEqual.apply(this, args); - expect(true).toBe(true); -}; - -export const throws = (...args: Parameters) => { - assertNode.throws.apply(this, args); - expect(true).toBe(true); -}; - -export const ok = (...args: Parameters) => { - assertNode.ok.apply(this, args); - expect(true).toBe(true); -}; - -export const ifError = (...args: Parameters) => { - assertNode.ifError.apply(this, args); - expect(true).toBe(true); -}; - -export const match = (...args: Parameters) => { - assertNode.match.apply(this, args); - expect(true).toBe(true); -}; - -export const assert = function (...args: any[]) { - // @ts-ignore - assertNode(...args); -}; - -Object.assign(assert, { - strictEqual, - deepStrictEqual, - notStrictEqual, - throws, - ok, - ifError, - match, -}); - -// End assert - -export const createCallCheckCtx = (done: DoneCb) => { - const createDone = createDoneDotAll(done); - - // const mustCallChecks = []; - - // failed.forEach(function (context) { - // console.log( - // "Mismatched %s function calls. Expected %s, actual %d.", - // context.name, - // context.messageSegment, - // context.actual - // ); - // console.log(context.stack.split("\n").slice(2).join("\n")); - // }); - - // TODO: Implement this to be exact only - function mustCall(fn?: (...args) => any, exact?: number) { - return mustCallAtLeast(fn, exact); - } - - function mustNotCall(reason: string = "function should not have been called") { - const localDone = createDone(); - setTimeout(() => localDone(), 200); - return () => { - done(new Error(reason)); - }; - } - - function mustSucceed(fn: () => any, exact?: number) { - return mustCall(function (err, ...args) { - ifError(err); - // @ts-ignore - if (typeof fn === "function") return fn.apply(this, args as []); - }, exact); - } - - function mustCallAtLeast(fn, minimum) { - return _mustCallInner(fn, minimum, "minimum"); - } - - function _mustCallInner(fn, criteria = 1, field) { - if (process._exiting) throw new Error("Cannot use common.mustCall*() in process exit handler"); - if (typeof fn === "number") { - criteria = fn; - fn = noop; - } else if (fn === undefined) { - fn = noop; - } - - if (typeof criteria !== "number") throw new TypeError(`Invalid ${field} value: ${criteria}`); - - let actual = 0; - let expected = criteria; - - // mustCallChecks.push(context); - const done = createDone(); - const _return = (...args) => { - try { - // @ts-ignore - const result = fn.apply(this, args); - actual++; - if (actual >= expected) { - done(); - } - return result; - } catch (err) { - if (err instanceof Error) done(err); - else if (err?.toString) done(new Error(err?.toString())); - else { - console.error("Unknown error", err); - done(new Error("Unknown error")); - } - } - }; - // Function instances have own properties that may be relevant. - // Let's replicate those properties to the returned function. - // Refs: https://tc39.es/ecma262/#sec-function-instances - Object.defineProperties(_return, { - name: { - value: fn.name, - writable: false, - enumerable: false, - configurable: true, - }, - length: { - value: fn.length, - writable: false, - enumerable: false, - configurable: true, - }, - }); - return _return; - } - return { - mustSucceed, - mustCall, - mustCallAtLeast, - mustNotCall, - }; -}; - -export function createDoneDotAll(done: DoneCb, globalTimeout?: number) { - let toComplete = 0; - let completed = 0; - const globalTimer = globalTimeout - ? setTimeout(() => { - console.log("Global Timeout"); - done(new Error("Timed out!")); - }, globalTimeout) - : undefined; - function createDoneCb(timeout?: number) { - toComplete += 1; - const timer = - timeout !== undefined - ? setTimeout(() => { - console.log("Timeout"); - done(new Error("Timed out!")); - }, timeout) - : timeout; - return (result?: Error) => { - if (timer) clearTimeout(timer); - if (globalTimer) clearTimeout(globalTimer); - if (result instanceof Error) { - done(result); - return; - } - completed += 1; - if (completed === toComplete) { - done(); - } - }; - } - return createDoneCb; -} diff --git a/test/bun.js/node-timers.test.ts b/test/bun.js/node-timers.test.ts deleted file mode 100644 index e6fa48010..000000000 --- a/test/bun.js/node-timers.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { describe, test } from "bun:test"; -import { setTimeout, clearTimeout, setInterval, setImmediate } from "node:timers"; - -for (const fn of [setTimeout, setInterval, setImmediate]) { - describe(fn.name, () => { - test("unref is possible", done => { - const timer = fn(() => { - done(new Error("should not be called")); - }, 1); - fn(() => { - done(); - }, 2); - timer.unref(); - if (fn !== setImmediate) clearTimeout(timer); - }); - }); -} diff --git a/test/bun.js/northwind.testdb b/test/bun.js/northwind.testdb deleted file mode 100644 index 66f01fdd2..000000000 Binary files a/test/bun.js/northwind.testdb and /dev/null differ diff --git a/test/bun.js/os.test.js b/test/bun.js/os.test.js deleted file mode 100644 index ea685cdb7..000000000 --- a/test/bun.js/os.test.js +++ /dev/null @@ -1,150 +0,0 @@ -import { it, expect } from "bun:test"; -import * as os from "node:os"; - -it("arch", () => { - expect(["x64", "x86", "arm64"].some(arch => os.arch() === arch)).toBe(true); -}); - -it("endianness", () => { - expect(/[BL]E/.test(os.endianness())).toBe(true); -}); - -it("freemem", () => { - expect(os.freemem() > 0).toBe(true); -}); - -it("totalmem", () => { - expect(os.totalmem() > 0).toBe(true); -}); - -it("getPriority", () => { - expect(os.getPriority()).toBe(0); - expect(os.getPriority(0)).toBe(0); -}); - -it("setPriority", () => { - expect(os.setPriority(0, 2)).toBe(undefined); - expect(os.getPriority()).toBe(2); - expect(os.setPriority(5)).toBe(undefined); - expect(os.getPriority()).toBe(5); -}); - -it("loadavg", () => { - expect(os.loadavg().length === 3).toBe(true); -}); - -it("homedir", () => { - expect(os.homedir() !== "unknown").toBe(true); -}); - -it("tmpdir", () => { - if (process.platform === "win32") { - expect(os.tmpdir()).toBe(process.env.TEMP || process.env.TMP); - expect(os.tmpdir()).toBe(`${process.env.SystemRoot || process.env.windir}\\temp`); - } else { - let dir = process.env.TMPDIR || process.env.TMP || process.env.TEMP || "/tmp"; - if (dir.length > 1 && dir.endsWith("/")) { - dir = dir.substring(0, dir.length - 1); - } - expect(os.tmpdir()).toBe(dir); - } -}); - -it("hostname", () => { - expect(os.hostname() !== "unknown").toBe(true); -}); - -it("platform", () => { - expect(["win32", "darwin", "linux", "wasm"].some(platform => os.platform() === platform)).toBe(true); -}); - -it("release", () => { - expect(os.release().length > 1).toBe(true); -}); - -it("type", () => { - expect(["Windows_NT", "Darwin", "Linux"].some(type => os.type() === type)).toBe(true); -}); - -it("uptime", () => { - expect(os.uptime() > 0).toBe(true); -}); - -it("version", () => { - expect(typeof os.version() === "string").toBe(true); -}); - -it("userInfo", () => { - const info = os.userInfo(); - - if (process.platform !== "win32") { - expect(info.username).toBe(process.env.USER); - expect(info.shell).toBe(process.env.SHELL); - expect(info.uid >= 0).toBe(true); - expect(info.gid >= 0).toBe(true); - } else { - expect(info.username).toBe(process.env.USERNAME); - expect(info.shell).toBe(null); - expect(info.uid).toBe(-1); - expect(info.gid).toBe(-1); - } -}); - -it("cpus", () => { - const cpus = os.cpus(); - - for (const cpu of cpus) { - expect(typeof cpu.model === "string").toBe(true); - expect(typeof cpu.speed === "number").toBe(true); - expect(typeof cpu.times.idle === "number").toBe(true); - expect(typeof cpu.times.irq === "number").toBe(true); - expect(typeof cpu.times.nice === "number").toBe(true); - expect(typeof cpu.times.sys === "number").toBe(true); - expect(typeof cpu.times.user === "number").toBe(true); - } -}); - -it("networkInterfaces", () => { - const networkInterfaces = os.networkInterfaces(); - - for (const networkInterface of Object.values(networkInterfaces)) { - for (const nI of networkInterface) { - expect(typeof nI.address === "string").toBe(true); - expect(typeof nI.netmask === "string").toBe(true); - expect(typeof nI.family === "string").toBe(true); - expect(typeof nI.mac === "string").toBe(true); - expect(typeof nI.internal === "boolean").toBe(true); - if (nI.cidr) - // may be null - expect(typeof nI.cidr).toBe("string"); - } - } -}); - -it("machine", () => { - const possibleValues = [ - "arm", - "arm64", - "aarch64", - "mips", - "mips64", - "ppc64", - "ppc64le", - "s390", - "s390x", - "i386", - "i686", - "x86_64", - ]; - expect(possibleValues.includes(os.machine())).toBe(true); -}); - -it("EOL", () => { - if (process.platform === "win32") expect(os.EOL).toBe("\\r\\n"); - else expect(os.EOL).toBe("\n"); -}); - -it("devNull", () => { - if (process.platform === "win32") expect(os.devNull).toBe("\\\\.\\nul"); - else expect(os.devNull).toBe("/dev/null"); -}); diff --git a/test/bun.js/package.json b/test/bun.js/package.json deleted file mode 100644 index 709dc6602..000000000 --- a/test/bun.js/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "bun-tests", - "dependencies": { - "esbuild": "0.15.13", - "uuid": "^9.0.0", - "tiny-typed-emitter": "^1.0.0", - "svelte": "^3.52.0" - }, - "prettier": "../../.prettierrc.cjs" -} diff --git a/test/bun.js/path.test.js b/test/bun.js/path.test.js deleted file mode 100644 index ad5688ea7..000000000 --- a/test/bun.js/path.test.js +++ /dev/null @@ -1,426 +0,0 @@ -const { file } = import.meta; - -import { describe, it, expect } from "bun:test"; -import * as path from "node:path"; -import assert from "assert"; - -const strictEqual = (...args) => { - assert.strictEqual(...args); - expect(true).toBe(true); -}; - -it("path.basename", () => { - strictEqual(path.basename(file), "path.test.js"); - strictEqual(path.basename(file, ".js"), "path.test"); - strictEqual(path.basename(".js", ".js"), ""); - strictEqual(path.basename(""), ""); - strictEqual(path.basename("/dir/basename.ext"), "basename.ext"); - strictEqual(path.basename("/basename.ext"), "basename.ext"); - strictEqual(path.basename("basename.ext"), "basename.ext"); - strictEqual(path.basename("basename.ext/"), "basename.ext"); - strictEqual(path.basename("basename.ext//"), "basename.ext"); - strictEqual(path.basename("aaa/bbb", "/bbb"), "bbb"); - strictEqual(path.basename("aaa/bbb", "a/bbb"), "bbb"); - strictEqual(path.basename("aaa/bbb", "bbb"), "bbb"); - strictEqual(path.basename("aaa/bbb//", "bbb"), "bbb"); - strictEqual(path.basename("aaa/bbb", "bb"), "b"); - strictEqual(path.basename("aaa/bbb", "b"), "bb"); - strictEqual(path.basename("/aaa/bbb", "/bbb"), "bbb"); - strictEqual(path.basename("/aaa/bbb", "a/bbb"), "bbb"); - strictEqual(path.basename("/aaa/bbb", "bbb"), "bbb"); - strictEqual(path.basename("/aaa/bbb//", "bbb"), "bbb"); - strictEqual(path.basename("/aaa/bbb", "bb"), "b"); - strictEqual(path.basename("/aaa/bbb", "b"), "bb"); - strictEqual(path.basename("/aaa/bbb"), "bbb"); - strictEqual(path.basename("/aaa/"), "aaa"); - strictEqual(path.basename("/aaa/b"), "b"); - strictEqual(path.basename("/a/b"), "b"); - strictEqual(path.basename("//a"), "a"); - strictEqual(path.basename("a", "a"), ""); - - // // On Windows a backslash acts as a path separator. - strictEqual(path.win32.basename("\\dir\\basename.ext"), "basename.ext"); - strictEqual(path.win32.basename("\\basename.ext"), "basename.ext"); - strictEqual(path.win32.basename("basename.ext"), "basename.ext"); - strictEqual(path.win32.basename("basename.ext\\"), "basename.ext"); - strictEqual(path.win32.basename("basename.ext\\\\"), "basename.ext"); - strictEqual(path.win32.basename("foo"), "foo"); - strictEqual(path.win32.basename("aaa\\bbb", "\\bbb"), "bbb"); - strictEqual(path.win32.basename("aaa\\bbb", "a\\bbb"), "bbb"); - strictEqual(path.win32.basename("aaa\\bbb", "bbb"), "bbb"); - strictEqual(path.win32.basename("aaa\\bbb\\\\\\\\", "bbb"), "bbb"); - strictEqual(path.win32.basename("aaa\\bbb", "bb"), "b"); - strictEqual(path.win32.basename("aaa\\bbb", "b"), "bb"); - strictEqual(path.win32.basename("C:"), ""); - strictEqual(path.win32.basename("C:."), "."); - strictEqual(path.win32.basename("C:\\"), ""); - strictEqual(path.win32.basename("C:\\dir\\base.ext"), "base.ext"); - strictEqual(path.win32.basename("C:\\basename.ext"), "basename.ext"); - strictEqual(path.win32.basename("C:basename.ext"), "basename.ext"); - strictEqual(path.win32.basename("C:basename.ext\\"), "basename.ext"); - strictEqual(path.win32.basename("C:basename.ext\\\\"), "basename.ext"); - strictEqual(path.win32.basename("C:foo"), "foo"); - strictEqual(path.win32.basename("file:stream"), "file:stream"); - strictEqual(path.win32.basename("a", "a"), ""); - - // On unix a backslash is just treated as any other character. - strictEqual(path.posix.basename("\\dir\\basename.ext"), "\\dir\\basename.ext"); - strictEqual(path.posix.basename("\\basename.ext"), "\\basename.ext"); - strictEqual(path.posix.basename("basename.ext"), "basename.ext"); - strictEqual(path.posix.basename("basename.ext\\"), "basename.ext\\"); - strictEqual(path.posix.basename("basename.ext\\\\"), "basename.ext\\\\"); - strictEqual(path.posix.basename("foo"), "foo"); - - // POSIX filenames may include control characters - // c.f. http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html - const controlCharFilename = `Icon${String.fromCharCode(13)}`; - strictEqual(path.posix.basename(`/a/b/${controlCharFilename}`), controlCharFilename); -}); - -it("path.join", () => { - const failures = []; - const backslashRE = /\\/g; - - const joinTests = [ - [ - [path.posix.join], - // Arguments result - [ - [[".", "x/b", "..", "/b/c.js"], "x/b/c.js"], - // [[], '.'], - [["/.", "x/b", "..", "/b/c.js"], "/x/b/c.js"], - [["/foo", "../../../bar"], "/bar"], - [["foo", "../../../bar"], "../../bar"], - [["foo/", "../../../bar"], "../../bar"], - [["foo/x", "../../../bar"], "../bar"], - [["foo/x", "./bar"], "foo/x/bar"], - [["foo/x/", "./bar"], "foo/x/bar"], - [["foo/x/", ".", "bar"], "foo/x/bar"], - [["./"], "./"], - [[".", "./"], "./"], - [[".", ".", "."], "."], - [[".", "./", "."], "."], - [[".", "/./", "."], "."], - [[".", "/////./", "."], "."], - [["."], "."], - [["", "."], "."], - [["", "foo"], "foo"], - [["foo", "/bar"], "foo/bar"], - [["", "/foo"], "/foo"], - [["", "", "/foo"], "/foo"], - [["", "", "foo"], "foo"], - [["foo", ""], "foo"], - [["foo/", ""], "foo/"], - [["foo", "", "/bar"], "foo/bar"], - [["./", "..", "/foo"], "../foo"], - [["./", "..", "..", "/foo"], "../../foo"], - [[".", "..", "..", "/foo"], "../../foo"], - [["", "..", "..", "/foo"], "../../foo"], - [["/"], "/"], - [["/", "."], "/"], - [["/", ".."], "/"], - [["/", "..", ".."], "/"], - [[""], "."], - [["", ""], "."], - [[" /foo"], " /foo"], - [[" ", "foo"], " /foo"], - [[" ", "."], " "], - [[" ", "/"], " /"], - [[" ", ""], " "], - [["/", "foo"], "/foo"], - [["/", "/foo"], "/foo"], - [["/", "//foo"], "/foo"], - [["/", "", "/foo"], "/foo"], - [["", "/", "foo"], "/foo"], - [["", "/", "/foo"], "/foo"], - ], - ], - ]; - - // // Windows-specific join tests - // joinTests.push([ - // path.win32.join, - // joinTests[0][1].slice(0).concat([ - // // Arguments result - // // UNC path expected - // [["//foo/bar"], "\\\\foo\\bar\\"], - // [["\\/foo/bar"], "\\\\foo\\bar\\"], - // [["\\\\foo/bar"], "\\\\foo\\bar\\"], - // // UNC path expected - server and share separate - // [["//foo", "bar"], "\\\\foo\\bar\\"], - // [["//foo/", "bar"], "\\\\foo\\bar\\"], - // [["//foo", "/bar"], "\\\\foo\\bar\\"], - // // UNC path expected - questionable - // [["//foo", "", "bar"], "\\\\foo\\bar\\"], - // [["//foo/", "", "bar"], "\\\\foo\\bar\\"], - // [["//foo/", "", "/bar"], "\\\\foo\\bar\\"], - // // UNC path expected - even more questionable - // [["", "//foo", "bar"], "\\\\foo\\bar\\"], - // [["", "//foo/", "bar"], "\\\\foo\\bar\\"], - // [["", "//foo/", "/bar"], "\\\\foo\\bar\\"], - // // No UNC path expected (no double slash in first component) - // [["\\", "foo/bar"], "\\foo\\bar"], - // [["\\", "/foo/bar"], "\\foo\\bar"], - // [["", "/", "/foo/bar"], "\\foo\\bar"], - // // No UNC path expected (no non-slashes in first component - - // // questionable) - // [["//", "foo/bar"], "\\foo\\bar"], - // [["//", "/foo/bar"], "\\foo\\bar"], - // [["\\\\", "/", "/foo/bar"], "\\foo\\bar"], - // [["//"], "\\"], - // // No UNC path expected (share name missing - questionable). - // [["//foo"], "\\foo"], - // [["//foo/"], "\\foo\\"], - // [["//foo", "/"], "\\foo\\"], - // [["//foo", "", "/"], "\\foo\\"], - // // No UNC path expected (too many leading slashes - questionable) - // [["///foo/bar"], "\\foo\\bar"], - // [["////foo", "bar"], "\\foo\\bar"], - // [["\\\\\\/foo/bar"], "\\foo\\bar"], - // // Drive-relative vs drive-absolute paths. This merely describes the - // // status quo, rather than being obviously right - // [["c:"], "c:."], - // [["c:."], "c:."], - // [["c:", ""], "c:."], - // [["", "c:"], "c:."], - // [["c:.", "/"], "c:.\\"], - // [["c:.", "file"], "c:file"], - // [["c:", "/"], "c:\\"], - // [["c:", "file"], "c:\\file"], - // ]), - // ]); - joinTests.forEach(test => { - if (!Array.isArray(test[0])) test[0] = [test[0]]; - test[0].forEach(join => { - test[1].forEach(test => { - const actual = join.apply(null, test[0]); - const expected = test[1]; - // For non-Windows specific tests with the Windows join(), we need to try - // replacing the slashes since the non-Windows specific tests' `expected` - // use forward slashes - let actualAlt; - let os; - if (join === path.win32.join) { - actualAlt = actual.replace(backslashRE, "/"); - os = "win32"; - } else { - os = "posix"; - } - if (actual !== expected && actualAlt !== expected) { - const delimiter = test[0].map(JSON.stringify).join(","); - const message = `path.${os}.join(${delimiter})\n expect=${JSON.stringify( - expected, - )}\n actual=${JSON.stringify(actual)}`; - failures.push(`\n${message}`); - } - }); - }); - }); - strictEqual(failures.length, 0, failures.join("")); -}); - -it("path.relative", () => { - const failures = []; - - const relativeTests = [ - // [ - // path.win32.relative, - // // Arguments result - // [ - // ["c:/blah\\blah", "d:/games", "d:\\games"], - // ["c:/aaaa/bbbb", "c:/aaaa", ".."], - // ["c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc"], - // ["c:/aaaa/bbbb", "c:/aaaa/bbbb", ""], - // ["c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc"], - // ["c:/aaaa/", "c:/aaaa/cccc", "cccc"], - // ["c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb"], - // ["c:/aaaa/bbbb", "d:\\", "d:\\"], - // ["c:/AaAa/bbbb", "c:/aaaa/bbbb", ""], - // ["c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc"], - // ["C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\.."], - // [ - // "C:\\foo\\test", - // "C:\\foo\\test\\bar\\package.json", - // "bar\\package.json", - // ], - // ["C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz"], - // ["C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux"], - // ["\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz"], - // ["\\\\foo\\bar\\baz", "\\\\foo\\bar", ".."], - // ["\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz"], - // ["\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux"], - // ["C:\\baz-quux", "C:\\baz", "..\\baz"], - // ["C:\\baz", "C:\\baz-quux", "..\\baz-quux"], - // ["\\\\foo\\baz-quux", "\\\\foo\\baz", "..\\baz"], - // ["\\\\foo\\baz", "\\\\foo\\baz-quux", "..\\baz-quux"], - // ["C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz"], - // ["\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz"], - // ], - // ], - [ - path.posix.relative, - // Arguments result - [ - ["/var/lib", "/var", ".."], - ["/var/lib", "/bin", "../../bin"], - ["/var/lib", "/var/lib", ""], - ["/var/lib", "/var/apache", "../apache"], - ["/var/", "/var/lib", "lib"], - ["/", "/var/lib", "var/lib"], - ["/foo/test", "/foo/test/bar/package.json", "bar/package.json"], - ["/Users/a/web/b/test/mails", "/Users/a/web/b", "../.."], - ["/foo/bar/baz-quux", "/foo/bar/baz", "../baz"], - ["/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux"], - ["/baz-quux", "/baz", "../baz"], - ["/baz", "/baz-quux", "../baz-quux"], - ["/page1/page2/foo", "/", "../../.."], - [process.cwd(), "foo", "foo"], - ], - ], - ]; - - relativeTests.forEach(test => { - const relative = test[0]; - test[1].forEach(test => { - const actual = relative(test[0], test[1]); - const expected = test[2]; - if (actual !== expected) { - const os = relative === path.win32.relative ? "win32" : "posix"; - const message = `path.${os}.relative(${test - .slice(0, 2) - .map(JSON.stringify) - .join(",")})\n expect=${JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; - failures.push(`\n${message}`); - } - }); - }); - - strictEqual(failures.length, 0, failures.join("")); - expect(true).toBe(true); -}); - -it("path.normalize", () => { - // strictEqual( - // path.win32.normalize("./fixtures///b/../b/c.js"), - // "fixtures\\b\\c.js" - // ); - // strictEqual(path.win32.normalize("/foo/../../../bar"), "\\bar"); - // strictEqual(path.win32.normalize("a//b//../b"), "a\\b"); - // strictEqual(path.win32.normalize("a//b//./c"), "a\\b\\c"); - // strictEqual(path.win32.normalize("a//b//."), "a\\b"); - // strictEqual( - // path.win32.normalize("//server/share/dir/file.ext"), - // "\\\\server\\share\\dir\\file.ext" - // ); - // strictEqual(path.win32.normalize("/a/b/c/../../../x/y/z"), "\\x\\y\\z"); - // strictEqual(path.win32.normalize("C:"), "C:."); - // strictEqual(path.win32.normalize("C:..\\abc"), "C:..\\abc"); - // strictEqual(path.win32.normalize("C:..\\..\\abc\\..\\def"), "C:..\\..\\def"); - // strictEqual(path.win32.normalize("C:\\."), "C:\\"); - // strictEqual(path.win32.normalize("file:stream"), "file:stream"); - // strictEqual(path.win32.normalize("bar\\foo..\\..\\"), "bar\\"); - // strictEqual(path.win32.normalize("bar\\foo..\\.."), "bar"); - // strictEqual(path.win32.normalize("bar\\foo..\\..\\baz"), "bar\\baz"); - // strictEqual(path.win32.normalize("bar\\foo..\\"), "bar\\foo..\\"); - // strictEqual(path.win32.normalize("bar\\foo.."), "bar\\foo.."); - // strictEqual(path.win32.normalize("..\\foo..\\..\\..\\bar"), "..\\..\\bar"); - // strictEqual( - // path.win32.normalize("..\\...\\..\\.\\...\\..\\..\\bar"), - // "..\\..\\bar" - // ); - // strictEqual( - // path.win32.normalize("../../../foo/../../../bar"), - // "..\\..\\..\\..\\..\\bar" - // ); - // strictEqual( - // path.win32.normalize("../../../foo/../../../bar/../../"), - // "..\\..\\..\\..\\..\\..\\" - // ); - // strictEqual( - // path.win32.normalize("../foobar/barfoo/foo/../../../bar/../../"), - // "..\\..\\" - // ); - // strictEqual( - // path.win32.normalize("../.../../foobar/../../../bar/../../baz"), - // "..\\..\\..\\..\\baz" - // ); - // strictEqual(path.win32.normalize("foo/bar\\baz"), "foo\\bar\\baz"); - - strictEqual(path.posix.normalize("./fixtures///b/../b/c.js"), "fixtures/b/c.js"); - strictEqual(path.posix.normalize("/foo/../../../bar"), "/bar"); - strictEqual(path.posix.normalize("a//b//../b"), "a/b"); - strictEqual(path.posix.normalize("a//b//./c"), "a/b/c"); - strictEqual(path.posix.normalize("a//b//."), "a/b"); - strictEqual(path.posix.normalize("/a/b/c/../../../x/y/z"), "/x/y/z"); - strictEqual(path.posix.normalize("///..//./foo/.//bar"), "/foo/bar"); - strictEqual(path.posix.normalize("bar/foo../../"), "bar/"); - strictEqual(path.posix.normalize("bar/foo../.."), "bar"); - strictEqual(path.posix.normalize("bar/foo../../baz"), "bar/baz"); - strictEqual(path.posix.normalize("bar/foo../"), "bar/foo../"); - strictEqual(path.posix.normalize("bar/foo.."), "bar/foo.."); - strictEqual(path.posix.normalize("../foo../../../bar"), "../../bar"); - strictEqual(path.posix.normalize("../.../.././.../../../bar"), "../../bar"); - strictEqual(path.posix.normalize("../../../foo/../../../bar"), "../../../../../bar"); - strictEqual(path.posix.normalize("../../../foo/../../../bar/../../"), "../../../../../../"); - strictEqual(path.posix.normalize("../foobar/barfoo/foo/../../../bar/../../"), "../../"); - strictEqual(path.posix.normalize("../.../../foobar/../../../bar/../../baz"), "../../../../baz"); - strictEqual(path.posix.normalize("foo/bar\\baz"), "foo/bar\\baz"); -}); - -it("path.resolve", () => { - const failures = []; - const slashRE = /\//g; - const backslashRE = /\\/g; - - const resolveTests = [ - // [ - // path.win32.resolve, - // // Arguments result - // [ - // [["c:/blah\\blah", "d:/games", "c:../a"], "c:\\blah\\a"], - // [["c:/ignore", "d:\\a/b\\c/d", "\\e.exe"], "d:\\e.exe"], - // [["c:/ignore", "c:/some/file"], "c:\\some\\file"], - // [["d:/ignore", "d:some/dir//"], "d:\\ignore\\some\\dir"], - // [["."], process.cwd()], - // [["//server/share", "..", "relative\\"], "\\\\server\\share\\relative"], - // [["c:/", "//"], "c:\\"], - // [["c:/", "//dir"], "c:\\dir"], - // [["c:/", "//server/share"], "\\\\server\\share\\"], - // [["c:/", "//server//share"], "\\\\server\\share\\"], - // [["c:/", "///some//dir"], "c:\\some\\dir"], - // [ - // ["C:\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js"], - // "C:\\foo\\tmp.3\\cycles\\root.js", - // ], - // ], - // ], - [ - path.posix.resolve, - // Arguments result - [ - [["/var/lib", "../", "file/"], "/var/file"], - [["/var/lib", "/../", "file/"], "/file"], - [["a/b/c/", "../../.."], process.cwd()], - [["."], process.cwd()], - [["/some/dir", ".", "/absolute/"], "/absolute"], - [["/foo/tmp.3/", "../tmp.3/cycles/root.js"], "/foo/tmp.3/cycles/root.js"], - ], - ], - ]; - const isWindows = false; - resolveTests.forEach(([resolve, tests]) => { - tests.forEach(([test, expected]) => { - const actual = resolve.apply(null, test); - let actualAlt; - const os = resolve === path.win32.resolve ? "win32" : "posix"; - if (resolve === path.win32.resolve && !isWindows) actualAlt = actual.replace(backslashRE, "/"); - else if (resolve !== path.win32.resolve && isWindows) actualAlt = actual.replace(slashRE, "\\"); - - const message = `path.${os}.resolve(${test.map(JSON.stringify).join(",")})\n expect=${JSON.stringify( - expected, - )}\n actual=${JSON.stringify(actual)}`; - if (actual !== expected && actualAlt !== expected) failures.push(message); - }); - }); - strictEqual(failures.length, 0, failures.join("\n")); -}); diff --git a/test/bun.js/peek.test.ts b/test/bun.js/peek.test.ts deleted file mode 100644 index 421c306d8..000000000 --- a/test/bun.js/peek.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { peek } from "bun"; -import { expect, test } from "bun:test"; - -test("peek", () => { - const promise = Promise.resolve(true); - - // no await necessary! - expect(peek(promise)).toBe(true); - - // if we peek again, it returns the same value - const again = peek(promise); - expect(again).toBe(true); - - // if we peek a non-promise, it returns the value - const value = peek(42); - expect(value).toBe(42); - - // if we peek a pending promise, it returns the promise again - const pending = new Promise(() => {}); - expect(peek(pending)).toBe(pending); - - // If we peek a rejected promise, it: - // - returns the error - // - does not mark the promise as handled - const rejected = Promise.reject(new Error("Succesfully tested promise rejection")); - const peeked = peek(rejected); - expect(peeked instanceof Error).toBe(true); - expect((peeked as Error).message).toBe("Succesfully tested promise rejection"); - rejected.catch(() => {}); -}); - -test("peek.status", () => { - const promise = Promise.resolve(true); - expect(peek.status(promise)).toBe("fulfilled"); - - const pending = new Promise(() => {}); - expect(peek.status(pending)).toBe("pending"); - - const rejected = Promise.reject(new Error("oh nooo")); - expect(peek.status(rejected)).toBe("rejected"); - rejected.catch(() => {}); -}); diff --git a/test/bun.js/performance.test.js b/test/bun.js/performance.test.js deleted file mode 100644 index dd50c4dc6..000000000 --- a/test/bun.js/performance.test.js +++ /dev/null @@ -1,22 +0,0 @@ -import { expect, it } from "bun:test"; - -it("performance.now() should be monotonic", () => { - const first = performance.now(); - const second = performance.now(); - const third = performance.now(); - const fourth = performance.now(); - const fifth = performance.now(); - const sixth = performance.now(); - expect(first < second).toBe(true); - expect(second < third).toBe(true); - expect(third < fourth).toBe(true); - expect(fourth < fifth).toBe(true); - expect(fifth < sixth).toBe(true); - expect(Bun.nanoseconds() > 0).toBe(true); - expect(Bun.nanoseconds() > sixth).toBe(true); - expect(typeof Bun.nanoseconds() === "number").toBe(true); -}); - -it("performance.timeOrigin + performance.now() should be similar to Date.now()", () => { - expect(Math.abs(performance.timeOrigin + performance.now() - Date.now()) < 1000).toBe(true); -}); diff --git a/test/bun.js/plugins.d.ts b/test/bun.js/plugins.d.ts deleted file mode 100644 index aebfd952b..000000000 --- a/test/bun.js/plugins.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -declare var objectModuleResult: any; -declare var laterCode: any; - -declare module "beep:*"; -declare module "async:*"; -declare module "asyncret:*"; -declare module "asyncfail:*"; -declare module "async-obj:*"; -declare module "obj:*"; -declare module "delay:*"; -declare module "./*.svelte"; diff --git a/test/bun.js/plugins.test.ts b/test/bun.js/plugins.test.ts deleted file mode 100644 index e533cef06..000000000 --- a/test/bun.js/plugins.test.ts +++ /dev/null @@ -1,311 +0,0 @@ -/// -import { plugin } from "bun"; -import { describe, expect, it } from "bun:test"; -import { resolve } from "path"; - -plugin({ - name: "boop beep beep", - setup(builder) { - builder.onResolve({ filter: /boop/, namespace: "beep" }, () => ({ - path: "boop", - namespace: "beep", - })); - - builder.onLoad({ filter: /boop/, namespace: "beep" }, () => ({ - contents: `export default 42;`, - loader: "js", - })); - }, -}); - -plugin({ - name: "an object module", - setup(builder) { - globalThis.objectModuleResult ||= { - hello: "world", - }; - builder.onResolve({ filter: /.*/, namespace: "obj" }, ({ path }) => ({ - path, - namespace: "obj", - })); - - builder.onLoad({ filter: /.*/, namespace: "obj" }, () => ({ - exports: globalThis.objectModuleResult, - loader: "object", - })); - }, -}); - -plugin({ - name: "failing loader", - setup(builder) { - globalThis.failingObject ||= {}; - builder.onResolve({ filter: /.*/, namespace: "fail" }, ({ path }) => ({ - path, - namespace: "fail", - })); - builder.onLoad({ filter: /.*/, namespace: "fail" }, () => globalThis.failingObject); - }, -}); - -plugin({ - name: "delayed loader", - setup(builder) { - globalThis.laterCode = ""; - - builder.onResolve({ filter: /.*/, namespace: "delay" }, ({ path }) => ({ - namespace: "delay", - path, - })); - - builder.onLoad({ filter: /.*/, namespace: "delay" }, ({ path }) => ({ - contents: (globalThis.laterCode ||= ""), - loader: "js", - })); - }, -}); - -plugin({ - name: "async onLoad", - setup(builder) { - globalThis.asyncOnLoad = ""; - - builder.onResolve({ filter: /.*/, namespace: "async" }, ({ path }) => ({ - namespace: "async", - path, - })); - - builder.onLoad({ filter: /.*/, namespace: "async" }, async ({ path }) => { - await Promise.resolve(1); - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve({ - contents: (globalThis.asyncOnLoad ||= ""), - loader: "js", - }); - }, 1); - }); - }); - - builder.onResolve({ filter: /.*/, namespace: "async-obj" }, ({ path }) => ({ - namespace: "async-obj", - path, - })); - globalThis.asyncObject = {}; - builder.onLoad({ filter: /.*/, namespace: "async-obj" }, async ({ path }) => { - await Promise.resolve(1); - return new Promise((resolve, reject) => { - setTimeout(() => { - resolve({ - exports: (globalThis.asyncObject ||= {}), - loader: "object", - }); - }, 1); - }); - }); - - builder.onResolve({ filter: /.*/, namespace: "asyncfail" }, ({ path }) => ({ - namespace: "asyncfail", - path, - })); - - globalThis.asyncfail = false; - builder.onLoad({ filter: /.*/, namespace: "asyncfail" }, async ({ path }) => { - await Promise.resolve(1); - await 1; - throw globalThis.asyncfail; - }); - - builder.onResolve({ filter: /.*/, namespace: "asyncret" }, ({ path }) => ({ - namespace: "asyncret", - path, - })); - - globalThis.asyncret = 123; - builder.onLoad({ filter: /.*/, namespace: "asyncret" }, async ({ path }) => { - await 100; - await Promise.resolve(10); - return await globalThis.asyncret; - }); - }, -}); - -// This is to test that it works when imported from a separate file -import "bun-loader-svelte"; - -describe("require", () => { - it("SSRs `

Hello world!

` with Svelte", () => { - const { default: App } = require("./hello.svelte"); - const { html } = App.render(); - - expect(html).toBe("

Hello world!

"); - }); - - it("beep:boop returns 42", () => { - const result = require("beep:boop"); - expect(result.default).toBe(42); - }); - - it("object module works", () => { - const result = require("obj:boop"); - expect(result.hello).toBe(objectModuleResult.hello); - objectModuleResult.there = true; - const result2 = require("obj:boop2"); - expect(result.there).toBe(undefined); - expect(result2.there).toBe(objectModuleResult.there); - expect(result2.there).toBe(true); - }); -}); - -describe("dynamic import", () => { - it("SSRs `

Hello world!

` with Svelte", async () => { - const { default: App }: any = await import("./hello.svelte"); - - const { html } = App.render(); - - expect(html).toBe("

Hello world!

"); - }); - - it("beep:boop returns 42", async () => { - const result = await import("beep:boop"); - expect(result.default).toBe(42); - }); - - it("async:onLoad returns 42", async () => { - globalThis.asyncOnLoad = "export default 42;"; - const result = await import("async:hello42"); - expect(result.default).toBe(42); - }); - - it("async object loader returns 42", async () => { - globalThis.asyncObject = { foo: 42, default: 43 }; - const result = await import("async-obj:hello42"); - expect(result.foo).toBe(42); - expect(result.default).toBe(43); - }); -}); - -describe("import statement", () => { - it("SSRs `

Hello world!

` with Svelte", async () => { - laterCode = ` -import Hello from "${resolve(import.meta.dir, "hello2.svelte")}"; -export default Hello; -`; - const { default: SvelteApp } = await import("delay:hello2.svelte"); - const { html } = SvelteApp.render(); - - expect(html).toBe("

Hello world!

"); - }); -}); - -describe("errors", () => { - it("valid loaders work", () => { - const validLoaders = ["js", "jsx", "ts", "tsx"]; - const inputs = ["export default 'hi';", "export default 'hi';", "export default 'hi';", "export default 'hi';"]; - for (let i = 0; i < validLoaders.length; i++) { - const loader = validLoaders[i]; - const input = inputs[i]; - globalThis.failingObject = { contents: input, loader }; - expect(require(`fail:my-file-${loader}`).default).toBe("hi"); - } - }); - - it("invalid loaders throw", () => { - const invalidLoaders = ["blah", "blah2", "blah3", "blah4"]; - const inputs = ["body { background: red; }", "

hi

", '{"hi": "there"}', "hi"]; - for (let i = 0; i < invalidLoaders.length; i++) { - const loader = invalidLoaders[i]; - const input = inputs[i]; - globalThis.failingObject = { contents: input, loader }; - try { - require(`fail:my-file-${loader}`); - throw -1; - } catch (e: any) { - if (e === -1) { - throw new Error("Expected error"); - } - expect(e.message.length > 0).toBe(true); - } - } - }); - - it("transpiler errors work", () => { - const invalidLoaders = ["ts"]; - const inputs = ["const x: string = -NaNAn../!!;"]; - for (let i = 0; i < invalidLoaders.length; i++) { - const loader = invalidLoaders[i]; - const input = inputs[i]; - globalThis.failingObject = { contents: input, loader }; - try { - require(`fail:my-file-${loader}-3`); - throw -1; - } catch (e: any) { - if (e === -1) { - throw new Error("Expected error"); - } - expect(e.message.length > 0).toBe(true); - } - } - }); - - it("invalid async return value", async () => { - try { - globalThis.asyncret = { wat: true }; - await import("asyncret:my-file"); - throw -1; - } catch (e: any) { - if (e === -1) { - throw new Error("Expected error"); - } - - expect(e.message.length > 0).toBe(true); - } - }); - - it("async errors work", async () => { - try { - globalThis.asyncfail = new Error("async error"); - await import("asyncfail:my-file"); - throw -1; - } catch (e: any) { - if (e === -1) { - throw new Error("Expected error"); - } - expect(e.message.length > 0).toBe(true); - } - }); - - it("invalid onLoad objects throw", () => { - const invalidOnLoadObjects = [ - {}, - { contents: -1 }, - { contents: "", loader: -1 }, - { contents: "", loader: "klz", resolveDir: -1 }, - ]; - for (let i = 0; i < invalidOnLoadObjects.length; i++) { - globalThis.failingObject = invalidOnLoadObjects[i]; - try { - require(`fail:my-file-${i}-2`); - throw -1; - } catch (e: any) { - if (e === -1) { - throw new Error("Expected error"); - } - expect(e.message.length > 0).toBe(true); - } - } - }); - - it("async transpiler errors work", async () => { - try { - globalThis.asyncOnLoad = `const x: string = -NaNAn../!!;`; - await import("async:fail"); - throw -1; - } catch (e: any) { - if (e === -1) { - throw new Error("Expected error"); - } - expect(e.message.length > 0).toBe(true); - } - }); -}); diff --git a/test/bun.js/preload-test.test.js b/test/bun.js/preload-test.test.js deleted file mode 100644 index 76603b3a0..000000000 --- a/test/bun.js/preload-test.test.js +++ /dev/null @@ -1,227 +0,0 @@ -import { spawnSync } from "bun"; -import { describe, expect, test } from "bun:test"; -import { mkdirSync, realpathSync } from "fs"; -import { tmpdir } from "os"; -import { join } from "path"; -import { bunEnv } from "./bunEnv"; -import { bunExe } from "./bunExe"; -const preloadModule = ` -import {plugin} from 'bun'; - -plugin({ - setup(build) { - build.onResolve({ filter: /.*\.txt$/, }, async (args) => { - return { - path: args.path, - namespace: 'boop' - } - }); - build.onResolve({ namespace: "boop", filter: /.*/ }, async (args) => { - return { - path: args.path, - namespace: 'boop' - } - }); - build.onLoad({ namespace: "boop", filter: /.*/ }, async (args) => { - return { - contents: '"hello world"', - loader: 'json' - } - }); - } -}); - `; - -const mainModule = ` - import hey from './hey.txt'; - - if (hey !== 'hello world') { - throw new Error('preload test failed'); - } - - console.log('Test passed'); - process.exit(0); -`; - -const bunfig = `preload = ["./preload.js"]`; - -describe("preload", () => { - test("works", async () => { - const preloadDir = join(realpathSync(tmpdir()), "bun-preload-test"); - mkdirSync(preloadDir, { recursive: true }); - const preloadPath = join(preloadDir, "preload.js"); - const mainPath = join(preloadDir, "main.js"); - const bunfigPath = join(preloadDir, "bunfig.toml"); - await Bun.write(preloadPath, preloadModule); - await Bun.write(mainPath, mainModule); - await Bun.write(bunfigPath, bunfig); - - const cmds = [ - [bunExe(), "run", mainPath], - [bunExe(), mainPath], - ]; - - for (let cmd of cmds) { - const { stderr, exitCode, stdout } = spawnSync({ - cmd, - cwd: preloadDir, - stderr: "pipe", - stdout: "pipe", - env: bunEnv, - }); - - expect(exitCode).toBe(0); - expect(stderr.toString()).toBe(""); - expect(stdout.toString()).toContain("Test passed"); - } - }); - - test("works from CLI", async () => { - const preloadDir = join(realpathSync(tmpdir()), "bun-preload-test4"); - mkdirSync(preloadDir, { recursive: true }); - const preloadPath = join(preloadDir, "preload.js"); - const mainPath = join(preloadDir, "main.js"); - await Bun.write(preloadPath, preloadModule); - await Bun.write(mainPath, mainModule); - - const cmds = [ - [bunExe(), "-r=" + preloadPath, "run", mainPath], - [bunExe(), "-r=" + preloadPath, mainPath], - ]; - - for (let cmd of cmds) { - const { stderr, exitCode, stdout } = spawnSync({ - cmd, - cwd: preloadDir, - stderr: "pipe", - stdout: "pipe", - env: bunEnv, - }); - - expect(exitCode).toBe(0); - expect(stderr.toString()).toBe(""); - expect(stdout.toString()).toContain("Test passed"); - } - }); - - describe("as entry point", () => { - const preloadModule = ` -import {plugin} from 'bun'; - -plugin({ - setup(build) { - build.onResolve({ filter: /.*\.txt$/, }, async (args) => { - return { - path: args.path, - namespace: 'boop' - } - }); - build.onResolve({ namespace: "boop", filter: /.*/ }, async (args) => { - return { - path: args.path, - namespace: 'boop' - } - }); - build.onLoad({ namespace: "boop", filter: /.*/ }, async (args) => { - return { - contents: 'console.log("Test passed")', - loader: 'js' - } - }); - } -}); - `; - - test("works from CLI", async () => { - const preloadDir = join(realpathSync(tmpdir()), "bun-preload-test6"); - mkdirSync(preloadDir, { recursive: true }); - const preloadPath = join(preloadDir, "preload.js"); - const mainPath = join(preloadDir, "boop.txt"); - await Bun.write(preloadPath, preloadModule); - await Bun.write(mainPath, "beep"); - - const cmds = [ - [bunExe(), "-r=" + preloadPath, "run", mainPath], - [bunExe(), "-r=" + preloadPath, mainPath], - ]; - - for (let cmd of cmds) { - const { stderr, exitCode, stdout } = spawnSync({ - cmd, - cwd: preloadDir, - stderr: "pipe", - stdout: "pipe", - env: bunEnv, - }); - - expect(stderr.toString()).toBe(""); - expect(stdout.toString()).toContain("Test passed"); - expect(exitCode).toBe(0); - } - }); - }); - - test("throws an error when preloaded module fails to execute", async () => { - const preloadModule = "throw new Error('preload test failed');"; - - const preloadDir = join(realpathSync(tmpdir()), "bun-preload-test3"); - mkdirSync(preloadDir, { recursive: true }); - const preloadPath = join(preloadDir, "preload.js"); - const mainPath = join(preloadDir, "main.js"); - const bunfigPath = join(preloadDir, "bunfig.toml"); - await Bun.write(preloadPath, preloadModule); - await Bun.write(mainPath, mainModule); - await Bun.write(bunfigPath, bunfig); - - const cmds = [ - [bunExe(), "run", mainPath], - [bunExe(), mainPath], - ]; - - for (let cmd of cmds) { - const { stderr, exitCode, stdout } = spawnSync({ - cmd, - cwd: preloadDir, - stderr: "pipe", - stdout: "pipe", - env: bunEnv, - }); - - expect(stderr.toString()).toContain("preload test failed"); - expect(stdout.toString()).toBe(""); - expect(exitCode).toBe(1); - } - }); - - test("throws an error when preloaded module not found", async () => { - const bunfig = `preload = ["./bad-file.js"]`; - - const preloadDir = join(realpathSync(tmpdir()), "bun-preload-test2"); - mkdirSync(preloadDir, { recursive: true }); - const preloadPath = join(preloadDir, "preload.js"); - const mainPath = join(preloadDir, "main.js"); - const bunfigPath = join(preloadDir, "bunfig.toml"); - await Bun.write(preloadPath, preloadModule); - await Bun.write(mainPath, mainModule); - await Bun.write(bunfigPath, bunfig); - - const cmds = [ - [bunExe(), "run", mainPath], - [bunExe(), mainPath], - ]; - - for (let cmd of cmds) { - const { stderr, exitCode, stdout } = spawnSync({ - cmd, - cwd: preloadDir, - stderr: "pipe", - stdout: "pipe", - env: bunEnv, - }); - - expect(stderr.toString()).toContain("preload not found "); - expect(stdout.toString()).toBe(""); - expect(exitCode).toBe(1); - } - }); -}); diff --git a/test/bun.js/print-process-args.js b/test/bun.js/print-process-args.js deleted file mode 100644 index 0ab238122..000000000 --- a/test/bun.js/print-process-args.js +++ /dev/null @@ -1,4 +0,0 @@ -var writer = Bun.stdout.writer(); -writer.write(JSON.stringify(process.argv)); -await writer.flush(true); -process.exit(0); diff --git a/test/bun.js/process-args.test.js b/test/bun.js/process-args.test.js deleted file mode 100644 index a2c79616f..000000000 --- a/test/bun.js/process-args.test.js +++ /dev/null @@ -1,40 +0,0 @@ -import { spawn } from "bun"; -import { test, expect } from "bun:test"; -import { bunExe } from "./bunExe"; - -test("args exclude run", async () => { - const arg0 = process.argv[0]; - const arg1 = import.meta.dir + "/print-process-args.js"; - const exe = bunExe(); - const { stdout: s1 } = spawn([exe, "print-process-args.js"], { - cwd: import.meta.dir, - env: { BUN_DEBUG_QUIET_LOGS: "1" }, - }); - const t1 = JSON.parse(await new Response(s1).text()); - expect(t1[0]).toBe(arg0); - expect(t1[1]).toBe(arg1); - const { stdout: s2 } = spawn([exe, "print-process-args.js", "arg1"], { - cwd: import.meta.dir, - env: { BUN_DEBUG_QUIET_LOGS: "1" }, - }); - const t2 = JSON.parse(await new Response(s2).text()); - expect(t2[0]).toBe(arg0); - expect(t2[1]).toBe(arg1); - expect(t2[2]).toBe("arg1"); - const { stdout: s3 } = spawn([exe, "run", "print-process-args.js"], { - cwd: import.meta.dir, - env: { BUN_DEBUG_QUIET_LOGS: "1" }, - }); - const t3 = JSON.parse(await new Response(s3).text()); - expect(t3[0]).toBe(arg0); - expect(t3[1]).toBe(arg1); - const { stdout: s4 } = spawn([exe, "run", "print-process-args.js", "arg1", "arg2"], { - cwd: import.meta.dir, - env: { BUN_DEBUG_QUIET_LOGS: "1" }, - }); - const t4 = JSON.parse(await new Response(s4).text()); - expect(t4[0]).toBe(arg0); - expect(t4[1]).toBe(arg1); - expect(t4[2]).toBe("arg1"); - expect(t4[3]).toBe("arg2"); -}); diff --git a/test/bun.js/process-nexttick.js b/test/bun.js/process-nexttick.js deleted file mode 100644 index c6b24ba14..000000000 --- a/test/bun.js/process-nexttick.js +++ /dev/null @@ -1,84 +0,0 @@ -// You can verify this test is correct by copy pasting this into a browser's console and checking it doesn't throw an error. -var run = 0; - -var queueMicrotask = process.nextTick; - -await new Promise((resolve, reject) => { - queueMicrotask(() => { - if (run++ != 0) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - queueMicrotask(() => { - if (run++ != 3) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - }); - }); - queueMicrotask(() => { - if (run++ != 1) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - queueMicrotask(() => { - if (run++ != 4) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - - queueMicrotask(() => { - if (run++ != 6) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - }); - }); - }); - queueMicrotask(() => { - if (run++ != 2) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - queueMicrotask(() => { - if (run++ != 5) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - - queueMicrotask(() => { - if (run++ != 7) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - resolve(true); - }); - }); - }); -}); - -{ - var passed = false; - try { - queueMicrotask(1234); - } catch (exception) { - passed = exception instanceof TypeError; - } - - if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is not a function"); -} - -{ - var passed = false; - try { - queueMicrotask(); - } catch (exception) { - passed = exception instanceof TypeError; - } - - if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is empty"); -} - -await new Promise((resolve, reject) => { - process.nextTick( - (first, second) => { - console.log(first, second); - if (first !== 12345 || second !== "hello") reject(new Error("process.nextTick called with wrong arguments")); - resolve(true); - }, - 12345, - "hello", - ); -}); diff --git a/test/bun.js/process-nexttick.test.js b/test/bun.js/process-nexttick.test.js deleted file mode 100644 index becf3c236..000000000 --- a/test/bun.js/process-nexttick.test.js +++ /dev/null @@ -1,99 +0,0 @@ -import { it } from "bun:test"; - -it("process.nextTick", async () => { - // You can verify this test is correct by copy pasting this into a browser's console and checking it doesn't throw an error. - var run = 0; - var queueMicrotask = process.nextTick; - - await new Promise((resolve, reject) => { - queueMicrotask(() => { - if (run++ != 0) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - queueMicrotask(() => { - if (run++ != 3) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - }); - }); - queueMicrotask(() => { - if (run++ != 1) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - queueMicrotask(() => { - if (run++ != 4) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - - queueMicrotask(() => { - if (run++ != 6) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - }); - }); - }); - queueMicrotask(() => { - if (run++ != 2) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - queueMicrotask(() => { - if (run++ != 5) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - - queueMicrotask(() => { - if (run++ != 7) { - reject(new Error("Microtask execution order is wrong: " + run)); - } - resolve(true); - }); - }); - }); - }); - - { - var passed = false; - try { - queueMicrotask(1234); - } catch (exception) { - passed = exception instanceof TypeError; - } - - if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is not a function"); - } - - { - var passed = false; - try { - queueMicrotask(); - } catch (exception) { - passed = exception instanceof TypeError; - } - - if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is empty"); - } -}); - -it("process.nextTick 2 args", async () => { - await new Promise((resolve, reject) => { - process.nextTick( - (first, second) => { - if (first !== 12345 || second !== "hello") reject(new Error("process.nextTick called with wrong arguments")); - resolve(true); - }, - 12345, - "hello", - ); - }); -}); - -it("process.nextTick 5 args", async () => { - await new Promise((resolve, reject) => { - var args = [12345, "hello", "hello", "hello", 5]; - process.nextTick((...receivedArgs) => { - if (!args.every((arg, index) => arg === receivedArgs[index])) - reject(new Error("process.nextTick called with wrong arguments")); - resolve(true); - }, ...args); - }); -}); diff --git a/test/bun.js/process-stdin-echo.js b/test/bun.js/process-stdin-echo.js deleted file mode 100644 index e265cc76d..000000000 --- a/test/bun.js/process-stdin-echo.js +++ /dev/null @@ -1,11 +0,0 @@ -process.stdin.setEncoding("utf8"); -process.stdin.on("data", data => { - process.stdout.write(data); -}); -process.stdin.once("end", () => { - process.stdout.write("ENDED"); -}); -if (process.argv[2] == "resume") { - process.stdout.write("RESUMED"); - process.stdin.resume(); -} diff --git a/test/bun.js/process-stdio.test.ts b/test/bun.js/process-stdio.test.ts deleted file mode 100644 index 6054eeeeb..000000000 --- a/test/bun.js/process-stdio.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { spawn, spawnSync } from "bun"; -import { describe, expect, it, test } from "bun:test"; -import { bunExe } from "bunExe"; -import { isatty } from "tty"; - -test("process.stdin", () => { - expect(process.stdin).toBeDefined(); - expect(process.stdout.isTTY).toBe(isatty(0)); - expect(process.stdin.on("close", function () {})).toBe(process.stdin); - expect(process.stdin.once("end", function () {})).toBe(process.stdin); -}); - -test("process.stdin - read", async () => { - const { stdin, stdout } = spawn({ - cmd: [bunExe(), import.meta.dir + "/process-stdin-echo.js"], - stdout: "pipe", - stdin: "pipe", - stderr: null, - env: { - ...process.env, - BUN_DEBUG_QUIET_LOGS: "1", - }, - }); - expect(stdin).toBeDefined(); - expect(stdout).toBeDefined(); - var lines = ["Get Emoji", "— All Emojis to ✂️ Copy and 📋 Paste", "👌", ""]; - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - setTimeout(() => { - if (line) { - stdin?.write(line + "\n"); - stdin?.flush(); - } else { - stdin?.end(); - } - }, i * 200); - } - var text = await new Response(stdout).text(); - expect(text).toBe(lines.join("\n") + "ENDED"); -}); - -test("process.stdin - resume", async () => { - const { stdin, stdout } = spawn({ - cmd: [bunExe(), import.meta.dir + "/process-stdin-echo.js", "resume"], - stdout: "pipe", - stdin: "pipe", - stderr: null, - env: { - ...process.env, - BUN_DEBUG_QUIET_LOGS: "1", - }, - }); - expect(stdin).toBeDefined(); - expect(stdout).toBeDefined(); - var lines = ["Get Emoji", "— All Emojis to ✂️ Copy and 📋 Paste", "👌", ""]; - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - setTimeout(() => { - if (line) { - stdin?.write(line + "\n"); - stdin?.flush(); - } else { - stdin?.end(); - } - }, i * 200); - } - var text = await new Response(stdout).text(); - expect(text).toBe("RESUMED" + lines.join("\n") + "ENDED"); -}); - -test("process.stdout", () => { - expect(process.stdout).toBeDefined(); - expect(process.stdout.isTTY).toBe(isatty(1)); -}); - -test("process.stderr", () => { - expect(process.stderr).toBeDefined(); - expect(process.stderr.isTTY).toBe(isatty(2)); -}); - -test("process.stdout - write", () => { - const { stdout } = spawnSync({ - cmd: [bunExe(), import.meta.dir + "/stdio-test-instance.js"], - stdout: "pipe", - stdin: null, - stderr: null, - env: { - ...process.env, - BUN_DEBUG_QUIET_LOGS: "1", - }, - }); - - expect(stdout?.toString()).toBe(`hello worldhello again|😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌`); -}); - -test("process.stdout - write a lot (string)", () => { - const { stdout } = spawnSync({ - cmd: [bunExe(), import.meta.dir + "/stdio-test-instance-a-lot.js"], - stdout: "pipe", - stdin: null, - stderr: null, - env: { - ...process.env, - BUN_DEBUG_QUIET_LOGS: "1", - TEST_STDIO_STRING: "1", - }, - }); - - expect(stdout?.toString()).toBe( - `hello worldhello again|😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌`.repeat(9999), - ); -}); - -test("process.stdout - write a lot (bytes)", () => { - const { stdout } = spawnSync({ - cmd: [bunExe(), import.meta.dir + "/stdio-test-instance-a-lot.js"], - stdout: "pipe", - stdin: null, - stderr: null, - env: { - ...process.env, - BUN_DEBUG_QUIET_LOGS: "1", - }, - }); - expect(stdout?.toString()).toBe( - `hello worldhello again|😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌`.repeat(9999), - ); -}); diff --git a/test/bun.js/process.test.js b/test/bun.js/process.test.js deleted file mode 100644 index 8cc3e6b87..000000000 --- a/test/bun.js/process.test.js +++ /dev/null @@ -1,184 +0,0 @@ -import { resolveSync, which } from "bun"; -import { describe, expect, it } from "bun:test"; -import { readFileSync, realpathSync } from "fs"; -import { basename } from "path"; - -it("process", () => { - // this property isn't implemented yet but it should at least return a string - const isNode = !process.isBun; - - if (!isNode && process.title !== "bun") throw new Error("process.title is not 'bun'"); - - if (typeof process.env.USER !== "string") throw new Error("process.env is not an object"); - - if (process.env.USER.length === 0) throw new Error("process.env is missing a USER property"); - - if (process.platform !== "darwin" && process.platform !== "linux") throw new Error("process.platform is invalid"); - - if (isNode) throw new Error("process.isBun is invalid"); - - // partially to test it doesn't crash due to various strange types - process.env.BACON = "yummy"; - if (process.env.BACON !== "yummy") { - throw new Error("process.env is not writable"); - } - - delete process.env.BACON; - if (typeof process.env.BACON !== "undefined") { - throw new Error("process.env is not deletable"); - } - - process.env.BACON = "yummy"; - if (process.env.BACON !== "yummy") { - throw new Error("process.env is not re-writable"); - } - if (!JSON.stringify(process.env)) { - throw new Error("process.env is not serializable"); - } - - if (typeof JSON.parse(JSON.stringify(process.env)).toJSON !== "undefined") { - throw new Error("process.env should call toJSON to hide its internal state"); - } - - var { env, ...proces } = process; - console.log(proces); - - console.log("CWD", process.cwd()); - console.log("SET CWD", process.chdir("../")); - console.log("CWD", process.cwd()); -}); - -it("process.hrtime()", () => { - const start = process.hrtime(); - const end = process.hrtime(start); - const end2 = process.hrtime(); - expect(end[0]).toBe(0); - expect(end2[1] > start[1]).toBe(true); -}); - -it("process.hrtime.bigint()", () => { - const start = process.hrtime.bigint(); - const end = process.hrtime.bigint(); - expect(end > start).toBe(true); -}); - -it("process.release", () => { - expect(process.release.name).toBe("bun"); - expect(process.release.sourceUrl).toBe( - `https://github.com/oven-sh/bun/release/bun-v${process.versions.bun}/bun-${process.platform}-${ - { arm64: "aarch64", x64: "x64" }[process.arch] || process.arch - }.zip`, - ); -}); - -it("process.env", () => { - process.env["LOL SMILE UTF16 😂"] = "😂"; - expect(process.env["LOL SMILE UTF16 😂"]).toBe("😂"); - delete process.env["LOL SMILE UTF16 😂"]; - expect(process.env["LOL SMILE UTF16 😂"]).toBe(undefined); - - process.env["LOL SMILE latin1 "] = ""; - expect(process.env["LOL SMILE latin1 "]).toBe(""); - delete process.env["LOL SMILE latin1 "]; - expect(process.env["LOL SMILE latin1 "]).toBe(undefined); -}); - -it("process.env is spreadable and editable", () => { - process.env["LOL SMILE UTF16 😂"] = "😂"; - const { "LOL SMILE UTF16 😂": lol, ...rest } = process.env; - expect(lol).toBe("😂"); - delete process.env["LOL SMILE UTF16 😂"]; - expect(rest).toEqual(process.env); - const orig = (getter => process.env[getter])("USER"); - expect(process.env).toEqual(process.env); - eval(`globalThis.process.env.USER = 'bun';`); - expect(eval(`globalThis.process.env.USER`)).toBe("bun"); - expect(eval(`globalThis.process.env.USER = "${orig}"`)).toBe(orig); -}); - -it("process.version starts with v", () => { - expect(process.version.startsWith("v")).toBeTruthy(); -}); - -it("process.argv0", () => { - expect(basename(process.argv0)).toBe(basename(process.argv[0])); -}); - -it("process.execPath", () => { - expect(process.execPath).not.toBe(basename(process.argv0)); - expect(which(process.execPath)).not.toBeNull(); -}); - -it("process.uptime()", () => { - expect(process.uptime()).toBeGreaterThan(0); - expect(Math.floor(process.uptime())).toBe(Math.floor(performance.now() / 1000)); -}); - -it("process.umask()", () => { - const orig = process.umask(777); - expect(orig).toBeGreaterThan(0); - expect(process.umask(orig)).toBe(777); -}); - -it("process.versions", () => { - // Generate a list of all the versions in the versions object - // example: - // pub const boringssl = "b275c5ce1c88bc06f5a967026d3c0ce1df2be815"; - // pub const libarchive = "dc321febde83dd0f31158e1be61a7aedda65e7a2"; - // pub const mimalloc = "3c7079967a269027e438a2aac83197076d9fe09d"; - // pub const picohttpparser = "066d2b1e9ab820703db0837a7255d92d30f0c9f5"; - // pub const uws = "70b1b9fc1341e8b791b42c5447f90505c2abe156"; - // pub const webkit = "60d11703a533fd694cd1d6ddda04813eecb5d69f"; - // pub const zlib = "885674026394870b7e7a05b7bf1ec5eb7bd8a9c0"; - // pub const tinycc = "2d3ad9e0d32194ad7fd867b66ebe218dcc8cb5cd"; - // pub const lolhtml = "2eed349dcdfa4ff5c19fe7c6e501cfd687601033"; - // pub const c_ares = "0e7a5dee0fbb04080750cf6eabbe89d8bae87faa"; - // pub const usockets = "fafc241e8664243fc0c51d69684d5d02b9805134"; - const versions = Object.fromEntries( - readFileSync(import.meta.dir + "/../../src/generated_versions_list.zig", "utf8") - .split("\n") - .filter(line => line.startsWith("pub const") && !line.includes("zig") && line.includes(' = "')) - .map(line => line.split(" = ")) - .map(([name, hash]) => [name.slice(9).trim(), hash.slice(1, -2)]), - ); - versions.uwebsockets = versions.uws; - delete versions.uws; - versions["ares"] = versions.c_ares; - delete versions.c_ares; - - for (const name in versions) { - expect(process.versions).toHaveProperty(name); - expect(process.versions[name]).toBe(versions[name]); - } -}); - -it("process.config", () => { - expect(process.config).toEqual({ - variables: { - v8_enable_i8n_support: 1, - }, - target_defaults: {}, - }); -}); - -it("process.emitWarning", () => { - process.emitWarning("-- Testing process.emitWarning --"); - var called = 0; - process.on("warning", err => { - called++; - expect(err.message).toBe("-- Testing process.on('warning') --"); - }); - process.emitWarning("-- Testing process.on('warning') --"); - expect(called).toBe(1); - expect(process.off("warning")).toBe(process); - process.emitWarning("-- Testing process.on('warning') --"); - expect(called).toBe(1); -}); - -it("process.execArgv", () => { - expect(process.execArgv instanceof Array).toBe(true); -}); - -it("process.binding", () => { - expect(() => process.binding("buffer")).toThrow(); -}); diff --git a/test/bun.js/proxy.test.js b/test/bun.js/proxy.test.js deleted file mode 100644 index c903efab3..000000000 --- a/test/bun.js/proxy.test.js +++ /dev/null @@ -1,69 +0,0 @@ -import { afterAll, beforeAll, describe, expect, it } from "bun:test"; -import { gc } from "./gc"; - -let proxy, server; - -// TODO: Proxy with TLS requests - -beforeAll(() => { - proxy = Bun.serve({ - async fetch(request) { - // if is not an proxy connection just drop it - if (!request.headers.has("proxy-connection")) { - return new Response("Bad Request", { status: 400 }); - } - - // simple http proxy - if (request.url.startsWith("http://")) { - return await fetch(request.url, { - method: request.method, - body: await request.text(), - }); - } - - // no TLS support here - return new Response("Bad Request", { status: 400 }); - }, - port: 54312, - }); - server = Bun.serve({ - async fetch(request) { - if (request.method === "POST") { - const text = await request.text(); - return new Response(text, { status: 200 }); - } - return new Response("Hello, World", { status: 200 }); - }, - port: 54322, - }); -}); - -afterAll(() => { - server.stop(); - proxy.stop(); -}); - -describe("proxy", () => { - const requests = [ - [new Request("http://localhost:54322"), "fetch() GET with non-TLS Proxy", "http://localhost:54312"], - [ - new Request("http://localhost:54322", { - method: "POST", - body: "Hello, World", - }), - "fetch() POST with non-TLS Proxy", - "http://localhost:54312", - ], - ]; - for (let [request, name, proxy] of requests) { - gc(); - it(name, async () => { - gc(); - const response = await fetch(request, { verbose: true, proxy }); - gc(); - const text = await response.text(); - gc(); - expect(text).toBe("Hello, World"); - }); - } -}); diff --git a/test/bun.js/react-dom-server.bun.cjs b/test/bun.js/react-dom-server.bun.cjs deleted file mode 100644 index f67e54a8c..000000000 --- a/test/bun.js/react-dom-server.bun.cjs +++ /dev/null @@ -1,2670 +0,0 @@ -/** - * @license React - * react-dom-server.bun.production.min.js - * - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -"use strict"; -var ba = require("react"), - ca = require("react-dom"); -function l(a, b) { - 0 !== b.length && a.write(b); -} -function da(a, b) { - "function" === typeof a.error ? a.error(b) : a.close(); -} -var r = Object.prototype.hasOwnProperty, - ea = - /^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/, - fa = {}, - ha = {}; -function ia(a) { - if (r.call(ha, a)) return !0; - if (r.call(fa, a)) return !1; - if (ea.test(a)) return (ha[a] = !0); - fa[a] = !0; - return !1; -} -function t(a, b, c, d, e, f, g) { - this.acceptsBooleans = 2 === b || 3 === b || 4 === b; - this.attributeName = d; - this.attributeNamespace = e; - this.mustUseProperty = c; - this.propertyName = a; - this.type = b; - this.sanitizeURL = f; - this.removeEmptyString = g; -} -var v = {}, - ja = - "children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split( - " ", - ); -ja.push("innerText", "textContent"); -ja.forEach(function (a) { - v[a] = new t(a, 0, !1, a, null, !1, !1); -}); -[ - ["acceptCharset", "accept-charset"], - ["className", "class"], - ["htmlFor", "for"], - ["httpEquiv", "http-equiv"], -].forEach(function (a) { - var b = a[0]; - v[b] = new t(b, 1, !1, a[1], null, !1, !1); -}); -["contentEditable", "draggable", "spellCheck", "value"].forEach(function (a) { - v[a] = new t(a, 2, !1, a.toLowerCase(), null, !1, !1); -}); -["autoReverse", "externalResourcesRequired", "focusable", "preserveAlpha"].forEach(function (a) { - v[a] = new t(a, 2, !1, a, null, !1, !1); -}); -"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope" - .split(" ") - .forEach(function (a) { - v[a] = new t(a, 3, !1, a.toLowerCase(), null, !1, !1); - }); -["checked", "multiple", "muted", "selected"].forEach(function (a) { - v[a] = new t(a, 3, !0, a, null, !1, !1); -}); -["capture", "download"].forEach(function (a) { - v[a] = new t(a, 4, !1, a, null, !1, !1); -}); -["cols", "rows", "size", "span"].forEach(function (a) { - v[a] = new t(a, 6, !1, a, null, !1, !1); -}); -["rowSpan", "start"].forEach(function (a) { - v[a] = new t(a, 5, !1, a.toLowerCase(), null, !1, !1); -}); -var ka = /[\-:]([a-z])/g; -function la(a) { - return a[1].toUpperCase(); -} -"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height" - .split(" ") - .forEach(function (a) { - var b = a.replace(ka, la); - v[b] = new t(b, 1, !1, a, null, !1, !1); - }); -"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function (a) { - var b = a.replace(ka, la); - v[b] = new t(b, 1, !1, a, "http://www.w3.org/1999/xlink", !1, !1); -}); -["xml:base", "xml:lang", "xml:space"].forEach(function (a) { - var b = a.replace(ka, la); - v[b] = new t(b, 1, !1, a, "http://www.w3.org/XML/1998/namespace", !1, !1); -}); -["tabIndex", "crossOrigin"].forEach(function (a) { - v[a] = new t(a, 1, !1, a.toLowerCase(), null, !1, !1); -}); -v.xlinkHref = new t("xlinkHref", 1, !1, "xlink:href", "http://www.w3.org/1999/xlink", !0, !1); -["src", "href", "action", "formAction"].forEach(function (a) { - v[a] = new t(a, 1, !1, a.toLowerCase(), null, !0, !0); -}); -var ma = { - animationIterationCount: !0, - aspectRatio: !0, - borderImageOutset: !0, - borderImageSlice: !0, - borderImageWidth: !0, - boxFlex: !0, - boxFlexGroup: !0, - boxOrdinalGroup: !0, - columnCount: !0, - columns: !0, - flex: !0, - flexGrow: !0, - flexPositive: !0, - flexShrink: !0, - flexNegative: !0, - flexOrder: !0, - gridArea: !0, - gridRow: !0, - gridRowEnd: !0, - gridRowSpan: !0, - gridRowStart: !0, - gridColumn: !0, - gridColumnEnd: !0, - gridColumnSpan: !0, - gridColumnStart: !0, - fontWeight: !0, - lineClamp: !0, - lineHeight: !0, - opacity: !0, - order: !0, - orphans: !0, - tabSize: !0, - widows: !0, - zIndex: !0, - zoom: !0, - fillOpacity: !0, - floodOpacity: !0, - stopOpacity: !0, - strokeDasharray: !0, - strokeDashoffset: !0, - strokeMiterlimit: !0, - strokeOpacity: !0, - strokeWidth: !0, - }, - na = ["Webkit", "ms", "Moz", "O"]; -Object.keys(ma).forEach(function (a) { - na.forEach(function (b) { - b = b + a.charAt(0).toUpperCase() + a.substring(1); - ma[b] = ma[a]; - }); -}); -var oa = /["'&<>]/; -function w(a) { - if ("boolean" === typeof a || "number" === typeof a) return "" + a; - a = "" + a; - var b = oa.exec(a); - if (b) { - var c = "", - d, - e = 0; - for (d = b.index; d < a.length; d++) { - switch (a.charCodeAt(d)) { - case 34: - b = """; - break; - case 38: - b = "&"; - break; - case 39: - b = "'"; - break; - case 60: - b = "<"; - break; - case 62: - b = ">"; - break; - default: - continue; - } - e !== d && (c += a.substring(e, d)); - e = d + 1; - c += b; - } - a = e !== d ? c + a.substring(e, d) : c; - } - return a; -} -var pa = /([A-Z])/g, - qa = /^ms-/, - ra = Array.isArray, - x = Object.assign, - y = null, - sa = [], - va = { preload: ta, preinit: ua }; -function ta(a, b) { - if (y) { - var c = y; - if ("string" === typeof a && a && "object" === typeof b && null !== b) { - var d = b.as, - e = c.preloadsMap.get(a); - e || - (e = z(c, a, d, { - href: a, - rel: "preload", - as: d, - crossOrigin: "font" === d ? "" : b.crossOrigin, - integrity: b.integrity, - })); - switch (d) { - case "font": - c.fontPreloads.add(e); - break; - case "style": - c.explicitStylePreloads.add(e); - break; - case "script": - c.explicitScriptPreloads.add(e); - } - } - } -} -function ua(a, b) { - if (y) { - var c = y; - if ("string" === typeof a && a && "object" === typeof b && null !== b) - switch (b.as) { - case "style": - var d = c.stylesMap.get(a); - d || - ((d = b.precedence || "default"), - (d = wa(c, a, d, { - rel: "stylesheet", - href: a, - "data-precedence": d, - crossOrigin: b.crossOrigin, - }))); - d.set.add(d); - c.explicitStylePreloads.add(d.hint); - break; - case "script": - (d = c.scriptsMap.get(a)), - d || - ((d = xa(c, a, { - src: a, - async: !0, - crossOrigin: b.crossOrigin, - integrity: b.integrity, - })), - c.scripts.add(d)); - } - } -} -function ya(a, b) { - return { - rel: "preload", - as: "style", - href: a, - crossOrigin: b.crossOrigin, - integrity: b.integrity, - media: b.media, - hrefLang: b.hrefLang, - referrerPolicy: b.referrerPolicy, - }; -} -function za(a, b) { - return { - rel: "preload", - as: "script", - href: a, - crossOrigin: b.crossOrigin, - integrity: b.integrity, - referrerPolicy: b.referrerPolicy, - }; -} -function z(a, b, c, d) { - c = { type: "preload", as: c, href: b, flushed: !1, props: d }; - a.preloadsMap.set(b, c); - return c; -} -function wa(a, b, c, d) { - var e = a.stylesMap, - f = a.preloadsMap, - g = a.precedences, - h = g.get(c); - h || ((h = new Set()), g.set(c, h)); - (f = f.get(b)) - ? ((a = f.props), - null == d.crossOrigin && (d.crossOrigin = a.crossOrigin), - null == d.referrerPolicy && (d.referrerPolicy = a.referrerPolicy), - null == d.title && (d.title = a.title)) - : ((f = ya(b, d)), (f = z(a, b, "style", f)), a.explicitStylePreloads.add(f)); - c = { - type: "style", - href: b, - precedence: c, - flushed: !1, - inShell: !1, - props: d, - hint: f, - set: h, - }; - e.set(b, c); - return c; -} -function xa(a, b, c) { - var d = a.scriptsMap, - e = a.preloadsMap.get(b); - e - ? ((a = e.props), - null == c.crossOrigin && (c.crossOrigin = a.crossOrigin), - null == c.referrerPolicy && (c.referrerPolicy = a.referrerPolicy), - null == c.integrity && (c.integrity = a.integrity)) - : ((e = za(b, c)), (e = z(a, b, "script", e)), a.explicitScriptPreloads.add(e)); - c = { type: "script", src: b, flushed: !1, props: c, hint: e }; - d.set(b, c); - return c; -} -function Aa(a, b) { - if (!y) throw Error('"currentResources" was expected to exist. This is a bug in React.'); - var c = y; - switch (a) { - case "title": - var d = b.children; - Array.isArray(d) && 1 === d.length && (d = d[0]); - if ("string" === typeof d || "number" === typeof d) { - var e = "title::" + d; - a = c.headsMap.get(e); - a || - ((b = x({}, b)), - (b.children = d), - (a = { type: "title", props: b, flushed: !1 }), - c.headsMap.set(e, a), - c.headResources.add(a)); - } - return !0; - case "meta": - if ("string" === typeof b.charSet) e = "charSet"; - else if ("string" === typeof b.content) - if (((a = "::" + b.content), "string" === typeof b.httpEquiv)) e = "httpEquiv::" + b.httpEquiv + a; - else if ("string" === typeof b.name) e = "name::" + b.name + a; - else if ("string" === typeof b.itemProp) e = "itemProp::" + b.itemProp + a; - else if ("string" === typeof b.property) { - var f = b.property; - e = "property::" + f + a; - d = f; - a = f.split(":").slice(0, -1).join(":"); - (a = c.structuredMetaKeys.get(a)) && (e = a.key + "::child::" + e); - } - e && - !c.headsMap.has(e) && - ((b = { type: "meta", key: e, props: x({}, b), flushed: !1 }), - c.headsMap.set(e, b), - "charSet" === e ? (c.charset = b) : (d && c.structuredMetaKeys.set(d, b), c.headResources.add(b))); - return !0; - case "base": - return ( - (e = b.target), - (d = b.href), - (e = - "base" + - ("string" === typeof d ? '[href="' + d + '"]' : ":not([href])") + - ("string" === typeof e ? '[target="' + e + '"]' : ":not([target])")), - c.headsMap.has(e) || - ((b = { type: "base", props: x({}, b), flushed: !1 }), c.headsMap.set(e, b), c.bases.add(b)), - !0 - ); - } - return !1; -} -function Ba(a) { - if (!y) throw Error('"currentResources" was expected to exist. This is a bug in React.'); - var b = y, - c = a.rel, - d = a.href; - if (!d || "string" !== typeof d || !c || "string" !== typeof c) return !1; - switch (c) { - case "stylesheet": - var e = a.onLoad, - f = a.onError; - c = a.precedence; - var g = a.disabled; - if ("string" !== typeof c || e || f || null != g) - return (c = b.preloadsMap.get(d)), c || ((c = z(b, d, "style", ya(d, a))), b.usedStylePreloads.add(c)), !1; - e = b.stylesMap.get(d); - e || - ((a = x({}, a)), - (a.href = d), - (a.rel = "stylesheet"), - (a["data-precedence"] = c), - delete a.precedence, - (e = wa(y, d, c, a)), - b.usedStylePreloads.add(e.hint)); - b.boundaryResources ? b.boundaryResources.add(e) : e.set.add(e); - return !0; - case "preload": - switch (((e = a.as), e)) { - case "script": - case "style": - case "font": - c = b.preloadsMap.get(d); - if (!c) - switch ( - ((a = x({}, a)), - (a.href = d), - (a.rel = "preload"), - (a.as = e), - "font" === e && (a.crossOrigin = ""), - (c = z(b, d, e, a)), - e) - ) { - case "script": - b.explicitScriptPreloads.add(c); - break; - case "style": - b.explicitStylePreloads.add(c); - break; - case "font": - b.fontPreloads.add(c); - } - return !0; - } - } - if (a.onLoad || a.onError) return !0; - d = - "rel:" + - c + - "::href:" + - d + - "::sizes:" + - ("string" === typeof a.sizes ? a.sizes : "") + - "::media:" + - ("string" === typeof a.media ? a.media : ""); - e = b.headsMap.get(d); - if (!e) - switch (((e = { type: "link", props: x({}, a), flushed: !1 }), b.headsMap.set(d, e), c)) { - case "preconnect": - case "dns-prefetch": - b.preconnects.add(e); - break; - default: - b.headResources.add(e); - } - return !0; -} -function Ca(a, b) { - var c = a.boundaryResources; - c && - (b.forEach(function (a) { - return c.add(a); - }), - b.clear()); -} -function Da(a, b) { - b.forEach(function (a) { - return a.set.add(a); - }); - b.clear(); -} -var Ea = ca.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Dispatcher, - Fa = /(<\/|<)(s)(cript)/gi; -function Ga(a, b, c, d) { - return "" + b + ("s" === c ? "\\u0073" : "\\u0053") + d; -} -function Ha(a, b, c, d, e) { - a = void 0 === a ? "" : a; - b = void 0 === b ? " + +

Hello {name}!

diff --git a/test/js/bun/plugin/hello2.svelte b/test/js/bun/plugin/hello2.svelte new file mode 100644 index 000000000..05dac4294 --- /dev/null +++ b/test/js/bun/plugin/hello2.svelte @@ -0,0 +1,5 @@ + + +

Hello {name}!

diff --git a/test/js/bun/plugin/plugins.d.ts b/test/js/bun/plugin/plugins.d.ts new file mode 100644 index 000000000..aebfd952b --- /dev/null +++ b/test/js/bun/plugin/plugins.d.ts @@ -0,0 +1,11 @@ +declare var objectModuleResult: any; +declare var laterCode: any; + +declare module "beep:*"; +declare module "async:*"; +declare module "asyncret:*"; +declare module "asyncfail:*"; +declare module "async-obj:*"; +declare module "obj:*"; +declare module "delay:*"; +declare module "./*.svelte"; diff --git a/test/js/bun/plugin/plugins.test.ts b/test/js/bun/plugin/plugins.test.ts new file mode 100644 index 000000000..2bac4b4d4 --- /dev/null +++ b/test/js/bun/plugin/plugins.test.ts @@ -0,0 +1,311 @@ +/// +import { plugin } from "bun"; +import { describe, expect, it } from "bun:test"; +import { resolve } from "path"; + +plugin({ + name: "boop beep beep", + setup(builder) { + builder.onResolve({ filter: /boop/, namespace: "beep" }, () => ({ + path: "boop", + namespace: "beep", + })); + + builder.onLoad({ filter: /boop/, namespace: "beep" }, () => ({ + contents: `export default 42;`, + loader: "js", + })); + }, +}); + +plugin({ + name: "an object module", + setup(builder) { + globalThis.objectModuleResult ||= { + hello: "world", + }; + builder.onResolve({ filter: /.*/, namespace: "obj" }, ({ path }) => ({ + path, + namespace: "obj", + })); + + builder.onLoad({ filter: /.*/, namespace: "obj" }, () => ({ + exports: globalThis.objectModuleResult, + loader: "object", + })); + }, +}); + +plugin({ + name: "failing loader", + setup(builder) { + globalThis.failingObject ||= {}; + builder.onResolve({ filter: /.*/, namespace: "fail" }, ({ path }) => ({ + path, + namespace: "fail", + })); + builder.onLoad({ filter: /.*/, namespace: "fail" }, () => globalThis.failingObject); + }, +}); + +plugin({ + name: "delayed loader", + setup(builder) { + globalThis.laterCode = ""; + + builder.onResolve({ filter: /.*/, namespace: "delay" }, ({ path }) => ({ + namespace: "delay", + path, + })); + + builder.onLoad({ filter: /.*/, namespace: "delay" }, ({ path }) => ({ + contents: (globalThis.laterCode ||= ""), + loader: "js", + })); + }, +}); + +plugin({ + name: "async onLoad", + setup(builder) { + globalThis.asyncOnLoad = ""; + + builder.onResolve({ filter: /.*/, namespace: "async" }, ({ path }) => ({ + namespace: "async", + path, + })); + + builder.onLoad({ filter: /.*/, namespace: "async" }, async ({ path }) => { + await Promise.resolve(1); + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve({ + contents: (globalThis.asyncOnLoad ||= ""), + loader: "js", + }); + }, 1); + }); + }); + + builder.onResolve({ filter: /.*/, namespace: "async-obj" }, ({ path }) => ({ + namespace: "async-obj", + path, + })); + globalThis.asyncObject = {}; + builder.onLoad({ filter: /.*/, namespace: "async-obj" }, async ({ path }) => { + await Promise.resolve(1); + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve({ + exports: (globalThis.asyncObject ||= {}), + loader: "object", + }); + }, 1); + }); + }); + + builder.onResolve({ filter: /.*/, namespace: "asyncfail" }, ({ path }) => ({ + namespace: "asyncfail", + path, + })); + + globalThis.asyncfail = false; + builder.onLoad({ filter: /.*/, namespace: "asyncfail" }, async ({ path }) => { + await Promise.resolve(1); + await 1; + throw globalThis.asyncfail; + }); + + builder.onResolve({ filter: /.*/, namespace: "asyncret" }, ({ path }) => ({ + namespace: "asyncret", + path, + })); + + globalThis.asyncret = 123; + builder.onLoad({ filter: /.*/, namespace: "asyncret" }, async ({ path }) => { + await 100; + await Promise.resolve(10); + return await globalThis.asyncret; + }); + }, +}); + +// This is to test that it works when imported from a separate file +import "../../third_party/svelte"; + +describe("require", () => { + it("SSRs `

Hello world!

` with Svelte", () => { + const { default: App } = require("./hello.svelte"); + const { html } = App.render(); + + expect(html).toBe("

Hello world!

"); + }); + + it("beep:boop returns 42", () => { + const result = require("beep:boop"); + expect(result.default).toBe(42); + }); + + it("object module works", () => { + const result = require("obj:boop"); + expect(result.hello).toBe(objectModuleResult.hello); + objectModuleResult.there = true; + const result2 = require("obj:boop2"); + expect(result.there).toBe(undefined); + expect(result2.there).toBe(objectModuleResult.there); + expect(result2.there).toBe(true); + }); +}); + +describe("dynamic import", () => { + it("SSRs `

Hello world!

` with Svelte", async () => { + const { default: App }: any = await import("./hello.svelte"); + + const { html } = App.render(); + + expect(html).toBe("

Hello world!

"); + }); + + it("beep:boop returns 42", async () => { + const result = await import("beep:boop"); + expect(result.default).toBe(42); + }); + + it("async:onLoad returns 42", async () => { + globalThis.asyncOnLoad = "export default 42;"; + const result = await import("async:hello42"); + expect(result.default).toBe(42); + }); + + it("async object loader returns 42", async () => { + globalThis.asyncObject = { foo: 42, default: 43 }; + const result = await import("async-obj:hello42"); + expect(result.foo).toBe(42); + expect(result.default).toBe(43); + }); +}); + +describe("import statement", () => { + it("SSRs `

Hello world!

` with Svelte", async () => { + laterCode = ` +import Hello from "${resolve(import.meta.dir, "hello2.svelte")}"; +export default Hello; +`; + const { default: SvelteApp } = await import("delay:hello2.svelte"); + const { html } = SvelteApp.render(); + + expect(html).toBe("

Hello world!

"); + }); +}); + +describe("errors", () => { + it("valid loaders work", () => { + const validLoaders = ["js", "jsx", "ts", "tsx"]; + const inputs = ["export default 'hi';", "export default 'hi';", "export default 'hi';", "export default 'hi';"]; + for (let i = 0; i < validLoaders.length; i++) { + const loader = validLoaders[i]; + const input = inputs[i]; + globalThis.failingObject = { contents: input, loader }; + expect(require(`fail:my-file-${loader}`).default).toBe("hi"); + } + }); + + it("invalid loaders throw", () => { + const invalidLoaders = ["blah", "blah2", "blah3", "blah4"]; + const inputs = ["body { background: red; }", "

hi

", '{"hi": "there"}', "hi"]; + for (let i = 0; i < invalidLoaders.length; i++) { + const loader = invalidLoaders[i]; + const input = inputs[i]; + globalThis.failingObject = { contents: input, loader }; + try { + require(`fail:my-file-${loader}`); + throw -1; + } catch (e: any) { + if (e === -1) { + throw new Error("Expected error"); + } + expect(e.message.length > 0).toBe(true); + } + } + }); + + it("transpiler errors work", () => { + const invalidLoaders = ["ts"]; + const inputs = ["const x: string = -NaNAn../!!;"]; + for (let i = 0; i < invalidLoaders.length; i++) { + const loader = invalidLoaders[i]; + const input = inputs[i]; + globalThis.failingObject = { contents: input, loader }; + try { + require(`fail:my-file-${loader}-3`); + throw -1; + } catch (e: any) { + if (e === -1) { + throw new Error("Expected error"); + } + expect(e.message.length > 0).toBe(true); + } + } + }); + + it("invalid async return value", async () => { + try { + globalThis.asyncret = { wat: true }; + await import("asyncret:my-file"); + throw -1; + } catch (e: any) { + if (e === -1) { + throw new Error("Expected error"); + } + + expect(e.message.length > 0).toBe(true); + } + }); + + it("async errors work", async () => { + try { + globalThis.asyncfail = new Error("async error"); + await import("asyncfail:my-file"); + throw -1; + } catch (e: any) { + if (e === -1) { + throw new Error("Expected error"); + } + expect(e.message.length > 0).toBe(true); + } + }); + + it("invalid onLoad objects throw", () => { + const invalidOnLoadObjects = [ + {}, + { contents: -1 }, + { contents: "", loader: -1 }, + { contents: "", loader: "klz", resolveDir: -1 }, + ]; + for (let i = 0; i < invalidOnLoadObjects.length; i++) { + globalThis.failingObject = invalidOnLoadObjects[i]; + try { + require(`fail:my-file-${i}-2`); + throw -1; + } catch (e: any) { + if (e === -1) { + throw new Error("Expected error"); + } + expect(e.message.length > 0).toBe(true); + } + } + }); + + it("async transpiler errors work", async () => { + try { + globalThis.asyncOnLoad = `const x: string = -NaNAn../!!;`; + await import("async:fail"); + throw -1; + } catch (e: any) { + if (e === -1) { + throw new Error("Expected error"); + } + expect(e.message.length > 0).toBe(true); + } + }); +}); diff --git a/test/js/bun/resolve/baz.js b/test/js/bun/resolve/baz.js new file mode 100644 index 000000000..5837bb3bb --- /dev/null +++ b/test/js/bun/resolve/baz.js @@ -0,0 +1,2 @@ +// this file is used in resolve.test.js +export default {}; diff --git a/test/js/bun/resolve/file-importing-nonexistent-file.js b/test/js/bun/resolve/file-importing-nonexistent-file.js new file mode 100644 index 000000000..f805e78bc --- /dev/null +++ b/test/js/bun/resolve/file-importing-nonexistent-file.js @@ -0,0 +1 @@ +import "./does-not-exist.js.js.js.js"; diff --git a/test/js/bun/resolve/first.mjs b/test/js/bun/resolve/first.mjs new file mode 100644 index 000000000..17021c623 --- /dev/null +++ b/test/js/bun/resolve/first.mjs @@ -0,0 +1,8 @@ +import { end, start } from "./startEnd.mjs"; + +start("First"); + +import "./second.mjs"; +import "./third.mjs"; + +end("First"); diff --git a/test/js/bun/resolve/import-meta.test.js b/test/js/bun/resolve/import-meta.test.js new file mode 100644 index 000000000..f87c8f89f --- /dev/null +++ b/test/js/bun/resolve/import-meta.test.js @@ -0,0 +1,164 @@ +import { it, expect } from "bun:test"; +import { mkdirSync, rmSync, writeFileSync } from "node:fs"; +import * as Module from "node:module"; +import sync from "./require-json.json.js"; + +const { path, dir } = import.meta; + +it("primordials are not here!", () => { + expect(import.meta.primordials === undefined).toBe(true); +}); + +it("import.meta.resolveSync", () => { + expect(import.meta.resolveSync("./" + import.meta.file, import.meta.path)).toBe(path); + const require = Module.createRequire(import.meta.path); + expect(require.resolve(import.meta.path)).toBe(path); + expect(require.resolve("./" + import.meta.file)).toBe(path); + + // check it works with URL objects + expect(Module.createRequire(new URL(import.meta.url)).resolve(import.meta.path)).toBe(import.meta.path); +}); + +it("require with a query string works on dynamically created content", () => { + rmSync("/tmp/bun-test-import-meta-dynamic-dir", { + recursive: true, + force: true, + }); + try { + const require = Module.createRequire("/tmp/bun-test-import-meta-dynamic-dir/foo.js"); + try { + require("./bar.js?query=123.js"); + } catch (e) { + expect(e.name).toBe("ResolveError"); + } + + mkdirSync("/tmp/bun-test-import-meta-dynamic-dir", { recursive: true }); + + writeFileSync("/tmp/bun-test-import-meta-dynamic-dir/bar.js", "export default 'hello';", "utf8"); + + expect(require("./bar.js?query=123.js").default).toBe("hello"); + } catch (e) { + throw e; + } finally { + rmSync("/tmp/bun-test-import-meta-dynamic-dir", { + recursive: true, + force: true, + }); + } +}); + +it("import.meta.require (json)", () => { + expect(import.meta.require("./require-json.json").hello).toBe(sync.hello); + const require = Module.createRequire(import.meta.path); + expect(require("./require-json.json.js").hello).toBe(sync.hello); +}); + +it("const f = require;require(json)", () => { + const f = require; + console.log(f); + expect(f("./require-json.json").hello).toBe(sync.hello); +}); + +it("Module.createRequire().resolve", () => { + const expected = Bun.resolveSync("./require-json.json", import.meta.dir); + + const createdRequire = Module.createRequire(import.meta.path); + const result = createdRequire.resolve("./require-json.json"); + + expect(result).toBe(expected); +}); + +// this is stubbed out +it("Module._nodeModulePaths()", () => { + const expected = Module._nodeModulePaths(); + expect(!!expected).toBe(true); +}); + +// this isn't used in bun but exists anyway +// we just want it to not be undefined +it("Module._cache", () => { + const expected = typeof Module._cache === "object" && Module._cache; + expect(!!expected).toBe(true); +}); + +it("Module._resolveFileName()", () => { + const expected = Bun.resolveSync(import.meta.path, "/"); + const result = Module._resolveFileName(import.meta.path, "/", true); + expect(result).toBe(expected); +}); + +it("Module.createRequire(file://url).resolve(file://url)", () => { + const expected = Bun.resolveSync("./require-json.json", import.meta.dir); + + const createdRequire = Module.createRequire(import.meta.url); + const result1 = createdRequire.resolve("./require-json.json"); + const result2 = createdRequire.resolve("file://./require-json.json"); + + expect(result1).toBe(expected); + expect(result2).toBe(expected); +}); + +it("import.meta.require.resolve", () => { + const expected = Bun.resolveSync("./require-json.json", import.meta.dir); + var { resolve } = import.meta.require; + const result = resolve("./require-json.json"); + expect(result).toBe(expected); +}); + +it("import.meta.require (javascript)", () => { + expect(import.meta.require("./require-js.js").hello).toBe(sync.hello); + const require = Module.createRequire(import.meta.path); + expect(require("./require-js.js").hello).toBe(sync.hello); +}); + +it("import() require + TLA", async () => { + expect((await import("./import-require-tla.js")).foo).toBe("bar"); +}); + +it("import.meta.require (javascript, live bindings)", () => { + var Source = import.meta.require("./import.live.decl.js"); + + // require transpiles to import.meta.require + var ReExport = require("./import.live.rexport.js"); + + // dynamic require (string interpolation that way forces it to be dynamic) + var ReExportDynamic = require(`./import.live.${"rexport".split("").join("")}.js`); + + expect(Source.foo).toBe(1); + Source.setFoo(Source.foo + 1); + + expect(ReExport.foo).toBe(2); + expect(Source.foo).toBe(2); + expect(ReExportDynamic.foo).toBe(2); + + Source.setFoo(Source.foo + 1); + + var { Namespace } = require("./import.live.rexport-require.js"); + + expect(Namespace).toBe(Source); + expect(ReExport.foo).toBe(3); + expect(Source.foo).toBe(3); + expect(Namespace.foo).toBe(3); + + ReExport.setFoo(ReExport.foo + 1); + + expect(ReExport.foo).toBe(4); + expect(Source.foo).toBe(4); + expect(Namespace.foo).toBe(4); +}); + +it("import.meta.dir", () => { + expect(dir.endsWith("/bun/test/js/bun/resolve")).toBe(true); +}); + +it("import.meta.path", () => { + expect(path.endsWith("/bun/test/js/bun/resolve/import-meta.test.js")).toBe(true); +}); + +it('require("bun") works', () => { + expect(require("bun")).toBe(Bun); +}); + +it('import("bun") works', async () => { + expect(await import("bun")).toBe(Bun); +}); diff --git a/test/js/bun/resolve/import-require-tla.js b/test/js/bun/resolve/import-require-tla.js new file mode 100644 index 000000000..732fc34dd --- /dev/null +++ b/test/js/bun/resolve/import-require-tla.js @@ -0,0 +1,7 @@ +const Fs = require("fs"); + +const DirEnt = Fs.Dirent; + +await Promise.resolve(123); + +export const foo = "bar"; diff --git a/test/js/bun/resolve/import.live.decl.js b/test/js/bun/resolve/import.live.decl.js new file mode 100644 index 000000000..46e67c9bd --- /dev/null +++ b/test/js/bun/resolve/import.live.decl.js @@ -0,0 +1,4 @@ +export var foo = 1; +export function setFoo(val) { + foo = val; +} diff --git a/test/js/bun/resolve/import.live.rexport-require.js b/test/js/bun/resolve/import.live.rexport-require.js new file mode 100644 index 000000000..10c993e08 --- /dev/null +++ b/test/js/bun/resolve/import.live.rexport-require.js @@ -0,0 +1 @@ +export const Namespace = import.meta.require("./import.live.decl.js"); diff --git a/test/js/bun/resolve/import.live.rexport.js b/test/js/bun/resolve/import.live.rexport.js new file mode 100644 index 000000000..6709c3466 --- /dev/null +++ b/test/js/bun/resolve/import.live.rexport.js @@ -0,0 +1,2 @@ +export { foo, setFoo } from "./import.live.decl.js"; +import { foo as bar } from "./import.live.decl.js"; diff --git a/test/js/bun/resolve/png/test-png-import.test.js b/test/js/bun/resolve/png/test-png-import.test.js new file mode 100644 index 000000000..ca2d0b9ce --- /dev/null +++ b/test/js/bun/resolve/png/test-png-import.test.js @@ -0,0 +1,7 @@ +import { expect, test } from "bun:test"; +import { resolve } from "path"; +import MyPNG from "./test-png.png"; + +test("png import", () => { + expect(MyPNG).toBe(resolve(__dirname, "./test-png.png")); +}); diff --git a/test/js/bun/resolve/png/test-png.png b/test/js/bun/resolve/png/test-png.png new file mode 100644 index 000000000..e69de29bb diff --git a/test/js/bun/resolve/require-js-top-level-await.js b/test/js/bun/resolve/require-js-top-level-await.js new file mode 100644 index 000000000..16bff41c3 --- /dev/null +++ b/test/js/bun/resolve/require-js-top-level-await.js @@ -0,0 +1 @@ +export const fail = await 1; diff --git a/test/js/bun/resolve/require-js.js b/test/js/bun/resolve/require-js.js new file mode 100644 index 000000000..36fc31432 --- /dev/null +++ b/test/js/bun/resolve/require-js.js @@ -0,0 +1,2 @@ +import { hello } from "./require-js2.js"; +export { hello }; diff --git a/test/js/bun/resolve/require-js2.js b/test/js/bun/resolve/require-js2.js new file mode 100644 index 000000000..518e69641 --- /dev/null +++ b/test/js/bun/resolve/require-js2.js @@ -0,0 +1 @@ +export const hello = -123; diff --git a/test/js/bun/resolve/require-json.json b/test/js/bun/resolve/require-json.json new file mode 100644 index 000000000..6414edc0e --- /dev/null +++ b/test/js/bun/resolve/require-json.json @@ -0,0 +1,3 @@ +{ + "hello": -123 +} diff --git a/test/js/bun/resolve/require-referenceerror.snapshot.js b/test/js/bun/resolve/require-referenceerror.snapshot.js new file mode 100644 index 000000000..c52af3d22 --- /dev/null +++ b/test/js/bun/resolve/require-referenceerror.snapshot.js @@ -0,0 +1,5 @@ +/** + * https://github.com/oven-sh/bun/issues/685 + */ +import { v4 as uuidv4 } from "uuid"; +Bun.inspect(uuidv4()); diff --git a/test/js/bun/resolve/resolve-error.test.ts b/test/js/bun/resolve/resolve-error.test.ts new file mode 100644 index 000000000..cddd2c051 --- /dev/null +++ b/test/js/bun/resolve/resolve-error.test.ts @@ -0,0 +1,11 @@ +import { expect, it, describe } from "bun:test"; + +describe("ResolveError", () => { + it("position object does not segfault", async () => { + try { + await import("./file-importing-nonexistent-file.js"); + } catch (e: any) { + expect(Bun.inspect(e.position).length > 0).toBe(true); + } + }); +}); diff --git a/test/js/bun/resolve/resolve-typescript-file.tsx b/test/js/bun/resolve/resolve-typescript-file.tsx new file mode 100644 index 000000000..ff8b4c563 --- /dev/null +++ b/test/js/bun/resolve/resolve-typescript-file.tsx @@ -0,0 +1 @@ +export default {}; diff --git a/test/js/bun/resolve/resolve.test.js b/test/js/bun/resolve/resolve.test.js new file mode 100644 index 000000000..305a374a2 --- /dev/null +++ b/test/js/bun/resolve/resolve.test.js @@ -0,0 +1,229 @@ +import { it, expect } from "bun:test"; +import { mkdirSync, writeFileSync } from "fs"; +import { join, resolve } from "path"; + +function resolveFrom(from) { + return specifier => import.meta.resolveSync(specifier, from); +} + +it("#imports", async () => { + await writePackageJSONImportsFixture(); + + const baz = await import.meta.resolve("#foo", join(await import.meta.resolve("package-json-imports/baz"), "../")); + expect(baz.endsWith("foo/private-foo.js")).toBe(true); + + const subpath = await import.meta.resolve( + "#foo/bar", + join(await import.meta.resolve("package-json-imports/baz"), "../"), + ); + expect(subpath.endsWith("foo/private-foo.js")).toBe(true); + + const react = await import.meta.resolve( + "#internal-react", + join(await import.meta.resolve("package-json-imports/baz"), "../"), + ); + expect(react.endsWith("/react/index.js")).toBe(true); + + // Check that #foo is not resolved to the package.json file. + try { + await import.meta.resolve("#foo"); + throw new Error("Test failed"); + } catch (exception) { + expect(exception instanceof ResolveError).toBe(true); + expect(exception.referrer).toBe(import.meta.path); + expect(exception.name).toBe("ResolveError"); + } + + // Chcek that package-json-imports/#foo doesn't work + try { + await import.meta.resolve("package-json-imports/#foo"); + throw new Error("Test failed"); + } catch (exception) { + expect(exception instanceof ResolveError).toBe(true); + expect(exception.referrer).toBe(import.meta.path); + expect(exception.name).toBe("ResolveError"); + } +}); + +it("#imports with wildcard", async () => { + await writePackageJSONImportsFixture(); + const run = resolveFrom(resolve(import.meta.dir + "/node_modules/package-json-imports/package.json")); + + const wildcard = resolve(import.meta.dir + "/node_modules/package-json-imports/foo/wildcard.js"); + expect(run("#foo/wildcard.js")).toBe(wildcard); + expect(run("#foo/extensionless/wildcard")).toBe(wildcard); +}); + +it("import.meta.resolve", async () => { + expect(await import.meta.resolve("./resolve.test.js")).toBe(import.meta.path); + + expect(await import.meta.resolve("./resolve.test.js", import.meta.path)).toBe(import.meta.path); + + expect( + // optional second param can be any path, including a dir + await import.meta.resolve("./bun.js/resolve.test.js", join(import.meta.path, "../")), + ).toBe(import.meta.path); + + // can be a package path + expect((await import.meta.resolve("react", import.meta.path)).length > 0).toBe(true); + + // file extensions are optional + expect(await import.meta.resolve("./resolve.test")).toBe(import.meta.path); + + // works with tsconfig.json "paths" + expect(await import.meta.resolve("foo/bar")).toBe(join(import.meta.path, "../baz.js")); + expect(await import.meta.resolve("@faasjs/baz")).toBe(join(import.meta.path, "../baz.js")); + + // works with package.json "exports" + writePackageJSONExportsFixture(); + expect(await import.meta.resolve("package-json-exports/baz")).toBe( + join(import.meta.path, "../node_modules/package-json-exports/foo/bar.js"), + ); + + // if they never exported /package.json, allow reading from it too + expect(await import.meta.resolve("package-json-exports/package.json")).toBe( + join(import.meta.path, "../node_modules/package-json-exports/package.json"), + ); + + // if an unnecessary ".js" extension was added, try against /baz + expect(await import.meta.resolve("package-json-exports/baz.js")).toBe( + join(import.meta.path, "../node_modules/package-json-exports/foo/bar.js"), + ); + + // works with TypeScript compiler edgecases like: + // - If the file ends with .js and it doesn't exist, try again with .ts and .tsx + expect(await import.meta.resolve("./resolve-typescript-file.js")).toBe( + join(import.meta.path, "../resolve-typescript-file.tsx"), + ); + expect(await import.meta.resolve("./resolve-typescript-file.tsx")).toBe( + join(import.meta.path, "../resolve-typescript-file.tsx"), + ); + + // throws a ResolveError on failure + try { + await import.meta.resolve("THIS FILE DOESNT EXIST"); + throw new Error("Test failed"); + } catch (exception) { + expect(exception instanceof ResolveError).toBe(true); + expect(exception.referrer).toBe(import.meta.path); + expect(exception.name).toBe("ResolveError"); + } +}); + +// the slightly lower level API, which doesn't prefill the second param +// and expects a directory instead of a filepath +it("Bun.resolve", async () => { + expect(await Bun.resolve("./resolve.test.js", import.meta.dir)).toBe(import.meta.path); +}); + +// synchronous +it("Bun.resolveSync", () => { + expect(Bun.resolveSync("./resolve.test.js", import.meta.dir)).toBe(import.meta.path); +}); + +it("self-referencing imports works", async () => { + await writePackageJSONExportsFixture(); + + const baz = await import.meta.resolve("package-json-exports/baz"); + const namespace = await import.meta.resolve("package-json-exports/references-baz"); + Loader.registry.delete(baz); + Loader.registry.delete(namespace); + var a = await import(baz); + var b = await import(namespace); + expect(a.bar).toBe(1); + expect(b.bar).toBe(1); + + Loader.registry.delete(baz); + Loader.registry.delete(namespace); + var a = await import("package-json-exports/baz"); + var b = await import("package-json-exports/references-baz"); + expect(a.bar).toBe(1); + expect(b.bar).toBe(1); + + Loader.registry.delete(baz); + Loader.registry.delete(namespace); + var a = import.meta.require("package-json-exports/baz"); + var b = import.meta.require("package-json-exports/references-baz"); + expect(a.bar).toBe(1); + expect(b.bar).toBe(1); + + Loader.registry.delete(baz); + Loader.registry.delete(namespace); + var a = import.meta.require(baz); + var b = import.meta.require(namespace); + expect(a.bar).toBe(1); + expect(b.bar).toBe(1); + + // test that file:// works + Loader.registry.delete(baz); + Loader.registry.delete(namespace); + var a = import.meta.require("file://" + baz); + var b = import.meta.require("file://" + namespace); + expect(a.bar).toBe(1); + expect(b.bar).toBe(1); +}); + +function writePackageJSONExportsFixture() { + try { + mkdirSync(join(import.meta.dir, "./node_modules/package-json-exports/foo"), { + recursive: true, + }); + } catch (exception) {} + writeFileSync(join(import.meta.dir, "./node_modules/package-json-exports/foo/bar.js"), "export const bar = 1;"); + writeFileSync( + join(import.meta.dir, "./node_modules/package-json-exports/foo/references-baz.js"), + "export {bar} from 'package-json-exports/baz';", + ); + writeFileSync( + join(import.meta.dir, "./node_modules/package-json-exports/package.json"), + JSON.stringify( + { + name: "package-json-exports", + exports: { + "./baz": "./foo/bar.js", + "./references-baz": "./foo/references-baz.js", + }, + }, + null, + 2, + ), + ); +} + +function writePackageJSONImportsFixture() { + try { + mkdirSync(join(import.meta.dir, "./node_modules/package-json-imports/foo"), { + recursive: true, + }); + } catch (exception) {} + writeFileSync(join(import.meta.dir, "./node_modules/package-json-imports/foo/bar.js"), "export const bar = 1;"); + writeFileSync( + join(import.meta.dir, "./node_modules/package-json-imports/foo/wildcard.js"), + "export const wildcard = 1;", + ); + writeFileSync( + join(import.meta.dir, "./node_modules/package-json-imports/foo/private-foo.js"), + "export {bar} from 'package-json-imports/#foo';", + ); + writeFileSync( + join(import.meta.dir, "./node_modules/package-json-imports/package.json"), + JSON.stringify( + { + name: "package-json-imports", + exports: { + "./baz": "./foo/bar.js", + }, + imports: { + "#foo/bar": "./foo/private-foo.js", + "#foo/*.js": "./foo/*.js", + "#foo/extensionless/*": "./foo/*.js", + "#foo": "./foo/private-foo.js", + + "#internal-react": "react", + }, + }, + null, + 2, + ), + ); +} diff --git a/test/js/bun/resolve/second-child.mjs b/test/js/bun/resolve/second-child.mjs new file mode 100644 index 000000000..5fb06ed45 --- /dev/null +++ b/test/js/bun/resolve/second-child.mjs @@ -0,0 +1,5 @@ +import { start, end } from "./startEnd.mjs"; + +start("Second (nested import)"); + +end("Second (nested import)"); diff --git a/test/js/bun/resolve/second.mjs b/test/js/bun/resolve/second.mjs new file mode 100644 index 000000000..888eb11b9 --- /dev/null +++ b/test/js/bun/resolve/second.mjs @@ -0,0 +1,7 @@ +import { start, end } from "./startEnd.mjs"; + +start("Second"); + +import "./second-child.mjs"; + +end("Second"); diff --git a/test/js/bun/resolve/startEnd.mjs b/test/js/bun/resolve/startEnd.mjs new file mode 100644 index 000000000..8b5549802 --- /dev/null +++ b/test/js/bun/resolve/startEnd.mjs @@ -0,0 +1,6 @@ +export function start(name) { + console.log(`[start] ${name}`); +} +export function end(name) { + console.log(`[end] ${name}`); +} diff --git a/test/js/bun/resolve/third.mjs b/test/js/bun/resolve/third.mjs new file mode 100644 index 000000000..f5ba5cc84 --- /dev/null +++ b/test/js/bun/resolve/third.mjs @@ -0,0 +1,4 @@ +import { end, start } from "./startEnd.mjs"; + +start("Third"); +end("Third"); diff --git a/test/js/bun/resolve/toml/toml-fixture.toml b/test/js/bun/resolve/toml/toml-fixture.toml new file mode 100644 index 000000000..090563ef7 --- /dev/null +++ b/test/js/bun/resolve/toml/toml-fixture.toml @@ -0,0 +1,39 @@ + +framework = "next" +origin = "http://localhost:5000" +inline.array = [1234, 4, 5, 6] + + +[macros] +react-relay = { "graphql" = "node_modules/bun-macro-relay/bun-macro-relay.tsx" } + +[install.scopes] +"@mybigcompany2" = { "token" = "123456", "url" = "https://registry.mybigcompany.com" } +"@mybigcompany3" = { "token" = "123456", "url" = "https://registry.mybigcompany.com", "three" = 4 } + + +[install.scopes."@mybigcompany"] +token = "123456" +url = "https://registry.mybigcompany.com" + +[bundle.packages] +"@emotion/react" = true + + +[dev] +foo = 123 +"foo.bar" = "baz" +"abba.baba" = "baba" +dabba = -123 +doo = 123.456 +one.two.three = 4 + +[[array]] +entry_one = "one" +entry_two = "two" + +[[array]] +entry_one = "three" + +[[array.nested]] +entry_one = "four" diff --git a/test/js/bun/resolve/toml/toml.test.js b/test/js/bun/resolve/toml/toml.test.js new file mode 100644 index 000000000..17d167476 --- /dev/null +++ b/test/js/bun/resolve/toml/toml.test.js @@ -0,0 +1,26 @@ +import { describe, it, expect } from "bun:test"; +import { gc } from "harness"; + +it("syntax", async () => { + gc(); + + const toml = (await import("./toml-fixture.toml")).default; + gc(); + + expect(toml.framework).toBe("next"); + expect(toml.bundle.packages["@emotion/react"]).toBe(true); + expect(toml.array[0].entry_one).toBe("one"); + expect(toml.array[0].entry_two).toBe("two"); + expect(toml.array[1].entry_one).toBe("three"); + expect(toml.array[1].entry_two).toBe(undefined); + expect(toml.array[1].nested[0].entry_one).toBe("four"); + expect(toml.dev.one.two.three).toBe(4); + expect(toml.dev.foo).toBe(123); + expect(toml.inline.array[0]).toBe(1234); + expect(toml.inline.array[1]).toBe(4); + expect(toml.dev["foo.bar"]).toBe("baz"); + expect(toml.install.scopes["@mybigcompany"].url).toBe("https://registry.mybigcompany.com"); + expect(toml.install.scopes["@mybigcompany2"].url).toBe("https://registry.mybigcompany.com"); + expect(toml.install.scopes["@mybigcompany3"].three).toBe(4); + gc(); +}); diff --git a/test/js/bun/spawn/bash-echo.sh b/test/js/bun/spawn/bash-echo.sh new file mode 100644 index 000000000..57bca4b01 --- /dev/null +++ b/test/js/bun/spawn/bash-echo.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +myvar=$(cat /dev/stdin) +echo -e "$myvar" diff --git a/test/js/bun/spawn/bun-spawn-test.js b/test/js/bun/spawn/bun-spawn-test.js new file mode 100644 index 000000000..1617a8588 --- /dev/null +++ b/test/js/bun/spawn/bun-spawn-test.js @@ -0,0 +1,21 @@ +const EventEmitter = import.meta.require("events"); +class TestClass extends EventEmitter { + #handle = null; + spawn() { + this.#handle = Bun.spawn(["pwd"], { + cwd: "/tmp", + onExit: this.#handleOnExit.bind(this), + }); + } + #handleOnExit(code) { + console.log(code); + this.emit("exit"); + } +} + +const testClass = new TestClass(); +testClass.spawn(); +testClass.on("exit", () => { + console.log("exiting"); + process.exit(0); +}); diff --git a/test/js/bun/spawn/exit-code-0.js b/test/js/bun/spawn/exit-code-0.js new file mode 100644 index 000000000..dcbbff6c9 --- /dev/null +++ b/test/js/bun/spawn/exit-code-0.js @@ -0,0 +1 @@ +process.exit(0); diff --git a/test/js/bun/spawn/exit-code-1.js b/test/js/bun/spawn/exit-code-1.js new file mode 100644 index 000000000..6cee2e1e7 --- /dev/null +++ b/test/js/bun/spawn/exit-code-1.js @@ -0,0 +1 @@ +process.exit(1); diff --git a/test/js/bun/spawn/exit-code-await-throw-1.js b/test/js/bun/spawn/exit-code-await-throw-1.js new file mode 100644 index 000000000..6b8c42eab --- /dev/null +++ b/test/js/bun/spawn/exit-code-await-throw-1.js @@ -0,0 +1,3 @@ +await (async function () { + throw 42; +})(); diff --git a/test/js/bun/spawn/exit-code-unhandled-throw.js b/test/js/bun/spawn/exit-code-unhandled-throw.js new file mode 100644 index 000000000..e8f5ca4cb --- /dev/null +++ b/test/js/bun/spawn/exit-code-unhandled-throw.js @@ -0,0 +1,3 @@ +(async function () { + throw 42; +})(); diff --git a/test/js/bun/spawn/exit-code.test.ts b/test/js/bun/spawn/exit-code.test.ts new file mode 100644 index 000000000..cda76a395 --- /dev/null +++ b/test/js/bun/spawn/exit-code.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, it, test } from "bun:test"; +import { bunExe } from "harness"; +import { spawnSync } from "bun"; + +it("process.exit(1) works", () => { + const { exitCode } = spawnSync([bunExe(), import.meta.dir + "/exit-code-1.js"]); + expect(exitCode).toBe(1); +}); + +it("await on a thrown value reports exit code 1", () => { + const { exitCode } = spawnSync([bunExe(), import.meta.dir + "/exit-code-await-throw-1.js"]); + expect(exitCode).toBe(1); +}); + +it("unhandled promise rejection reports exit code 1", () => { + const { exitCode } = spawnSync([bunExe(), import.meta.dir + "/exit-code-unhandled-throw.js"]); + expect(exitCode).toBe(1); +}); + +it("process.exit(0) works", () => { + const { exitCode } = spawnSync([bunExe(), import.meta.dir + "/exit-code-0.js"]); + expect(exitCode).toBe(0); +}); diff --git a/test/js/bun/spawn/exit.js b/test/js/bun/spawn/exit.js new file mode 100644 index 000000000..fb28b1fb4 --- /dev/null +++ b/test/js/bun/spawn/exit.js @@ -0,0 +1,2 @@ +process.exit(0); +throw new Error("Well that didn't work"); diff --git a/test/js/bun/spawn/spawn-streaming-stdin.test.ts b/test/js/bun/spawn/spawn-streaming-stdin.test.ts new file mode 100644 index 000000000..e2d346ec8 --- /dev/null +++ b/test/js/bun/spawn/spawn-streaming-stdin.test.ts @@ -0,0 +1,57 @@ +import { it, test, expect } from "bun:test"; +import { spawn } from "bun"; +import { bunExe, bunEnv, gcTick } from "harness"; +import { closeSync, openSync } from "fs"; + +const N = 100; +test("spawn can write to stdin multiple chunks", async () => { + const maxFD = openSync("/dev/null", "w"); + for (let i = 0; i < N; i++) { + var exited; + await (async function () { + const proc = spawn({ + cmd: [bunExe(), import.meta.dir + "/stdin-repro.js"], + stdout: "pipe", + stdin: "pipe", + stderr: Bun.file("/tmp/out.log"), + env: bunEnv, + }); + exited = proc.exited; + var counter = 0; + var inCounter = 0; + var chunks = []; + const prom = (async function () { + try { + for await (var chunk of proc.stdout) { + chunks.push(chunk); + } + } catch (e) { + console.log(e.stack); + throw e; + } + })(); + + const prom2 = (async function () { + while (true) { + proc.stdin.write("Wrote to stdin!\n"); + inCounter++; + await new Promise(resolve => setTimeout(resolve, 8)); + + if (inCounter === 4) break; + } + proc.stdin.end(); + })(); + + await Promise.all([prom, prom2]); + expect(Buffer.concat(chunks).toString().trim()).toBe("Wrote to stdin!\n".repeat(4).trim()); + await proc.exited; + })(); + } + + closeSync(maxFD); + const newMaxFD = openSync("/dev/null", "w"); + closeSync(newMaxFD); + + // assert we didn't leak any file descriptors + expect(newMaxFD).toBe(maxFD); +}); diff --git a/test/js/bun/spawn/spawn-streaming-stdout-repro.js b/test/js/bun/spawn/spawn-streaming-stdout-repro.js new file mode 100644 index 000000000..3976ff095 --- /dev/null +++ b/test/js/bun/spawn/spawn-streaming-stdout-repro.js @@ -0,0 +1,5 @@ +var writer = Bun.stdout.writer(); +setInterval(() => { + writer.write("Wrote to stdout\n"); + writer.flush(); +}, 20); diff --git a/test/js/bun/spawn/spawn-streaming-stdout.test.ts b/test/js/bun/spawn/spawn-streaming-stdout.test.ts new file mode 100644 index 000000000..75e36ca2c --- /dev/null +++ b/test/js/bun/spawn/spawn-streaming-stdout.test.ts @@ -0,0 +1,42 @@ +import { it, test, expect } from "bun:test"; +import { spawn } from "bun"; +import { bunExe, bunEnv, gcTick } from "harness"; +import { closeSync, openSync } from "fs"; + +test("spawn can read from stdout multiple chunks", async () => { + gcTick(true); + const maxFD = openSync("/dev/null", "w"); + closeSync(maxFD); + + for (let i = 0; i < 10; i++) + await (async function () { + var exited; + const proc = spawn({ + cmd: [bunExe(), import.meta.dir + "/spawn-streaming-stdout-repro.js"], + stdin: "ignore", + stdout: "pipe", + stderr: "ignore", + env: bunEnv, + }); + var chunks = []; + let counter = 0; + try { + for await (var chunk of proc.stdout) { + chunks.push(chunk); + counter++; + if (counter > 3) break; + } + } catch (e) { + console.log(e.stack); + throw e; + } + expect(counter).toBe(4); + // TODO: fix bug with returning SIGHUP instead of exit code 1 + proc.kill(); + expect(Buffer.concat(chunks).toString()).toBe("Wrote to stdout\n".repeat(4)); + })(); + + const newMaxFD = openSync("/dev/null", "w"); + closeSync(newMaxFD); + expect(newMaxFD).toBe(maxFD); +}); diff --git a/test/js/bun/spawn/spawn.test.ts b/test/js/bun/spawn/spawn.test.ts new file mode 100644 index 000000000..876985e66 --- /dev/null +++ b/test/js/bun/spawn/spawn.test.ts @@ -0,0 +1,380 @@ +import { ArrayBufferSink, readableStreamToText, spawn, spawnSync, write } from "bun"; +import { describe, expect, it } from "bun:test"; +import { gcTick as _gcTick, bunEnv } from "harness"; +import { rmdirSync, unlinkSync, rmSync, writeFileSync } from "node:fs"; + +for (let [gcTick, label] of [ + [_gcTick, "gcTick"], + // [() => {}, "no gc tick"], +] as const) { + Bun.gc(true); + describe(label, () => { + describe("spawnSync", () => { + const hugeString = "hello".repeat(10000).slice(); + + it("as an array", () => { + const { stdout } = spawnSync(["echo", "hi"]); + gcTick(); + // stdout is a Buffer + const text = stdout!.toString(); + expect(text).toBe("hi\n"); + gcTick(); + }); + + it("Uint8Array works as stdin", async () => { + const { stdout, stderr } = spawnSync({ + cmd: ["cat"], + stdin: new TextEncoder().encode(hugeString), + }); + gcTick(); + expect(stdout!.toString()).toBe(hugeString); + expect(stderr!.byteLength).toBe(0); + gcTick(); + }); + + it("check exit code", async () => { + const { exitCode: exitCode1 } = spawnSync({ + cmd: ["ls"], + }); + gcTick(); + const { exitCode: exitCode2 } = spawnSync({ + cmd: ["false"], + }); + gcTick(); + expect(exitCode1).toBe(0); + expect(exitCode2).toBe(1); + gcTick(); + }); + }); + + describe("spawn", () => { + const hugeString = "hello".repeat(10000).slice(); + + it("as an array", async () => { + gcTick(); + await (async () => { + const { stdout } = spawn(["echo", "hello"], { + stdout: "pipe", + stderr: null, + stdin: null, + }); + gcTick(); + const text = await new Response(stdout).text(); + expect(text).toBe("hello\n"); + })(); + gcTick(); + }); + + it("as an array with options object", async () => { + gcTick(); + const { stdout } = spawn(["printenv", "FOO"], { + cwd: "/tmp", + env: { + ...process.env, + FOO: "bar", + }, + stdin: null, + stdout: "pipe", + stderr: null, + }); + gcTick(); + const text = await new Response(stdout).text(); + expect(text).toBe("bar\n"); + gcTick(); + }); + + it("Uint8Array works as stdin", async () => { + rmSync("/tmp/out.123.txt", { force: true }); + gcTick(); + const { exited } = spawn({ + cmd: ["cat"], + stdin: new TextEncoder().encode(hugeString), + stdout: Bun.file("/tmp/out.123.txt"), + }); + gcTick(); + await exited; + expect(require("fs").readFileSync("/tmp/out.123.txt", "utf8")).toBe(hugeString); + gcTick(); + }); + + it("check exit code", async () => { + const exitCode1 = await spawn({ + cmd: ["ls"], + }).exited; + gcTick(); + const exitCode2 = await spawn({ + cmd: ["false"], + }).exited; + gcTick(); + expect(exitCode1).toBe(0); + expect(exitCode2).toBe(1); + gcTick(); + }); + + it("nothing to stdout and sleeping doesn't keep process open 4ever", async () => { + const proc = spawn({ + cmd: ["sleep", "0.1"], + }); + gcTick(); + for await (const _ of proc.stdout!) { + throw new Error("should not happen"); + } + gcTick(); + }); + + it("check exit code from onExit", async () => { + for (let i = 0; i < 1000; i++) { + var exitCode1, exitCode2; + await new Promise(resolve => { + var counter = 0; + spawn({ + cmd: ["ls"], + stdin: "ignore", + stdout: "ignore", + stderr: "ignore", + onExit(subprocess, code) { + exitCode1 = code; + counter++; + if (counter === 2) { + resolve(); + } + }, + }); + + spawn({ + cmd: ["false"], + stdin: "ignore", + stdout: "ignore", + stderr: "ignore", + onExit(subprocess, code) { + exitCode2 = code; + counter++; + + if (counter === 2) { + resolve(); + } + }, + }); + }); + + expect(exitCode1).toBe(0); + expect(exitCode2).toBe(1); + } + }); + + it("Blob works as stdin", async () => { + rmSync("/tmp/out.123.txt", { force: true }); + gcTick(); + const { exited } = spawn({ + cmd: ["cat"], + stdin: new Blob([new TextEncoder().encode(hugeString)]), + stdout: Bun.file("/tmp/out.123.txt"), + }); + + await exited; + expect(await Bun.file("/tmp/out.123.txt").text()).toBe(hugeString); + }); + + it("Bun.file() works as stdout", async () => { + rmSync("/tmp/out.123.txt", { force: true }); + gcTick(); + const { exited } = spawn({ + cmd: ["echo", "hello"], + stdout: Bun.file("/tmp/out.123.txt"), + }); + + await exited; + gcTick(); + expect(await Bun.file("/tmp/out.123.txt").text()).toBe("hello\n"); + }); + + it("Bun.file() works as stdin", async () => { + await write(Bun.file("/tmp/out.456.txt"), "hello there!"); + gcTick(); + const { stdout } = spawn({ + cmd: ["cat"], + stdout: "pipe", + stdin: Bun.file("/tmp/out.456.txt"), + }); + gcTick(); + expect(await readableStreamToText(stdout!)).toBe("hello there!"); + }); + + it("Bun.file() works as stdin and stdout", async () => { + writeFileSync("/tmp/out.456.txt", "hello!"); + gcTick(); + writeFileSync("/tmp/out.123.txt", "wrong!"); + gcTick(); + + const { exited } = spawn({ + cmd: ["cat"], + stdout: Bun.file("/tmp/out.123.txt"), + stdin: Bun.file("/tmp/out.456.txt"), + }); + gcTick(); + await exited; + expect(await Bun.file("/tmp/out.456.txt").text()).toBe("hello!"); + gcTick(); + expect(await Bun.file("/tmp/out.123.txt").text()).toBe("hello!"); + }); + + it("stdout can be read", async () => { + await Bun.write("/tmp/out.txt", hugeString); + gcTick(); + const { stdout } = spawn({ + cmd: ["cat", "/tmp/out.txt"], + stdout: "pipe", + }); + + gcTick(); + + const text = await readableStreamToText(stdout!); + gcTick(); + expect(text).toBe(hugeString); + }); + + it("kill(1) works", async () => { + const process = spawn({ + cmd: ["bash", "-c", "sleep 1000"], + stdout: "pipe", + }); + gcTick(); + const prom = process.exited; + process.kill(1); + await prom; + }); + + it("kill() works", async () => { + const process = spawn({ + cmd: ["bash", "-c", "sleep 1000"], + stdout: "pipe", + }); + gcTick(); + const prom = process.exited; + process.kill(); + await prom; + }); + + it("stdin can be read and stdout can be written", async () => { + const proc = spawn({ + cmd: ["bash", import.meta.dir + "/bash-echo.sh"], + stdout: "pipe", + stdin: "pipe", + lazy: true, + stderr: "inherit", + }); + + var stdout = proc.stdout!; + var reader = stdout.getReader(); + proc.stdin!.write("hey\n"); + await proc.stdin!.end(); + var text = ""; + + reader; + var done = false, + value; + + while (!done) { + ({ value, done } = await reader.read()); + if (value) text += new TextDecoder().decode(value); + if (done && text.length === 0) { + reader.releaseLock(); + reader = stdout.getReader(); + done = false; + } + } + + expect(text.trim().length).toBe("hey".length); + expect(text.trim()).toBe("hey"); + gcTick(); + await proc.exited; + }); + + describe("pipe", () => { + function huge() { + return spawn({ + cmd: ["echo", hugeString], + stdout: "pipe", + stdin: "pipe", + stderr: "inherit", + lazy: true, + }); + } + + function helloWorld() { + return spawn({ + cmd: ["echo", "hello"], + stdout: "pipe", + stdin: "ignore", + }); + } + + const fixtures = [ + [helloWorld, "hello"], + [huge, hugeString], + ] as const; + + for (const [callback, fixture] of fixtures) { + describe(fixture.slice(0, 12), () => { + describe("should should allow reading stdout", () => { + it("before exit", async () => { + const process = callback(); + const output = await readableStreamToText(process.stdout!); + await process.exited; + const expected = fixture + "\n"; + + expect(output.length).toBe(expected.length); + expect(output).toBe(expected); + }); + + it("before exit (chunked)", async () => { + const process = callback(); + var sink = new ArrayBufferSink(); + var any = false; + await (async function () { + var reader = process.stdout?.getReader(); + + reader?.closed.then( + a => { + console.log("Closed!"); + }, + err => { + console.log("Closed!", err); + }, + ); + var done = false, + value; + while (!done) { + ({ value, done } = await reader!.read()); + + if (value) { + any = true; + sink.write(value); + } + } + })(); + expect(any).toBe(true); + + const expected = fixture + "\n"; + + const output = await new Response(sink.end()).text(); + expect(output.length).toBe(expected.length); + await process.exited; + expect(output).toBe(expected); + }); + + it("after exit", async () => { + const process = callback(); + await process.exited; + const output = await readableStreamToText(process.stdout!); + const expected = fixture + "\n"; + expect(output.length).toBe(expected.length); + expect(output).toBe(expected); + }); + }); + }); + } + }); + }); + }); +} diff --git a/test/js/bun/spawn/stdin-repro.js b/test/js/bun/spawn/stdin-repro.js new file mode 100644 index 000000000..02840d00b --- /dev/null +++ b/test/js/bun/spawn/stdin-repro.js @@ -0,0 +1,10 @@ +var stdout = Bun.stdout.writer(); +console.error("Started"); +var count = 0; +for await (let chunk of Bun.stdin.stream()) { + const str = new Buffer(chunk).toString(); + stdout.write(str); + stdout.flush(); + count++; +} +console.error("Finished with", count); diff --git a/test/js/bun/spawn/stdio-test-instance-a-lot.js b/test/js/bun/spawn/stdio-test-instance-a-lot.js new file mode 100644 index 000000000..71815ddea --- /dev/null +++ b/test/js/bun/spawn/stdio-test-instance-a-lot.js @@ -0,0 +1,19 @@ +import { ArrayBufferSink } from "bun"; + +const sink = new ArrayBufferSink(); + +sink.write("hello"); +sink.write(" "); +sink.write("world"); +sink.write(new TextEncoder().encode("hello again|")); +sink.write(new TextEncoder().encode("😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌")); + +const string = Buffer.from(sink.end()).toString().repeat(9999); + +if (process.env.TEST_STDIO_STRING) { + const result = string; + process.stdout.write(result); +} else { + const result = Buffer.from(string); + process.stdout.write(result); +} diff --git a/test/js/bun/spawn/stdio-test-instance.js b/test/js/bun/spawn/stdio-test-instance.js new file mode 100644 index 000000000..fd820dc25 --- /dev/null +++ b/test/js/bun/spawn/stdio-test-instance.js @@ -0,0 +1,5 @@ +process.stdout.write("hello"); +process.stdout.write(" "); +process.stdout.write("world"); +process.stdout.write(new TextEncoder().encode("hello again|")); +process.stdout.write(new TextEncoder().encode("😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌")); diff --git a/test/js/bun/sqlite/northwind.testdb b/test/js/bun/sqlite/northwind.testdb new file mode 100644 index 000000000..66f01fdd2 Binary files /dev/null and b/test/js/bun/sqlite/northwind.testdb differ diff --git a/test/js/bun/sqlite/sql-raw.test.js b/test/js/bun/sqlite/sql-raw.test.js new file mode 100644 index 000000000..9b6312dda --- /dev/null +++ b/test/js/bun/sqlite/sql-raw.test.js @@ -0,0 +1,55 @@ +import { expect, it } from "bun:test"; + +var SQL = globalThis[Symbol.for("Bun.lazy")]("sqlite"); +const dbPath = import.meta.dir + "/northwind.testdb"; + +it("works", () => { + const handle = SQL.open(dbPath); + + const stmt = SQL.prepare(handle, 'SELECT * FROM "Orders" WHERE OrderDate > date($date)'); + expect(stmt.toString()).toBe(`SELECT * FROM "Orders" WHERE OrderDate > date(NULL)`); + + expect( + Array.isArray( + stmt.all({ + // do the conversion this way so that this test runs in multiple timezones + $date: new Date(new Date(1996, 8, 1, 0, 0, 0, 0).toUTCString()).toISOString(), + }), + ), + ).toBe(true); + expect(stmt.toString()).toBe(`SELECT * FROM "Orders" WHERE OrderDate > date('1996-09-01T07:00:00.000Z')`); + + var ran = stmt.run({ + $date: new Date(new Date(1997, 8, 1, 0, 0, 0, 0).toUTCString()).toISOString(), + }); + expect(Array.isArray(ran)).toBe(false); + expect(ran === undefined).toBe(true); + expect(stmt.toString()).toBe(`SELECT * FROM "Orders" WHERE OrderDate > date('1997-09-01T07:00:00.000Z')`); + + expect( + Array.isArray( + stmt.get({ + $date: new Date(new Date(1998, 8, 1, 0, 0, 0, 0).toUTCString()).toISOString(), + }), + ), + ).toBe(false); + expect(stmt.toString()).toBe(`SELECT * FROM "Orders" WHERE OrderDate > date('1998-09-01T07:00:00.000Z')`); + expect(stmt.paramsCount).toBe(1); + expect(stmt.columnsCount).toBe(14); + expect(stmt.columns.length).toBe(14); + stmt.finalize(); + SQL.close(handle); +}); + +it("SQL.run works", () => { + const handle = SQL.open(dbPath); + expect(typeof handle).toBe("number"); + + expect( + SQL.run(handle, 'SELECT * FROM "Orders" WHERE OrderDate > date($date)', { + $date: new Date(1996, 8, 1).toISOString(), + }), + ).toBe(undefined); + + SQL.close(handle); +}); diff --git a/test/js/bun/sqlite/sqlite-cross-process.js b/test/js/bun/sqlite/sqlite-cross-process.js new file mode 100644 index 000000000..d5b9b87b5 --- /dev/null +++ b/test/js/bun/sqlite/sqlite-cross-process.js @@ -0,0 +1,45 @@ +// https://github.com/oven-sh/bun/issues/1366 +import { Database } from "bun:sqlite"; +import { rmSync } from "fs"; + +const dir = process.env.SQLITE_DIR; + +rmSync(dir + "get-persist.sqlite", { force: true }); + +var db = Database.open(dir + "get-persist.sqlite", { create: true }); + +// Note, I've played with various values and it doesn't seem to change +// the behavior. The "beter-sqlite3" npm package does not exhibit this +// bug, so it doesn't seem to be a general SQLite thing. +db.run(`PRAGMA journal_mode = WAL`); +db.run(`PRAGMA synchrounous = NORMAL`); + +db.run( + `CREATE TABLE IF NOT EXISTS examples ( + id TEXT PRIMARY KEY + )`, +); + +// This persists, but if you place this call +db.run( + ` + INSERT INTO examples + VALUES ('hello') + ON CONFLICT (id) DO + UPDATE SET id='hello' + RETURNING id + `, +); + +db.query(`SELECT id FROM examples WHERE id='hello'`).get().id; +db.query( + ` +INSERT INTO examples +VALUES ('world') +ON CONFLICT (id) DO + UPDATE SET id='world' +RETURNING id +`, +).get(); + +process.exit(0); diff --git a/test/js/bun/sqlite/sqlite.test.js b/test/js/bun/sqlite/sqlite.test.js new file mode 100644 index 000000000..a23c2f037 --- /dev/null +++ b/test/js/bun/sqlite/sqlite.test.js @@ -0,0 +1,529 @@ +import { expect, it, describe } from "bun:test"; +import { Database, constants } from "bun:sqlite"; +import { existsSync, fstat, realpathSync, rmSync, writeFileSync } from "fs"; +import { spawnSync } from "bun"; +import { bunExe } from "harness"; +import { tmpdir } from "os"; +var encode = text => new TextEncoder().encode(text); + +it("Database.open", () => { + // in a folder which doesn't exist + try { + Database.open("/this/database/does/not/exist.sqlite", constants.SQLITE_OPEN_READWRITE); + throw new Error("Expected an error to be thrown"); + } catch (error) { + expect(error.message).toBe("unable to open database file"); + } + + // in a file which doesn't exist + try { + Database.open(`/tmp/database-${Math.random()}.sqlite`, constants.SQLITE_OPEN_READWRITE); + throw new Error("Expected an error to be thrown"); + } catch (error) { + expect(error.message).toBe("unable to open database file"); + } + + // in a file which doesn't exist + try { + Database.open(`/tmp/database-${Math.random()}.sqlite`, { readonly: true }); + throw new Error("Expected an error to be thrown"); + } catch (error) { + expect(error.message).toBe("unable to open database file"); + } + + // in a file which doesn't exist + try { + Database.open(`/tmp/database-${Math.random()}.sqlite`, { readwrite: true }); + throw new Error("Expected an error to be thrown"); + } catch (error) { + expect(error.message).toBe("unable to open database file"); + } + + // create works + { + var db = Database.open(`/tmp/database-${Math.random()}.sqlite`, { + create: true, + }); + db.close(); + } + + // this should not throw + // it creates an in-memory db + new Database().close(); +}); + +it("upsert cross-process, see #1366", () => { + const dir = realpathSync(tmpdir()) + "/"; + const { exitCode } = spawnSync([bunExe(), import.meta.dir + "/sqlite-cross-process.js"], { + env: { + SQLITE_DIR: dir, + }, + stderr: "inherit", + }); + expect(exitCode).toBe(0); + + const db2 = Database.open(dir + "get-persist.sqlite"); + + expect(db2.query(`SELECT id FROM examples`).all()).toEqual([{ id: "hello" }, { id: "world" }]); +}); + +it("creates", () => { + const db = Database.open(":memory:"); + db.exec( + "CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT, value INTEGER, created TEXT, deci FLOAT, blobby BLOB)", + ); + const stmt = db.prepare("INSERT INTO test (name, value, deci, created, blobby) VALUES (?, ?, ?, ?, ?)"); + + stmt.run(["foo", 1, Math.fround(1.111), new Date(1995, 12, 19).toISOString(), encode("Hello World")]); + stmt.run(["bar", 2, Math.fround(2.222), new Date(1995, 12, 19).toISOString(), encode("Hello World")]); + stmt.run(["baz", 3, Math.fround(3.333), new Date(1995, 12, 19).toISOString(), encode("Hello World")]); + + stmt.finalize(); + + const stmt2 = db.prepare("SELECT * FROM test"); + expect(JSON.stringify(stmt2.get())).toBe( + JSON.stringify({ + id: 1, + name: "foo", + value: 1, + created: new Date(1995, 12, 19).toISOString(), + deci: Math.fround(1.111), + blobby: encode("Hello World"), + }), + ); + + expect(JSON.stringify(stmt2.all())).toBe( + JSON.stringify([ + { + id: 1, + name: "foo", + value: 1, + created: new Date(1995, 12, 19).toISOString(), + deci: Math.fround(1.111), + blobby: encode("Hello World"), + }, + { + id: 2, + name: "bar", + value: 2, + created: new Date(1995, 12, 19).toISOString(), + deci: Math.fround(2.222), + blobby: encode("Hello World"), + }, + { + id: 3, + name: "baz", + value: 3, + created: new Date(1995, 12, 19).toISOString(), + deci: Math.fround(3.333), + blobby: encode("Hello World"), + }, + ]), + ); + expect(stmt2.run()).toBe(undefined); + + // not necessary to run but it's a good practice + stmt2.finalize(); +}); + +it("int52", () => { + const db = Database.open(":memory:"); + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, int64 INTEGER)"); + db.run("INSERT INTO test (int64) VALUES (?)", Number.MAX_SAFE_INTEGER); + expect(db.query("SELECT * FROM test").get().int64).toBe(Number.MAX_SAFE_INTEGER); +}); + +it("typechecks", () => { + const db = Database.open(":memory:"); + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)"); + db.exec('INSERT INTO test (name) VALUES ("Hello")'); + db.exec('INSERT INTO test (name) VALUES ("World")'); + + const q = db.prepare("SELECT * FROM test WHERE (name = ?)"); + + var expectfail = val => { + try { + q.run([val]); + throw new Error("Expected error"); + } catch (e) { + expect(e.message !== "Expected error").toBe(true); + expect(e.name).toBe("TypeError"); + } + + try { + q.all([val]); + throw new Error("Expected error"); + } catch (e) { + expect(e.message !== "Expected error").toBe(true); + expect(e.name).toBe("TypeError"); + } + + try { + q.get([val]); + throw new Error("Expected error"); + } catch (e) { + expect(e.message !== "Expected error").toBe(true); + expect(e.name).toBe("TypeError"); + } + }; + + expectfail(Symbol("oh hai")); + expectfail(new Date()); + expectfail(class Foo {}); + expectfail(() => class Foo {}); + expectfail(new RangeError("what")); + expectfail(new Map()); + expectfail(new Map([["foo", "bar"]])); + expectfail(new Set()); + expectfail(new Set([1, 2, 3])); +}); + +it("db.query supports TypedArray", () => { + const db = Database.open(":memory:"); + + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, blobby BLOB)"); + + const stmt = db.prepare("INSERT INTO test (blobby) VALUES (?)"); + stmt.run([encode("Hello World")]); + stmt.finalize(); + + const stmt2 = db.prepare("SELECT * FROM test"); + expect(JSON.stringify(stmt2.get())).toBe( + JSON.stringify({ + id: 1, + blobby: encode("Hello World"), + }), + ); + + const stmt3 = db.prepare("SELECT * FROM test WHERE (blobby = ?)"); + + expect(JSON.stringify(stmt3.get([encode("Hello World")]))).toBe( + JSON.stringify({ + id: 1, + blobby: encode("Hello World"), + }), + ); + + expect(JSON.stringify(db.query("SELECT * FROM test WHERE (blobby = ?)").get([encode("Hello World")]))).toBe( + JSON.stringify({ + id: 1, + blobby: encode("Hello World"), + }), + ); + + expect(stmt3.get([encode("Hello World NOT")])).toBe(null); +}); + +it("supports serialize/deserialize", () => { + const db = Database.open(":memory:"); + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)"); + db.exec('INSERT INTO test (name) VALUES ("Hello")'); + db.exec('INSERT INTO test (name) VALUES ("World")'); + + const input = db.serialize(); + const db2 = new Database(input); + + const stmt = db2.prepare("SELECT * FROM test"); + expect(JSON.stringify(stmt.get())).toBe( + JSON.stringify({ + id: 1, + name: "Hello", + }), + ); + + expect(JSON.stringify(stmt.all())).toBe( + JSON.stringify([ + { + id: 1, + name: "Hello", + }, + { + id: 2, + name: "World", + }, + ]), + ); + db2.exec("insert into test (name) values ('foo')"); + expect(JSON.stringify(stmt.all())).toBe( + JSON.stringify([ + { + id: 1, + name: "Hello", + }, + { + id: 2, + name: "World", + }, + { + id: 3, + name: "foo", + }, + ]), + ); + + const db3 = new Database(input, { readonly: true }); + try { + db3.exec("insert into test (name) values ('foo')"); + throw new Error("Expected error"); + } catch (e) { + expect(e.message).toBe("attempt to write a readonly database"); + } +}); + +it("db.query()", () => { + const db = Database.open(":memory:"); + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)"); + + expect(db[Symbol.for("Bun.Database.cache.count")]).toBe(0); + + var q = db.query("SELECT * FROM test WHERE name = ?"); + expect(q.get("Hello") === null).toBe(true); + + db.exec('INSERT INTO test (name) VALUES ("Hello")'); + db.exec('INSERT INTO test (name) VALUES ("World")'); + + var rows = db.query("SELECT * FROM test WHERE name = ?").all(["Hello"]); + + expect(JSON.stringify(rows)).toBe(JSON.stringify([{ id: 1, name: "Hello" }])); + + rows = db.query("SELECT * FROM test WHERE name = ?").all(["World"]); + + // if this fails, it means the query caching failed to update + expect(JSON.stringify(rows)).toBe(JSON.stringify([{ id: 2, name: "World" }])); + + rows = db.query("SELECT * FROM test WHERE name = ?").all(["Hello"]); + expect(JSON.stringify(rows)).toBe(JSON.stringify([{ id: 1, name: "Hello" }])); + + // check that the query is cached + expect(db[Symbol.for("Bun.Database.cache.count")]).toBe(1); + + db.clearQueryCache(); + + // check clearing the cache decremented the counter + expect(db[Symbol.for("Bun.Database.cache.count")]).toBe(0); + + q.finalize(); + try { + // check clearing the cache decremented the counter + + q.all(["Hello"]); + throw new Error("Should have thrown"); + } catch (e) { + expect(e.message !== "Should have thrown").toBe(true); + } + + // check that invalid queries are not cached + // and invalid queries throw + try { + db.query("SELECT * FROM BACON", ["Hello"]).all(); + throw new Error("Should have thrown"); + } catch (e) { + expect(e.message !== "Should have thrown").toBe(true); + expect(db[Symbol.for("Bun.Database.cache.count")]).toBe(0); + } + + // check that it supports multiple arguments + expect(JSON.stringify(db.query("SELECT * FROM test where (name = ? OR name = ?)").all(["Hello", "Fooooo"]))).toBe( + JSON.stringify([{ id: 1, name: "Hello" }]), + ); + expect(JSON.stringify(db.query("SELECT * FROM test where (name = ? OR name = ?)").all("Hello", "Fooooo"))).toBe( + JSON.stringify([{ id: 1, name: "Hello" }]), + ); + + // throws if insufficeint arguments + try { + db.query("SELECT * FROM test where (name = ? OR name = ?)").all("Hello"); + } catch (e) { + expect(e.message).toBe("Expected 2 values, got 1"); + } + + // named parameters + expect( + JSON.stringify( + db.query("SELECT * FROM test where (name = $hello OR name = $goodbye)").all({ + $hello: "Hello", + $goodbye: "Fooooo", + }), + ), + ).toBe(JSON.stringify([{ id: 1, name: "Hello" }])); + + db.close(); + + // Check that a closed database doesn't crash + // and does throw an error when trying to run a query + try { + db.query("SELECT * FROM test WHERE name = ?").all(["Hello"]); + throw new Error("Should have thrown"); + } catch (e) { + expect(e.message !== "Should have thrown").toBe(true); + } + + // check that we can call close multiple times + // it should not throw so that your code doesn't break + db.close(); + db.close(); + db.close(); +}); + +it("db.transaction()", () => { + const db = Database.open(":memory:"); + + db.exec("CREATE TABLE cats (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, age INTEGER)"); + + const insert = db.prepare("INSERT INTO cats (name, age) VALUES (@name, @age)"); + + expect(db.inTransaction).toBe(false); + const insertMany = db.transaction(cats => { + expect(db.inTransaction).toBe(true); + try { + for (const cat of cats) insert.run(cat); + } catch (exception) { + throw exception; + } + }); + + try { + insertMany([ + { "@name": "Joey", "@age": 2 }, + { "@name": "Sally", "@age": 4 }, + { "@name": "Junior", "@age": 1 }, + { "@name": "Sally", "@age": 4 }, + ]); + throw new Error("Should have thrown"); + } catch (exception) { + expect(exception.message).toBe("constraint failed"); + } + + expect(db.inTransaction).toBe(false); + expect(db.query("SELECT * FROM cats").all().length).toBe(0); + + expect(db.inTransaction).toBe(false); + insertMany([ + { "@name": "Joey", "@age": 2 }, + { "@name": "Sally", "@age": 4 }, + { "@name": "Junior", "@age": 1 }, + ]); + expect(db.inTransaction).toBe(false); + expect(db.query("SELECT * FROM cats").all().length).toBe(3); + expect(db.inTransaction).toBe(false); +}); + +// this bug was fixed by ensuring FinalObject has no more than 64 properties +it("inlineCapacity #987", async () => { + const path = "/tmp/bun-987.db"; + if (!existsSync(path)) { + const arrayBuffer = await (await fetch("https://github.com/oven-sh/bun/files/9265429/logs.log")).arrayBuffer(); + writeFileSync(path, arrayBuffer); + } + + const db = new Database(path); + + const query = `SELECT + media.mid, + UPPER(media.name) as name, + media.url, + media.duration, + time(media.duration, 'unixepoch') AS durationStr, + sum(totalDurations) AS totalDurations, + sum(logs.views) AS views, + total.venues, + total.devices, + SUM(CASE WHEN day = '01' THEN logs.views ELSE 0 END) as 'vi01', SUM(CASE WHEN day = '02' THEN logs.views ELSE 0 END) as 'vi02', SUM(CASE WHEN day = '03' THEN logs.views ELSE 0 END) as 'vi03', SUM(CASE WHEN day = '04' THEN logs.views ELSE 0 END) as 'vi04', SUM(CASE WHEN day = '05' THEN logs.views ELSE 0 END) as 'vi05', SUM(CASE WHEN day = '06' THEN logs.views ELSE 0 END) as 'vi06', SUM(CASE WHEN day = '07' THEN logs.views ELSE 0 END) as 'vi07', SUM(CASE WHEN day = '08' THEN logs.views ELSE 0 END) as 'vi08', SUM(CASE WHEN day = '09' THEN logs.views ELSE 0 END) as 'vi09', SUM(CASE WHEN day = '10' THEN logs.views ELSE 0 END) as 'vi10', SUM(CASE WHEN day = '11' THEN logs.views ELSE 0 END) as 'vi11', SUM(CASE WHEN day = '12' THEN logs.views ELSE 0 END) as 'vi12', SUM(CASE WHEN day = '13' THEN logs.views ELSE 0 END) as 'vi13', SUM(CASE WHEN day = '14' THEN logs.views ELSE 0 END) as 'vi14', SUM(CASE WHEN day = '15' THEN logs.views ELSE 0 END) as 'vi15', SUM(CASE WHEN day = '16' THEN logs.views ELSE 0 END) as 'vi16', SUM(CASE WHEN day = '17' THEN logs.views ELSE 0 END) as 'vi17', SUM(CASE WHEN day = '18' THEN logs.views ELSE 0 END) as 'vi18', SUM(CASE WHEN day = '19' THEN logs.views ELSE 0 END) as 'vi19', SUM(CASE WHEN day = '20' THEN logs.views ELSE 0 END) as 'vi20', SUM(CASE WHEN day = '21' THEN logs.views ELSE 0 END) as 'vi21', SUM(CASE WHEN day = '22' THEN logs.views ELSE 0 END) as 'vi22', SUM(CASE WHEN day = '23' THEN logs.views ELSE 0 END) as 'vi23', SUM(CASE WHEN day = '24' THEN logs.views ELSE 0 END) as 'vi24', SUM(CASE WHEN day = '25' THEN logs.views ELSE 0 END) as 'vi25', SUM(CASE WHEN day = '26' THEN logs.views ELSE 0 END) as 'vi26', SUM(CASE WHEN day = '27' THEN logs.views ELSE 0 END) as 'vi27', SUM(CASE WHEN day = '28' THEN logs.views ELSE 0 END) as 'vi28', SUM(CASE WHEN day = '29' THEN logs.views ELSE 0 END) as 'vi29', SUM(CASE WHEN day = '30' THEN logs.views ELSE 0 END) as 'vi30', MAX(CASE WHEN day = '01' THEN logs.venues ELSE 0 END) as 've01', MAX(CASE WHEN day = '02' THEN logs.venues ELSE 0 END) as 've02', MAX(CASE WHEN day = '03' THEN logs.venues ELSE 0 END) as 've03', MAX(CASE WHEN day = '04' THEN logs.venues ELSE 0 END) as 've04', MAX(CASE WHEN day = '05' THEN logs.venues ELSE 0 END) as 've05', MAX(CASE WHEN day = '06' THEN logs.venues ELSE 0 END) as 've06', MAX(CASE WHEN day = '07' THEN logs.venues ELSE 0 END) as 've07', MAX(CASE WHEN day = '08' THEN logs.venues ELSE 0 END) as 've08', MAX(CASE WHEN day = '09' THEN logs.venues ELSE 0 END) as 've09', MAX(CASE WHEN day = '10' THEN logs.venues ELSE 0 END) as 've10', MAX(CASE WHEN day = '11' THEN logs.venues ELSE 0 END) as 've11', MAX(CASE WHEN day = '12' THEN logs.venues ELSE 0 END) as 've12', MAX(CASE WHEN day = '13' THEN logs.venues ELSE 0 END) as 've13', MAX(CASE WHEN day = '14' THEN logs.venues ELSE 0 END) as 've14', MAX(CASE WHEN day = '15' THEN logs.venues ELSE 0 END) as 've15', MAX(CASE WHEN day = '16' THEN logs.venues ELSE 0 END) as 've16', MAX(CASE WHEN day = '17' THEN logs.venues ELSE 0 END) as 've17', MAX(CASE WHEN day = '18' THEN logs.venues ELSE 0 END) as 've18', MAX(CASE WHEN day = '19' THEN logs.venues ELSE 0 END) as 've19', MAX(CASE WHEN day = '20' THEN logs.venues ELSE 0 END) as 've20', MAX(CASE WHEN day = '21' THEN logs.venues ELSE 0 END) as 've21', MAX(CASE WHEN day = '22' THEN logs.venues ELSE 0 END) as 've22', MAX(CASE WHEN day = '23' THEN logs.venues ELSE 0 END) as 've23', MAX(CASE WHEN day = '24' THEN logs.venues ELSE 0 END) as 've24', MAX(CASE WHEN day = '25' THEN logs.venues ELSE 0 END) as 've25', MAX(CASE WHEN day = '26' THEN logs.venues ELSE 0 END) as 've26', MAX(CASE WHEN day = '27' THEN logs.venues ELSE 0 END) as 've27', MAX(CASE WHEN day = '28' THEN logs.venues ELSE 0 END) as 've28', MAX(CASE WHEN day = '29' THEN logs.venues ELSE 0 END) as 've29', MAX(CASE WHEN day = '30' THEN logs.venues ELSE 0 END) as 've30', MAX(CASE WHEN day = '01' THEN logs.devices ELSE 0 END) as 'de01', MAX(CASE WHEN day = '02' THEN logs.devices ELSE 0 END) as 'de02', MAX(CASE WHEN day = '03' THEN logs.devices ELSE 0 END) as 'de03', MAX(CASE WHEN day = '04' THEN logs.devices ELSE 0 END) as 'de04', MAX(CASE WHEN day = '05' THEN logs.devices ELSE 0 END) as 'de05', MAX(CASE WHEN day = '06' THEN logs.devices ELSE 0 END) as 'de06', MAX(CASE WHEN day = '07' THEN logs.devices ELSE 0 END) as 'de07', MAX(CASE WHEN day = '08' THEN logs.devices ELSE 0 END) as 'de08', MAX(CASE WHEN day = '09' THEN logs.devices ELSE 0 END) as 'de09', MAX(CASE WHEN day = '10' THEN logs.devices ELSE 0 END) as 'de10', MAX(CASE WHEN day = '11' THEN logs.devices ELSE 0 END) as 'de11', MAX(CASE WHEN day = '12' THEN logs.devices ELSE 0 END) as 'de12', MAX(CASE WHEN day = '13' THEN logs.devices ELSE 0 END) as 'de13', MAX(CASE WHEN day = '14' THEN logs.devices ELSE 0 END) as 'de14', MAX(CASE WHEN day = '15' THEN logs.devices ELSE 0 END) as 'de15', MAX(CASE WHEN day = '16' THEN logs.devices ELSE 0 END) as 'de16', MAX(CASE WHEN day = '17' THEN logs.devices ELSE 0 END) as 'de17', MAX(CASE WHEN day = '18' THEN logs.devices ELSE 0 END) as 'de18', MAX(CASE WHEN day = '19' THEN logs.devices ELSE 0 END) as 'de19', MAX(CASE WHEN day = '20' THEN logs.devices ELSE 0 END) as 'de20', MAX(CASE WHEN day = '21' THEN logs.devices ELSE 0 END) as 'de21', MAX(CASE WHEN day = '22' THEN logs.devices ELSE 0 END) as 'de22', MAX(CASE WHEN day = '23' THEN logs.devices ELSE 0 END) as 'de23', MAX(CASE WHEN day = '24' THEN logs.devices ELSE 0 END) as 'de24', MAX(CASE WHEN day = '25' THEN logs.devices ELSE 0 END) as 'de25', MAX(CASE WHEN day = '26' THEN logs.devices ELSE 0 END) as 'de26', MAX(CASE WHEN day = '27' THEN logs.devices ELSE 0 END) as 'de27', MAX(CASE WHEN day = '28' THEN logs.devices ELSE 0 END) as 'de28', MAX(CASE WHEN day = '29' THEN logs.devices ELSE 0 END) as 'de29', MAX(CASE WHEN day = '30' THEN logs.devices ELSE 0 END) as 'de30' + FROM + ( + SELECT + logs.mid, + sum(logs.duration) AS totalDurations, + strftime ('%d', START, 'unixepoch', 'localtime') AS day, + count(*) AS views, + count(DISTINCT did) AS devices, + count(DISTINCT vid) AS venues + FROM + logs + WHERE strftime('%m-%Y', start, 'unixepoch', 'localtime')='06-2022' + GROUP BY + day, + logs.mid + ) logs + INNER JOIN media ON media.id = logs.mid + INNER JOIN ( + SELECT + mid, + count(DISTINCT vid) as venues, + count(DISTINCT did) as devices + FROM + logs + WHERE strftime('%m-%Y', start, 'unixepoch', 'localtime')='06-2022' + GROUP by + mid + ) total ON logs.mid = total.mid + ORDER BY + name`; + + expect(Object.keys(db.query(query).all()[0]).length).toBe(99); +}); + +// https://github.com/oven-sh/bun/issues/1553 +it("latin1 supplement chars", () => { + const db = new Database(); + db.run("CREATE TABLE IF NOT EXISTS foo (id INTEGER PRIMARY KEY AUTOINCREMENT, greeting TEXT)"); + db.run("INSERT INTO foo (greeting) VALUES (?)", "Welcome to bun!"); + db.run("INSERT INTO foo (greeting) VALUES (?)", "Español"); + db.run("INSERT INTO foo (greeting) VALUES (?)", "¿Qué sucedió?"); + + expect(db.query("SELECT * FROM foo").all()).toEqual([ + { + id: 1, + greeting: "Welcome to bun!", + }, + { + id: 2, + greeting: "Español", + }, + { + id: 3, + greeting: "¿Qué sucedió?", + }, + ]); + + // test that it doesn't break when we do a structure transition + db.query("SELECT * FROM foo").all()[0].booop = true; + db.query("SELECT * FROM foo").all()[0].beep = true; + expect(db.query("SELECT * FROM foo").all()).toEqual([ + { + id: 1, + greeting: "Welcome to bun!", + }, + { + id: 2, + greeting: "Español", + }, + { + id: 3, + greeting: "¿Qué sucedió?", + }, + ]); +}); + +describe("Database.run", () => { + it("should not throw error `not an error` when provided query containing only whitespace", () => { + const db = Database.open(":memory:"); + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)"); + + expect(db[Symbol.for("Bun.Database.cache.count")]).toBe(0); + + var q = db.query("SELECT * FROM test WHERE name = ?"); + expect(q.get("Hello") === null).toBe(true); + + db.exec('INSERT INTO test (name) VALUES ("Hello")'); + db.exec('INSERT INTO test (name) VALUES ("World")'); + + try { + db.run(" "); + expect(true).toBeFalsy(); + } catch (e) { + expect(e.message).not.toBe("not an error"); + expect(e.message).toBe("Query contained no valid SQL statement; likely empty query."); + } + }); +}); diff --git a/test/js/bun/test/bigint.test.js b/test/js/bun/test/bigint.test.js new file mode 100644 index 000000000..46ad50b84 --- /dev/null +++ b/test/js/bun/test/bigint.test.js @@ -0,0 +1,14 @@ +import { describe, expect, it } from "bun:test"; + +describe("BigInt", () => { + it("compares correctly (literal)", () => { + expect(42n).toBe(42n); + }); + + it("compares correctly (object)", () => { + expect(BigInt(42n)).toBe(BigInt(42n)); + expect(42n).toBe(BigInt(42n)); + expect(BigInt(Bun.inspect(42n).substring(0, 2))).toBe(BigInt(42n)); + expect(BigInt(42n).valueOf()).toBe(BigInt(42n)); + }); +}); diff --git a/test/js/bun/test/bun-test.test.ts b/test/js/bun/test/bun-test.test.ts new file mode 100644 index 000000000..cc6bf644a --- /dev/null +++ b/test/js/bun/test/bun-test.test.ts @@ -0,0 +1,6 @@ +import { expect, test } from "bun:test"; + +test("Bun.version", () => { + expect(process.versions.bun).toBe(Bun.version); + expect(process.revision).toBe(Bun.revision); +}); diff --git a/test/js/bun/test/jest-doesnt-auto-import.js b/test/js/bun/test/jest-doesnt-auto-import.js new file mode 100644 index 000000000..4d4a02b37 --- /dev/null +++ b/test/js/bun/test/jest-doesnt-auto-import.js @@ -0,0 +1,12 @@ +export function getJestGlobals() { + return { + describe: typeof describe === "function" ? describe : undefined, + it: typeof it === "function" ? it : undefined, + test: typeof test === "function" ? test : undefined, + expect: typeof expect === "function" ? expect : undefined, + beforeAll: typeof beforeAll === "function" ? beforeAll : undefined, + beforeEach: typeof beforeEach === "function" ? beforeEach : undefined, + afterAll: typeof afterAll === "function" ? afterAll : undefined, + afterEach: typeof afterEach === "function" ? afterEach : undefined, + }; +} diff --git a/test/js/bun/test/jest-hooks.test.ts b/test/js/bun/test/jest-hooks.test.ts new file mode 100644 index 000000000..c99dc7759 --- /dev/null +++ b/test/js/bun/test/jest-hooks.test.ts @@ -0,0 +1,195 @@ +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "bun:test"; + +describe("test jest hooks in bun-test", () => { + describe("test beforeAll hook", () => { + let animal = "tiger"; + + beforeAll(() => { + animal = "lion"; + }); + + it("string should be set by hook", () => { + expect(animal).toEqual("lion"); + }); + }); + + describe("test beforeEach hook", () => { + let animal = "tiger"; + + beforeEach(() => { + animal = "lion"; + }); + + it("string should be set by hook", () => { + expect(animal).toEqual("lion"); + animal = "dog"; + }); + + it("string should be re-set by hook", () => { + expect(animal).toEqual("lion"); + }); + }); + + describe("test afterEach hook", () => { + let animal = "tiger"; + + afterEach(() => { + animal = "lion"; + }); + + it("string should not be set by hook", () => { + expect(animal).toEqual("tiger"); + animal = "dog"; + }); + + it("string should be set by hook", () => { + expect(animal).toEqual("lion"); + }); + }); + + describe("test afterAll hook", () => { + let animal = "tiger"; + + describe("test afterAll hook", () => { + afterAll(() => { + animal = "lion"; + }); + + it("string should not be set by hook", () => { + expect(animal).toEqual("tiger"); + animal = "dog"; + }); + }); + + it("string should be set by hook", () => { + expect(animal).toEqual("lion"); + }); + }); + + describe("test async hooks", async () => { + let beforeAllCalled = 0; + let beforeEachCalled = 0; + let afterAllCalled = 0; + let afterEachCalled = 0; + + beforeAll(async () => { + beforeAllCalled += await 1; + }); + + beforeEach(async () => { + beforeEachCalled += await 1; + }); + + afterAll(async () => { + afterAllCalled += await 1; + }); + + afterEach(async () => { + afterEachCalled += await 1; + }); + + it("should run after beforeAll()", () => { + expect(beforeAllCalled).toBe(1); + expect(beforeEachCalled).toBe(1); + expect(afterAllCalled).toBe(0); + expect(afterEachCalled).toBe(0); + }); + + it("should run after beforeEach()", () => { + expect(beforeAllCalled).toBe(1); + expect(beforeEachCalled).toBe(2); + expect(afterAllCalled).toBe(0); + expect(afterEachCalled).toBe(1); + }); + }); + + describe("test done callback in hooks", () => { + let beforeAllCalled = 0; + let beforeEachCalled = 0; + let afterAllCalled = 0; + let afterEachCalled = 0; + + beforeAll(done => { + setImmediate(() => { + beforeAllCalled++; + done(); + }); + }); + + beforeEach(done => { + setImmediate(() => { + beforeEachCalled++; + done(); + }); + }); + + afterAll(done => { + setImmediate(() => { + afterAllCalled++; + done(); + }); + }); + + afterEach(done => { + setImmediate(() => { + afterEachCalled++; + done(); + }); + }); + + it("should run after beforeAll()", () => { + expect(beforeAllCalled).toBe(1); + expect(beforeEachCalled).toBe(1); + expect(afterAllCalled).toBe(0); + expect(afterEachCalled).toBe(0); + }); + + it("should run after beforeEach()", () => { + expect(beforeAllCalled).toBe(1); + expect(beforeEachCalled).toBe(2); + expect(afterAllCalled).toBe(0); + expect(afterEachCalled).toBe(1); + }); + }); + + describe("test async hooks with done()", () => { + let beforeAllCalled = 0; + let beforeEachCalled = 0; + let afterAllCalled = 0; + let afterEachCalled = 0; + + beforeAll(async done => { + beforeAllCalled += await 1; + setTimeout(done, 1); + }); + + beforeEach(async done => { + beforeEachCalled += await 1; + setTimeout(done, 1); + }); + + afterAll(async done => { + afterAllCalled += await 1; + setTimeout(done, 1); + }); + + afterEach(async done => { + afterEachCalled += await 1; + setTimeout(done, 1); + }); + + it("should run after beforeAll()", () => { + expect(beforeAllCalled).toBe(1); + expect(beforeEachCalled).toBe(1); + expect(afterAllCalled).toBe(0); + expect(afterEachCalled).toBe(0); + }); + + it("should run after beforeEach()", () => { + expect(beforeAllCalled).toBe(1); + expect(beforeEachCalled).toBe(2); + expect(afterAllCalled).toBe(0); + expect(afterEachCalled).toBe(1); + }); + }); +}); diff --git a/test/js/bun/test/nested-describes.test.ts b/test/js/bun/test/nested-describes.test.ts new file mode 100644 index 000000000..636714fdc --- /dev/null +++ b/test/js/bun/test/nested-describes.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, test } from "bun:test"; + +/* +In this test we want the tests to print out the following on a success. +Each success / fail should show the path of describe and test scopes + +✓ outer most describe > mid describe 1 > inner most describe 1 > first +✓ outer most describe > mid describe 1 > inner most describe 2 > second +✓ outer most describe > mid describe 2 > inner most describe 3 > first + +@TODO add testing for this, would require to read the test console output +*/ + +describe("outer most describe", () => { + describe("mid describe 1", () => { + describe("inner most describe 1", () => { + test("first", () => { + expect(5).toEqual(5); + }); + }); + describe("inner most describe 2", () => { + test("second", () => { + expect(5).toEqual(5); + }); + }); + }); + describe("mid describe 2", () => { + describe("inner most describe 3", () => { + test("third", () => { + expect(5).toEqual(5); + }); + }); + }); +}); diff --git a/test/js/bun/test/test-auto-import-jest-globals.test.js b/test/js/bun/test/test-auto-import-jest-globals.test.js new file mode 100644 index 000000000..5baeae43e --- /dev/null +++ b/test/js/bun/test/test-auto-import-jest-globals.test.js @@ -0,0 +1,24 @@ +test("Jest auto imports", () => { + expect(true).toBe(true); + expect(typeof describe).toBe("function"); + expect(typeof it).toBe("function"); + expect(typeof test).toBe("function"); + expect(typeof expect).toBe("function"); + expect(typeof beforeAll).toBe("function"); + expect(typeof beforeEach).toBe("function"); + expect(typeof afterAll).toBe("function"); + expect(typeof afterEach).toBe("function"); +}); + +test("Jest's globals aren't available in every file", async () => { + const jestGlobals = await import("./jest-doesnt-auto-import.js"); + + expect(typeof jestGlobals.describe).toBe("undefined"); + expect(typeof jestGlobals.it).toBe("undefined"); + expect(typeof jestGlobals.test).toBe("undefined"); + expect(typeof jestGlobals.expect).toBe("undefined"); + expect(typeof jestGlobals.beforeAll).toBe("undefined"); + expect(typeof jestGlobals.beforeEach).toBe("undefined"); + expect(typeof jestGlobals.afterAll).toBe("undefined"); + expect(typeof jestGlobals.afterEach).toBe("undefined"); +}); diff --git a/test/js/bun/test/test-test.test.ts b/test/js/bun/test/test-test.test.ts new file mode 100644 index 000000000..b834d2152 --- /dev/null +++ b/test/js/bun/test/test-test.test.ts @@ -0,0 +1,2098 @@ +import { spawn, spawnSync } from "bun"; +import { describe, expect, it, test } from "bun:test"; +import { bunExe, bunEnv } from "harness"; +import { mkdirSync, realpathSync, rmSync, writeFileSync } from "fs"; +import { mkdtemp, rm, writeFile } from "fs/promises"; +import { tmpdir } from "os"; +import { join } from "path"; + +test("toStrictEqual() vs toEqual()", () => { + expect([1, , 3]).toEqual([1, , 3]); + expect({}).toEqual({}); + expect({}).toStrictEqual({}); + expect({}).toEqual({ a: undefined }); + expect({}).not.toStrictEqual({ a: undefined }); + + class C { + hi = 34; + } + class D { + hi = 34; + } + let c = new C(); + let d = new D(); + + expect(d).toEqual(c); + expect(d).not.toStrictEqual(c); + expect({ a: 1, b: undefined }).toEqual({ a: 1 }); + expect({ a: 1 }).toEqual({ a: 1, b: undefined }); + expect({ a: 1, b: undefined }).toEqual({ a: 1, b: undefined }); + + expect({ a: 1, b: undefined }).not.toStrictEqual({ a: 1 }); + expect({ a: 1 }).not.toStrictEqual({ a: 1, b: undefined }); + expect({ a: 1, b: undefined }).toStrictEqual({ a: 1, b: undefined }); + + expect({ a: 1, b: null }).not.toEqual({ a: 1 }); + expect({ a: 1 }).not.toEqual({ a: 1, b: null }); + expect({ a: 1, b: null }).toEqual({ a: 1, b: null }); + + expect({ a: 1 }).not.toEqual({ a: true }); + expect({ a: 1 }).not.toEqual({ a: "1" }); + expect({ a: 1 }).not.toEqual({ a: 1, b: 2 }); + expect({ a: 1, b: 2 }).not.toEqual({ a: 1 }); + expect({ a: 1 }).not.toStrictEqual({ a: true }); + expect({ a: 1 }).not.toStrictEqual({ a: "1" }); + expect({ a: 1 }).not.toStrictEqual({ a: 1, b: 2 }); + expect({ a: 1, b: 2 }).not.toStrictEqual({ a: 1 }); + expect({ a: 1 }).toStrictEqual({ a: 1 }); + + expect([1, undefined, 3]).toEqual([1, undefined, 3]); + expect([1, undefined, 3]).toStrictEqual([1, undefined, 3]); + expect([1, undefined, 3]).not.toEqual([1, 2, 3]); + expect([1, undefined, 3]).not.toStrictEqual([1, 2, 3]); + expect([1, undefined, 3]).not.toEqual([1, 2]); + expect([1, undefined, 3]).not.toStrictEqual([1, 2]); + expect([1, undefined, 3]).not.toEqual([1]); + expect([1, undefined, 3]).not.toStrictEqual([1]); + expect([1, undefined, 3]).not.toEqual([]); + expect([1, undefined, 3]).not.toStrictEqual([]); + expect([1, undefined, 3]).not.toEqual([1, 3]); + expect([1, undefined, 3]).not.toStrictEqual([1, 3]); + + expect([1, null, 3]).toEqual([1, null, 3]); + expect([1, null, 3]).toStrictEqual([1, null, 3]); + expect([1, null, 3]).not.toEqual([1, 2, 3]); + expect([1, null, 3]).not.toStrictEqual([1, 2, 3]); + expect([1, null, 3]).not.toEqual([1, 2]); + expect([1, null, 3]).not.toStrictEqual([1, 2]); + expect([1, null, 3]).not.toEqual([1]); + expect([1, null, 3]).not.toStrictEqual([1]); + expect([1, null, 3]).not.toEqual([]); + expect([1, null, 3]).not.toStrictEqual([]); + expect([1, null, 3]).not.toEqual([1, 3]); + expect([1, null, 3]).not.toStrictEqual([1, 3]); + + expect([, 1]).toEqual([, 1]); + expect([, 1]).toStrictEqual([, 1]); + expect([, 1]).not.toEqual([1]); + expect([1]).not.toEqual([, 1]); + expect([, 1]).not.toStrictEqual([1]); + expect([1]).not.toStrictEqual([, 1]); + expect([, 1]).toEqual([undefined, 1]); + expect([, 1]).not.toStrictEqual([undefined, 1]); + expect([, 1]).not.toEqual([null, 1]); + expect([, 1]).not.toStrictEqual([null, 1]); + expect([undefined, 1]).toEqual([, 1]); + expect([undefined, 1]).not.toStrictEqual([, 1]); + expect([null, 1]).not.toEqual([, 1]); + expect([null, 1]).not.toStrictEqual([, 1]); + expect([undefined, 1]).toEqual([undefined, 1]); + expect([undefined, 1]).toStrictEqual([undefined, 1]); + + expect([0, , 2]).toEqual([0, undefined, 2]); + expect([, "boo2"]).toEqual([undefined, "boo2"]); + expect([, "boo"]).toEqual([, "boo"]); + expect([, 1]).toEqual([undefined, 1]); + + const s1 = Symbol("test1"); + const s2 = Symbol("test2"); + + let a = { a: 1, b: 2 }; + let b = { a: 1, b: 2 }; + a[s1] = 1; + b[s1] = 1; + a[s2] = undefined; + b[s2] = null; + expect(a).not.toEqual(b); + class F extends String { + constructor() { + super(); + } + } + + let f = new F("hello"); + let j = new String("hello"); + expect(f).not.toEqual(j); + class LaCroix { + constructor(flavor) { + this.flavor = flavor; + } + } + expect(new LaCroix("pamplemousse")).not.toStrictEqual({ + flavor: "pamplemousse", + }); + expect(new LaCroix("pamplemousse")).toEqual({ flavor: "pamplemousse" }); + + expect([, 1]).not.toStrictEqual([undefined, 1]); + + expect([0, , 2]).toEqual([0, undefined, 2]); + expect([, "boo2"]).toEqual([undefined, "boo2"]); + expect([, "boo"]).toEqual([, "boo"]); + expect([, 1]).toEqual([undefined, 1]); +}); + +function f1() { + return "hello!"; +} +function f2() { + return "hey!"; +} +test("deepEquals regex", () => { + expect(/a/imu).toEqual(/a/imu); + expect(/a/imu).not.toEqual(/ab/imu); + + expect(new RegExp("s", "g")).toEqual(new RegExp("s", "g")); + expect(new RegExp("s", "g")).not.toEqual(new RegExp("s", "i")); +}); + +test("toThrow", () => { + expect(() => { + throw new Error("hello"); + }).toThrow("hello"); + + var err = new Error("bad"); + expect(() => { + throw err; + }).toThrow(err); + + expect(() => { + throw new Error("good"); + }).toThrow(); + + expect(() => { + throw new Error("foo"); + }).toThrow(/oo/); + + expect(() => + expect(() => { + throw new Error("bar"); + }).toThrow(/baz/), + ).toThrow("/baz/"); + + expect(() => { + return true; + }).not.toThrow(); + + expect(() => { + return true; + }).not.toThrow(err); +}); + +test("deepEquals derived strings and strings", () => { + let a = new String("hello"); + let b = "hello"; + expect(a).toEqual(a); + expect(b).toEqual(b); + expect(a).not.toEqual(b); + expect(b).not.toEqual(a); + + class F extends String { + constructor() { + super(); + } + } + + let f = new F("hello"); + expect(f).toEqual(f); + expect(f).not.toEqual(b); + expect(b).not.toEqual(f); + + let j = new String("hello"); + expect(f).not.toEqual(j); + + class G extends String { + constructor() { + super(); + this.x = 0; + } + } + + let g = new G("hello"); + expect(g).not.toEqual(f); + expect(f).not.toEqual(g); + expect(g).toEqual(g); + expect(g).not.toEqual(b); + expect(b).not.toEqual(g); + expect(g).not.toEqual(a); +}); + +test("deepEquals throw getters", () => { + let a = { + get x() { + throw new Error("a"); + }, + }; + + let b = { + get x() { + return 3; + }, + }; + + try { + expect(a).not.toEqual(b); + } catch (e) { + expect(e.message).toContain("a"); + } + + class B { + get x() { + throw new Error("b"); + } + } + + class C { + get x() { + return 3; + } + } + + expect(() => { + expect(new B()).not.toEqual(new C()); + }).toThrow(); + + let o = [ + { + get x() { + throw new Error("c"); + }, + }, + ]; + + let p = [ + { + get x() { + return 3; + }, + }, + ]; + + try { + expect(o).not.toEqual(p); + } catch (e) { + expect(e.message).toContain("c"); + } + + const s = Symbol("s"); + let q = { + get x() { + throw new Error("d"); + }, + }; + q[s] = 3; + + let r = { + get x() { + return 3; + }, + }; + r[s] = 3; + + try { + expect(q).not.toEqual(r); + } catch (e) { + expect(e.message).toContain("d"); + } +}); + +test("deepEquals large object", () => { + let o = {}; + for (let i = 0; i < 65; i++) { + o["bun" + i] = i; + } + expect(o).toEqual(o); + let b = {}; + for (let i = 0; i < 63; i++) { + b["bun" + i] = i; + } + expect(b).toEqual(b); + expect(o).not.toEqual(b); + expect(b).not.toEqual(o); + + let c = { d: [Array(o)] }; + let d = { d: [Array(b)] }; + expect(c).toEqual(c); + expect(d).toEqual(d); + expect(c).not.toEqual(d); + expect(d).not.toEqual(c); + + let e = { d: [Array(o), Array(o)] }; + let f = { d: [Array(b), Array(b)] }; + expect(e).toEqual(e); + expect(f).toEqual(f); + expect(e).not.toEqual(f); + expect(f).not.toEqual(e); + + let p = []; + p[0] = {}; + for (let i = 0; i < 1000; i++) { + p[0]["bun" + i] = i; + } + let q = []; + q[0] = {}; + for (let i = 0; i < 1000; i++) { + q[0]["bun" + i] = i; + } + expect(p).toEqual(p); + expect(q).toEqual(q); + + q[0].bun789 = 788; + expect(p).not.toEqual(q); + expect(q).not.toEqual(p); + + let r = { d: {} }; + let s = { d: {} }; + for (let i = 0; i < 1000; i++) { + r.d["bun" + i] = i; + s.d["bun" + i] = i; + } + + expect(r).toEqual(r); + expect(s).toEqual(s); + + r.d.bun790 = 791; + expect(r).not.toEqual(s); + expect(s).not.toEqual(r); + + let t = []; + t[5] = {}; + let u = []; + u[5] = {}; + for (let i = 0; i < 1000; i++) { + t[5]["bun" + i] = i; + } + for (let i = 0; i < 30; i++) { + u[5]["bun" + i] = i; + } + expect(t).toEqual(t); + expect(u).toEqual(u); + expect(t).not.toEqual(u); + expect(u).not.toEqual(t); + + let v = { j: {} }; + let w = { j: {} }; + for (let i = 0; i < 1000; i++) { + v.j["bun" + i] = i; + w.j["bun" + i] = i; + } + + expect(v).toEqual(v); + expect(w).toEqual(w); + + v.j.bun999 = 1000; + expect(v).not.toEqual(w); + expect(w).not.toEqual(v); + expect(v).toEqual(v); + + v.j.bun999 = 999; + w.j.bun0 = 1; + expect(v).not.toEqual(w); + expect(w).not.toEqual(v); + expect(v).toEqual(v); + expect(w).toEqual(w); +}); + +test("deepEquals - Date", () => { + let d = new Date(); + expect(d).toEqual(d); + let b = d; + expect(b).toEqual(d); + d.setFullYear(1998); + expect(b).toEqual(d); + expect(b).not.toEqual(new Date()); + + var date = new Date(); + date.setFullYear(1995); + expect(new Date()).not.toEqual(date); +}); + +test("deepEquals toString and functions", () => { + expect({ toString: f1 }).toEqual({ + toString: f1, + }); + expect({ toString: f1 }).not.toEqual({ + toString: f2, + }); + + expect(f1).toEqual(f1); + expect(f1).not.toEqual(f2); +}); + +test("deepEquals set and map", () => { + let e = new Map(); + e.set("a", 1); + e.set("b", 2); + e.set("c", 3); + e.set(8, 6); + + let d = new Map(); + d.set("a", 1); + d.set("b", 2); + d.set("c", 3); + d.set(8, 6); + + expect(e).toEqual(d); + expect(d).toEqual(e); + + let f = new Map(); + f.set("a", 1); + f.set("b", 2); + f.set("c", 3); + f.set(8, 7); + expect(e).not.toEqual(f); + + let g = new Map(); + g.set({ a: { b: { c: 89 } } }, 1); + + let h = new Map(); + h.set({ a: { b: { c: 89 } } }, 1); + expect(g).toEqual(h); + + let i = new Map(); + i.set({ a: { b: { c: 89 } } }, 1); + i.set({ a: { b: { c: 89 } } }, 1); + expect(g).not.toEqual(i); + + let j = new Map(); + j.set({ a: { b: { c: 89 } } }, 1); + j.set({ a: { b: { c: 89 } } }, 1); + expect(i).toEqual(j); + + let p = new Map(); + p.set({ a: { b: { c: 90 } } }, 1); + expect(p).not.toEqual(g); + + let q = new Map(); + q.set({ a: { b: { c: 90 } } }, { a: { b: 45 } }); + + let r = new Map(); + r.set({ a: { b: { c: 90 } } }, { a: { b: 45 } }); + expect(q).toEqual(r); + + let s = new Map(); + s.set({ a: { b: { c: 90 } } }, { a: { b: 49 } }); + expect(q).not.toEqual(s); + + const u = { a: 1, b: 2 }; + + let a = new Set(); + a.add({ a: 1 }); + a.add([1, 2, 3]); + a.add("hello"); + a.add(89); + + let b = new Set(); + b.add({ a: 1 }); + b.add("hello"); + b.add([1, 2, 3]); + b.add(89); + expect(a).toEqual(b); + expect(b).toEqual(a); + let c = new Set(); + c.add(89); + c.add("hello"); + c.add({ a: 1 }); + c.add([1, 2, 3, 4]); + expect(a).not.toEqual(c); +}); + +test("deepEquals - symbols", () => { + const x = [5, 6]; + x[99] = 7; + + const y = [5, 6]; + y[99] = 7; + + expect(x).toEqual(y); + + const s1 = Symbol("test1"); + const s2 = Symbol("test2"); + + const o = { a: 1 }; + o[s1] = 45; + o[99] = 99; + o[s2] = 3; + + const k = { a: 1 }; + k[99] = 99; + k[s2] = 3; + k[s1] = 45; + + expect(o).toEqual(k); +}); + +test("toEqual objects and arrays", () => { + expect("hello").toEqual("hello"); + const s1 = Symbol("test1"); + const s2 = Symbol("test2"); + + expect({ a: 1, b: 2 }).toEqual({ b: 2, a: 1 }); + expect([1, 2, 3]).toEqual([1, 2, 3]); + expect({ a: 1, b: 2 }).not.toEqual({ b: 2, a: 1, c: 3 }); + expect([1, 2, 3]).not.toEqual([1, 2, 3, 4]); + expect({ a: 1, b: 2, c: 3 }).not.toEqual({ a: 1, b: 2 }); + expect([1, 2, 3, 4]).not.toEqual([1, 2, 3]); + + let a = [{ a: 1 }, { b: 2, c: 3, d: 4 }, { e: 5, f: 6 }]; + let b = [{ a: 1 }, { b: 2, c: 3, d: 4 }, { e: 5, f: 6 }]; + expect(a).toEqual(b); + expect(b).toEqual(a); + a[0].a = 2; + expect(a).not.toEqual(b); + expect(b).not.toEqual(a); + + let c = { [Symbol("test")]: 1 }; + let d = { [Symbol("test")]: 1 }; + expect(c).not.toEqual(d); + expect(d).not.toEqual(c); + + a = { [s1]: 1 }; + a[s1] = 1; + b = { [s2]: 1 }; + b[s2] = 1; + expect(a).not.toEqual(b); + expect(b).not.toEqual(a); + + a = {}; + b = {}; + a[s1] = 1; + b[s1] = 1; + expect(a).toEqual(b); + + a = {}; + b = {}; + a[s1] = 1; + b[s1] = 2; + expect(a).not.toEqual(b); + + a = {}; + b = {}; + a[s1] = 1; + b[s1] = 1; + a[s2] = 2; + b[s2] = 2; + expect(a).toEqual(b); + + a = {}; + b = {}; + a[s1] = 1; + b[s1] = 1; + a[s2] = 2; + b[s2] = 3; + expect(a).not.toEqual(b); + + a = { a: 1, b: 2 }; + b = { a: 1, b: 2 }; + a[s1] = 1; + b[s1] = 1; + expect(a).toEqual(b); + + a = { a: 2, b: 2 }; + b = { a: 1, b: 2 }; + a[s1] = 1; + b[s1] = 1; + expect(a).not.toEqual(b); + + // do the same tests for arrays + a = [{ a: 1 }, { b: 2, c: 3, d: 4 }, { e: 5, f: 6 }]; + b = [{ a: 1 }, { b: 2, c: 3, d: 4 }, { e: 5, f: 6 }]; + expect(a).toEqual(b); + expect(b).toEqual(a); + a[0].a = 2; + expect(a).not.toEqual(b); + expect(b).not.toEqual(a); + + a = [1, 2, 3]; + b = [1, 2, 3]; + a[s1] = 1; + b[s1] = 1; + expect(a).toEqual(b); + + a = [1, 2, 3]; + b = [1, 2, 3]; + a[s1] = 1; + b[s1] = 2; + expect(a).not.toEqual(b); + + a = [1, 2, 3]; + b = [1, 2, 3]; + a[s1] = 1; + b[s1] = 1; + a[s2] = 2; + b[s2] = 2; + expect(a).toEqual(b); + + a = [1, 2, 3]; + b = [1, 2, 3]; + a[s1] = 1; + b[s1] = 1; + a[s2] = 2; + b[s2] = 3; + expect(a).not.toEqual(b); + + a = [1, 2, 3]; + b = [1, 2, 3]; + a[s1] = 1; + b[s1] = 1; + expect(a).toEqual(b); + + a = [2, 2, 3]; + b = [1, 2, 3]; + a[s1] = 1; + b[s1] = 1; + expect(a).not.toEqual(b); + + // do the same tests for objects and arrays with null and undefined + a = { a: 1, b: 2 }; + b = { a: 1, b: 2 }; + a[s1] = 1; + b[s1] = 1; + a[s2] = null; + b[s2] = undefined; + expect(a).not.toEqual(b); + + a = { a: 1, b: 2 }; + b = { a: 1, b: 2 }; + a[s1] = 1; + b[s1] = 1; + a[s2] = undefined; + b[s2] = null; + expect(a).not.toEqual(b); + + a = { a: 1, b: 2 }; + b = { a: 1, b: 2 }; + a[s1] = 1; + b[s1] = 1; + a[s2] = null; + b[s2] = null; + expect(a).toEqual(b); + + a = { a: 1, b: 2 }; + b = { a: 1, b: 2 }; + a[s1] = 1; + b[s1] = 1; + a[s2] = undefined; + b[s2] = undefined; + expect(a).toEqual(b); + + a = [1, 2, 3]; + b = [1, 2, 3]; + a[s1] = 1; + b[s1] = 1; + a[s2] = null; + b[s2] = undefined; + expect(a).not.toEqual(b); + + a = [1, 2, 3]; + b = [1, 2, 3]; + a[s1] = 1; + b[s1] = 1; + a[s2] = undefined; + b[s2] = null; + expect(a).not.toEqual(b); + + a = [1, 2, 3]; + b = [1, 2, 3]; + a[s1] = 1; + b[s1] = 1; + a[s2] = null; + b[s2] = null; + expect(a).toEqual(b); + + a = [1, 2, 3]; + b = [1, 2, 3]; + a[s1] = 1; + b[s1] = 1; + a[s2] = undefined; + b[s2] = undefined; + expect(a).toEqual(b); + + // similar tests for indexed objects + a = { 0: 1, 1: 2, 2: 3 }; + b = { 0: 1, 1: 2, 2: 3 }; + a[s1] = 1; + b[s1] = 1; + expect(a).toEqual(b); + + a = { 0: 1, 1: 2, 2: 3 }; + b = { 0: 1, 1: 2, 2: 3 }; + a[s1] = 1; + b[s1] = 1; + a[s2] = 2; + b[s2] = 3; + expect(a).not.toEqual(b); + + a = { 0: 1, 1: 3, 2: 3 }; + b = { 0: 1, 1: 2, 2: 3 }; + a[s1] = 1; + b[s1] = 1; + a[s2] = 2; + b[s2] = 2; + expect(a).not.toEqual(b); + + a = [1, 2, 3]; + b = [1, 2, 3, 4]; + expect(a).not.toEqual(b); + + a = [1, 2, 3, 4]; + b = [1, 2, 3]; + expect(a).not.toEqual(b); + + a = { a: 1, b: 2 }; + b = { a: 1, b: 2, c: 3 }; + expect(a).not.toEqual(b); + + a = { a: 1, b: 2, c: 3 }; + b = { a: 1, b: 2 }; + expect(a).not.toEqual(b); +}); + +test("symbol based keys in arrays are processed correctly", () => { + const mySymbol = Symbol("test"); + + const actual1 = []; + actual1[mySymbol] = 3; + + const actual2 = []; + actual2[mySymbol] = 4; + + const expected = []; + expected[mySymbol] = 3; + + expect(actual2).not.toEqual(expected); + expect(actual1).toEqual(expected); +}); + +test("non-enumerable members should be skipped during equal", () => { + const actual = { + x: 3, + }; + Object.defineProperty(actual, "test", { + enumerable: false, + value: 5, + }); + expect(actual).toEqual({ x: 3 }); +}); + +test("non-enumerable symbolic members should be skipped during equal", () => { + const actual = { + x: 3, + }; + const mySymbol = Symbol("test"); + Object.defineProperty(actual, mySymbol, { + enumerable: false, + value: 5, + }); + expect(actual).toEqual({ x: 3 }); +}); + +test("properties with the same circularity are equal", () => { + const a = {}; + a.x = a; + const b = {}; + b.x = b; + expect(a).toEqual(b); + expect(b).toEqual(a); + + const c = { + x: a, + }; + const d = { + x: b, + }; + + expect(d).toEqual(c); + expect(c).toEqual(d); +}); + +test("toEqual() - arrays", () => { + expect([1, 2, 3]).toEqual([1, 2, 3]); + expect([1, 2, 3, 4]).not.toEqual([1, 2, 3]); +}); + +test("properties with different circularity are not equal", () => { + const a = {}; + a.x = { y: a }; + const b = {}; + const bx = {}; + b.x = bx; + bx.y = bx; + expect(a).not.toEqual(b); + expect(b).not.toEqual(a); + + const c = {}; + c.x = a; + const d = {}; + d.x = b; + expect(c).not.toEqual(d); + expect(d).not.toEqual(c); +}); + +test("are not equal if circularity is not on the same property", () => { + const a = {}; + const b = {}; + a.a1 = a; + b.a1 = {}; + b.a1.a1 = a; + + expect(a).not.toEqual(b); + expect(b).not.toEqual(a); + + const c = {}; + c.x = { x: c }; + const d = {}; + d.x = d; + + expect(d).not.toEqual(c); + expect(c).not.toEqual(d); +}); + +test("random isEqual tests", () => { + expect(1).toEqual(1); + expect(1).not.toEqual(2); + expect(1).not.toEqual("1"); + expect(1).not.toEqual(true); + expect(1).not.toEqual(false); + expect(1).not.toEqual(null); + expect(1).not.toEqual(undefined); + expect(1).not.toEqual({}); + expect(1).not.toEqual([]); + expect(1).not.toEqual([1]); + expect(1).not.toEqual([1, 2]); + expect(1).not.toEqual([1, 2, 3]); + expect(1).not.toEqual([1, 2, 3, 4]); + expect(1).not.toEqual([1, 2, 3, 4, 5]); + expect(1).not.toEqual([1, 2, 3, 4, 5, 6]); + expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7]); + expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8]); + expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]); + expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); + expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]); + expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8]); + + // test toEquals for objects with getters and setters + + expect([]).toEqual([]); + expect([1]).toEqual([1]); + expect([1, 2]).toEqual([1, 2]); + expect([1, 2, 3]).toEqual([1, 2, 3]); + expect({}).toEqual({}); + expect({}).not.toEqual([]); + expect([]).not.toEqual({}); + + const obj = { + get a() { + return 1; + }, + }; + expect(obj).toEqual({ a: 1 }); + expect({ a: 1 }).toEqual(obj); + expect(obj).not.toEqual({ a: 2 }); + expect({ a: 2 }).not.toEqual(obj); + + let a = new Set(); + a.add([1, 2, 3]); + a.add("hello"); + a.add({ a: 1 }); + a.add(89); + let b = new Set(); + b.add(89); + b.add({ a: 1 }); + b.add("hello"); + b.add([1, 2, 3]); + expect(a).toEqual(b); + expect(b).toEqual(a); + let c = new Set(); + c.add(89); + c.add("helo"); + c.add({ a: 1 }); + c.add([1, 2, 3]); + expect(a).not.toEqual(c); + + a = new Map(); + a.set(1, 89); + a.set("hello", 2); + a.set({ a: 1 }, 3); + a.set([1, 2, 3], 4); + b = new Map(); + b.set(1, 89); + b.set("hello", 2); + b.set({ a: 1 }, 3); + b.set([1, 2, 3], 4); + expect(a).toEqual(b); + expect(b).toEqual(a); + c = new Map(); + c.set({ a: 1 }, 3); + c.set(1, 80); + c.set([1, 2, 3], 4); + c.set("hello", 2); + expect(a).not.toEqual(c); + + a = new Set(); + a.add(89); + a.add("hello"); + a.add({ a: 1 }); + a.add([1, 2, 3]); + a.add(a); + b = new Set(); + b.add(89); + b.add("hello"); + b.add(b); + b.add({ a: 1 }); + b.add([1, 2, 3]); + expect(a).toEqual(b); + expect(b).toEqual(a); +}); + +test("testing Bun.deepEquals() using isEqual()", () => { + const t = new Uint8Array([1, 2, 3, 4, 5]); + expect(t).toEqual(t.slice()); + + expect(t.subarray(1)).toEqual(t.slice(1)); + expect(t.subarray(1, 9)).toEqual(t.slice().subarray(1, 9)); + + var a = { foo: 1, bar: 2, baz: null }; + var b = { foo: 1, bar: 2, baz: null }; + a.baz = a; + b.baz = b; + expect(a).toEqual(b); + + var a = { car: 1, cdr: { car: 2, cdr: null } }; + var b = { car: 1, cdr: { car: 2, cdr: null } }; + a.cdr.cdr = a; + b.cdr.cdr = b.cdr; + expect(a).not.toEqual(b); + + expect(1n).not.toEqual(1); + expect(1).not.toEqual(1n); + expect(1n).toEqual(1n); + expect(undefined).not.toEqual([]); + + var a = [1, 2, 3, null]; + var b = [1, 2, 3, null]; + a[3] = b; + b[3] = a; + expect(a).toEqual(b); + + var a = [1, 2, 3, null]; + var b = [1, 2, 3, null]; + a[3] = a; + b[3] = a; + expect(a).toEqual(b); + + var a = [1, [2, [3, null]]]; + var b = [1, [2, [3, null]]]; + a[1][1][1] = a; + b[1][1][1] = b[1][1]; + expect(a).not.toEqual(b); + + const foo = [1]; + foo[1] = foo; + + expect(foo).toEqual([1, foo]); + + expect(1).toEqual(1); + expect([1]).toEqual([1]); + + // expect(a).toEqual(a); + expect([1, 2, 3]).toEqual([1, 2, 3]); + + let o = { a: 1, b: 2 }; + expect(o).toEqual(o); + expect(o).toEqual({ a: 1, b: 2 }); + expect(o).toEqual({ b: 2, a: 1 }); + expect({ a: 1, b: 2 }).toEqual(o); + expect({ b: 2, a: 1 }).toEqual(o); + expect(o).not.toEqual({ a: 1, b: 2, c: 3 }); + expect({ a: 1, b: 2, c: 3, d: 4 }).not.toEqual(o); + expect({ a: 1, b: 2 }).toEqual({ a: 1, b: 2 }); + expect({ a: 1, b: 2 }).not.toEqual({ a: 1 }); + + expect("a").toEqual("a"); + expect("aaaa").toEqual("aaaa"); + expect("aaaa").not.toEqual("aaaaa"); + expect("aaaa").not.toEqual("aaba"); + expect("a").not.toEqual("b"); + + expect(undefined).not.toEqual(null); + expect(null).not.toEqual(undefined); + expect(undefined).not.toEqual(0); + expect(0).not.toEqual(undefined); + expect(null).not.toEqual(0); + expect(0).not.toEqual(null); + expect(undefined).not.toEqual(""); + expect("").not.toEqual(undefined); + expect(null).not.toEqual(""); + expect("").not.toEqual(null); + expect(undefined).not.toEqual(false); + expect(false).not.toEqual(undefined); + expect(null).not.toEqual(false); + expect(false).not.toEqual(null); + expect(undefined).not.toEqual(true); + expect(true).not.toEqual(undefined); + expect(null).not.toEqual(true); + expect(true).not.toEqual(null); + expect([]).not.toEqual(undefined); + expect(null).not.toEqual([]); + expect([]).not.toEqual(null); + + expect(0).toEqual(0); + expect(-0).toEqual(-0); + expect(0).not.toEqual(-0); + expect(-0).not.toEqual(0); + + expect(NaN).toEqual(NaN); + + expect(null).toEqual(null); + expect(undefined).toEqual(undefined); + + expect(1).toEqual(1); + expect(1).not.toEqual(2); + + expect(NaN).toEqual(NaN); + expect(NaN).toEqual(0 / 0); + expect(Infinity).toEqual(Infinity); + expect(Infinity).toEqual(1 / 0); + expect(-Infinity).toEqual(-Infinity); + expect(-Infinity).toEqual(-1 / 0); +}); + +test("toHaveProperty() - emojis", () => { + expect({ "👍": "thumbs up" }).toHaveProperty("👍", "thumbs up"); + expect({ "👩‍👩‍👧‍👧": "family" }).toHaveProperty("👩‍👩‍👧‍👧", "family"); + expect({ "😶‍🌫️": "fog" }).toHaveProperty("😶‍🌫️", "fog"); + expect({ "👩‍❤️‍👨": "couple" }).toHaveProperty("👩‍❤️‍👨", "couple"); + expect({ "👩‍❤️‍👨‍👨‍👧‍👧": "family" }).toHaveProperty("👩‍❤️‍👨‍👨‍👧‍👧", "family"); + expect({ "👩‍❤️‍👨‍👨‍👧": "family" }).toHaveProperty("👩‍❤️‍👨‍👨‍👧", "family"); + expect({ "👩‍❤️‍👨‍👨‍👧": "family" }).not.toHaveProperty("👩‍❤️‍👨‍👨‍👧‍👧", "family"); + + // emojis in array + expect(["👍", "👎"]).toHaveProperty("0", "👍"); + expect(["👍", "👎"]).toHaveProperty("1", "👎"); + expect(["👍", "👎"]).not.toHaveProperty("0", "👎"); + expect(["👍", "👎"]).not.toHaveProperty("1", "👍"); + expect(["👩‍❤️‍👨‍👨‍👧‍👧"]).toHaveProperty("0", "👩‍❤️‍👨‍👨‍👧‍👧"); + expect(["👩‍❤️‍👨‍👨‍👧‍👧"]).toHaveProperty([0], "👩‍❤️‍👨‍👨‍👧‍👧"); + expect(["😶‍🌫️"]).toHaveProperty([0], "😶‍🌫️"); +}); + +test("toHaveProperty() - dot and bracket notation edge cases", () => { + expect({ a: 1 }).not.toHaveProperty("."); + expect({ a: 1 }).not.toHaveProperty("]"); + expect({ a: 1 }).not.toHaveProperty("["); + expect({ a: 1 }).not.toHaveProperty("[]"); + expect({ a: 1 }).not.toHaveProperty("[[]]"); + expect({ a: 1 }).not.toHaveProperty("[["); + expect({ a: 1 }).not.toHaveProperty("]]"); + expect({ a: 1 }).not.toHaveProperty("[]]"); + expect({ a: 1 }).not.toHaveProperty("[[]"); + expect({ a: 1 }).not.toHaveProperty(".]"); + expect({ a: 1 }).not.toHaveProperty(".["); + expect({ "": 1 }).toHaveProperty("[.", 1); + expect({ a: 1 }).not.toHaveProperty("[."); + expect({ a: 1 }).not.toHaveProperty("]."); + expect({ a: 1 }).not.toHaveProperty("].["); + expect({ a: 1 }).not.toHaveProperty("].]"); + expect({ a: 1 }).not.toHaveProperty("[.]"); + expect({ a: 1 }).not.toHaveProperty("[.["); + + expect([1]).toHaveProperty("[0]", 1); + expect([1]).toHaveProperty("[0][", 1); + expect([1]).toHaveProperty("[0]]", 1); + expect([1]).toHaveProperty("[0][[", 1); + expect([1]).toHaveProperty("[][[[0]", 1); + expect([1]).toHaveProperty("[][[[]][[][][.0", 1); + expect([1]).toHaveProperty("[][[[]][[][][.[][[][[[][][0", 1); + expect([1]).not.toHaveProperty("......1.............", 1); + expect([1]).not.toHaveProperty("......0.............", 1); + expect([1]).not.toHaveProperty(".0", 1); + expect([1]).not.toHaveProperty("0.", 1); + expect([{ "": 1 }]).toHaveProperty("0.", 1); + expect({ "": { "": 1 } }).toHaveProperty(".", 1); + expect({ "": { "": { "": 1 } } }).toHaveProperty("..", 1); + expect({ "": { "": { "": 1 } } }).not.toHaveProperty(".", 1); + expect({ "": { "": { "": 1 } } }).not.toHaveProperty("...", 1); + expect({ "": { "": { "": 1 } } }).not.toHaveProperty("....", 1); + expect([1]).toHaveProperty("0.[[[][][]][[[][[]]]]", 1); + expect([1]).not.toHaveProperty("[0].", 1); + expect([1]).toHaveProperty("0", 1); + expect([1]).toHaveProperty("[].0", 1); + expect([1]).toHaveProperty("[.0", 1); + expect([1]).toHaveProperty("].0", 1); + expect([1]).toHaveProperty("0[]][[[]", 1); + expect([1]).toHaveProperty("[[]][[[][][0", 1); + expect([1]).toHaveProperty("0", 1); + expect([1]).toHaveProperty("0.[", 1); + expect([1]).not.toHaveProperty("0........[", 1); + expect([1]).not.toHaveProperty("0..[", 1); + expect([1]).not.toHaveProperty(".0", 1); + expect([1]).toHaveProperty("[].0", 1); + expect([1]).not.toHaveProperty("[]..0", 1); + expect([1]).toHaveProperty("[.][.[[.]]]]].[.[].].]]]]].].].0", 1); + expect([1]).not.toHaveProperty("[.][.[[.]]]]].[.[].].]]0]]].].].", 1); + expect([1]).toHaveProperty("[.][.[[.]]]]].[.[].].]]0]]].].]", 1); + expect([1]).not.toHaveProperty("[.][.[[..]]]]].[.[].].]]0]]].].]", 1); + expect([1]).toHaveProperty("[.][.[[.]]]]].[.[].].0.]]]]].].]", 1); + expect([1]).not.toHaveProperty("[.][.[[.]]]]].[.[].].0.]]] ]].].]", 1); + expect([1]).not.toHaveProperty("0 ", 1); + expect([1]).not.toHaveProperty(" 0 ", 1); + expect([1]).not.toHaveProperty(" 0[] ", 1); + expect([1]).not.toHaveProperty(" 0] ", 1); + expect([1]).not.toHaveProperty(" .[0]", 1); + + expect({ "": 1 }).not.toHaveProperty(".", 1); + expect({ "": 1 }).not.toHaveProperty("]", 1); + expect({ "": 1 }).not.toHaveProperty("[", 1); + expect({ "": 1 }).toHaveProperty("", 1); + + expect({ "": 1 }).not.toHaveProperty("..", 1); + expect({ "": { "": 1 } }).not.toHaveProperty("..", 1); + expect([{ "": 1 }]).toHaveProperty("0.", 1); + expect([{ "": 1 }]).not.toHaveProperty(".0.", 1); + expect({ "": [1] }).toHaveProperty(".0", 1); + expect({ "": [1] }).not.toHaveProperty("..0", 1); + expect([{ "": 1 }]).not.toHaveProperty("0..", 1); + expect([{ "": { "": 1 } }]).toHaveProperty("0..", 1); + + expect([1]).not.toHaveProperty("[0].", 1); + expect([1]).not.toHaveProperty("[0][0]", 1); + expect({ a: [1] }).toHaveProperty("a[[[[[[[[[0]]]", 1); + expect({ "[[[": 0 }).not.toHaveProperty("[[[", 0); +}); + +test("toHaveProperty() - with string or array", () => { + const a = new Array(["a", "b", "c"]); + expect(a).toHaveProperty("0.1", "b"); + const b = new Array("a", "b", "c"); + expect({ a: { b: { c: 1 } } }).toHaveProperty(b); + const c = { + a: { b: 1 }, + "a.b": 2, + }; + const d = new Array("a.b"); + expect(c).toHaveProperty(d, 2); + const houseForSale = { + bath: true, + bedrooms: 4, + kitchen: { + amenities: ["oven", "stove", "washer"], + area: 20, + wallColor: "white", + "nice.oven": true, + }, + livingroom: { + amenities: [ + { + couch: [ + ["large", { dimensions: [20, 20] }], + ["small", { dimensions: [10, 10] }], + ], + }, + ], + }, + sunroom: "yes", + "ceiling.height": 20, + "entrance.window": 3, + entrance: { window: 5 }, + }; + expect(houseForSale).toHaveProperty("entrance.window", 5); + expect(houseForSale).toHaveProperty(["entrance", "window"], 5); + expect(houseForSale).toHaveProperty(["entrance.window"], 3); + expect(houseForSale).toHaveProperty("bath"); + expect(houseForSale).not.toHaveProperty("jacuzzi"); + // expect(houseForSale).toHaveProperty("jacuzzi"); + // expect(houseForSale).not.toHaveProperty("bath"); + expect(houseForSale).toHaveProperty("bath", true); + expect(houseForSale).not.toHaveProperty("bath", false); + // expect(houseForSale).toHaveProperty("bath", false); + // expect(houseForSale).not.toHaveProperty("bath", true); + expect(houseForSale).toHaveProperty("bedrooms", 4); + expect(houseForSale).toHaveProperty(["sunroom"], "yes"); + expect(houseForSale).toHaveProperty("kitchen.area", 20); + expect(houseForSale).toHaveProperty("kitchen.amenities", ["oven", "stove", "washer"]); + expect(houseForSale).not.toHaveProperty(["kitchen", "area"], 21); + expect(houseForSale).toHaveProperty(["kitchen", "area"], 20); + expect(houseForSale).not.toHaveProperty(["kitchen", "area"], 29); + expect(houseForSale).toHaveProperty(["kitchen", "amenities"], ["oven", "stove", "washer"]); + expect(houseForSale).toHaveProperty("kitchen.amenities[2]", "washer"); + expect(houseForSale).toHaveProperty(["kitchen", "amenities", 1], "stove"); + expect(houseForSale).toHaveProperty(["kitchen", "amenities", 0], "oven"); + expect(houseForSale).toHaveProperty("livingroom.amenities[0].couch[0][1].dimensions[0]", 20); + expect(houseForSale).toHaveProperty(["kitchen", "nice.oven"]); + expect(houseForSale).not.toHaveProperty(["kitchen", "open"]); + expect(houseForSale).toHaveProperty(["ceiling.height"], 20); + expect({ a: { b: 1 } }).toHaveProperty("a.b"); + expect({ a: [2, 3, 4] }).toHaveProperty("a.0"); + expect({ a: [2, 3, 4] }).toHaveProperty("a.1"); + expect({ a: [2, 3, 4] }).toHaveProperty("a.2"); + expect({ a: [2, 3, 4] }).toHaveProperty("a[1]"); + expect([2, 3, 4]).toHaveProperty("1"); + expect([2, 3, 4]).toHaveProperty("[1]"); + expect([2, [6, 9], 4]).toHaveProperty("1.1"); + expect([2, [6, 9], 4]).toHaveProperty("1[1]"); + expect([2, [6, 9], 4]).toHaveProperty("[1].1"); + expect([2, [6, 9], 4]).toHaveProperty("[1][1]"); + expect([2, [6, 9], 4]).toHaveProperty([0], 2); + expect({ a: { b: 1 } }).toHaveProperty("a.b"); + expect({ a: [1, 2, [3, { b: 1 }]] }).toHaveProperty("a.2.1.b"); + expect({ a: [1, 2, [3, { b: 1 }]] }).toHaveProperty("a"); + expect({ a: [1, 2, [3, { b: 1 }]] }).toHaveProperty("a[2][1].b"); + expect({ a: [1, 2, [3, { b: 1 }]] }).toHaveProperty("a[2][1]"); + expect({ a: [1, 2, [3, { b: 1 }]] }).not.toHaveProperty("a[2][1].c"); + expect("test").toHaveProperty("length"); + expect({}).toHaveProperty("constructor"); + expect({}).toHaveProperty("constructor.name"); + expect({}).toHaveProperty("constructor.name", "Object"); + expect(new Date()).toHaveProperty("getTime"); +}); + +test("toHaveProperty() - all", () => { + expect({ a: 1 }).toHaveProperty("a"); + expect({ a: 1 }).toHaveProperty("a", 1); + expect({ a: 1 }).not.toHaveProperty("b"); + expect({ a: 1 }).not.toHaveProperty("a", 2); + + // test with object with property "a" with all types of values (including undefined) + expect({ a: undefined }).toHaveProperty("a"); + expect({ a: null }).toHaveProperty("a"); + expect({ a: 0 }).toHaveProperty("a"); + expect({ a: false }).toHaveProperty("a"); + expect({ a: "" }).toHaveProperty("a"); + expect({ a: {} }).toHaveProperty("a"); + expect({ a: [] }).toHaveProperty("a"); + expect({ a: () => {} }).toHaveProperty("a"); + + // test with object with property "a" with all types of values (including undefined) + expect({ a: undefined }).toHaveProperty("a", undefined); + expect({ a: null }).toHaveProperty("a", null); + expect({ a: 0 }).toHaveProperty("a", 0); + expect({ a: false }).toHaveProperty("a", false); + expect({ a: "" }).toHaveProperty("a", ""); + expect({ a: {} }).toHaveProperty("a", {}); + expect({ a: [] }).toHaveProperty("a", []); + expect({ a: () => {} }).not.toHaveProperty("a", () => {}); + + // test with object with property "a" with all types of values (including undefined) + + expect({ a: undefined }).not.toHaveProperty("a", null); + expect({ a: null }).not.toHaveProperty("a", undefined); + expect({ a: 0 }).not.toHaveProperty("a", null); + expect({ a: false }).not.toHaveProperty("a", null); + expect({ a: "" }).not.toHaveProperty("a", null); + expect({ a: {} }).not.toHaveProperty("a", null); + expect({ a: [] }).not.toHaveProperty("a", null); + expect({ a: () => {} }).not.toHaveProperty("a", null); + + expect({ a: undefined }).not.toHaveProperty("a", 0); + expect({ a: null }).not.toHaveProperty("a", 0); + expect({ a: 0 }).not.toHaveProperty("a", 1); + expect({ a: false }).not.toHaveProperty("a", 0); + expect({ a: "" }).not.toHaveProperty("a", 0); + expect({ a: {} }).not.toHaveProperty("a", 0); + expect({ a: [] }).not.toHaveProperty("a", 0); + expect({ a: () => {} }).not.toHaveProperty("a", 0); + + expect({ a: undefined }).not.toHaveProperty("a", false); + expect({ a: null }).not.toHaveProperty("a", false); + expect({ a: 0 }).not.toHaveProperty("a", false); + expect({ a: false }).not.toHaveProperty("a", true); + expect({ a: "" }).not.toHaveProperty("a", false); + expect({ a: {} }).not.toHaveProperty("a", false); + expect({ a: [] }).not.toHaveProperty("a", false); + expect({ a: () => {} }).not.toHaveProperty("a", false); + + expect({ a: undefined }).not.toHaveProperty("a", ""); + expect({ a: null }).not.toHaveProperty("a", ""); + expect({ a: 0 }).not.toHaveProperty("a", ""); + expect({ a: false }).not.toHaveProperty("a", ""); + expect({ a: "" }).not.toHaveProperty("a", "a"); + expect({ a: {} }).not.toHaveProperty("a", ""); + expect({ a: [] }).not.toHaveProperty("a", ""); + expect({ a: () => {} }).not.toHaveProperty("a", ""); + + expect({ a: undefined }).not.toHaveProperty("a", {}); + expect({ a: null }).not.toHaveProperty("a", {}); + expect({ a: 0 }).not.toHaveProperty("a", {}); + expect({ a: false }).not.toHaveProperty("a", {}); + expect({ a: "" }).not.toHaveProperty("a", {}); + expect({ a: {} }).not.toHaveProperty("a", { a: 1 }); + expect({ a: [] }).not.toHaveProperty("a", {}); + expect({ a: () => {} }).not.toHaveProperty("a", {}); + + // test object with property "a" with value set, map, string + expect({ a: new Set([1, 2, 3]) }).toHaveProperty("a", new Set([3, 2, 1])); + expect({ a: new Map([{ a: 1 }, { b: 2 }, { c: 3 }]) }).toHaveProperty("a", new Map([{ c: 3 }, { b: 2 }, { a: 1 }])); + expect({ a: new String("a") }).toHaveProperty("a", new String("a")); + expect({ a: new String("a") }).not.toHaveProperty("a", "a"); + expect({ a: new String("a") }).not.toHaveProperty("a", "b"); + expect({ a: new String("a") }).not.toHaveProperty("a", new String("b")); + expect({ a: new String("a") }).not.toHaveProperty("a", new Number(1)); + expect({ a: new String("a") }).not.toHaveProperty("a", new Boolean(true)); + expect({ a: new String("a") }).not.toHaveProperty("a", new Boolean(false)); + expect({ a: new String("a") }).not.toHaveProperty("a", new Object()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Function()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Date()); + expect({ a: new String("a") }).not.toHaveProperty("a", new RegExp()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Error()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Promise(() => {})); + expect({ a: new String("a") }).not.toHaveProperty("a", new WeakSet()); + expect({ a: new String("a") }).not.toHaveProperty("a", new WeakMap()); + expect({ a: new String("a") }).not.toHaveProperty("a", Symbol("a")); + expect({ a: new String("a") }).not.toHaveProperty("a", new Int8Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Uint8Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Uint8ClampedArray()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Int16Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Uint16Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Int32Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Uint32Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Float32Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new Float64Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new BigInt64Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new BigUint64Array()); + expect({ a: new String("a") }).not.toHaveProperty("a", new ArrayBuffer()); + expect({ a: new String("a") }).not.toHaveProperty("a", new SharedArrayBuffer()); + expect({ a: new String("a") }).not.toHaveProperty("a", new DataView(new ArrayBuffer(1))); + + // test property equality with sets, maps, objects, arrays, and String + expect({ a: new Set([1, 2, 3]) }).toHaveProperty("a", new Set([1, 2, 3])); + expect({ a: new Map([{ a: 1 }, { b: 2 }, { c: 3 }]) }).toHaveProperty("a", new Map([{ a: 1 }, { b: 2 }, { c: 3 }])); + expect({ a: { a: 1, b: 2, c: 3 } }).toHaveProperty("a", { a: 1, b: 2, c: 3 }); + expect({ a: [1, 2, 3] }).toHaveProperty("a", [1, 2, 3]); + expect({ a: "a" }).toHaveProperty("a", "a"); + expect({ a: new String("a") }).toHaveProperty("a", new String("a")); + expect({ a: new String("a") }).not.toHaveProperty("a", "a"); +}); + +test("toBe()", () => { + const a = 1; + const b = 1; + expect(a).toBe(a); + expect(a).toBe(b); + expect(a).toBe(1); + expect(1).toBe(a); + expect(b).toBe(a); + + const c = { a: 1 }; + const d = { a: 1 }; + expect(c).toBe(c); + expect(c).not.toBe(d); + expect(c).not.toBe({ a: 1 }); + expect({ a: 1 }).not.toBe(c); + expect(d).not.toBe(c); + + expect(1).toBe(1); + // expect(1).not.toBe(1); + + expect(1).not.toBe(2); + expect(1).not.toBe("1"); + expect("hello test").toBe("hello test"); + expect("hello test").not.toBe("hello test2"); +}); + +test("toHaveLength()", () => { + expect({ length: Number.MAX_SAFE_INTEGER }).toHaveLength(Number.MAX_SAFE_INTEGER); + expect("123").toHaveLength(3); + expect([1, 2, 3]).toHaveLength(3); + expect([1, 2, 3]).not.toHaveLength(2); + expect("123").not.toHaveLength(2); + expect({ length: 3 }).toHaveLength(3); + expect({ length: 3 }).not.toHaveLength(2); + expect({ length: 3 }).not.toHaveLength(Number.MAX_SAFE_INTEGER); + expect({ length: Number.MAX_SAFE_INTEGER }).not.toHaveLength(Number.MAX_SAFE_INTEGER - 1); + expect({ length: 3.3 }).not.toHaveLength(3); + expect("123").not.toHaveLength(-0); +}); + +test("toContain()", () => { + const s1 = new String("123"); + expect(s1).not.toContain("12"); + const s2 = "123"; + expect(s2).toContain("12"); + + expect("test").toContain("es"); + expect("test").toContain("est"); + // expect("test").not.toContain("test"); + expect(["test", "es"]).toContain("es"); + expect("").toContain(""); + expect([""]).toContain(""); + + expect(["lemon", "lime"]).not.toContain("orange"); + expect("citrus fruits").toContain("fruit"); + + const a = new Uint16Array([1, 2, 3]); + expect(a).toContain(2); + expect(a).not.toContain(4); + expect([2, "2335", 5, true, false, null, undefined]).toContain(5); + expect([2, "2335", 5, true, false, null, undefined]).toContain("2335"); + expect([2, "2335", 5, true, false, null, undefined]).toContain(true); + expect([2, "2335", 5, true, false, null, undefined]).toContain(false); + expect([2, "2335", 5, true, false, null, undefined]).toContain(null); + expect([2, "2335", 5, true, false, null, undefined]).toContain(undefined); + expect([2, "2335", 5, true, false, null, undefined]).not.toContain(3); + expect([2, "2335", 5, true, false, null, undefined]).not.not.not.toContain(3); + + // expect([4, 5, 6]).not.toContain(5); + + expect([]).not.toContain([]); +}); + +test("toBeTruthy()", () => { + expect("test").toBeTruthy(); + expect(true).toBeTruthy(); + expect(1).toBeTruthy(); + expect({}).toBeTruthy(); + expect([]).toBeTruthy(); + expect(() => {}).toBeTruthy(); + // expect(() => {}).not.toBeTruthy(); + + expect("").not.toBeTruthy(); + expect(0).not.toBeTruthy(); + expect(-0).not.toBeTruthy(); + expect(NaN).not.toBeTruthy(); + expect(0n).not.toBeTruthy(); + expect(false).not.toBeTruthy(); + expect(null).not.toBeTruthy(); + expect(undefined).not.toBeTruthy(); +}); + +test("toBeUndefined()", () => { + expect(undefined).toBeUndefined(); + // expect(undefined).not.toBeUndefined(); + + expect(null).not.toBeUndefined(); + expect(null).not.not.not.toBeUndefined(); + expect(0).not.toBeUndefined(); + expect("hello defined").not.toBeUndefined(); +}); + +test("toBeNaN()", () => { + expect(NaN).toBeNaN(); + // expect(NaN).not.toBeNaN(); + + expect(0).not.toBeNaN(); + expect("hello not NaN").not.toBeNaN(); +}); + +test("toBeNull()", () => { + expect(null).toBeNull(); + // expect(null).not.toBeNull(); + + expect(undefined).not.toBeNull(); + expect(0).not.toBeNull(); + expect("hello not null").not.toBeNull(); +}); + +test("toBeDefined()", () => { + expect(0).toBeDefined(); + expect("hello defined").toBeDefined(); + expect(null).toBeDefined(); + // expect(null).not.toBeDefined(); + + expect(undefined).not.toBeDefined(); +}); + +test("toBeFalsy()", () => { + expect("").toBeFalsy(); + expect(0).toBeFalsy(); + expect(-0).toBeFalsy(); + expect(NaN).toBeFalsy(); + expect(0n).toBeFalsy(); + expect(false).toBeFalsy(); + expect(null).toBeFalsy(); + expect(undefined).toBeFalsy(); + // expect(undefined).not.toBeFalsy(); + + expect("hello not falsy").not.toBeFalsy(); + expect("hello not falsy").not.not.not.toBeFalsy(); + expect(1).not.toBeFalsy(); + expect(true).not.toBeFalsy(); + expect({}).not.toBeFalsy(); + expect([]).not.toBeFalsy(); + expect(() => {}).not.toBeFalsy(); +}); + +test("toBeGreaterThan()", () => { + expect(3n).toBeGreaterThan(2); + expect(Number.MAX_VALUE).not.toBeGreaterThan(Number.MAX_VALUE); + expect(1).not.toBeGreaterThan(BigInt(Number.MAX_VALUE)); + expect(1).not.toBeGreaterThan(Number.MAX_SAFE_INTEGER); + expect(1).not.toBeGreaterThan(BigInt(Number.MAX_SAFE_INTEGER)); + expect(Number.MAX_SAFE_INTEGER).not.toBeGreaterThan(Number.MAX_SAFE_INTEGER); + expect(BigInt(Number.MAX_SAFE_INTEGER)).not.toBeGreaterThan(BigInt(Number.MAX_SAFE_INTEGER)); + + expect(Infinity).toBeGreaterThan(-Infinity); + expect(-Infinity).not.toBeGreaterThan(Infinity); + + expect(NaN).not.toBeGreaterThan(NaN); + expect(NaN).not.toBeGreaterThan(-Infinity); + + expect(10).toBeGreaterThan(9); + expect(10).not.toBeGreaterThan(10); + expect(10).not.toBeGreaterThan(11); + expect(10).not.toBeGreaterThan(Infinity); + expect(10).toBeGreaterThan(-Infinity); + expect(10).not.toBeGreaterThan(NaN); + expect(10).toBeGreaterThan(0); + expect(10).toBeGreaterThan(-0); + expect(10).toBeGreaterThan(0.1); + expect(10).toBeGreaterThan(-0.1); + expect(10).toBeGreaterThan(0.9); + expect(10).toBeGreaterThan(-0.9); + expect(10).toBeGreaterThan(1); + expect(10).toBeGreaterThan(-1); + // switch the order + expect(9).not.toBeGreaterThan(10); + expect(10).not.toBeGreaterThan(10); + expect(11).toBeGreaterThan(10); + expect(Infinity).toBeGreaterThan(10); + expect(-Infinity).not.toBeGreaterThan(10); + expect(NaN).not.toBeGreaterThan(10); + expect(0).not.toBeGreaterThan(10); + expect(-0).not.toBeGreaterThan(10); + expect(0.1).not.toBeGreaterThan(10); + expect(-0.1).not.toBeGreaterThan(10); + expect(0.9).not.toBeGreaterThan(10); + expect(-0.9).not.toBeGreaterThan(10); + expect(1).not.toBeGreaterThan(10); + expect(-1).not.toBeGreaterThan(10); + + // same tests but use bigints + expect(10n).toBeGreaterThan(9n); + expect(10n).not.toBeGreaterThan(10n); + expect(10n).not.toBeGreaterThan(11n); + expect(10n).not.toBeGreaterThan(Infinity); + expect(10n).toBeGreaterThan(-Infinity); + expect(10n).not.toBeGreaterThan(NaN); + expect(10n).toBeGreaterThan(0n); + expect(10n).toBeGreaterThan(-0n); + expect(10n).toBeGreaterThan(1n); + expect(10n).toBeGreaterThan(-1n); + // switch the order + expect(9n).not.toBeGreaterThan(10n); + expect(10n).not.toBeGreaterThan(10n); + expect(11n).toBeGreaterThan(10n); + expect(Infinity).toBeGreaterThan(10n); + expect(-Infinity).not.toBeGreaterThan(10n); + expect(NaN).not.toBeGreaterThan(10n); + expect(0n).not.toBeGreaterThan(10n); + expect(-0n).not.toBeGreaterThan(10n); + expect(1n).not.toBeGreaterThan(10n); + expect(-1n).not.toBeGreaterThan(10n); + + // use bigints and numbers + expect(10n).toBeGreaterThan(9); + expect(10n).not.toBeGreaterThan(10); + expect(10n).not.toBeGreaterThan(11); + expect(10n).not.toBeGreaterThan(Infinity); + expect(10n).toBeGreaterThan(-Infinity); + expect(10n).not.toBeGreaterThan(NaN); + expect(10n).toBeGreaterThan(0); + expect(10n).toBeGreaterThan(-0); + expect(10n).toBeGreaterThan(0.1); + expect(10n).toBeGreaterThan(-0.1); + expect(10n).toBeGreaterThan(0.9); + expect(10n).toBeGreaterThan(-0.9); + expect(10n).toBeGreaterThan(1); + expect(10n).toBeGreaterThan(-1); + // switch the order + expect(9n).not.toBeGreaterThan(10); + expect(10n).not.toBeGreaterThan(10); + expect(11n).toBeGreaterThan(10); + expect(Infinity).toBeGreaterThan(10n); + expect(-Infinity).not.toBeGreaterThan(10n); + expect(NaN).not.toBeGreaterThan(10n); + expect(0n).not.toBeGreaterThan(10); + expect(-0n).not.toBeGreaterThan(10); + expect(1n).not.toBeGreaterThan(10); + expect(-1n).not.toBeGreaterThan(10); + + expect(1n).not.toBeGreaterThan(1); + expect(1n).not.toBeGreaterThan(Number.MAX_SAFE_INTEGER); + expect(1n).not.toBeGreaterThan(Number.MAX_VALUE); + expect(1).not.toBeGreaterThan(1n); + expect(Number.MAX_SAFE_INTEGER).toBeGreaterThan(1n); + expect(Number.MAX_VALUE).toBeGreaterThan(1n); + + expect(BigInt(Number.MAX_SAFE_INTEGER)).toBeGreaterThan(1n); + expect(BigInt(Number.MAX_VALUE)).toBeGreaterThan(1n); + expect(1n).not.toBeGreaterThan(BigInt(Number.MAX_SAFE_INTEGER)); + expect(1n).not.toBeGreaterThan(BigInt(Number.MAX_VALUE)); + + expect(BigInt(Number.MAX_SAFE_INTEGER)).toBeGreaterThan(1); + expect(BigInt(Number.MAX_VALUE)).toBeGreaterThan(1); + expect(1).not.toBeGreaterThan(BigInt(Number.MAX_SAFE_INTEGER)); +}); + +test("toBeGreaterThanOrEqual()", () => { + expect(Number.MAX_VALUE).toBeGreaterThanOrEqual(Number.MAX_VALUE); + expect(1).not.toBeGreaterThanOrEqual(Number.MAX_SAFE_INTEGER); + expect(1).not.toBeGreaterThanOrEqual(BigInt(Number.MAX_SAFE_INTEGER)); + expect(1).not.toBeGreaterThanOrEqual(BigInt(Number.MAX_VALUE)); + expect(Number.MAX_SAFE_INTEGER).toBeGreaterThanOrEqual(Number.MAX_SAFE_INTEGER); + expect(BigInt(Number.MAX_SAFE_INTEGER)).toBeGreaterThanOrEqual(BigInt(Number.MAX_SAFE_INTEGER)); + + expect(Infinity).toBeGreaterThanOrEqual(-Infinity); + expect(-Infinity).not.toBeGreaterThanOrEqual(Infinity); + + expect(NaN).not.toBeGreaterThanOrEqual(NaN); + expect(NaN).not.toBeGreaterThanOrEqual(-Infinity); + + expect(10).toBeGreaterThanOrEqual(9); + expect(10).toBeGreaterThanOrEqual(10); + expect(10).not.toBeGreaterThanOrEqual(11); + expect(10).not.toBeGreaterThanOrEqual(Infinity); + expect(10).toBeGreaterThanOrEqual(-Infinity); + expect(10).not.toBeGreaterThanOrEqual(NaN); + expect(10).toBeGreaterThanOrEqual(0); + expect(10).toBeGreaterThanOrEqual(-0); + expect(10).toBeGreaterThanOrEqual(0.1); + expect(10).toBeGreaterThanOrEqual(-0.1); + expect(10).toBeGreaterThanOrEqual(0.9); + expect(10).toBeGreaterThanOrEqual(-0.9); + expect(10).toBeGreaterThanOrEqual(1); + expect(10).toBeGreaterThanOrEqual(-1); + // switch the order + expect(9).not.toBeGreaterThanOrEqual(10); + expect(10).toBeGreaterThanOrEqual(10); + expect(11).toBeGreaterThanOrEqual(10); + expect(Infinity).toBeGreaterThanOrEqual(10); + expect(-Infinity).not.toBeGreaterThanOrEqual(10); + expect(NaN).not.toBeGreaterThanOrEqual(10); + expect(0).not.toBeGreaterThanOrEqual(10); + expect(-0).not.toBeGreaterThanOrEqual(10); + expect(0.1).not.toBeGreaterThanOrEqual(10); + expect(-0.1).not.toBeGreaterThanOrEqual(10); + expect(0.9).not.toBeGreaterThanOrEqual(10); + expect(-0.9).not.toBeGreaterThanOrEqual(10); + expect(1).not.toBeGreaterThanOrEqual(10); + expect(-1).not.toBeGreaterThanOrEqual(10); + + // same tests but use bigints + expect(10n).toBeGreaterThanOrEqual(9n); + expect(10n).toBeGreaterThanOrEqual(10n); + expect(10n).not.toBeGreaterThanOrEqual(11n); + expect(10n).not.toBeGreaterThanOrEqual(Infinity); + expect(10n).toBeGreaterThanOrEqual(-Infinity); + expect(10n).not.toBeGreaterThanOrEqual(NaN); + expect(10n).toBeGreaterThanOrEqual(0n); + expect(10n).toBeGreaterThanOrEqual(-0n); + expect(10n).toBeGreaterThanOrEqual(1n); + expect(10n).toBeGreaterThanOrEqual(-1n); + // switch the order + expect(9n).not.toBeGreaterThanOrEqual(10n); + expect(10n).toBeGreaterThanOrEqual(10n); + expect(11n).toBeGreaterThanOrEqual(10n); + expect(Infinity).toBeGreaterThanOrEqual(10n); + expect(-Infinity).not.toBeGreaterThanOrEqual(10n); + expect(NaN).not.toBeGreaterThanOrEqual(10n); + expect(0n).not.toBeGreaterThanOrEqual(10n); + expect(-0n).not.toBeGreaterThanOrEqual(10n); + expect(1n).not.toBeGreaterThanOrEqual(10n); + expect(-1n).not.toBeGreaterThanOrEqual(10n); + + // use bigints and numbers + expect(10n).toBeGreaterThanOrEqual(9); + expect(10n).toBeGreaterThanOrEqual(10); + expect(10n).not.toBeGreaterThanOrEqual(11); + expect(10n).not.toBeGreaterThanOrEqual(Infinity); + expect(10n).toBeGreaterThanOrEqual(-Infinity); + expect(10n).not.toBeGreaterThanOrEqual(NaN); + expect(10n).toBeGreaterThanOrEqual(0); + expect(10n).toBeGreaterThanOrEqual(-0); + expect(10n).toBeGreaterThanOrEqual(0.1); + expect(10n).toBeGreaterThanOrEqual(-0.1); + expect(10n).toBeGreaterThanOrEqual(0.9); + expect(10n).toBeGreaterThanOrEqual(-0.9); + expect(10n).toBeGreaterThanOrEqual(1); + expect(10n).toBeGreaterThanOrEqual(-1); + // switch the order + expect(9n).not.toBeGreaterThanOrEqual(10); + expect(10n).toBeGreaterThanOrEqual(10); + expect(11n).toBeGreaterThanOrEqual(10); + expect(Infinity).toBeGreaterThanOrEqual(10n); + expect(-Infinity).not.toBeGreaterThanOrEqual(10n); + expect(NaN).not.toBeGreaterThanOrEqual(10n); + expect(0n).not.toBeGreaterThanOrEqual(10); + expect(-0n).not.toBeGreaterThanOrEqual(10); + expect(1n).not.toBeGreaterThanOrEqual(10); + expect(-1n).not.toBeGreaterThanOrEqual(10); + + expect(1n).toBeGreaterThanOrEqual(1); + expect(1n).not.toBeGreaterThanOrEqual(Number.MAX_SAFE_INTEGER); + expect(1n).not.toBeGreaterThanOrEqual(Number.MAX_VALUE); + expect(1).toBeGreaterThanOrEqual(1n); + expect(Number.MAX_SAFE_INTEGER).toBeGreaterThanOrEqual(1n); + expect(Number.MAX_VALUE).toBeGreaterThanOrEqual(1n); + + expect(1).not.toBeGreaterThanOrEqual(BigInt(Number.MAX_VALUE)); +}); + +test("toBeLessThan()", () => { + expect(3n).not.toBeLessThan(2); + expect(Number.MAX_VALUE).not.toBeLessThan(Number.MAX_VALUE); + expect(1).toBeLessThan(BigInt(Number.MAX_VALUE)); + expect(1).toBeLessThan(Number.MAX_SAFE_INTEGER); + expect(1).toBeLessThan(BigInt(Number.MAX_SAFE_INTEGER)); + expect(Number.MAX_SAFE_INTEGER).not.toBeLessThan(Number.MAX_SAFE_INTEGER); + expect(BigInt(Number.MAX_SAFE_INTEGER)).not.toBeLessThan(BigInt(Number.MAX_SAFE_INTEGER)); + + expect(Number.MAX_VALUE).not.toBeLessThan(BigInt(Number.MAX_VALUE)); + + expect(NaN).not.toBeLessThan(NaN); + expect(NaN).not.toBeLessThan(-Infinity); + + expect(10).not.toBeLessThan(9); + expect(10).not.toBeLessThan(10); + expect(10).toBeLessThan(11); + expect(10).toBeLessThan(Infinity); + expect(10).not.toBeLessThan(-Infinity); + expect(10).not.toBeLessThan(NaN); + expect(10).not.toBeLessThan(0); + expect(10).not.toBeLessThan(-0); + expect(10).not.toBeLessThan(0.1); + expect(10).not.toBeLessThan(-0.1); + expect(10).not.toBeLessThan(0.9); + expect(10).not.toBeLessThan(-0.9); + expect(10).not.toBeLessThan(1); + expect(10).not.toBeLessThan(-1); + // switch the order + expect(9).toBeLessThan(10); + expect(10).not.toBeLessThan(10); + expect(11).not.toBeLessThan(10); + expect(Infinity).not.toBeLessThan(10); + expect(-Infinity).toBeLessThan(10); + expect(NaN).not.toBeLessThan(10); + expect(0).toBeLessThan(10); + expect(-0).toBeLessThan(10); + expect(0.1).toBeLessThan(10); + expect(-0.1).toBeLessThan(10); + expect(0.9).toBeLessThan(10); + expect(-0.9).toBeLessThan(10); + expect(1).toBeLessThan(10); + expect(-1).toBeLessThan(10); + + // same tests but use bigints + expect(10n).not.toBeLessThan(9n); + expect(10n).not.toBeLessThan(10n); + expect(10n).toBeLessThan(11n); + expect(10n).toBeLessThan(Infinity); + expect(10n).not.toBeLessThan(-Infinity); + expect(10n).not.toBeLessThan(NaN); + expect(10n).not.toBeLessThan(0n); + expect(10n).not.toBeLessThan(-0n); + expect(10n).not.toBeLessThan(1n); + expect(10n).not.toBeLessThan(-1n); + // switch the order + expect(9n).toBeLessThan(10n); + expect(10n).not.toBeLessThan(10n); + expect(11n).not.toBeLessThan(10n); + expect(Infinity).not.toBeLessThan(10n); + expect(-Infinity).toBeLessThan(10n); + expect(NaN).not.toBeLessThan(10n); + expect(0n).toBeLessThan(10n); + expect(-0n).toBeLessThan(10n); + expect(1n).toBeLessThan(10n); + expect(-1n).toBeLessThan(10n); + + // use bigints and numbers + expect(10n).not.toBeLessThan(9); + expect(10n).not.toBeLessThan(10); + expect(10n).toBeLessThan(11); + expect(10n).toBeLessThan(Infinity); + expect(10n).not.toBeLessThan(-Infinity); + expect(10n).not.toBeLessThan(NaN); + expect(10n).not.toBeLessThan(0); + expect(10n).not.toBeLessThan(-0); + expect(10n).not.toBeLessThan(0.1); + expect(10n).not.toBeLessThan(-0.1); + expect(10n).not.toBeLessThan(0.9); + expect(10n).not.toBeLessThan(-0.9); + expect(10n).not.toBeLessThan(1); + expect(10n).not.toBeLessThan(-1); + // switch the order + expect(9n).toBeLessThan(10); + expect(10n).not.toBeLessThan(10); + expect(11n).not.toBeLessThan(10); + expect(Infinity).not.toBeLessThan(10n); + expect(-Infinity).toBeLessThan(10n); + expect(NaN).not.toBeLessThan(10n); + expect(0n).toBeLessThan(10); + expect(-0n).toBeLessThan(10); + expect(1n).toBeLessThan(10); + expect(-1n).toBeLessThan(10); + + expect(1n).not.toBeLessThan(1); + expect(1n).toBeLessThan(Number.MAX_SAFE_INTEGER); + expect(1n).toBeLessThan(Number.MAX_VALUE); + expect(1).not.toBeLessThan(1n); + expect(Number.MAX_SAFE_INTEGER).not.toBeLessThan(1n); + expect(Number.MAX_VALUE).not.toBeLessThan(1n); + + expect(BigInt(Number.MAX_SAFE_INTEGER)).not.toBeLessThan(1n); + expect(BigInt(Number.MAX_VALUE)).not.toBeLessThan(1n); + expect(1n).toBeLessThan(BigInt(Number.MAX_SAFE_INTEGER)); + expect(1n).toBeLessThan(BigInt(Number.MAX_VALUE)); + + expect(BigInt(Number.MAX_SAFE_INTEGER)).not.toBeLessThan(1); + expect(BigInt(Number.MAX_VALUE)).not.toBeLessThan(1); + expect(1).toBeLessThan(BigInt(Number.MAX_SAFE_INTEGER)); +}); + +test("toBeLessThanOrEqual()", () => { + expect(3n).not.toBeLessThanOrEqual(2); + expect(Number.MAX_VALUE).toBeLessThanOrEqual(Number.MAX_VALUE); + expect(1).toBeLessThanOrEqual(BigInt(Number.MAX_VALUE)); + expect(1).toBeLessThanOrEqual(Number.MAX_SAFE_INTEGER); + expect(1).toBeLessThanOrEqual(BigInt(Number.MAX_SAFE_INTEGER)); + expect(Number.MAX_SAFE_INTEGER).toBeLessThanOrEqual(Number.MAX_SAFE_INTEGER); + expect(BigInt(Number.MAX_SAFE_INTEGER)).toBeLessThanOrEqual(BigInt(Number.MAX_SAFE_INTEGER)); + + expect(Number.MAX_VALUE).toBeLessThanOrEqual(BigInt(Number.MAX_VALUE)); + expect(BigInt(Number.MAX_VALUE)).toBeLessThanOrEqual(Number.MAX_VALUE); + + expect(NaN).not.toBeLessThanOrEqual(NaN); + expect(NaN).not.toBeLessThanOrEqual(-Infinity); + + expect(10).not.toBeLessThanOrEqual(9); + expect(10).toBeLessThanOrEqual(10); + expect(10).toBeLessThanOrEqual(11); + expect(10).toBeLessThanOrEqual(Infinity); + expect(10).not.toBeLessThanOrEqual(-Infinity); + expect(10).not.toBeLessThanOrEqual(NaN); + expect(10).not.toBeLessThanOrEqual(0); + expect(10).not.toBeLessThanOrEqual(-0); + expect(10).not.toBeLessThanOrEqual(0.1); + expect(10).not.toBeLessThanOrEqual(-0.1); + expect(10).not.toBeLessThanOrEqual(0.9); + expect(10).not.toBeLessThanOrEqual(-0.9); + expect(10).not.toBeLessThanOrEqual(1); + expect(10).not.toBeLessThanOrEqual(-1); + // switch the order + expect(9).toBeLessThanOrEqual(10); + expect(10).toBeLessThanOrEqual(10); + expect(11).not.toBeLessThanOrEqual(10); + expect(Infinity).not.toBeLessThanOrEqual(10); + expect(-Infinity).toBeLessThanOrEqual(10); + expect(NaN).not.toBeLessThanOrEqual(10); + expect(0).toBeLessThanOrEqual(10); + expect(-0).toBeLessThanOrEqual(10); + expect(0.1).toBeLessThanOrEqual(10); + expect(-0.1).toBeLessThanOrEqual(10); + expect(0.9).toBeLessThanOrEqual(10); + expect(-0.9).toBeLessThanOrEqual(10); + expect(1).toBeLessThanOrEqual(10); + expect(-1).toBeLessThanOrEqual(10); + + // same tests but use bigints + expect(10n).not.toBeLessThanOrEqual(9n); + expect(10n).toBeLessThanOrEqual(10n); + expect(10n).toBeLessThanOrEqual(11n); + expect(10n).toBeLessThanOrEqual(Infinity); + expect(10n).not.toBeLessThanOrEqual(-Infinity); + expect(10n).not.toBeLessThanOrEqual(NaN); + expect(10n).not.toBeLessThanOrEqual(0n); + expect(10n).not.toBeLessThanOrEqual(-0n); + expect(10n).not.toBeLessThanOrEqual(1n); + expect(10n).not.toBeLessThanOrEqual(-1n); + // switch the order + expect(9n).toBeLessThanOrEqual(10n); + expect(10n).toBeLessThanOrEqual(10n); + expect(11n).not.toBeLessThanOrEqual(10n); + expect(Infinity).not.toBeLessThanOrEqual(10n); + expect(-Infinity).toBeLessThanOrEqual(10n); + expect(NaN).not.toBeLessThanOrEqual(10n); + expect(0n).toBeLessThanOrEqual(10n); + expect(-0n).toBeLessThanOrEqual(10n); + expect(1n).toBeLessThanOrEqual(10n); + expect(-1n).toBeLessThanOrEqual(10n); + + // use bigints and numbers + expect(10n).not.toBeLessThanOrEqual(9); + expect(10n).toBeLessThanOrEqual(10); + expect(10n).toBeLessThanOrEqual(11); + expect(10n).toBeLessThanOrEqual(Infinity); + expect(10n).not.toBeLessThanOrEqual(-Infinity); + expect(10n).not.toBeLessThanOrEqual(NaN); + expect(10n).not.toBeLessThanOrEqual(0); + expect(10n).not.toBeLessThanOrEqual(-0); + expect(10n).not.toBeLessThanOrEqual(0.1); + expect(10n).not.toBeLessThanOrEqual(-0.1); + expect(10n).not.toBeLessThanOrEqual(0.9); + expect(10n).not.toBeLessThanOrEqual(-0.9); + expect(10n).not.toBeLessThanOrEqual(1); + expect(10n).not.toBeLessThanOrEqual(-1); + // switch the order + expect(9n).toBeLessThanOrEqual(10); + expect(10n).toBeLessThanOrEqual(10); + expect(11n).not.toBeLessThanOrEqual(10); + expect(Infinity).not.toBeLessThanOrEqual(10n); + expect(-Infinity).toBeLessThanOrEqual(10n); + expect(NaN).not.toBeLessThanOrEqual(10n); + expect(0n).toBeLessThanOrEqual(10); + expect(-0n).toBeLessThanOrEqual(10); + expect(1n).toBeLessThanOrEqual(10); + expect(-1n).toBeLessThanOrEqual(10); + + expect(1n).toBeLessThanOrEqual(1); + expect(1n).toBeLessThanOrEqual(Number.MAX_SAFE_INTEGER); + expect(1n).toBeLessThanOrEqual(Number.MAX_VALUE); + expect(1).toBeLessThanOrEqual(1n); + expect(Number.MAX_SAFE_INTEGER).not.toBeLessThanOrEqual(1n); + expect(Number.MAX_VALUE).not.toBeLessThanOrEqual(1n); + + expect(BigInt(Number.MAX_SAFE_INTEGER)).not.toBeLessThanOrEqual(1n); + expect(BigInt(Number.MAX_VALUE)).not.toBeLessThanOrEqual(1n); + expect(1n).toBeLessThanOrEqual(BigInt(Number.MAX_SAFE_INTEGER)); + expect(1n).toBeLessThanOrEqual(BigInt(Number.MAX_VALUE)); + + expect(BigInt(Number.MAX_SAFE_INTEGER)).not.toBeLessThanOrEqual(1); + expect(BigInt(Number.MAX_VALUE)).not.toBeLessThanOrEqual(1); + expect(1).toBeLessThanOrEqual(BigInt(Number.MAX_SAFE_INTEGER)); +}); + +try { + test("test this doesnt crash"); +} catch (e) {} + +try { + test(); +} catch (e) {} + +describe("throw in describe scope doesn't enqueue tests after thrown", () => { + it("test enqueued before a describe scope throws is never run", () => { + throw new Error("This test failed"); + }); + + class TestPass extends Error { + constructor(message) { + super(message); + this.name = "TestPass"; + } + } + + throw new TestPass("This test passed. Ignore the error message"); + + it("test enqueued after a describe scope throws is never run", () => { + throw new Error("This test failed"); + }); +}); + +it("a describe scope throwing doesn't cause all other tests in the file to fail", () => { + expect(true).toBe(true); +}); + +test("test async exceptions fail tests", () => { + const code = ` + import {test, expect} from 'bun:test'; + import {EventEmitter} from 'events'; + test('test throwing inside an EventEmitter fails the test', () => { + const emitter = new EventEmitter(); + emitter.on('event', () => { + throw new Error('test throwing inside an EventEmitter #FAIL001'); + }); + emitter.emit('event'); + }); + + test('test throwing inside a queueMicrotask callback fails', async () => { + + queueMicrotask(() => { + throw new Error('test throwing inside an EventEmitter #FAIL002'); + }); + + await 1; + }); + + test('test throwing inside a process.nextTick callback fails', async () => { + + process.nextTick(() => { + throw new Error('test throwing inside an EventEmitter #FAIL003'); + }); + + await 1; + }); + + test('test throwing inside a setTimeout', async () => { + await new Promise((resolve, reject) => { + setTimeout(() => { + resolve(); + throw new Error('test throwing inside an EventEmitter #FAIL004'); + }, 0); + }); + }); + + test('test throwing inside an async setTimeout', async () => { + await new Promise((resolve, reject) => { + setTimeout(async () => { + await 1; + resolve(); + throw new Error('test throwing inside an EventEmitter #FAIL005'); + }, 0); + }); + }); + + + test('test throwing inside an async setTimeout no await' , async () => { + await new Promise((resolve, reject) => { + setTimeout(async () => { + resolve(); + throw new Error('test throwing inside an EventEmitter #FAIL006'); + }, 0); + }); + }); + + `; + + rmSync("/tmp/test-throwing-bun/test-throwing-eventemitter.test.js", { + force: true, + }); + + try { + mkdirSync("/tmp/test-throwing-bun", { recursive: true }); + } catch (e) {} + writeFileSync("/tmp/test-throwing-bun/test-throwing-eventemitter.test.js", code); + + const { stderr, exitCode } = spawnSync([bunExe(), "wiptest", "test-throwing-eventemitter"], { + cwd: realpathSync("/tmp/test-throwing-bun"), + env: bunEnv, + }); + + const str = stderr!.toString(); + expect(str).toContain("#FAIL001"); + expect(str).toContain("#FAIL002"); + expect(str).toContain("#FAIL003"); + expect(str).toContain("#FAIL004"); + expect(str).toContain("#FAIL005"); + expect(str).toContain("#FAIL006"); + expect(str).toContain("6 fail"); + expect(str).toContain("0 pass"); + + expect(exitCode).toBe(1); +}); + +it("should return non-zero exit code for invalid syntax", async () => { + const test_dir = realpathSync(await mkdtemp(join(tmpdir(), "test"))); + try { + await writeFile(join(test_dir, "bad.test.js"), "!!!"); + const { stdout, stderr, exited } = spawn({ + cmd: [bunExe(), "test", "bad.test.js"], + cwd: test_dir, + stdout: null, + stdin: "pipe", + stderr: "pipe", + env: bunEnv, + }); + const err = await new Response(stderr).text(); + expect(err).toContain("error: Unexpected end of file"); + expect(err).toContain(" 0 pass"); + expect(err).toContain(" 1 fail"); + expect(err).toContain("Ran 1 tests across 1 files"); + expect(stdout).toBeDefined(); + expect(await new Response(stdout).text()).toBe(""); + expect(await exited).toBe(1); + } finally { + await rm(test_dir, { force: true, recursive: true }); + } +}); diff --git a/test/js/bun/util/arraybuffersink.test.ts b/test/js/bun/util/arraybuffersink.test.ts new file mode 100644 index 000000000..373ac1116 --- /dev/null +++ b/test/js/bun/util/arraybuffersink.test.ts @@ -0,0 +1,64 @@ +import { ArrayBufferSink } from "bun"; +import { describe, expect, it } from "bun:test"; +import { withoutAggressiveGC } from "harness"; + +describe("ArrayBufferSink", () => { + const fixtures = [ + [ + ["abcdefghijklmnopqrstuvwxyz"], + new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz"), + "abcdefghijklmnopqrstuvwxyz", + ], + [ + ["abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"], + new TextEncoder().encode("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + ], + [ + ["😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"], + new TextEncoder().encode("😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"), + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌", + ], + [ + ["abcdefghijklmnopqrstuvwxyz", "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"], + new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"), + "abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌", + ], + [ + ["abcdefghijklmnopqrstuvwxyz", "😋", " Get Emoji — All Emojis", " to ✂️ Copy and 📋 Paste 👌"], + new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"), + "(rope) " + "abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌", + ], + [ + [ + new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz"), + "😋", + " Get Emoji — All Emojis", + " to ✂️ Copy and 📋 Paste 👌", + ], + new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"), + "(array) " + "abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌", + ], + ] as const; + + for (const [input, expected, label] of fixtures) { + it(`${JSON.stringify(label)}`, () => { + const sink = new ArrayBufferSink(); + withoutAggressiveGC(() => { + for (let i = 0; i < input.length; i++) { + const el = input[i]; + if (typeof el !== "number") { + sink.write(el); + } + } + }); + const output = new Uint8Array(sink.end()); + withoutAggressiveGC(() => { + for (let i = 0; i < expected.length; i++) { + expect(output[i]).toBe(expected[i]); + } + }); + expect(output.byteLength).toBe(expected.byteLength); + }); + } +}); diff --git a/test/js/bun/util/concat.test.js b/test/js/bun/util/concat.test.js new file mode 100644 index 000000000..0cea303fe --- /dev/null +++ b/test/js/bun/util/concat.test.js @@ -0,0 +1,43 @@ +import { describe, it, expect } from "bun:test"; +import { concatArrayBuffers } from "bun"; + +describe("concat", () => { + function polyfill(chunks) { + var size = 0; + for (const chunk of chunks) { + size += chunk.byteLength; + } + var buffer = new ArrayBuffer(size); + var view = new Uint8Array(buffer); + var offset = 0; + for (const chunk of chunks) { + view.set(chunk, offset); + offset += chunk.byteLength; + } + return buffer; + } + + function concatToString(chunks) { + return Array.from(new Uint8Array(concatArrayBuffers(chunks))).join(""); + } + + function polyfillToString(chunks) { + return Array.from(new Uint8Array(polyfill(chunks))).join(""); + } + + it("works with one element", () => { + expect(concatToString([new Uint8Array([123])])).toBe(polyfillToString([new Uint8Array([123])])); + }); + + it("works with two elements", () => { + expect(concatToString([Uint8Array.from([123]), Uint8Array.from([456])])).toBe( + polyfillToString([Uint8Array.from([123]), Uint8Array.from([456])]), + ); + }); + + it("works with mix of ArrayBuffer and TypedArray elements", () => { + expect(concatToString([Uint8Array.from([123]).buffer, Uint8Array.from([456])])).toBe( + polyfillToString([Uint8Array.from([123]), Uint8Array.from([456])]), + ); + }); +}); diff --git a/test/js/bun/util/empty.js b/test/js/bun/util/empty.js new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/test/js/bun/util/empty.js @@ -0,0 +1 @@ +export {}; diff --git a/test/js/bun/util/escapeHTML.test.js b/test/js/bun/util/escapeHTML.test.js new file mode 100644 index 000000000..bdddf4a45 --- /dev/null +++ b/test/js/bun/util/escapeHTML.test.js @@ -0,0 +1,91 @@ +import { describe, it, expect } from "bun:test"; +import { escapeHTML } from "bun"; + +describe("escapeHTML", () => { + // The matrix of cases we need to test for: + // 1. Works with short strings + // 2. Works with long strings + // 3. Works with latin1 strings + // 4. Works with utf16 strings + // 5. Works when the text to escape is somewhere in the middle + // 6. Works when the text to escape is in the beginning + // 7. Works when the text to escape is in the end + // 8. Returns the same string when there's no need to escape + it("works", () => { + expect(escapeHTML("absolutely nothing to do here")).toBe("absolutely nothing to do here"); + expect(escapeHTML("")).toBe("<script>alert(1)</script>"); + expect(escapeHTML("<")).toBe("<"); + expect(escapeHTML(">")).toBe(">"); + expect(escapeHTML("&")).toBe("&"); + expect(escapeHTML("'")).toBe("'"); + expect(escapeHTML('"')).toBe("""); + expect(escapeHTML("\n")).toBe("\n"); + expect(escapeHTML("\r")).toBe("\r"); + expect(escapeHTML("\t")).toBe("\t"); + expect(escapeHTML("\f")).toBe("\f"); + expect(escapeHTML("\v")).toBe("\v"); + expect(escapeHTML("\b")).toBe("\b"); + expect(escapeHTML("\u00A0")).toBe("\u00A0"); + expect(escapeHTML("" + "lalala")).toBe( + "lalala<script>alert(1)</script>lalala", + ); + + expect(escapeHTML("" + "lalala")).toBe("<script>alert(1)</script>lalala"); + expect(escapeHTML("lalala" + "")).toBe("lalala" + "<script>alert(1)</script>"); + + expect(escapeHTML("What does 😊 mean?")).toBe("What does 😊 mean?"); + const output = escapeHTML("What does 😊 mean in text?")).toBe("<div>What does 😊 mean in text?"); + + expect(escapeHTML(("lalala" + "" + "lalala").repeat(900))).toBe( + "lalala<script>alert(1)</script>lalala".repeat(900), + ); + expect(escapeHTML(("" + "lalala").repeat(900))).toBe( + "<script>alert(1)</script>lalala".repeat(900), + ); + expect(escapeHTML(("lalala" + "").repeat(900))).toBe( + ("lalala" + "<script>alert(1)</script>").repeat(900), + ); + + // the positions of the unicode codepoint are important + // our simd code for U16 is at 8 bytes, so we need to especially check the boundaries + expect(escapeHTML("😊lalala" + "" + "lalala")).toBe( + "😊lalala<script>alert(1)</script>lalala", + ); + expect(escapeHTML("" + "lalala")).toBe("<script>😊alert(1)</script>lalala"); + expect(escapeHTML("" + "lalala")).toBe("<script>alert(1)😊</script>lalala"); + expect(escapeHTML("" + "😊lalala")).toBe("<script>alert(1)</script>😊lalala"); + expect(escapeHTML("" + "lal😊ala")).toBe("<script>alert(1)</script>lal😊ala"); + expect(escapeHTML("" + "lal😊ala".repeat(10))).toBe( + "<script>alert(1)</script>" + "lal😊ala".repeat(10), + ); + + for (let i = 1; i < 10; i++) + expect(escapeHTML("" + "la😊".repeat(i))).toBe( + "<script>alert(1)</script>" + "la😊".repeat(i), + ); + + expect(escapeHTML("la😊" + "")).toBe("la😊" + "<script>alert(1)</script>"); + expect(escapeHTML(("lalala" + "😊").repeat(1))).toBe( + ("lalala" + "<script>alert(1)</script>😊").repeat(1), + ); + + expect(escapeHTML("😊".repeat(100))).toBe("😊".repeat(100)); + expect(escapeHTML("😊<".repeat(100))).toBe("😊<".repeat(100)); + expect(escapeHTML("<😊>".repeat(100))).toBe("<😊>".repeat(100)); + expect(escapeHTML("😊")).toBe("😊"); + expect(escapeHTML("😊😊")).toBe("😊😊"); + expect(escapeHTML("😊lo")).toBe("😊lo"); + expect(escapeHTML("lo😊")).toBe("lo😊"); + + expect(escapeHTML(" ".repeat(32) + "😊")).toBe(" ".repeat(32) + "😊"); + expect(escapeHTML(" ".repeat(32) + "😊😊")).toBe(" ".repeat(32) + "😊😊"); + expect(escapeHTML(" ".repeat(32) + "😊lo")).toBe(" ".repeat(32) + "😊lo"); + expect(escapeHTML(" ".repeat(32) + "lo😊")).toBe(" ".repeat(32) + "lo😊"); + }); +}); diff --git a/test/js/bun/util/fileUrl.test.js b/test/js/bun/util/fileUrl.test.js new file mode 100644 index 000000000..ebae570f8 --- /dev/null +++ b/test/js/bun/util/fileUrl.test.js @@ -0,0 +1,16 @@ +import { expect, it, describe } from "bun:test"; +import { pathToFileURL, fileURLToPath } from "bun"; +describe("pathToFileURL", () => { + it("should convert a path to a file url", () => { + expect(pathToFileURL("/path/to/file.js").href).toBe("file:///path/to/file.js"); + }); +}); + +describe("fileURLToPath", () => { + it("should convert a file url to a path", () => { + expect(fileURLToPath("file:///path/to/file.js")).toBe("/path/to/file.js"); + }); + it("should convert a URL to a path", () => { + expect(fileURLToPath(new URL("file:///path/to/file.js"))).toBe("/path/to/file.js"); + }); +}); diff --git a/test/js/bun/util/filesink.test.ts b/test/js/bun/util/filesink.test.ts new file mode 100644 index 000000000..31fd70e54 --- /dev/null +++ b/test/js/bun/util/filesink.test.ts @@ -0,0 +1,141 @@ +import { ArrayBufferSink } from "bun"; +import { describe, expect, it } from "bun:test"; +import { mkfifo } from "mkfifo"; + +describe("FileSink", () => { + const fixtures = [ + [ + ["abcdefghijklmnopqrstuvwxyz"], + new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz"), + "abcdefghijklmnopqrstuvwxyz", + ], + [ + ["abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"], + new TextEncoder().encode("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + ], + [ + ["😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"], + new TextEncoder().encode("😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"), + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌", + ], + [ + ["abcdefghijklmnopqrstuvwxyz", "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"], + new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"), + "abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌", + ], + [ + ["abcdefghijklmnopqrstuvwxyz", "😋", " Get Emoji — All Emojis", " to ✂️ Copy and 📋 Paste 👌"], + new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"), + "(rope) " + "abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌", + ], + [ + [ + new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz"), + "😋", + " Get Emoji — All Emojis", + " to ✂️ Copy and 📋 Paste 👌", + ], + new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"), + "(array) " + "abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌", + ], + ] as const; + + function getPath(label) { + const path = `/tmp/bun-test-${Bun.hash(label).toString(10)}.txt`; + try { + require("fs").unlinkSync(path); + } catch (e) {} + return path; + } + + var activeFIFO: Promise; + var decoder = new TextDecoder(); + + function getFd(label) { + const path = `/tmp/bun-test-${Bun.hash(label).toString(10)}.txt`; + try { + require("fs").unlinkSync(path); + } catch (e) {} + mkfifo(path, 0o666); + activeFIFO = (async function (stream: ReadableStream) { + var chunks: Uint8Array[] = []; + for await (const chunk of stream) { + chunks.push(chunk); + } + return Buffer.concat(chunks).toString(); + // test it on a small chunk size + })(Bun.file(path).stream(64)); + return path; + } + + for (let isPipe of [true, false] as const) { + describe(isPipe ? "pipe" : "file", () => { + for (const [input, expected, label] of fixtures) { + var getPathOrFd = () => (isPipe ? getFd(label) : getPath(label)); + + it(`${JSON.stringify(label)}`, async () => { + const path = getPathOrFd(); + const sink = Bun.file(path).writer(); + for (let i = 0; i < input.length; i++) { + sink.write(input[i]); + } + await sink.end(); + + if (!isPipe) { + const output = new Uint8Array(await Bun.file(path).arrayBuffer()); + for (let i = 0; i < expected.length; i++) { + expect(output[i]).toBe(expected[i]); + } + expect(output.byteLength).toBe(expected.byteLength); + } else { + console.log("reading"); + const output = await activeFIFO; + expect(output).toBe(decoder.decode(expected)); + } + }); + + it(`flushing -> ${JSON.stringify(label)}`, async () => { + const path = getPathOrFd(); + const sink = Bun.file(path).writer(); + for (let i = 0; i < input.length; i++) { + sink.write(input[i]); + await sink.flush(); + } + await sink.end(); + if (!isPipe) { + const output = new Uint8Array(await Bun.file(path).arrayBuffer()); + for (let i = 0; i < expected.length; i++) { + expect(output[i]).toBe(expected[i]); + } + expect(output.byteLength).toBe(expected.byteLength); + } else { + const output = await activeFIFO; + expect(output).toBe(decoder.decode(expected)); + } + }); + + it(`highWaterMark -> ${JSON.stringify(label)}`, async () => { + const path = getPathOrFd(); + const sink = Bun.file(path).writer({ highWaterMark: 1 }); + for (let i = 0; i < input.length; i++) { + sink.write(input[i]); + await sink.flush(); + } + await sink.end(); + + if (!isPipe) { + const output = new Uint8Array(await Bun.file(path).arrayBuffer()); + for (let i = 0; i < expected.length; i++) { + expect(output[i]).toBe(expected[i]); + } + expect(output.byteLength).toBe(expected.byteLength); + } else { + const output = await activeFIFO; + expect(output).toBe(decoder.decode(expected)); + } + }); + } + }); + } +}); diff --git a/test/js/bun/util/filesystem_router.test.ts b/test/js/bun/util/filesystem_router.test.ts new file mode 100644 index 000000000..b55e716c0 --- /dev/null +++ b/test/js/bun/util/filesystem_router.test.ts @@ -0,0 +1,354 @@ +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()) + "/"; + +function createTree(basedir, paths) { + for (const end of paths) { + const abs = path.join(basedir, end); + try { + const dir = dirname(abs); + if (dir.length > 0 && dir !== "/") fs.mkdirSync(dir, { recursive: true }); + } catch (e) {} + fs.writeFileSync(abs, "export default " + JSON.stringify(end) + ";\n"); + } +} +var count = 0; +function make(files) { + const dir = tempdir + `fs-router-test-${count++}`; + rmSync(dir, { + recursive: true, + force: true, + }); + + createTree(dir, files); + if (files.length === 0) mkdirSync(dir, { recursive: true }); + return { + dir, + }; +} + +it("should find files", () => { + const { dir } = make([ + `index.tsx`, + `[id].tsx`, + `a.tsx`, + `abc/index.tsx`, + `abc/[id].tsx`, + `abc/def/[id].tsx`, + `abc/def/ghi/index.tsx`, + `abc/def/ghi/[id].tsx`, + `abc/def/ghi/jkl/index.tsx`, + `abc/def/ghi/jkl/[id].tsx`, + `abc/def/index.tsx`, + `b.tsx`, + `foo/[id].tsx`, + `catch-all/[[...id]].tsx`, + ]); + + const router = new FileSystemRouter({ + dir, + fileExtensions: [".tsx"], + style: "nextjs", + }); + + const routes = router.routes; + const fixture = { + "/": `${dir}/index.tsx`, + "/[id]": `${dir}/[id].tsx`, + "/a": `${dir}/a.tsx`, + "/abc": `${dir}/abc/index.tsx`, + "/abc/[id]": `${dir}/abc/[id].tsx`, + "/abc/def/[id]": `${dir}/abc/def/[id].tsx`, + "/abc/def/ghi": `${dir}/abc/def/ghi/index.tsx`, + "/abc/def/ghi/[id]": `${dir}/abc/def/ghi/[id].tsx`, + "/abc/def/ghi/jkl": `${dir}/abc/def/ghi/jkl/index.tsx`, + "/abc/def/ghi/jkl/[id]": `${dir}/abc/def/ghi/jkl/[id].tsx`, + "/abc/def": `${dir}/abc/def/index.tsx`, + "/b": `${dir}/b.tsx`, + "/foo/[id]": `${dir}/foo/[id].tsx`, + "/catch-all/[[...id]]": `${dir}/catch-all/[[...id]].tsx`, + }; + + for (const route in fixture) { + if (!(route in routes)) { + throw new Error(`Route ${route} not found`); + } + + expect(routes[route]).toBe(fixture[route]); + } + + expect(Object.keys(routes).length).toBe(Object.keys(fixture).length); + expect(Object.values(routes).length).toBe(Object.values(fixture).length); +}); + +it("should handle empty dirs", () => { + const { dir } = make([]); + + const router = new FileSystemRouter({ + dir, + fileExtensions: [".tsx"], + style: "nextjs", + }); + + // assert this doesn't crash + expect(router.bar).toBeUndefined(); + + const routes = router.routes; + expect(Object.keys(routes).length).toBe(0); + expect(Object.values(routes).length).toBe(0); +}); + +it("should match dynamic routes", () => { + // set up the test + const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx"]); + + const router = new Bun.FileSystemRouter({ + dir, + style: "nextjs", + }); + + const { name, filePath } = router.match("/posts/hello-world"); + + expect(name).toBe("/posts/[id]"); + expect(filePath).toBe(`${dir}/posts/[id].tsx`); +}); + +it(".params works on dynamic routes", () => { + // set up the test + const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx"]); + + const router = new Bun.FileSystemRouter({ + dir, + style: "nextjs", + }); + + const { + params: { id }, + } = router.match("/posts/hello-world"); + + expect(id).toBe("hello-world"); +}); + +it("should support static routes", () => { + // set up the test + const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx", "posts/hey.tsx"]); + + const router = new Bun.FileSystemRouter({ + dir, + style: "nextjs", + }); + + const { name, params, filePath } = router.match("/posts/hey"); + + expect(name).toBe("/posts/hey"); + expect(filePath).toBe(`${dir}/posts/hey.tsx`); +}); + +it("should support optional catch-all routes", () => { + // set up the test + const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx", "posts/hey.tsx", "posts/[[...id]].tsx"]); + + const router = new Bun.FileSystemRouter({ + dir, + style: "nextjs", + }); + + for (let fixture of ["/posts/123", "/posts/hey", "/posts/zorp", "/posts", "/index", "/posts/"]) { + expect(router.match(fixture)?.name).not.toBe("/posts/[[...id]]"); + } + + for (let fixture of ["/posts/hey/there", "/posts/hey/there/you", "/posts/zorp/123"]) { + const { name, params, filePath } = router.match(fixture); + + expect(name).toBe("/posts/[[...id]]"); + expect(filePath).toBe(`${dir}/posts/[[...id]].tsx`); + expect(params.id).toBe(fixture.split("/").slice(2).join("/")); + } +}); + +it("should support catch-all routes", () => { + // set up the test + const { dir } = make([ + "index.tsx", + "posts/[id].tsx", + "posts.tsx", + "posts/hey.tsx", + "posts/[...id].tsx", + "posts/wow/[[...id]].tsx", + ]); + + const router = new Bun.FileSystemRouter({ + dir, + style: "nextjs", + }); + + for (let fixture of ["/posts/123", "/posts/hey", "/posts/zorp", "/posts", "/index", "/posts/"]) { + expect(router.match(fixture)?.name).not.toBe("/posts/[...id]"); + } + + for (let fixture of ["/posts/hey/there", "/posts/hey/there/you", "/posts/zorp/123", "/posts/wow/hey/there"]) { + const { name, params, filePath } = router.match(fixture); + + expect(name).toBe("/posts/[...id]"); + expect(filePath).toBe(`${dir}/posts/[...id].tsx`); + expect(params.id).toBe(fixture.split("/").slice(2).join("/")); + } +}); + +it("should support index routes", () => { + // set up the test + const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx", "posts/hey.tsx"]); + + const router = new Bun.FileSystemRouter({ + dir, + style: "nextjs", + }); + + for (let route of ["/", "/index"]) { + const { name, params, filePath } = router.match(route); + + expect(name).toBe("/"); + expect(filePath).toBe(`${dir}/index.tsx`); + expect(Object.keys(params).length).toBe(0); + } + + for (let route of ["/posts", "/posts/index", "/posts/"]) { + const { name, params, filePath } = router.match(route); + + expect(name).toBe("/posts"); + expect(filePath).toBe(`${dir}/posts.tsx`); + expect(Object.keys(params).length).toBe(0); + } +}); + +it("should support Request", async () => { + // set up the test + const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx"]); + + const router = new Bun.FileSystemRouter({ + dir, + style: "nextjs", + }); + + for (let current of [ + new Request({ url: "/posts/hello-world" }), + new Request({ url: "http://example.com/posts/hello-world" }), + ]) { + const { + name, + params: { id }, + filePath, + } = router.match(current); + expect(name).toBe("/posts/[id]"); + expect(filePath).toBe(`${dir}/posts/[id].tsx`); + expect(id).toBe("hello-world"); + } +}); + +it("assetPrefix, src, and origin", async () => { + // set up the test + const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx"]); + + const router = new Bun.FileSystemRouter({ + dir, + style: "nextjs", + assetPrefix: "/_next/static/", + origin: "https://nextjs.org", + }); + + for (let current of [ + // Reuqest + new Request({ url: "/posts/hello-world" }), + new Request({ url: "https://nextjs.org/posts/hello-world" }), + ]) { + const { name, src, filePath, checkThisDoesntCrash } = router.match(current); + expect(name).toBe("/posts/[id]"); + + // check nothing is weird on the MatchedRoute object + expect(checkThisDoesntCrash).toBeUndefined(); + + expect(src).toBe("https://nextjs.org/_next/static/posts/[id].tsx"); + expect(filePath).toBe(`${dir}/posts/[id].tsx`); + } +}); + +it(".query works", () => { + // set up the test + const { dir } = make(["posts.tsx"]); + + const router = new Bun.FileSystemRouter({ + dir, + style: "nextjs", + assetPrefix: "/_next/static/", + origin: "https://nextjs.org", + }); + + for (let [current, object] of [ + [new URL("https://example.com/posts?hello=world").href, { hello: "world" }], + [new URL("https://example.com/posts?hello=world&second=2").href, { hello: "world", second: "2" }], + [ + new URL("https://example.com/posts?hello=world&second=2&third=3").href, + { hello: "world", second: "2", third: "3" }, + ], + [new URL("https://example.com/posts").href, {}], + ]) { + const { name, src, filePath, checkThisDoesntCrash, query } = router.match(current); + expect(name).toBe("/posts"); + + // check nothing is weird on the MatchedRoute object + expect(checkThisDoesntCrash).toBeUndefined(); + + expect(JSON.stringify(query)).toBe(JSON.stringify(object)); + expect(filePath).toBe(`${dir}/posts.tsx`); + } +}); + +it("reload() works", () => { + // set up the test + const { dir } = make(["posts.tsx"]); + + const router = new Bun.FileSystemRouter({ + dir, + style: "nextjs", + assetPrefix: "/_next/static/", + origin: "https://nextjs.org", + }); + + expect(router.match("/posts").name).toBe("/posts"); + router.reload(); + expect(router.match("/posts").name).toBe("/posts"); +}); + +it(".query works with dynamic routes, including params", () => { + // set up the test + const { dir } = make(["posts/[id].tsx"]); + + const router = new Bun.FileSystemRouter({ + dir, + style: "nextjs", + assetPrefix: "/_next/static/", + origin: "https://nextjs.org", + }); + + for (let [current, object] of [ + [new URL("https://example.com/posts/123?hello=world").href, { id: "123", hello: "world" }], + [new URL("https://example.com/posts/123?hello=world&second=2").href, { id: "123", hello: "world", second: "2" }], + [ + new URL("https://example.com/posts/123?hello=world&second=2&third=3").href, + { id: "123", hello: "world", second: "2", third: "3" }, + ], + [new URL("https://example.com/posts/123").href, { id: "123" }], + ]) { + const { name, src, filePath, checkThisDoesntCrash, query } = router.match(current); + expect(name).toBe("/posts/[id]"); + + // check nothing is weird on the MatchedRoute object + expect(checkThisDoesntCrash).toBeUndefined(); + + expect(JSON.stringify(query)).toBe(JSON.stringify(object)); + expect(filePath).toBe(`${dir}/posts/[id].tsx`); + } +}); diff --git a/test/js/bun/util/hash.test.js b/test/js/bun/util/hash.test.js new file mode 100644 index 000000000..87a5a9ce3 --- /dev/null +++ b/test/js/bun/util/hash.test.js @@ -0,0 +1,47 @@ +import fs from "fs"; +import { it, expect } from "bun:test"; +import path from "path"; +import { gcTick } from "harness"; + +it(`Bun.hash()`, () => { + gcTick(); + Bun.hash("hello world"); + Bun.hash(new TextEncoder().encode("hello world")); +}); +it(`Bun.hash.wyhash()`, () => { + Bun.hash.wyhash("hello world"); + gcTick(); + Bun.hash.wyhash(new TextEncoder().encode("hello world")); +}); +it(`Bun.hash.adler32()`, () => { + Bun.hash.adler32("hello world"); + gcTick(); + Bun.hash.adler32(new TextEncoder().encode("hello world")); +}); +it(`Bun.hash.crc32()`, () => { + Bun.hash.crc32("hello world"); + gcTick(); + Bun.hash.crc32(new TextEncoder().encode("hello world")); +}); +it(`Bun.hash.cityHash32()`, () => { + Bun.hash.cityHash32("hello world"); + gcTick(); + Bun.hash.cityHash32(new TextEncoder().encode("hello world")); + gcTick(); +}); +it(`Bun.hash.cityHash64()`, () => { + Bun.hash.cityHash64("hello world"); + gcTick(); + Bun.hash.cityHash64(new TextEncoder().encode("hello world")); + gcTick(); +}); +it(`Bun.hash.murmur32v3()`, () => { + Bun.hash.murmur32v3("hello world"); + gcTick(); + Bun.hash.murmur32v3(new TextEncoder().encode("hello world")); +}); +it(`Bun.hash.murmur64v2()`, () => { + Bun.hash.murmur64v2("hello world"); + gcTick(); + Bun.hash.murmur64v2(new TextEncoder().encode("hello world")); +}); diff --git a/test/js/bun/util/index-of-line.test.ts b/test/js/bun/util/index-of-line.test.ts new file mode 100644 index 000000000..1c6cbaea2 --- /dev/null +++ b/test/js/bun/util/index-of-line.test.ts @@ -0,0 +1,37 @@ +import { expect, test } from "bun:test"; +import { indexOfLine } from "bun"; + +test("indexOfLine", () => { + const source = ` + const a = 1; + + const b = 2; + + 😋const c = 3; // handles unicode + + 😋 Get Emoji — All Emojis to ✂️ + + const b = 2; + + const c = 3; +`; + var i = 0; + var j = 0; + const buffer = Buffer.from(source); + var nonEmptyLineCount = 0; + while (i < buffer.length) { + const prev = j; + j = source.indexOf("\n", j); + i = indexOfLine(buffer, i); + + const delta = Buffer.byteLength(source.slice(0, j), "utf8") - j; + console.log(source.slice(prev + 1, j)); + if (i === -1) { + expect(j).toBe(-1); + expect(nonEmptyLineCount).toBe(6); + break; + } + expect(i++ - delta).toBe(j++); + nonEmptyLineCount++; + } +}); diff --git a/test/js/bun/util/inspect.test.js b/test/js/bun/util/inspect.test.js new file mode 100644 index 000000000..ec2fb0b45 --- /dev/null +++ b/test/js/bun/util/inspect.test.js @@ -0,0 +1,315 @@ +import { it, expect, describe } from "bun:test"; + +it("Timeout", () => { + const id = setTimeout(() => {}, 0); + expect(Bun.inspect(id)).toBe(`Timeout (#${+id})`); + + const id2 = setInterval(() => {}, 1); + id2.unref(); + expect(Bun.inspect(id2)).toBe(`Timeout (#${+id2}, repeats)`); +}); + +it("when prototype defines the same property, don't print the same property twice", () => { + var base = { + foo: "123", + }; + var obj = Object.create(base); + obj.foo = "456"; + expect(Bun.inspect(obj).trim()).toBe('{\n foo: "456"\n}'.trim()); +}); + +it("Blob inspect", () => { + expect(Bun.inspect(new Blob(["123"]))).toBe(`Blob (3 bytes)`); + expect(Bun.inspect(new Blob(["123".repeat(900)]))).toBe(`Blob (2.70 KB)`); + expect(Bun.inspect(Bun.file("/tmp/file.txt"))).toBe(`FileRef ("/tmp/file.txt") { + type: "text/plain;charset=utf-8" +}`); + expect(Bun.inspect(Bun.file(123))).toBe(`FileRef (fd: 123) { + type: "application/octet-stream" +}`); + expect(Bun.inspect(new Response(new Blob()))).toBe(`Response (0 KB) { + ok: true, + url: "", + statusText: "", + redirected: false, + bodyUsed: false, + status: 200, + [Blob detached] +}`); + expect(Bun.inspect(new Response("Hello"))).toBe(`Response (5 bytes) { + ok: true, + url: "", + statusText: "", + redirected: false, + bodyUsed: false, + status: 200, + Blob (5 bytes) +}`); +}); + +it.skip("utf16 property name", () => { + var { Database } = require("bun:sqlite"); + const db = Database.open(":memory:"); + expect("笑".codePointAt(0)).toBe(31505); + + // latin1 escaping identifier issue + expect(Object.keys({ 笑: "hey" })[0].codePointAt(0)).toBe(31505); + + const output = JSON.stringify( + [ + { + 笑: "😀", + }, + ], + null, + 2, + ); + expect(Bun.inspect(db.prepare("select '😀' as 笑").all())).toBe(output); +}); + +it("latin1", () => { + expect(Bun.inspect("English")).toBe("English"); + expect(Bun.inspect("Français")).toBe("Français"); + expect(Bun.inspect("Ελληνική")).toBe("Ελληνική"); + expect(Bun.inspect("日本語")).toBe("日本語"); + expect(Bun.inspect("Emoji😎")).toBe("Emoji😎"); + expect(Bun.inspect("Français / Ελληνική")).toBe("Français / Ελληνική"); +}); + +it("Request object", () => { + expect(Bun.inspect(new Request({ url: "https://example.com" })).trim()).toBe( + ` +Request (0 KB) { + method: "GET", + url: "https://example.com" +}`.trim(), + ); +}); + +it("MessageEvent", () => { + expect(Bun.inspect(new MessageEvent("message", { data: 123 }))).toBe( + `MessageEvent { + type: "message", + data: 123 +}`, + ); +}); + +// https://github.com/oven-sh/bun/issues/561 +it("TypedArray prints", () => { + for (let TypedArray of [ + Uint8Array, + Uint16Array, + Uint32Array, + Uint8ClampedArray, + Int8Array, + Int16Array, + Int32Array, + Float32Array, + Float64Array, + ]) { + const buffer = new TypedArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + const input = Bun.inspect(buffer); + + expect(input).toBe(`${TypedArray.name}(${buffer.length}) [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]`); + for (let i = 1; i < buffer.length + 1; i++) { + expect(Bun.inspect(buffer.subarray(i))).toBe( + `${TypedArray.name}(${buffer.length - i}) [ ` + [...buffer.subarray(i)].join(", ") + " ]", + ); + } + } +}); + +it("BigIntArray", () => { + for (let TypedArray of [BigInt64Array, BigUint64Array]) { + const buffer = new TypedArray([1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 10n]); + const input = Bun.inspect(buffer); + + expect(input).toBe(`${TypedArray.name}(${buffer.length}) [ 1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 10n ]`); + for (let i = 1; i < buffer.length + 1; i++) { + expect(Bun.inspect(buffer.subarray(i))).toBe( + `${TypedArray.name}(${buffer.length - i}) [ ` + + [...buffer.subarray(i)].map(a => a.toString(10) + "n").join(", ") + + " ]", + ); + } + } +}); + +it("FloatArray", () => { + for (let TypedArray of [Float32Array, Float64Array]) { + const buffer = new TypedArray([Math.fround(42.68)]); + const input = Bun.inspect(buffer); + + expect(input).toBe(`${TypedArray.name}(${buffer.length}) [ ${[Math.fround(42.68)].join(", ")} ]`); + for (let i = 1; i < buffer.length + 1; i++) { + expect(Bun.inspect(buffer.subarray(i))).toBe( + `${TypedArray.name}(${buffer.length - i}) [ ` + [...buffer.subarray(i)].join(", ") + " ]", + ); + } + } +}); + +it("jsx with two elements", () => { + const input = Bun.inspect( +
+ + string inside child +
, + ); + + const output = `
+ + string inside child +
`; + + expect(input).toBe(output); +}); + +const Foo = () =>
foo
; + +it("jsx with anon component", () => { + const input = Bun.inspect(); + + const output = ``; + + expect(input).toBe(output); +}); + +it("jsx with fragment", () => { + const input = Bun.inspect(<>foo bar); + + const output = `<>foo bar`; + + expect(input).toBe(output); +}); + +it("inspect", () => { + expect(Bun.inspect(new TypeError("what")).includes("TypeError: what")).toBe(true); + expect("hi").toBe("hi"); + expect(Bun.inspect(1)).toBe("1"); + expect(Bun.inspect(NaN)).toBe("NaN"); + expect(Bun.inspect(Infinity)).toBe("Infinity"); + expect(Bun.inspect(-Infinity)).toBe("-Infinity"); + expect(Bun.inspect(1, "hi")).toBe("1 hi"); + expect(Bun.inspect([])).toBe("[]"); + expect(Bun.inspect({})).toBe("{}"); + expect(Bun.inspect({ hello: 1 })).toBe("{\n hello: 1\n}"); + expect(Bun.inspect({ hello: 1, there: 2 })).toBe("{\n hello: 1,\n there: 2\n}"); + expect(Bun.inspect({ hello: "1", there: 2 })).toBe('{\n hello: "1",\n there: 2\n}'); + expect(Bun.inspect({ 'hello-"there': "1", there: 2 })).toBe('{\n "hello-\\"there": "1",\n there: 2\n}'); + var str = "123"; + while (str.length < 4096) { + str += "123"; + } + expect(Bun.inspect(str)).toBe(str); + // expect(Bun.inspect(new Headers())).toBe("Headers (0 KB) {}"); + expect(Bun.inspect(new Response()).length > 0).toBe(true); + // expect( + // JSON.stringify( + // new Headers({ + // hi: "ok", + // }) + // ) + // ).toBe('{"hi":"ok"}'); + expect(Bun.inspect(new Set())).toBe("Set {}"); + expect(Bun.inspect(new Map())).toBe("Map {}"); + expect(Bun.inspect(new Map([["foo", "bar"]]))).toBe('Map(1) {\n "foo": "bar",\n}'); + expect(Bun.inspect(new Set(["bar"]))).toBe('Set(1) {\n "bar",\n}'); + expect(Bun.inspect(
foo
)).toBe("
foo
"); + expect(Bun.inspect(
foo
)).toBe("
foo
"); + expect(Bun.inspect(
foo
)).toBe("
foo
"); + expect(Bun.inspect(
hi
)).toBe("
hi
"); + expect(Bun.inspect(
quoted
)).toBe('
quoted
'); + expect( + Bun.inspect( +
+ +
, + ), + ).toBe( + ` +
+ +
`.trim(), + ); + expect(Bun.inspect(BigInt(32))).toBe("32n"); +}); + +describe("latin1 supplemental", () => { + const fixture = [ + [["äbc"], '[ "äbc" ]'], + [["cbä"], '[ "cbä" ]'], + [["cäb"], '[ "cäb" ]'], + [["äbc äbc"], '[ "äbc äbc" ]'], + [["cbä cbä"], '[ "cbä cbä" ]'], + [["cäb cäb"], '[ "cäb cäb" ]'], + ]; + + for (let [input, output] of fixture) { + it(`latin1 (input) \"${input}\" ${output}`, () => { + expect(Bun.inspect(input)).toBe(output); + }); + it(`latin1 (output) \"${output}\"`, () => { + expect(Bun.inspect(output)).toBe(output); + }); + // this test is failing: + // it(`latin1 (property key)`, () => { + // expect( + // Object.keys({ + // ä: 1, + // })[0].codePointAt(0), + // ).toBe(228); + // }); + } +}); + +const fixture = [ + () => globalThis, + () => Bun.file("/tmp/log.txt").stream(), + () => Bun.file("/tmp/log.1.txt").stream().getReader(), + () => Bun.file("/tmp/log.2.txt").writer(), + () => + new WritableStream({ + write(chunk) {}, + }), + () => require("events"), + () => { + return new (import.meta.require("events").EventEmitter)(); + }, + async () => await import("node:assert"), + async () => await import("../../empty.js.js"), + () => import.meta.require("./empty.js"), + () => new Proxy({ yolo: 1 }, {}), + () => + new Proxy( + { yolo: 1 }, + { + get(target, prop) { + return prop + "!"; + }, + has(target, prop) { + return true; + }, + ownKeys() { + return ["foo"]; + }, + }, + ), +]; + +describe("crash testing", () => { + for (let input of fixture) { + it(`inspecting "${input.toString().slice(0, 20).replaceAll("\n", "\\n")}" doesn't crash`, async () => { + try { + Bun.inspect(await input()); + } catch (e) { + // this can throw its fine + } + }); + } +}); + +it("possibly formatted emojis log", () => { + expect(Bun.inspect("✔", "hey")).toBe("✔ hey"); +}); diff --git a/test/js/bun/util/mmap.test.js b/test/js/bun/util/mmap.test.js new file mode 100644 index 000000000..4972359da --- /dev/null +++ b/test/js/bun/util/mmap.test.js @@ -0,0 +1,69 @@ +import { describe, it, expect } from "bun:test"; +import { gcTick } from "harness"; + +describe("Bun.mmap", async () => { + await gcTick(); + const path = `/tmp/bun-mmap-test_${Math.random()}.txt`; + await gcTick(); + await Bun.write(path, "hello"); + await gcTick(); + + it("mmap finalizer", async () => { + let map = Bun.mmap(path); + await gcTick(); + const map2 = Bun.mmap(path); + + map = null; + await gcTick(); + }); + + it("mmap passed to other syscalls", async () => { + const map = Bun.mmap(path); + await gcTick(); + await Bun.write(path + "1", map); + await gcTick(); + const text = await (await Bun.file(path + "1")).text(); + await gcTick(); + + expect(text).toBe(new TextDecoder().decode(map)); + }); + + it("mmap sync", async () => { + const map = Bun.mmap(path); + await gcTick(); + const map2 = Bun.mmap(path); + await gcTick(); + + const old = map[0]; + await gcTick(); + map[0] = 0; + await gcTick(); + expect(map2[0]).toBe(0); + + map2[0] = old; + await gcTick(); + expect(map[0]).toBe(old); + await gcTick(); + await Bun.write(path, "olleh"); + await gcTick(); + expect(new TextDecoder().decode(map)).toBe("olleh"); + await gcTick(); + }); + + it("mmap private", async () => { + await gcTick(); + const map = Bun.mmap(path, { shared: true }); + await gcTick(); + const map2 = Bun.mmap(path, { shared: false }); + await gcTick(); + const old = map[0]; + + await gcTick(); + map2[0] = 0; + await gcTick(); + expect(map2[0]).toBe(0); + await gcTick(); + expect(map[0]).toBe(old); + await gcTick(); + }); +}); diff --git a/test/js/bun/util/peek.test.ts b/test/js/bun/util/peek.test.ts new file mode 100644 index 000000000..421c306d8 --- /dev/null +++ b/test/js/bun/util/peek.test.ts @@ -0,0 +1,42 @@ +import { peek } from "bun"; +import { expect, test } from "bun:test"; + +test("peek", () => { + const promise = Promise.resolve(true); + + // no await necessary! + expect(peek(promise)).toBe(true); + + // if we peek again, it returns the same value + const again = peek(promise); + expect(again).toBe(true); + + // if we peek a non-promise, it returns the value + const value = peek(42); + expect(value).toBe(42); + + // if we peek a pending promise, it returns the promise again + const pending = new Promise(() => {}); + expect(peek(pending)).toBe(pending); + + // If we peek a rejected promise, it: + // - returns the error + // - does not mark the promise as handled + const rejected = Promise.reject(new Error("Succesfully tested promise rejection")); + const peeked = peek(rejected); + expect(peeked instanceof Error).toBe(true); + expect((peeked as Error).message).toBe("Succesfully tested promise rejection"); + rejected.catch(() => {}); +}); + +test("peek.status", () => { + const promise = Promise.resolve(true); + expect(peek.status(promise)).toBe("fulfilled"); + + const pending = new Promise(() => {}); + expect(peek.status(pending)).toBe("pending"); + + const rejected = Promise.reject(new Error("oh nooo")); + expect(peek.status(rejected)).toBe("rejected"); + rejected.catch(() => {}); +}); diff --git a/test/js/bun/util/reportError.test.js b/test/js/bun/util/reportError.test.js new file mode 100644 index 000000000..e51f93309 --- /dev/null +++ b/test/js/bun/util/reportError.test.js @@ -0,0 +1,25 @@ +import { it } from "bun:test"; + +it("reportError", () => { + console.log("---BEGIN REPORT ERROR TEST--"); + // make sure we don't crash when given non-sensical types + reportError(new Error("reportError Test!")); + reportError(true); + reportError(false); + reportError(null); + reportError(123); + reportError(Infinity); + reportError(NaN); + reportError(-NaN); + reportError(""); + reportError(new Uint8Array(1)); + reportError(new Uint8Array(0)); + reportError(new ArrayBuffer(0)); + reportError(new ArrayBuffer(1)); + reportError("string"); + reportError([]); + reportError([123, null]); + reportError({}); + reportError([{}]); + console.log("---END REPORT ERROR TEST--"); +}); diff --git a/test/js/bun/util/sleep.js b/test/js/bun/util/sleep.js new file mode 100644 index 000000000..1ec79e79d --- /dev/null +++ b/test/js/bun/util/sleep.js @@ -0,0 +1,10 @@ +const interval = 10; +const now = performance.now(); +console.time("Slept"); +Bun.sleepSync(interval); +const elapsed = performance.now() - now; +if (elapsed < interval) { + throw new Error("Didn't sleep"); +} + +console.timeEnd("Slept"); diff --git a/test/js/bun/util/sleepSync.test.ts b/test/js/bun/util/sleepSync.test.ts new file mode 100644 index 000000000..dd2e8818a --- /dev/null +++ b/test/js/bun/util/sleepSync.test.ts @@ -0,0 +1,32 @@ +import { it, expect } from "bun:test"; +import { sleepSync } from "bun"; + +it("sleepSync uses milliseconds", async () => { + const start = Date.now(); + sleepSync(5); + const end = Date.now(); + expect(end - start).toBeGreaterThanOrEqual(5); + expect(end - start).toBeLessThan(10); +}); + +it("sleepSync with no arguments throws", async () => { + expect(() => sleepSync()).toThrow(); +}); + +it("sleepSync with non-numbers throws", async () => { + expect(() => sleepSync(true)).toThrow(); + expect(() => sleepSync(false)).toThrow(); + expect(() => sleepSync("hi")).toThrow(); + expect(() => sleepSync({})).toThrow(); + expect(() => sleepSync([])).toThrow(); + expect(() => sleepSync(undefined)).toThrow(); + expect(() => sleepSync(null)).toThrow(); +}); + +it("sleepSync with negative number throws", async () => { + expect(() => sleepSync(-10)).toThrow(); +}); + +it("can map with sleepSync", async () => { + [1, 2, 3].map(sleepSync); +}); diff --git a/test/js/bun/util/unsafe.test.js b/test/js/bun/util/unsafe.test.js new file mode 100644 index 000000000..38830fa36 --- /dev/null +++ b/test/js/bun/util/unsafe.test.js @@ -0,0 +1,51 @@ +import { test, expect, it, describe } from "bun:test"; +import { gc } from "harness"; + +it("arrayBufferToString u8", async () => { + var encoder = new TextEncoder(); + const bytes = encoder.encode("hello world"); + gc(true); + expect(Bun.unsafe.arrayBufferToString(bytes)).toBe("hello world"); + gc(true); + await new Promise(resolve => setTimeout(resolve, 0)); + gc(true); +}); + +it("arrayBufferToString ArrayBuffer", async () => { + var encoder = new TextEncoder(); + var bytes = encoder.encode("hello world"); + gc(true); + const out = Bun.unsafe.arrayBufferToString(bytes.buffer); + expect(out).toBe("hello world"); + gc(true); + await new Promise(resolve => setTimeout(resolve, 0)); + globalThis.bytes = bytes; + gc(true); + expect(out).toBe("hello world"); +}); + +it("arrayBufferToString u16", () => { + var encoder = new TextEncoder(); + const bytes = encoder.encode("hello world"); + var uint16 = new Uint16Array(bytes.byteLength); + uint16.set(bytes); + const charCodes = Bun.unsafe + .arrayBufferToString(uint16) + .split("") + .map(a => a.charCodeAt(0)); + gc(true); + for (let i = 0; i < charCodes.length; i++) { + expect("hello world"[i]).toBe(String.fromCharCode(charCodes[i])); + } + gc(true); + expect(charCodes.length).toBe("hello world".length); + gc(true); +}); + +it("Bun.allocUnsafe", () => { + var buffer = Bun.allocUnsafe(1024); + expect(buffer instanceof Uint8Array).toBe(true); + expect(buffer.length).toBe(1024); + buffer[0] = 0; + expect(buffer[0]).toBe(0); +}); diff --git a/test/js/bun/util/which.test.ts b/test/js/bun/util/which.test.ts new file mode 100644 index 000000000..e142e398c --- /dev/null +++ b/test/js/bun/util/which.test.ts @@ -0,0 +1,65 @@ +import { test, expect } from "bun:test"; + +import { which } from "bun"; +import { chmodSync, mkdirSync, unlinkSync } from "node:fs"; + +test("which", () => { + writeFixture("/tmp/myscript.sh"); + + // Our cwd is not /tmp + expect(which("myscript.sh")).toBe(null); + + try { + mkdirSync("myscript.sh"); + chmodSync("myscript.sh", "755"); + } catch (e) {} + + // directories should not be returned + expect(which("myscript.sh")).toBe(null); + + // "bun" is in our PATH + expect(which("bun")?.length > 0).toBe(true); + + expect( + // You can override PATH + which("myscript.sh", { + PATH: "/tmp", + }), + ).toBe("/tmp/myscript.sh"); + + expect( + which("myscript.sh", { + PATH: "/not-tmp", + }), + ).toBe(null); + + expect( + // PATH works like the $PATH environment variable, respecting colons + which("myscript.sh", { + PATH: "/not-tmp:/tmp", + }), + ).toBe("/tmp/myscript.sh"); + + expect( + // cwd is checked first + which("myscript.sh", { + cwd: "/tmp", + }), + ).toBe("/tmp/myscript.sh"); + + try { + unlinkSync("myscript.sh"); + } catch (e) {} +}); + +function writeFixture(path) { + var fs = require("fs"); + try { + fs.unlinkSync(path); + } catch (e) {} + + var script_name = path; + var script_content = "echo Hello world!"; + fs.writeFileSync(script_name, script_content); + fs.chmodSync(script_name, "755"); +} diff --git a/test/js/bun/wasm/hello-wasi.wasm b/test/js/bun/wasm/hello-wasi.wasm new file mode 100755 index 000000000..e134bcee4 Binary files /dev/null and b/test/js/bun/wasm/hello-wasi.wasm differ diff --git a/test/js/bun/wasm/wasi.test.js b/test/js/bun/wasm/wasi.test.js new file mode 100644 index 000000000..ac1d986d4 --- /dev/null +++ b/test/js/bun/wasm/wasi.test.js @@ -0,0 +1,14 @@ +import { spawnSync } from "bun"; +import { expect, it } from "bun:test"; +import { bunExe, bunEnv } from "harness"; + +it("Should support printing 'hello world'", () => { + const { stdout, exitCode } = spawnSync({ + cmd: [bunExe(), import.meta.dir + "/hello-wasi.wasm"], + stdout: "pipe", + env: bunEnv, + }); + + expect(stdout.toString()).toEqual("hello world\n"); + expect(exitCode).toBe(0); +}); diff --git a/test/js/bun/wasm/wasm-return-1-test.zig b/test/js/bun/wasm/wasm-return-1-test.zig new file mode 100644 index 000000000..d46bdae92 --- /dev/null +++ b/test/js/bun/wasm/wasm-return-1-test.zig @@ -0,0 +1,5 @@ +export fn hello() i32 { + return 1; +} + +pub fn main() void {} diff --git a/test/js/bun/wasm/wasm.js b/test/js/bun/wasm/wasm.js new file mode 100644 index 000000000..a4daaaffe --- /dev/null +++ b/test/js/bun/wasm/wasm.js @@ -0,0 +1 @@ +import * as wasm from "./wasm-return-1-test.wasm"; diff --git a/test/js/bun/websocket/websocket-server.test.ts b/test/js/bun/websocket/websocket-server.test.ts new file mode 100644 index 000000000..b3b2e57b5 --- /dev/null +++ b/test/js/bun/websocket/websocket-server.test.ts @@ -0,0 +1,954 @@ +import { describe, expect, it } from "bun:test"; +import { gcTick } from "harness"; +import { serve } from "bun"; + +describe("websocket server", () => { + it("can do publish()", async done => { + var server = serve({ + port: 0, + websocket: { + open(ws) { + ws.subscribe("all"); + }, + message(ws, msg) {}, + close(ws) {}, + }, + fetch(req, server) { + if (server.upgrade(req)) { + return; + } + + return new Response("success"); + }, + }); + + await new Promise((resolve2, reject2) => { + var socket = new WebSocket(`ws://${server.hostname}:${server.port}`); + var clientCounter = 0; + + socket.onmessage = e => { + expect(e.data).toBe("hello"); + resolve2(); + }; + socket.onopen = () => { + queueMicrotask(() => { + server.publish("all", "hello"); + }); + }; + }); + server.stop(true); + done(); + }); + + it("can do publish() with publishToSelf: false", async done => { + var server = serve({ + port: 0, + websocket: { + open(ws) { + ws.subscribe("all"); + ws.publish("all", "hey"); + server.publish("all", "hello"); + }, + message(ws, msg) { + if (new TextDecoder().decode(msg) !== "hello") { + done(new Error("unexpected message")); + } + }, + close(ws) {}, + publishToSelf: false, + }, + fetch(req, server) { + if (server.upgrade(req)) { + return; + } + + return new Response("success"); + }, + }); + + await new Promise((resolve2, reject2) => { + var socket = new WebSocket(`ws://${server.hostname}:${server.port}`); + + socket.onmessage = e => { + expect(e.data).toBe("hello"); + resolve2(); + }; + }); + server.stop(true); + done(); + }); + + for (let method of ["publish", "publishText", "publishBinary"]) { + describe(method, () => { + it("in close() should work", async () => { + var count = 0; + var server = serve({ + port: 0, + websocket: { + open(ws) { + ws.subscribe("all"); + }, + message(ws, msg) {}, + close(ws) { + ws[method]("all", method === "publishBinary" ? Buffer.from("bye!") : "bye!"); + count++; + + if (count >= 2) { + server.stop(true); + } + }, + }, + fetch(req, server) { + if (server.upgrade(req)) { + return; + } + + return new Response("success"); + }, + }); + + try { + const first = await new Promise((resolve2, reject2) => { + var socket = new WebSocket(`ws://${server.hostname}:${server.port}`); + socket.onopen = () => resolve2(socket); + }); + + await new Promise((resolve2, reject2) => { + var socket = new WebSocket(`ws://${server.hostname}:${server.port}`); + socket.onopen = () => { + queueMicrotask(() => first.close()); + }; + socket.onmessage = ev => { + var msg = ev.data; + if (typeof msg !== "string") { + msg = new TextDecoder().decode(msg); + } + + if (msg === "bye!") { + socket.close(0); + resolve2(socket); + } else { + reject2(msg); + } + }; + }); + } finally { + server.stop(true); + } + }); + }); + } + + it("close inside open", async () => { + var resolve; + console.trace("here"); + var server = serve({ + port: 0, + websocket: { + open(ws) {}, + message(ws, msg) {}, + close() { + resolve(); + server.stop(true); + }, + }, + fetch(req, server) { + if ( + server.upgrade(req, { + data: "hello world", + + // check that headers works + headers: { + "x-a": "text/plain", + }, + }) + ) { + if (server.upgrade(req)) { + throw new Error("should not upgrade twice"); + } + return; + } + + return new Response("noooooo hello world"); + }, + }); + + await new Promise((resolve_, reject) => { + resolve = resolve_; + const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`); + websocket.onopen = () => { + websocket.close(); + }; + websocket.onmessage = e => {}; + websocket.onerror = e => {}; + }); + }); + + it("headers error doesn't crash", async () => { + await new Promise((resolve, reject) => { + const server = serve({ + port: 0, + websocket: { + open(ws) { + ws.close(); + }, + message(ws, msg) {}, + close() { + resolve(); + server.stop(true); + }, + }, + error(err) { + resolve(); + server.stop(true); + }, + fetch(req, server) { + expect(() => { + if ( + server.upgrade(req, { + data: "hello world", + headers: 1238 as any, + }) + ) { + reject(new Error("should not upgrade")); + return new Response("should not upgrade"); + } + }).toThrow("upgrade options.headers must be a Headers or an object"); + resolve(); + return new Response("success"); + }, + }); + + const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`); + websocket.onopen = () => websocket.close(); + websocket.onmessage = e => {}; + websocket.onerror = e => {}; + }); + }); + it("can do hello world", async () => { + const server = serve({ + port: 0, + websocket: { + open(ws) { + server.stop(); + }, + message(ws, msg) { + ws.send("hello world"); + }, + }, + fetch(req, server) { + server.stop(); + if ( + server.upgrade(req, { + data: "hello world", + + // check that headers works + headers: { + "x-a": "text/plain", + }, + }) + ) { + if (server.upgrade(req)) { + throw new Error("should not upgrade twice"); + } + return; + } + + return new Response("noooooo hello world"); + }, + }); + + await new Promise((resolve, reject) => { + const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`); + websocket.onopen = () => { + websocket.send("hello world"); + }; + websocket.onmessage = e => { + try { + expect(e.data).toBe("hello world"); + resolve(); + } catch (r) { + reject(r); + } finally { + websocket.close(); + } + }; + websocket.onerror = e => { + reject(e); + }; + }); + }); + + it("fetch() allows a Response object to be returned for an upgraded ServerWebSocket", () => { + const server = serve({ + port: 0, + websocket: { + open(ws) { + server.stop(); + }, + message(ws, msg) { + ws.send("hello world"); + }, + }, + error(err) { + console.error(err); + }, + fetch(req, server) { + server.stop(); + if ( + server.upgrade(req, { + data: "hello world", + + // check that headers works + headers: { + "x-a": "text/plain", + }, + }) + ) { + if (server.upgrade(req)) { + throw new Error("should not upgrade twice"); + } + return new Response("lol!", { + status: 101, + }); + } + + return new Response("noooooo hello world"); + }, + }); + + return new Promise((resolve, reject) => { + const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`); + websocket.onopen = () => { + websocket.send("hello world"); + }; + websocket.onmessage = e => { + try { + expect(e.data).toBe("hello world"); + resolve(); + } catch (r) { + reject(r); + } finally { + websocket.close(); + } + }; + websocket.onerror = e => { + reject(e); + }; + }); + }); + + it("fetch() allows a Promise object to be returned for an upgraded ServerWebSocket", () => { + const server = serve({ + port: 0, + websocket: { + async open(ws) { + server.stop(); + }, + async message(ws, msg) { + await 1; + ws.send("hello world"); + }, + }, + error(err) { + console.error(err); + }, + async fetch(req, server) { + server.stop(); + await 1; + if ( + server.upgrade(req, { + data: "hello world", + + // check that headers works + headers: { + "x-a": "text/plain", + }, + }) + ) { + if (server.upgrade(req)) { + throw new Error("should not upgrade twice"); + } + return new Response("lol!", { + status: 101, + }); + } + + return new Response("noooooo hello world"); + }, + }); + return new Promise((resolve, reject) => { + const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`); + websocket.onopen = () => { + websocket.send("hello world"); + }; + websocket.onmessage = e => { + try { + expect(e.data).toBe("hello world"); + resolve(); + } catch (r) { + reject(r); + } finally { + websocket.close(); + } + }; + websocket.onerror = e => { + reject(e); + }; + }); + }); + it("binaryType works", async () => { + var done = false; + const server = serve({ + port: 0, + websocket: { + open(ws) { + server.stop(); + }, + message(ws, msg) { + if (ws.binaryType === "uint8array") { + expect(ws.binaryType).toBe("uint8array"); + ws.binaryType = "arraybuffer"; + expect(ws.binaryType).toBe("arraybuffer"); + expect(msg instanceof Uint8Array).toBe(true); + } else { + expect(ws.binaryType).toBe("arraybuffer"); + expect(msg instanceof ArrayBuffer).toBe(true); + done = true; + } + + ws.send("hello world"); + }, + }, + fetch(req, server) { + server.stop(); + if (server.upgrade(req, { data: "hello world" })) { + if (server.upgrade(req)) { + throw new Error("should not upgrade twice"); + } + return; + } + + return new Response("noooooo hello world"); + }, + }); + + await new Promise((resolve, reject) => { + var counter = 0; + const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`); + websocket.onopen = () => { + websocket.send(Buffer.from("hello world")); + }; + websocket.onmessage = e => { + try { + expect(e.data).toBe("hello world"); + + if (counter++ > 0) { + websocket.close(); + resolve(done); + } + websocket.send(Buffer.from("oaksd")); + } catch (r) { + websocket.close(); + reject(r); + } + }; + websocket.onerror = e => { + reject(e); + }; + }); + }); + + it("does not upgrade for non-websocket connections", async () => { + await new Promise(async (resolve, reject) => { + var server = serve({ + port: 0, + websocket: { + open(ws) { + ws.send("hello world"); + }, + message(ws, msg) {}, + }, + fetch(req, server) { + if (server.upgrade(req)) { + reject(new Error("should not upgrade")); + } + + return new Response("success"); + }, + }); + + const response = await fetch(`http://${server.hostname}:${server.port}`); + expect(await response.text()).toBe("success"); + resolve(); + server.stop(true); + }); + }); + + it("does not upgrade for non-websocket servers", async () => { + await new Promise(async (resolve, reject) => { + const server = serve({ + port: 0, + fetch(req, server) { + server.stop(); + expect(() => { + server.upgrade(req); + }).toThrow('To enable websocket support, set the "websocket" object in Bun.serve({})'); + return new Response("success"); + }, + }); + + const response = await fetch(`http://${server.hostname}:${server.port}`); + expect(await response.text()).toBe("success"); + resolve(); + }); + }); + + it("async can do hello world", async () => { + const server = serve({ + port: 0, + websocket: { + async open(ws) { + server.stop(true); + ws.send("hello world"); + }, + message(ws, msg) {}, + }, + async fetch(req, server) { + server.stop(); + await 1; + if (server.upgrade(req)) return; + + return new Response("noooooo hello world"); + }, + }); + + await new Promise((resolve, reject) => { + const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`); + + websocket.onmessage = e => { + try { + expect(e.data).toBe("hello world"); + resolve(); + } catch (r) { + reject(r); + } finally { + websocket.close(); + } + }; + websocket.onerror = e => { + reject(e); + }; + }); + }); + + it("publishText()", async () => { + await new Promise((resolve, reject) => { + var websocket; + var server = serve({ + port: 0, + websocket: { + async open(ws) { + // we don't care about the data + // we just want to make sure the DOMJIT call doesn't crash + for (let i = 0; i < 40_000; i++) ws.publishText("hello", "world"); + websocket.close(); + server.stop(true); + resolve(); + }, + message(ws, msg) {}, + }, + async fetch(req, server) { + await 1; + if (server.upgrade(req)) return; + + return new Response("noooooo hello world"); + }, + }); + + websocket = new WebSocket(`ws://${server.hostname}:${server.port}`); + }); + }); + + it("publishBinary()", async () => { + const bytes = Buffer.from("hello"); + + await new Promise((resolve, reject) => { + var websocket; + var server = serve({ + port: 0, + websocket: { + async open(ws) { + // we don't care about the data + // we just want to make sure the DOMJIT call doesn't crash + for (let i = 0; i < 40_000; i++) ws.publishBinary("hello", bytes); + websocket.close(); + server.stop(true); + resolve(); + }, + message(ws, msg) {}, + }, + async fetch(req, server) { + await 1; + if (server.upgrade(req)) return; + + return new Response("noooooo hello world"); + }, + }); + + websocket = new WebSocket(`ws://${server.hostname}:${server.port}`); + }); + }); + + it("sendText()", async () => { + await new Promise((resolve, reject) => { + var websocket; + var server = serve({ + port: 0, + websocket: { + async open(ws) { + // we don't care about the data + // we just want to make sure the DOMJIT call doesn't crash + for (let i = 0; i < 40_000; i++) ws.sendText("hello world", true); + resolve(); + websocket.close(); + server.stop(true); + }, + message(ws, msg) {}, + }, + async fetch(req, server) { + await 1; + if (server.upgrade(req)) return; + + return new Response("noooooo hello world"); + }, + }); + websocket = new WebSocket(`ws://${server.hostname}:${server.port}`); + }); + }); + + it("sendBinary()", async () => { + const bytes = Buffer.from("hello"); + await new Promise((resolve, reject) => { + var websocket; + var server = serve({ + port: 0, + websocket: { + async open(ws) { + // we don't care about the data + // we just want to make sure the DOMJIT call doesn't crash + for (let i = 0; i < 40_000; i++) ws.sendBinary(bytes, true); + websocket.close(); + server.stop(true); + resolve(); + }, + message(ws, msg) {}, + }, + async fetch(req, server) { + await 1; + if (server.upgrade(req)) return; + + return new Response("noooooo hello world"); + }, + }); + + websocket = new WebSocket(`ws://${server.hostname}:${server.port}`); + }); + }); + + it("can do hello world corked", async () => { + const server = serve({ + port: 0, + websocket: { + open(ws) { + server.stop(); + ws.send("hello world"); + }, + message(ws, msg) { + ws.cork(() => { + ws.send("hello world"); + }); + }, + }, + fetch(req, server) { + server.stop(); + if (server.upgrade(req)) return; + return new Response("noooooo hello world"); + }, + }); + + await new Promise((resolve, reject) => { + const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`); + + websocket.onmessage = e => { + try { + expect(e.data).toBe("hello world"); + resolve(); + } catch (r) { + reject(r); + } finally { + websocket.close(); + } + }; + websocket.onerror = e => { + reject(e); + }; + }); + server.stop(true); + }); + + it("can do some back and forth", async () => { + var dataCount = 0; + const server = serve({ + port: 0, + websocket: { + open(ws) { + server.stop(); + }, + message(ws, msg) { + if (msg === "first") { + ws.send("first"); + return; + } + ws.send(`counter: ${dataCount++}`); + }, + }, + fetch(req, server) { + server.stop(); + if ( + server.upgrade(req, { + data: { count: 0 }, + }) + ) + return new Response("noooooo hello world"); + }, + }); + + await new Promise((resolve, reject) => { + const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`); + websocket.onerror = e => { + reject(e); + }; + + var counter = 0; + websocket.onopen = () => websocket.send("first"); + websocket.onmessage = e => { + try { + switch (counter++) { + case 0: { + expect(e.data).toBe("first"); + websocket.send("where are the loops"); + break; + } + case 1: { + expect(e.data).toBe("counter: 0"); + websocket.send("br0ther may i have some loops"); + break; + } + case 2: { + expect(e.data).toBe("counter: 1"); + websocket.send("br0ther may i have some loops"); + break; + } + case 3: { + expect(e.data).toBe("counter: 2"); + resolve(); + break; + } + } + } catch (r) { + reject(r); + websocket.close(); + } + }; + }); + server.stop(true); + }); + + it("send rope strings", async () => { + var ropey = "hello world".repeat(10); + var sendQueue: any[] = []; + for (var i = 0; i < 100; i++) { + sendQueue.push(ropey + " " + i); + } + + var serverCounter = 0; + var clientCounter = 0; + + const server = serve({ + port: 0, + websocket: { + open(ws) { + server.stop(); + }, + message(ws, msg) { + ws.send(sendQueue[serverCounter++] + " "); + gcTick(); + }, + }, + fetch(req, server) { + server.stop(); + if ( + server.upgrade(req, { + data: { count: 0 }, + }) + ) + return; + + return new Response("noooooo hello world"); + }, + }); + + await new Promise((resolve, reject) => { + const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`); + websocket.onerror = e => { + reject(e); + }; + + var counter = 0; + websocket.onopen = () => websocket.send("first"); + websocket.onmessage = e => { + try { + const expected = sendQueue[clientCounter++] + " "; + expect(e.data).toBe(expected); + websocket.send("next"); + if (clientCounter === sendQueue.length) { + websocket.close(); + resolve(); + } + } catch (r) { + reject(r); + console.error(r); + websocket.close(); + } + }; + }); + server.stop(true); + }); + + // this test sends 100 messages to 10 connected clients via pubsub + it("pub/sub", async () => { + var ropey = "hello world".repeat(10); + var sendQueue: any[] = []; + for (var i = 0; i < 100; i++) { + sendQueue.push(ropey + " " + i); + gcTick(); + } + var serverCounter = 0; + var clientCount = 0; + const server = serve({ + port: 0, + websocket: { + open(ws) { + server.stop(); + ws.subscribe("test"); + gcTick(); + if (!ws.isSubscribed("test")) { + throw new Error("not subscribed"); + } + ws.unsubscribe("test"); + if (ws.isSubscribed("test")) { + throw new Error("subscribed"); + } + ws.subscribe("test"); + clientCount++; + if (clientCount === 10) setTimeout(() => ws.publish("test", "hello world"), 1); + }, + message(ws, msg) { + if (serverCounter < sendQueue.length) ws.publish("test", sendQueue[serverCounter++] + " "); + }, + }, + fetch(req) { + gcTick(); + server.stop(); + if ( + server.upgrade(req, { + data: { count: 0 }, + }) + ) + return; + return new Response("noooooo hello world"); + }, + }); + + const connections = new Array(10); + const websockets = new Array(connections.length); + var doneCounter = 0; + await new Promise(done => { + for (var i = 0; i < connections.length; i++) { + var j = i; + var resolve, reject, resolveConnection, rejectConnection; + connections[j] = new Promise((res, rej) => { + resolveConnection = res; + rejectConnection = rej; + }); + websockets[j] = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + gcTick(); + const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`); + websocket.onerror = e => { + reject(e); + }; + websocket.onclose = () => { + doneCounter++; + if (doneCounter === connections.length) { + done(); + } + }; + var hasOpened = false; + websocket.onopen = () => { + if (!hasOpened) { + hasOpened = true; + resolve(websocket); + } + }; + + let clientCounter = -1; + var hasSentThisTick = false; + + websocket.onmessage = e => { + gcTick(); + + if (!hasOpened) { + hasOpened = true; + resolve(websocket); + } + + if (e.data === "hello world") { + clientCounter = 0; + websocket.send("first"); + return; + } + + try { + expect(!!sendQueue.find(a => a + " " === e.data)).toBe(true); + + if (!hasSentThisTick) { + websocket.send("second"); + hasSentThisTick = true; + queueMicrotask(() => { + hasSentThisTick = false; + }); + } + + gcTick(); + + if (clientCounter++ === sendQueue.length - 1) { + websocket.close(); + resolveConnection(); + } + } catch (r) { + console.error(r); + websocket.close(); + rejectConnection(r); + gcTick(); + } + }; + } + }); + expect(serverCounter).toBe(sendQueue.length); + server.stop(true); + }); +}); diff --git a/test/js/first_party/undici/undici.test.ts b/test/js/first_party/undici/undici.test.ts new file mode 100644 index 000000000..603b7a03d --- /dev/null +++ b/test/js/first_party/undici/undici.test.ts @@ -0,0 +1,140 @@ +import { describe, it, expect } from "bun:test"; +import { request } from "undici"; + +describe("undici", () => { + describe("request", () => { + it("should make a GET request when passed a URL string", async () => { + const { body } = await request("https://httpbin.org/get"); + expect(body).toBeDefined(); + const json = (await body.json()) as { url: string }; + expect(json.url).toBe("https://httpbin.org/get"); + }); + + it("should error when body has already been consumed", async () => { + const { body } = await request("https://httpbin.org/get"); + await body.json(); + expect(body.bodyUsed).toBe(true); + try { + await body.json(); + throw new Error("Should have errored"); + } catch (e) { + expect((e as Error).message).toBe("unusable"); + } + }); + + it("should make a POST request when provided a body and POST method", async () => { + const { body } = await request("https://httpbin.org/post", { + method: "POST", + body: "Hello world", + }); + expect(body).toBeDefined(); + const json = (await body.json()) as { data: string }; + expect(json.data).toBe("Hello world"); + }); + + it("should accept a URL class object", async () => { + const { body } = await request(new URL("https://httpbin.org/get")); + expect(body).toBeDefined(); + const json = (await body.json()) as { url: string }; + expect(json.url).toBe("https://httpbin.org/get"); + }); + + // it("should accept an undici UrlObject", async () => { + // // @ts-ignore + // const { body } = await request({ protocol: "https:", hostname: "httpbin.org", path: "/get" }); + // expect(body).toBeDefined(); + // const json = (await body.json()) as { url: string }; + // expect(json.url).toBe("https://httpbin.org/get"); + // }); + + it("should prevent body from being attached to GET or HEAD requests", async () => { + try { + await request("https://httpbin.org/get", { + method: "GET", + body: "Hello world", + }); + throw new Error("Should have errored"); + } catch (e) { + expect((e as Error).message).toBe("Body not allowed for GET or HEAD requests"); + } + + try { + await request("https://httpbin.org/head", { + method: "HEAD", + body: "Hello world", + }); + throw new Error("Should have errored"); + } catch (e) { + expect((e as Error).message).toBe("Body not allowed for GET or HEAD requests"); + } + }); + + it("should allow a query string to be passed", async () => { + const { body } = await request("https://httpbin.org/get?foo=bar"); + expect(body).toBeDefined(); + const json = (await body.json()) as { args: { foo: string } }; + expect(json.args.foo).toBe("bar"); + + const { body: body2 } = await request("https://httpbin.org/get", { + query: { foo: "bar" }, + }); + expect(body2).toBeDefined(); + const json2 = (await body2.json()) as { args: { foo: string } }; + expect(json2.args.foo).toBe("bar"); + }); + + it("should throw on HTTP 4xx or 5xx error when throwOnError is true", async () => { + try { + await request("https://httpbin.org/status/404", { throwOnError: true }); + throw new Error("Should have errored"); + } catch (e) { + expect((e as Error).message).toBe("Request failed with status code 404"); + } + + try { + await request("https://httpbin.org/status/500", { throwOnError: true }); + throw new Error("Should have errored"); + } catch (e) { + expect((e as Error).message).toBe("Request failed with status code 500"); + } + }); + + it("should allow us to abort the request with a signal", async () => { + const controller = new AbortController(); + try { + setTimeout(() => controller.abort(), 1000); + const req = await request("https://httpbin.org/delay/5", { + signal: controller.signal, + }); + await req.body.json(); + throw new Error("Should have errored"); + } catch (e) { + expect((e as Error).message).toBe("The operation was aborted."); + } + }); + + it("should properly append headers to the request", async () => { + const { body } = await request("https://httpbin.org/headers", { + headers: { + "x-foo": "bar", + }, + }); + expect(body).toBeDefined(); + const json = (await body.json()) as { headers: { "X-Foo": string } }; + expect(json.headers["X-Foo"]).toBe("bar"); + }); + + // it("should allow the use of FormData", async () => { + // const form = new FormData(); + // form.append("foo", "bar"); + // const { body } = await request("https://httpbin.org/post", { + // method: "POST", + // body: form, + // }); + + // expect(body).toBeDefined(); + // const json = (await body.json()) as { form: { foo: string } }; + // expect(json.form.foo).toBe("bar"); + // }); + }); +}); diff --git a/test/js/node/assert/assert-test.test.ts b/test/js/node/assert/assert-test.test.ts new file mode 100644 index 000000000..1723b7d47 --- /dev/null +++ b/test/js/node/assert/assert-test.test.ts @@ -0,0 +1,11 @@ +import assert from "assert"; +import { expect, test } from "bun:test"; + +// https://github.com/oven-sh/bun/issues/941 +test("assert as a function does not throw", () => assert(true)); +test("assert as a function does throw", () => { + try { + assert(false); + expect(false).toBe(true); + } catch (e) {} +}); diff --git a/test/js/node/buffer.test.js b/test/js/node/buffer.test.js new file mode 100644 index 000000000..568bf8d44 --- /dev/null +++ b/test/js/node/buffer.test.js @@ -0,0 +1,2563 @@ +import { describe, it, expect, beforeEach, afterEach, test } from "bun:test"; +import { gc } from "harness"; + +const BufferModule = await import("buffer"); + +beforeEach(() => gc()); +afterEach(() => gc()); + +function assert(a) { + expect(a).toBeTruthy(); +} + +Object.assign(assert, { + ok(a) { + expect(a).toBeTruthy(); + }, + deepStrictEqual(a, b) { + expect(b).toStrictEqual(a); + }, + strictEqual(a, b) { + expect(a).toBe(b); + }, + throws(a, b) { + expect(a).toThrow(); + }, +}); + +// https://github.com/oven-sh/bun/issues/2052 +it("Buffer global is settable", () => { + var prevBuffer = globalThis.Buffer; + globalThis.Buffer = 42; + expect(globalThis.Buffer).toBe(42); + globalThis.Buffer = prevBuffer; + expect(globalThis.Buffer).toBe(BufferModule.Buffer); + expect(globalThis.Buffer).toBe(prevBuffer); +}); + +it("Buffer.alloc", () => { + // Verify the maximum Uint8Array size. There is no concrete limit by spec. The + // internal limits should be updated if this fails. + assert.throws(() => new Uint8Array(2 ** 32 + 1), { + message: "Invalid typed array length: 4294967297", + }); + + const b = Buffer.allocUnsafe(1024); + assert.strictEqual(b.length, 1024); + + b[0] = -1; + assert.strictEqual(b[0], 255); + + for (let i = 0; i < 1024; i++) { + b[i] = i % 256; + } + + for (let i = 0; i < 1024; i++) { + assert.strictEqual(i % 256, b[i]); + } + + const c = Buffer.allocUnsafe(512); + assert.strictEqual(c.length, 512); + + const d = Buffer.from([]); + assert.strictEqual(d.length, 0); + + // Test offset properties + { + const b = Buffer.alloc(128); + assert.strictEqual(b.length, 128); + assert.strictEqual(b.byteOffset, 0); + assert.strictEqual(b.offset, 0); + } + + // Test creating a Buffer from a Uint32Array + { + const ui32 = new Uint32Array(4).fill(42); + const e = Buffer.from(ui32); + for (const [index, value] of e.entries()) { + assert.strictEqual(value, ui32[index]); + } + } + // Test creating a Buffer from a Uint32Array (old constructor) + { + const ui32 = new Uint32Array(4).fill(42); + const e = Buffer(ui32); + for (const [key, value] of e.entries()) { + assert.deepStrictEqual(value, ui32[key]); + } + } + + // Test invalid encoding for Buffer.toString + assert.throws(() => b.toString("invalid"), /Unknown encoding: invalid/); + // Invalid encoding for Buffer.write + assert.throws(() => b.write("test string", 0, 5, "invalid"), /Unknown encoding: invalid/); + // Unsupported arguments for Buffer.write + // assert.throws(() => b.write("test", "utf8", 0), { + // code: "ERR_INVALID_ARG_TYPE", + // }); + + // Try to create 0-length buffers. Should not throw. + Buffer.from(""); + Buffer.from("", "ascii"); + Buffer.from("", "latin1"); + Buffer.alloc(0); + Buffer.allocUnsafe(0); + new Buffer(""); + new Buffer("", "ascii"); + new Buffer("", "latin1"); + new Buffer("", "binary"); + Buffer(0); + + const outOfRangeError = { + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + }; + + // Try to write a 0-length string beyond the end of b + // assert.throws(() => b.write("", 2048), outOfRangeError); + + // // Throw when writing to negative offset + // assert.throws(() => b.write("a", -1), outOfRangeError); + + // // Throw when writing past bounds from the pool + // assert.throws(() => b.write("a", 2048), outOfRangeError); + + // // Throw when writing to negative offset + // assert.throws(() => b.write("a", -1), outOfRangeError); + + // Try to copy 0 bytes worth of data into an empty buffer + b.copy(Buffer.alloc(0), 0, 0, 0); + + // Try to copy 0 bytes past the end of the target buffer + b.copy(Buffer.alloc(0), 1, 1, 1); + b.copy(Buffer.alloc(1), 1, 1, 1); + + // Try to copy 0 bytes from past the end of the source buffer + b.copy(Buffer.alloc(1), 0, 2048, 2048); + + // Testing for smart defaults and ability to pass string values as offset + { + const writeTest = Buffer.from("abcdes"); + writeTest.write("n", "ascii"); + assert.throws(() => writeTest.write("o", "1", "ascii"), { + code: "ERR_INVALID_ARG_TYPE", + }); + writeTest.write("o", 1, "ascii"); + writeTest.write("d", 2, "ascii"); + writeTest.write("e", 3, "ascii"); + writeTest.write("j", 4, "ascii"); + assert.strictEqual(writeTest.toString(), "nodejs"); + } + + // Offset points to the end of the buffer and does not throw. + // (see https://github.com/nodejs/node/issues/8127). + Buffer.alloc(1).write("", 1, 0); + + // ASCII slice test + { + const asciiString = "hello world"; + + for (let i = 0; i < asciiString.length; i++) { + b[i] = asciiString.charCodeAt(i); + } + const asciiSlice = b.toString("ascii", 0, asciiString.length); + assert.strictEqual(asciiString, asciiSlice); + } + + { + const asciiString = "hello world"; + const offset = 100; + + assert.strictEqual(asciiString.length, b.write(asciiString, offset, "ascii")); + const asciiSlice = b.toString("ascii", offset, offset + asciiString.length); + assert.strictEqual(asciiString, asciiSlice); + } + + { + const asciiString = "hello world"; + const offset = 100; + + const sliceA = b.slice(offset, offset + asciiString.length); + const sliceB = b.slice(offset, offset + asciiString.length); + for (let i = 0; i < asciiString.length; i++) { + assert.strictEqual(sliceA[i], sliceB[i]); + } + } + + // UTF-8 slice test + { + const utf8String = "¡hέlló wôrld!"; + const offset = 100; + + b.write(utf8String, 0, Buffer.byteLength(utf8String), "utf8"); + let utf8Slice = b.toString("utf8", 0, Buffer.byteLength(utf8String)); + assert.strictEqual(utf8String, utf8Slice); + + assert.strictEqual(Buffer.byteLength(utf8String), b.write(utf8String, offset, "utf8")); + utf8Slice = b.toString("utf8", offset, offset + Buffer.byteLength(utf8String)); + assert.strictEqual(utf8String, utf8Slice); + + const sliceA = b.slice(offset, offset + Buffer.byteLength(utf8String)); + const sliceB = b.slice(offset, offset + Buffer.byteLength(utf8String)); + for (let i = 0; i < Buffer.byteLength(utf8String); i++) { + assert.strictEqual(sliceA[i], sliceB[i]); + } + } + + { + const slice = b.slice(100, 150); + assert.strictEqual(slice.length, 50); + for (let i = 0; i < 50; i++) { + assert.strictEqual(b[100 + i], slice[i]); + } + } + + { + // Make sure only top level parent propagates from allocPool + const b = Buffer.allocUnsafe(5); + const c = b.slice(0, 4); + const d = c.slice(0, 2); + assert.strictEqual(b.parent, c.parent); + assert.strictEqual(b.parent, d.parent); + } + + { + // Also from a non-pooled instance + const b = Buffer.allocUnsafeSlow(5); + const c = b.slice(0, 4); + const d = c.slice(0, 2); + assert.strictEqual(c.parent, d.parent); + } + + { + // Bug regression test + const testValue = "\u00F6\u65E5\u672C\u8A9E"; // ö日本語 + const buffer = Buffer.allocUnsafe(32); + const size = buffer.write(testValue, 0, "utf8"); + const slice = buffer.toString("utf8", 0, size); + assert.strictEqual(slice, testValue); + } + + { + // Test triple slice + const a = Buffer.allocUnsafe(8); + for (let i = 0; i < 8; i++) a[i] = i; + const b = a.slice(4, 8); + assert.strictEqual(b[0], 4); + assert.strictEqual(b[1], 5); + assert.strictEqual(b[2], 6); + assert.strictEqual(b[3], 7); + const c = b.slice(2, 4); + assert.strictEqual(c[0], 6); + assert.strictEqual(c[1], 7); + } + + { + const d = Buffer.from([23, 42, 255]); + assert.strictEqual(d.length, 3); + assert.strictEqual(d[0], 23); + assert.strictEqual(d[1], 42); + assert.strictEqual(d[2], 255); + assert.deepStrictEqual(d, Buffer.from(d)); + } + + { + // Test for proper UTF-8 Encoding + const e = Buffer.from("über"); + assert.deepStrictEqual(e, Buffer.from([195, 188, 98, 101, 114])); + } + + { + // Test for proper ascii Encoding, length should be 4 + const f = Buffer.from("über", "ascii"); + assert.deepStrictEqual(f, Buffer.from([252, 98, 101, 114])); + } + + ["ucs2", "ucs-2", "utf16le", "utf-16le"].forEach(encoding => { + { + // Test for proper UTF16LE encoding, length should be 8 + const f = Buffer.from("über", encoding); + assert.deepStrictEqual(f, Buffer.from([252, 0, 98, 0, 101, 0, 114, 0])); + } + + { + // Length should be 12 + const f = Buffer.from("привет", encoding); + assert.deepStrictEqual(f, Buffer.from([63, 4, 64, 4, 56, 4, 50, 4, 53, 4, 66, 4])); + assert.strictEqual(f.toString(encoding), "привет"); + } + + { + const f = Buffer.from([0, 0, 0, 0, 0]); + assert.strictEqual(f.length, 5); + const size = f.write("あいうえお", encoding); + assert.strictEqual(size, 4); + assert.deepStrictEqual(f, Buffer.from([0x42, 0x30, 0x44, 0x30, 0x00])); + } + }); + + { + const f = Buffer.from("\uD83D\uDC4D", "utf-16le"); // THUMBS UP SIGN (U+1F44D) + assert.strictEqual(f.length, 4); + assert.deepStrictEqual(f, Buffer.from("3DD84DDC", "hex")); + } + + // Test construction from arrayish object + { + const arrayIsh = { 0: 0, 1: 1, 2: 2, 3: 3, length: 4 }; + let g = Buffer.from(arrayIsh); + assert.deepStrictEqual(g, Buffer.from([0, 1, 2, 3])); + const strArrayIsh = { 0: "0", 1: "1", 2: "2", 3: "3", length: 4 }; + g = Buffer.from(strArrayIsh); + assert.deepStrictEqual(g, Buffer.from([0, 1, 2, 3])); + } + + // + // Test toString('base64') + // + assert.strictEqual(Buffer.from("Man").toString("base64"), "TWFu"); + assert.strictEqual(Buffer.from("Woman").toString("base64"), "V29tYW4="); + + // + // Test toString('base64url') + // + assert.strictEqual(Buffer.from("Man").toString("base64url"), "TWFu"); + assert.strictEqual(Buffer.from("Woman").toString("base64url"), "V29tYW4"); + + { + // Test that regular and URL-safe base64 both work both ways + const expected = [0xff, 0xff, 0xbe, 0xff, 0xef, 0xbf, 0xfb, 0xef, 0xff]; + assert.deepStrictEqual(Buffer.from("//++/++/++//", "base64"), Buffer.from(expected)); + assert.deepStrictEqual(Buffer.from("__--_--_--__", "base64"), Buffer.from(expected)); + assert.deepStrictEqual(Buffer.from("//++/++/++//", "base64url"), Buffer.from(expected)); + assert.deepStrictEqual(Buffer.from("__--_--_--__", "base64url"), Buffer.from(expected)); + } + + const base64flavors = ["base64", "base64url"]; + + { + // Test that regular and URL-safe base64 both work both ways with padding + const expected = [0xff, 0xff, 0xbe, 0xff, 0xef, 0xbf, 0xfb, 0xef, 0xff, 0xfb]; + assert.deepStrictEqual(Buffer.from("//++/++/++//+w==", "base64"), Buffer.from(expected)); + assert.deepStrictEqual(Buffer.from("//++/++/++//+w==", "base64"), Buffer.from(expected)); + assert.deepStrictEqual(Buffer.from("//++/++/++//+w==", "base64url"), Buffer.from(expected)); + assert.deepStrictEqual(Buffer.from("//++/++/++//+w==", "base64url"), Buffer.from(expected)); + } + + { + // big example + const quote = + "Man is distinguished, not only by his reason, but by this " + + "singular passion from other animals, which is a lust " + + "of the mind, that by a perseverance of delight in the " + + "continued and indefatigable generation of knowledge, " + + "exceeds the short vehemence of any carnal pleasure."; + const expected = + "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb" + + "24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlci" + + "BhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQ" + + "gYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu" + + "dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZ" + + "GdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm" + + "5hbCBwbGVhc3VyZS4="; + assert.strictEqual(Buffer.from(quote).toString("base64"), expected); + assert.strictEqual( + Buffer.from(quote).toString("base64url"), + expected.replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", ""), + ); + + base64flavors.forEach(encoding => { + let b = Buffer.allocUnsafe(1024); + let bytesWritten = b.write(expected, 0, encoding); + assert.strictEqual(quote.length, bytesWritten); + assert.strictEqual(quote, b.toString("ascii", 0, quote.length)); + + // Check that the base64 decoder ignores whitespace + const expectedWhite = + `${expected.slice(0, 60)} \n` + + `${expected.slice(60, 120)} \n` + + `${expected.slice(120, 180)} \n` + + `${expected.slice(180, 240)} \n` + + `${expected.slice(240, 300)}\n` + + `${expected.slice(300, 360)}\n`; + b = Buffer.allocUnsafe(1024); + bytesWritten = b.write(expectedWhite, 0, encoding); + assert.strictEqual(quote.length, bytesWritten); + assert.strictEqual(quote, b.toString("ascii", 0, quote.length)); + + // Check that the base64 decoder on the constructor works + // even in the presence of whitespace. + b = Buffer.from(expectedWhite, encoding); + assert.strictEqual(quote.length, b.length); + assert.strictEqual(quote, b.toString("ascii", 0, quote.length)); + + // Check that the base64 decoder ignores illegal chars + const expectedIllegal = + expected.slice(0, 60) + + " \x80" + + expected.slice(60, 120) + + " \xff" + + expected.slice(120, 180) + + " \x00" + + expected.slice(180, 240) + + " \x98" + + expected.slice(240, 300) + + "\x03" + + expected.slice(300, 360); + b = Buffer.from(expectedIllegal, encoding); + assert.strictEqual(quote.length, b.length); + assert.strictEqual(quote, b.toString("ascii", 0, quote.length)); + }); + } + + base64flavors.forEach(encoding => { + assert.strictEqual(Buffer.from("", encoding).toString(), ""); + assert.strictEqual(Buffer.from("K", encoding).toString(), ""); + + // multiple-of-4 with padding + assert.strictEqual(Buffer.from("Kg==", encoding).toString(), "*"); + assert.strictEqual(Buffer.from("Kio=", encoding).toString(), "*".repeat(2)); + assert.strictEqual(Buffer.from("Kioq", encoding).toString(), "*".repeat(3)); + assert.strictEqual(Buffer.from("KioqKg==", encoding).toString(), "*".repeat(4)); + assert.strictEqual(Buffer.from("KioqKio=", encoding).toString(), "*".repeat(5)); + assert.strictEqual(Buffer.from("KioqKioq", encoding).toString(), "*".repeat(6)); + assert.strictEqual(Buffer.from("KioqKioqKg==", encoding).toString(), "*".repeat(7)); + assert.strictEqual(Buffer.from("KioqKioqKio=", encoding).toString(), "*".repeat(8)); + assert.strictEqual(Buffer.from("KioqKioqKioq", encoding).toString(), "*".repeat(9)); + assert.strictEqual(Buffer.from("KioqKioqKioqKg==", encoding).toString(), "*".repeat(10)); + assert.strictEqual(Buffer.from("KioqKioqKioqKio=", encoding).toString(), "*".repeat(11)); + assert.strictEqual(Buffer.from("KioqKioqKioqKioq", encoding).toString(), "*".repeat(12)); + assert.strictEqual(Buffer.from("KioqKioqKioqKioqKg==", encoding).toString(), "*".repeat(13)); + assert.strictEqual(Buffer.from("KioqKioqKioqKioqKio=", encoding).toString(), "*".repeat(14)); + assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioq", encoding).toString(), "*".repeat(15)); + assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKg==", encoding).toString(), "*".repeat(16)); + assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKio=", encoding).toString(), "*".repeat(17)); + assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKioq", encoding).toString(), "*".repeat(18)); + assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKioqKg==", encoding).toString(), "*".repeat(19)); + assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKioqKio=", encoding).toString(), "*".repeat(20)); + + // No padding, not a multiple of 4 + assert.strictEqual(Buffer.from("Kg", encoding).toString(), "*"); + assert.strictEqual(Buffer.from("Kio", encoding).toString(), "*".repeat(2)); + assert.strictEqual(Buffer.from("KioqKg", encoding).toString(), "*".repeat(4)); + assert.strictEqual(Buffer.from("KioqKio", encoding).toString(), "*".repeat(5)); + assert.strictEqual(Buffer.from("KioqKioqKg", encoding).toString(), "*".repeat(7)); + assert.strictEqual(Buffer.from("KioqKioqKio", encoding).toString(), "*".repeat(8)); + assert.strictEqual(Buffer.from("KioqKioqKioqKg", encoding).toString(), "*".repeat(10)); + assert.strictEqual(Buffer.from("KioqKioqKioqKio", encoding).toString(), "*".repeat(11)); + assert.strictEqual(Buffer.from("KioqKioqKioqKioqKg", encoding).toString(), "*".repeat(13)); + assert.strictEqual(Buffer.from("KioqKioqKioqKioqKio", encoding).toString(), "*".repeat(14)); + assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKg", encoding).toString(), "*".repeat(16)); + assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKio", encoding).toString(), "*".repeat(17)); + assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKioqKg", encoding).toString(), "*".repeat(19)); + assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKioqKio", encoding).toString(), "*".repeat(20)); + }); + + // Handle padding graciously, multiple-of-4 or not + assert.strictEqual(Buffer.from("72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw==", "base64").length, 32); + assert.strictEqual(Buffer.from("72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw==", "base64url").length, 32); + assert.strictEqual(Buffer.from("72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw=", "base64").length, 32); + assert.strictEqual(Buffer.from("72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw=", "base64url").length, 32); + assert.strictEqual(Buffer.from("72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw", "base64").length, 32); + assert.strictEqual(Buffer.from("72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw", "base64url").length, 32); + assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg==", "base64").length, 31); + assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg==", "base64url").length, 31); + assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg=", "base64").length, 31); + assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg=", "base64url").length, 31); + assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg", "base64").length, 31); + assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg", "base64url").length, 31); + + { + // This string encodes single '.' character in UTF-16 + const dot = Buffer.from("//4uAA==", "base64"); + assert.strictEqual(dot[0], 0xff); + assert.strictEqual(dot[1], 0xfe); + assert.strictEqual(dot[2], 0x2e); + assert.strictEqual(dot[3], 0x00); + assert.strictEqual(dot.toString("base64"), "//4uAA=="); + } + + { + // This string encodes single '.' character in UTF-16 + const dot = Buffer.from("//4uAA", "base64url"); + assert.strictEqual(dot[0], 0xff); + assert.strictEqual(dot[1], 0xfe); + assert.strictEqual(dot[2], 0x2e); + assert.strictEqual(dot[3], 0x00); + assert.strictEqual(dot.toString("base64url"), "__4uAA"); + } + + { + // Writing base64 at a position > 0 should not mangle the result. + // + // https://github.com/joyent/node/issues/402 + const segments = ["TWFkbmVzcz8h", "IFRoaXM=", "IGlz", "IG5vZGUuanMh"]; + const b = Buffer.allocUnsafe(64); + let pos = 0; + + for (let i = 0; i < segments.length; ++i) { + pos += b.write(segments[i], pos, "base64"); + } + assert.strictEqual(b.toString("latin1", 0, pos), "Madness?! This is node.js!"); + } + + { + // Writing base64url at a position > 0 should not mangle the result. + // + // https://github.com/joyent/node/issues/402 + const segments = ["TWFkbmVzcz8h", "IFRoaXM", "IGlz", "IG5vZGUuanMh"]; + const b = Buffer.allocUnsafe(64); + let pos = 0; + + for (let i = 0; i < segments.length; ++i) { + pos += b.write(segments[i], pos, "base64url"); + } + assert.strictEqual(b.toString("latin1", 0, pos), "Madness?! This is node.js!"); + } + + // Regression test for https://github.com/nodejs/node/issues/3496. + assert.strictEqual(Buffer.from("=bad".repeat(1e4), "base64").length, 0); + + // Regression test for https://github.com/nodejs/node/issues/11987. + assert.deepStrictEqual(Buffer.from("w0 ", "base64"), Buffer.from("w0", "base64")); + + // Regression test for https://github.com/nodejs/node/issues/13657. + assert.deepStrictEqual(Buffer.from(" YWJvcnVtLg", "base64"), Buffer.from("YWJvcnVtLg", "base64")); + + { + // Creating buffers larger than pool size. + const l = Buffer.poolSize + 5; + const s = "h".repeat(l); + const b = Buffer.from(s); + + for (let i = 0; i < l; i++) { + assert.strictEqual(b[i], "h".charCodeAt(0)); + } + + const sb = b.toString(); + assert.strictEqual(sb.length, s.length); + assert.strictEqual(sb, s); + } + + { + // test hex toString + const hexb = Buffer.allocUnsafe(256); + for (let i = 0; i < 256; i++) { + hexb[i] = i; + } + const hexStr = hexb.toString("hex"); + assert.strictEqual( + hexStr, + "000102030405060708090a0b0c0d0e0f" + + "101112131415161718191a1b1c1d1e1f" + + "202122232425262728292a2b2c2d2e2f" + + "303132333435363738393a3b3c3d3e3f" + + "404142434445464748494a4b4c4d4e4f" + + "505152535455565758595a5b5c5d5e5f" + + "606162636465666768696a6b6c6d6e6f" + + "707172737475767778797a7b7c7d7e7f" + + "808182838485868788898a8b8c8d8e8f" + + "909192939495969798999a9b9c9d9e9f" + + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" + + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" + + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" + + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" + + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" + + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + ); + + const hexb2 = Buffer.from(hexStr, "hex"); + for (let i = 0; i < 256; i++) { + assert.strictEqual(hexb2[i], hexb[i]); + } + } + + // Test single hex character is discarded. + assert.strictEqual(Buffer.from("A", "hex").length, 0); + + // Test that if a trailing character is discarded, rest of string is processed. + assert.deepStrictEqual(Buffer.from("Abx", "hex"), Buffer.from("Ab", "hex")); + + // Test single base64 char encodes as 0. + assert.strictEqual(Buffer.from("A", "base64").length, 0); + + { + // Test an invalid slice end. + const b = Buffer.from([1, 2, 3, 4, 5]); + const b2 = b.toString("hex", 1, 10000); + const b3 = b.toString("hex", 1, 5); + const b4 = b.toString("hex", 1); + assert.strictEqual(b2, b3); + assert.strictEqual(b2, b4); + } + + function buildBuffer(data) { + if (Array.isArray(data)) { + const buffer = Buffer.allocUnsafe(data.length); + data.forEach((v, k) => (buffer[k] = v)); + return buffer; + } + return null; + } + + const x = buildBuffer([0x81, 0xa3, 0x66, 0x6f, 0x6f, 0xa3, 0x62, 0x61, 0x72]); + + // assert.strictEqual(x.inspect(), ""); + + { + const z = x.slice(4); + assert.strictEqual(z.length, 5); + assert.strictEqual(z[0], 0x6f); + assert.strictEqual(z[1], 0xa3); + assert.strictEqual(z[2], 0x62); + assert.strictEqual(z[3], 0x61); + assert.strictEqual(z[4], 0x72); + } + + { + const z = x.slice(0); + assert.strictEqual(z.length, x.length); + } + + { + const z = x.slice(0, 4); + assert.strictEqual(z.length, 4); + assert.strictEqual(z[0], 0x81); + assert.strictEqual(z[1], 0xa3); + } + + { + const z = x.slice(0, 9); + assert.strictEqual(z.length, 9); + } + + { + const z = x.slice(1, 4); + assert.strictEqual(z.length, 3); + assert.strictEqual(z[0], 0xa3); + } + + { + const z = x.slice(2, 4); + assert.strictEqual(z.length, 2); + assert.strictEqual(z[0], 0x66); + assert.strictEqual(z[1], 0x6f); + } + + ["ucs2", "ucs-2", "utf16le", "utf-16le"].forEach(encoding => { + const b = Buffer.allocUnsafe(10); + b.write("あいうえお", encoding); + assert.strictEqual(b.toString(encoding), "あいうえお"); + }); + + ["ucs2", "ucs-2", "utf16le", "utf-16le"].forEach(encoding => { + const b = Buffer.allocUnsafe(11); + b.write("あいうえお", 1, encoding); + assert.strictEqual(b.toString(encoding, 1), "あいうえお"); + }); + + { + // latin1 encoding should write only one byte per character. + const b = Buffer.from([0xde, 0xad, 0xbe, 0xef]); + let s = String.fromCharCode(0xffff); + b.write(s, 0, "latin1"); + assert.strictEqual(b[0], 0xff); + assert.strictEqual(b[1], 0xad); + assert.strictEqual(b[2], 0xbe); + assert.strictEqual(b[3], 0xef); + s = String.fromCharCode(0xaaee); + b.write(s, 0, "latin1"); + assert.strictEqual(b[0], 0xee); + assert.strictEqual(b[1], 0xad); + assert.strictEqual(b[2], 0xbe); + assert.strictEqual(b[3], 0xef); + } + + { + // Binary encoding should write only one byte per character. + const b = Buffer.from([0xde, 0xad, 0xbe, 0xef]); + let s = String.fromCharCode(0xffff); + b.write(s, 0, "latin1"); + assert.strictEqual(b[0], 0xff); + assert.strictEqual(b[1], 0xad); + assert.strictEqual(b[2], 0xbe); + assert.strictEqual(b[3], 0xef); + s = String.fromCharCode(0xaaee); + b.write(s, 0, "latin1"); + assert.strictEqual(b[0], 0xee); + assert.strictEqual(b[1], 0xad); + assert.strictEqual(b[2], 0xbe); + assert.strictEqual(b[3], 0xef); + } + + { + // https://github.com/nodejs/node-v0.x-archive/pull/1210 + // Test UTF-8 string includes null character + let buf = Buffer.from("\0"); + assert.strictEqual(buf.length, 1); + buf = Buffer.from("\0\0"); + assert.strictEqual(buf.length, 2); + } + + { + const buf = Buffer.allocUnsafe(2); + assert.strictEqual(buf.write(""), 0); // 0bytes + assert.strictEqual(buf.write("\0"), 1); // 1byte (v8 adds null terminator) + assert.strictEqual(buf.write("a\0"), 2); // 1byte * 2 + assert.strictEqual(buf.write("あ"), 0); // 3bytes + assert.strictEqual(buf.write("\0あ"), 1); // 1byte + 3bytes + assert.strictEqual(buf.write("\0\0あ"), 2); // 1byte * 2 + 3bytes + } + + { + const buf = Buffer.allocUnsafe(10); + assert.strictEqual(buf.write("あいう"), 9); // 3bytes * 3 (v8 adds null term.) + assert.strictEqual(buf.write("あいう\0"), 10); // 3bytes * 3 + 1byte + } + + { + // https://github.com/nodejs/node-v0.x-archive/issues/243 + // Test write() with maxLength + const buf = Buffer.allocUnsafe(4); + buf.fill(0xff); + assert.strictEqual(buf.write("abcd", 1, 2, "utf8"), 2); + assert.strictEqual(buf[0], 0xff); + assert.strictEqual(buf[1], 0x61); + assert.strictEqual(buf[2], 0x62); + assert.strictEqual(buf[3], 0xff); + + buf.fill(0xff); + assert.strictEqual(buf.write("abcd", 1, 4), 3); + assert.strictEqual(buf[0], 0xff); + assert.strictEqual(buf[1], 0x61); + assert.strictEqual(buf[2], 0x62); + assert.strictEqual(buf[3], 0x63); + + buf.fill(0xff); + assert.strictEqual(buf.write("abcd", 1, 2, "utf8"), 2); + assert.strictEqual(buf[0], 0xff); + assert.strictEqual(buf[1], 0x61); + assert.strictEqual(buf[2], 0x62); + assert.strictEqual(buf[3], 0xff); + + buf.fill(0xff); + assert.strictEqual(buf.write("abcdef", 1, 2, "hex"), 2); + assert.strictEqual(buf[0], 0xff); + assert.strictEqual(buf[1], 0xab); + assert.strictEqual(buf[2], 0xcd); + assert.strictEqual(buf[3], 0xff); + + ["ucs2", "ucs-2", "utf16le", "utf-16le"].forEach(encoding => { + buf.fill(0xff); + assert.strictEqual(buf.write("abcd", 0, 2, encoding), 2); + assert.strictEqual(buf[0], 0x61); + assert.strictEqual(buf[1], 0x00); + assert.strictEqual(buf[2], 0xff); + assert.strictEqual(buf[3], 0xff); + }); + } + + { + // Test offset returns are correct + const b = Buffer.allocUnsafe(16); + assert.strictEqual(b.writeUInt32LE(0, 0), 4); + assert.strictEqual(b.writeUInt16LE(0, 4), 6); + assert.strictEqual(b.writeUInt8(0, 6), 7); + assert.strictEqual(b.writeInt8(0, 7), 8); + assert.strictEqual(b.writeDoubleLE(0, 8), 16); + } + + { + // Test unmatched surrogates not producing invalid utf8 output + // ef bf bd = utf-8 representation of unicode replacement character + // see https://codereview.chromium.org/121173009/ + let buf = Buffer.from("ab\ud800cd", "utf8"); + assert.strictEqual(buf[0], 0x61); + assert.strictEqual(buf[1], 0x62); + assert.strictEqual(buf[2], 0xef); + assert.strictEqual(buf[3], 0xbf); + assert.strictEqual(buf[4], 0xbd); + assert.strictEqual(buf[5], 0x63); + assert.strictEqual(buf[6], 0x64); + + buf = Buffer.from("abcd\ud800", "utf8"); + expect(buf[0]).toBe(0x61); + expect(buf[1]).toBe(0x62); + expect(buf[2]).toBe(0x63); + expect(buf[3]).toBe(0x64); + expect(buf[4]).toBe(0xef); + expect(buf[5]).toBe(0xbf); + expect(buf[6]).toBe(0xbd); + + buf = Buffer.from("\ud800abcd", "utf8"); + expect(buf[0]).toBe(0xef); + expect(buf[1]).toBe(0xbf); + expect(buf[2]).toBe(0xbd); + expect(buf[3]).toBe(0x61); + expect(buf[4]).toBe(0x62); + expect(buf[5]).toBe(0x63); + expect(buf[6]).toBe(0x64); + } + + { + // Test for buffer overrun + const buf = Buffer.from([0, 0, 0, 0, 0]); // length: 5 + const sub = buf.slice(0, 4); // length: 4 + assert.strictEqual(sub.write("12345", "latin1"), 4); + assert.strictEqual(buf[4], 0); + assert.strictEqual(sub.write("12345", "binary"), 4); + assert.strictEqual(buf[4], 0); + } + + { + // Test alloc with fill option + const buf = Buffer.alloc(5, "800A", "hex"); + assert.strictEqual(buf[0], 128); + assert.strictEqual(buf[1], 10); + assert.strictEqual(buf[2], 128); + assert.strictEqual(buf[3], 10); + assert.strictEqual(buf[4], 128); + } + + // Check for fractional length args, junk length args, etc. + // https://github.com/joyent/node/issues/1758 + + // Call .fill() first, stops valgrind warning about uninitialized memory reads. + Buffer.allocUnsafe(3.3).fill().toString(); + // Throws bad argument error in commit 43cb4ec + Buffer.alloc(3.3).fill().toString(); + assert.strictEqual(Buffer.allocUnsafe(3.3).length, 3); + assert.strictEqual(Buffer.from({ length: 3.3 }).length, 3); + assert.strictEqual(Buffer.from({ length: "BAM" }).length, 0); + + // Make sure that strings are not coerced to numbers. + assert.strictEqual(Buffer.from("99").length, 2); + assert.strictEqual(Buffer.from("13.37").length, 5); + + // Ensure that the length argument is respected. + ["ascii", "utf8", "hex", "base64", "latin1", "binary"].forEach(enc => { + assert.strictEqual(Buffer.allocUnsafe(1).write("aaaaaa", 0, 1, enc), 1); + }); + + { + // Regression test, guard against buffer overrun in the base64 decoder. + const a = Buffer.allocUnsafe(3); + const b = Buffer.from("xxx"); + a.write("aaaaaaaa", "base64"); + assert.strictEqual(b.toString(), "xxx"); + } + + // issue GH-3416 + Buffer.from(Buffer.allocUnsafe(0), 0, 0); + + // issue GH-5587 + assert.throws(() => Buffer.alloc(8).writeFloatLE(0, 5), outOfRangeError); + assert.throws(() => Buffer.alloc(16).writeDoubleLE(0, 9), outOfRangeError); + + // Attempt to overflow buffers, similar to previous bug in array buffers + assert.throws(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, 0xffffffff), outOfRangeError); + assert.throws(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, 0xffffffff), outOfRangeError); + + // Ensure negative values can't get past offset + assert.throws(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, -1), outOfRangeError); + assert.throws(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, -1), outOfRangeError); + + // Test for common write(U)IntLE/BE + { + let buf = Buffer.allocUnsafe(3); + buf.writeUIntLE(0x123456, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0x56, 0x34, 0x12]); + assert.strictEqual(buf.readUIntLE(0, 3), 0x123456); + + buf.fill(0xff); + buf.writeUIntBE(0x123456, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56]); + assert.strictEqual(buf.readUIntBE(0, 3), 0x123456); + + buf.fill(0xff); + buf.writeIntLE(0x123456, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0x56, 0x34, 0x12]); + assert.strictEqual(buf.readIntLE(0, 3), 0x123456); + + buf.fill(0xff); + buf.writeIntBE(0x123456, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56]); + assert.strictEqual(buf.readIntBE(0, 3), 0x123456); + + buf.fill(0xff); + buf.writeIntLE(-0x123456, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0xaa, 0xcb, 0xed]); + assert.strictEqual(buf.readIntLE(0, 3), -0x123456); + + buf.fill(0xff); + buf.writeIntBE(-0x123456, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0xed, 0xcb, 0xaa]); + assert.strictEqual(buf.readIntBE(0, 3), -0x123456); + + buf.fill(0xff); + buf.writeIntLE(-0x123400, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0x00, 0xcc, 0xed]); + assert.strictEqual(buf.readIntLE(0, 3), -0x123400); + + buf.fill(0xff); + buf.writeIntBE(-0x123400, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0xed, 0xcc, 0x00]); + assert.strictEqual(buf.readIntBE(0, 3), -0x123400); + + buf.fill(0xff); + buf.writeIntLE(-0x120000, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0x00, 0x00, 0xee]); + assert.strictEqual(buf.readIntLE(0, 3), -0x120000); + + buf.fill(0xff); + buf.writeIntBE(-0x120000, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0xee, 0x00, 0x00]); + assert.strictEqual(buf.readIntBE(0, 3), -0x120000); + + buf = Buffer.allocUnsafe(5); + buf.writeUIntLE(0x1234567890, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0x90, 0x78, 0x56, 0x34, 0x12]); + assert.strictEqual(buf.readUIntLE(0, 5), 0x1234567890); + + buf.fill(0xff); + buf.writeUIntBE(0x1234567890, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56, 0x78, 0x90]); + assert.strictEqual(buf.readUIntBE(0, 5), 0x1234567890); + + buf.fill(0xff); + buf.writeIntLE(0x1234567890, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0x90, 0x78, 0x56, 0x34, 0x12]); + assert.strictEqual(buf.readIntLE(0, 5), 0x1234567890); + + buf.fill(0xff); + buf.writeIntBE(0x1234567890, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56, 0x78, 0x90]); + assert.strictEqual(buf.readIntBE(0, 5), 0x1234567890); + + buf.fill(0xff); + buf.writeIntLE(-0x1234567890, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0x70, 0x87, 0xa9, 0xcb, 0xed]); + assert.strictEqual(buf.readIntLE(0, 5), -0x1234567890); + + buf.fill(0xff); + buf.writeIntBE(-0x1234567890, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0xed, 0xcb, 0xa9, 0x87, 0x70]); + assert.strictEqual(buf.readIntBE(0, 5), -0x1234567890); + + buf.fill(0xff); + buf.writeIntLE(-0x0012000000, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0x00, 0x00, 0x00, 0xee, 0xff]); + assert.strictEqual(buf.readIntLE(0, 5), -0x0012000000); + + buf.fill(0xff); + buf.writeIntBE(-0x0012000000, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0xff, 0xee, 0x00, 0x00, 0x00]); + assert.strictEqual(buf.readIntBE(0, 5), -0x0012000000); + } + + // Regression test for https://github.com/nodejs/node-v0.x-archive/issues/5482: + // should throw but not assert in C++ land. + assert.throws(() => Buffer.from("", "buffer"), { + code: "ERR_UNKNOWN_ENCODING", + name: "TypeError", + message: "Unknown encoding: buffer", + }); + + // Regression test for https://github.com/nodejs/node-v0.x-archive/issues/6111. + // Constructing a buffer from another buffer should a) work, and b) not corrupt + // the source buffer. + { + const a = [...Array(128).keys()]; // [0, 1, 2, 3, ... 126, 127] + const b = Buffer.from(a); + const c = Buffer.from(b); + assert.strictEqual(b.length, a.length); + assert.strictEqual(c.length, a.length); + for (let i = 0, k = a.length; i < k; ++i) { + assert.strictEqual(a[i], i); + assert.strictEqual(b[i], i); + assert.strictEqual(c[i], i); + } + } + + // if (common.hasCrypto) { + // eslint-disable-line node-core/crypto-check + // Test truncation after decode + const crypto = require("crypto"); + + const b1 = Buffer.from("YW55=======", "base64"); + const b2 = Buffer.from("YW55", "base64"); + + assert.strictEqual( + crypto.createHash("sha1").update(b1).digest("hex"), + crypto.createHash("sha1").update(b2).digest("hex"), + ); + // } else { + // common.printSkipMessage("missing crypto"); + // } + + const ps = Buffer.poolSize; + Buffer.poolSize = 0; + assert(Buffer.allocUnsafe(1).parent instanceof ArrayBuffer); + Buffer.poolSize = ps; + + assert.throws(() => Buffer.allocUnsafe(10).copy(), { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + message: 'The "target" argument must be an instance of Buffer or ' + "Uint8Array. Received undefined", + }); + + assert.throws(() => Buffer.from(), { + name: "TypeError", + message: + "The first argument must be of type string or an instance of " + + "Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined", + }); + assert.throws(() => Buffer.from(null), { + name: "TypeError", + message: + "The first argument must be of type string or an instance of " + + "Buffer, ArrayBuffer, or Array or an Array-like Object. Received null", + }); + + // Test prototype getters don't throw + assert.strictEqual(Buffer.prototype.parent, undefined); + assert.strictEqual(Buffer.prototype.offset, undefined); + assert.strictEqual(SlowBuffer.prototype.parent, undefined); + assert.strictEqual(SlowBuffer.prototype.offset, undefined); + + { + // Test that large negative Buffer length inputs don't affect the pool offset. + // Use the fromArrayLike() variant here because it's more lenient + // about its input and passes the length directly to allocate(). + assert.deepStrictEqual(Buffer.from({ length: -Buffer.poolSize }), Buffer.from("")); + assert.deepStrictEqual(Buffer.from({ length: -100 }), Buffer.from("")); + + // Check pool offset after that by trying to write string into the pool. + Buffer.from("abc"); + } + + // Test that ParseArrayIndex handles full uint32 + { + const errMsg = common.expectsError({ + code: "ERR_BUFFER_OUT_OF_BOUNDS", + name: "RangeError", + message: '"offset" is outside of buffer bounds', + }); + assert.throws(() => Buffer.from(new ArrayBuffer(0), -1 >>> 0), errMsg); + } + + // ParseArrayIndex() should reject values that don't fit in a 32 bits size_t. + assert.throws(() => { + const a = Buffer.alloc(1); + const b = Buffer.alloc(1); + a.copy(b, 0, 0x100000000, 0x100000001); + }, outOfRangeError); + + // Unpooled buffer (replaces SlowBuffer) + { + const ubuf = Buffer.allocUnsafeSlow(10); + assert(ubuf); + assert(ubuf.buffer); + assert.strictEqual(ubuf.buffer.byteLength, 10); + } + + // Regression test to verify that an empty ArrayBuffer does not throw. + Buffer.from(new ArrayBuffer()); + + // Test that ArrayBuffer from a different context is detected correctly. + // const arrayBuf = vm.runInNewContext("new ArrayBuffer()"); + // Buffer.from(arrayBuf); + // Buffer.from({ buffer: arrayBuf }); + + assert.throws(() => Buffer.alloc({ valueOf: () => 1 }), /"size" argument must be of type number/); + assert.throws(() => Buffer.alloc({ valueOf: () => -1 }), /"size" argument must be of type number/); + + assert.strictEqual(Buffer.prototype.toLocaleString, Buffer.prototype.toString); + { + const buf = Buffer.from("test"); + assert.strictEqual(buf.toLocaleString(), buf.toString()); + } + + assert.throws( + () => { + Buffer.alloc(0x1000, "This is not correctly encoded", "hex"); + }, + { + code: "ERR_INVALID_ARG_VALUE", + name: "TypeError", + }, + ); + + assert.throws( + () => { + Buffer.alloc(0x1000, "c", "hex"); + }, + { + code: "ERR_INVALID_ARG_VALUE", + name: "TypeError", + }, + ); + + assert.throws( + () => { + Buffer.alloc(1, Buffer.alloc(0)); + }, + { + code: "ERR_INVALID_ARG_VALUE", + name: "TypeError", + }, + ); + + assert.throws( + () => { + Buffer.alloc(40, "x", 20); + }, + { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + }, + ); +}); + +it("Buffer.toJSON()", () => { + expect(JSON.stringify(Buffer.from("hello"))).toBe( + JSON.stringify({ + type: "Buffer", + data: [104, 101, 108, 108, 111], + }), + ); +}); + +it("buffer", () => { + var buf = new Buffer(20); + gc(); + // if this fails or infinitely loops, it means there is a memory issue with the JSC::Structure object + expect(Object.keys(buf).length > 0).toBe(true); + gc(); + expect(buf.write("hello world ")).toBe(12); + expect(buf.write("hello world ", "utf8")).toBe(12); + + gc(); + expect(buf.toString("utf8", 0, "hello world ".length)).toBe("hello world "); + gc(); + expect(buf.toString("base64url", 0, "hello world ".length)).toBe(btoa("hello world ")); + gc(); + expect(buf instanceof Uint8Array).toBe(true); + gc(); + expect(buf instanceof Buffer).toBe(true); + gc(); + expect(buf.slice() instanceof Uint8Array).toBe(true); + gc(); + expect(buf.slice(0, 1) instanceof Buffer).toBe(true); + gc(); + expect(buf.slice(0, 1) instanceof Uint8Array).toBe(true); + gc(); + expect(buf.slice(0, 1) instanceof Buffer).toBe(true); + gc(); + expect(buf.slice(0, 0).length).toBe(0); +}); + +it("Buffer", () => { + var inputs = ["hello world", "hello world".repeat(100), `😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌`]; + var good = inputs.map(a => new TextEncoder().encode(a)); + for (let i = 0; i < inputs.length; i++) { + var input = inputs[i]; + expect(new Buffer(input).toString("utf8")).toBe(inputs[i]); + gc(); + expect(Array.from(new Buffer(input)).join(",")).toBe(good[i].join(",")); + gc(); + expect(Buffer.byteLength(input)).toBe(good[i].length); + gc(); + expect(Buffer.from(input).byteLength).toBe(Buffer.byteLength(input)); + } +}); + +it("Buffer.byteLength", () => { + expect(Buffer.byteLength("😀😃😄😁😆😅😂🤣☺️😊😊😇")).toBe( + new TextEncoder().encode("😀😃😄😁😆😅😂🤣☺️😊😊😇").byteLength, + ); +}); + +it("Buffer.isBuffer", () => { + expect(Buffer.isBuffer(new Buffer(1))).toBe(true); + gc(); + expect(Buffer.isBuffer(new Buffer(0))).toBe(true); + gc(); + expect(Buffer.isBuffer(new Uint8Array(0))).toBe(false); + gc(); + expect(Buffer.isBuffer(new Uint8Array(1))).toBe(false); + gc(); + var a = new Uint8Array(1); + gc(); + expect(Buffer.isBuffer(a)).toBe(false); + gc(); + a = new Buffer(a.buffer); + gc(); + expect(Buffer.isBuffer(a)).toBe(true); + gc(); + expect(a instanceof Buffer).toBe(true); + expect(a instanceof Uint8Array).toBe(true); + expect(new Uint8Array(0) instanceof Buffer).toBe(false); + + // DOMJIT + for (let i = 0; i < 9000; i++) { + if (!Buffer.isBuffer(a)) { + throw new Error("Buffer.isBuffer failed"); + } + + if (Buffer.isBuffer("wat")) { + throw new Error("Buffer.isBuffer failed"); + } + } +}); + +it("writeInt", () => { + var buf = new Buffer(1024); + var data = new DataView(buf.buffer); + buf.writeInt32BE(100); + expect(data.getInt32(0, false)).toBe(100); + buf.writeInt32BE(100); + expect(data.getInt32(0, false)).toBe(100); + var childBuf = buf.subarray(0, 4); + expect(data.getInt32(0, false)).toBe(100); + expect(childBuf.readInt32BE(0, false)).toBe(100); +}); + +it("Buffer.from", () => { + expect(Buffer.from("hello world").toString("utf8")).toBe("hello world"); + expect(Buffer.from("hello world", "ascii").toString("utf8")).toBe("hello world"); + expect(Buffer.from("hello world", "latin1").toString("utf8")).toBe("hello world"); + gc(); + expect(Buffer.from([254]).join(",")).toBe("254"); + + expect(Buffer.from([254], "utf8").join(",")).toBe("254"); + expect(Buffer.from([254], "utf-8").join(",")).toBe("254"); + expect(Buffer.from([254], "latin").join(",")).toBe("254"); + expect(Buffer.from([254], "uc2").join(",")).toBe("254"); + expect(Buffer.from([254], "utf16").join(",")).toBe("254"); + expect(Buffer.isBuffer(Buffer.from([254], "utf16"))).toBe(true); + + expect(() => Buffer.from(123).join(",")).toThrow(); + + expect(Buffer.from({ length: 124 }).join(",")).toBe(Uint8Array.from({ length: 124 }).join(",")); + + expect(Buffer.from(new ArrayBuffer(1024), 0, 512).join(",")).toBe(new Uint8Array(512).join(",")); + + expect(Buffer.from(new Buffer(new ArrayBuffer(1024), 0, 512)).join(",")).toBe(new Uint8Array(512).join(",")); + gc(); +}); + +it("Buffer.from latin1 vs ascii", () => { + const simpleBuffer = Buffer.from("\xa4", "binary"); + expect(simpleBuffer.toString("latin1")).toBe("¤"); + expect(simpleBuffer.toString("ascii")).toBe("$"); + gc(); + const asciiBuffer = Buffer.from("\xa4", "ascii"); + expect(asciiBuffer.toString("latin1")).toBe("¤"); + expect(asciiBuffer.toString("ascii")).toBe("$"); + gc(); +}); + +it("Buffer.equals", () => { + var a = new Uint8Array(10); + a[2] = 1; + var b = new Uint8Array(10); + b[2] = 1; + a = new Buffer(a.buffer); + b = new Buffer(b.buffer); + expect(a.equals(b)).toBe(true); + b[2] = 0; + expect(a.equals(b)).toBe(false); +}); + +it("Buffer.compare", () => { + var a = new Uint8Array(10); + a[2] = 1; + var b = new Uint8Array(10); + b[2] = 1; + a = new Buffer(a.buffer); + b = new Buffer(b.buffer); + expect(a.compare(b)).toBe(0); + b[2] = 0; + expect(a.compare(b)).toBe(1); + expect(b.compare(a)).toBe(-1); + + const buf = Buffer.from("0123456789", "utf8"); + const expectedSameBufs = [ + [buf.slice(-10, 10), Buffer.from("0123456789", "utf8")], + [buf.slice(-20, 10), Buffer.from("0123456789", "utf8")], + [buf.slice(-20, -10), Buffer.from("", "utf8")], + [buf.slice(), Buffer.from("0123456789", "utf8")], + [buf.slice(0), Buffer.from("0123456789", "utf8")], + [buf.slice(0, 0), Buffer.from("", "utf8")], + [buf.slice(undefined), Buffer.from("0123456789", "utf8")], + [buf.slice("foobar"), Buffer.from("0123456789", "utf8")], + [buf.slice(undefined, undefined), Buffer.from("0123456789", "utf8")], + [buf.slice(2), Buffer.from("23456789", "utf8")], + [buf.slice(5), Buffer.from("56789", "utf8")], + [buf.slice(10), Buffer.from("", "utf8")], + [buf.slice(5, 8), Buffer.from("567", "utf8")], + [buf.slice(8, -1), Buffer.from("8", "utf8")], + [buf.slice(-10), Buffer.from("0123456789", "utf8")], + [buf.slice(0, -9), Buffer.from("0", "utf8")], + [buf.slice(0, -10), Buffer.from("", "utf8")], + [buf.slice(0, -1), Buffer.from("012345678", "utf8")], + [buf.slice(2, -2), Buffer.from("234567", "utf8")], + [buf.slice(0, 65536), Buffer.from("0123456789", "utf8")], + [buf.slice(65536, 0), Buffer.from("", "utf8")], + [buf.slice(-5, -8), Buffer.from("", "utf8")], + [buf.slice(-5, -3), Buffer.from("56", "utf8")], + [buf.slice(-10, 10), Buffer.from("0123456789", "utf8")], + [buf.slice("0", "1"), Buffer.from("0", "utf8")], + [buf.slice("-5", "10"), Buffer.from("56789", "utf8")], + [buf.slice("-10", "10"), Buffer.from("0123456789", "utf8")], + [buf.slice("-10", "-5"), Buffer.from("01234", "utf8")], + [buf.slice("-10", "-0"), Buffer.from("", "utf8")], + [buf.slice("111"), Buffer.from("", "utf8")], + [buf.slice("0", "-111"), Buffer.from("", "utf8")], + ]; + + for (let i = 0, s = buf.toString(); i < buf.length; ++i) { + expectedSameBufs.push( + [buf.slice(i), Buffer.from(s.slice(i))], + [buf.slice(0, i), Buffer.from(s.slice(0, i))], + [buf.slice(-i), Buffer.from(s.slice(-i))], + [buf.slice(0, -i), Buffer.from(s.slice(0, -i))], + ); + } + + expectedSameBufs.forEach(([buf1, buf2]) => { + expect(Buffer.compare(buf1, buf2)).toBe(0); + }); + + { + const buf = Buffer.from([ + 1, 29, 0, 0, 1, 143, 216, 162, 92, 254, 248, 63, 0, 0, 0, 18, 184, 6, 0, 175, 29, 0, 8, 11, 1, 0, 0, + ]); + const chunk1 = Buffer.from([1, 29, 0, 0, 1, 143, 216, 162, 92, 254, 248, 63, 0]); + const chunk2 = Buffer.from([0, 0, 18, 184, 6, 0, 175, 29, 0, 8, 11, 1, 0, 0]); + const middle = buf.length / 2; + + expect(JSON.stringify(buf.slice(0, middle))).toBe(JSON.stringify(chunk1)); + expect(JSON.stringify(buf.slice(middle))).toBe(JSON.stringify(chunk2)); + } +}); + +it("Buffer.copy", () => { + var array1 = new Uint8Array(128); + array1.fill(100); + array1 = new Buffer(array1.buffer); + var array2 = new Uint8Array(128); + array2.fill(200); + array2 = new Buffer(array2.buffer); + var array3 = new Uint8Array(128); + array3 = new Buffer(array3.buffer); + gc(); + expect(array1.copy(array2)).toBe(128); + expect(array1.join("")).toBe(array2.join("")); + + { + // Create two `Buffer` instances. + const buf1 = Buffer.allocUnsafe(26); + const buf2 = Buffer.allocUnsafe(26).fill("!"); + + for (let i = 0; i < 26; i++) { + // 97 is the decimal ASCII value for 'a'. + buf1[i] = i + 97; + } + + // Copy `buf1` bytes 16 through 19 into `buf2` starting at byte 8 of `buf2`. + buf1.copy(buf2, 8, 16, 20); + expect(buf2.toString("ascii", 0, 25)).toBe("!!!!!!!!qrst!!!!!!!!!!!!!"); + } + + { + const buf = Buffer.allocUnsafe(26); + + for (let i = 0; i < 26; i++) { + // 97 is the decimal ASCII value for 'a'. + buf[i] = i + 97; + } + + buf.copy(buf, 0, 4, 10); + expect(buf.toString()).toBe("efghijghijklmnopqrstuvwxyz"); + } +}); + +export function fillRepeating(dstBuffer, start, end) { + let len = dstBuffer.length, // important: use indices length, not byte-length + sLen = end - start, + p = sLen; // set initial position = source sequence length + + // step 2: copy existing data doubling segment length per iteration + while (p < len) { + if (p + sLen > len) sLen = len - p; // if not power of 2, truncate last segment + dstBuffer.copyWithin(p, start, sLen); // internal copy + p += sLen; // add current length to offset + sLen <<= 1; // double length for next segment + } +} + +describe("Buffer.fill string", () => { + for (let text of ["hello world", "1234567890", "\uD83D\uDE00", "😀😃😄😁😆😅😂🤣☺️😊😊😇"]) { + it(text, () => { + var input = new Buffer(1024); + input.fill(text); + var demo = new Uint8Array(1024); + var encoded = new TextEncoder().encode(text); + + demo.set(encoded); + fillRepeating(demo, 0, encoded.length); + expect(input.join("")).toBe(demo.join("")); + }); + } +}); + +it("Buffer.fill 1 char string", () => { + var input = new Buffer(1024); + input.fill("h"); + var demo = new Uint8Array(1024); + var encoded = new TextEncoder().encode("h"); + + demo.set(encoded); + fillRepeating(demo, 0, encoded.length); + expect(input.join("")).toBe(demo.join("")); +}); + +it("Buffer.concat", () => { + var array1 = new Uint8Array(128); + array1.fill(100); + var array2 = new Uint8Array(128); + array2.fill(200); + var array3 = new Uint8Array(128); + array3.fill(300); + gc(); + expect(Buffer.concat([array1, array2, array3]).join("")).toBe(array1.join("") + array2.join("") + array3.join("")); + expect(Buffer.concat([array1, array2, array3], 222).length).toBe(222); + expect(Buffer.concat([array1, array2, array3], 222).subarray(0, 128).join("")).toBe("100".repeat(128)); + expect(Buffer.concat([array1, array2, array3], 222).subarray(129, 222).join("")).toBe("200".repeat(222 - 129)); +}); + +it("read", () => { + var buf = new Buffer(1024); + var data = new DataView(buf.buffer); + function reset() { + new Uint8Array(buf.buffer).fill(0); + } + data.setBigInt64(0, BigInt(1000), false); + expect(buf.readBigInt64BE(0)).toBe(BigInt(1000)); + reset(); + + data.setBigInt64(0, BigInt(1000), true); + expect(buf.readBigInt64LE(0)).toBe(BigInt(1000)); + reset(); + + data.setBigUint64(0, BigInt(1000), false); + expect(buf.readBigUInt64BE(0)).toBe(BigInt(1000)); + reset(); + + data.setBigUint64(0, BigInt(1000), true); + expect(buf.readBigUInt64LE(0)).toBe(BigInt(1000)); + reset(); + + data.setFloat64(0, 1000, false); + expect(buf.readDoubleBE(0)).toBe(1000); + reset(); + + data.setFloat64(0, 1000, true); + expect(buf.readDoubleLE(0)).toBe(1000); + reset(); + + data.setFloat32(0, 1000, false); + expect(buf.readFloatBE(0)).toBe(1000); + reset(); + + data.setFloat32(0, 1000, true); + expect(buf.readFloatLE(0)).toBe(1000); + reset(); + + data.setInt16(0, 1000, false); + expect(buf.readInt16BE(0)).toBe(1000); + reset(); + + data.setInt16(0, 1000, true); + expect(buf.readInt16LE(0)).toBe(1000); + reset(); + + data.setInt32(0, 1000, false); + expect(buf.readInt32BE(0)).toBe(1000); + reset(); + + data.setInt32(0, 1000, true); + expect(buf.readInt32LE(0)).toBe(1000); + reset(); + + data.setInt8(0, 100, false); + expect(buf.readInt8(0)).toBe(100); + reset(); + + data.setUint16(0, 1000, false); + expect(buf.readUInt16BE(0)).toBe(1000); + reset(); + + data.setUint16(0, 1000, true); + expect(buf.readUInt16LE(0)).toBe(1000); + reset(); + + data.setUint32(0, 1000, false); + expect(buf.readUInt32BE(0)).toBe(1000); + reset(); + + data.setUint32(0, 1000, true); + expect(buf.readUInt32LE(0)).toBe(1000); + reset(); + + data.setUint8(0, 255, false); + expect(buf.readUInt8(0)).toBe(255); + reset(); + + data.setUint8(0, 255, false); + expect(buf.readUInt8(0)).toBe(255); + reset(); +}); + +// this is for checking the simd code path +it("write long utf16 string works", () => { + const long = "😀😃😄😁😆😅😂🤣☺️😊😊😇".repeat(200); + const buf = Buffer.alloc(long.length * 2); + buf.write(long, 0, "utf16le"); + expect(buf.toString("utf16le")).toBe(long); + for (let offset = 0; offset < long.length; offset += 48) { + expect(buf.toString("utf16le", offset, offset + 4)).toBe("😀"); + expect(buf.toString("utf16le", offset, offset + 8)).toBe("😀😃"); + expect(buf.toString("utf16le", offset, offset + 12)).toBe("😀😃😄"); + expect(buf.toString("utf16le", offset, offset + 16)).toBe("😀😃😄😁"); + expect(buf.toString("utf16le", offset, offset + 20)).toBe("😀😃😄😁😆"); + expect(buf.toString("utf16le", offset, offset + 24)).toBe("😀😃😄😁😆😅"); + expect(buf.toString("utf16le", offset, offset + 28)).toBe("😀😃😄😁😆😅😂"); + expect(buf.toString("utf16le", offset, offset + 32)).toBe("😀😃😄😁😆😅😂🤣"); + expect(buf.toString("utf16le", offset, offset + 36)).toBe("😀😃😄😁😆😅😂🤣☺️"); + expect(buf.toString("utf16le", offset, offset + 40)).toBe("😀😃😄😁😆😅😂🤣☺️😊"); + expect(buf.toString("utf16le", offset, offset + 44)).toBe("😀😃😄😁😆😅😂🤣☺️😊😊"); + expect(buf.toString("utf16le", offset, offset + 48)).toBe("😀😃😄😁😆😅😂🤣☺️😊😊😇"); + } +}); + +it("write", () => { + const resultMap = new Map([ + ["utf8", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ["ucs2", Buffer.from([102, 0, 111, 0, 111, 0, 0, 0, 0])], + ["ascii", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ["latin1", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ["binary", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ["utf16le", Buffer.from([102, 0, 111, 0, 111, 0, 0, 0, 0])], + ["base64", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ["base64url", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ["hex", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ]); + + let buf = Buffer.alloc(9); + function reset() { + new Uint8Array(buf.buffer).fill(0); + } + + // utf8, ucs2, ascii, latin1, utf16le + const encodings = ["utf8", "utf-8", "ucs2", "ucs-2", "ascii", "latin1", "binary", "utf16le", "utf-16le"]; + + encodings + .reduce((es, e) => es.concat(e, e.toUpperCase()), []) + .forEach(encoding => { + reset(); + + const len = Buffer.byteLength("foo", encoding); + expect(buf.write("foo", 0, len, encoding)).toBe(len); + + if (encoding.includes("-")) encoding = encoding.replace("-", ""); + + expect(buf).toStrictEqual(resultMap.get(encoding.toLowerCase())); + }); + + // base64 + ["base64", "BASE64", "base64url", "BASE64URL"].forEach(encoding => { + reset(); + + const len = Buffer.byteLength("Zm9v", encoding); + + expect(buf.write("Zm9v", 0, len, encoding)).toBe(len); + expect(buf).toStrictEqual(resultMap.get(encoding.toLowerCase())); + }); + + // hex + ["hex", "HEX"].forEach(encoding => { + reset(); + const len = Buffer.byteLength("666f6f", encoding); + + expect(buf.write("666f6f", 0, len, encoding)).toBe(len); + expect(buf).toStrictEqual(resultMap.get(encoding.toLowerCase())); + }); + + // UCS-2 overflow CVE-2018-12115 + for (let i = 1; i < 4; i++) { + // Allocate two Buffers sequentially off the pool. Run more than once in case + // we hit the end of the pool and don't get sequential allocations + const x = Buffer.allocUnsafe(4).fill(0); + const y = Buffer.allocUnsafe(4).fill(1); + // Should not write anything, pos 3 doesn't have enough room for a 16-bit char + expect(x.write("ыыыыыы", 3, "ucs2")).toBe(0); + // CVE-2018-12115 experienced via buffer overrun to next block in the pool + expect(Buffer.compare(y, Buffer.alloc(4, 1))).toBe(0); + } + + // // Should not write any data when there is no space for 16-bit chars + const z = Buffer.alloc(4, 0); + expect(z.write("\u0001", 3, "ucs2")).toBe(0); + expect(Buffer.compare(z, Buffer.alloc(4, 0))).toBe(0); + // Make sure longer strings are written up to the buffer end. + expect(z.write("abcd", 2)).toBe(2); + expect([...z]).toStrictEqual([0, 0, 0x61, 0x62]); + + //Large overrun could corrupt the process with utf8 + expect(Buffer.alloc(4).write("a".repeat(100), 3, "utf8")).toBe(1); + + // Large overrun could corrupt the process + expect(Buffer.alloc(4).write("ыыыыыы".repeat(100), 3, "utf16le")).toBe(0); + + { + // .write() does not affect the byte after the written-to slice of the Buffer. + // Refs: https://github.com/nodejs/node/issues/26422 + const buf = Buffer.alloc(8); + expect(buf.write("ыы", 1, "utf16le")).toBe(4); + expect([...buf]).toStrictEqual([0, 0x4b, 0x04, 0x4b, 0x04, 0, 0, 0]); + } +}); + +it("includes", () => { + const buf = Buffer.from("this is a buffer"); + + expect(buf.includes("this")).toBe(true); + expect(buf.includes("is")).toBe(true); + expect(buf.includes(Buffer.from("a buffer"))).toBe(true); + expect(buf.includes(97)).toBe(true); + expect(buf.includes(Buffer.from("a buffer example"))).toBe(false); + expect(buf.includes(Buffer.from("a buffer example").slice(0, 8))).toBe(true); + expect(buf.includes("this", 4)).toBe(false); +}); + +it("indexOf", () => { + const buf = Buffer.from("this is a buffer"); + + expect(buf.indexOf("this")).toBe(0); + expect(buf.indexOf("is")).toBe(2); + expect(buf.indexOf(Buffer.from("a buffer"))).toBe(8); + expect(buf.indexOf(97)).toBe(8); + expect(buf.indexOf(Buffer.from("a buffer example"))).toBe(-1); + expect(buf.indexOf(Buffer.from("a buffer example").slice(0, 8))).toBe(8); + + const utf16Buffer = Buffer.from("\u039a\u0391\u03a3\u03a3\u0395", "utf16le"); + + expect(utf16Buffer.indexOf("\u03a3", 0, "utf16le")).toBe(4); + expect(utf16Buffer.indexOf("\u03a3", -4, "utf16le")).toBe(6); + + const b = Buffer.from("abcdef"); + + // Passing a value that's a number, but not a valid byte. + // Prints: 2, equivalent to searching for 99 or 'c'. + expect(b.indexOf(99.9)).toBe(2); + expect(b.indexOf(256 + 99)).toBe(2); + + // Passing a byteOffset that coerces to NaN or 0. + // Prints: 1, searching the whole buffer. + expect(b.indexOf("b", undefined)).toBe(1); + expect(b.indexOf("b", {})).toBe(1); + expect(b.indexOf("b", null)).toBe(1); + expect(b.indexOf("b", [])).toBe(1); +}); + +it("lastIndexOf", () => { + const buf = Buffer.from("this buffer is a buffer"); + + expect(buf.lastIndexOf("this")).toBe(0); + expect(buf.lastIndexOf("this", 0)).toBe(0); + expect(buf.lastIndexOf("this", -1000)).toBe(-1); + expect(buf.lastIndexOf("buffer")).toBe(17); + expect(buf.lastIndexOf(Buffer.from("buffer"))).toBe(17); + expect(buf.lastIndexOf(97)).toBe(15); + expect(buf.lastIndexOf(Buffer.from("yolo"))).toBe(-1); + expect(buf.lastIndexOf("buffer", 5)).toBe(5); + expect(buf.lastIndexOf("buffer", 4)).toBe(-1); + + const utf16Buffer = Buffer.from("\u039a\u0391\u03a3\u03a3\u0395", "utf16le"); + + expect(utf16Buffer.lastIndexOf("\u03a3", undefined, "utf16le")).toBe(6); + expect(utf16Buffer.lastIndexOf("\u03a3", -5, "utf16le")).toBe(4); + + const b = Buffer.from("abcdef"); + + // Passing a value that's a number, but not a valid byte. + // Prints: 2, equivalent to searching for 99 or 'c'. + expect(b.lastIndexOf(99.9)).toBe(2); + expect(b.lastIndexOf(256 + 99)).toBe(2); + + // Passing a byteOffset that coerces to NaN or 0. + // Prints: 1, searching the whole buffer. + expect(b.lastIndexOf("b", undefined)).toBe(1); + expect(b.lastIndexOf("b", {})).toBe(1); + + // Passing a byteOffset that coerces to 0. + // Prints: -1, equivalent to passing 0. + expect(b.lastIndexOf("b", null)).toBe(-1); + expect(b.lastIndexOf("b", [])).toBe(-1); +}); + +for (let fn of [Buffer.prototype.slice, Buffer.prototype.subarray]) { + it(`Buffer.${fn.name}`, () => { + const buf = new Buffer("buffer"); + const slice = fn.call(buf, 1, 3); + expect(slice.toString()).toBe("uf"); + const slice2 = fn.call(slice, 100); + expect(slice2.toString()).toBe(""); + + const slice3 = fn.call(slice, -1); + expect(slice3.toString()).toBe("f"); + }); +} + +it("Buffer.from(base64)", () => { + const buf = Buffer.from("aGVsbG8gd29ybGQ=", "base64"); + expect(buf.toString()).toBe("hello world"); + + expect(Buffer.from(btoa('console.log("hello world")\n'), "base64").toString()).toBe('console.log("hello world")\n'); +}); + +it("Buffer.swap16", () => { + const examples = [ + ["", ""], + ["a1", "1a"], + ["a1b2", "1a2b"], + ]; + + for (let i = 0; i < examples.length; i++) { + const input = examples[i][0]; + const output = examples[i][1]; + const buf = Buffer.from(input, "utf-8"); + + const ref = buf.swap16(); + expect(ref instanceof Buffer).toBe(true); + expect(buf.toString()).toBe(output); + } + + const buf = Buffer.from("123", "utf-8"); + try { + buf.swap16(); + expect(false).toBe(true); + } catch (exception) { + expect(exception.message).toBe("Buffer size must be a multiple of 16-bits"); + } +}); + +it("Buffer.swap32", () => { + const examples = [ + ["", ""], + ["a1b2", "2b1a"], + ["a1b2c3d4", "2b1a4d3c"], + ]; + + for (let i = 0; i < examples.length; i++) { + const input = examples[i][0]; + const output = examples[i][1]; + const buf = Buffer.from(input, "utf-8"); + + const ref = buf.swap32(); + expect(ref instanceof Buffer).toBe(true); + expect(buf.toString()).toBe(output); + } + + const buf = Buffer.from("12345", "utf-8"); + try { + buf.swap32(); + expect(false).toBe(true); + } catch (exception) { + expect(exception.message).toBe("Buffer size must be a multiple of 32-bits"); + } +}); + +it("Buffer.swap64", () => { + const examples = [ + ["", ""], + ["a1b2c3d4", "4d3c2b1a"], + ["a1b2c3d4e5f6g7h8", "4d3c2b1a8h7g6f5e"], + ]; + + for (let i = 0; i < examples.length; i++) { + const input = examples[i][0]; + const output = examples[i][1]; + const buf = Buffer.from(input, "utf-8"); + + const ref = buf.swap64(); + expect(ref instanceof Buffer).toBe(true); + expect(buf.toString()).toBe(output); + } + + const buf = Buffer.from("123456789", "utf-8"); + try { + buf.swap64(); + expect(false).toBe(true); + } catch (exception) { + expect(exception.message).toBe("Buffer size must be a multiple of 64-bits"); + } +}); + +it("Buffer.toString regessions", () => { + expect( + Buffer.from([65, 0]) + .toString("utf16le") + .split("") + .map(x => x.charCodeAt(0)), + ).toEqual([65]); + expect(Buffer.from([65, 0]).toString("base64")).toBe("QQA="); + expect(Buffer.from('{"alg":"RS256","typ":"JWT"}', "latin1").toString("latin1")).toBe('{"alg":"RS256","typ":"JWT"}'); + expect(Buffer.from('{"alg":"RS256","typ":"JWT"}', "utf8").toString("utf8")).toBe('{"alg":"RS256","typ":"JWT"}'); +}); + +it("Buffer.toString(utf16le)", () => { + const buf = Buffer.from("hello world", "utf16le"); + expect(buf.toString("utf16le")).toBe("hello world"); + expect(buf.toString("utf16le", 0, 5)).toBe("he"); +}); + +it("Buffer.toString(binary)", () => { + var x = Buffer.from(" { + { + const buf = Buffer.from("hello world"); + expect(buf.toString("base64")).toBe("aGVsbG8gd29ybGQ="); + } + + { + expect(Buffer.from(`console.log("hello world")\n`).toString("base64")).toBe(btoa('console.log("hello world")\n')); + } +}); + +it("Buffer can be mocked", () => { + function MockBuffer() { + const noop = function () {}; + const res = Buffer.alloc(0); + for (const op in Buffer.prototype) { + if (typeof res[op] === "function") { + res[op] = noop; + } + } + return res; + } + + const buf = MockBuffer(); + + expect(() => { + buf.write("hello world"); + buf.writeUint16BE(0); + buf.writeUint32BE(0); + buf.writeBigInt64BE(0); + buf.writeBigUInt64BE(0); + buf.writeBigInt64LE(0); + buf.writeBigUInt64LE(0); + }).not.toThrow(); +}); + +it("constants", () => { + expect(BufferModule.constants.MAX_LENGTH).toBe(4294967296); + expect(BufferModule.constants.MAX_STRING_LENGTH).toBe(536870888); + expect(BufferModule.default.constants.MAX_LENGTH).toBe(4294967296); + expect(BufferModule.default.constants.MAX_STRING_LENGTH).toBe(536870888); +}); + +it("File", () => { + expect(BufferModule.File).toBe(Blob); +}); + +it("transcode", () => { + expect(typeof BufferModule.transcode).toBe("undefined"); + + // This is a masqueradesAsUndefined function + expect(() => BufferModule.transcode()).toThrow("Not implemented"); +}); + +it("Buffer.from (Node.js test/test-buffer-from.js)", () => { + const checkString = "test"; + + const check = Buffer.from(checkString); + + class MyString extends String { + constructor() { + super(checkString); + } + } + + class MyPrimitive { + [Symbol.toPrimitive]() { + return checkString; + } + } + + class MyBadPrimitive { + [Symbol.toPrimitive]() { + return 1; + } + } + + expect(Buffer.from(new String(checkString))).toStrictEqual(check); + expect(Buffer.from(new MyString())).toStrictEqual(check); + expect(Buffer.from(new MyPrimitive())).toStrictEqual(check); + + [ + {}, + new Boolean(true), + { + valueOf() { + return null; + }, + }, + { + valueOf() { + return undefined; + }, + }, + { valueOf: null }, + Object.create(null), + new Number(true), + new MyBadPrimitive(), + Symbol(), + 5n, + (one, two, three) => {}, + undefined, + null, + ].forEach(input => { + expect(() => Buffer.from(input)).toThrow(); + expect(() => Buffer.from(input, "hex")).toThrow(); + }); + + expect(() => Buffer.allocUnsafe(10)).not.toThrow(); // Should not throw. + expect(() => Buffer.from("deadbeaf", "hex")).not.toThrow(); // Should not throw. +}); + +it("new Buffer() (Node.js test/test-buffer-new.js)", () => { + const LENGTH = 16; + + const ab = new ArrayBuffer(LENGTH); + const dv = new DataView(ab); + const ui = new Uint8Array(ab); + const buf = Buffer.from(ab); + + expect(buf instanceof Buffer).toBe(true); + // expect(buf.parent, buf.buffer); + expect(buf.buffer).toBe(ab); + expect(buf.length).toBe(ab.byteLength); + + buf.fill(0xc); + for (let i = 0; i < LENGTH; i++) { + expect(ui[i]).toBe(0xc); + ui[i] = 0xf; + expect(buf[i]).toBe(0xf); + } + + buf.writeUInt32LE(0xf00, 0); + buf.writeUInt32BE(0xb47, 4); + buf.writeDoubleLE(3.1415, 8); + expect(dv.getUint32(0, true)).toBe(0xf00); + expect(dv.getUint32(4)).toBe(0xb47); + expect(dv.getFloat64(8, true)).toBe(3.1415); + + // Now test protecting users from doing stupid things + + // expect(function () { + // function AB() {} + // Object.setPrototypeOf(AB, ArrayBuffer); + // Object.setPrototypeOf(AB.prototype, ArrayBuffer.prototype); + // // Buffer.from(new AB()); + // }).toThrow(); + // console.log(origAB !== ab); + + // Test the byteOffset and length arguments + { + const ab = new Uint8Array(5); + ab[0] = 1; + ab[1] = 2; + ab[2] = 3; + ab[3] = 4; + ab[4] = 5; + const buf = Buffer.from(ab.buffer, 1, 3); + expect(buf.length).toBe(3); + expect(buf[0]).toBe(2); + expect(buf[1]).toBe(3); + expect(buf[2]).toBe(4); + buf[0] = 9; + expect(ab[1]).toBe(9); + + expect(() => Buffer.from(ab.buffer, 6)).toThrow(); + expect(() => Buffer.from(ab.buffer, 3, 6)).toThrow(); + } + + // Test the deprecated Buffer() version also + { + const ab = new Uint8Array(5); + ab[0] = 1; + ab[1] = 2; + ab[2] = 3; + ab[3] = 4; + ab[4] = 5; + const buf = Buffer(ab.buffer, 1, 3); + expect(buf.length).toBe(3); + expect(buf[0]).toBe(2); + expect(buf[1]).toBe(3); + expect(buf[2]).toBe(4); + buf[0] = 9; + expect(ab[1]).toBe(9); + + expect(() => Buffer(ab.buffer, 6)).toThrow(); + expect(() => Buffer(ab.buffer, 3, 6)).toThrow(); + } + + { + // If byteOffset is not numeric, it defaults to 0. + const ab = new ArrayBuffer(10); + const expected = Buffer.from(ab, 0); + expect(Buffer.from(ab, "fhqwhgads")).toStrictEqual(expected); + expect(Buffer.from(ab, NaN)).toStrictEqual(expected); + expect(Buffer.from(ab, {})).toStrictEqual(expected); + expect(Buffer.from(ab, [])).toStrictEqual(expected); + + // If byteOffset can be converted to a number, it will be. + expect(Buffer.from(ab, [1])).toStrictEqual(Buffer.from(ab, 1)); + + // If byteOffset is Infinity, throw. + expect(() => { + Buffer.from(ab, Infinity); + }).toThrow(); + } + + { + // If length is not numeric, it defaults to 0. + const ab = new ArrayBuffer(10); + const expected = Buffer.from(ab, 0, 0); + expect(Buffer.from(ab, 0, "fhqwhgads")).toStrictEqual(expected); + expect(Buffer.from(ab, 0, NaN)).toStrictEqual(expected); + expect(Buffer.from(ab, 0, {})).toStrictEqual(expected); + expect(Buffer.from(ab, 0, [])).toStrictEqual(expected); + + // If length can be converted to a number, it will be. + expect(Buffer.from(ab, 0, [1])).toStrictEqual(Buffer.from(ab, 0, 1)); + + // If length is Infinity, throw. + expect(() => Buffer.from(ab, 0, Infinity)).toThrow(); + } + + // Test an array like entry with the length set to NaN. + expect(Buffer.from({ length: NaN })).toStrictEqual(Buffer.alloc(0)); +}); + +it("Buffer.fill (Node.js tests)", () => { + "use strict"; + const SIZE = 28; + + const buf1 = Buffer.allocUnsafe(SIZE); + const buf2 = Buffer.allocUnsafe(SIZE); + + function assertEqual(a, b) { + expect(a).toEqual(b); + } + + // Default encoding + testBufs("abc"); + testBufs("\u0222aa"); + testBufs("a\u0234b\u0235c\u0236"); + testBufs("abc", 4); + testBufs("abc", 5); + testBufs("abc", SIZE); + testBufs("\u0222aa", 2); + testBufs("\u0222aa", 8); + testBufs("a\u0234b\u0235c\u0236", 4); + testBufs("a\u0234b\u0235c\u0236", 12); + testBufs("abc", 4, 1); + testBufs("abc", 5, 1); + testBufs("\u0222aa", 8, 1); + testBufs("a\u0234b\u0235c\u0236", 4, 1); + testBufs("a\u0234b\u0235c\u0236", 12, 1); + + // UTF8 + testBufs("abc", "utf8"); + testBufs("\u0222aa", "utf8"); + testBufs("a\u0234b\u0235c\u0236", "utf8"); + testBufs("abc", 4, "utf8"); + testBufs("abc", 5, "utf8"); + testBufs("abc", SIZE, "utf8"); + testBufs("\u0222aa", 2, "utf8"); + testBufs("\u0222aa", 8, "utf8"); + testBufs("a\u0234b\u0235c\u0236", 4, "utf8"); + testBufs("a\u0234b\u0235c\u0236", 12, "utf8"); + testBufs("abc", 4, 1, "utf8"); + testBufs("abc", 5, 1, "utf8"); + testBufs("\u0222aa", 8, 1, "utf8"); + testBufs("a\u0234b\u0235c\u0236", 4, 1, "utf8"); + testBufs("a\u0234b\u0235c\u0236", 12, 1, "utf8"); + assertEqual(Buffer.allocUnsafe(1).fill(0).fill("\u0222")[0], 0xc8); + + // BINARY + testBufs("abc", "binary"); + testBufs("\u0222aa", "binary"); + testBufs("a\u0234b\u0235c\u0236", "binary"); + testBufs("abc", 4, "binary"); + testBufs("abc", 5, "binary"); + testBufs("abc", SIZE, "binary"); + testBufs("\u0222aa", 2, "binary"); + testBufs("\u0222aa", 8, "binary"); + testBufs("a\u0234b\u0235c\u0236", 4, "binary"); + testBufs("a\u0234b\u0235c\u0236", 12, "binary"); + testBufs("abc", 4, 1, "binary"); + testBufs("abc", 5, 1, "binary"); + testBufs("\u0222aa", 8, 1, "binary"); + testBufs("a\u0234b\u0235c\u0236", 4, 1, "binary"); + testBufs("a\u0234b\u0235c\u0236", 12, 1, "binary"); + + // LATIN1 + testBufs("abc", "latin1"); + testBufs("\u0222aa", "latin1"); + testBufs("a\u0234b\u0235c\u0236", "latin1"); + testBufs("abc", 4, "latin1"); + testBufs("abc", 5, "latin1"); + testBufs("abc", SIZE, "latin1"); + testBufs("\u0222aa", 2, "latin1"); + testBufs("\u0222aa", 8, "latin1"); + testBufs("a\u0234b\u0235c\u0236", 4, "latin1"); + testBufs("a\u0234b\u0235c\u0236", 12, "latin1"); + testBufs("abc", 4, 1, "latin1"); + testBufs("abc", 5, 1, "latin1"); + testBufs("\u0222aa", 8, 1, "latin1"); + testBufs("a\u0234b\u0235c\u0236", 4, 1, "latin1"); + testBufs("a\u0234b\u0235c\u0236", 12, 1, "latin1"); + + // UCS2 + testBufs("abc", "ucs2"); + testBufs("\u0222aa", "ucs2"); + testBufs("a\u0234b\u0235c\u0236", "ucs2"); + testBufs("abc", 4, "ucs2"); + testBufs("abc", SIZE, "ucs2"); + testBufs("\u0222aa", 2, "ucs2"); + testBufs("\u0222aa", 8, "ucs2"); + testBufs("a\u0234b\u0235c\u0236", 4, "ucs2"); + testBufs("a\u0234b\u0235c\u0236", 12, "ucs2"); + testBufs("abc", 4, 1, "ucs2"); + testBufs("abc", 5, 1, "ucs2"); + testBufs("\u0222aa", 8, 1, "ucs2"); + testBufs("a\u0234b\u0235c\u0236", 4, 1, "ucs2"); + testBufs("a\u0234b\u0235c\u0236", 12, 1, "ucs2"); + assertEqual(Buffer.allocUnsafe(1).fill("\u0222", "ucs2")[0], 0x22); + + // HEX + testBufs("616263", "hex"); + testBufs("c8a26161", "hex"); + testBufs("61c8b462c8b563c8b6", "hex"); + testBufs("616263", 4, "hex"); + testBufs("616263", 5, "hex"); + testBufs("616263", SIZE, "hex"); + testBufs("c8a26161", 2, "hex"); + testBufs("c8a26161", 8, "hex"); + testBufs("61c8b462c8b563c8b6", 4, "hex"); + testBufs("61c8b462c8b563c8b6", 12, "hex"); + testBufs("616263", 4, 1, "hex"); + testBufs("616263", 5, 1, "hex"); + testBufs("c8a26161", 8, 1, "hex"); + testBufs("61c8b462c8b563c8b6", 4, 1, "hex"); + testBufs("61c8b462c8b563c8b6", 12, 1, "hex"); + + expect(() => { + const buf = Buffer.allocUnsafe(SIZE); + + buf.fill("yKJh", "hex"); + }).toThrow(); + + expect(() => { + const buf = Buffer.allocUnsafe(SIZE); + + buf.fill("\u0222", "hex"); + }).toThrow(); + + // BASE64 + testBufs("YWJj", "base64"); + testBufs("yKJhYQ==", "base64"); + testBufs("Yci0Ysi1Y8i2", "base64"); + testBufs("YWJj", 4, "base64"); + testBufs("YWJj", SIZE, "base64"); + testBufs("yKJhYQ==", 2, "base64"); + testBufs("yKJhYQ==", 8, "base64"); + testBufs("Yci0Ysi1Y8i2", 4, "base64"); + testBufs("Yci0Ysi1Y8i2", 12, "base64"); + testBufs("YWJj", 4, 1, "base64"); + testBufs("YWJj", 5, 1, "base64"); + testBufs("yKJhYQ==", 8, 1, "base64"); + testBufs("Yci0Ysi1Y8i2", 4, 1, "base64"); + testBufs("Yci0Ysi1Y8i2", 12, 1, "base64"); + + // BASE64URL + testBufs("YWJj", "base64url"); + testBufs("yKJhYQ", "base64url"); + testBufs("Yci0Ysi1Y8i2", "base64url"); + testBufs("YWJj", 4, "base64url"); + testBufs("YWJj", SIZE, "base64url"); + testBufs("yKJhYQ", 2, "base64url"); + testBufs("yKJhYQ", 8, "base64url"); + testBufs("Yci0Ysi1Y8i2", 4, "base64url"); + testBufs("Yci0Ysi1Y8i2", 12, "base64url"); + testBufs("YWJj", 4, 1, "base64url"); + testBufs("YWJj", 5, 1, "base64url"); + testBufs("yKJhYQ", 8, 1, "base64url"); + testBufs("Yci0Ysi1Y8i2", 4, 1, "base64url"); + testBufs("Yci0Ysi1Y8i2", 12, 1, "base64url"); + + // Buffer + function deepStrictEqualValues(buf, arr) { + for (const [index, value] of buf.entries()) { + expect(value).toStrictEqual(arr[index]); + } + } + + const buf2Fill = Buffer.allocUnsafe(1).fill(2); + deepStrictEqualValues(genBuffer(4, [buf2Fill]), [2, 2, 2, 2]); + deepStrictEqualValues(genBuffer(4, [buf2Fill, 1]), [0, 2, 2, 2]); + deepStrictEqualValues(genBuffer(4, [buf2Fill, 1, 3]), [0, 2, 2, 0]); + deepStrictEqualValues(genBuffer(4, [buf2Fill, 1, 1]), [0, 0, 0, 0]); + const hexBufFill = Buffer.allocUnsafe(2).fill(0).fill("0102", "hex"); + deepStrictEqualValues(genBuffer(4, [hexBufFill]), [1, 2, 1, 2]); + deepStrictEqualValues(genBuffer(4, [hexBufFill, 1]), [0, 1, 2, 1]); + deepStrictEqualValues(genBuffer(4, [hexBufFill, 1, 3]), [0, 1, 2, 0]); + deepStrictEqualValues(genBuffer(4, [hexBufFill, 1, 1]), [0, 0, 0, 0]); + + // Check exceptions + [ + [0, -1], + [0, 0, buf1.length + 1], + ["", -1], + ["", 0, buf1.length + 1], + ["", 1, -1], + ].forEach(args => { + expect(() => buf1.fill(...args)).toThrow(); + }); + + expect(() => buf1.fill("a", 0, buf1.length, "node rocks!")).toThrow(); + + [ + ["a", 0, 0, NaN], + ["a", 0, 0, false], + ].forEach(args => { + expect(() => buf1.fill(...args)).toThrow(); + }); + + expect(() => buf1.fill("a", 0, 0, "foo")).toThrow(); + + function genBuffer(size, args) { + const b = Buffer.allocUnsafe(size); + return b.fill(0).fill.apply(b, args); + } + + function bufReset() { + buf1.fill(0); + buf2.fill(0); + } + + // This is mostly accurate. Except write() won't write partial bytes to the + // string while fill() blindly copies bytes into memory. To account for that an + // error will be thrown if not all the data can be written, and the SIZE has + // been massaged to work with the input characters. + function writeToFill(string, offset, end, encoding) { + if (typeof offset === "string") { + encoding = offset; + offset = 0; + end = buf2.length; + } else if (typeof end === "string") { + encoding = end; + end = buf2.length; + } else if (end === undefined) { + end = buf2.length; + } + + // Should never be reached. + if (offset < 0 || end > buf2.length) throw new ERR_OUT_OF_RANGE(); + + if (end <= offset) return buf2; + + offset >>>= 0; + end >>>= 0; + expect(offset <= buf2.length).toBe(true); + + // Convert "end" to "length" (which write understands). + const length = end - offset < 0 ? 0 : end - offset; + + let wasZero = false; + do { + const written = buf2.write(string, offset, length, encoding); + offset += written; + // Safety check in case write falls into infinite loop. + if (written === 0) { + if (wasZero) throw new Error("Could not write all data to Buffer at " + offset); + else wasZero = true; + } + } while (offset < buf2.length); + + return buf2; + } + + function testBufs(string, offset, length, encoding) { + bufReset(); + buf1.fill.apply(buf1, arguments); + // Swap bytes on BE archs for ucs2 encoding. + expect(buf1.fill.apply(buf1, arguments)).toStrictEqual(writeToFill.apply(null, arguments)); + } + + // Make sure these throw. + expect(() => Buffer.allocUnsafe(8).fill("a", -1)).toThrow(); + expect(() => Buffer.allocUnsafe(8).fill("a", 0, 9)).toThrow(); + + // Make sure this doesn't hang indefinitely. + Buffer.allocUnsafe(8).fill(""); + Buffer.alloc(8, ""); + + { + const buf = Buffer.alloc(64, 10); + for (let i = 0; i < buf.length; i++) assertEqual(buf[i], 10); + + buf.fill(11, 0, buf.length >> 1); + for (let i = 0; i < buf.length >> 1; i++) assertEqual(buf[i], 11); + for (let i = (buf.length >> 1) + 1; i < buf.length; i++) assertEqual(buf[i], 10); + + buf.fill("h"); + for (let i = 0; i < buf.length; i++) assertEqual(buf[i], "h".charCodeAt(0)); + + buf.fill(0); + for (let i = 0; i < buf.length; i++) assertEqual(buf[i], 0); + + buf.fill(null); + for (let i = 0; i < buf.length; i++) assertEqual(buf[i], 0); + + buf.fill(1, 16, 32); + for (let i = 0; i < 16; i++) assertEqual(buf[i], 0); + for (let i = 16; i < 32; i++) assertEqual(buf[i], 1); + for (let i = 32; i < buf.length; i++) assertEqual(buf[i], 0); + } + + { + const buf = Buffer.alloc(10, "abc"); + assertEqual(buf.toString(), "abcabcabca"); + buf.fill("է"); + assertEqual(buf.toString(), "էէէէէ"); + } + + // // Testing process.binding. Make sure "start" is properly checked for range + // // errors. + // assert.throws( + // () => { + // internalBinding("buffer").fill(Buffer.alloc(1), 1, -1, 0, 1); + // }, + // { code: "ERR_OUT_OF_RANGE" }, + // ); + + // Make sure "end" is properly checked, even if it's magically mangled using + // Symbol.toPrimitive. + { + expect(() => { + const end = { + [Symbol.toPrimitive]() { + return 1; + }, + }; + Buffer.alloc(1).fill(Buffer.alloc(1), 0, end); + }).toThrow(); + } + + // Testing process.binding. Make sure "end" is properly checked for range + // errors. + // assert.throws( + // () => { + // internalBinding("buffer").fill(Buffer.alloc(1), 1, 1, -2, 1); + // }, + // { code: "ERR_OUT_OF_RANGE" }, + // ); + + // Test that bypassing 'length' won't cause an abort. + expect(() => { + const buf = Buffer.from("w00t"); + Object.defineProperty(buf, "length", { + value: 1337, + enumerable: true, + }); + buf.fill(""); + }).toThrow(); + + assertEqual(Buffer.allocUnsafeSlow(16).fill("ab", "utf16le"), Buffer.from("61006200610062006100620061006200", "hex")); + + assertEqual(Buffer.allocUnsafeSlow(15).fill("ab", "utf16le"), Buffer.from("610062006100620061006200610062", "hex")); + + assertEqual(Buffer.allocUnsafeSlow(16).fill("ab", "utf16le"), Buffer.from("61006200610062006100620061006200", "hex")); + assertEqual(Buffer.allocUnsafeSlow(16).fill("a", "utf16le"), Buffer.from("61006100610061006100610061006100", "hex")); + + assertEqual(Buffer.allocUnsafeSlow(16).fill("a", "utf16le").toString("utf16le"), "a".repeat(8)); + assertEqual(Buffer.allocUnsafeSlow(16).fill("a", "latin1").toString("latin1"), "a".repeat(16)); + assertEqual(Buffer.allocUnsafeSlow(16).fill("a", "utf8").toString("utf8"), "a".repeat(16)); + + assertEqual(Buffer.allocUnsafeSlow(16).fill("Љ", "utf16le").toString("utf16le"), "Љ".repeat(8)); + assertEqual(Buffer.allocUnsafeSlow(16).fill("Љ", "latin1").toString("latin1"), "\t".repeat(16)); + assertEqual(Buffer.allocUnsafeSlow(16).fill("Љ", "utf8").toString("utf8"), "Љ".repeat(8)); + + expect(() => { + const buf = Buffer.from("a".repeat(1000)); + + buf.fill("This is not correctly encoded", "hex"); + }).toThrow(); +}); + +test("Buffer.byteLength", () => { + const SlowBuffer = require("buffer").SlowBuffer; + + [[32, "latin1"], [NaN, "utf8"], [{}, "latin1"], []].forEach(args => { + assert.throws(() => Buffer.byteLength(...args)); + }); + + assert.strictEqual(Buffer.byteLength("", undefined, true), 0); + + assert(ArrayBuffer.isView(new Buffer(10))); + assert(ArrayBuffer.isView(new SlowBuffer(10))); + assert(ArrayBuffer.isView(Buffer.alloc(10))); + assert(ArrayBuffer.isView(Buffer.allocUnsafe(10))); + assert(ArrayBuffer.isView(Buffer.allocUnsafeSlow(10))); + assert(ArrayBuffer.isView(Buffer.from(""))); + + // buffer + const incomplete = Buffer.from([0xe4, 0xb8, 0xad, 0xe6, 0x96]); + assert.strictEqual(Buffer.byteLength(incomplete), 5); + const ascii = Buffer.from("abc"); + assert.strictEqual(Buffer.byteLength(ascii), 3); + + // ArrayBuffer + const buffer = new ArrayBuffer(8); + assert.strictEqual(Buffer.byteLength(buffer), 8); + + // TypedArray + const int8 = new Int8Array(8); + assert.strictEqual(Buffer.byteLength(int8), 8); + const uint8 = new Uint8Array(8); + assert.strictEqual(Buffer.byteLength(uint8), 8); + const uintc8 = new Uint8ClampedArray(2); + assert.strictEqual(Buffer.byteLength(uintc8), 2); + const int16 = new Int16Array(8); + assert.strictEqual(Buffer.byteLength(int16), 16); + const uint16 = new Uint16Array(8); + assert.strictEqual(Buffer.byteLength(uint16), 16); + const int32 = new Int32Array(8); + assert.strictEqual(Buffer.byteLength(int32), 32); + const uint32 = new Uint32Array(8); + assert.strictEqual(Buffer.byteLength(uint32), 32); + const float32 = new Float32Array(8); + assert.strictEqual(Buffer.byteLength(float32), 32); + const float64 = new Float64Array(8); + assert.strictEqual(Buffer.byteLength(float64), 64); + + // DataView + const dv = new DataView(new ArrayBuffer(2)); + assert.strictEqual(Buffer.byteLength(dv), 2); + + // Special case: zero length string + assert.strictEqual(Buffer.byteLength("", "ascii"), 0); + assert.strictEqual(Buffer.byteLength("", "HeX"), 0); + + // utf8 + assert.strictEqual(Buffer.byteLength("∑éllö wørl∂!", "utf-8"), 19); + assert.strictEqual(Buffer.byteLength("κλμνξο", "utf8"), 12); + assert.strictEqual(Buffer.byteLength("挵挶挷挸挹", "utf-8"), 15); + assert.strictEqual(Buffer.byteLength("𠝹𠱓𠱸", "UTF8"), 12); + // Without an encoding, utf8 should be assumed + assert.strictEqual(Buffer.byteLength("hey there"), 9); + assert.strictEqual(Buffer.byteLength("𠱸挶νξ#xx :)"), 17); + assert.strictEqual(Buffer.byteLength("hello world", ""), 11); + // It should also be assumed with unrecognized encoding + assert.strictEqual(Buffer.byteLength("hello world", "abc"), 11); + assert.strictEqual(Buffer.byteLength("ßœ∑≈", "unkn0wn enc0ding"), 10); + + // base64 + assert.strictEqual(Buffer.byteLength("aGVsbG8gd29ybGQ=", "base64"), 11); + assert.strictEqual(Buffer.byteLength("aGVsbG8gd29ybGQ=", "BASE64"), 11); + assert.strictEqual(Buffer.byteLength("bm9kZS5qcyByb2NrcyE=", "base64"), 14); + assert.strictEqual(Buffer.byteLength("aGkk", "base64"), 3); + assert.strictEqual(Buffer.byteLength("bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw==", "base64"), 25); + // base64url + assert.strictEqual(Buffer.byteLength("aGVsbG8gd29ybGQ", "base64url"), 11); + assert.strictEqual(Buffer.byteLength("aGVsbG8gd29ybGQ", "BASE64URL"), 11); + assert.strictEqual(Buffer.byteLength("bm9kZS5qcyByb2NrcyE", "base64url"), 14); + assert.strictEqual(Buffer.byteLength("aGkk", "base64url"), 3); + assert.strictEqual(Buffer.byteLength("bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw", "base64url"), 25); + // special padding + assert.strictEqual(Buffer.byteLength("aaa=", "base64"), 2); + assert.strictEqual(Buffer.byteLength("aaaa==", "base64"), 3); + assert.strictEqual(Buffer.byteLength("aaa=", "base64url"), 2); + assert.strictEqual(Buffer.byteLength("aaaa==", "base64url"), 3); + assert.strictEqual(Buffer.byteLength("Il était tué", "utf8"), 14); + assert.strictEqual(Buffer.byteLength("Il était tué"), 14); + + ["ascii", "latin1", "binary"] + .reduce((es, e) => es.concat(e, e.toUpperCase()), []) + .forEach(encoding => { + assert.strictEqual(Buffer.byteLength("Il était tué", encoding), 12); + }); + + ["ucs2", "ucs-2", "utf16le", "utf-16le"] + .reduce((es, e) => es.concat(e, e.toUpperCase()), []) + .forEach(encoding => { + assert.strictEqual(Buffer.byteLength("Il était tué", encoding), 24); + }); + + // Test that ArrayBuffer from a different context is detected correctly + // const arrayBuf = vm.runInNewContext("new ArrayBuffer()"); + // assert.strictEqual(Buffer.byteLength(arrayBuf), 0); + + // Verify that invalid encodings are treated as utf8 + for (let i = 1; i < 10; i++) { + const encoding = String(i).repeat(i); + + assert.ok(!Buffer.isEncoding(encoding)); + assert.strictEqual(Buffer.byteLength("foo", encoding), Buffer.byteLength("foo", "utf8")); + } +}); + +it("should not crash on invalid UTF-8 byte sequence", () => { + const buf = Buffer.from([0xc0, 0xfd]); + expect(buf.length).toBe(2); + const str = buf.toString(); + expect(str.length).toBe(2); + expect(str).toBe("\uFFFD\uFFFD"); +}); + +it("should not crash on invalid UTF-8 byte sequence with ASCII head", () => { + const buf = Buffer.from([0x42, 0xc0, 0xfd]); + expect(buf.length).toBe(3); + const str = buf.toString(); + expect(str.length).toBe(3); + expect(str).toBe("B\uFFFD\uFFFD"); +}); + +it("should not perform out-of-bound access on invalid UTF-8 byte sequence", () => { + const buf = Buffer.from([0x01, 0x9a, 0x84, 0x13, 0x12, 0x11, 0x10, 0x09]).subarray(2); + expect(buf.length).toBe(6); + const str = buf.toString(); + expect(str.length).toBe(6); + expect(str).toBe("\uFFFD\x13\x12\x11\x10\x09"); +}); + +it("repro #2063", () => { + const buf = Buffer.from( + "eyJlbWFpbCI6Ijg3MTg4NDYxN0BxcS5jb20iLCJpZCI6OCwicm9sZSI6Im5vcm1hbCIsImlhdCI6MTY3NjI4NDQyMSwiZXhwIjoxNjc2ODg5MjIxfQ", + "base64", + ); + expect(buf.length).toBe(85); + expect(buf[82]).toBe(50); + expect(buf[83]).toBe(49); + expect(buf[84]).toBe(125); +}); diff --git a/test/js/node/child_process/child-process-stdio.test.js b/test/js/node/child_process/child-process-stdio.test.js new file mode 100644 index 000000000..d0c6d9bc7 --- /dev/null +++ b/test/js/node/child_process/child-process-stdio.test.js @@ -0,0 +1,112 @@ +import { describe, it, expect, beforeAll } from "bun:test"; +import { spawn, execSync } from "node:child_process"; +import { bunExe, bunEnv } from "harness"; + +const CHILD_PROCESS_FILE = import.meta.dir + "/spawned-child.js"; +const OUT_FILE = import.meta.dir + "/stdio-test-out.txt"; + +describe("process.stdout", () => { + it("should allow us to write to it", done => { + const child = spawn(bunExe(), [CHILD_PROCESS_FILE, "STDOUT"], { + env: bunEnv, + }); + child.stdout.setEncoding("utf8"); + child.stdout.on("data", data => { + try { + expect(data).toBe("stdout_test"); + done(); + } catch (err) { + done(err); + } + }); + }); +}); + +describe("process.stdin", () => { + it("should allow us to read from stdin in readable mode", done => { + const input = "hello\n"; + // Child should read from stdin and write it back + const child = spawn(bunExe(), [CHILD_PROCESS_FILE, "STDIN", "READABLE"], { + env: bunEnv, + }); + let data = ""; + child.stdout.setEncoding("utf8"); + child.stdout + .on("data", chunk => { + data += chunk; + }) + .on("end", function () { + try { + expect(data).toBe(`data: ${input}`); + done(); + } catch (err) { + done(err); + } + }); + child.stdin.write(input); + child.stdin.end(); + }); + + it("should allow us to read from stdin via flowing mode", done => { + const input = "hello\n"; + // Child should read from stdin and write it back + const child = spawn(bunExe(), [CHILD_PROCESS_FILE, "STDIN", "FLOWING"], { + env: bunEnv, + }); + let data = ""; + child.stdout.setEncoding("utf8"); + child.stdout + .on("readable", () => { + let chunk; + while ((chunk = child.stdout.read()) !== null) { + data += chunk; + } + }) + .on("end", function () { + try { + expect(data).toBe(`data: ${input}`); + done(); + } catch (err) { + done(err); + } + }); + child.stdin.write(input); + child.stdin.end(); + }); + + it("should allow us to read > 65kb from stdin", done => { + const numReps = Math.ceil((66 * 1024) / 5); + const input = "hello".repeat(numReps); + // Child should read from stdin and write it back + const child = spawn(bunExe(), [CHILD_PROCESS_FILE, "STDIN", "FLOWING"], { + env: bunEnv, + }); + let data = ""; + child.stdout.setEncoding("utf8"); + child.stdout + .on("readable", () => { + let chunk; + while ((chunk = child.stdout.read()) !== null) { + data += chunk; + } + }) + .on("end", function () { + try { + expect(data).toBe(`data: ${input}`); + done(); + } catch (err) { + done(err); + } + }); + child.stdin.write(input); + child.stdin.end(); + }); + + it("should allow us to read from a file", () => { + const result = execSync(`${bunExe()} ${CHILD_PROCESS_FILE} STDIN FLOWING < ${import.meta.dir}/readFileSync.txt`, { + encoding: "utf8", + env: bunEnv, + }); + expect(result).toEqual("data: File read successfully"); + }); +}); diff --git a/test/js/node/child_process/child_process-node.test.js b/test/js/node/child_process/child_process-node.test.js new file mode 100644 index 000000000..deb3bfb86 --- /dev/null +++ b/test/js/node/child_process/child_process-node.test.js @@ -0,0 +1,481 @@ +import { beforeAll, describe, expect, it } from "bun:test"; +import { ChildProcess, spawn, exec } from "node:child_process"; +import { throws, assert, createCallCheckCtx, createDoneDotAll } from "node-harness"; +import { tmpdir } from "node:os"; +const strictEqual = (a, b) => expect(a).toStrictEqual(b); +const debug = process.env.DEBUG ? console.log : () => {}; + +const platformTmpDir = require("fs").realpathSync(tmpdir()); + +const TYPE_ERR_NAME = "TypeError"; + +console.log(process.cwd()); + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +const common = { + pwdCommand: ["pwd", []], +}; + +describe("ChildProcess.constructor", () => { + it("should be a function", () => { + strictEqual(typeof ChildProcess, "function"); + }); +}); + +describe("ChildProcess.spawn()", () => { + it("should throw on invalid options", () => { + // Verify that invalid options to spawn() throw. + const child = new ChildProcess(); + + [undefined, null, "foo", 0, 1, NaN, true, false].forEach(options => { + throws( + () => { + child.spawn(options); + }, + { + code: "ERR_INVALID_ARG_TYPE", + name: TYPE_ERR_NAME, + // message: + // 'The "options" argument must be of type object.' + + // `${common.invalidArgTypeHelper(options)}`, + }, + ); + }); + }); + + it("should throw if file is not a string", () => { + // Verify that spawn throws if file is not a string. + const child = new ChildProcess(); + [undefined, null, 0, 1, NaN, true, false, {}].forEach(file => { + throws( + () => { + child.spawn({ file }); + }, + { + code: "ERR_INVALID_ARG_TYPE", + name: TYPE_ERR_NAME, + // message: + // 'The "options.file" property must be of type string.' + + // `${common.invalidArgTypeHelper(file)}`, + }, + ); + }); + }); + + it("should throw if envPairs is not an array or undefined", () => { + // Verify that spawn throws if envPairs is not an array or undefined. + const child = new ChildProcess(); + + [null, 0, 1, NaN, true, false, {}, "foo"].forEach(envPairs => { + throws( + () => { + child.spawn({ + envPairs, + stdio: ["ignore", "ignore", "ignore", "ipc"], + }); + }, + { + code: "ERR_INVALID_ARG_TYPE", + name: TYPE_ERR_NAME, + // message: + // 'The "options.envPairs" property must be an instance of Array.' + + // common.invalidArgTypeHelper(envPairs), + }, + ); + }); + }); + + it("should throw if stdio is not an array or undefined", () => { + // Verify that spawn throws if args is not an array or undefined. + const child = new ChildProcess(); + + [null, 0, 1, NaN, true, false, {}, "foo"].forEach(args => { + throws( + () => { + child.spawn({ file: "foo", args }); + }, + { + code: "ERR_INVALID_ARG_TYPE", + name: TYPE_ERR_NAME, + // message: + // 'The "options.args" property must be an instance of Array.' + + // common.invalidArgTypeHelper(args), + }, + ); + }); + }); +}); + +describe("ChildProcess.spawn", () => { + function getChild() { + const child = new ChildProcess(); + child.spawn({ + file: "node", + // file: process.execPath, + args: ["node", "--interactive"], + cwd: process.cwd(), + stdio: ["ignore", "ignore", "ignore"], + }); + return child; + } + + it("should spawn a process", () => { + const child = getChild(); + // Test that we can call spawn + + strictEqual(Object.hasOwn(child, "pid"), true); + assert(Number.isInteger(child.pid)); + child.kill(); + }); + + it("should throw error on invalid signal", () => { + const child = getChild(); + // Try killing with invalid signal + throws( + () => { + child.kill("foo"); + }, + { code: "ERR_UNKNOWN_SIGNAL", name: TYPE_ERR_NAME }, + ); + }); +}); + +describe("ChildProcess spawn bad stdio", () => { + // Monkey patch spawn() to create a child process normally, but destroy the + // stdout and stderr streams. This replicates the conditions where the streams + // cannot be properly created. + function createChild(options, callback, done, target) { + var __originalSpawn = ChildProcess.prototype.spawn; + ChildProcess.prototype.spawn = function () { + const err = __originalSpawn.apply(this, arguments); + + this.stdout.destroy(); + this.stderr.destroy(); + + return err; + }; + + const { mustCall } = createCallCheckCtx(done); + let cmd = `bun ${import.meta.dir}/spawned-child.js`; + if (target) cmd += " " + target; + const child = exec(cmd, options, mustCall(callback)); + ChildProcess.prototype.spawn = __originalSpawn; + return child; + } + + it("should handle normal execution of child process", done => { + createChild( + {}, + (err, stdout, stderr) => { + strictEqual(err, null); + strictEqual(stdout, ""); + strictEqual(stderr, ""); + }, + done, + ); + }); + + it("should handle error event of child process", done => { + const error = new Error(`Command failed: bun ${import.meta.dir}/spawned-child.js ERROR`); + createChild( + {}, + (err, stdout, stderr) => { + strictEqual(err.message, error.message); + strictEqual(stdout, ""); + strictEqual(stderr, ""); + }, + done, + "ERROR", + ); + }); + + it("should handle killed process", done => { + createChild( + { timeout: 1 }, + (err, stdout, stderr) => { + strictEqual(err.killed, true); + strictEqual(stdout, ""); + strictEqual(stderr, ""); + }, + done, + ); + }); +}); + +describe("child_process cwd", () => { + // Spawns 'pwd' with given options, then test + // - whether the child pid is undefined or number, + // - whether the exit code equals expectCode, + // - optionally whether the trimmed stdout result matches expectData + function testCwd(options, { expectPidType, expectCode = 0, expectData }, done = () => {}) { + const createDone = createDoneDotAll(done); + const { mustCall } = createCallCheckCtx(createDone(1500)); + const exitDone = createDone(5000); + + const child = spawn(...common.pwdCommand, options); + + strictEqual(typeof child.pid, expectPidType); + + child.stdout.setEncoding("utf8"); + + // No need to assert callback since `data` is asserted. + let data = ""; + child.stdout.on("data", chunk => { + data += chunk; + }); + + // TODO: Test exit events + // // Can't assert callback, as stayed in to API: + // // _The 'exit' event may or may not fire after an error has occurred._ + child.on("exit", (code, signal) => { + try { + strictEqual(code, expectCode); + exitDone(); + } catch (err) { + exitDone(err); + } + }); + + child.on( + "close", + mustCall(() => { + expectData && strictEqual(data.trim(), expectData); + }), + ); + + return child; + } + + // TODO: Make sure this isn't important + // Currently Bun.spawn will still spawn even though cwd doesn't exist + // // Assume does-not-exist doesn't exist, expect exitCode=-1 and errno=ENOENT + // it("should throw an error when given cwd doesn't exist", () => { + // testCwd({ cwd: "does-not-exist" }, "undefined", -1).on( + // "error", + // mustCall(function (e) { + // console.log(e); + // strictEqual(e.code, "ENOENT"); + // }) + // ); + // }); + + // TODO: Make sure this isn't an important test + // it("should throw when cwd is a non-file url", () => { + // throws(() => { + // testCwd( + // { + // cwd: new URL("http://example.com/"), + // }, + // "number", + // 0, + // tmpdir.path + // ); + // }, /The URL must be of scheme file/); + + // // if (process.platform !== "win32") { + // // throws(() => { + // // testCwd( + // // { + // // cwd: new URL("file://host/dev/null"), + // // }, + // // "number", + // // 0, + // // tmpdir.path + // // ); + // // }, /File URL host must be "localhost" or empty on/); + // // } + // }); + + it("should work for valid given cwd", done => { + const tmpdir = { path: platformTmpDir }; + const createDone = createDoneDotAll(done); + + // Assume these exist, and 'pwd' gives us the right directory back + testCwd( + { cwd: tmpdir.path }, + { + expectPidType: "number", + expectCode: 0, + expectData: platformTmpDir, + }, + createDone(1500), + ); + const shouldExistDir = "/dev"; + testCwd( + { cwd: shouldExistDir }, + { + expectPidType: "number", + expectCode: 0, + expectData: shouldExistDir, + }, + createDone(1500), + ); + testCwd( + { cwd: Bun.pathToFileURL(tmpdir.path) }, + { + expectPidType: "number", + expectCode: 0, + expectData: platformTmpDir, + }, + createDone(1500), + ); + }); + + it.skip("shouldn't try to chdir to an invalid cwd", done => { + const createDone = createDoneDotAll(done); + // Spawn() shouldn't try to chdir() to invalid arg, so this should just work + testCwd({ cwd: "" }, { expectPidType: "number" }, createDone(1500)); + testCwd({ cwd: undefined }, { expectPidType: "number" }, createDone(1500)); + testCwd({ cwd: null }, { expectPidType: "number" }, createDone(1500)); + }); +}); + +describe("child_process default options", () => { + it("should use process.env as default env", done => { + globalThis.process.env.TMPDIR = platformTmpDir; + + let child = spawn("printenv", [], {}); + let response = ""; + + child.stdout.setEncoding("utf8"); + child.stdout.on("data", chunk => { + debug(`stdout: ${chunk}`); + response += chunk; + }); + + // NOTE: Original test used child.on("exit"), but this is unreliable + // because the process can exit before the stream is closed and the data is read + child.stdout.on("close", () => { + expect(response.includes(`TMPDIR=${platformTmpDir}`)).toBe(true); + done(); + }); + }); +}); + +describe("child_process double pipe", () => { + it("should allow two pipes to be used at once", done => { + // const { mustCallAtLeast, mustCall } = createCallCheckCtx(done); + const mustCallAtLeast = fn => fn; + const mustCall = fn => fn; + let grep, sed, echo; + grep = spawn("grep", ["o"], { stdio: ["pipe", "pipe", "pipe"] }); + sed = spawn("sed", ["s/o/O/"]); + echo = spawn("echo", ["hello\nnode\nand\nworld\n"]); + + // pipe grep | sed + grep.stdout.on( + "data", + mustCallAtLeast(data => { + debug(`grep stdout ${data.length}`); + if (!sed.stdin.write(data)) { + grep.stdout.pause(); + } + }), + ); + + // print sed's output + sed.stdout.on( + "data", + mustCallAtLeast(data => { + result += data.toString("utf8"); + debug(data); + }), + ); + + echo.stdout.on( + "data", + mustCallAtLeast(data => { + debug(`grep stdin write ${data.length}`); + if (!grep.stdin.write(data)) { + debug("echo stdout pause"); + echo.stdout.pause(); + } + }), + ); + + // TODO(Derrick): We don't implement the full API for this yet, + // So stdin has no 'drain' event. + // TODO(@jasnell): This does not appear to ever be + // emitted. It's not clear if it is necessary. + grep.stdin.on("drain", () => { + debug("echo stdout resume"); + echo.stdout.resume(); + }); + + // Propagate end from echo to grep + echo.stdout.on( + "end", + mustCall(() => { + debug("echo stdout end"); + grep.stdin.end(); + }), + ); + + echo.on( + "exit", + mustCall(() => { + debug("echo exit"); + }), + ); + + grep.on( + "exit", + mustCall(() => { + debug("grep exit"); + }), + ); + + sed.on( + "exit", + mustCall(() => { + debug("sed exit"); + }), + ); + + // TODO(@jasnell): This does not appear to ever be + // emitted. It's not clear if it is necessary. + sed.stdin.on("drain", () => { + grep.stdout.resume(); + }); + + // Propagate end from grep to sed + grep.stdout.on( + "end", + mustCall(() => { + debug("grep stdout end"); + sed.stdin.end(); + }), + ); + + let result = ""; + + sed.stdout.on( + "end", + mustCall(() => { + debug("result: " + result); + strictEqual(result, `hellO\nnOde\nwOrld\n`); + done(); + }), + ); + }); +}); diff --git a/test/js/node/child_process/child_process.test.ts b/test/js/node/child_process/child_process.test.ts new file mode 100644 index 000000000..167cbd8b0 --- /dev/null +++ b/test/js/node/child_process/child_process.test.ts @@ -0,0 +1,347 @@ +import { describe, it as it_, expect as expect_ } from "bun:test"; +import { gcTick } from "harness"; +import { ChildProcess, spawn, execFile, exec, fork, spawnSync, execFileSync, execSync } from "node:child_process"; +import { tmpdir } from "node:os"; +import { promisify } from "node:util"; + +const expect: typeof expect_ = (actual: unknown) => { + gcTick(); + const ret = expect_(actual); + gcTick(); + return ret; +}; + +const it: typeof it_ = (label, fn) => { + const hasDone = fn.length === 1; + if (fn.constructor.name === "AsyncFunction" && hasDone) { + return it_(label, async done => { + gcTick(); + await fn(done); + gcTick(); + }); + } else if (hasDone) { + return it_(label, done => { + gcTick(); + fn(done); + gcTick(); + }); + } else if (fn.constructor.name === "AsyncFunction") { + return it_(label, async () => { + gcTick(); + await fn(() => {}); + gcTick(); + }); + } else { + return it_(label, () => { + gcTick(); + fn(() => {}); + gcTick(); + }); + } +}; + +const debug = process.env.DEBUG ? console.log : () => {}; + +const platformTmpDir = require("fs").realpathSync(tmpdir()); + +// Semver regex: https://gist.github.com/jhorsman/62eeea161a13b80e39f5249281e17c39?permalink_comment_id=2896416#gistcomment-2896416 +// Not 100% accurate, but good enough for this test +const SEMVER_REGEX = + /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[a-zA-Z\d][-a-zA-Z.\d]*)?(\+[a-zA-Z\d][-a-zA-Z.\d]*)?$/; + +describe("ChildProcess.spawn()", () => { + it("should emit `spawn` on spawn", async () => { + const proc = new ChildProcess(); + const result = await new Promise(resolve => { + proc.on("spawn", () => { + resolve(true); + }); + proc.spawn({ file: "bun", args: ["bun", "-v"] }); + }); + expect(result).toBe(true); + }); + + it("should emit `exit` when killed", async () => { + const proc = new ChildProcess(); + const result = await new Promise(resolve => { + proc.on("exit", () => { + resolve(true); + }); + + proc.spawn({ file: "bun", args: ["bun", "-v"] }); + proc.kill(); + }); + expect(result).toBe(true); + }); +}); + +describe("spawn()", () => { + it("should spawn a process", () => { + const child = spawn("echo", ["hello"]); + expect(!!child).toBe(true); + }); + + it("should disallow invalid filename", () => { + let child; + let child2; + try { + // @ts-ignore + child = spawn(123); + // @ts-ignore + child2 = spawn(["echo", "hello"]); + } catch (e) {} + expect(!!child).toBe(false); + expect(!!child2).toBe(false); + }); + + it("should allow stdout to be read via Node stream.Readable `data` events", async () => { + const child = spawn("bun", ["-v"]); + const result: string = await new Promise(resolve => { + child.stdout.on("error", e => { + console.error(e); + }); + child.stdout.on("data", data => { + debug(`stdout: ${data}`); + resolve(data.toString()); + }); + child.stderr.on("data", data => { + debug(`stderr: ${data}`); + }); + }); + expect(SEMVER_REGEX.test(result.trim())).toBe(true); + }); + + it("should allow stdout to be read via .read() API", async done => { + const child = spawn("bun", ["-v"]); + const result: string = await new Promise(resolve => { + let finalData = ""; + child.stdout.on("error", e => { + done(e); + }); + child.stdout.on("readable", () => { + let data; + + while ((data = child.stdout.read()) !== null) { + finalData += data.toString(); + } + resolve(finalData); + }); + }); + expect(SEMVER_REGEX.test(result.trim())).toBe(true); + done(); + }); + + it("should accept stdio option with 'ignore' for no stdio fds", async () => { + const child1 = spawn("bun", ["-v"], { + stdio: "ignore", + }); + const child2 = spawn("bun", ["-v"], { + stdio: ["ignore", "ignore", "ignore"], + }); + + expect(!!child1).toBe(true); + expect(child1.stdin).toBe(null); + expect(child1.stdout).toBe(null); + expect(child1.stderr).toBe(null); + + expect(!!child2).toBe(true); + expect(child2.stdin).toBe(null); + expect(child2.stdout).toBe(null); + expect(child2.stderr).toBe(null); + }); + + it("should allow us to set cwd", async () => { + const child = spawn("pwd", { cwd: platformTmpDir }); + const result: string = await new Promise(resolve => { + child.stdout.on("data", data => { + resolve(data.toString()); + }); + }); + expect(result.trim()).toBe(platformTmpDir); + }); + + it("should allow us to write to stdin", async () => { + const child = spawn("tee"); + const result: string = await new Promise(resolve => { + child.stdin.write("hello"); + child.stdout.on("data", data => { + resolve(data.toString()); + }); + }); + expect(result.trim()).toBe("hello"); + }); + + it("should allow us to timeout hanging processes", async () => { + const child = spawn("sleep", ["2"], { timeout: 3 }); + const start = performance.now(); + let end; + await new Promise(resolve => { + child.on("exit", () => { + end = performance.now(); + resolve(true); + }); + }); + expect(end - start < 2000).toBe(true); + }); + + it("should allow us to set env", async () => { + const child = spawn("env", { env: { TEST: "test" } }); + const result: string = await new Promise(resolve => { + child.stdout.on("data", data => { + resolve(data.toString()); + }); + }); + expect(/TEST\=test/.test(result)).toBe(true); + }); + + it("should allow explicit setting of argv0", async () => { + var resolve; + const promise = new Promise(resolve1 => { + resolve = resolve1; + }); + process.env.NO_COLOR = "1"; + const child = spawn("node", ["--help"], { argv0: "bun" }); + delete process.env.NO_COLOR; + let msg = ""; + + child.stdout.on("data", data => { + msg += data.toString(); + }); + + child.stdout.on("close", () => { + resolve(msg); + }); + + const result = await promise; + expect(/Open bun's Discord server/.test(result)).toBe(true); + }); + + it("should allow us to spawn in a shell", async () => { + const result1: string = await new Promise(resolve => { + const child1 = spawn("echo", ["$0"], { shell: true }); + child1.stdout.on("data", data => { + resolve(data.toString()); + }); + }); + const result2: string = await new Promise(resolve => { + const child2 = spawn("echo", ["$0"], { shell: "bash" }); + child2.stdout.on("data", data => { + resolve(data.toString()); + }); + }); + expect(result1.trim()).toBe(Bun.which("sh")); + expect(result2.trim()).toBe(Bun.which("bash")); + }); + it("should spawn a process synchronously", () => { + const { stdout } = spawnSync("echo", ["hello"], { encoding: "utf8" }); + expect(stdout.trim()).toBe("hello"); + }); +}); + +describe("execFile()", () => { + it("should execute a file", async () => { + const result: Buffer = await new Promise((resolve, reject) => { + execFile("bun", ["-v"], { encoding: "buffer" }, (error, stdout, stderr) => { + if (error) { + reject(error); + } + resolve(stdout); + }); + }); + expect(SEMVER_REGEX.test(result.toString().trim())).toBe(true); + }); +}); + +describe("exec()", () => { + it("should execute a command in a shell", async () => { + const result: Buffer = await new Promise((resolve, reject) => { + exec("bun -v", { encoding: "buffer" }, (error, stdout, stderr) => { + if (error) { + reject(error); + } + resolve(stdout); + }); + }); + expect(SEMVER_REGEX.test(result.toString().trim())).toBe(true); + }); + + it("should return an object w/ stdout and stderr when promisified", async () => { + const result = await promisify(exec)("bun -v"); + expect(typeof result).toBe("object"); + expect(typeof result.stdout).toBe("string"); + expect(typeof result.stderr).toBe("string"); + + const { stdout, stderr } = result; + expect(SEMVER_REGEX.test(stdout.trim())).toBe(true); + expect(stderr.trim()).toBe(""); + }); +}); + +describe("fork()", () => { + it("should throw an error when used", () => { + let err; + try { + fork("index.js"); + } catch (e) { + err = e; + } + expect(err instanceof Error).toBe(true); + }); +}); + +describe("spawnSync()", () => { + it("should spawn a process synchronously", () => { + const { stdout } = spawnSync("echo", ["hello"], { encoding: "utf8" }); + expect(stdout.trim()).toBe("hello"); + }); +}); + +describe("execFileSync()", () => { + it("should execute a file synchronously", () => { + const result = execFileSync("bun", ["-v"], { encoding: "utf8" }); + expect(SEMVER_REGEX.test(result.trim())).toBe(true); + }); + + it("should allow us to pass input to the command", () => { + const result = execFileSync("node", [import.meta.dir + "/spawned-child.js", "STDIN"], { + input: "hello world!", + encoding: "utf8", + }); + expect(result.trim()).toBe("data: hello world!"); + }); +}); + +describe("execSync()", () => { + it("should execute a command in the shell synchronously", () => { + const result = execSync("bun -v", { encoding: "utf8" }); + expect(SEMVER_REGEX.test(result.trim())).toBe(true); + }); +}); + +describe("Bun.spawn()", () => { + it("should return exit code 0 on successful execution", async () => { + const proc = Bun.spawn({ + cmd: ["echo", "hello"], + stdout: "pipe", + }); + + for await (const chunk of proc.stdout!) { + const text = new TextDecoder().decode(chunk); + expect(text.trim()).toBe("hello"); + } + + const result = await new Promise(resolve => { + const maybeExited = Bun.peek(proc.exited); + if (maybeExited === proc.exited) { + proc.exited.then(code => resolve(code)); + } else { + resolve(maybeExited); + } + }); + expect(result).toBe(0); + }); + // it("should fail when given an invalid cwd", () => { + // const child = Bun.spawn({ cmd: ["echo", "hello"], cwd: "/invalid" }); + // expect(child.pid).toBe(undefined); + // }); +}); diff --git a/test/js/node/child_process/readFileSync.txt b/test/js/node/child_process/readFileSync.txt new file mode 100644 index 000000000..ddc94b988 --- /dev/null +++ b/test/js/node/child_process/readFileSync.txt @@ -0,0 +1 @@ +File read successfully \ No newline at end of file diff --git a/test/js/node/child_process/spawned-child.js b/test/js/node/child_process/spawned-child.js new file mode 100644 index 000000000..263c566f9 --- /dev/null +++ b/test/js/node/child_process/spawned-child.js @@ -0,0 +1,29 @@ +const TARGET = process.argv[2]; +const MODE = process.argv[3]; + +if (TARGET === "STDIN") { + let data = ""; + process.stdin.setEncoding("utf8"); + if (MODE === "READABLE") { + process.stdin.on("readable", () => { + let chunk; + while ((chunk = process.stdin.read()) !== null) { + data += chunk; + } + }); + } else { + process.stdin.on("data", chunk => { + data += chunk; + }); + } + process.stdin.on("end", () => { + process.stdout.write("data: "); + process.stdout.write(data); + }); +} else if (TARGET === "STDOUT") { + process.stdout.write("stdout_test"); +} else if (TARGET === "ERROR") { + console.log("oops"); +} else { + // nothing +} diff --git a/test/js/node/crypto/crypto-scrypt.test.js b/test/js/node/crypto/crypto-scrypt.test.js new file mode 100644 index 000000000..4b7412251 --- /dev/null +++ b/test/js/node/crypto/crypto-scrypt.test.js @@ -0,0 +1,232 @@ +// most of these tests are taken from Node.js +// thank you Node.js team for the tests +import { expect, it } from "bun:test"; +const crypto = require("crypto"); + +const good = [ + // Zero-length key is legal, functions as a parameter validation check. + { + pass: "", + salt: "", + keylen: 0, + N: 16, + p: 1, + r: 1, + expected: "", + }, + // Test vectors from https://tools.ietf.org/html/rfc7914#page-13 that + // should pass. Note that the test vector with N=1048576 is omitted + // because it takes too long to complete and uses over 1 GB of memory. + { + pass: "", + salt: "", + keylen: 64, + N: 16, + p: 1, + r: 1, + expected: + "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442" + + "fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906", + }, + { + pass: "password", + salt: "NaCl", + keylen: 64, + N: 1024, + p: 16, + r: 8, + expected: + "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162" + + "2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640", + }, + { + pass: "pleaseletmein", + salt: "SodiumChloride", + keylen: 64, + N: 16384, + p: 1, + r: 8, + expected: + "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2" + + "d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887", + }, + { + pass: "", + salt: "", + keylen: 64, + cost: 16, + parallelization: 1, + blockSize: 1, + expected: + "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442" + + "fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906", + }, + { + pass: "password", + salt: "NaCl", + keylen: 64, + cost: 1024, + parallelization: 16, + blockSize: 8, + expected: + "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162" + + "2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640", + }, + { + pass: "pleaseletmein", + salt: "SodiumChloride", + keylen: 64, + cost: 16384, + parallelization: 1, + blockSize: 8, + expected: + "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2" + + "d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887", + }, +]; + +// Test vectors that should fail. +const bad = [ + { N: 1, p: 1, r: 1 }, // N < 2 + { N: 3, p: 1, r: 1 }, // Not power of 2. + { N: 1, cost: 1 }, // Both N and cost + // TODO: these should error, but I don't quite understand why. + // { p: 1, parallelization: 1 }, // Both p and parallelization + // { r: 1, blockSize: 1 }, // Both r and blocksize +]; + +// Test vectors where 128*N*r exceeds maxmem. +const toobig = [ + { N: 2 ** 16, p: 1, r: 1 }, // N >= 2**(r*16) + { N: 2, p: 2 ** 30, r: 1 }, // p > (2**30-1)/r + { N: 2 ** 20, p: 1, r: 8 }, + { N: 2 ** 10, p: 1, r: 8, maxmem: 2 ** 20 }, +]; + +const badargs = [ + { + args: [], + expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"password"/ */ }, + }, + { + args: [null], + expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"password"/ */ }, + }, + { + args: [""], + expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"salt"/ */ }, + }, + { + args: ["", null], + expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"salt"/ */ }, + }, + { + args: ["", ""], + expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"keylen"/ */ }, + }, + { + args: ["", "", null], + expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"keylen"/ */ }, + }, + { + args: ["", "", 42, null], + expected: { code: "ERR_INVALID_ARG_TYPE" }, + }, + // TODO: throw on these + // { + // args: ["", "", 42, {}], + // expected: { code: "ERR_INVALID_ARG_TYPE" }, + // }, + // { + // args: ["", "", 42, {}, {}], + // expected: { code: "ERR_INVALID_ARG_TYPE" }, + // }, + // { + // args: ["", "", 42, {}, null], + // expected: { code: "ERR_INVALID_ARG_TYPE" }, + // }, + // { + // args: ["", "", 0.42], + // expected: { code: "ERR_OUT_OF_RANGE" /*message: /"keylen"/ */ }, + // }, + // { + // args: ["", "", -42], + // expected: { code: "ERR_OUT_OF_RANGE" /*message: /"keylen"/ */ }, + // }, + // { + // args: ["", "", 2147485780], + // expected: { code: "ERR_OUT_OF_RANGE" /*message: /"keylen"/ */ }, + // }, + // { + // args: ["", "", 0, { maxmem: 2 ** 53 }], + // expected: { code: "ERR_OUT_OF_RANGE" /*message: /"keylen"/ */ }, + // }, +]; + +it("scrypt good", () => { + for (const options of good) { + const { pass, salt, keylen, expected } = options; + const actual = crypto.scryptSync(pass, salt, keylen, options); + expect(actual.toString("hex")).toBe(expected); + } +}); + +it("scrypt bad", () => { + for (const options of bad) { + expect(() => crypto.scryptSync("pass", "salt", 1, options)).toThrow(/Invalid scrypt param/); + } +}); + +it("scrypt toobig", () => { + for (const options of toobig) { + expect(() => crypto.scryptSync("pass", "salt", 1, options)).toThrow(/Invalid scrypt param/); + } +}); + +it("scrypt defaults eql", () => { + const defaults = { N: 16384, p: 1, r: 8 }; + const expected = crypto.scryptSync("pass", "salt", 1, defaults); + const actual = crypto.scryptSync("pass", "salt", 1); + expect(actual.toString("hex")).toBe(expected.toString("hex")); +}); + +// TODO: DEFAULT_ENCODING is read-only +// it("scrypt defaults encoding", () => { +// { +// const defaultEncoding = crypto.DEFAULT_ENCODING; +// const defaults = { N: 16384, p: 1, r: 8 }; +// const expected = crypto.scryptSync("pass", "salt", 1, defaults); + +// const testEncoding = "latin1"; +// crypto.DEFAULT_ENCODING = testEncoding; +// const actual = crypto.scryptSync("pass", "salt", 1); +// expect(actual).toBe(expected.toString(testEncoding)); + +// crypto.DEFAULT_ENCODING = defaultEncoding; +// } +// }); + +it("scrypt badargs", () => { + for (const { args, expected } of badargs) { + try { + crypto.scryptSync(...args); + expect(() => {}).toThrow(); + } catch (e) { + if (!("code" in e)) throw e; + expect(e.code).toBe(expected.code); + } + } + + // { + // // Values for maxmem that do not fit in 32 bits but that are still safe + // // integers should be allowed. + // crypto.scrypt( + // "", + // "", + // 4, + // { maxmem: 2 ** 52 }, + // common.mustSucceed((actual) => { + // expect(actual.toString("hex")).toBe("d72c87d0"); + // }), + // ); +}); diff --git a/test/js/node/crypto/crypto.test.js b/test/js/node/crypto/crypto.test.js new file mode 100644 index 000000000..b5b8e9286 --- /dev/null +++ b/test/js/node/crypto/crypto.test.js @@ -0,0 +1,106 @@ +import { sha, MD5, MD4, SHA1, SHA224, SHA256, SHA384, SHA512, SHA512_256, gc, CryptoHasher } from "bun"; +import { it, expect, describe } from "bun:test"; +import { readFileSync } from "fs"; + +const HashClasses = [MD5, MD4, SHA1, SHA224, SHA256, SHA384, SHA512, SHA512_256]; + +describe("CryptoHasher", () => { + it("CryptoHasher.algorithms", () => { + expect(CryptoHasher.algorithms).toEqual([ + "blake2b256", + "md4", + "md5", + "ripemd160", + "sha1", + "sha224", + "sha256", + "sha384", + "sha512", + "sha512-256", + ]); + }); + + it("CryptoHasher md5", () => { + var hasher = new CryptoHasher("md5"); + hasher.update("hello world"); + expect(hasher.digest("hex")).toBe("5eb63bbbe01eeed093cb22bb8f5acdc3"); + expect(hasher.algorithm).toBe("md5"); + }); + + it("CryptoHasher blake2b256", () => { + var hasher = new CryptoHasher("blake2b256"); + hasher.update("hello world"); + expect(hasher.algorithm).toBe("blake2b256"); + + expect(hasher.digest("hex")).toBe( + // b2sum --length=256 + "256c83b297114d201b30179f3f0ef0cace9783622da5974326b436178aeef610", + ); + }); + + it("CryptoHasher sha512", () => { + var hasher = new CryptoHasher("sha512"); + hasher.update("hello world"); + expect(hasher.digest("hex")).toBe( + "309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f989dd35bc5ff499670da34255b45b0cfd830e81f605dcf7dc5542e93ae9cd76f", + ); + expect(hasher.algorithm).toBe("sha512"); + }); +}); + +describe("crypto", () => { + for (let Hash of HashClasses) { + for (let [input, label] of [ + ["hello world", '"hello world"'], + ["hello world".repeat(20).slice(), '"hello world" x 20'], + ["", "empty string"], + ["a", '"a"'], + ]) { + describe(label, () => { + gc(true); + + it(`${Hash.name} base64`, () => { + gc(true); + const result = new Hash(); + result.update(input); + expect(typeof result.digest("base64")).toBe("string"); + gc(true); + }); + + it(`${Hash.name} hash base64`, () => { + Hash.hash(input, "base64"); + gc(true); + }); + + it(`${Hash.name} hex`, () => { + const result = new Hash(); + result.update(input); + expect(typeof result.digest("hex")).toBe("string"); + gc(true); + }); + + it(`${Hash.name} hash hex`, () => { + expect(typeof Hash.hash(input, "hex")).toBe("string"); + gc(true); + }); + + it(`${Hash.name} buffer`, () => { + var buf = new Uint8Array(256); + const result = new Hash(); + + result.update(input); + expect(result.digest(buf)).toBe(buf); + expect(buf[0] != 0).toBe(true); + gc(true); + }); + + it(`${Hash.name} buffer`, () => { + var buf = new Uint8Array(256); + + expect(Hash.hash(input, buf) instanceof Uint8Array).toBe(true); + gc(true); + }); + }); + } + } +}); diff --git a/test/js/node/crypto/node-crypto.test.js b/test/js/node/crypto/node-crypto.test.js new file mode 100644 index 000000000..f148f4fe9 --- /dev/null +++ b/test/js/node/crypto/node-crypto.test.js @@ -0,0 +1,29 @@ +import { it, expect } from "bun:test"; + +import crypto from "node:crypto"; + +it("crypto.randomBytes should return a Buffer", () => { + expect(crypto.randomBytes(1) instanceof Buffer).toBe(true); + expect(Buffer.isBuffer(crypto.randomBytes(1))).toBe(true); +}); + +// https://github.com/oven-sh/bun/issues/1839 +it("crypto.createHash ", () => { + function fn() { + crypto.createHash("sha1").update(Math.random(), "ascii").digest("base64"); + } + + for (let i = 0; i < 10; i++) fn(); +}); + +it("crypto.createHmac", () => { + const result = crypto.createHmac("sha256", "key").update("message").digest("base64"); + + expect(result).toBe("bp7ym3X//Ft6uuUn1Y/a2y/kLnIZARl2kXNDBl9Y7Uo="); +}); + +it("web crypto", async () => { + let bytes = new Uint8Array(32); + crypto.getRandomValues(bytes); + await crypto.subtle.digest("SHA-256", bytes); +}); diff --git a/test/js/node/dirname.test.js b/test/js/node/dirname.test.js new file mode 100644 index 000000000..98292dc49 --- /dev/null +++ b/test/js/node/dirname.test.js @@ -0,0 +1,9 @@ +import { expect, it } from "bun:test"; + +it("__dirname should work", () => { + expect(import.meta.dir).toBe(__dirname); +}); + +it("__filename should work", () => { + expect(import.meta.path).toBe(__filename); +}); diff --git a/test/js/node/disabled-module.test.js b/test/js/node/disabled-module.test.js new file mode 100644 index 000000000..c12676959 --- /dev/null +++ b/test/js/node/disabled-module.test.js @@ -0,0 +1,38 @@ +import { expect, test } from "bun:test"; + +test("not implemented yet module masquerades as undefined and throws an error", () => { + const worker_threads = import.meta.require("worker_threads"); + + expect(typeof worker_threads).toBe("undefined"); + expect(typeof worker_threads.getEnvironmentData).toBe("undefined"); +}); + +test("AsyncLocalStorage polyfill", () => { + const { AsyncLocalStorage } = import.meta.require("async_hooks"); + + const store = new AsyncLocalStorage(); + var called = false; + expect(store.getStore()).toBe(null); + store.run({ foo: "bar" }, () => { + expect(store.getStore()).toEqual({ foo: "bar" }); + called = true; + }); + expect(store.getStore()).toBe(null); + expect(called).toBe(true); +}); + +test("AsyncResource polyfill", () => { + const { AsyncResource } = import.meta.require("async_hooks"); + + const resource = new AsyncResource("test"); + var called = false; + resource.runInAsyncScope( + () => { + called = true; + }, + null, + "foo", + "bar", + ); + expect(called).toBe(true); +}); diff --git a/test/js/node/dns/dns.node.mjs b/test/js/node/dns/dns.node.mjs new file mode 100644 index 000000000..e69de29bb diff --git a/test/js/node/dns/node-dns.test.js b/test/js/node/dns/node-dns.test.js new file mode 100644 index 000000000..6f4cac22f --- /dev/null +++ b/test/js/node/dns/node-dns.test.js @@ -0,0 +1,148 @@ +import { expect, test } from "bun:test"; +import * as dns from "node:dns"; + +// TODO: +test("it exists", () => { + expect(dns).toBeDefined(); + expect(dns.lookup).toBeDefined(); + expect(dns.lookupService).toBeDefined(); + expect(dns.resolve).toBeDefined(); + expect(dns.resolve4).toBeDefined(); + expect(dns.resolve6).toBeDefined(); + expect(dns.resolveSrv).toBeDefined(); + expect(dns.resolveTxt).toBeDefined(); + expect(dns.resolveSoa).toBeDefined(); + expect(dns.resolveNaptr).toBeDefined(); + expect(dns.resolveMx).toBeDefined(); + expect(dns.resolveCaa).toBeDefined(); + expect(dns.resolveNs).toBeDefined(); + expect(dns.resolvePtr).toBeDefined(); + expect(dns.resolveCname).toBeDefined(); +}); + +// //TODO: use a bun.sh SRV for testing +test("dns.resolveSrv (_test._tcp.test.socketify.dev)", done => { + dns.resolveSrv("_test._tcp.test.socketify.dev", (err, results) => { + expect(err).toBeNull(); + expect(results instanceof Array).toBe(true); + expect(results[0].name).toBe("_dc-srv.130c90ab9de1._test._tcp.test.socketify.dev"); + expect(results[0].priority).toBe(50); + expect(results[0].weight).toBe(50); + expect(results[0].port).toBe(80); + done(err); + }); +}); + +test("dns.resolveSrv (_test._tcp.invalid.localhost)", done => { + dns.resolveSrv("_test._tcp.invalid.localhost", (err, results) => { + expect(err).toBeTruthy(); + expect(results).toBeUndefined(true); + done(); + }); +}); + +test("dns.resolveTxt (txt.socketify.dev)", done => { + dns.resolveTxt("txt.socketify.dev", (err, results) => { + expect(err).toBeNull(); + expect(results instanceof Array).toBe(true); + expect(results[0][0]).toBe("bun_test;test"); + done(err); + }); +}); + +test("dns.resolveSoa (bun.sh)", done => { + dns.resolveSoa("bun.sh", (err, result) => { + expect(err).toBeNull(); + expect(typeof result.serial).toBe("number"); + expect(result.refresh).toBe(10000); + expect(result.retry).toBe(2400); + expect(result.expire).toBe(604800); + expect(result.minttl).toBe(3600); + expect(result.nsname).toBe("hans.ns.cloudflare.com"); + expect(result.hostmaster).toBe("dns.cloudflare.com"); + done(err); + }); +}); + +test("dns.resolveNaptr (naptr.socketify.dev)", done => { + dns.resolveNaptr("naptr.socketify.dev", (err, results) => { + expect(err).toBeNull(); + expect(results instanceof Array).toBe(true); + expect(results[0].flags).toBe("S"); + expect(results[0].service).toBe("test"); + expect(results[0].regexp).toBe(""); + expect(results[0].replacement).toBe(""); + expect(results[0].order).toBe(1); + expect(results[0].preference).toBe(12); + done(err); + }); +}); + +test("dns.resolveCaa (caa.socketify.dev)", done => { + dns.resolveCaa("caa.socketify.dev", (err, results) => { + expect(err).toBeNull(); + expect(results instanceof Array).toBe(true); + expect(results[0].critical).toBe(0); + expect(results[0].issue).toBe("bun.sh"); + done(err); + }); +}); + +test("dns.resolveMx (bun.sh)", done => { + dns.resolveMx("bun.sh", (err, results) => { + expect(err).toBeNull(); + expect(results instanceof Array).toBe(true); + const priority = results[0].priority; + expect(priority >= 0 && priority < 65535).toBe(true); + expect(results[0].exchange.includes(".registrar-servers.com")).toBe(true); + done(err); + }); +}); + +test("dns.resolveNs (bun.sh) ", done => { + dns.resolveNs("bun.sh", (err, results) => { + expect(err).toBeNull(); + expect(results instanceof Array).toBe(true); + expect(results[0].includes(".ns.cloudflare.com")).toBe(true); + done(err); + }); +}); + +test("dns.resolvePtr (ptr.socketify.dev)", done => { + dns.resolvePtr("ptr.socketify.dev", (err, results) => { + expect(err).toBeNull(); + expect(results instanceof Array).toBe(true); + expect(results[0]).toBe("bun.sh"); + done(err); + }); +}); + +test("dns.resolveCname (cname.socketify.dev)", done => { + dns.resolveCname("cname.socketify.dev", (err, results) => { + expect(err).toBeNull(); + expect(results instanceof Array).toBe(true); + expect(results[0]).toBe("bun.sh"); + done(err); + }); +}); + +test("dns.lookup (example.com)", done => { + dns.lookup("example.com", (err, address, family) => { + expect(err).toBeNull(); + expect(typeof address).toBe("string"); + done(err); + }); +}); + +test("dns.lookup (localhost)", done => { + dns.lookup("localhost", (err, address, family) => { + expect(err).toBeNull(); + if (family === 6) { + expect(address).toBe("::1"); + } else { + expect(address).toBe("127.0.0.1"); + } + + done(err); + }); +}); diff --git a/test/js/node/events/event-emitter.test.ts b/test/js/node/events/event-emitter.test.ts new file mode 100644 index 000000000..2bb891778 --- /dev/null +++ b/test/js/node/events/event-emitter.test.ts @@ -0,0 +1,169 @@ +import { test, describe, expect, it } from "bun:test"; +import fs from "node:fs"; + +// this is also testing that imports with default and named imports in the same statement work +// our transpiler transform changes this to a var with import.meta.require +import EventEmitter, { getEventListeners, captureRejectionSymbol } from "node:events"; +import { heapStats } from "bun:jsc"; + +describe("EventEmitter", () => { + it("captureRejectionSymbol", () => { + expect(EventEmitter.captureRejectionSymbol).toBeDefined(); + expect(captureRejectionSymbol).toBeDefined(); + }); + test("getEventListeners", () => { + expect(getEventListeners(new EventEmitter(), "hey").length).toBe(0); + }); + test("EventEmitter constructor", () => { + var emitter = new EventEmitter(); + emitter.setMaxListeners(100); + expect(emitter.getMaxListeners()).toBe(100); + }); + + test("EventEmitter.removeAllListeners()", () => { + var emitter = new EventEmitter(); + var ran = false; + emitter.on("hey", () => { + ran = true; + }); + emitter.removeAllListeners(); + expect(emitter.listenerCount("hey")).toBe(0); + emitter.emit("hey"); + expect(ran).toBe(false); + emitter.on("hey", () => { + ran = true; + }); + emitter.emit("hey"); + expect(ran).toBe(true); + expect(emitter.listenerCount("hey")).toBe(1); + }); + + // These are also tests for the done() function in the test runner. + test("EventEmitter emit (different tick)", done => { + var emitter = new EventEmitter(); + emitter.on("wow", () => done()); + queueMicrotask(() => { + emitter.emit("wow"); + }); + }); + + // Unlike Jest, bun supports async and done + test("async EventEmitter emit (microtask)", async done => { + await 1; + var emitter = new EventEmitter(); + emitter.on("wow", () => done()); + emitter.emit("wow"); + }); + + test("async EventEmitter emit (microtask) after", async done => { + var emitter = new EventEmitter(); + emitter.on("wow", () => done()); + await 1; + emitter.emit("wow"); + }); + + test("EventEmitter emit (same tick)", done => { + var emitter = new EventEmitter(); + + emitter.on("wow", () => done()); + + emitter.emit("wow"); + }); + + test("EventEmitter emit (setTimeout task)", done => { + var emitter = new EventEmitter(); + emitter.on("wow", () => done()); + setTimeout(() => emitter.emit("wow"), 1); + }); +}); + +const waysOfCreating = [ + () => Object.create(EventEmitter.prototype), + () => new EventEmitter(), + () => new (class extends EventEmitter {})(), + () => { + class MyEmitter extends EventEmitter {} + return new MyEmitter(); + }, + () => { + var foo = {}; + Object.setPrototypeOf(foo, EventEmitter.prototype); + return foo; + }, + () => { + const FakeEmitter = function FakeEmitter() { + return EventEmitter.call(this); + }; + Object.setPrototypeOf(FakeEmitter.prototype, EventEmitter.prototype); + Object.setPrototypeOf(FakeEmitter, EventEmitter); + return new FakeEmitter(); + }, + () => { + const FakeEmitter = function FakeEmitter() { + EventEmitter.call(this); + }; + Object.assign(FakeEmitter.prototype, EventEmitter.prototype); + Object.assign(FakeEmitter, EventEmitter); + return new FakeEmitter(); + }, + () => { + var foo = {}; + Object.assign(foo, EventEmitter.prototype); + return foo; + }, +]; + +for (let create of waysOfCreating) { + it(`${create.toString().slice(10, 40).replaceAll("\n", "\\n").trim()} should work`, () => { + var myEmitter = create(); + var called = false; + myEmitter.once("event", function () { + called = true; + expect(this as any).toBe(myEmitter); + }); + var firstEvents = myEmitter._events; + expect(myEmitter.listenerCount("event")).toBe(1); + + expect(myEmitter.emit("event")).toBe(true); + expect(myEmitter.listenerCount("event")).toBe(0); + + expect(firstEvents).toBe(myEmitter._events); + expect(called).toBe(true); + }); +} + +test("EventEmitter.on", () => { + var myEmitter = new EventEmitter(); + expect(myEmitter.on("foo", () => {})).toBe(myEmitter); +}); + +test("EventEmitter.off", () => { + var myEmitter = new EventEmitter(); + expect(myEmitter.off("foo", () => {})).toBe(myEmitter); +}); + +// Internally, EventEmitter has a JSC::Weak with the thisValue of the listener +test("EventEmitter GCs", () => { + Bun.gc(true); + + const startCount = heapStats().objectTypeCounts["EventEmitter"] || 0; + (function () { + Bun.gc(true); + + function EventEmitterSubclass(this: any) { + EventEmitter.call(this); + } + + Object.setPrototypeOf(EventEmitterSubclass.prototype, EventEmitter.prototype); + Object.setPrototypeOf(EventEmitterSubclass, EventEmitter); + + var myEmitter = new EventEmitterSubclass(); + myEmitter.on("foo", () => {}); + myEmitter.emit("foo"); + Bun.gc(true); + })(); + Bun.gc(true); + + const endCount = heapStats().objectTypeCounts["EventEmitter"] || 0; + expect(endCount).toBe(startCount); +}); diff --git a/test/js/node/events/node-builtins.test.js b/test/js/node/events/node-builtins.test.js new file mode 100644 index 000000000..67050f31a --- /dev/null +++ b/test/js/node/events/node-builtins.test.js @@ -0,0 +1,18 @@ +import { describe, it, expect } from "bun:test"; + +import { EventEmitter } from "events"; +var emitters = [EventEmitter, require("events")]; +describe("EventEmitter", () => { + it("should emit events", () => { + for (let Emitter of emitters) { + const emitter = new Emitter(); + var called = false; + const listener = () => { + called = true; + }; + emitter.on("test", listener); + emitter.emit("test"); + expect(called).toBe(true); + } + }); +}); diff --git a/test/js/node/fs/export-*-from.ts b/test/js/node/fs/export-*-from.ts new file mode 100644 index 000000000..1c2b97875 --- /dev/null +++ b/test/js/node/fs/export-*-from.ts @@ -0,0 +1 @@ +export * from "node:fs"; diff --git a/test/js/node/fs/export-from.ts b/test/js/node/fs/export-from.ts new file mode 100644 index 000000000..5258f9fde --- /dev/null +++ b/test/js/node/fs/export-from.ts @@ -0,0 +1 @@ +export { ReadStream, WriteStream } from "node:fs"; diff --git a/test/js/node/fs/fs-stream.js b/test/js/node/fs/fs-stream.js new file mode 100644 index 000000000..db56fdfaa --- /dev/null +++ b/test/js/node/fs/fs-stream.js @@ -0,0 +1,21 @@ +import { createReadStream, createWriteStream, readFileSync } from "fs"; + +await new Promise((resolve, reject) => { + createReadStream("fs-stream.js") + .pipe(createWriteStream("/tmp/fs-stream.copy.js")) + .once("error", err => reject(err)) + .once("finish", () => { + try { + const copied = readFileSync("/tmp/fs-stream.copy.js", "utf8"); + const real = readFileSync("/tmp/fs-stream.js", "utf8"); + if (copied !== real) { + reject(new Error("fs-stream.js is not the same as fs-stream.copy.js")); + return; + } + + resolve(true); + } catch (err) { + reject(err); + } + }); +}); diff --git a/test/js/node/fs/fs-stream.link.js b/test/js/node/fs/fs-stream.link.js new file mode 120000 index 000000000..0cadae0e5 --- /dev/null +++ b/test/js/node/fs/fs-stream.link.js @@ -0,0 +1 @@ +./test/bun.js/fs-stream.js \ No newline at end of file diff --git a/test/js/node/fs/fs.test.ts b/test/js/node/fs/fs.test.ts new file mode 100644 index 000000000..48abef6cb --- /dev/null +++ b/test/js/node/fs/fs.test.ts @@ -0,0 +1,1013 @@ +import { beforeEach, describe, expect, it } from "bun:test"; +import { gc, gcTick } from "harness"; +import fs, { + closeSync, + existsSync, + mkdirSync, + openSync, + readdirSync, + readFile, + readFileSync, + readSync, + writeFileSync, + writeSync, + statSync, + lstatSync, + copyFileSync, + rmSync, + rmdir, + rmdirSync, + createReadStream, + createWriteStream, + promises, + unlinkSync, + mkdtempSync, + constants, + Dirent, + Stats, +} from "node:fs"; + +import _promises from "node:fs/promises"; + +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +import { ReadStream as ReadStream_, WriteStream as WriteStream_ } from "./export-from.js"; +import { ReadStream as ReadStreamStar_, WriteStream as WriteStreamStar_ } from "./export-*-from.js"; + +const Buffer = globalThis.Buffer || Uint8Array; + +if (!import.meta.dir) { + import.meta.dir = "."; +} + +function mkdirForce(path) { + if (!existsSync(path)) mkdirSync(path, { recursive: true }); +} + +describe("copyFileSync", () => { + it("should work for files < 128 KB", () => { + const tempdir = `/tmp/fs.test.js/${Date.now()}/1234/hi`; + expect(existsSync(tempdir)).toBe(false); + expect(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true); + + // that don't exist + copyFileSync(import.meta.path, tempdir + "/copyFileSync.js"); + expect(existsSync(tempdir + "/copyFileSync.js")).toBe(true); + expect(readFileSync(tempdir + "/copyFileSync.js", "utf-8")).toBe(readFileSync(import.meta.path, "utf-8")); + + // that do exist + copyFileSync(tempdir + "/copyFileSync.js", tempdir + "/copyFileSync.js1"); + writeFileSync(tempdir + "/copyFileSync.js1", "hello"); + copyFileSync(tempdir + "/copyFileSync.js1", tempdir + "/copyFileSync.js"); + + expect(readFileSync(tempdir + "/copyFileSync.js", "utf-8")).toBe("hello"); + }); + + it("should work for files > 128 KB ", () => { + const tempdir = `/tmp/fs.test.js/${Date.now()}-1/1234/hi`; + expect(existsSync(tempdir)).toBe(false); + expect(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true); + var buffer = new Int32Array(128 * 1024); + for (let i = 0; i < buffer.length; i++) { + buffer[i] = i % 256; + } + + const hash = Bun.hash(buffer.buffer); + writeFileSync(tempdir + "/copyFileSync.src.blob", buffer.buffer); + + expect(existsSync(tempdir + "/copyFileSync.dest.blob")).toBe(false); + expect(existsSync(tempdir + "/copyFileSync.src.blob")).toBe(true); + copyFileSync(tempdir + "/copyFileSync.src.blob", tempdir + "/copyFileSync.dest.blob"); + + expect(Bun.hash(readFileSync(tempdir + "/copyFileSync.dest.blob"))).toBe(hash); + buffer[0] = 255; + writeFileSync(tempdir + "/copyFileSync.src.blob", buffer.buffer); + copyFileSync(tempdir + "/copyFileSync.src.blob", tempdir + "/copyFileSync.dest.blob"); + expect(Bun.hash(readFileSync(tempdir + "/copyFileSync.dest.blob"))).toBe(Bun.hash(buffer.buffer)); + }); +}); + +describe("mkdirSync", () => { + it("should create a directory", () => { + const tempdir = `/tmp/fs.test.js/${Date.now()}/1234/hi`; + expect(existsSync(tempdir)).toBe(false); + expect(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true); + expect(existsSync(tempdir)).toBe(true); + }); +}); + +it("readdirSync on import.meta.dir", () => { + const dirs = readdirSync(import.meta.dir); + expect(dirs.length > 0).toBe(true); + var match = false; + gc(true); + for (let i = 0; i < dirs.length; i++) { + if (dirs[i] === import.meta.file) { + match = true; + } + } + gc(true); + expect(match).toBe(true); +}); + +// https://github.com/oven-sh/bun/issues/1887 +it("mkdtempSync, readdirSync, rmdirSync and unlinkSync with non-ascii", () => { + const tempdir = mkdtempSync(`/tmp/emoji-fruit-🍇 🍈 🍉 🍊 🍋`); + expect(existsSync(tempdir)).toBe(true); + writeFileSync(tempdir + "/non-ascii-👍.txt", "hello"); + const dirs = readdirSync(tempdir); + expect(dirs.length > 0).toBe(true); + var match = false; + gc(true); + for (let i = 0; i < dirs.length; i++) { + if (dirs[i].endsWith("non-ascii-👍.txt")) { + match = true; + break; + } + } + gc(true); + expect(match).toBe(true); + unlinkSync(tempdir + "/non-ascii-👍.txt"); + expect(existsSync(tempdir + "/non-ascii-👍.txt")).toBe(false); + rmdirSync(tempdir); + expect(existsSync(tempdir)).toBe(false); +}); + +it("mkdtempSync() empty name", () => { + // @ts-ignore-next-line + const tempdir = mkdtempSync(); + expect(existsSync(tempdir)).toBe(true); + writeFileSync(tempdir + "/non-ascii-👍.txt", "hello"); + const dirs = readdirSync(tempdir); + expect(dirs.length > 0).toBe(true); + var match = false; + gc(true); + for (let i = 0; i < dirs.length; i++) { + if (dirs[i].endsWith("non-ascii-👍.txt")) { + match = true; + break; + } + } + gc(true); + expect(match).toBe(true); + unlinkSync(tempdir + "/non-ascii-👍.txt"); + expect(existsSync(tempdir + "/non-ascii-👍.txt")).toBe(false); + rmdirSync(tempdir); + expect(existsSync(tempdir)).toBe(false); +}); + +it("readdirSync on import.meta.dir with trailing slash", () => { + const dirs = readdirSync(import.meta.dir + "/"); + expect(dirs.length > 0).toBe(true); + // this file should exist in it + var match = false; + for (let i = 0; i < dirs.length; i++) { + if (dirs[i] === import.meta.file) { + match = true; + } + } + expect(match).toBe(true); +}); + +it("readdirSync works on empty directories", () => { + const path = `/tmp/fs-test-empty-dir-${(Math.random() * 100000 + 100).toString(32)}`; + mkdirSync(path, { recursive: true }); + expect(readdirSync(path).length).toBe(0); +}); + +it("readdirSync works on directories with under 32 files", () => { + const path = `/tmp/fs-test-one-dir-${(Math.random() * 100000 + 100).toString(32)}`; + mkdirSync(path, { recursive: true }); + writeFileSync(`${path}/a`, "a"); + const results = readdirSync(path); + expect(results.length).toBe(1); + expect(results[0]).toBe("a"); +}); + +it("readdirSync throws when given a file path", () => { + try { + readdirSync(import.meta.path); + throw new Error("should not get here"); + } catch (exception: any) { + expect(exception.name).toBe("ENOTDIR"); + } +}); + +it("readdirSync throws when given a path that doesn't exist", () => { + try { + readdirSync(import.meta.path + "/does-not-exist/really"); + throw new Error("should not get here"); + } catch (exception: any) { + expect(exception.name).toBe("ENOTDIR"); + } +}); + +it("readdirSync throws when given a file path with trailing slash", () => { + try { + readdirSync(import.meta.path + "/"); + throw new Error("should not get here"); + } catch (exception: any) { + expect(exception.name).toBe("ENOTDIR"); + } +}); + +describe("readSync", () => { + const firstFourBytes = new Uint32Array(new TextEncoder().encode("File").buffer)[0]; + it("works with a position set to 0", () => { + const fd = openSync(import.meta.dir + "/readFileSync.txt", "r"); + const four = new Uint8Array(4); + + { + const count = readSync(fd, four, 0, 4, 0); + const u32 = new Uint32Array(four.buffer)[0]; + expect(u32).toBe(firstFourBytes); + expect(count).toBe(4); + } + closeSync(fd); + }); + it("works without position set", () => { + const fd = openSync(import.meta.dir + "/readFileSync.txt", "r"); + const four = new Uint8Array(4); + { + const count = readSync(fd, four); + const u32 = new Uint32Array(four.buffer)[0]; + expect(u32).toBe(firstFourBytes); + expect(count).toBe(4); + } + closeSync(fd); + }); +}); + +describe("writeSync", () => { + it("works with a position set to 0", () => { + const fd = openSync(import.meta.dir + "/writeFileSync.txt", "w+"); + const four = new Uint8Array(4); + + { + const count = writeSync(fd, new TextEncoder().encode("File"), 0, 4, 0); + expect(count).toBe(4); + } + closeSync(fd); + }); + it("works without position set", () => { + const fd = openSync(import.meta.dir + "/writeFileSync.txt", "w+"); + const four = new Uint8Array(4); + { + const count = writeSync(fd, new TextEncoder().encode("File")); + expect(count).toBe(4); + } + closeSync(fd); + }); +}); + +describe("readFileSync", () => { + it("works", () => { + gc(); + const text = readFileSync(import.meta.dir + "/readFileSync.txt", "utf8"); + gc(); + expect(text).toBe("File read successfully"); + gc(); + }); + + it("works with a file url", () => { + gc(); + const text = readFileSync(new URL("file://" + import.meta.dir + "/readFileSync.txt"), "utf8"); + gc(); + expect(text).toBe("File read successfully"); + }); + + it("works with special files in the filesystem", () => { + { + const text = readFileSync("/dev/null", "utf8"); + gc(); + expect(text).toBe(""); + } + + if (process.platform === "linux") { + const text = readFileSync("/proc/filesystems"); + gc(); + expect(text.length > 0).toBe(true); + } + }); + + it("returning Buffer works", () => { + const text = readFileSync(import.meta.dir + "/readFileSync.txt"); + const encoded = [ + 70, 105, 108, 101, 32, 114, 101, 97, 100, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108, 121, + ]; + for (let i = 0; i < encoded.length; i++) { + expect(text[i]).toBe(encoded[i]); + } + }); +}); + +describe("readFile", () => { + it("works", async () => { + gc(); + await new Promise((resolve, reject) => { + readFile(import.meta.dir + "/readFileSync.txt", "utf8", (err, text) => { + gc(); + expect(text).toBe("File read successfully"); + resolve(true); + }); + }); + }); + + it("returning Buffer works", async () => { + gc(); + await new Promise((resolve, reject) => { + gc(); + readFile(import.meta.dir + "/readFileSync.txt", (err, text) => { + const encoded = [ + 70, 105, 108, 101, 32, 114, 101, 97, 100, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108, 121, + ]; + gc(); + for (let i = 0; i < encoded.length; i++) { + expect(text[i]).toBe(encoded[i]); + } + resolve(true); + }); + }); + }); +}); + +describe("writeFileSync", () => { + it("works", () => { + const path = `/tmp/${Date.now()}.writeFileSync.txt`; + writeFileSync(path, "File written successfully", "utf8"); + + expect(readFileSync(path, "utf8")).toBe("File written successfully"); + }); + + it("returning Buffer works", () => { + const buffer = new Buffer([ + 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 = `/tmp/${Date.now()}.blob.writeFileSync.txt`; + writeFileSync(path, buffer); + const out = readFileSync(path); + + for (let i = 0; i < buffer.length; i++) { + expect(buffer[i]).toBe(out[i]); + } + }); + it("returning ArrayBuffer works", () => { + const buffer = new Buffer([ + 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 = `/tmp/${Date.now()}.blob2.writeFileSync.txt`; + writeFileSync(path, buffer); + const out = readFileSync(path); + + for (let i = 0; i < buffer.length; i++) { + expect(buffer[i]).toBe(out[i]); + } + }); +}); + +function triggerDOMJIT(target, fn, result) { + for (let i = 0; i < 9999; i++) { + if (fn.apply(target) !== result) { + throw new Error("DOMJIT failed"); + } + } +} + +describe("lstat", () => { + it("file metadata is correct", () => { + const fileStats = lstatSync(new URL("./fs-stream.js", import.meta.url).toString().slice("file://".length - 1)); + expect(fileStats.isSymbolicLink()).toBe(false); + expect(fileStats.isFile()).toBe(true); + expect(fileStats.isDirectory()).toBe(false); + + triggerDOMJIT(fileStats, fileStats.isFile, true); + triggerDOMJIT(fileStats, fileStats.isDirectory, false); + triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false); + }); + + it("folder metadata is correct", () => { + const fileStats = lstatSync(new URL("../../test", import.meta.url).toString().slice("file://".length - 1)); + expect(fileStats.isSymbolicLink()).toBe(false); + expect(fileStats.isFile()).toBe(false); + expect(fileStats.isDirectory()).toBe(true); + + triggerDOMJIT(fileStats, fileStats.isFile, false); + triggerDOMJIT(fileStats, fileStats.isDirectory, true); + triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false); + }); + + it("symlink metadata is correct", () => { + const linkStats = lstatSync(new URL("./fs-stream.link.js", import.meta.url).toString().slice("file://".length - 1)); + expect(linkStats.isSymbolicLink()).toBe(true); + expect(linkStats.isFile()).toBe(false); + expect(linkStats.isDirectory()).toBe(false); + + triggerDOMJIT(linkStats, linkStats.isFile, false); + triggerDOMJIT(linkStats, linkStats.isDirectory, false); + triggerDOMJIT(linkStats, linkStats.isSymbolicLink, true); + }); +}); + +describe("stat", () => { + it("file metadata is correct", () => { + const fileStats = statSync(new URL("./fs-stream.js", import.meta.url).toString().slice("file://".length - 1)); + expect(fileStats.isSymbolicLink()).toBe(false); + expect(fileStats.isFile()).toBe(true); + expect(fileStats.isDirectory()).toBe(false); + + triggerDOMJIT(fileStats, fileStats.isFile, true); + triggerDOMJIT(fileStats, fileStats.isDirectory, false); + triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false); + }); + + it("folder metadata is correct", () => { + const fileStats = statSync(new URL("../../test", import.meta.url).toString().slice("file://".length - 1)); + expect(fileStats.isSymbolicLink()).toBe(false); + expect(fileStats.isFile()).toBe(false); + expect(fileStats.isDirectory()).toBe(true); + expect(typeof fileStats.dev).toBe("number"); + expect(typeof fileStats.ino).toBe("number"); + expect(typeof fileStats.mode).toBe("number"); + expect(typeof fileStats.nlink).toBe("number"); + expect(typeof fileStats.uid).toBe("number"); + expect(typeof fileStats.gid).toBe("number"); + expect(typeof fileStats.rdev).toBe("number"); + expect(typeof fileStats.size).toBe("number"); + expect(typeof fileStats.blksize).toBe("number"); + expect(typeof fileStats.blocks).toBe("number"); + expect(typeof fileStats.atimeMs).toBe("number"); + expect(typeof fileStats.mtimeMs).toBe("number"); + expect(typeof fileStats.ctimeMs).toBe("number"); + expect(typeof fileStats.birthtimeMs).toBe("number"); + expect(typeof fileStats.atime).toBe("object"); + expect(typeof fileStats.mtime).toBe("object"); + expect(typeof fileStats.ctime).toBe("object"); + expect(typeof fileStats.birthtime).toBe("object"); + + triggerDOMJIT(fileStats, fileStats.isFile, false); + triggerDOMJIT(fileStats, fileStats.isDirectory, true); + triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false); + }); + + it("stat returns ENOENT", () => { + try { + statSync("/tmp/doesntexist"); + throw "statSync should throw"; + } catch (e: any) { + expect(e.code).toBe("ENOENT"); + } + }); +}); + +describe("rm", () => { + it("removes a file", () => { + const path = `/tmp/${Date.now()}.rm.txt`; + writeFileSync(path, "File written successfully", "utf8"); + expect(existsSync(path)).toBe(true); + rmSync(path); + expect(existsSync(path)).toBe(false); + }); + + it("removes a dir", () => { + const path = `/tmp/${Date.now()}.rm.dir`; + try { + mkdirSync(path); + } catch (e) {} + expect(existsSync(path)).toBe(true); + rmSync(path); + expect(existsSync(path)).toBe(false); + }); + + it("removes a dir recursively", () => { + const path = `/tmp/${Date.now()}.rm.dir/foo/bar`; + try { + mkdirSync(path, { recursive: true }); + } catch (e) {} + expect(existsSync(path)).toBe(true); + rmSync(join(path, "../../"), { recursive: true }); + expect(existsSync(path)).toBe(false); + }); +}); + +describe("rmdir", () => { + it("removes a file", done => { + const path = `/tmp/${Date.now()}.rm.txt`; + writeFileSync(path, "File written successfully", "utf8"); + expect(existsSync(path)).toBe(true); + rmdir(path, err => { + try { + expect(err).toBeDefined(); + expect(err!.code).toBe("EPERM"); + expect(err!.message).toBe("Operation not permitted"); + expect(existsSync(path)).toBe(true); + } catch (e) { + return done(e); + } finally { + done(); + } + }); + }); + + it("removes a dir", done => { + const path = `/tmp/${Date.now()}.rm.dir`; + try { + mkdirSync(path); + } catch (e) {} + expect(existsSync(path)).toBe(true); + rmdir(path, err => { + if (err) return done(err); + expect(existsSync(path)).toBe(false); + done(); + }); + }); + // TODO support `recursive: true` + it("removes a dir recursively", done => { + const path = `/tmp/${Date.now()}.rm.dir/foo/bar`; + try { + mkdirSync(path, { recursive: true }); + } catch (e) {} + expect(existsSync(path)).toBe(true); + rmdir(join(path, "../../"), { recursive: true }, err => { + try { + expect(existsSync(path)).toBe(false); + done(err); + } catch (e) { + return done(e); + } finally { + done(); + } + }); + }); +}); + +describe("rmdirSync", () => { + it("removes a file", () => { + const path = `/tmp/${Date.now()}.rm.txt`; + writeFileSync(path, "File written successfully", "utf8"); + expect(existsSync(path)).toBe(true); + expect(() => { + rmdirSync(path); + }).toThrow("Operation not permitted"); + expect(existsSync(path)).toBe(true); + }); + it("removes a dir", () => { + const path = `/tmp/${Date.now()}.rm.dir`; + try { + mkdirSync(path); + } catch (e) {} + expect(existsSync(path)).toBe(true); + rmdirSync(path); + expect(existsSync(path)).toBe(false); + }); + // TODO support `recursive: true` + it("removes a dir recursively", () => { + const path = `/tmp/${Date.now()}.rm.dir/foo/bar`; + try { + mkdirSync(path, { recursive: true }); + } catch (e) {} + expect(existsSync(path)).toBe(true); + rmdirSync(join(path, "../../"), { recursive: true }); + expect(existsSync(path)).toBe(false); + }); +}); + +describe("createReadStream", () => { + it("works (1 chunk)", async () => { + return await new Promise((resolve, reject) => { + var stream = createReadStream(import.meta.dir + "/readFileSync.txt", {}); + + stream.on("error", e => { + reject(e); + }); + + stream.on("data", chunk => { + expect(chunk instanceof Buffer).toBe(true); + expect(chunk.length).toBe("File read successfully".length); + expect(chunk.toString()).toBe("File read successfully"); + }); + + stream.on("close", () => { + resolve(true); + }); + }); + }); + + it("works (22 chunk)", async () => { + var stream = createReadStream(import.meta.dir + "/readFileSync.txt", { + highWaterMark: 1, + }); + + var data = readFileSync(import.meta.dir + "/readFileSync.txt", "utf8"); + var i = 0; + return await new Promise(resolve => { + stream.on("data", chunk => { + expect(chunk instanceof Buffer).toBe(true); + expect(chunk.length).toBe(1); + expect(chunk.toString()).toBe(data[i++]); + }); + + stream.on("end", () => { + resolve(true); + }); + }); + }); +}); + +describe("fs.WriteStream", () => { + it("should be exported", () => { + expect(fs.WriteStream).toBeDefined(); + }); + + it("should be constructable", () => { + // @ts-ignore-next-line + const stream = new fs.WriteStream("test.txt"); + expect(stream instanceof fs.WriteStream).toBe(true); + }); + + it("should be able to write to a file", done => { + const pathToDir = `${tmpdir()}/${Date.now()}`; + mkdirForce(pathToDir); + const path = join(pathToDir, `fs-writestream-test.txt`); + + // @ts-ignore-next-line + const stream = new fs.WriteStream(path, { flags: "w+" }); + stream.write("Test file written successfully"); + stream.end(); + + stream.on("error", e => { + done(e instanceof Error ? e : new Error(e)); + }); + + stream.on("finish", () => { + expect(readFileSync(path, "utf8")).toBe("Test file written successfully"); + done(); + }); + }); + + it("should work if re-exported by name", () => { + // @ts-ignore-next-line + const stream = new WriteStream_("test.txt"); + expect(stream instanceof WriteStream_).toBe(true); + expect(stream instanceof WriteStreamStar_).toBe(true); + expect(stream instanceof fs.WriteStream).toBe(true); + }); + + it("should work if re-exported by name, called without new", () => { + // @ts-ignore-next-line + const stream = WriteStream_("test.txt"); + expect(stream instanceof WriteStream_).toBe(true); + expect(stream instanceof WriteStreamStar_).toBe(true); + expect(stream instanceof fs.WriteStream).toBe(true); + }); + + it("should work if re-exported, as export * from ...", () => { + // @ts-ignore-next-line + const stream = new WriteStreamStar_("test.txt"); + expect(stream instanceof WriteStream_).toBe(true); + expect(stream instanceof WriteStreamStar_).toBe(true); + expect(stream instanceof fs.WriteStream).toBe(true); + }); + + it("should work if re-exported, as export * from..., called without new", () => { + // @ts-ignore-next-line + const stream = WriteStreamStar_("test.txt"); + expect(stream instanceof WriteStream_).toBe(true); + expect(stream instanceof WriteStreamStar_).toBe(true); + expect(stream instanceof fs.WriteStream).toBe(true); + }); + + it("should be able to write to a file with re-exported WriteStream", done => { + const pathToDir = `${tmpdir()}/${Date.now()}`; + mkdirForce(pathToDir); + const path = join(pathToDir, `fs-writestream-re-exported-test.txt`); + // @ts-ignore-next-line + const stream = new WriteStream_(path, { flags: "w+" }); + stream.write("Test file written successfully"); + stream.end(); + + stream.on("error", e => { + done(e instanceof Error ? e : new Error(e)); + }); + + stream.on("finish", () => { + expect(readFileSync(path, "utf8")).toBe("Test file written successfully"); + done(); + }); + }); +}); + +describe("fs.ReadStream", () => { + it("should be exported", () => { + expect(fs.ReadStream).toBeDefined(); + }); + + it("should be constructable", () => { + // @ts-ignore-next-line + const stream = new fs.ReadStream("test.txt"); + expect(stream instanceof fs.ReadStream).toBe(true); + }); + + it("should be able to read from a file", done => { + const pathToDir = `${tmpdir()}/${Date.now()}`; + mkdirForce(pathToDir); + const path = join(pathToDir, `fs-readstream-test.txt`); + + writeFileSync(path, "Test file written successfully", { + encoding: "utf8", + flag: "w+", + }); + // @ts-ignore-next-line + const stream = new fs.ReadStream(path); + stream.setEncoding("utf8"); + stream.on("error", e => { + done(e instanceof Error ? e : new Error(e)); + }); + + let data = ""; + + stream.on("data", chunk => { + data += chunk; + }); + + stream.on("end", () => { + expect(data).toBe("Test file written successfully"); + done(); + }); + }); + + it("should work if re-exported by name", () => { + // @ts-ignore-next-line + const stream = new ReadStream_("test.txt"); + expect(stream instanceof ReadStream_).toBe(true); + expect(stream instanceof ReadStreamStar_).toBe(true); + expect(stream instanceof fs.ReadStream).toBe(true); + }); + + it("should work if re-exported by name, called without new", () => { + // @ts-ignore-next-line + const stream = ReadStream_("test.txt"); + expect(stream instanceof ReadStream_).toBe(true); + expect(stream instanceof ReadStreamStar_).toBe(true); + expect(stream instanceof fs.ReadStream).toBe(true); + }); + + it("should work if re-exported as export * from ...", () => { + // @ts-ignore-next-line + const stream = new ReadStreamStar_("test.txt"); + expect(stream instanceof ReadStreamStar_).toBe(true); + expect(stream instanceof ReadStream_).toBe(true); + expect(stream instanceof fs.ReadStream).toBe(true); + }); + + it("should work if re-exported as export * from ..., called without new", () => { + // @ts-ignore-next-line + const stream = ReadStreamStar_("test.txt"); + expect(stream instanceof ReadStreamStar_).toBe(true); + expect(stream instanceof ReadStream_).toBe(true); + expect(stream instanceof fs.ReadStream).toBe(true); + }); + + it("should be able to read from a file, with re-exported ReadStream", done => { + const pathToDir = `${tmpdir()}/${Date.now()}`; + mkdirForce(pathToDir); + const path = join(pathToDir, `fs-readstream-re-exported-test.txt`); + + writeFileSync(path, "Test file written successfully", { + encoding: "utf8", + flag: "w+", + }); + + // @ts-ignore-next-line + const stream = new ReadStream_(path); + stream.setEncoding("utf8"); + stream.on("error", e => { + done(e instanceof Error ? e : new Error(e)); + }); + + let data = ""; + + stream.on("data", chunk => { + data += chunk; + }); + + stream.on("end", () => { + expect(data).toBe("Test file written successfully"); + done(); + }); + }); +}); + +describe("createWriteStream", () => { + it("simple write stream finishes", async () => { + const path = `/tmp/fs.test.js/${Date.now()}.createWriteStream.txt`; + const stream = createWriteStream(path); + stream.write("Test file written successfully"); + stream.end(); + + return await new Promise((resolve, reject) => { + stream.on("error", e => { + reject(e); + }); + + stream.on("finish", () => { + expect(readFileSync(path, "utf8")).toBe("Test file written successfully"); + resolve(true); + }); + }); + }); + + it("writing null throws ERR_STREAM_NULL_VALUES", async () => { + const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamNulls.txt`; + const stream = createWriteStream(path); + try { + stream.write(null); + expect(() => {}).toThrow(Error); + } catch (exception: any) { + expect(exception.code).toBe("ERR_STREAM_NULL_VALUES"); + } + }); + + it("writing null throws ERR_STREAM_NULL_VALUES (objectMode: true)", async () => { + const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamNulls.txt`; + const stream = createWriteStream(path, { + // @ts-ignore-next-line + objectMode: true, + }); + try { + stream.write(null); + expect(() => {}).toThrow(Error); + } catch (exception: any) { + expect(exception.code).toBe("ERR_STREAM_NULL_VALUES"); + } + }); + + it("writing false throws ERR_INVALID_ARG_TYPE", async () => { + const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamFalse.txt`; + const stream = createWriteStream(path); + try { + stream.write(false); + expect(() => {}).toThrow(Error); + } catch (exception: any) { + expect(exception.code).toBe("ERR_INVALID_ARG_TYPE"); + } + }); + + it("writing false throws ERR_INVALID_ARG_TYPE (objectMode: true)", async () => { + const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamFalse.txt`; + const stream = createWriteStream(path, { + // @ts-ignore-next-line + objectMode: true, + }); + try { + stream.write(false); + expect(() => {}).toThrow(Error); + } catch (exception: any) { + expect(exception.code).toBe("ERR_INVALID_ARG_TYPE"); + } + }); +}); + +describe("fs/promises", () => { + const { exists, mkdir, readFile, rmdir, stat, writeFile } = promises; + + it("should not segfault on exception", async () => { + try { + await stat("foo/bar"); + } catch (e) {} + }); + + it("readFile", async () => { + const data = await readFile(import.meta.dir + "/readFileSync.txt", "utf8"); + expect(data).toBe("File read successfully"); + }); + + it("writeFile", async () => { + const path = `/tmp/fs.test.js/${Date.now()}.writeFile.txt`; + await writeFile(path, "File written successfully"); + expect(readFileSync(path, "utf8")).toBe("File written successfully"); + }); + + it("readdir()", async () => { + const files = await promises.readdir(import.meta.dir); + expect(files.length).toBeGreaterThan(0); + }); + + it("readdir() no args doesnt segfault", async () => { + const fizz = [ + [], + [Symbol("ok")], + [Symbol("ok"), Symbol("ok")], + [Symbol("ok"), Symbol("ok"), Symbol("ok")], + [Infinity, -NaN, -Infinity], + "\0\0\0\0", + "\r\n", + ]; + for (const args of fizz) { + try { + // check it doens't segfault when called with invalid arguments + await promises.readdir(...(args as [any, ...any[]])); + } catch (e) { + // check that producing the error doesn't cause any crashes + Bun.inspect(e); + } + } + }); + + describe("rmdir", () => { + it("removes a file", async () => { + const path = `/tmp/${Date.now()}.rm.txt`; + await writeFile(path, "File written successfully", "utf8"); + expect(await exists(path)).toBe(true); + try { + await rmdir(path); + expect(() => {}).toThrow(); + } catch (err: any) { + expect(err.code).toBe("ENOTDIR"); + // expect(err.message).toBe("Operation not permitted"); + expect(await exists(path)).toBe(true); + } + }); + + it("removes a dir", async () => { + const path = `/tmp/${Date.now()}.rm.dir`; + try { + await mkdir(path); + } catch (e) {} + expect(await exists(path)).toBe(true); + await rmdir(path); + expect(await exists(path)).toBe(false); + }); + // TODO support `recursive: true` + // it("removes a dir recursively", async () => { + // const path = `/tmp/${Date.now()}.rm.dir/foo/bar`; + // try { + // await mkdir(path, { recursive: true }); + // } catch (e) {} + // expect(await exists(path)).toBe(true); + // await rmdir(join(path, "../../"), { recursive: true }); + // expect(await exists(path)).toBe(false); + // }); + }); +}); + +it("fs.constants", () => { + expect(constants).toBeDefined(); + expect(constants.F_OK).toBeDefined(); + expect(constants.R_OK).toBeDefined(); + expect(constants.W_OK).toBeDefined(); + expect(constants.X_OK).toBeDefined(); + expect(constants.O_RDONLY).toBeDefined(); + expect(constants.O_WRONLY).toBeDefined(); + expect(constants.O_RDWR).toBeDefined(); + expect(constants.O_CREAT).toBeDefined(); + expect(constants.O_EXCL).toBeDefined(); + expect(constants.O_NOCTTY).toBeDefined(); + expect(constants.O_TRUNC).toBeDefined(); + expect(constants.O_APPEND).toBeDefined(); + expect(constants.O_DIRECTORY).toBeDefined(); + expect(constants.O_NOATIME).toBeDefined(); + expect(constants.O_NOFOLLOW).toBeDefined(); + expect(constants.O_SYNC).toBeDefined(); + expect(constants.O_DSYNC).toBeDefined(); + expect(constants.O_SYMLINK).toBeDefined(); + expect(constants.O_DIRECT).toBeDefined(); + expect(constants.O_NONBLOCK).toBeDefined(); + expect(constants.S_IFMT).toBeDefined(); + expect(constants.S_IFREG).toBeDefined(); + expect(constants.S_IFDIR).toBeDefined(); + expect(constants.S_IFCHR).toBeDefined(); + expect(constants.S_IFBLK).toBeDefined(); + expect(constants.S_IFIFO).toBeDefined(); + expect(constants.S_IFLNK).toBeDefined(); + expect(constants.S_IFSOCK).toBeDefined(); + expect(constants.S_IRWXU).toBeDefined(); + expect(constants.S_IRUSR).toBeDefined(); + expect(constants.S_IWUSR).toBeDefined(); + expect(constants.S_IXUSR).toBeDefined(); + expect(constants.S_IRWXG).toBeDefined(); + expect(constants.S_IRGRP).toBeDefined(); + expect(constants.S_IWGRP).toBeDefined(); + expect(constants.S_IXGRP).toBeDefined(); + expect(constants.S_IRWXO).toBeDefined(); + expect(constants.S_IROTH).toBeDefined(); + expect(constants.S_IWOTH).toBeDefined(); +}); + +it("fs.Dirent", () => { + expect(Dirent).toBeDefined(); +}); + +it("fs.Stats", () => { + expect(Stats).toBeDefined(); +}); + +it("repro 1516: can use undefined/null to specify default flag", () => { + const path = "/tmp/repro_1516.txt"; + writeFileSync(path, "b", { flag: undefined }); + // @ts-ignore-next-line + expect(readFileSync(path, { encoding: "utf8", flag: null })).toBe("b"); + rmSync(path); +}); diff --git a/test/js/node/fs/readFileSync.txt b/test/js/node/fs/readFileSync.txt new file mode 100644 index 000000000..ddc94b988 --- /dev/null +++ b/test/js/node/fs/readFileSync.txt @@ -0,0 +1 @@ +File read successfully \ No newline at end of file diff --git a/test/js/node/fs/test.txt b/test/js/node/fs/test.txt new file mode 100644 index 000000000..e69de29bb diff --git a/test/js/node/fs/writeFileSync.txt b/test/js/node/fs/writeFileSync.txt new file mode 100644 index 000000000..a0fe4515f --- /dev/null +++ b/test/js/node/fs/writeFileSync.txt @@ -0,0 +1 @@ +File \ No newline at end of file diff --git a/test/js/node/harness.test.js b/test/js/node/harness.test.js new file mode 100644 index 000000000..de00e30ca --- /dev/null +++ b/test/js/node/harness.test.js @@ -0,0 +1,169 @@ +// import { describe, it, expect } from "bun:test"; +// import { +// throws, +// assert, +// strictEqual, +// createCallCheckCtx, +// createDoneDotAll, +// } from "./node-test-helpers"; + +// describe("NodeTestHelpers.throws()", () => { +// it("should pass when the function throws", () => { +// throws(() => { +// throw new Error("THROWN!"); +// }); +// }); + +// it("should fail when the function doesn't throw", () => { +// let err; +// try { +// throws(() => {}, Error); +// } catch (e) { +// err = e; +// } + +// expect(err instanceof Error).toBe(true); +// }); +// }); + +// describe("NodeTestHelpers.assert()", () => { +// it("should pass when the provided value is true", () => { +// assert(true); +// }); + +// it("should fail when the provided value is false", () => { +// let err; +// try { +// assert(false); +// } catch (e) { +// err = e; +// } +// expect(err instanceof Error).toBe(true); +// }); +// }); + +// describe("NodeTestHelpers.strictEqual()", () => { +// it("should pass when the provided values are deeply equal", () => { +// strictEqual(1, 1); +// strictEqual("hello", "hello"); +// const testing = { hello: "world" }; +// const testing2 = testing; +// testing2.hello = "bla"; +// strictEqual(testing, testing2); +// strictEqual(NaN, NaN); +// strictEqual(Infinity, Infinity); +// strictEqual(-Infinity, -Infinity); +// strictEqual(null, null); +// strictEqual(undefined, undefined); +// }); + +// it("should fail when the provided values are not deeply equal", () => { +// let err = null; +// try { +// strictEqual(1, 5); +// } catch (e) { +// err = e; +// } +// expect(err instanceof Error).toBe(true); +// err = null; +// try { +// strictEqual({ foo: "bar" }, { foo: "bar" }); +// } catch (e) { +// err = e; +// } +// expect(err instanceof Error).toBe(true); +// err = null; +// try { +// strictEqual("1", 1); +// } catch (e) { +// err = e; +// } +// expect(err instanceof Error).toBe(true); +// err = null; +// const obj1 = { foo: "bar" }; +// const obj2 = JSON.parse(JSON.stringify(obj1)); +// try { +// strictEqual(obj1, obj2); +// } catch (e) { +// err = e; +// } +// expect(err instanceof Error).toBe(true); +// }); +// }); + +// describe("NodeTestHelpers.createCallCheckCtx", () => { +// it("should pass when all mustCall marked callbacks have been called", (done) => { +// const { mustCall } = createCallCheckCtx(done); +// const fn1 = mustCall(() => {}); +// const fn2 = mustCall(() => {}); +// fn1(); +// fn2(); +// }); + +// it("should fail when all mustCall marked callbacks have NOT been called", (done) => { +// const mockDone = (result) => { +// expect(result instanceof Error).toBe(true); +// done(); +// }; +// const { mustCall } = createCallCheckCtx(mockDone, 600); +// const fn1 = mustCall(() => {}); +// mustCall(() => {}); +// fn1(); +// }); + +// it("should allow us to get the args of the wrapped callback from mustCall", (done) => { +// const { mustCall } = createCallCheckCtx(done); +// const fn1 = mustCall((arg1, arg2) => { +// expect(arg1).toBe("hello"); +// expect(arg2).toBe("world"); +// }); +// fn1("hello", "world"); +// }); +// }); + +// describe("NodeTestHelpers.createDoneDotAll()", () => { +// it("should pass when all dones have been called", (done) => { +// const createDone = createDoneDotAll(done); +// const done1 = createDone(600); +// const done2 = createDone(600); +// setTimeout(() => done1(), 300); +// setTimeout(() => done2(), 450); +// }); + +// it("should fail when all dones have NOT been called before timeout", (done) => { +// const mockDone = (result) => { +// expect(result instanceof Error).toBe(true); +// done(); +// }; +// const createDone = createDoneDotAll(mockDone); +// const done1 = createDone(400); +// createDone(400); +// setTimeout(() => done1(), 200); +// }); + +// it("should allow us to combine mustCall and multiple dones", (done) => { +// const createDone = createDoneDotAll(done); +// const { mustCall } = createCallCheckCtx(createDone(600)); +// const done1 = createDone(600); +// const done2 = createDone(600); +// const fn1 = mustCall(() => {}); +// const fn2 = mustCall(() => {}); +// setTimeout(() => done1(), 300); +// setTimeout(() => done2(), 450); +// setTimeout(() => fn1(), 200); +// setTimeout(() => fn2(), 200); +// }); + +// it("should fail if a done is called with an error", (done) => { +// const mockDone = (result) => { +// expect(result instanceof Error).toBe(true); +// done(); +// }; +// const createDone = createDoneDotAll(mockDone); + +// const done1 = createDone(600); +// const done2 = createDone(600); +// setTimeout(() => done1(), 300); +// setTimeout(() => done2(new Error("ERROR!")), 450); +// }); +// }); diff --git a/test/js/node/harness.ts b/test/js/node/harness.ts new file mode 100644 index 000000000..227009a64 --- /dev/null +++ b/test/js/node/harness.ts @@ -0,0 +1,201 @@ +import { expect as expect_ } from "bun:test"; +import { gcTick } from "harness"; +import assertNode from "node:assert"; + +type DoneCb = (err?: Error) => any; +function noop() {} + +const expect = actual => { + gcTick(); + const ret = expect_(actual); + gcTick(); + return ret; +}; + +// Assert +export const strictEqual = (...args: Parameters) => { + assertNode.strictEqual.apply(this, args); + expect(true).toBe(true); +}; + +export const notStrictEqual = (...args: Parameters) => { + assertNode.notStrictEqual.apply(this, args); + expect(true).toBe(true); +}; + +export const deepStrictEqual = (...args: Parameters) => { + assertNode.deepStrictEqual.apply(this, args); + expect(true).toBe(true); +}; + +export const throws = (...args: Parameters) => { + assertNode.throws.apply(this, args); + expect(true).toBe(true); +}; + +export const ok = (...args: Parameters) => { + assertNode.ok.apply(this, args); + expect(true).toBe(true); +}; + +export const ifError = (...args: Parameters) => { + assertNode.ifError.apply(this, args); + expect(true).toBe(true); +}; + +export const match = (...args: Parameters) => { + assertNode.match.apply(this, args); + expect(true).toBe(true); +}; + +export const assert = function (...args: any[]) { + // @ts-ignore + assertNode(...args); +}; + +Object.assign(assert, { + strictEqual, + deepStrictEqual, + notStrictEqual, + throws, + ok, + ifError, + match, +}); + +// End assert + +export const createCallCheckCtx = (done: DoneCb) => { + const createDone = createDoneDotAll(done); + + // const mustCallChecks = []; + + // failed.forEach(function (context) { + // console.log( + // "Mismatched %s function calls. Expected %s, actual %d.", + // context.name, + // context.messageSegment, + // context.actual + // ); + // console.log(context.stack.split("\n").slice(2).join("\n")); + // }); + + // TODO: Implement this to be exact only + function mustCall(fn?: (...args) => any, exact?: number) { + return mustCallAtLeast(fn, exact); + } + + function mustNotCall(reason: string = "function should not have been called") { + const localDone = createDone(); + setTimeout(() => localDone(), 200); + return () => { + done(new Error(reason)); + }; + } + + function mustSucceed(fn: () => any, exact?: number) { + return mustCall(function (err, ...args) { + ifError(err); + // @ts-ignore + if (typeof fn === "function") return fn.apply(this, args as []); + }, exact); + } + + function mustCallAtLeast(fn, minimum) { + return _mustCallInner(fn, minimum, "minimum"); + } + + function _mustCallInner(fn, criteria = 1, field) { + if (process._exiting) throw new Error("Cannot use common.mustCall*() in process exit handler"); + if (typeof fn === "number") { + criteria = fn; + fn = noop; + } else if (fn === undefined) { + fn = noop; + } + + if (typeof criteria !== "number") throw new TypeError(`Invalid ${field} value: ${criteria}`); + + let actual = 0; + let expected = criteria; + + // mustCallChecks.push(context); + const done = createDone(); + const _return = (...args) => { + try { + // @ts-ignore + const result = fn.apply(this, args); + actual++; + if (actual >= expected) { + done(); + } + return result; + } catch (err) { + if (err instanceof Error) done(err); + else if (err?.toString) done(new Error(err?.toString())); + else { + console.error("Unknown error", err); + done(new Error("Unknown error")); + } + } + }; + // Function instances have own properties that may be relevant. + // Let's replicate those properties to the returned function. + // Refs: https://tc39.es/ecma262/#sec-function-instances + Object.defineProperties(_return, { + name: { + value: fn.name, + writable: false, + enumerable: false, + configurable: true, + }, + length: { + value: fn.length, + writable: false, + enumerable: false, + configurable: true, + }, + }); + return _return; + } + return { + mustSucceed, + mustCall, + mustCallAtLeast, + mustNotCall, + }; +}; + +export function createDoneDotAll(done: DoneCb, globalTimeout?: number) { + let toComplete = 0; + let completed = 0; + const globalTimer = globalTimeout + ? setTimeout(() => { + console.log("Global Timeout"); + done(new Error("Timed out!")); + }, globalTimeout) + : undefined; + function createDoneCb(timeout?: number) { + toComplete += 1; + const timer = + timeout !== undefined + ? setTimeout(() => { + console.log("Timeout"); + done(new Error("Timed out!")); + }, timeout) + : timeout; + return (result?: Error) => { + if (timer) clearTimeout(timer); + if (globalTimer) clearTimeout(globalTimer); + if (result instanceof Error) { + done(result); + return; + } + completed += 1; + if (completed === toComplete) { + done(); + } + }; + } + return createDoneCb; +} diff --git a/test/js/node/http/node-http.fixme.ts b/test/js/node/http/node-http.fixme.ts new file mode 100644 index 000000000..d47002a6f --- /dev/null +++ b/test/js/node/http/node-http.fixme.ts @@ -0,0 +1,604 @@ +import { describe, expect, it, beforeAll, afterAll } from "bun:test"; +import { createServer, request, get, Agent, globalAgent, Server } from "node:http"; +import { createDoneDotAll } from "node-harness"; + +function listen(server: any): Promise { + return new Promise((resolve, reject) => { + server.listen({ port: 0 }, (err, hostname, port) => { + if (err) { + reject(err); + } else { + resolve(new URL(`http://${hostname}:${port}`)); + } + }); + setTimeout(() => reject("Timed out"), 5000); + }); +} + +describe("node:http", () => { + describe("createServer", async () => { + it("hello world", async () => { + const server = createServer((req, res) => { + expect(req.url).toBe("/hello?world"); + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("Hello World"); + }); + const url = await listen(server); + const res = await fetch(new URL("/hello?world", url)); + expect(await res.text()).toBe("Hello World"); + server.close(); + }); + + it("request & response body streaming (large)", async () => { + const bodyBlob = new Blob(["hello world", "hello world".repeat(9000)]); + + const input = await bodyBlob.text(); + + const server = createServer((req, res) => { + res.writeHead(200, { "Content-Type": "text/plain" }); + req.on("data", chunk => { + res.write(chunk); + }); + + req.on("end", () => { + res.end(); + }); + }); + const url = await listen(server); + const res = await fetch(url, { + method: "POST", + body: bodyBlob, + }); + + const out = await res.text(); + expect(out).toBe(input); + server.close(); + }); + + it("request & response body streaming (small)", async () => { + const bodyBlob = new Blob(["hello world", "hello world".repeat(4)]); + + const input = await bodyBlob.text(); + + const server = createServer((req, res) => { + res.writeHead(200, { "Content-Type": "text/plain" }); + req.on("data", chunk => { + res.write(chunk); + }); + + req.on("end", () => { + res.end(); + }); + }); + const url = await listen(server); + const res = await fetch(url, { + method: "POST", + body: bodyBlob, + }); + + const out = await res.text(); + expect(out).toBe(input); + server.close(); + }); + + it("listen should return server", async () => { + const server = createServer(); + const listenResponse = server.listen(0); + expect(listenResponse instanceof Server).toBe(true); + expect(listenResponse).toBe(server); + listenResponse.close(); + }); + }); + + describe("request", () => { + let server; + let serverPort; + let timer: Timer | null = null; + beforeAll(() => { + server = createServer((req, res) => { + const reqUrl = new URL(req.url!, `http://${req.headers.host}`); + if (reqUrl.pathname) { + if (reqUrl.pathname === "/redirect") { + // Temporary redirect + res.writeHead(301, { + Location: `http://localhost:${serverPort}/redirected`, + }); + res.end("Got redirect!\n"); + return; + } + if (reqUrl.pathname === "/redirected") { + res.writeHead(404, { "Content-Type": "text/plain" }); + res.end("Not Found"); + return; + } + if (reqUrl.pathname === "/lowerCaseHeaders") { + res.writeHead(200, { "content-type": "text/plain", "X-Custom-Header": "custom_value" }); + res.end("Hello World"); + return; + } + if (reqUrl.pathname.includes("timeout")) { + if (timer) clearTimeout(timer); + timer = setTimeout(() => { + res.end("Hello World"); + timer = null; + }, 3000); + return; + } + if (reqUrl.pathname === "/pathTest") { + res.end("Path correct!\n"); + return; + } + } + + res.writeHead(200, { "Content-Type": "text/plain" }); + + if (req.headers["x-test"]) { + res.write(`x-test: ${req.headers["x-test"]}\n`); + } + + // Check for body + if (req.method === "POST") { + req.on("data", chunk => { + res.write(chunk); + }); + + req.on("end", () => { + res.write("POST\n"); + res.end("Hello World"); + }); + } else { + if (req.headers["X-Test"] !== undefined) { + res.write(`X-Test: test\n`); + } + res.write("Maybe GET maybe not\n"); + res.end("Hello World"); + } + }); + server.listen({ port: 0 }, (_, __, port) => { + serverPort = port; + }); + }); + afterAll(() => { + server.close(); + if (timer) clearTimeout(timer); + }); + + it("check for expected fields", done => { + const req = request({ host: "localhost", port: serverPort, method: "GET" }, res => { + res.on("end", () => { + done(); + }); + res.on("error", err => done(err)); + }); + expect(req.path).toEqual("/"); + expect(req.method).toEqual("GET"); + expect(req.host).toEqual("localhost"); + expect(req.protocol).toEqual("http:"); + req.end(); + }); + + it("should make a standard GET request when passed string as first arg", done => { + const req = request(`http://localhost:${serverPort}`, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("Maybe GET maybe not\nHello World"); + done(); + }); + res.on("error", err => done(err)); + }); + req.end(); + }); + + it("should make a https:// GET request when passed string as first arg", done => { + const req = request("https://example.com", res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toContain("This domain is for use in illustrative examples in documents"); + done(); + }); + res.on("error", err => done(err)); + }); + req.end(); + }); + + it("should make a POST request when provided POST method, even without a body", done => { + const req = request({ host: "localhost", port: serverPort, method: "POST" }, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("POST\nHello World"); + done(); + }); + res.on("error", err => done(err)); + }); + req.end(); + }); + + it("should correctly handle a POST request with a body", done => { + const req = request({ host: "localhost", port: serverPort, method: "POST" }, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("Posting\nPOST\nHello World"); + done(); + }); + res.on("error", err => done(err)); + }); + req.write("Posting\n"); + req.end(); + }); + + it("should noop request.setSocketKeepAlive without error", () => { + const req = request(`http://localhost:${serverPort}`); + req.setSocketKeepAlive(true, 1000); + req.end(); + expect(true).toBe(true); + }); + + it("should allow us to set timeout with request.setTimeout or `timeout` in options", done => { + const createDone = createDoneDotAll(done); + const req1Done = createDone(); + const req2Done = createDone(); + + // const start = Date.now(); + const req1 = request( + { + host: "localhost", + port: serverPort, + path: "/timeout", + timeout: 500, + }, + res => { + req1Done(new Error("Should not have received response")); + }, + ); + req1.on("timeout", () => req1Done()); + + const req2 = request( + { + host: "localhost", + port: serverPort, + path: "/timeout", + }, + res => { + req2Done(new Error("Should not have received response")); + }, + ); + + req2.setTimeout(500, () => { + req2Done(); + }); + req1.end(); + req2.end(); + }); + + it("should correctly set path when path provided", done => { + const createDone = createDoneDotAll(done); + const req1Done = createDone(); + const req2Done = createDone(); + + const req1 = request(`http://localhost:${serverPort}/pathTest`, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("Path correct!\n"); + req1Done(); + }); + res.on("error", err => req1Done(err)); + }); + + const req2 = request(`http://localhost:${serverPort}`, { path: "/pathTest" }, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("Path correct!\n"); + req2Done(); + }); + res.on("error", err => req2Done(err)); + }); + + req1.end(); + req2.end(); + + expect(req1.path).toBe("/pathTest"); + expect(req2.path).toBe("/pathTest"); + }); + + it("should emit response when response received", done => { + const req = request(`http://localhost:${serverPort}`); + + req.on("response", res => { + expect(res.statusCode).toBe(200); + done(); + }); + req.end(); + }); + + // NOTE: Node http.request doesn't follow redirects by default + it("should handle redirects properly", done => { + const req = request(`http://localhost:${serverPort}/redirect`, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("Got redirect!\n"); + done(); + }); + res.on("error", err => done(err)); + }); + req.end(); + }); + + it("should correctly attach headers to request", done => { + const req = request({ host: "localhost", port: serverPort, headers: { "X-Test": "test" } }, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("x-test: test\nMaybe GET maybe not\nHello World"); + done(); + }); + res.on("error", err => done(err)); + }); + req.end(); + expect(req.getHeader("X-Test")).toBe("test"); + }); + + it("should correct casing of method param", done => { + const req = request({ host: "localhost", port: serverPort, method: "get" }, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("Maybe GET maybe not\nHello World"); + done(); + }); + res.on("error", err => done(err)); + }); + req.end(); + }); + + it("should allow for port as a string", done => { + const req = request({ host: "localhost", port: `${serverPort}`, method: "GET" }, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("Maybe GET maybe not\nHello World"); + done(); + }); + res.on("error", err => done(err)); + }); + req.end(); + }); + + it("should allow us to pass a URL object", done => { + const req = request(new URL(`http://localhost:${serverPort}`), { method: "POST" }, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("Hello WorldPOST\nHello World"); + done(); + }); + res.on("error", err => done(err)); + }); + req.write("Hello World"); + req.end(); + }); + + it("should ignore body when method is GET/HEAD/OPTIONS", done => { + const createDone = createDoneDotAll(done); + const methods = ["GET", "HEAD", "OPTIONS"]; + const dones = {}; + for (const method of methods) { + dones[method] = createDone(); + } + for (const method of methods) { + const req = request(`http://localhost:${serverPort}`, { method }, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe(method === "GET" ? "Maybe GET maybe not\nHello World" : ""); + dones[method](); + }); + res.on("error", err => dones[method](err)); + }); + req.write("BODY"); + req.end(); + } + }); + + it("should return response with lowercase headers", done => { + const req = request(`http://localhost:${serverPort}/lowerCaseHeaders`, res => { + console.log(res.headers); + expect(res.headers["content-type"]).toBe("text/plain"); + expect(res.headers["x-custom-header"]).toBe("custom_value"); + done(); + }); + req.end(); + }); + }); + + describe("signal", () => { + it("should abort and close the server", done => { + const server = createServer((req, res) => { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("Hello World"); + }); + + //force timeout to not hang tests + const interval = setTimeout(() => { + expect(false).toBe(true); + server.close(); + done(); + }, 100); + + const signal = AbortSignal.timeout(30); + signal.addEventListener("abort", () => { + clearTimeout(interval); + expect(true).toBe(true); + done(); + }); + + server.listen({ signal, port: 0 }); + }); + }); + + describe("get", () => { + let server; + let url; + beforeAll(async () => { + server = createServer((req, res) => { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("Hello World"); + }); + url = await listen(server); + }); + afterAll(() => { + server.close(); + }); + it("should make a standard GET request, like request", done => { + get(url, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(data).toBe("Hello World"); + done(); + }); + res.on("error", err => done(err)); + }); + }); + }); + + describe("Agent", () => { + let server; + let dummyReq; + let dummyAgent; + beforeAll(() => { + dummyAgent = new Agent(); + server = createServer((req, res) => { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("Hello World"); + }); + server.listen({ port: 0 }, (_, host, port) => { + // Setup request after server is listening + dummyReq = request( + { + host, + port, + agent: dummyAgent, + }, + res => {}, + ); + dummyReq.on("error", () => {}); + }); + }); + + afterAll(() => { + dummyReq.end(); + server.close(); + }); + + it("should be a class", () => { + expect(Agent instanceof Function).toBe(true); + }); + + it("should have a default maxSockets of Infinity", () => { + expect(dummyAgent.maxSockets).toBe(Infinity); + }); + + it("should have a keepAlive value", () => { + expect(dummyAgent.keepAlive).toBe(false); + }); + + it("should noop keepSocketAlive", () => { + const agent = new Agent({ keepAlive: true }); + // @ts-ignore + expect(agent.keepAlive).toBe(true); + + agent.keepSocketAlive(dummyReq.socket); + }); + + it("should provide globalAgent", () => { + expect(globalAgent instanceof Agent).toBe(true); + }); + }); + + describe("ClientRequest.signal", () => { + let server; + let server_port; + let server_host; + beforeAll(() => { + server = createServer((req, res) => { + Bun.sleep(10).then(() => { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("Hello World"); + }); + }); + server.listen({ port: 0 }, (_err, host, port) => { + server_port = port; + server_host = host; + }); + }); + afterAll(() => { + server.close(); + }); + it("should attempt to make a standard GET request and abort", done => { + get(`http://${server_host}:${server_port}`, { signal: AbortSignal.timeout(5) }, res => { + let data = ""; + res.setEncoding("utf8"); + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + expect(true).toBeFalsy(); + done(); + }); + res.on("error", _ => { + expect(true).toBeFalsy(); + done(); + }); + }).on("error", err => { + expect(err?.name).toBe("AbortError"); + done(); + }); + }); + }); +}); diff --git a/test/js/node/module/node-module-module.test.js b/test/js/node/module/node-module-module.test.js new file mode 100644 index 000000000..549b5e085 --- /dev/null +++ b/test/js/node/module/node-module-module.test.js @@ -0,0 +1,5 @@ +import { expect, test } from "bun:test"; + +test("module.globalPaths exists", () => { + expect(Array.isArray(require("module").globalPaths)).toBe(true); +}); diff --git a/test/js/node/net/node-net.test.ts b/test/js/node/net/node-net.test.ts new file mode 100644 index 000000000..e6c17d931 --- /dev/null +++ b/test/js/node/net/node-net.test.ts @@ -0,0 +1,268 @@ +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "bun:test"; +import { connect, isIP, isIPv4, isIPv6, Socket } from "net"; + +it("should support net.isIP()", () => { + expect(isIP("::1")).toBe(6); + expect(isIP("foobar")).toBe(0); + expect(isIP("127.0.0.1")).toBe(4); + expect(isIP("127.0.0.1/24")).toBe(0); + expect(isIP("127.000.000.001")).toBe(0); +}); + +it("should support net.isIPv4()", () => { + expect(isIPv4("::1")).toBe(false); + expect(isIPv4("foobar")).toBe(false); + expect(isIPv4("127.0.0.1")).toBe(true); + expect(isIPv4("127.0.0.1/24")).toBe(false); + expect(isIPv4("127.000.000.001")).toBe(false); +}); + +it("should support net.isIPv6()", () => { + expect(isIPv6("::1")).toBe(true); + expect(isIPv6("foobar")).toBe(false); + expect(isIPv6("127.0.0.1")).toBe(false); + expect(isIPv6("127.0.0.1/24")).toBe(false); + expect(isIPv6("127.000.000.001")).toBe(false); +}); + +describe("net.Socket read", () => { + var port = 12345; + for (let [message, label] of [ + // ["Hello World!".repeat(1024), "long message"], + ["Hello!", "short message"], + ]) { + describe(label, () => { + function runWithServer(cb) { + return done => { + function drain(socket) { + const message = socket.data.message; + const written = socket.write(message); + if (written < message.length) { + socket.data.message = message.slice(written); + } else { + socket.end(); + } + } + + var server = Bun.listen({ + hostname: "localhost", + port: port++, + socket: { + open(socket) { + socket.data.message = message; + drain(socket); + }, + drain, + error(socket, err) { + done(err); + }, + }, + data: { + message: "", + }, + }); + + function onDone(err) { + server.stop(); + done(err); + } + + try { + cb(server, drain, onDone); + } catch (e) { + onDone(e); + } + }; + } + + it( + "should work with .connect(port)", + runWithServer((server, drain, done) => { + var data = ""; + const socket = new Socket() + .connect(server.port) + .on("connect", () => { + expect(socket).toBeDefined(); + expect(socket.connecting).toBe(false); + }) + .setEncoding("utf8") + .on("data", chunk => { + data += chunk; + }) + .on("end", () => { + try { + expect(data).toBe(message); + done(); + } catch (e) { + server.stop(); + done(e); + } + }) + .on("error", done); + }), + ); + + it( + "should work with .connect(port, listener)", + runWithServer((server, drain, done) => { + var data = ""; + const socket = new Socket() + .connect(server.port, () => { + expect(socket).toBeDefined(); + expect(socket.connecting).toBe(false); + }) + .setEncoding("utf8") + .on("data", chunk => { + data += chunk; + }) + .on("end", () => { + try { + expect(data).toBe(message); + done(); + } catch (e) { + server.stop(); + done(e); + } + }) + .on("error", done); + }), + ); + + it( + "should work with .connect(port, host, listener)", + runWithServer((server, drain, done) => { + var data = ""; + const socket = new Socket() + .connect(server.port, "localhost", () => { + expect(socket).toBeDefined(); + expect(socket.connecting).toBe(false); + }) + .setEncoding("utf8") + .on("data", chunk => { + data += chunk; + }) + .on("end", () => { + try { + expect(data).toBe(message); + done(); + } catch (e) { + done(e); + } + }) + .on("error", done); + }), + ); + }); + } +}); + +describe("net.Socket write", () => { + const message = "Hello World!".repeat(1024); + let port = 53213; + + function runWithServer(cb) { + return done => { + let server; + + function close(socket) { + expect(Buffer.concat(socket.data).toString("utf8")).toBe(message); + done(); + } + + var leaky; + server = Bun.listen({ + hostname: "0.0.0.0", + port: port++, + socket: { + close, + data(socket, buffer) { + leaky = socket; + if (!Buffer.isBuffer(buffer)) { + done(new Error("buffer is not a Buffer")); + } + + socket.data.push(buffer); + }, + end: close, + error(socket, err) { + leaky = socket; + done(err); + }, + open(socket) { + leaky = socket; + socket.data = []; + }, + }, + data: [] as Buffer[], + }); + + function onDone(err) { + server.stop(); + done(err); + } + + try { + cb(server, onDone); + } catch (e) { + onDone(e); + } + }; + } + + it( + "should work with .end(data)", + runWithServer((server, done) => { + const socket = new Socket() + .connect(server.port) + .on("ready", () => { + expect(socket).toBeDefined(); + expect(socket.connecting).toBe(false); + }) + .on("error", done) + .end(message); + }), + ); + + it( + "should work with .write(data).end()", + runWithServer((server, done) => { + const socket = new Socket() + .connect(server.port, () => { + expect(socket).toBeDefined(); + expect(socket.connecting).toBe(false); + }) + .on("error", done); + socket.write(message); + socket.end(); + }), + ); + + it( + "should work with multiple .write()s", + runWithServer((server, done) => { + const socket = new Socket() + .connect(server.port, server.hostname, () => { + expect(socket).toBeDefined(); + expect(socket.connecting).toBe(false); + }) + .on("error", done); + const size = 10; + for (let i = 0; i < message.length; i += size) { + socket.write(message.slice(i, i + size)); + } + socket.end(); + }), + ); +}); + +it("should handle connection error", done => { + var data = {}; + connect(55555, () => { + done(new Error("Should not have connected")); + }).on("error", error => { + expect(error).toBeDefined(); + expect(error.name).toBe("SystemError"); + expect(error.message).toBe("Failed to connect"); + done(); + }); +}); diff --git a/test/js/node/os/os.test.js b/test/js/node/os/os.test.js new file mode 100644 index 000000000..ea685cdb7 --- /dev/null +++ b/test/js/node/os/os.test.js @@ -0,0 +1,150 @@ +import { it, expect } from "bun:test"; +import * as os from "node:os"; + +it("arch", () => { + expect(["x64", "x86", "arm64"].some(arch => os.arch() === arch)).toBe(true); +}); + +it("endianness", () => { + expect(/[BL]E/.test(os.endianness())).toBe(true); +}); + +it("freemem", () => { + expect(os.freemem() > 0).toBe(true); +}); + +it("totalmem", () => { + expect(os.totalmem() > 0).toBe(true); +}); + +it("getPriority", () => { + expect(os.getPriority()).toBe(0); + expect(os.getPriority(0)).toBe(0); +}); + +it("setPriority", () => { + expect(os.setPriority(0, 2)).toBe(undefined); + expect(os.getPriority()).toBe(2); + expect(os.setPriority(5)).toBe(undefined); + expect(os.getPriority()).toBe(5); +}); + +it("loadavg", () => { + expect(os.loadavg().length === 3).toBe(true); +}); + +it("homedir", () => { + expect(os.homedir() !== "unknown").toBe(true); +}); + +it("tmpdir", () => { + if (process.platform === "win32") { + expect(os.tmpdir()).toBe(process.env.TEMP || process.env.TMP); + expect(os.tmpdir()).toBe(`${process.env.SystemRoot || process.env.windir}\\temp`); + } else { + let dir = process.env.TMPDIR || process.env.TMP || process.env.TEMP || "/tmp"; + if (dir.length > 1 && dir.endsWith("/")) { + dir = dir.substring(0, dir.length - 1); + } + expect(os.tmpdir()).toBe(dir); + } +}); + +it("hostname", () => { + expect(os.hostname() !== "unknown").toBe(true); +}); + +it("platform", () => { + expect(["win32", "darwin", "linux", "wasm"].some(platform => os.platform() === platform)).toBe(true); +}); + +it("release", () => { + expect(os.release().length > 1).toBe(true); +}); + +it("type", () => { + expect(["Windows_NT", "Darwin", "Linux"].some(type => os.type() === type)).toBe(true); +}); + +it("uptime", () => { + expect(os.uptime() > 0).toBe(true); +}); + +it("version", () => { + expect(typeof os.version() === "string").toBe(true); +}); + +it("userInfo", () => { + const info = os.userInfo(); + + if (process.platform !== "win32") { + expect(info.username).toBe(process.env.USER); + expect(info.shell).toBe(process.env.SHELL); + expect(info.uid >= 0).toBe(true); + expect(info.gid >= 0).toBe(true); + } else { + expect(info.username).toBe(process.env.USERNAME); + expect(info.shell).toBe(null); + expect(info.uid).toBe(-1); + expect(info.gid).toBe(-1); + } +}); + +it("cpus", () => { + const cpus = os.cpus(); + + for (const cpu of cpus) { + expect(typeof cpu.model === "string").toBe(true); + expect(typeof cpu.speed === "number").toBe(true); + expect(typeof cpu.times.idle === "number").toBe(true); + expect(typeof cpu.times.irq === "number").toBe(true); + expect(typeof cpu.times.nice === "number").toBe(true); + expect(typeof cpu.times.sys === "number").toBe(true); + expect(typeof cpu.times.user === "number").toBe(true); + } +}); + +it("networkInterfaces", () => { + const networkInterfaces = os.networkInterfaces(); + + for (const networkInterface of Object.values(networkInterfaces)) { + for (const nI of networkInterface) { + expect(typeof nI.address === "string").toBe(true); + expect(typeof nI.netmask === "string").toBe(true); + expect(typeof nI.family === "string").toBe(true); + expect(typeof nI.mac === "string").toBe(true); + expect(typeof nI.internal === "boolean").toBe(true); + if (nI.cidr) + // may be null + expect(typeof nI.cidr).toBe("string"); + } + } +}); + +it("machine", () => { + const possibleValues = [ + "arm", + "arm64", + "aarch64", + "mips", + "mips64", + "ppc64", + "ppc64le", + "s390", + "s390x", + "i386", + "i686", + "x86_64", + ]; + expect(possibleValues.includes(os.machine())).toBe(true); +}); + +it("EOL", () => { + if (process.platform === "win32") expect(os.EOL).toBe("\\r\\n"); + else expect(os.EOL).toBe("\n"); +}); + +it("devNull", () => { + if (process.platform === "win32") expect(os.devNull).toBe("\\\\.\\nul"); + else expect(os.devNull).toBe("/dev/null"); +}); diff --git a/test/js/node/path/path.test.js b/test/js/node/path/path.test.js new file mode 100644 index 000000000..ad5688ea7 --- /dev/null +++ b/test/js/node/path/path.test.js @@ -0,0 +1,426 @@ +const { file } = import.meta; + +import { describe, it, expect } from "bun:test"; +import * as path from "node:path"; +import assert from "assert"; + +const strictEqual = (...args) => { + assert.strictEqual(...args); + expect(true).toBe(true); +}; + +it("path.basename", () => { + strictEqual(path.basename(file), "path.test.js"); + strictEqual(path.basename(file, ".js"), "path.test"); + strictEqual(path.basename(".js", ".js"), ""); + strictEqual(path.basename(""), ""); + strictEqual(path.basename("/dir/basename.ext"), "basename.ext"); + strictEqual(path.basename("/basename.ext"), "basename.ext"); + strictEqual(path.basename("basename.ext"), "basename.ext"); + strictEqual(path.basename("basename.ext/"), "basename.ext"); + strictEqual(path.basename("basename.ext//"), "basename.ext"); + strictEqual(path.basename("aaa/bbb", "/bbb"), "bbb"); + strictEqual(path.basename("aaa/bbb", "a/bbb"), "bbb"); + strictEqual(path.basename("aaa/bbb", "bbb"), "bbb"); + strictEqual(path.basename("aaa/bbb//", "bbb"), "bbb"); + strictEqual(path.basename("aaa/bbb", "bb"), "b"); + strictEqual(path.basename("aaa/bbb", "b"), "bb"); + strictEqual(path.basename("/aaa/bbb", "/bbb"), "bbb"); + strictEqual(path.basename("/aaa/bbb", "a/bbb"), "bbb"); + strictEqual(path.basename("/aaa/bbb", "bbb"), "bbb"); + strictEqual(path.basename("/aaa/bbb//", "bbb"), "bbb"); + strictEqual(path.basename("/aaa/bbb", "bb"), "b"); + strictEqual(path.basename("/aaa/bbb", "b"), "bb"); + strictEqual(path.basename("/aaa/bbb"), "bbb"); + strictEqual(path.basename("/aaa/"), "aaa"); + strictEqual(path.basename("/aaa/b"), "b"); + strictEqual(path.basename("/a/b"), "b"); + strictEqual(path.basename("//a"), "a"); + strictEqual(path.basename("a", "a"), ""); + + // // On Windows a backslash acts as a path separator. + strictEqual(path.win32.basename("\\dir\\basename.ext"), "basename.ext"); + strictEqual(path.win32.basename("\\basename.ext"), "basename.ext"); + strictEqual(path.win32.basename("basename.ext"), "basename.ext"); + strictEqual(path.win32.basename("basename.ext\\"), "basename.ext"); + strictEqual(path.win32.basename("basename.ext\\\\"), "basename.ext"); + strictEqual(path.win32.basename("foo"), "foo"); + strictEqual(path.win32.basename("aaa\\bbb", "\\bbb"), "bbb"); + strictEqual(path.win32.basename("aaa\\bbb", "a\\bbb"), "bbb"); + strictEqual(path.win32.basename("aaa\\bbb", "bbb"), "bbb"); + strictEqual(path.win32.basename("aaa\\bbb\\\\\\\\", "bbb"), "bbb"); + strictEqual(path.win32.basename("aaa\\bbb", "bb"), "b"); + strictEqual(path.win32.basename("aaa\\bbb", "b"), "bb"); + strictEqual(path.win32.basename("C:"), ""); + strictEqual(path.win32.basename("C:."), "."); + strictEqual(path.win32.basename("C:\\"), ""); + strictEqual(path.win32.basename("C:\\dir\\base.ext"), "base.ext"); + strictEqual(path.win32.basename("C:\\basename.ext"), "basename.ext"); + strictEqual(path.win32.basename("C:basename.ext"), "basename.ext"); + strictEqual(path.win32.basename("C:basename.ext\\"), "basename.ext"); + strictEqual(path.win32.basename("C:basename.ext\\\\"), "basename.ext"); + strictEqual(path.win32.basename("C:foo"), "foo"); + strictEqual(path.win32.basename("file:stream"), "file:stream"); + strictEqual(path.win32.basename("a", "a"), ""); + + // On unix a backslash is just treated as any other character. + strictEqual(path.posix.basename("\\dir\\basename.ext"), "\\dir\\basename.ext"); + strictEqual(path.posix.basename("\\basename.ext"), "\\basename.ext"); + strictEqual(path.posix.basename("basename.ext"), "basename.ext"); + strictEqual(path.posix.basename("basename.ext\\"), "basename.ext\\"); + strictEqual(path.posix.basename("basename.ext\\\\"), "basename.ext\\\\"); + strictEqual(path.posix.basename("foo"), "foo"); + + // POSIX filenames may include control characters + // c.f. http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html + const controlCharFilename = `Icon${String.fromCharCode(13)}`; + strictEqual(path.posix.basename(`/a/b/${controlCharFilename}`), controlCharFilename); +}); + +it("path.join", () => { + const failures = []; + const backslashRE = /\\/g; + + const joinTests = [ + [ + [path.posix.join], + // Arguments result + [ + [[".", "x/b", "..", "/b/c.js"], "x/b/c.js"], + // [[], '.'], + [["/.", "x/b", "..", "/b/c.js"], "/x/b/c.js"], + [["/foo", "../../../bar"], "/bar"], + [["foo", "../../../bar"], "../../bar"], + [["foo/", "../../../bar"], "../../bar"], + [["foo/x", "../../../bar"], "../bar"], + [["foo/x", "./bar"], "foo/x/bar"], + [["foo/x/", "./bar"], "foo/x/bar"], + [["foo/x/", ".", "bar"], "foo/x/bar"], + [["./"], "./"], + [[".", "./"], "./"], + [[".", ".", "."], "."], + [[".", "./", "."], "."], + [[".", "/./", "."], "."], + [[".", "/////./", "."], "."], + [["."], "."], + [["", "."], "."], + [["", "foo"], "foo"], + [["foo", "/bar"], "foo/bar"], + [["", "/foo"], "/foo"], + [["", "", "/foo"], "/foo"], + [["", "", "foo"], "foo"], + [["foo", ""], "foo"], + [["foo/", ""], "foo/"], + [["foo", "", "/bar"], "foo/bar"], + [["./", "..", "/foo"], "../foo"], + [["./", "..", "..", "/foo"], "../../foo"], + [[".", "..", "..", "/foo"], "../../foo"], + [["", "..", "..", "/foo"], "../../foo"], + [["/"], "/"], + [["/", "."], "/"], + [["/", ".."], "/"], + [["/", "..", ".."], "/"], + [[""], "."], + [["", ""], "."], + [[" /foo"], " /foo"], + [[" ", "foo"], " /foo"], + [[" ", "."], " "], + [[" ", "/"], " /"], + [[" ", ""], " "], + [["/", "foo"], "/foo"], + [["/", "/foo"], "/foo"], + [["/", "//foo"], "/foo"], + [["/", "", "/foo"], "/foo"], + [["", "/", "foo"], "/foo"], + [["", "/", "/foo"], "/foo"], + ], + ], + ]; + + // // Windows-specific join tests + // joinTests.push([ + // path.win32.join, + // joinTests[0][1].slice(0).concat([ + // // Arguments result + // // UNC path expected + // [["//foo/bar"], "\\\\foo\\bar\\"], + // [["\\/foo/bar"], "\\\\foo\\bar\\"], + // [["\\\\foo/bar"], "\\\\foo\\bar\\"], + // // UNC path expected - server and share separate + // [["//foo", "bar"], "\\\\foo\\bar\\"], + // [["//foo/", "bar"], "\\\\foo\\bar\\"], + // [["//foo", "/bar"], "\\\\foo\\bar\\"], + // // UNC path expected - questionable + // [["//foo", "", "bar"], "\\\\foo\\bar\\"], + // [["//foo/", "", "bar"], "\\\\foo\\bar\\"], + // [["//foo/", "", "/bar"], "\\\\foo\\bar\\"], + // // UNC path expected - even more questionable + // [["", "//foo", "bar"], "\\\\foo\\bar\\"], + // [["", "//foo/", "bar"], "\\\\foo\\bar\\"], + // [["", "//foo/", "/bar"], "\\\\foo\\bar\\"], + // // No UNC path expected (no double slash in first component) + // [["\\", "foo/bar"], "\\foo\\bar"], + // [["\\", "/foo/bar"], "\\foo\\bar"], + // [["", "/", "/foo/bar"], "\\foo\\bar"], + // // No UNC path expected (no non-slashes in first component - + // // questionable) + // [["//", "foo/bar"], "\\foo\\bar"], + // [["//", "/foo/bar"], "\\foo\\bar"], + // [["\\\\", "/", "/foo/bar"], "\\foo\\bar"], + // [["//"], "\\"], + // // No UNC path expected (share name missing - questionable). + // [["//foo"], "\\foo"], + // [["//foo/"], "\\foo\\"], + // [["//foo", "/"], "\\foo\\"], + // [["//foo", "", "/"], "\\foo\\"], + // // No UNC path expected (too many leading slashes - questionable) + // [["///foo/bar"], "\\foo\\bar"], + // [["////foo", "bar"], "\\foo\\bar"], + // [["\\\\\\/foo/bar"], "\\foo\\bar"], + // // Drive-relative vs drive-absolute paths. This merely describes the + // // status quo, rather than being obviously right + // [["c:"], "c:."], + // [["c:."], "c:."], + // [["c:", ""], "c:."], + // [["", "c:"], "c:."], + // [["c:.", "/"], "c:.\\"], + // [["c:.", "file"], "c:file"], + // [["c:", "/"], "c:\\"], + // [["c:", "file"], "c:\\file"], + // ]), + // ]); + joinTests.forEach(test => { + if (!Array.isArray(test[0])) test[0] = [test[0]]; + test[0].forEach(join => { + test[1].forEach(test => { + const actual = join.apply(null, test[0]); + const expected = test[1]; + // For non-Windows specific tests with the Windows join(), we need to try + // replacing the slashes since the non-Windows specific tests' `expected` + // use forward slashes + let actualAlt; + let os; + if (join === path.win32.join) { + actualAlt = actual.replace(backslashRE, "/"); + os = "win32"; + } else { + os = "posix"; + } + if (actual !== expected && actualAlt !== expected) { + const delimiter = test[0].map(JSON.stringify).join(","); + const message = `path.${os}.join(${delimiter})\n expect=${JSON.stringify( + expected, + )}\n actual=${JSON.stringify(actual)}`; + failures.push(`\n${message}`); + } + }); + }); + }); + strictEqual(failures.length, 0, failures.join("")); +}); + +it("path.relative", () => { + const failures = []; + + const relativeTests = [ + // [ + // path.win32.relative, + // // Arguments result + // [ + // ["c:/blah\\blah", "d:/games", "d:\\games"], + // ["c:/aaaa/bbbb", "c:/aaaa", ".."], + // ["c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc"], + // ["c:/aaaa/bbbb", "c:/aaaa/bbbb", ""], + // ["c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc"], + // ["c:/aaaa/", "c:/aaaa/cccc", "cccc"], + // ["c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb"], + // ["c:/aaaa/bbbb", "d:\\", "d:\\"], + // ["c:/AaAa/bbbb", "c:/aaaa/bbbb", ""], + // ["c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc"], + // ["C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\.."], + // [ + // "C:\\foo\\test", + // "C:\\foo\\test\\bar\\package.json", + // "bar\\package.json", + // ], + // ["C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz"], + // ["C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux"], + // ["\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz"], + // ["\\\\foo\\bar\\baz", "\\\\foo\\bar", ".."], + // ["\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz"], + // ["\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux"], + // ["C:\\baz-quux", "C:\\baz", "..\\baz"], + // ["C:\\baz", "C:\\baz-quux", "..\\baz-quux"], + // ["\\\\foo\\baz-quux", "\\\\foo\\baz", "..\\baz"], + // ["\\\\foo\\baz", "\\\\foo\\baz-quux", "..\\baz-quux"], + // ["C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz"], + // ["\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz"], + // ], + // ], + [ + path.posix.relative, + // Arguments result + [ + ["/var/lib", "/var", ".."], + ["/var/lib", "/bin", "../../bin"], + ["/var/lib", "/var/lib", ""], + ["/var/lib", "/var/apache", "../apache"], + ["/var/", "/var/lib", "lib"], + ["/", "/var/lib", "var/lib"], + ["/foo/test", "/foo/test/bar/package.json", "bar/package.json"], + ["/Users/a/web/b/test/mails", "/Users/a/web/b", "../.."], + ["/foo/bar/baz-quux", "/foo/bar/baz", "../baz"], + ["/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux"], + ["/baz-quux", "/baz", "../baz"], + ["/baz", "/baz-quux", "../baz-quux"], + ["/page1/page2/foo", "/", "../../.."], + [process.cwd(), "foo", "foo"], + ], + ], + ]; + + relativeTests.forEach(test => { + const relative = test[0]; + test[1].forEach(test => { + const actual = relative(test[0], test[1]); + const expected = test[2]; + if (actual !== expected) { + const os = relative === path.win32.relative ? "win32" : "posix"; + const message = `path.${os}.relative(${test + .slice(0, 2) + .map(JSON.stringify) + .join(",")})\n expect=${JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; + failures.push(`\n${message}`); + } + }); + }); + + strictEqual(failures.length, 0, failures.join("")); + expect(true).toBe(true); +}); + +it("path.normalize", () => { + // strictEqual( + // path.win32.normalize("./fixtures///b/../b/c.js"), + // "fixtures\\b\\c.js" + // ); + // strictEqual(path.win32.normalize("/foo/../../../bar"), "\\bar"); + // strictEqual(path.win32.normalize("a//b//../b"), "a\\b"); + // strictEqual(path.win32.normalize("a//b//./c"), "a\\b\\c"); + // strictEqual(path.win32.normalize("a//b//."), "a\\b"); + // strictEqual( + // path.win32.normalize("//server/share/dir/file.ext"), + // "\\\\server\\share\\dir\\file.ext" + // ); + // strictEqual(path.win32.normalize("/a/b/c/../../../x/y/z"), "\\x\\y\\z"); + // strictEqual(path.win32.normalize("C:"), "C:."); + // strictEqual(path.win32.normalize("C:..\\abc"), "C:..\\abc"); + // strictEqual(path.win32.normalize("C:..\\..\\abc\\..\\def"), "C:..\\..\\def"); + // strictEqual(path.win32.normalize("C:\\."), "C:\\"); + // strictEqual(path.win32.normalize("file:stream"), "file:stream"); + // strictEqual(path.win32.normalize("bar\\foo..\\..\\"), "bar\\"); + // strictEqual(path.win32.normalize("bar\\foo..\\.."), "bar"); + // strictEqual(path.win32.normalize("bar\\foo..\\..\\baz"), "bar\\baz"); + // strictEqual(path.win32.normalize("bar\\foo..\\"), "bar\\foo..\\"); + // strictEqual(path.win32.normalize("bar\\foo.."), "bar\\foo.."); + // strictEqual(path.win32.normalize("..\\foo..\\..\\..\\bar"), "..\\..\\bar"); + // strictEqual( + // path.win32.normalize("..\\...\\..\\.\\...\\..\\..\\bar"), + // "..\\..\\bar" + // ); + // strictEqual( + // path.win32.normalize("../../../foo/../../../bar"), + // "..\\..\\..\\..\\..\\bar" + // ); + // strictEqual( + // path.win32.normalize("../../../foo/../../../bar/../../"), + // "..\\..\\..\\..\\..\\..\\" + // ); + // strictEqual( + // path.win32.normalize("../foobar/barfoo/foo/../../../bar/../../"), + // "..\\..\\" + // ); + // strictEqual( + // path.win32.normalize("../.../../foobar/../../../bar/../../baz"), + // "..\\..\\..\\..\\baz" + // ); + // strictEqual(path.win32.normalize("foo/bar\\baz"), "foo\\bar\\baz"); + + strictEqual(path.posix.normalize("./fixtures///b/../b/c.js"), "fixtures/b/c.js"); + strictEqual(path.posix.normalize("/foo/../../../bar"), "/bar"); + strictEqual(path.posix.normalize("a//b//../b"), "a/b"); + strictEqual(path.posix.normalize("a//b//./c"), "a/b/c"); + strictEqual(path.posix.normalize("a//b//."), "a/b"); + strictEqual(path.posix.normalize("/a/b/c/../../../x/y/z"), "/x/y/z"); + strictEqual(path.posix.normalize("///..//./foo/.//bar"), "/foo/bar"); + strictEqual(path.posix.normalize("bar/foo../../"), "bar/"); + strictEqual(path.posix.normalize("bar/foo../.."), "bar"); + strictEqual(path.posix.normalize("bar/foo../../baz"), "bar/baz"); + strictEqual(path.posix.normalize("bar/foo../"), "bar/foo../"); + strictEqual(path.posix.normalize("bar/foo.."), "bar/foo.."); + strictEqual(path.posix.normalize("../foo../../../bar"), "../../bar"); + strictEqual(path.posix.normalize("../.../.././.../../../bar"), "../../bar"); + strictEqual(path.posix.normalize("../../../foo/../../../bar"), "../../../../../bar"); + strictEqual(path.posix.normalize("../../../foo/../../../bar/../../"), "../../../../../../"); + strictEqual(path.posix.normalize("../foobar/barfoo/foo/../../../bar/../../"), "../../"); + strictEqual(path.posix.normalize("../.../../foobar/../../../bar/../../baz"), "../../../../baz"); + strictEqual(path.posix.normalize("foo/bar\\baz"), "foo/bar\\baz"); +}); + +it("path.resolve", () => { + const failures = []; + const slashRE = /\//g; + const backslashRE = /\\/g; + + const resolveTests = [ + // [ + // path.win32.resolve, + // // Arguments result + // [ + // [["c:/blah\\blah", "d:/games", "c:../a"], "c:\\blah\\a"], + // [["c:/ignore", "d:\\a/b\\c/d", "\\e.exe"], "d:\\e.exe"], + // [["c:/ignore", "c:/some/file"], "c:\\some\\file"], + // [["d:/ignore", "d:some/dir//"], "d:\\ignore\\some\\dir"], + // [["."], process.cwd()], + // [["//server/share", "..", "relative\\"], "\\\\server\\share\\relative"], + // [["c:/", "//"], "c:\\"], + // [["c:/", "//dir"], "c:\\dir"], + // [["c:/", "//server/share"], "\\\\server\\share\\"], + // [["c:/", "//server//share"], "\\\\server\\share\\"], + // [["c:/", "///some//dir"], "c:\\some\\dir"], + // [ + // ["C:\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js"], + // "C:\\foo\\tmp.3\\cycles\\root.js", + // ], + // ], + // ], + [ + path.posix.resolve, + // Arguments result + [ + [["/var/lib", "../", "file/"], "/var/file"], + [["/var/lib", "/../", "file/"], "/file"], + [["a/b/c/", "../../.."], process.cwd()], + [["."], process.cwd()], + [["/some/dir", ".", "/absolute/"], "/absolute"], + [["/foo/tmp.3/", "../tmp.3/cycles/root.js"], "/foo/tmp.3/cycles/root.js"], + ], + ], + ]; + const isWindows = false; + resolveTests.forEach(([resolve, tests]) => { + tests.forEach(([test, expected]) => { + const actual = resolve.apply(null, test); + let actualAlt; + const os = resolve === path.win32.resolve ? "win32" : "posix"; + if (resolve === path.win32.resolve && !isWindows) actualAlt = actual.replace(backslashRE, "/"); + else if (resolve !== path.win32.resolve && isWindows) actualAlt = actual.replace(slashRE, "\\"); + + const message = `path.${os}.resolve(${test.map(JSON.stringify).join(",")})\n expect=${JSON.stringify( + expected, + )}\n actual=${JSON.stringify(actual)}`; + if (actual !== expected && actualAlt !== expected) failures.push(message); + }); + }); + strictEqual(failures.length, 0, failures.join("\n")); +}); diff --git a/test/js/node/process/print-process-args.js b/test/js/node/process/print-process-args.js new file mode 100644 index 000000000..0ab238122 --- /dev/null +++ b/test/js/node/process/print-process-args.js @@ -0,0 +1,4 @@ +var writer = Bun.stdout.writer(); +writer.write(JSON.stringify(process.argv)); +await writer.flush(true); +process.exit(0); diff --git a/test/js/node/process/process-args.test.js b/test/js/node/process/process-args.test.js new file mode 100644 index 000000000..4da3a5381 --- /dev/null +++ b/test/js/node/process/process-args.test.js @@ -0,0 +1,40 @@ +import { spawn } from "bun"; +import { test, expect } from "bun:test"; +import { bunExe } from "harness"; + +test("args exclude run", async () => { + const arg0 = process.argv[0]; + const arg1 = import.meta.dir + "/print-process-args.js"; + const exe = bunExe(); + const { stdout: s1 } = spawn([exe, "print-process-args.js"], { + cwd: import.meta.dir, + env: { BUN_DEBUG_QUIET_LOGS: "1" }, + }); + const t1 = JSON.parse(await new Response(s1).text()); + expect(t1[0]).toBe(arg0); + expect(t1[1]).toBe(arg1); + const { stdout: s2 } = spawn([exe, "print-process-args.js", "arg1"], { + cwd: import.meta.dir, + env: { BUN_DEBUG_QUIET_LOGS: "1" }, + }); + const t2 = JSON.parse(await new Response(s2).text()); + expect(t2[0]).toBe(arg0); + expect(t2[1]).toBe(arg1); + expect(t2[2]).toBe("arg1"); + const { stdout: s3 } = spawn([exe, "run", "print-process-args.js"], { + cwd: import.meta.dir, + env: { BUN_DEBUG_QUIET_LOGS: "1" }, + }); + const t3 = JSON.parse(await new Response(s3).text()); + expect(t3[0]).toBe(arg0); + expect(t3[1]).toBe(arg1); + const { stdout: s4 } = spawn([exe, "run", "print-process-args.js", "arg1", "arg2"], { + cwd: import.meta.dir, + env: { BUN_DEBUG_QUIET_LOGS: "1" }, + }); + const t4 = JSON.parse(await new Response(s4).text()); + expect(t4[0]).toBe(arg0); + expect(t4[1]).toBe(arg1); + expect(t4[2]).toBe("arg1"); + expect(t4[3]).toBe("arg2"); +}); diff --git a/test/js/node/process/process-nexttick.js b/test/js/node/process/process-nexttick.js new file mode 100644 index 000000000..c6b24ba14 --- /dev/null +++ b/test/js/node/process/process-nexttick.js @@ -0,0 +1,84 @@ +// You can verify this test is correct by copy pasting this into a browser's console and checking it doesn't throw an error. +var run = 0; + +var queueMicrotask = process.nextTick; + +await new Promise((resolve, reject) => { + queueMicrotask(() => { + if (run++ != 0) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + queueMicrotask(() => { + if (run++ != 3) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + }); + }); + queueMicrotask(() => { + if (run++ != 1) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + queueMicrotask(() => { + if (run++ != 4) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + + queueMicrotask(() => { + if (run++ != 6) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + }); + }); + }); + queueMicrotask(() => { + if (run++ != 2) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + queueMicrotask(() => { + if (run++ != 5) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + + queueMicrotask(() => { + if (run++ != 7) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + resolve(true); + }); + }); + }); +}); + +{ + var passed = false; + try { + queueMicrotask(1234); + } catch (exception) { + passed = exception instanceof TypeError; + } + + if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is not a function"); +} + +{ + var passed = false; + try { + queueMicrotask(); + } catch (exception) { + passed = exception instanceof TypeError; + } + + if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is empty"); +} + +await new Promise((resolve, reject) => { + process.nextTick( + (first, second) => { + console.log(first, second); + if (first !== 12345 || second !== "hello") reject(new Error("process.nextTick called with wrong arguments")); + resolve(true); + }, + 12345, + "hello", + ); +}); diff --git a/test/js/node/process/process-nexttick.test.js b/test/js/node/process/process-nexttick.test.js new file mode 100644 index 000000000..becf3c236 --- /dev/null +++ b/test/js/node/process/process-nexttick.test.js @@ -0,0 +1,99 @@ +import { it } from "bun:test"; + +it("process.nextTick", async () => { + // You can verify this test is correct by copy pasting this into a browser's console and checking it doesn't throw an error. + var run = 0; + var queueMicrotask = process.nextTick; + + await new Promise((resolve, reject) => { + queueMicrotask(() => { + if (run++ != 0) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + queueMicrotask(() => { + if (run++ != 3) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + }); + }); + queueMicrotask(() => { + if (run++ != 1) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + queueMicrotask(() => { + if (run++ != 4) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + + queueMicrotask(() => { + if (run++ != 6) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + }); + }); + }); + queueMicrotask(() => { + if (run++ != 2) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + queueMicrotask(() => { + if (run++ != 5) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + + queueMicrotask(() => { + if (run++ != 7) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + resolve(true); + }); + }); + }); + }); + + { + var passed = false; + try { + queueMicrotask(1234); + } catch (exception) { + passed = exception instanceof TypeError; + } + + if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is not a function"); + } + + { + var passed = false; + try { + queueMicrotask(); + } catch (exception) { + passed = exception instanceof TypeError; + } + + if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is empty"); + } +}); + +it("process.nextTick 2 args", async () => { + await new Promise((resolve, reject) => { + process.nextTick( + (first, second) => { + if (first !== 12345 || second !== "hello") reject(new Error("process.nextTick called with wrong arguments")); + resolve(true); + }, + 12345, + "hello", + ); + }); +}); + +it("process.nextTick 5 args", async () => { + await new Promise((resolve, reject) => { + var args = [12345, "hello", "hello", "hello", 5]; + process.nextTick((...receivedArgs) => { + if (!args.every((arg, index) => arg === receivedArgs[index])) + reject(new Error("process.nextTick called with wrong arguments")); + resolve(true); + }, ...args); + }); +}); diff --git a/test/js/node/process/process-stdin-echo.js b/test/js/node/process/process-stdin-echo.js new file mode 100644 index 000000000..e265cc76d --- /dev/null +++ b/test/js/node/process/process-stdin-echo.js @@ -0,0 +1,11 @@ +process.stdin.setEncoding("utf8"); +process.stdin.on("data", data => { + process.stdout.write(data); +}); +process.stdin.once("end", () => { + process.stdout.write("ENDED"); +}); +if (process.argv[2] == "resume") { + process.stdout.write("RESUMED"); + process.stdin.resume(); +} diff --git a/test/js/node/process/process-stdio.test.ts b/test/js/node/process/process-stdio.test.ts new file mode 100644 index 000000000..463ab5fda --- /dev/null +++ b/test/js/node/process/process-stdio.test.ts @@ -0,0 +1,128 @@ +import { spawn, spawnSync } from "bun"; +import { describe, expect, it, test } from "bun:test"; +import { bunExe } from "harness"; +import { isatty } from "tty"; + +test("process.stdin", () => { + expect(process.stdin).toBeDefined(); + expect(process.stdout.isTTY).toBe(isatty(0)); + expect(process.stdin.on("close", function () {})).toBe(process.stdin); + expect(process.stdin.once("end", function () {})).toBe(process.stdin); +}); + +test("process.stdin - read", async () => { + const { stdin, stdout } = spawn({ + cmd: [bunExe(), import.meta.dir + "/process-stdin-echo.js"], + stdout: "pipe", + stdin: "pipe", + stderr: null, + env: { + ...process.env, + BUN_DEBUG_QUIET_LOGS: "1", + }, + }); + expect(stdin).toBeDefined(); + expect(stdout).toBeDefined(); + var lines = ["Get Emoji", "— All Emojis to ✂️ Copy and 📋 Paste", "👌", ""]; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + setTimeout(() => { + if (line) { + stdin?.write(line + "\n"); + stdin?.flush(); + } else { + stdin?.end(); + } + }, i * 200); + } + var text = await new Response(stdout).text(); + expect(text).toBe(lines.join("\n") + "ENDED"); +}); + +test("process.stdin - resume", async () => { + const { stdin, stdout } = spawn({ + cmd: [bunExe(), import.meta.dir + "/process-stdin-echo.js", "resume"], + stdout: "pipe", + stdin: "pipe", + stderr: null, + env: { + ...process.env, + BUN_DEBUG_QUIET_LOGS: "1", + }, + }); + expect(stdin).toBeDefined(); + expect(stdout).toBeDefined(); + var lines = ["Get Emoji", "— All Emojis to ✂️ Copy and 📋 Paste", "👌", ""]; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + setTimeout(() => { + if (line) { + stdin?.write(line + "\n"); + stdin?.flush(); + } else { + stdin?.end(); + } + }, i * 200); + } + var text = await new Response(stdout).text(); + expect(text).toBe("RESUMED" + lines.join("\n") + "ENDED"); +}); + +test("process.stdout", () => { + expect(process.stdout).toBeDefined(); + expect(process.stdout.isTTY).toBe(isatty(1)); +}); + +test("process.stderr", () => { + expect(process.stderr).toBeDefined(); + expect(process.stderr.isTTY).toBe(isatty(2)); +}); + +test("process.stdout - write", () => { + const { stdout } = spawnSync({ + cmd: [bunExe(), import.meta.dir + "/stdio-test-instance.js"], + stdout: "pipe", + stdin: null, + stderr: null, + env: { + ...process.env, + BUN_DEBUG_QUIET_LOGS: "1", + }, + }); + + expect(stdout?.toString()).toBe(`hello worldhello again|😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌`); +}); + +test("process.stdout - write a lot (string)", () => { + const { stdout } = spawnSync({ + cmd: [bunExe(), import.meta.dir + "/stdio-test-instance-a-lot.js"], + stdout: "pipe", + stdin: null, + stderr: null, + env: { + ...process.env, + BUN_DEBUG_QUIET_LOGS: "1", + TEST_STDIO_STRING: "1", + }, + }); + + expect(stdout?.toString()).toBe( + `hello worldhello again|😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌`.repeat(9999), + ); +}); + +test("process.stdout - write a lot (bytes)", () => { + const { stdout } = spawnSync({ + cmd: [bunExe(), import.meta.dir + "/stdio-test-instance-a-lot.js"], + stdout: "pipe", + stdin: null, + stderr: null, + env: { + ...process.env, + BUN_DEBUG_QUIET_LOGS: "1", + }, + }); + expect(stdout?.toString()).toBe( + `hello worldhello again|😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌`.repeat(9999), + ); +}); diff --git a/test/js/node/process/process.test.js b/test/js/node/process/process.test.js new file mode 100644 index 000000000..59f54c53f --- /dev/null +++ b/test/js/node/process/process.test.js @@ -0,0 +1,185 @@ +import { resolveSync, which } from "bun"; +import { describe, expect, it } from "bun:test"; +import { existsSync, readFileSync, realpathSync } from "fs"; +import { basename } from "path"; + +it("process", () => { + // this property isn't implemented yet but it should at least return a string + const isNode = !process.isBun; + + if (!isNode && process.title !== "bun") throw new Error("process.title is not 'bun'"); + + if (typeof process.env.USER !== "string") throw new Error("process.env is not an object"); + + if (process.env.USER.length === 0) throw new Error("process.env is missing a USER property"); + + if (process.platform !== "darwin" && process.platform !== "linux") throw new Error("process.platform is invalid"); + + if (isNode) throw new Error("process.isBun is invalid"); + + // partially to test it doesn't crash due to various strange types + process.env.BACON = "yummy"; + if (process.env.BACON !== "yummy") { + throw new Error("process.env is not writable"); + } + + delete process.env.BACON; + if (typeof process.env.BACON !== "undefined") { + throw new Error("process.env is not deletable"); + } + + process.env.BACON = "yummy"; + if (process.env.BACON !== "yummy") { + throw new Error("process.env is not re-writable"); + } + if (!JSON.stringify(process.env)) { + throw new Error("process.env is not serializable"); + } + + if (typeof JSON.parse(JSON.stringify(process.env)).toJSON !== "undefined") { + throw new Error("process.env should call toJSON to hide its internal state"); + } + + var { env, ...proces } = process; + console.log(proces); + + console.log("CWD", process.cwd()); + console.log("SET CWD", process.chdir("../")); + console.log("CWD", process.cwd()); +}); + +it("process.hrtime()", () => { + const start = process.hrtime(); + const end = process.hrtime(start); + const end2 = process.hrtime(); + expect(end[0]).toBe(0); + expect(end2[1] > start[1]).toBe(true); +}); + +it("process.hrtime.bigint()", () => { + const start = process.hrtime.bigint(); + const end = process.hrtime.bigint(); + expect(end > start).toBe(true); +}); + +it("process.release", () => { + expect(process.release.name).toBe("bun"); + expect(process.release.sourceUrl).toBe( + `https://github.com/oven-sh/bun/release/bun-v${process.versions.bun}/bun-${process.platform}-${ + { arm64: "aarch64", x64: "x64" }[process.arch] || process.arch + }.zip`, + ); +}); + +it("process.env", () => { + process.env["LOL SMILE UTF16 😂"] = "😂"; + expect(process.env["LOL SMILE UTF16 😂"]).toBe("😂"); + delete process.env["LOL SMILE UTF16 😂"]; + expect(process.env["LOL SMILE UTF16 😂"]).toBe(undefined); + + process.env["LOL SMILE latin1 "] = ""; + expect(process.env["LOL SMILE latin1 "]).toBe(""); + delete process.env["LOL SMILE latin1 "]; + expect(process.env["LOL SMILE latin1 "]).toBe(undefined); +}); + +it("process.env is spreadable and editable", () => { + process.env["LOL SMILE UTF16 😂"] = "😂"; + const { "LOL SMILE UTF16 😂": lol, ...rest } = process.env; + expect(lol).toBe("😂"); + delete process.env["LOL SMILE UTF16 😂"]; + expect(rest).toEqual(process.env); + const orig = (getter => process.env[getter])("USER"); + expect(process.env).toEqual(process.env); + eval(`globalThis.process.env.USER = 'bun';`); + expect(eval(`globalThis.process.env.USER`)).toBe("bun"); + expect(eval(`globalThis.process.env.USER = "${orig}"`)).toBe(orig); +}); + +it("process.version starts with v", () => { + expect(process.version.startsWith("v")).toBeTruthy(); +}); + +it("process.argv0", () => { + expect(basename(process.argv0)).toBe(basename(process.argv[0])); +}); + +it("process.execPath", () => { + expect(process.execPath).not.toBe(basename(process.argv0)); + expect(which(process.execPath)).not.toBeNull(); +}); + +it("process.uptime()", () => { + expect(process.uptime()).toBeGreaterThan(0); + expect(Math.floor(process.uptime())).toBe(Math.floor(performance.now() / 1000)); +}); + +it("process.umask()", () => { + const orig = process.umask(777); + expect(orig).toBeGreaterThan(0); + expect(process.umask(orig)).toBe(777); +}); + +const versions = existsSync(import.meta.dir + "/../../src/generated_versions_list.zig"); +(versions ? it : it.skip)("process.versions", () => { + // Generate a list of all the versions in the versions object + // example: + // pub const boringssl = "b275c5ce1c88bc06f5a967026d3c0ce1df2be815"; + // pub const libarchive = "dc321febde83dd0f31158e1be61a7aedda65e7a2"; + // pub const mimalloc = "3c7079967a269027e438a2aac83197076d9fe09d"; + // pub const picohttpparser = "066d2b1e9ab820703db0837a7255d92d30f0c9f5"; + // pub const uws = "70b1b9fc1341e8b791b42c5447f90505c2abe156"; + // pub const webkit = "60d11703a533fd694cd1d6ddda04813eecb5d69f"; + // pub const zlib = "885674026394870b7e7a05b7bf1ec5eb7bd8a9c0"; + // pub const tinycc = "2d3ad9e0d32194ad7fd867b66ebe218dcc8cb5cd"; + // pub const lolhtml = "2eed349dcdfa4ff5c19fe7c6e501cfd687601033"; + // pub const c_ares = "0e7a5dee0fbb04080750cf6eabbe89d8bae87faa"; + // pub const usockets = "fafc241e8664243fc0c51d69684d5d02b9805134"; + const versions = Object.fromEntries( + readFileSync(import.meta.dir + "/../../src/generated_versions_list.zig", "utf8") + .split("\n") + .filter(line => line.startsWith("pub const") && !line.includes("zig") && line.includes(' = "')) + .map(line => line.split(" = ")) + .map(([name, hash]) => [name.slice(9).trim(), hash.slice(1, -2)]), + ); + versions.uwebsockets = versions.uws; + delete versions.uws; + versions["ares"] = versions.c_ares; + delete versions.c_ares; + + for (const name in versions) { + expect(process.versions).toHaveProperty(name); + expect(process.versions[name]).toBe(versions[name]); + } +}); + +it("process.config", () => { + expect(process.config).toEqual({ + variables: { + v8_enable_i8n_support: 1, + }, + target_defaults: {}, + }); +}); + +it("process.emitWarning", () => { + process.emitWarning("-- Testing process.emitWarning --"); + var called = 0; + process.on("warning", err => { + called++; + expect(err.message).toBe("-- Testing process.on('warning') --"); + }); + process.emitWarning("-- Testing process.on('warning') --"); + expect(called).toBe(1); + expect(process.off("warning")).toBe(process); + process.emitWarning("-- Testing process.on('warning') --"); + expect(called).toBe(1); +}); + +it("process.execArgv", () => { + expect(process.execArgv instanceof Array).toBe(true); +}); + +it("process.binding", () => { + expect(() => process.binding("buffer")).toThrow(); +}); diff --git a/test/js/node/readline/readline.node.test.ts b/test/js/node/readline/readline.node.test.ts new file mode 100644 index 000000000..7dfa51ac8 --- /dev/null +++ b/test/js/node/readline/readline.node.test.ts @@ -0,0 +1,2001 @@ +import { beforeEach, describe, it } from "bun:test"; +import readline from "node:readline"; +import { Writable, PassThrough } from "node:stream"; +import { EventEmitter } from "node:events"; +import { createDoneDotAll, createCallCheckCtx, assert } from "node-harness"; + +var { + CSI, + utils: { getStringWidth, stripVTControlCharacters }, +} = readline[Symbol.for("__BUN_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__")]; + +// ---------------------------------------------------------------------------- +// Helpers +// ---------------------------------------------------------------------------- + +class TestWritable extends Writable { + data; + constructor() { + super(); + this.data = ""; + } + _write(chunk, encoding, callback) { + this.data += chunk.toString(); + callback(); + } +} + +class FakeInput extends EventEmitter { + resume() {} + pause() {} + write() {} + end() {} +} + +function isWarned(emitter) { + for (const name in emitter) { + const listeners = emitter[name]; + if (listeners.warned) return true; + } + return false; +} + +function getInterface(options) { + const fi = new FakeInput(); + const rli = new readline.Interface({ + input: fi, + output: fi, + ...options, + }); + return [rli, fi]; +} + +function assertCursorRowsAndCols(rli, rows, cols) { + const cursorPos = rli.getCursorPos(); + assert.strictEqual(cursorPos.rows, rows); + assert.strictEqual(cursorPos.cols, cols); +} + +const writable = new TestWritable(); +const input = new FakeInput(); + +// ---------------------------------------------------------------------------- +// Tests +// ---------------------------------------------------------------------------- + +describe("CSI", () => { + it("should be defined", () => { + assert.ok(CSI); + }); + + it("should have all the correct clear sequences", () => { + assert.strictEqual(CSI.kClearToLineBeginning, "\x1b[1K"); + assert.strictEqual(CSI.kClearToLineEnd, "\x1b[0K"); + assert.strictEqual(CSI.kClearLine, "\x1b[2K"); + assert.strictEqual(CSI.kClearScreenDown, "\x1b[0J"); + assert.strictEqual(CSI`1${2}3`, "\x1b[123"); + }); +}); + +describe("readline.clearScreenDown()", () => { + it("should put clear screen sequence into writable when called", done => { + const { mustCall } = createCallCheckCtx(done); + + assert.strictEqual(readline.clearScreenDown(writable), true); + assert.deepStrictEqual(writable.data, CSI.kClearScreenDown); + assert.strictEqual(readline.clearScreenDown(writable, mustCall()), true); + }); + + it("should throw on invalid callback", () => { + // Verify that clearScreenDown() throws on invalid callback. + assert.throws(() => { + readline.clearScreenDown(writable, null); + }, /ERR_INVALID_ARG_TYPE/); + }); + + it("should that clearScreenDown() does not throw on null or undefined stream", done => { + const { mustCall } = createCallCheckCtx(done); + assert.strictEqual( + readline.clearScreenDown( + null, + mustCall(err => { + assert.strictEqual(err, null); + }), + ), + true, + ); + assert.strictEqual(readline.clearScreenDown(undefined, mustCall()), true); + }); +}); + +describe("readline.clearLine()", () => { + beforeEach(() => { + writable.data = ""; + }); + + it("should clear to the left of cursor when given -1 as direction", () => { + assert.strictEqual(readline.clearLine(writable, -1), true); + assert.deepStrictEqual(writable.data, CSI.kClearToLineBeginning); + }); + + it("should clear to the right of cursor when given 1 as direction", () => { + assert.strictEqual(readline.clearLine(writable, 1), true); + assert.deepStrictEqual(writable.data, CSI.kClearToLineEnd); + }); + + it("should clear whole line when given 0 as direction", () => { + assert.strictEqual(readline.clearLine(writable, 0), true); + assert.deepStrictEqual(writable.data, CSI.kClearLine); + }); + + it("should call callback after clearing line", done => { + const { mustCall } = createCallCheckCtx(done); + assert.strictEqual(readline.clearLine(writable, -1, mustCall()), true); + assert.deepStrictEqual(writable.data, CSI.kClearToLineBeginning); + }); + + it("should throw on an invalid callback", () => { + // Verify that clearLine() throws on invalid callback. + assert.throws(() => { + readline.clearLine(writable, 0, null); + }, /ERR_INVALID_ARG_TYPE/); + }); + + it("shouldn't throw on on null or undefined stream", done => { + const { mustCall } = createCallCheckCtx(done); + // Verify that clearLine() does not throw on null or undefined stream. + assert.strictEqual(readline.clearLine(null, 0), true); + assert.strictEqual(readline.clearLine(undefined, 0), true); + assert.strictEqual( + readline.clearLine( + null, + 0, + mustCall(err => { + assert.strictEqual(err, null); + }), + ), + true, + ); + assert.strictEqual(readline.clearLine(undefined, 0, mustCall()), true); + }); +}); + +describe("readline.moveCursor()", () => { + it("shouldn't write when moveCursor(0, 0) is called", done => { + const { mustCall } = createCallCheckCtx(done); + // Nothing is written when moveCursor 0, 0 + [ + [0, 0, ""], + [1, 0, "\x1b[1C"], + [-1, 0, "\x1b[1D"], + [0, 1, "\x1b[1B"], + [0, -1, "\x1b[1A"], + [1, 1, "\x1b[1C\x1b[1B"], + [-1, 1, "\x1b[1D\x1b[1B"], + [-1, -1, "\x1b[1D\x1b[1A"], + [1, -1, "\x1b[1C\x1b[1A"], + ].forEach(set => { + writable.data = ""; + assert.strictEqual(readline.moveCursor(writable, set[0], set[1]), true); + assert.deepStrictEqual(writable.data, set[2]); + writable.data = ""; + assert.strictEqual(readline.moveCursor(writable, set[0], set[1], mustCall()), true); + assert.deepStrictEqual(writable.data, set[2]); + }); + }); + + it("should throw on invalid callback", () => { + // Verify that moveCursor() throws on invalid callback. + assert.throws(() => { + readline.moveCursor(writable, 1, 1, null); + }, /ERR_INVALID_ARG_TYPE/); + }); + + it("should not throw on null or undefined stream", done => { + const { mustCall } = createCallCheckCtx(done); + // Verify that moveCursor() does not throw on null or undefined stream. + assert.strictEqual(readline.moveCursor(null, 1, 1), true); + assert.strictEqual(readline.moveCursor(undefined, 1, 1), true); + assert.strictEqual( + readline.moveCursor( + null, + 1, + 1, + mustCall(err => { + assert.strictEqual(err, null); + }), + ), + true, + ); + assert.strictEqual(readline.moveCursor(undefined, 1, 1, mustCall()), true); + }); +}); + +describe("readline.cursorTo()", () => { + beforeEach(() => { + writable.data = ""; + }); + + it("should not throw on undefined or null as stream", done => { + const { mustCall } = createCallCheckCtx(done); + // Undefined or null as stream should not throw. + assert.strictEqual(readline.cursorTo(null), true); + assert.strictEqual(readline.cursorTo(), true); + assert.strictEqual(readline.cursorTo(null, 1, 1, mustCall()), true); + assert.strictEqual( + readline.cursorTo( + undefined, + 1, + 1, + mustCall(err => { + assert.strictEqual(err, null); + }), + ), + true, + ); + }); + + it("should not write if given invalid cursor position - [string, undefined]", () => { + assert.strictEqual(readline.cursorTo(writable, "a"), true); + assert.strictEqual(writable.data, ""); + }); + + it("should not write if given invalid cursor position - [string, string]", () => { + assert.strictEqual(readline.cursorTo(writable, "a", "b"), true); + assert.strictEqual(writable.data, ""); + }); + + it("should throw when x is not a number", () => { + assert.throws(() => readline.cursorTo(writable, "a", 1), { + name: "TypeError", + code: "ERR_INVALID_CURSOR_POS", + message: "Cannot set cursor row without setting its column", + }); + assert.strictEqual(writable.data, ""); + }); + + it("should write when given value cursor positions", done => { + const { mustCall } = createCallCheckCtx(done); + + assert.strictEqual(readline.cursorTo(writable, 1, "a"), true); + assert.strictEqual(writable.data, "\x1b[2G"); + + writable.data = ""; + assert.strictEqual(readline.cursorTo(writable, 1), true); + assert.strictEqual(writable.data, "\x1b[2G"); + + writable.data = ""; + assert.strictEqual(readline.cursorTo(writable, 1, 2), true); + assert.strictEqual(writable.data, "\x1b[3;2H"); + + writable.data = ""; + assert.strictEqual(readline.cursorTo(writable, 1, 2, mustCall()), true); + assert.strictEqual(writable.data, "\x1b[3;2H"); + + writable.data = ""; + assert.strictEqual(readline.cursorTo(writable, 1, mustCall()), true); + assert.strictEqual(writable.data, "\x1b[2G"); + }); + + it("should throw on invalid callback", () => { + // Verify that cursorTo() throws on invalid callback. + assert.throws(() => { + readline.cursorTo(writable, 1, 1, null); + }, /ERR_INVALID_ARG_TYPE/); + }); + + it("should throw if x or y is NaN", () => { + // Verify that cursorTo() throws if x or y is NaN. + assert.throws(() => { + readline.cursorTo(writable, NaN); + }, /ERR_INVALID_ARG_VALUE/); + + assert.throws(() => { + readline.cursorTo(writable, 1, NaN); + }, /ERR_INVALID_ARG_VALUE/); + + assert.throws(() => { + readline.cursorTo(writable, NaN, NaN); + }, /ERR_INVALID_ARG_VALUE/); + }); +}); + +describe("readline.emitKeyPressEvents()", () => { + // emitKeypressEvents is thoroughly tested in test-readline-keys.js. + // However, that test calls it implicitly. This is just a quick sanity check + // to verify that it works when called explicitly. + + const expectedSequence = ["f", "o", "o"]; + const expectedKeys = [ + { sequence: "f", name: "f", ctrl: false, meta: false, shift: false }, + { sequence: "o", name: "o", ctrl: false, meta: false, shift: false }, + { sequence: "o", name: "o", ctrl: false, meta: false, shift: false }, + ]; + + it("should emit the expected sequence when keypress listener added after called", () => { + const stream = new PassThrough(); + const sequence: any[] = []; + const keys: any[] = []; + + readline.emitKeypressEvents(stream); + stream.on("keypress", (s, k) => { + sequence.push(s); + keys.push(k); + }); + stream.write("foo"); + + assert.deepStrictEqual(sequence, expectedSequence); + assert.deepStrictEqual(keys, expectedKeys); + }); + + it("should emit the expected sequence when keypress listener added before called", () => { + const stream = new PassThrough(); + const sequence: any[] = []; + const keys: any[] = []; + + stream.on("keypress", (s, k) => { + sequence.push(s); + keys.push(k); + }); + readline.emitKeypressEvents(stream); + stream.write("foo"); + + assert.deepStrictEqual(sequence, expectedSequence); + assert.deepStrictEqual(keys, expectedKeys); + }); + + it("should allow keypress listeners to be removed and added again", () => { + const stream = new PassThrough(); + const sequence: any[] = []; + const keys: any[] = []; + const keypressListener = (s, k) => { + sequence.push(s); + keys.push(k); + }; + + stream.on("keypress", keypressListener); + readline.emitKeypressEvents(stream); + stream.removeListener("keypress", keypressListener); + stream.write("foo"); + + assert.deepStrictEqual(sequence, []); + assert.deepStrictEqual(keys, []); + + stream.on("keypress", keypressListener); + stream.write("foo"); + + assert.deepStrictEqual(sequence, expectedSequence); + assert.deepStrictEqual(keys, expectedKeys); + }); +}); + +describe("readline.Interface", () => { + it("should allow valid escapeCodeTimeout to be set", () => { + const fi = new FakeInput(); + const rli = new readline.Interface({ + input: fi, + output: fi, + escapeCodeTimeout: 50, + }); + assert.strictEqual(rli.escapeCodeTimeout, 50); + rli.close(); + }); + + it("should throw on invalid escapeCodeTimeout", () => { + [null, {}, NaN, "50"].forEach(invalidInput => { + assert.throws( + () => { + const fi = new FakeInput(); + const rli = new readline.Interface({ + input: fi, + output: fi, + escapeCodeTimeout: invalidInput, + }); + rli.close(); + }, + { + name: "TypeError", + code: "ERR_INVALID_ARG_VALUE", + }, + ); + }); + }); + + it("should create valid instances of readline.Interface", () => { + const input = new FakeInput(); + const rl = readline.Interface({ input }); + assert.ok(rl instanceof readline.Interface); + }); + + it("should call completer when input emits data", done => { + const { mustCall } = createCallCheckCtx(done); + const fi = new FakeInput(); + const rli = new readline.Interface( + fi, + fi, + mustCall(line => [[], line]), + true, + ); + + assert.ok(rli instanceof readline.Interface); + fi.emit("data", "a\t"); + rli.close(); + }); + + it("should allow crlfDelay to be set", () => { + [undefined, 50, 0, 100.5, 5000].forEach(crlfDelay => { + const [rli] = getInterface({ crlfDelay }); + assert.strictEqual(rli.crlfDelay, Math.max(crlfDelay || 100, 100)); + rli.close(); + }); + }); + + it("should throw if completer is not a function or is undefined", () => { + ["not an array", 123, 123n, {}, true, Symbol(), null].forEach(invalid => { + assert.throws( + () => { + readline.createInterface({ + input, + completer: invalid, + }); + }, + { + name: "TypeError", + code: "ERR_INVALID_ARG_VALUE", + }, + ); + }); + }); + + it("should throw if history is not an array", () => { + ["not an array", 123, 123, {}, true, Symbol(), null].forEach(history => { + assert.throws( + () => { + readline.createInterface({ + input, + history, + }); + }, + { + name: "TypeError", + code: "ERR_INVALID_ARG_TYPE", + }, + ); + }); + }); + + it("should throw if historySize is not a positive number", () => { + ["not a number", -1, NaN, {}, true, Symbol(), null].forEach(historySize => { + assert.throws( + () => { + readline.createInterface({ + input, + historySize, + }); + }, + { + // TODO: Revert to Range error when properly implemented errors with multiple bases + // name: "RangeError", + name: "TypeError", + code: "ERR_INVALID_ARG_VALUE", + }, + ); + }); + }); + + it("should throw on invalid tabSize", () => { + // Check for invalid tab sizes. + assert.throws( + () => + new readline.Interface({ + input, + tabSize: 0, + }), + { code: "ERR_OUT_OF_RANGE" }, + ); + + assert.throws( + () => + new readline.Interface({ + input, + tabSize: "4", + }), + { code: "ERR_INVALID_ARG_TYPE" }, + ); + + assert.throws( + () => + new readline.Interface({ + input, + tabSize: 4.5, + }), + { + code: "ERR_OUT_OF_RANGE", + // message: + // 'The value of "tabSize" is out of range. ' + + // "It must be an integer. Received 4.5", + }, + ); + }); + + // Sending a single character with no newline + it("should not emit line when only a single character sent with no newline", done => { + const { mustNotCall } = createCallCheckCtx(done); + const fi = new FakeInput(); + const rli = new readline.Interface(fi, {}); + rli.on("line", mustNotCall()); + fi.emit("data", "a"); + rli.close(); + }); + + it("should treat \\r like \\n when alone", done => { + const { mustCall } = createCallCheckCtx(done); + // Sending multiple newlines at once that does not end with a new line and a + // `end` event(last line is). \r should behave like \n when alone. + const [rli, fi] = getInterface({ terminal: true }); + const expectedLines = ["foo", "bar", "baz", "bat"]; + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, expectedLines.shift()); + }, expectedLines.length - 1), + ); + fi.emit("data", expectedLines.join("\r")); + rli.close(); + }); + + // \r at start of input should output blank line + it("should output blank line when \\r at start of input", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true }); + const expectedLines = ["", "foo"]; + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, expectedLines.shift()); + }, expectedLines.length), + ); + fi.emit("data", "\rfoo\r"); + rli.close(); + }); + + // \t does not become part of the input when there is a completer function + it("should not include \\t in input when there is a completer function", done => { + const { mustCall } = createCallCheckCtx(done); + const completer = line => [[], line]; + const [rli, fi] = getInterface({ terminal: true, completer }); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "foo"); + }), + ); + for (const character of "\tfo\to\t") { + fi.emit("data", character); + } + fi.emit("data", "\n"); + rli.close(); + }); + + // \t when there is no completer function should behave like an ordinary + // character + it("should treat \\t as an ordinary character when there is no completer function", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true }); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "\t"); + }), + ); + fi.emit("data", "\t"); + fi.emit("data", "\n"); + rli.close(); + }); + + // Adding history lines should emit the history event with + // the history array + it("should emit history event when adding history lines", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true }); + const expectedLines = ["foo", "bar", "baz", "bat"]; + rli.on( + "history", + mustCall(history => { + const expectedHistory = expectedLines.slice(0, history.length).reverse(); + assert.deepStrictEqual(history, expectedHistory); + }, expectedLines.length), + ); + for (const line of expectedLines) { + fi.emit("data", `${line}\n`); + } + rli.close(); + }); + + // Altering the history array in the listener should not alter + // the line being processed + it("should not alter the line being processed when history is altered", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true }); + const expectedLine = "foo"; + rli.on( + "history", + mustCall(history => { + assert.strictEqual(history[0], expectedLine); + history.shift(); + }), + ); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, expectedLine); + assert.strictEqual(rli.history.length, 0); + }), + ); + fi.emit("data", `${expectedLine}\n`); + rli.close(); + }); + + // Duplicate lines are removed from history when + // `options.removeHistoryDuplicates` is `true` + it("should remove duplicate lines from history when removeHistoryDuplicates is true", () => { + const [rli, fi] = getInterface({ + terminal: true, + removeHistoryDuplicates: true, + }); + const expectedLines = ["foo", "bar", "baz", "bar", "bat", "bat"]; + // ['foo', 'baz', 'bar', bat']; + let callCount = 0; + rli.on("line", line => { + assert.strictEqual(line, expectedLines[callCount]); + callCount++; + }); + fi.emit("data", `${expectedLines.join("\n")}\n`); + assert.strictEqual(callCount, expectedLines.length); + fi.emit("keypress", ".", { name: "up" }); // 'bat' + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit("keypress", ".", { name: "up" }); // 'bar' + assert.notStrictEqual(rli.line, expectedLines[--callCount]); + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit("keypress", ".", { name: "up" }); // 'baz' + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit("keypress", ".", { name: "up" }); // 'foo' + assert.notStrictEqual(rli.line, expectedLines[--callCount]); + assert.strictEqual(rli.line, expectedLines[--callCount]); + assert.strictEqual(callCount, 0); + fi.emit("keypress", ".", { name: "down" }); // 'baz' + assert.strictEqual(rli.line, "baz"); + assert.strictEqual(rli.historyIndex, 2); + fi.emit("keypress", ".", { name: "n", ctrl: true }); // 'bar' + assert.strictEqual(rli.line, "bar"); + assert.strictEqual(rli.historyIndex, 1); + fi.emit("keypress", ".", { name: "n", ctrl: true }); + assert.strictEqual(rli.line, "bat"); + assert.strictEqual(rli.historyIndex, 0); + // Activate the substring history search. + fi.emit("keypress", ".", { name: "down" }); // 'bat' + assert.strictEqual(rli.line, "bat"); + assert.strictEqual(rli.historyIndex, -1); + // Deactivate substring history search. + fi.emit("keypress", ".", { name: "backspace" }); // 'ba' + assert.strictEqual(rli.historyIndex, -1); + assert.strictEqual(rli.line, "ba"); + // Activate the substring history search. + fi.emit("keypress", ".", { name: "down" }); // 'ba' + assert.strictEqual(rli.historyIndex, -1); + assert.strictEqual(rli.line, "ba"); + fi.emit("keypress", ".", { name: "down" }); // 'ba' + assert.strictEqual(rli.historyIndex, -1); + assert.strictEqual(rli.line, "ba"); + fi.emit("keypress", ".", { name: "up" }); // 'bat' + assert.strictEqual(rli.historyIndex, 0); + assert.strictEqual(rli.line, "bat"); + fi.emit("keypress", ".", { name: "up" }); // 'bar' + assert.strictEqual(rli.historyIndex, 1); + assert.strictEqual(rli.line, "bar"); + fi.emit("keypress", ".", { name: "up" }); // 'baz' + assert.strictEqual(rli.historyIndex, 2); + assert.strictEqual(rli.line, "baz"); + fi.emit("keypress", ".", { name: "up" }); // 'ba' + assert.strictEqual(rli.historyIndex, 4); + assert.strictEqual(rli.line, "ba"); + fi.emit("keypress", ".", { name: "up" }); // 'ba' + assert.strictEqual(rli.historyIndex, 4); + assert.strictEqual(rli.line, "ba"); + // Deactivate substring history search and reset history index. + fi.emit("keypress", ".", { name: "right" }); // 'ba' + assert.strictEqual(rli.historyIndex, -1); + assert.strictEqual(rli.line, "ba"); + // Substring history search activated. + fi.emit("keypress", ".", { name: "up" }); // 'ba' + assert.strictEqual(rli.historyIndex, 0); + assert.strictEqual(rli.line, "bat"); + rli.close(); + }); + + // Duplicate lines are not removed from history when + // `options.removeHistoryDuplicates` is `false` + it("should not remove duplicate lines from history when removeHistoryDuplicates is false", () => { + const [rli, fi] = getInterface({ + terminal: true, + removeHistoryDuplicates: false, + }); + const expectedLines = ["foo", "bar", "baz", "bar", "bat", "bat"]; + let callCount = 0; + rli.on("line", line => { + assert.strictEqual(line, expectedLines[callCount]); + callCount++; + }); + fi.emit("data", `${expectedLines.join("\n")}\n`); + assert.strictEqual(callCount, expectedLines.length); + fi.emit("keypress", ".", { name: "up" }); // 'bat' + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit("keypress", ".", { name: "up" }); // 'bar' + assert.notStrictEqual(rli.line, expectedLines[--callCount]); + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit("keypress", ".", { name: "up" }); // 'baz' + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit("keypress", ".", { name: "up" }); // 'bar' + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit("keypress", ".", { name: "up" }); // 'foo' + assert.strictEqual(rli.line, expectedLines[--callCount]); + assert.strictEqual(callCount, 0); + rli.close(); + }); + + // Regression test for repl freeze, #1968: + // check that nothing fails if 'keypress' event throws. + it("should not fail if keypress throws", () => { + const [rli, fi] = getInterface({ terminal: true }); + const keys = [] as string[]; + const err = new Error("bad thing happened"); + fi.on("keypress", (key: string) => { + keys.push(key); + if (key === "X") { + throw err; + } + }); + assert.throws( + () => fi.emit("data", "fooX"), + e => { + console.log("ERRROR!", e); + assert.strictEqual(e, err); + return true; + }, + ); + fi.emit("data", "bar"); + assert.strictEqual(keys.join(""), "fooXbar"); + rli.close(); + }); + + // History is bound + it("should bind history", () => { + const [rli, fi] = getInterface({ terminal: true, historySize: 2 }); + const lines = ["line 1", "line 2", "line 3"]; + fi.emit("data", lines.join("\n") + "\n"); + assert.strictEqual(rli.history.length, 2); + assert.strictEqual(rli.history[0], "line 3"); + assert.strictEqual(rli.history[1], "line 2"); + }); + + // Question + it("should handle question", () => { + const [rli] = getInterface({ terminal: true }); + const expectedLines = ["foo"]; + rli.question(expectedLines[0], () => rli.close()); + assertCursorRowsAndCols(rli, 0, expectedLines[0].length); + rli.close(); + }); + + // Sending a multi-line question + it("should handle multi-line questions", () => { + const [rli] = getInterface({ terminal: true }); + const expectedLines = ["foo", "bar"]; + rli.question(expectedLines.join("\n"), () => rli.close()); + assertCursorRowsAndCols(rli, expectedLines.length - 1, expectedLines.slice(-1)[0].length); + rli.close(); + }); + + it("should handle beginning and end of line", () => { + // Beginning and end of line + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + fi.emit("keypress", ".", { ctrl: true, name: "a" }); + assertCursorRowsAndCols(rli, 0, 0); + fi.emit("keypress", ".", { ctrl: true, name: "e" }); + assertCursorRowsAndCols(rli, 0, 19); + rli.close(); + }); + + it("should handle back and forward one character", () => { + // Back and Forward one character + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + assertCursorRowsAndCols(rli, 0, 19); + + // Back one character + fi.emit("keypress", ".", { ctrl: true, name: "b" }); + assertCursorRowsAndCols(rli, 0, 18); + // Back one character + fi.emit("keypress", ".", { ctrl: true, name: "b" }); + assertCursorRowsAndCols(rli, 0, 17); + // Forward one character + fi.emit("keypress", ".", { ctrl: true, name: "f" }); + assertCursorRowsAndCols(rli, 0, 18); + // Forward one character + fi.emit("keypress", ".", { ctrl: true, name: "f" }); + assertCursorRowsAndCols(rli, 0, 19); + rli.close(); + }); + + // Back and Forward one astral character + it("should handle going back and forward one astral character", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "💻"); + + // Move left one character/code point + fi.emit("keypress", ".", { name: "left" }); + assertCursorRowsAndCols(rli, 0, 0); + + // Move right one character/code point + fi.emit("keypress", ".", { name: "right" }); + assertCursorRowsAndCols(rli, 0, 2); + + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "💻"); + }), + ); + fi.emit("data", "\n"); + rli.close(); + }); + + // Two astral characters left + it("should handle two astral characters left", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "💻"); + + // Move left one character/code point + fi.emit("keypress", ".", { name: "left" }); + assertCursorRowsAndCols(rli, 0, 0); + + fi.emit("data", "🐕"); + assertCursorRowsAndCols(rli, 0, 2); + + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "🐕💻"); + }), + ); + fi.emit("data", "\n"); + rli.close(); + }); + + // Two astral characters right + it("should handle two astral characters right", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "💻"); + + // Move left one character/code point + fi.emit("keypress", ".", { name: "right" }); + assertCursorRowsAndCols(rli, 0, 2); + + fi.emit("data", "🐕"); + assertCursorRowsAndCols(rli, 0, 4); + + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "💻🐕"); + }), + ); + fi.emit("data", "\n"); + rli.close(); + }); + + it("should handle wordLeft and wordRight", () => { + // `wordLeft` and `wordRight` + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + fi.emit("keypress", ".", { ctrl: true, name: "left" }); + assertCursorRowsAndCols(rli, 0, 16); + fi.emit("keypress", ".", { meta: true, name: "b" }); + assertCursorRowsAndCols(rli, 0, 10); + fi.emit("keypress", ".", { ctrl: true, name: "right" }); + assertCursorRowsAndCols(rli, 0, 16); + fi.emit("keypress", ".", { meta: true, name: "f" }); + assertCursorRowsAndCols(rli, 0, 19); + rli.close(); + }); + + // `deleteWordLeft` + it("should handle deleteWordLeft", done => { + const { mustCall } = createCallCheckCtx(done); + [ + { ctrl: true, name: "w" }, + { ctrl: true, name: "backspace" }, + { meta: true, name: "backspace" }, + ].forEach(deleteWordLeftKey => { + let [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + fi.emit("keypress", ".", { ctrl: true, name: "left" }); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "the quick fox"); + }), + ); + fi.emit("keypress", ".", deleteWordLeftKey); + fi.emit("data", "\n"); + rli.close(); + + // No effect if pressed at beginning of line + [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + fi.emit("keypress", ".", { ctrl: true, name: "a" }); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "the quick brown fox"); + }), + ); + fi.emit("keypress", ".", deleteWordLeftKey); + fi.emit("data", "\n"); + rli.close(); + }); + }); + + // `deleteWordRight` + it("should handle deleteWordRight", done => { + const { mustCall } = createCallCheckCtx(done); + [ + { ctrl: true, name: "delete" }, + { meta: true, name: "delete" }, + { meta: true, name: "d" }, + ].forEach(deleteWordRightKey => { + let [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + fi.emit("keypress", ".", { ctrl: true, name: "left" }); + fi.emit("keypress", ".", { ctrl: true, name: "left" }); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "the quick fox"); + }), + ); + fi.emit("keypress", ".", deleteWordRightKey); + fi.emit("data", "\n"); + rli.close(); + + // No effect if pressed at end of line + [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "the quick brown fox"); + }), + ); + fi.emit("keypress", ".", deleteWordRightKey); + fi.emit("data", "\n"); + rli.close(); + }); + }); + + // deleteLeft + it("should handle deleteLeft", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + assertCursorRowsAndCols(rli, 0, 19); + + // Delete left character + fi.emit("keypress", ".", { ctrl: true, name: "h" }); + assertCursorRowsAndCols(rli, 0, 18); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "the quick brown fo"); + }), + ); + fi.emit("data", "\n"); + rli.close(); + }); + + // deleteLeft astral character + it("should handle deleteLeft astral character", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "💻"); + assertCursorRowsAndCols(rli, 0, 2); + // Delete left character + fi.emit("keypress", ".", { ctrl: true, name: "h" }); + assertCursorRowsAndCols(rli, 0, 0); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, ""); + }), + ); + fi.emit("data", "\n"); + rli.close(); + }); + + // deleteRight + it("should handle deleteRight", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + + // Go to the start of the line + fi.emit("keypress", ".", { ctrl: true, name: "a" }); + assertCursorRowsAndCols(rli, 0, 0); + + // Delete right character + fi.emit("keypress", ".", { ctrl: true, name: "d" }); + assertCursorRowsAndCols(rli, 0, 0); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "he quick brown fox"); + }), + ); + fi.emit("data", "\n"); + rli.close(); + }); + + // deleteRight astral character + it("should handle deleteRight of astral characters", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "💻"); + + // Go to the start of the line + fi.emit("keypress", ".", { ctrl: true, name: "a" }); + assertCursorRowsAndCols(rli, 0, 0); + + // Delete right character + fi.emit("keypress", ".", { ctrl: true, name: "d" }); + assertCursorRowsAndCols(rli, 0, 0); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, ""); + }), + ); + fi.emit("data", "\n"); + rli.close(); + }); + + // deleteLineLeft + it("should handle deleteLineLeft", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + assertCursorRowsAndCols(rli, 0, 19); + + // Delete from current to start of line + fi.emit("keypress", ".", { ctrl: true, shift: true, name: "backspace" }); + assertCursorRowsAndCols(rli, 0, 0); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, ""); + }), + ); + fi.emit("data", "\n"); + rli.close(); + }); + + // deleteLineRight + it("should handle deleteLineRight", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + + // Go to the start of the line + fi.emit("keypress", ".", { ctrl: true, name: "a" }); + assertCursorRowsAndCols(rli, 0, 0); + + // Delete from current to end of line + fi.emit("keypress", ".", { ctrl: true, shift: true, name: "delete" }); + assertCursorRowsAndCols(rli, 0, 0); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, ""); + }), + ); + fi.emit("data", "\n"); + rli.close(); + }); + + // yank + it("should handle yank", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + assertCursorRowsAndCols(rli, 0, 19); + + // Go to the start of the line + fi.emit("keypress", ".", { ctrl: true, name: "a" }); + // Move forward one char + fi.emit("keypress", ".", { ctrl: true, name: "f" }); + // Delete the right part + fi.emit("keypress", ".", { ctrl: true, shift: true, name: "delete" }); + assertCursorRowsAndCols(rli, 0, 1); + + // Yank + fi.emit("keypress", ".", { ctrl: true, name: "y" }); + assertCursorRowsAndCols(rli, 0, 19); + + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "the quick brown fox"); + }), + ); + + fi.emit("data", "\n"); + rli.close(); + }); + + // yank pop + it("should handle yank pop", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + assertCursorRowsAndCols(rli, 0, 19); + + // Go to the start of the line + fi.emit("keypress", ".", { ctrl: true, name: "a" }); + // Move forward one char + fi.emit("keypress", ".", { ctrl: true, name: "f" }); + // Delete the right part + fi.emit("keypress", ".", { ctrl: true, shift: true, name: "delete" }); + assertCursorRowsAndCols(rli, 0, 1); + // Yank + fi.emit("keypress", ".", { ctrl: true, name: "y" }); + assertCursorRowsAndCols(rli, 0, 19); + + // Go to the start of the line + fi.emit("keypress", ".", { ctrl: true, name: "a" }); + // Move forward four chars + fi.emit("keypress", ".", { ctrl: true, name: "f" }); + fi.emit("keypress", ".", { ctrl: true, name: "f" }); + fi.emit("keypress", ".", { ctrl: true, name: "f" }); + fi.emit("keypress", ".", { ctrl: true, name: "f" }); + // Delete the right part + fi.emit("keypress", ".", { ctrl: true, shift: true, name: "delete" }); + assertCursorRowsAndCols(rli, 0, 4); + // Go to the start of the line + fi.emit("keypress", ".", { ctrl: true, name: "a" }); + assertCursorRowsAndCols(rli, 0, 0); + + // Yank: 'quick brown fox|the ' + fi.emit("keypress", ".", { ctrl: true, name: "y" }); + // Yank pop: 'he quick brown fox|the' + fi.emit("keypress", ".", { meta: true, name: "y" }); + assertCursorRowsAndCols(rli, 0, 18); + + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "he quick brown foxthe "); + }), + ); + + fi.emit("data", "\n"); + rli.close(); + }); + + // Close readline interface + it("Should close readline interface", () => { + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("keypress", ".", { ctrl: true, name: "c" }); + assert.ok(rli.closed); + }); + + // Multi-line input cursor position + it("should handle multi-line input cursors", () => { + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.columns = 10; + fi.emit("data", "multi-line text"); + assertCursorRowsAndCols(rli, 1, 5); + rli.close(); + }); + + // Multi-line input cursor position and long tabs + it("should handle long tabs", () => { + const [rli, fi] = getInterface({ + tabSize: 16, + terminal: true, + prompt: "", + }); + fi.columns = 10; + fi.emit("data", "multi-line\ttext \t"); + assert.strictEqual(rli.cursor, 17); + assertCursorRowsAndCols(rli, 3, 2); + rli.close(); + }); + + // Check for the default tab size. + it("should use the default tab size", () => { + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick\tbrown\tfox"); + assert.strictEqual(rli.cursor, 19); + // The first tab is 7 spaces long, the second one 3 spaces. + assertCursorRowsAndCols(rli, 0, 27); + }); + + // Multi-line prompt cursor position + it("should handle multi-line prompt cursor position", () => { + const [rli, fi] = getInterface({ + terminal: true, + prompt: "\nfilledline\nwraping text\n> ", + }); + fi.columns = 10; + fi.emit("data", "t"); + assertCursorRowsAndCols(rli, 4, 3); + rli.close(); + }); + + // Undo & Redo + it("should undo and redo", () => { + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + fi.emit("data", "the quick brown fox"); + assertCursorRowsAndCols(rli, 0, 19); + + // Delete the last eight chars + fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); + fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); + fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); + fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); + fi.emit("keypress", ",", { ctrl: true, shift: false, name: "k" }); + + fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); + fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); + fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); + fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" }); + fi.emit("keypress", ",", { ctrl: true, shift: false, name: "k" }); + + assertCursorRowsAndCols(rli, 0, 11); + // Perform undo twice + fi.emit("keypress", ",", { sequence: "\x1F" }); + assert.strictEqual(rli.line, "the quick brown"); + fi.emit("keypress", ",", { sequence: "\x1F" }); + assert.strictEqual(rli.line, "the quick brown fox"); + // Perform redo twice + fi.emit("keypress", ",", { sequence: "\x1E" }); + assert.strictEqual(rli.line, "the quick brown"); + fi.emit("keypress", ",", { sequence: "\x1E" }); + assert.strictEqual(rli.line, "the quick b"); + fi.emit("data", "\n"); + rli.close(); + }); + + // Clear the whole screen + it("should clear the whole screen", done => { + const { mustCall } = createCallCheckCtx(done); + const [rli, fi] = getInterface({ terminal: true, prompt: "" }); + const lines = ["line 1", "line 2", "line 3"]; + fi.emit("data", lines.join("\n")); + fi.emit("keypress", ".", { ctrl: true, name: "l" }); + assertCursorRowsAndCols(rli, 0, 6); + rli.on( + "line", + mustCall(line => { + assert.strictEqual(line, "line 3"); + }), + ); + fi.emit("data", "\n"); + rli.close(); + }); + + it("should treat wide characters as two columns", () => { + assert.strictEqual(getStringWidth("a"), 1); + assert.strictEqual(getStringWidth("あ"), 2); + assert.strictEqual(getStringWidth("谢"), 2); + assert.strictEqual(getStringWidth("고"), 2); + assert.strictEqual(getStringWidth(String.fromCodePoint(0x1f251)), 2); + assert.strictEqual(getStringWidth("abcde"), 5); + assert.strictEqual(getStringWidth("古池や"), 6); + assert.strictEqual(getStringWidth("ノード.js"), 9); + assert.strictEqual(getStringWidth("你好"), 4); + assert.strictEqual(getStringWidth("안녕하세요"), 10); + assert.strictEqual(getStringWidth("A\ud83c\ude00BC"), 5); + assert.strictEqual(getStringWidth("👨‍👩‍👦‍👦"), 8); + assert.strictEqual(getStringWidth("🐕𐐷あ💻😀"), 9); + // TODO(BridgeAR): This should have a width of 4. + assert.strictEqual(getStringWidth("⓬⓪"), 2); + assert.strictEqual(getStringWidth("\u0301\u200D\u200E"), 0); + }); + + // // Check if vt control chars are stripped + // assert.strictEqual(stripVTControlCharacters('\u001b[31m> \u001b[39m'), '> '); + // assert.strictEqual( + // stripVTControlCharacters('\u001b[31m> \u001b[39m> '), + // '> > ' + // ); + // assert.strictEqual(stripVTControlCharacters('\u001b[31m\u001b[39m'), ''); + // assert.strictEqual(stripVTControlCharacters('> '), '> '); + // assert.strictEqual(getStringWidth('\u001b[31m> \u001b[39m'), 2); + // assert.strictEqual(getStringWidth('\u001b[31m> \u001b[39m> '), 4); + // assert.strictEqual(getStringWidth('\u001b[31m\u001b[39m'), 0); + // assert.strictEqual(getStringWidth('> '), 2); + + // // Check EventEmitter memory leak + // for (let i = 0; i < 12; i++) { + // const rl = readline.createInterface({ + // input: process.stdin, + // output: process.stdout + // }); + // rl.close(); + // assert.strictEqual(isWarned(process.stdin._events), false); + // assert.strictEqual(isWarned(process.stdout._events), false); + // } + + // [true, false].forEach((terminal) => { + // // Disable history + // { + // const [rli, fi] = getInterface({ terminal, historySize: 0 }); + // assert.strictEqual(rli.historySize, 0); + + // fi.emit('data', 'asdf\n'); + // assert.deepStrictEqual(rli.history, []); + // rli.close(); + // } + + // // Default history size 30 + // { + // const [rli, fi] = getInterface({ terminal }); + // assert.strictEqual(rli.historySize, 30); + + // fi.emit('data', 'asdf\n'); + // assert.deepStrictEqual(rli.history, terminal ? ['asdf'] : []); + // rli.close(); + // } + + // // Sending a full line + // { + // const [rli, fi] = getInterface({ terminal }); + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, 'asdf'); + // })); + // fi.emit('data', 'asdf\n'); + // } + + // // Sending a blank line + // { + // const [rli, fi] = getInterface({ terminal }); + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, ''); + // })); + // fi.emit('data', '\n'); + // } + + // // Sending a single character with no newline and then a newline + // { + // const [rli, fi] = getInterface({ terminal }); + // let called = false; + // rli.on('line', (line) => { + // called = true; + // assert.strictEqual(line, 'a'); + // }); + // fi.emit('data', 'a'); + // assert.ok(!called); + // fi.emit('data', '\n'); + // assert.ok(called); + // rli.close(); + // } + + // // Sending multiple newlines at once + // { + // const [rli, fi] = getInterface({ terminal }); + // const expectedLines = ['foo', 'bar', 'baz']; + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, expectedLines.shift()); + // }, expectedLines.length)); + // fi.emit('data', `${expectedLines.join('\n')}\n`); + // rli.close(); + // } + + // // Sending multiple newlines at once that does not end with a new line + // { + // const [rli, fi] = getInterface({ terminal }); + // const expectedLines = ['foo', 'bar', 'baz', 'bat']; + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, expectedLines.shift()); + // }, expectedLines.length - 1)); + // fi.emit('data', expectedLines.join('\n')); + // rli.close(); + // } + + // // Sending multiple newlines at once that does not end with a new(empty) + // // line and a `end` event + // { + // const [rli, fi] = getInterface({ terminal }); + // const expectedLines = ['foo', 'bar', 'baz', '']; + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, expectedLines.shift()); + // }, expectedLines.length - 1)); + // rli.on('close', mustCall()); + // fi.emit('data', expectedLines.join('\n')); + // fi.emit('end'); + // rli.close(); + // } + + // // Sending a multi-byte utf8 char over multiple writes + // { + // const buf = Buffer.from('☮', 'utf8'); + // const [rli, fi] = getInterface({ terminal }); + // let callCount = 0; + // rli.on('line', (line) => { + // callCount++; + // assert.strictEqual(line, buf.toString('utf8')); + // }); + // for (const i of buf) { + // fi.emit('data', Buffer.from([i])); + // } + // assert.strictEqual(callCount, 0); + // fi.emit('data', '\n'); + // assert.strictEqual(callCount, 1); + // rli.close(); + // } + + // // Calling readline without `new` + // { + // const [rli, fi] = getInterface({ terminal }); + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, 'asdf'); + // })); + // fi.emit('data', 'asdf\n'); + // rli.close(); + // } + + // // Calling the question callback + // { + // const [rli] = getInterface({ terminal }); + // rli.question('foo?', mustCall((answer) => { + // assert.strictEqual(answer, 'bar'); + // })); + // rli.write('bar\n'); + // rli.close(); + // } + + // // Calling the question callback with abort signal + // { + // const [rli] = getInterface({ terminal }); + // const { signal } = new AbortController(); + // rli.question('foo?', { signal }, mustCall((answer) => { + // assert.strictEqual(answer, 'bar'); + // })); + // rli.write('bar\n'); + // rli.close(); + // } + + // // Calling the question multiple times + // { + // const [rli] = getInterface({ terminal }); + // rli.question('foo?', mustCall((answer) => { + // assert.strictEqual(answer, 'baz'); + // })); + // rli.question('bar?', mustNotCall(() => { + // })); + // rli.write('baz\n'); + // rli.close(); + // } + + // // Calling the promisified question + // { + // const [rli] = getInterface({ terminal }); + // const question = util.promisify(rli.question).bind(rli); + // question('foo?') + // .then(mustCall((answer) => { + // assert.strictEqual(answer, 'bar'); + // })); + // rli.write('bar\n'); + // rli.close(); + // } + + // // Calling the promisified question with abort signal + // { + // const [rli] = getInterface({ terminal }); + // const question = util.promisify(rli.question).bind(rli); + // const { signal } = new AbortController(); + // question('foo?', { signal }) + // .then(mustCall((answer) => { + // assert.strictEqual(answer, 'bar'); + // })); + // rli.write('bar\n'); + // rli.close(); + // } + + // // Aborting a question + // { + // const ac = new AbortController(); + // const signal = ac.signal; + // const [rli] = getInterface({ terminal }); + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, 'bar'); + // })); + // rli.question('hello?', { signal }, mustNotCall()); + // ac.abort(); + // rli.write('bar\n'); + // rli.close(); + // } + + // // Aborting a promisified question + // { + // const ac = new AbortController(); + // const signal = ac.signal; + // const [rli] = getInterface({ terminal }); + // const question = util.promisify(rli.question).bind(rli); + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, 'bar'); + // })); + // question('hello?', { signal }) + // .then(mustNotCall()) + // .catch(mustCall((error) => { + // assert.strictEqual(error.name, 'AbortError'); + // })); + // ac.abort(); + // rli.write('bar\n'); + // rli.close(); + // } + + // // pre-aborted signal + // { + // const signal = AbortSignal.abort(); + // const [rli] = getInterface({ terminal }); + // rli.pause(); + // rli.on('resume', mustNotCall()); + // rli.question('hello?', { signal }, mustNotCall()); + // rli.close(); + // } + + // // pre-aborted signal promisified question + // { + // const signal = AbortSignal.abort(); + // const [rli] = getInterface({ terminal }); + // const question = util.promisify(rli.question).bind(rli); + // rli.on('resume', mustNotCall()); + // rli.pause(); + // question('hello?', { signal }) + // .then(mustNotCall()) + // .catch(mustCall((error) => { + // assert.strictEqual(error.name, 'AbortError'); + // })); + // rli.close(); + // } + + // // Call question after close + // { + // const [rli, fi] = getInterface({ terminal }); + // rli.question('What\'s your name?', mustCall((name) => { + // assert.strictEqual(name, 'Node.js'); + // rli.close(); + // assert.throws(() => { + // rli.question('How are you?', mustNotCall()); + // }, { + // name: 'Error', + // code: 'ERR_USE_AFTER_CLOSE' + // }); + // assert.notStrictEqual(rli.getPrompt(), 'How are you?'); + // })); + // fi.emit('data', 'Node.js\n'); + // } + + // // Call promisified question after close + // { + // const [rli, fi] = getInterface({ terminal }); + // const question = util.promisify(rli.question).bind(rli); + // question('What\'s your name?').then(mustCall((name) => { + // assert.strictEqual(name, 'Node.js'); + // rli.close(); + // question('How are you?') + // .then(mustNotCall(), expectsError({ + // code: 'ERR_USE_AFTER_CLOSE', + // name: 'Error' + // })); + // assert.notStrictEqual(rli.getPrompt(), 'How are you?'); + // })); + // fi.emit('data', 'Node.js\n'); + // } + + // // Can create a new readline Interface with a null output argument + // { + // const [rli, fi] = getInterface({ output: null, terminal }); + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, 'asdf'); + // })); + // fi.emit('data', 'asdf\n'); + + // rli.setPrompt('ddd> '); + // rli.prompt(); + // rli.write("really shouldn't be seeing this"); + // rli.question('What do you think of node.js? ', (answer) => { + // console.log('Thank you for your valuable feedback:', answer); + // rli.close(); + // }); + // } + + // // Calling the getPrompt method + // { + // const expectedPrompts = ['$ ', '> ']; + // const [rli] = getInterface({ terminal }); + // for (const prompt of expectedPrompts) { + // rli.setPrompt(prompt); + // assert.strictEqual(rli.getPrompt(), prompt); + // } + // } + + // { + // const expected = terminal ? + // ['\u001b[1G', '\u001b[0J', '$ ', '\u001b[3G'] : + // ['$ ']; + + // const output = new Writable({ + // write: mustCall((chunk, enc, cb) => { + // assert.strictEqual(chunk.toString(), expected.shift()); + // cb(); + // rl.close(); + // }, expected.length) + // }); + + // const rl = readline.createInterface({ + // input: new Readable({ read: mustCall() }), + // output, + // prompt: '$ ', + // terminal + // }); + + // rl.prompt(); + + // assert.strictEqual(rl.getPrompt(), '$ '); + // } + + // { + // const fi = new FakeInput(); + // assert.deepStrictEqual(fi.listeners(terminal ? 'keypress' : 'data'), []); + // } + + // // Emit two line events when the delay + // // between \r and \n exceeds crlfDelay + // { + // const crlfDelay = 200; + // const [rli, fi] = getInterface({ terminal, crlfDelay }); + // let callCount = 0; + // rli.on('line', () => { + // callCount++; + // }); + // fi.emit('data', '\r'); + // setTimeout(mustCall(() => { + // fi.emit('data', '\n'); + // assert.strictEqual(callCount, 2); + // rli.close(); + // }), crlfDelay + 10); + // } + + // // For the purposes of the following tests, we do not care about the exact + // // value of crlfDelay, only that the behaviour conforms to what's expected. + // // Setting it to Infinity allows the test to succeed even under extreme + // // CPU stress. + // const crlfDelay = Infinity; + + // // Set crlfDelay to `Infinity` is allowed + // { + // const delay = 200; + // const [rli, fi] = getInterface({ terminal, crlfDelay }); + // let callCount = 0; + // rli.on('line', () => { + // callCount++; + // }); + // fi.emit('data', '\r'); + // setTimeout(mustCall(() => { + // fi.emit('data', '\n'); + // assert.strictEqual(callCount, 1); + // rli.close(); + // }), delay); + // } + + // // Sending multiple newlines at once that does not end with a new line + // // and a `end` event(last line is) + + // // \r\n should emit one line event, not two + // { + // const [rli, fi] = getInterface({ terminal, crlfDelay }); + // const expectedLines = ['foo', 'bar', 'baz', 'bat']; + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, expectedLines.shift()); + // }, expectedLines.length - 1)); + // fi.emit('data', expectedLines.join('\r\n')); + // rli.close(); + // } + + // // \r\n should emit one line event when split across multiple writes. + // { + // const [rli, fi] = getInterface({ terminal, crlfDelay }); + // const expectedLines = ['foo', 'bar', 'baz', 'bat']; + // let callCount = 0; + // rli.on('line', mustCall((line) => { + // assert.strictEqual(line, expectedLines[callCount]); + // callCount++; + // }, expectedLines.length)); + // expectedLines.forEach((line) => { + // fi.emit('data', `${line}\r`); + // fi.emit('data', '\n'); + // }); + // rli.close(); + // } + + // // Emit one line event when the delay between \r and \n is + // // over the default crlfDelay but within the setting value. + // { + // const delay = 125; + // const [rli, fi] = getInterface({ terminal, crlfDelay }); + // let callCount = 0; + // rli.on('line', () => callCount++); + // fi.emit('data', '\r'); + // setTimeout(mustCall(() => { + // fi.emit('data', '\n'); + // assert.strictEqual(callCount, 1); + // rli.close(); + // }), delay); + // } + // }); + + // // Ensure that the _wordLeft method works even for large input + // { + // const input = new Readable({ + // read() { + // this.push('\x1B[1;5D'); // CTRL + Left + // this.push(null); + // }, + // }); + // const output = new Writable({ + // write: mustCall((data, encoding, cb) => { + // assert.strictEqual(rl.cursor, rl.line.length - 1); + // cb(); + // }), + // }); + // const rl = new readline.createInterface({ + // input, + // output, + // terminal: true, + // }); + // rl.line = `a${' '.repeat(1e6)}a`; + // rl.cursor = rl.line.length; + // } + + // { + // const fi = new FakeInput(); + // const signal = AbortSignal.abort(); + + // const rl = readline.createInterface({ + // input: fi, + // output: fi, + // signal, + // }); + // rl.on('close', mustCall()); + // assert.strictEqual(getEventListeners(signal, 'abort').length, 0); + // } + + // { + // const fi = new FakeInput(); + // const ac = new AbortController(); + // const { signal } = ac; + // const rl = readline.createInterface({ + // input: fi, + // output: fi, + // signal, + // }); + // assert.strictEqual(getEventListeners(signal, 'abort').length, 1); + // rl.on('close', mustCall()); + // ac.abort(); + // assert.strictEqual(getEventListeners(signal, 'abort').length, 0); + // } + + // { + // const fi = new FakeInput(); + // const ac = new AbortController(); + // const { signal } = ac; + // const rl = readline.createInterface({ + // input: fi, + // output: fi, + // signal, + // }); + // assert.strictEqual(getEventListeners(signal, "abort").length, 1); + // rl.close(); + // assert.strictEqual(getEventListeners(signal, "abort").length, 0); + // } + + // { + // // Constructor throws if signal is not an abort signal + // assert.throws(() => { + // readline.createInterface({ + // input: new FakeInput(), + // signal: {}, + // }); + // }, { + // name: 'TypeError', + // code: 'ERR_INVALID_ARG_TYPE' + // }); + // } +}); + +describe("readline.createInterface()", () => { + it("should emit line when input ends line", done => { + const createDone = createDoneDotAll(done); + const lineDone = createDone(2000); + const { mustCall } = createCallCheckCtx(createDone(2000)); + const input = new PassThrough(); + const rl = readline.createInterface({ + terminal: true, + input: input, + }); + + rl.on( + "line", + mustCall(data => { + assert.strictEqual(data, "abc"); + lineDone(); + }), + ); + + input.end("abc"); + }); + + it("should not emit line when input ends without newline", done => { + const { mustNotCall } = createCallCheckCtx(done); + + const input = new PassThrough(); + const rl = readline.createInterface({ + terminal: true, + input: input, + }); + + rl.on("line", mustNotCall("must not be called before newline")); + input.write("abc"); + }); + + it("should read line by line", done => { + const createDone = createDoneDotAll(done); + const { mustCall } = createCallCheckCtx(createDone(3000)); + const lineDone = createDone(2000); + const input = new PassThrough(); + const rl = readline.createInterface({ + terminal: true, + input: input, + }); + + rl.on( + "line", + mustCall(data => { + assert.strictEqual(data, "abc"); + lineDone(); + }), + ); + + input.write("abc\n"); + }); + + it("should respond to home and end sequences for common pttys ", () => { + const input = new PassThrough(); + const rl = readline.createInterface({ + terminal: true, + input: input, + }); + + rl.write("foo"); + assert.strictEqual(rl.cursor, 3); + + const key = { + xterm: { + home: ["\x1b[H", { ctrl: true, name: "a" }], + end: ["\x1b[F", { ctrl: true, name: "e" }], + }, + gnome: { + home: ["\x1bOH", { ctrl: true, name: "a" }], + end: ["\x1bOF", { ctrl: true, name: "e" }], + }, + rxvt: { + home: ["\x1b[7", { ctrl: true, name: "a" }], + end: ["\x1b[8", { ctrl: true, name: "e" }], + }, + putty: { + home: ["\x1b[1~", { ctrl: true, name: "a" }], + end: ["\x1b[>~", { ctrl: true, name: "e" }], + }, + }; + + [key.xterm, key.gnome, key.rxvt, key.putty].forEach(key => { + rl.write.apply(rl, key.home); + assert.strictEqual(rl.cursor, 0); + rl.write.apply(rl, key.end); + assert.strictEqual(rl.cursor, 3); + }); + }); + + it("should allow for cursor movement with meta-f and meta-b", () => { + const input = new PassThrough(); + const rl = readline.createInterface({ + terminal: true, + input: input, + }); + + const key = { + xterm: { + home: ["\x1b[H", { ctrl: true, name: "a" }], + metab: ["\x1bb", { meta: true, name: "b" }], + metaf: ["\x1bf", { meta: true, name: "f" }], + }, + }; + + rl.write("foo bar.hop/zoo"); + rl.write.apply(rl, key.xterm.home); + [ + { cursor: 4, key: key.xterm.metaf }, + { cursor: 7, key: key.xterm.metaf }, + { cursor: 8, key: key.xterm.metaf }, + { cursor: 11, key: key.xterm.metaf }, + { cursor: 12, key: key.xterm.metaf }, + { cursor: 15, key: key.xterm.metaf }, + { cursor: 12, key: key.xterm.metab }, + { cursor: 11, key: key.xterm.metab }, + { cursor: 8, key: key.xterm.metab }, + { cursor: 7, key: key.xterm.metab }, + { cursor: 4, key: key.xterm.metab }, + { cursor: 0, key: key.xterm.metab }, + ].forEach(function (action) { + rl.write.apply(rl, action.key); + assert.strictEqual(rl.cursor, action.cursor); + }); + }); + + it("should properly allow for cursor movement with meta-d", () => { + const input = new PassThrough(); + const rl = readline.createInterface({ + terminal: true, + input: input, + }); + + const key = { + xterm: { + home: ["\x1b[H", { ctrl: true, name: "a" }], + metad: ["\x1bd", { meta: true, name: "d" }], + }, + }; + + rl.write("foo bar.hop/zoo"); + rl.write.apply(rl, key.xterm.home); + ["bar.hop/zoo", ".hop/zoo", "hop/zoo", "/zoo", "zoo", ""].forEach(function (expectedLine) { + rl.write.apply(rl, key.xterm.metad); + assert.strictEqual(rl.cursor, 0); + assert.strictEqual(rl.line, expectedLine); + }); + }); + + // TODO: Actual pseudo-tty test + // it("should operate correctly when process.env.DUMB is set", () => { + // process.env.TERM = "dumb"; + // const rl = readline.createInterface({ + // input: process.stdin, + // output: process.stdout, + // }); + // rl.write("text"); + // rl.write(null, { ctrl: true, name: "u" }); + // rl.write(null, { name: "return" }); + // rl.write("text"); + // rl.write(null, { name: "backspace" }); + // rl.write(null, { name: "escape" }); + // rl.write(null, { name: "enter" }); + // rl.write("text"); + // rl.write(null, { ctrl: true, name: "c" }); + // }); +}); diff --git a/test/js/node/readline/readline_promises.node.test.ts b/test/js/node/readline/readline_promises.node.test.ts new file mode 100644 index 000000000..c75d254ca --- /dev/null +++ b/test/js/node/readline/readline_promises.node.test.ts @@ -0,0 +1,51 @@ +import { describe, it } from "bun:test"; +import readlinePromises from "node:readline/promises"; +import { EventEmitter } from "node:events"; +import { createDoneDotAll, createCallCheckCtx, assert } from "node-harness"; + +// ---------------------------------------------------------------------------- +// Helpers +// ---------------------------------------------------------------------------- + +class FakeInput extends EventEmitter { + output = ""; + resume() {} + pause() {} + write(data) { + this.output += data; + } + end() {} + reset() { + this.output = ""; + } +} + +// ---------------------------------------------------------------------------- +// Tests +// ---------------------------------------------------------------------------- + +describe("readline/promises.createInterface()", () => { + it("should throw an error when failed completion", done => { + const createDone = createDoneDotAll(done); + const { mustCall, mustNotCall } = createCallCheckCtx(createDone()); + + const fi = new FakeInput(); + const rli = new readlinePromises.Interface({ + input: fi, + output: fi, + terminal: true, + completer: mustCall(() => Promise.reject(new Error("message"))), + }); + + rli.on("line", mustNotCall()); + fi.emit("data", "\t"); + const outCheckDone = createDone(); + process.nextTick(() => { + console.log("output", fi.output); + assert.match(fi.output, /^Tab completion error/); + fi.reset(); + outCheckDone(); + }); + rli.close(); + }); +}); diff --git a/test/js/node/stream/bufferlist.test.ts b/test/js/node/stream/bufferlist.test.ts new file mode 100644 index 000000000..b8a5443ea --- /dev/null +++ b/test/js/node/stream/bufferlist.test.ts @@ -0,0 +1,221 @@ +import { Readable } from "stream"; +import { it, expect } from "bun:test"; + +function makeUint8Array(str) { + return new Uint8Array( + [].map.call(str, function (ch) { + return ch.charCodeAt(0); + }), + ); +} + +it("should work with .clear()", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.push({})).toBeUndefined(); + expect(list.length).toBe(1); + expect(list.push({})).toBeUndefined(); + expect(list.length).toBe(2); + expect(list.clear()).toBeUndefined(); + expect(list.length).toBe(0); +}); + +it("should work with .concat()", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.push(makeUint8Array("foo"))).toBeUndefined(); + expect(list.length).toBe(1); + expect(list.concat(3)).toEqual(new Uint8Array([102, 111, 111])); + expect(list.push(makeUint8Array("bar"))).toBeUndefined(); + expect(list.length).toBe(2); + expect(list.concat(10)).toEqual(new Uint8Array([102, 111, 111, 98, 97, 114, 0, 0, 0, 0])); +}); + +it("should fail on .concat() with invalid items", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.push("foo")).toBeUndefined(); + expect(() => { + list.concat(42); + }).toThrow(TypeError); +}); + +it("should fail on .concat() buffer overflow", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.push(makeUint8Array("foo"))).toBeUndefined(); + expect(list.length).toBe(1); + expect(() => { + list.concat(2); + }).toThrow(RangeError); + expect(list.push(makeUint8Array("bar"))).toBeUndefined(); + expect(list.length).toBe(2); + expect(() => { + list.concat(5); + }).toThrow(RangeError); +}); + +it("should work with .consume() on strings", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.consume(42, true)).toBe(""); + expect(list.push("foo")).toBeUndefined(); + expect(list.push("bar")).toBeUndefined(); + expect(list.push("baz")).toBeUndefined(); + expect(list.push("moo")).toBeUndefined(); + expect(list.push("moz")).toBeUndefined(); + expect(list.length).toBe(5); + expect(list.consume(3, true)).toBe("foo"); + expect(list.length).toBe(4); + expect(list.consume(4, true)).toBe("barb"); + expect(list.length).toBe(3); + expect(list.consume(256, true)).toBe("azmoomoz"); + expect(list.length).toBe(0); +}); + +it("should work with .consume() on buffers", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.consume(42, false)).toEqual(new Uint8Array()); + expect(list.push(makeUint8Array("foo"))).toBeUndefined(); + expect(list.push(makeUint8Array("bar"))).toBeUndefined(); + expect(list.push(makeUint8Array("baz"))).toBeUndefined(); + expect(list.push(makeUint8Array("moo"))).toBeUndefined(); + expect(list.push(makeUint8Array("moz"))).toBeUndefined(); + expect(list.length).toBe(5); + expect(list.consume(3, false)).toEqual(makeUint8Array("foo")); + expect(list.length).toBe(4); + expect(list.consume(2, false)).toEqual(makeUint8Array("ba")); + expect(list.length).toBe(4); + expect(list.consume(4, false)).toEqual(makeUint8Array("rbaz")); + expect(list.length).toBe(2); + expect(list.consume(10, false)).toEqual(new Uint8Array([109, 111, 111, 109, 111, 122, 0, 0, 0, 0])); + expect(list.length).toBe(0); +}); + +it("should fail on .consume() with invalid items", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.push("foo")).toBeUndefined(); + expect(list.length).toBe(1); + expect(list.consume(0, false)).toEqual(new Uint8Array([])); + expect(() => { + list.consume(1, false); + }).toThrow(TypeError); + expect(list.consume(3, true)).toBe("foo"); + expect(list.length).toBe(0); + expect(list.push(makeUint8Array("bar"))).toBeUndefined(); + expect(list.length).toBe(1); + expect(list.consume(0, true)).toEqual(""); + expect(() => { + list.consume(1, true); + }).toThrow(TypeError); + expect(list.consume(3, false)).toEqual(new Uint8Array([98, 97, 114])); +}); + +it("should work with .first()", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.first()).toBeUndefined(); + const item = {}; + expect(list.push(item)).toBeUndefined(); + expect(list.length).toBe(1); + expect(list.first()).toBe(item); +}); + +it("should work with .join()", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.push(42)).toBeUndefined(); + expect(list.push(null)).toBeUndefined(); + expect(list.push("foo")).toBeUndefined(); + expect(list.push(makeUint8Array("bar"))).toBeUndefined(); + expect(list.length).toBe(4); + expect(list.join("")).toBe("42nullfoo98,97,114"); + expect(list.join(",")).toBe("42,null,foo,98,97,114"); + expect(list.join(" baz ")).toBe("42 baz null baz foo baz 98,97,114"); +}); + +it("should work with .push()", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + const item1 = {}; + expect(list.push(item1)).toBeUndefined(); + expect(list.length).toBe(1); + expect(list.first()).toBe(item1); + const item2 = {}; + expect(list.push(item2)).toBeUndefined(); + expect(list.length).toBe(2); + expect(list.shift()).toBe(item1); + expect(list.shift()).toBe(item2); + expect(list.shift()).toBeUndefined(); +}); + +it("should work with .shift()", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.shift()).toBeUndefined(); + const item = {}; + expect(list.push(item)).toBeUndefined(); + expect(list.length).toBe(1); + expect(list.shift()).toBe(item); + expect(list.shift()).toBeUndefined(); +}); + +it("should work with .unshift()", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + const item1 = {}; + expect(list.unshift(item1)).toBeUndefined(); + expect(list.length).toBe(1); + expect(list.first()).toBe(item1); + const item2 = {}; + expect(list.push(item2)).toBeUndefined(); + expect(list.length).toBe(2); + expect(list.first()).toBe(item1); + const item3 = {}; + expect(list.unshift(item3)).toBeUndefined(); + expect(list.length).toBe(3); + expect(list.shift()).toBe(item3); + expect(list.shift()).toBe(item1); + expect(list.shift()).toBe(item2); + expect(list.shift()).toBeUndefined(); +}); + +it("should work with partial .consume() followed by .first()", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.push("foo")).toBeUndefined(); + expect(list.push("bar")).toBeUndefined(); + expect(list.length).toBe(2); + expect(list.consume(4, true)).toEqual("foob"); + expect(list.length).toBe(1); + expect(list.first()).toEqual("ar"); + expect(list.length).toBe(1); +}); + +it("should work with partial .consume() followed by .shift()", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.push(makeUint8Array("foo"))).toBeUndefined(); + expect(list.push(makeUint8Array("bar"))).toBeUndefined(); + expect(list.length).toBe(2); + expect(list.consume(4, false)).toEqual(makeUint8Array("foob")); + expect(list.length).toBe(1); + expect(list.shift()).toEqual(makeUint8Array("ar")); + expect(list.length).toBe(0); +}); + +it("should work with partial .consume() followed by .unshift()", () => { + const list = new Readable().readableBuffer; + expect(list.length).toBe(0); + expect(list.push(makeUint8Array("😋😋😋"))).toBeUndefined(); + expect(list.push(makeUint8Array("📋📋📋"))).toBeUndefined(); + expect(list.length).toBe(2); + expect(list.consume(7, false)).toEqual(new Uint8Array([61, 11, 61, 11, 61, 11, 61])); + expect(list.length).toBe(1); + expect(list.unshift(makeUint8Array("👌👌👌"))).toBeUndefined(); + expect(list.length).toBe(2); + expect(list.consume(12, false)).toEqual(new Uint8Array([61, 76, 61, 76, 61, 76, 203, 61, 203, 61, 203, 0])); + expect(list.length).toBe(0); +}); diff --git a/test/js/node/stream/node-stream-uint8array.test.ts b/test/js/node/stream/node-stream-uint8array.test.ts new file mode 100644 index 000000000..ec2e95d34 --- /dev/null +++ b/test/js/node/stream/node-stream-uint8array.test.ts @@ -0,0 +1,111 @@ +import { beforeEach, describe, expect, it } from "bun:test"; +import { Readable, Writable } from "stream"; + +const ABC = new Uint8Array([0x41, 0x42, 0x43]); +const DEF = new Uint8Array([0x44, 0x45, 0x46]); +const GHI = new Uint8Array([0x47, 0x48, 0x49]); + +describe("Writable", () => { + let called; + + function logCall(fn, id) { + return function () { + called[id] = (called[id] || 0) + 1; + return fn.apply(this, arguments); + }; + } + + beforeEach(() => { + called = []; + }); + + it("should perform simple operations", () => { + let n = 0; + const writable = new Writable({ + write: logCall((chunk, encoding, cb) => { + expect(chunk instanceof Buffer).toBe(true); + if (n++ === 0) { + expect(String(chunk)).toBe("ABC"); + } else { + expect(String(chunk)).toBe("DEF"); + } + + cb(); + }, 0), + }); + + writable.write(ABC); + writable.end(DEF); + expect(called).toEqual([2]); + }); + + it("should pass in Uint8Array in object mode", () => { + const writable = new Writable({ + objectMode: true, + write: logCall((chunk, encoding, cb) => { + expect(chunk instanceof Buffer).toBe(false); + expect(chunk instanceof Uint8Array).toBe(true); + expect(chunk).toStrictEqual(ABC); + expect(encoding).toBe("utf8"); + cb(); + }, 0), + }); + + writable.end(ABC); + expect(called).toEqual([1]); + }); + + it("should handle multiple writes carried out via writev()", () => { + let callback; + + const writable = new Writable({ + write: logCall((chunk, encoding, cb) => { + expect(chunk instanceof Buffer).toBe(true); + expect(encoding).toBe("buffer"); + expect(String(chunk)).toBe("ABC"); + callback = cb; + }, 0), + writev: logCall((chunks, cb) => { + expect(chunks.length).toBe(2); + expect(chunks[0].encoding).toBe("buffer"); + expect(chunks[1].encoding).toBe("buffer"); + expect(chunks[0].chunk + chunks[1].chunk).toBe("DEFGHI"); + }, 1), + }); + + writable.write(ABC); + writable.write(DEF); + writable.end(GHI); + callback(); + expect(called).toEqual([1, 1]); + }); +}); + +describe("Readable", () => { + it("should perform simple operations", () => { + const readable = new Readable({ + read() {}, + }); + + readable.push(DEF); + readable.unshift(ABC); + + const buf = readable.read(); + expect(buf instanceof Buffer).toBe(true); + expect([...buf]).toEqual([...ABC, ...DEF]); + }); + + it("should work with setEncoding()", () => { + const readable = new Readable({ + read() {}, + }); + + readable.setEncoding("utf8"); + + readable.push(DEF); + readable.unshift(ABC); + + const out = readable.read(); + expect(out).toBe("ABCDEF"); + }); +}); diff --git a/test/js/node/stream/node-stream.test.js b/test/js/node/stream/node-stream.test.js new file mode 100644 index 000000000..6bff28b94 --- /dev/null +++ b/test/js/node/stream/node-stream.test.js @@ -0,0 +1,86 @@ +import { expect, describe, it } from "bun:test"; +import { Readable, Writable, Duplex, Transform, PassThrough } from "node:stream"; + +describe("Readable", () => { + it("should be able to be created without _construct method defined", done => { + const readable = new Readable({ + read() { + this.push("Hello World!\n"); + this.push(null); + }, + }); + expect(readable instanceof Readable).toBe(true); + let data = ""; + readable.on("data", chunk => { + data += chunk.toString(); + }); + readable.on("end", () => { + expect(data).toBe("Hello World!\n"); + done(); + }); + }); + + it("should be able to be piped via .pipe", done => { + const readable = new Readable({ + read() { + this.push("Hello World!"); + this.push(null); + }, + }); + + const writable = new Writable({ + write(chunk, encoding, callback) { + expect(chunk.toString()).toBe("Hello World!"); + callback(); + done(); + }, + }); + + readable.pipe(writable); + }); +}); + +describe("Duplex", () => { + it("should allow subclasses to be derived via .call() on class", () => { + function Subclass(opts) { + if (!(this instanceof Subclass)) return new Subclass(opts); + Duplex.call(this, opts); + } + + Object.setPrototypeOf(Subclass.prototype, Duplex.prototype); + Object.setPrototypeOf(Subclass, Duplex); + + const subclass = new Subclass(); + expect(subclass instanceof Duplex).toBe(true); + }); +}); + +describe("Transform", () => { + it("should allow subclasses to be derived via .call() on class", () => { + function Subclass(opts) { + if (!(this instanceof Subclass)) return new Subclass(opts); + Transform.call(this, opts); + } + + Object.setPrototypeOf(Subclass.prototype, Transform.prototype); + Object.setPrototypeOf(Subclass, Transform); + + const subclass = new Subclass(); + expect(subclass instanceof Transform).toBe(true); + }); +}); + +describe("PassThrough", () => { + it("should allow subclasses to be derived via .call() on class", () => { + function Subclass(opts) { + if (!(this instanceof Subclass)) return new Subclass(opts); + PassThrough.call(this, opts); + } + + Object.setPrototypeOf(Subclass.prototype, PassThrough.prototype); + Object.setPrototypeOf(Subclass, PassThrough); + + const subclass = new Subclass(); + expect(subclass instanceof PassThrough).toBe(true); + }); +}); diff --git a/test/js/node/string_decoder/string-decoder.test.js b/test/js/node/string_decoder/string-decoder.test.js new file mode 100644 index 000000000..f37326678 --- /dev/null +++ b/test/js/node/string_decoder/string-decoder.test.js @@ -0,0 +1,243 @@ +import { describe, expect, it } from "bun:test"; +import { withoutAggressiveGC } from "harness"; + +const RealStringDecoder = require("string_decoder").StringDecoder; + +it("require('string_decoder')", async () => { + expect((await import("string_decoder")).StringDecoder).toBe(RealStringDecoder); +}); + +it("Bun.inspect(StringDecoder)", async () => { + expect((await Bun.inspect(RealStringDecoder).length) > 0).toBe(true); +}); + +function FakeStringDecoderCall() { + RealStringDecoder.apply(this, arguments); +} +require("util").inherits(FakeStringDecoderCall, RealStringDecoder); + +// extending StringDecoder is not supported +for (const StringDecoder of [FakeStringDecoderCall, RealStringDecoder]) { + describe(StringDecoder.name, () => { + it("StringDecoder-utf8", () => { + test("utf-8", Buffer.from("$", "utf-8"), "$"); + test("utf-8", Buffer.from("¢", "utf-8"), "¢"); + test("utf-8", Buffer.from("€", "utf-8"), "€"); + test("utf-8", Buffer.from("𤭢", "utf-8"), "𤭢"); + // A mixed ascii and non-ascii string + // Test stolen from deps/v8/test/cctest/test-strings.cc + // U+02E4 -> CB A4 + // U+0064 -> 64 + // U+12E4 -> E1 8B A4 + // U+0030 -> 30 + // U+3045 -> E3 81 85 + test( + "utf-8", + Buffer.from([0xcb, 0xa4, 0x64, 0xe1, 0x8b, 0xa4, 0x30, 0xe3, 0x81, 0x85]), + "\u02e4\u0064\u12e4\u0030\u3045", + ); + }); + + it("StringDecoder-ucs-2", () => { + test("ucs2", Buffer.from("ababc", "ucs2"), "ababc"); + }); + + it("StringDecoder-utf16le", () => { + test("utf16le", Buffer.from("3DD84DDC", "hex"), "\ud83d\udc4d"); + }); + + it("StringDecoder-utf8-additional", () => { + let decoder = new StringDecoder("utf8"); + expect(decoder.write(Buffer.from("E18B", "hex"))).toBe(""); + expect(decoder.end()).toBe("\ufffd"); + + decoder = new StringDecoder("utf8"); + expect(decoder.write(Buffer.from("\ufffd"))).toBe("\ufffd"); + expect(decoder.end()).toBe(""); + + decoder = new StringDecoder("utf8"); + expect(decoder.write(Buffer.from("\ufffd\ufffd\ufffd"))).toBe("\ufffd\ufffd\ufffd"); + expect(decoder.end()).toBe(""); + + decoder = new StringDecoder("utf8"); + expect(decoder.write(Buffer.from("EFBFBDE2", "hex"))).toBe("\ufffd"); + expect(decoder.end()).toBe("\ufffd"); + + decoder = new StringDecoder("utf8"); + expect(decoder.write(Buffer.from("F1", "hex"))).toBe(""); + expect(decoder.write(Buffer.from("41F2", "hex"))).toBe("\ufffdA"); + expect(decoder.end()).toBe("\ufffd"); + + // Additional utf8Text test + decoder = new StringDecoder("utf8"); + expect(decoder.text(Buffer.from([0x41]), 2)).toBe(""); + }); + + it("StringDecoder-utf16le-additional", () => { + // Additional UTF-16LE surrogate pair tests + let decoder = new StringDecoder("utf16le"); + expect(decoder.write(Buffer.from("3DD8", "hex"))).toBe(""); + expect(decoder.write(Buffer.from("4D", "hex"))).toBe(""); + expect(decoder.write(Buffer.from("DC", "hex"))).toBe("\ud83d\udc4d"); + expect(decoder.end()).toBe(""); + + decoder = new StringDecoder("utf16le"); + expect(decoder.write(Buffer.from("3DD8", "hex"))).toBe(""); + expect(decoder.end()).toBe("\ud83d"); + + decoder = new StringDecoder("utf16le"); + expect(decoder.write(Buffer.from("3DD8", "hex"))).toBe(""); + expect(decoder.write(Buffer.from("4D", "hex"))).toBe(""); + expect(decoder.end()).toBe("\ud83d"); + + decoder = new StringDecoder("utf16le"); + expect(decoder.write(Buffer.from("3DD84D", "hex"))).toBe("\ud83d"); + expect(decoder.end()).toBe(""); + }); + + // Test verifies that StringDecoder will correctly decode the given input + // buffer with the given encoding to the expected output. It will attempt all + // possible ways to write() the input buffer, see writeSequences(). The + // singleSequence allows for easy debugging of a specific sequence which is + // useful in case of test failures. + function test(encoding, input, expected, singleSequence) { + withoutAggressiveGC(() => { + let sequences; + if (!singleSequence) { + sequences = writeSequences(input.length); + } else { + sequences = [singleSequence]; + } + sequences.forEach(sequence => { + const decoder = new StringDecoder(encoding); + let output = ""; + sequence.forEach(write => { + output += decoder.write(input.slice(write[0], write[1])); + }); + output += decoder.end(); + expect(output).toBe(expected); + }); + }); + } + + // writeSequences returns an array of arrays that describes all possible ways a + // buffer of the given length could be split up and passed to sequential write + // calls. + // + // e.G. writeSequences(3) will return: [ + // [ [ 0, 3 ] ], + // [ [ 0, 2 ], [ 2, 3 ] ], + // [ [ 0, 1 ], [ 1, 3 ] ], + // [ [ 0, 1 ], [ 1, 2 ], [ 2, 3 ] ] + // ] + function writeSequences(length, start, sequence) { + if (start === undefined) { + start = 0; + sequence = []; + } else if (start === length) { + return [sequence]; + } + let sequences = []; + for (let end = length; end > start; end--) { + const subSequence = sequence.concat([[start, end]]); + const subSequences = writeSequences(length, end, subSequence, sequences); + sequences = sequences.concat(subSequences); + } + return sequences; + } + + describe("StringDecoder.end", () => { + const encodings = ["base64", "base64url", "hex", "utf8", "utf16le", "ucs2"]; + + const bufs = ["☃💩", "asdf"].map(b => Buffer.from(b)); + + // Also test just arbitrary bytes from 0-15. + for (let i = 1; i <= 16; i++) { + const bytes = "." + .repeat(i - 1) + .split(".") + .map((_, j) => j + 0x78); + bufs.push(Buffer.from(bytes)); + } + + encodings.forEach(testEncoding); + + testEnd("utf8", Buffer.of(0xe2), Buffer.of(0x61), "\uFFFDa"); + testEnd("utf8", Buffer.of(0xe2), Buffer.of(0x82), "\uFFFD\uFFFD"); + testEnd("utf8", Buffer.of(0xe2), Buffer.of(0xe2), "\uFFFD\uFFFD"); + testEnd("utf8", Buffer.of(0xe2, 0x82), Buffer.of(0x61), "\uFFFDa"); + testEnd("utf8", Buffer.of(0xe2, 0x82), Buffer.of(0xac), "\uFFFD\uFFFD"); + testEnd("utf8", Buffer.of(0xe2, 0x82), Buffer.of(0xe2), "\uFFFD\uFFFD"); + testEnd("utf8", Buffer.of(0xe2, 0x82, 0xac), Buffer.of(0x61), "€a"); + + testEnd("utf16le", Buffer.of(0x3d), Buffer.of(0x61, 0x00), "a"); + testEnd("utf16le", Buffer.of(0x3d), Buffer.of(0xd8, 0x4d, 0xdc), "\u4DD8"); + testEnd("utf16le", Buffer.of(0x3d, 0xd8), Buffer.of(), "\uD83D"); + testEnd("utf16le", Buffer.of(0x3d, 0xd8), Buffer.of(0x61, 0x00), "\uD83Da"); + testEnd("utf16le", Buffer.of(0x3d, 0xd8), Buffer.of(0x4d, 0xdc), "\uD83D\uDC4D"); + testEnd("utf16le", Buffer.of(0x3d, 0xd8, 0x4d), Buffer.of(), "\uD83D"); + testEnd("utf16le", Buffer.of(0x3d, 0xd8, 0x4d), Buffer.of(0x61, 0x00), "\uD83Da"); + testEnd("utf16le", Buffer.of(0x3d, 0xd8, 0x4d), Buffer.of(0xdc), "\uD83D"); + testEnd("utf16le", Buffer.of(0x3d, 0xd8, 0x4d, 0xdc), Buffer.of(0x61, 0x00), "👍a"); + + testEnd("base64", Buffer.of(0x61), Buffer.of(), "YQ=="); + testEnd("base64", Buffer.of(0x61), Buffer.of(0x61), "YQ==YQ=="); + testEnd("base64", Buffer.of(0x61, 0x61), Buffer.of(), "YWE="); + testEnd("base64", Buffer.of(0x61, 0x61), Buffer.of(0x61), "YWE=YQ=="); + testEnd("base64", Buffer.of(0x61, 0x61, 0x61), Buffer.of(), "YWFh"); + testEnd("base64", Buffer.of(0x61, 0x61, 0x61), Buffer.of(0x61), "YWFhYQ=="); + + testEnd("base64url", Buffer.of(0x61), Buffer.of(), "YQ"); + testEnd("base64url", Buffer.of(0x61), Buffer.of(0x61), "YQYQ"); + testEnd("base64url", Buffer.of(0x61, 0x61), Buffer.of(), "YWE"); + testEnd("base64url", Buffer.of(0x61, 0x61), Buffer.of(0x61), "YWEYQ"); + testEnd("base64url", Buffer.of(0x61, 0x61, 0x61), Buffer.of(), "YWFh"); + testEnd("base64url", Buffer.of(0x61, 0x61, 0x61), Buffer.of(0x61), "YWFhYQ"); + + function testEncoding(encoding) { + it(encoding + " testbuf", () => { + bufs.forEach(buf => { + testBuf(encoding, buf); + }); + }); + } + + function testBuf(encoding, buf) { + // Write one byte at a time. + let s = new StringDecoder(encoding); + let res1 = ""; + for (let i = 0; i < buf.length; i++) { + res1 += s.write(buf.slice(i, i + 1)); + } + res1 += s.end(); + + // Write the whole buffer at once. + let res2 = ""; + s = new StringDecoder(encoding); + res2 += s.write(buf); + res2 += s.end(); + + // .toString() on the buffer + const res3 = buf.toString(encoding); + + // One byte at a time should match toString + expect(res1).toEqual(res3); + // All bytes at once should match toString + expect(res2).toEqual(res3); + } + + function testEnd(encoding, incomplete, next, expected) { + it(`${encoding} partial ${JSON.stringify(expected)}`, () => { + let res = ""; + const s = new StringDecoder(encoding); + res += s.write(incomplete); + res += s.end(); + res += s.write(next); + res += s.end(); + + expect(res).toEqual(expected); + }); + } + }); + }); +} diff --git a/test/js/node/timers/node-timers.test.ts b/test/js/node/timers/node-timers.test.ts new file mode 100644 index 000000000..e6fa48010 --- /dev/null +++ b/test/js/node/timers/node-timers.test.ts @@ -0,0 +1,17 @@ +import { describe, test } from "bun:test"; +import { setTimeout, clearTimeout, setInterval, setImmediate } from "node:timers"; + +for (const fn of [setTimeout, setInterval, setImmediate]) { + describe(fn.name, () => { + test("unref is possible", done => { + const timer = fn(() => { + done(new Error("should not be called")); + }, 1); + fn(() => { + done(); + }, 2); + timer.unref(); + if (fn !== setImmediate) clearTimeout(timer); + }); + }); +} diff --git a/test/js/node/util/test-util-types.test.js b/test/js/node/util/test-util-types.test.js new file mode 100644 index 000000000..f33ab4b1a --- /dev/null +++ b/test/js/node/util/test-util-types.test.js @@ -0,0 +1,240 @@ +const assert = require("assert"); +import { test, expect } from "bun:test"; +const types = require("util/types"); + +function inspect(val) { + return Bun.inspect(val); +} + +for (const [value, _method] of [ + [new Date()], + [ + (function () { + return arguments; + })(), + "isArgumentsObject", + ], + [new Boolean(), "isBooleanObject"], + [new Number(), "isNumberObject"], + [new String(), "isStringObject"], + [Object(Symbol()), "isSymbolObject"], + [Object(BigInt(0)), "isBigIntObject"], + [new Error(), "isNativeError"], + [new RegExp()], + [async function () {}, "isAsyncFunction"], + [function* () {}, "isGeneratorFunction"], + [(function* () {})(), "isGeneratorObject"], + [Promise.resolve()], + [new Map()], + [new Set()], + [new Map()[Symbol.iterator](), "isMapIterator"], + [new Set()[Symbol.iterator](), "isSetIterator"], + [new WeakMap()], + [new WeakSet()], + [new ArrayBuffer()], + [new Uint8Array()], + [new Uint8ClampedArray()], + [new Uint16Array()], + [new Uint32Array()], + [new Int8Array()], + [new Int16Array()], + [new Int32Array()], + [new Float32Array()], + [new Float64Array()], + [new BigInt64Array()], + [new BigUint64Array()], + [new DataView(new ArrayBuffer())], + [new SharedArrayBuffer()], + [new Proxy({}, {}), "isProxy"], +]) { + const method = _method || `is${value.constructor.name}`; + test(method, () => { + assert(method in types, `Missing ${method} for ${inspect(value)}`); + assert(types[method](value), `Want ${inspect(value)} to match ${method}`); + + for (const key of Object.keys(types)) { + if ( + ((types.isArrayBufferView(value) || types.isAnyArrayBuffer(value)) && key.includes("Array")) || + key === "isBoxedPrimitive" + ) { + continue; + } + + expect(types[key](value)).toBe(key === method); + } + }); +} + +// Check boxed primitives. +test("isBoxedPrimitive", () => { + [new Boolean(), new Number(), new String(), Object(Symbol()), Object(BigInt(0))].forEach(entry => + assert(types.isBoxedPrimitive(entry)), + ); +}); + +{ + const primitive = true; + const arrayBuffer = new ArrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + const dataView = new DataView(arrayBuffer); + const uint8Array = new Uint8Array(arrayBuffer); + const uint8ClampedArray = new Uint8ClampedArray(arrayBuffer); + const uint16Array = new Uint16Array(arrayBuffer); + const uint32Array = new Uint32Array(arrayBuffer); + const int8Array = new Int8Array(arrayBuffer); + const int16Array = new Int16Array(arrayBuffer); + const int32Array = new Int32Array(arrayBuffer); + const float32Array = new Float32Array(arrayBuffer); + const float64Array = new Float64Array(arrayBuffer); + const bigInt64Array = new BigInt64Array(arrayBuffer); + const bigUint64Array = new BigUint64Array(arrayBuffer); + + const fakeBuffer = Object.create(Buffer.prototype); + const fakeDataView = Object.create(DataView.prototype); + const fakeUint8Array = Object.create(Uint8Array.prototype); + const fakeUint8ClampedArray = Object.create(Uint8ClampedArray.prototype); + const fakeUint16Array = Object.create(Uint16Array.prototype); + const fakeUint32Array = Object.create(Uint32Array.prototype); + const fakeInt8Array = Object.create(Int8Array.prototype); + const fakeInt16Array = Object.create(Int16Array.prototype); + const fakeInt32Array = Object.create(Int32Array.prototype); + const fakeFloat32Array = Object.create(Float32Array.prototype); + const fakeFloat64Array = Object.create(Float64Array.prototype); + const fakeBigInt64Array = Object.create(BigInt64Array.prototype); + const fakeBigUint64Array = Object.create(BigUint64Array.prototype); + + const stealthyDataView = Object.setPrototypeOf(new DataView(arrayBuffer), Uint8Array.prototype); + const stealthyUint8Array = Object.setPrototypeOf(new Uint8Array(arrayBuffer), ArrayBuffer.prototype); + const stealthyUint8ClampedArray = Object.setPrototypeOf(new Uint8ClampedArray(arrayBuffer), ArrayBuffer.prototype); + const stealthyUint16Array = Object.setPrototypeOf(new Uint16Array(arrayBuffer), Uint16Array.prototype); + const stealthyUint32Array = Object.setPrototypeOf(new Uint32Array(arrayBuffer), Uint32Array.prototype); + const stealthyInt8Array = Object.setPrototypeOf(new Int8Array(arrayBuffer), Int8Array.prototype); + const stealthyInt16Array = Object.setPrototypeOf(new Int16Array(arrayBuffer), Int16Array.prototype); + const stealthyInt32Array = Object.setPrototypeOf(new Int32Array(arrayBuffer), Int32Array.prototype); + const stealthyFloat32Array = Object.setPrototypeOf(new Float32Array(arrayBuffer), Float32Array.prototype); + const stealthyFloat64Array = Object.setPrototypeOf(new Float64Array(arrayBuffer), Float64Array.prototype); + const stealthyBigInt64Array = Object.setPrototypeOf(new BigInt64Array(arrayBuffer), BigInt64Array.prototype); + const stealthyBigUint64Array = Object.setPrototypeOf(new BigUint64Array(arrayBuffer), BigUint64Array.prototype); + + const all = [ + primitive, + arrayBuffer, + buffer, + fakeBuffer, + dataView, + fakeDataView, + stealthyDataView, + uint8Array, + fakeUint8Array, + stealthyUint8Array, + uint8ClampedArray, + fakeUint8ClampedArray, + stealthyUint8ClampedArray, + uint16Array, + fakeUint16Array, + stealthyUint16Array, + uint32Array, + fakeUint32Array, + stealthyUint32Array, + int8Array, + fakeInt8Array, + stealthyInt8Array, + int16Array, + fakeInt16Array, + stealthyInt16Array, + int32Array, + fakeInt32Array, + stealthyInt32Array, + float32Array, + fakeFloat32Array, + stealthyFloat32Array, + float64Array, + fakeFloat64Array, + stealthyFloat64Array, + bigInt64Array, + fakeBigInt64Array, + stealthyBigInt64Array, + bigUint64Array, + fakeBigUint64Array, + stealthyBigUint64Array, + ]; + + const expected = { + isArrayBufferView: [ + buffer, + dataView, + stealthyDataView, + uint8Array, + stealthyUint8Array, + uint8ClampedArray, + stealthyUint8ClampedArray, + uint16Array, + stealthyUint16Array, + uint32Array, + stealthyUint32Array, + int8Array, + stealthyInt8Array, + int16Array, + stealthyInt16Array, + int32Array, + stealthyInt32Array, + float32Array, + stealthyFloat32Array, + float64Array, + stealthyFloat64Array, + bigInt64Array, + stealthyBigInt64Array, + bigUint64Array, + stealthyBigUint64Array, + ], + isTypedArray: [ + buffer, + uint8Array, + stealthyUint8Array, + uint8ClampedArray, + stealthyUint8ClampedArray, + uint16Array, + stealthyUint16Array, + uint32Array, + stealthyUint32Array, + int8Array, + stealthyInt8Array, + int16Array, + stealthyInt16Array, + int32Array, + stealthyInt32Array, + float32Array, + stealthyFloat32Array, + float64Array, + stealthyFloat64Array, + bigInt64Array, + stealthyBigInt64Array, + bigUint64Array, + stealthyBigUint64Array, + ], + isUint8Array: [buffer, uint8Array, stealthyUint8Array], + isUint8ClampedArray: [uint8ClampedArray, stealthyUint8ClampedArray], + isUint16Array: [uint16Array, stealthyUint16Array], + isUint32Array: [uint32Array, stealthyUint32Array], + isInt8Array: [int8Array, stealthyInt8Array], + isInt16Array: [int16Array, stealthyInt16Array], + isInt32Array: [int32Array, stealthyInt32Array], + isFloat32Array: [float32Array, stealthyFloat32Array], + isFloat64Array: [float64Array, stealthyFloat64Array], + isBigInt64Array: [bigInt64Array, stealthyBigInt64Array], + isBigUint64Array: [bigUint64Array, stealthyBigUint64Array], + }; + + for (const testedFunc of Object.keys(expected)) { + test(testedFunc, () => { + const func = types[testedFunc]; + const yup = []; + for (const value of all) { + if (func(value)) { + yup.push(value); + } + } + expect(yup).toEqual(expected[testedFunc]); + }); + } +} diff --git a/test/js/node/util/util-promisify.test.js b/test/js/node/util/util-promisify.test.js new file mode 100644 index 000000000..336e070f7 --- /dev/null +++ b/test/js/node/util/util-promisify.test.js @@ -0,0 +1,304 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Tests adapted from https://github.com/nodejs/node/blob/main/test/parallel/test-util-promisify.js +import { describe, it } from "bun:test"; +import fs from "node:fs"; +// TODO: vm module not implemented by bun yet +// import vm from 'node:vm'; +import { promisify } from "util"; +import assert from "assert"; + +const stat = promisify(fs.stat); + +// A helper function to simplify checking for ERR_INVALID_ARG_TYPE output. +function invalidArgTypeHelper(input) { + if (input == null) { + return ` Received ${input}`; + } + if (typeof input === "function" && input.name) { + return ` Received function ${input.name}`; + } + if (typeof input === "object") { + if (input.constructor?.name) { + return ` Received an instance of ${input.constructor.name}`; + } + return ` Received ${inspect(input, { depth: -1 })}`; + } + + let inspected = inspect(input, { colors: false }); + if (inspected.length > 28) { + inspected = `${inspected.slice(inspected, 0, 25)}...`; + } + + return ` Received type ${typeof input} (${inspected})`; +} + +describe("util.promisify", () => { + describe("promisify fs calls", () => { + // TODO: common.mustCall is not implemented here yet + // https://github.com/nodejs/node/blob/main/test/common/index.js#L398 + it.skip("all cases", () => { + const promise = stat(__filename); + assert.equal(promise instanceof Promise, true); + promise.then( + common.mustCall(value => { + assert.deepStrictEqual(value, fs.statSync(__filename)); + }), + ); + + const promiseFileDontExist = stat("/dontexist"); + promiseFileDontExist.catch( + common.mustCall(error => { + assert(error.message.includes("ENOENT: no such file or directory, stat")); + }), + ); + }); + }); + + describe("promisify.custom", () => { + it("double promisify", () => { + function fn() {} + + function promisifedFn() {} + fn[promisify.custom] = promisifedFn; + assert.strictEqual(promisify(fn), promisifedFn); + assert.strictEqual(promisify(promisify(fn)), promisifedFn); + }); + + it.skip("should register shared promisify symbol", () => { + function fn() {} + + function promisifiedFn() {} + + // TODO: register shared symbol promisify.custom + // util.promisify.custom is a shared symbol which can be accessed + // as `Symbol.for("nodejs.util.promisify.custom")`. + const kCustomPromisifiedSymbol = Symbol.for("nodejs.util.promisify.custom"); + fn[kCustomPromisifiedSymbol] = promisifiedFn; + + assert.strictEqual(kCustomPromisifiedSymbol, promisify.custom); + assert.strictEqual(promisify(fn), promisifiedFn); + assert.strictEqual(promisify(promisify(fn)), promisifiedFn); + }); + }); + + it("should fail when type is not a function", () => { + function fn() {} + fn[promisify.custom] = 42; + assert.throws( + () => promisify(fn), + // TODO: error code is not the same as node's. + // { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' } + { name: "TypeError" }, + ); + }); + + it("should call custom promised promised function with proper args", () => { + const firstValue = 5; + const secondValue = 17; + var called = false; + + function fn(callback) { + called = true; + callback(null, firstValue, secondValue); + } + + fn[Symbol("customPromisifyArgs")] = ["first", "second"]; + + promisify(fn)().then((firstValue, secondValue) => { + assert.strictEqual(called, true); + assert.strictEqual(firstValue, 5); + assert.strictEqual(secondValue, 17); + }); + }); + + // TODO: unable to test since vm module not implemented + // it("should run in new vm context", () => { + // const fn = vm.runInNewContext('(function() {})'); + // assert.notStrictEqual(Object.getPrototypeOf(promisify(fn)),Function.prototype); + // }); + + describe("callback cases", () => { + it("should run basic callback", async () => { + var called = false; + function fn(callback) { + called = true; + callback(null, "foo", "bar"); + } + await promisify(fn)().then(value => { + assert.strictEqual(value, "foo"); + assert.strictEqual(called, true); + }); + }); + + it("should not require value to be returned in callback", async () => { + var called = false; + function fn(callback) { + called = true; + callback(null); + } + await promisify(fn)().then(value => { + assert.strictEqual(value, undefined); + assert.strictEqual(called, true); + }); + }); + + it("should not require error to be passed", async () => { + var called = false; + function fn(callback) { + called = true; + callback(); + } + await promisify(fn)().then(value => { + assert.strictEqual(value, undefined); + assert.strictEqual(called, true); + }); + }); + + it("custom callback", async () => { + var called = false; + function fn(err, val, callback) { + called = true; + callback(err, val); + } + await promisify(fn)(null, 42).then(value => { + assert.strictEqual(value, 42); + assert.strictEqual(called, true); + }); + }); + + it("should catch error", async () => { + var called = false; + function fn(err, val, callback) { + called = true; + callback(err, val); + } + await promisify(fn)(new Error("oops"), null).catch(err => { + assert.strictEqual(err.message, "oops"); + assert.strictEqual(called, true); + }); + }); + + it("should call promisify properly inside async block", async () => { + var called = false; + function fn(err, val, callback) { + called = true; + callback(err, val); + } + + await (async () => { + const value = await promisify(fn)(null, 42); + assert.strictEqual(value, 42); + })().then(() => { + assert.strictEqual(called, true); + }); + }); + + it("should not break this reference", async () => { + const o = {}; + var called = false; + const fn = promisify(function (cb) { + called = true; + cb(null, this === o); + }); + + o.fn = fn; + + await o.fn().then(val => { + assert.strictEqual(called, true); + assert.strictEqual(val, true); + }); + }); + + it("should not have called callback with error", async () => { + const err = new Error("Should not have called the callback with the error."); + const stack = err.stack; + var called = false; + + const fn = promisify(function (cb) { + called = true; + cb(null); + cb(err); + }); + + await (async () => { + await fn(); + await Promise.resolve(); + return assert.strictEqual(stack, err.stack); + })().then(() => { + assert.strictEqual(called, true); + }); + }); + + it("should compare promised objects properly", () => { + function c() {} + const a = promisify(function () {}); + const b = promisify(a); + assert.notStrictEqual(c, a); + assert.strictEqual(a, b); + }); + + it("should throw error", async () => { + let errToThrow; + const thrower = promisify(function (a, b, c, cb) { + errToThrow = new Error(); + throw errToThrow; + }); + await thrower(1, 2, 3) + .then(assert.fail) + .then(assert.fail, e => assert.strictEqual(e, errToThrow)); + }); + + it("should also throw error inside Promise.all", async () => { + const err = new Error(); + + const a = promisify(cb => cb(err))(); + const b = promisify(() => { + throw err; + })(); + + await Promise.all([ + a.then(assert.fail, function (e) { + assert.strictEqual(err, e); + }), + b.then(assert.fail, function (e) { + assert.strictEqual(err, e); + }), + ]); + }); + }); + + describe("invalid input", () => { + // This test is failing because 'code' property + // is not thrown in the error. does it have different + // throw error implementation in bun? + it("should throw on invalid inputs for promisify", () => { + [undefined, null, true, 0, "str", {}, [], Symbol()].forEach(input => { + assert.throws(() => promisify(input), { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + message: 'The "original" argument must be of type Function', + }); + }); + }); + }); +}); diff --git a/test/js/node/util/util.test.js b/test/js/node/util/util.test.js new file mode 100644 index 000000000..ff01b508b --- /dev/null +++ b/test/js/node/util/util.test.js @@ -0,0 +1,269 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Tests adapted from https://github.com/nodejs/node/blob/main/test/parallel/test-util.js + +import { expect, describe, it } from "bun:test"; +import util from "util"; +import assert from "assert"; +// const context = require('vm').runInNewContext; // TODO: Use a vm polyfill + +const strictEqual = (...args) => { + expect(args[0]).toStrictEqual(args[1]); +}; + +const deepStrictEqual = (...args) => { + expect(args[0]).toEqual(args[1]); +}; + +// Tests adapted from https://github.com/nodejs/node/blob/main/test/parallel/test-util.js +describe("util", () => { + describe("isArray", () => { + it("all cases", () => { + strictEqual(util.isArray([]), true); + strictEqual(util.isArray(Array()), true); + strictEqual(util.isArray(new Array()), true); + strictEqual(util.isArray(new Array(5)), true); + strictEqual(util.isArray(new Array("with", "some", "entries")), true); + // strictEqual(util.isArray(context('Array')()), true); unable to test due to dependency on context + strictEqual(util.isArray({}), false); + strictEqual(util.isArray({ push: function () {} }), false); + strictEqual(util.isArray(/regexp/), false); + strictEqual(util.isArray(new Error()), false); + strictEqual(util.isArray(Object.create(Array.prototype)), false); + }); + }); + describe("isRegExp", () => { + it("all cases", () => { + strictEqual(util.isRegExp(/regexp/), true); + strictEqual(util.isRegExp(RegExp(), "foo"), true); + strictEqual(util.isRegExp(new RegExp()), true); + // strictEqual(util.isRegExp(context("RegExp")()), true); unable to test due to dependency on context + strictEqual(util.isRegExp({}), false); + strictEqual(util.isRegExp([]), false); + strictEqual(util.isRegExp(new Date()), false); + strictEqual(util.isRegExp(Object.create(RegExp.prototype)), false); + }); + }); + describe("isDate", () => { + it("all cases", () => { + strictEqual(util.isDate(new Date()), true); + strictEqual(util.isDate(new Date(0), "foo"), true); + // strictEqual(util.isDate(new (context("Date"))()), true); unable to test due to dependency on context + strictEqual(util.isDate(Date()), false); + strictEqual(util.isDate({}), false); + strictEqual(util.isDate([]), false); + strictEqual(util.isDate(new Error()), false); + strictEqual(util.isDate(Object.create(Date.prototype)), false); + }); + }); + + describe("isError", () => { + it("all cases", () => { + strictEqual(util.isError(new Error()), true); + strictEqual(util.isError(new TypeError()), true); + strictEqual(util.isError(new SyntaxError()), true); + // strictEqual(util.isError(new (context("Error"))()), true); unable to test due to dependency on context + // strictEqual(util.isError(new (context("TypeError"))()), true); unable to test due to dependency on context + // strictEqual(util.isError(new (context("SyntaxError"))()), true); unable to test due to dependency on context + strictEqual(util.isError({}), false); + strictEqual(util.isError({ name: "Error", message: "" }), false); + strictEqual(util.isError([]), false); + strictEqual(util.isError(Object.create(Error.prototype)), true); + }); + }); + + describe("isObject", () => { + it("all cases", () => { + strictEqual(util.isObject({}), true); + strictEqual(util.isObject([]), true); + strictEqual(util.isObject(new Number(3)), true); + strictEqual(util.isObject(Number(4)), false); + strictEqual(util.isObject(1), false); + }); + }); + + describe("isPrimitive", () => { + it("all cases", () => { + strictEqual(util.isPrimitive({}), false); + strictEqual(util.isPrimitive(new Error()), false); + strictEqual(util.isPrimitive(new Date()), false); + strictEqual(util.isPrimitive([]), false); + strictEqual(util.isPrimitive(/regexp/), false); + strictEqual( + util.isPrimitive(function () {}), + false, + ); + strictEqual(util.isPrimitive(new Number(1)), false); + strictEqual(util.isPrimitive(new String("bla")), false); + strictEqual(util.isPrimitive(new Boolean(true)), false); + strictEqual(util.isPrimitive(1), true); + strictEqual(util.isPrimitive("bla"), true); + strictEqual(util.isPrimitive(true), true); + strictEqual(util.isPrimitive(undefined), true); + strictEqual(util.isPrimitive(null), true); + strictEqual(util.isPrimitive(Infinity), true); + strictEqual(util.isPrimitive(NaN), true); + strictEqual(util.isPrimitive(Symbol("symbol")), true); + }); + }); + + describe("isBuffer", () => { + it("all cases", () => { + strictEqual(util.isBuffer("foo"), false); + strictEqual(util.isBuffer(Buffer.from("foo")), true); + }); + }); + + describe("_extend", () => { + it("all cases", () => { + deepStrictEqual(util._extend({ a: 1 }), { a: 1 }); + deepStrictEqual(util._extend({ a: 1 }, []), { a: 1 }); + deepStrictEqual(util._extend({ a: 1 }, null), { a: 1 }); + deepStrictEqual(util._extend({ a: 1 }, true), { a: 1 }); + deepStrictEqual(util._extend({ a: 1 }, false), { a: 1 }); + deepStrictEqual(util._extend({ a: 1 }, { b: 2 }), { a: 1, b: 2 }); + deepStrictEqual(util._extend({ a: 1, b: 2 }, { b: 3 }), { a: 1, b: 3 }); + }); + }); + + describe("isBoolean", () => { + it("all cases", () => { + strictEqual(util.isBoolean(true), true); + strictEqual(util.isBoolean(false), true); + strictEqual(util.isBoolean("string"), false); + }); + }); + + describe("isNull", () => { + it("all cases", () => { + strictEqual(util.isNull(null), true); + strictEqual(util.isNull(undefined), false); + strictEqual(util.isNull(), false); + strictEqual(util.isNull("string"), false); + }); + }); + + describe("isUndefined", () => { + it("all cases", () => { + strictEqual(util.isUndefined(undefined), true); + strictEqual(util.isUndefined(), true); + strictEqual(util.isUndefined(null), false); + strictEqual(util.isUndefined("string"), false); + }); + }); + + describe("isNullOrUndefined", () => { + it("all cases", () => { + strictEqual(util.isNullOrUndefined(null), true); + strictEqual(util.isNullOrUndefined(undefined), true); + strictEqual(util.isNullOrUndefined(), true); + strictEqual(util.isNullOrUndefined("string"), false); + }); + }); + + describe("isNumber", () => { + it("all cases", () => { + strictEqual(util.isNumber(42), true); + strictEqual(util.isNumber(), false); + strictEqual(util.isNumber("string"), false); + }); + }); + + describe("isString", () => { + it("all cases", () => { + strictEqual(util.isString("string"), true); + strictEqual(util.isString(), false); + strictEqual(util.isString(42), false); + }); + }); + + describe("isSymbol", () => { + it("all cases", () => { + strictEqual(util.isSymbol(Symbol()), true); + strictEqual(util.isSymbol(), false); + strictEqual(util.isSymbol("string"), false); + }); + }); + + describe("isFunction", () => { + it("all cases", () => { + strictEqual( + util.isFunction(() => {}), + true, + ); + strictEqual( + util.isFunction(function () {}), + true, + ); + strictEqual(util.isFunction(), false); + strictEqual(util.isFunction("string"), false); + }); + }); + + describe("types.isNativeError", () => { + it("all cases", () => { + strictEqual(util.types.isNativeError(new Error()), true); + strictEqual(util.types.isNativeError(new TypeError()), true); + strictEqual(util.types.isNativeError(new SyntaxError()), true); + // TODO: unable to test due to dependency on context + // strictEqual(util.types.isNativeError(new (context("Error"))()), true); + // strictEqual(util.types.isNativeError(new (context("TypeError"))()), true); + // strictEqual( + // util.types.isNativeError(new (context("SyntaxError"))()), + // true + // ); + strictEqual(util.types.isNativeError({}), false); + strictEqual(util.types.isNativeError({ name: "Error", message: "" }), false); + strictEqual(util.types.isNativeError([]), false); + // strictEqual( // FIXME: failing test + // util.types.isNativeError(Object.create(Error.prototype)), + // false + // ); + // strictEqual( // FIXME: failing test + // util.types.isNativeError(new errors.codes.ERR_IPC_CHANNEL_CLOSED()), + // true + // ); + }); + }); + + // describe("", () => { + // it("all cases", () => { + // strictEqual(util.toUSVString("string\ud801"), "string\ufffd"); // TODO: currently unsupported + // }); + // }); + + describe("TextEncoder", () => { + // test/bun.js/text-encoder.test.js covers test cases for TextEncoder + // here we test only if we use the same via util.TextEncoder + it("is same as global TextEncoder", () => { + expect(util.TextEncoder === globalThis.TextEncoder).toBe(true); + }); + }); + + describe("TextDecoder", () => { + // test/bun.js/text-decoder.test.js covers test cases for TextDecoder + // here we test only if we use the same via util.TextDecoder + it("is same as global TextDecoder", () => { + expect(util.TextDecoder === globalThis.TextDecoder).toBe(true); + }); + }); +}); diff --git a/test/js/node/v8/capture-stack-trace.test.js b/test/js/node/v8/capture-stack-trace.test.js new file mode 100644 index 000000000..789503960 --- /dev/null +++ b/test/js/node/v8/capture-stack-trace.test.js @@ -0,0 +1,303 @@ +import { test, expect } from "bun:test"; + +test("capture stack trace", () => { + function f1() { + f2(); + } + + function f2() { + f3(); + } + + function f3() { + logErrorStackTrace(); + } + + function logErrorStackTrace() { + let error = {}; + Error.captureStackTrace(error); + expect(error.stack !== undefined).toBe(true); + } + + f1(); +}); + +test("capture stack trace with message", () => { + function f1() { + f2(); + } + + function f2() { + f3(); + } + + function f3() { + logErrorStackTrace(); + } + + function logErrorStackTrace() { + let e1 = { message: "bad error!" }; + Error.captureStackTrace(e1); + expect(e1.message === "bad error!").toBe(true); + + let e2 = new Error("bad error!"); + Error.captureStackTrace(e2); + expect(e2.message === "bad error!").toBe(true); + } + + f1(); +}); + +test("capture stack trace with constructor", () => { + class S { + constructor() { + captureStackTrace(); + } + } + + function captureStackTrace() { + let e1 = {}; + Error.captureStackTrace(e1); + expect(e1.stack.split("\n")[2].includes("new S")).toBe(true); + } + + let s = new S(); +}); + +test("capture stack trace limit", () => { + function f1() { + f2(); + } + + function f2() { + f3(); + } + + function f3() { + f4(); + } + + function f4() { + f5(); + } + + function f5() { + f6(); + } + + function f6() { + f7(); + } + + function f7() { + f8(); + } + + function f8() { + f9(); + } + + function f9() { + f10(); + } + + function f10() { + captureStackTrace(); + } + + function captureStackTrace() { + let e1 = {}; + Error.captureStackTrace(e1); + expect(e1.stack.split("\n").length).toBe(11); + + let e2 = new Error(); + Error.captureStackTrace(e2); + expect(e2.stack.split("\n").length).toBe(11); + + let e3 = {}; + Error.stackTraceLimit = 4; + Error.captureStackTrace(e3); + expect(e3.stack.split("\n").length).toBe(5); + + let e4 = new Error(); + Error.captureStackTrace(e4); + expect(e4.stack.split("\n").length).toBe(5); + + let e5 = { stackTraceLimit: 2 }; + Error.captureStackTrace(e5); + expect(e5.stack.split("\n").length).toBe(5); + + let e6 = {}; + Error.stackTraceLimit = Infinity; + Error.captureStackTrace(e6); + expect(e6.stack.split("\n").length).toBe(13); + } + + f1(); +}); + +test("prepare stack trace", () => { + function f1() { + f2(); + } + + function f2() { + let e = {}; + let prevPrepareStackTrace = Error.prepareStackTrace; + Error.prepareStackTrace = (e, stack) => { + return "custom stack trace"; + }; + Error.captureStackTrace(e); + expect(e.stack).toBe("custom stack trace"); + Error.prepareStackTrace = prevPrepareStackTrace; + f3(); + } + + function f3() { + let e = { message: "bad error!" }; + let prevPrepareStackTrace = Error.prepareStackTrace; + Error.prepareStackTrace = (e, s) => { + expect(e.message === "bad error!").toBe(true); + expect(s.length).toBe(4); + }; + Error.stackTraceLimit = 10; + Error.captureStackTrace(e); + expect(e.stack === undefined).toBe(true); + Error.prepareStackTrace = prevPrepareStackTrace; + } + + f1(); +}); + +test("capture stack trace second argument", () => { + function f0() { + let s = new S(); + } + + class S { + constructor() { + f1(); + } + } + + function f1() { + f2(); + } + + function f2() { + f3(); + } + + function f3() { + f4(); + } + + function f4() { + f5(); + } + + function f5() { + f6(); + } + + function f6() { + let e = { message: "bad error!" }; + Error.captureStackTrace(e); + expect(e.stack.split("\n")[1].includes("at f6")).toBe(true); + expect(e.stack.split("\n")[2].includes("at f5")).toBe(true); + + let e2 = {}; + Error.captureStackTrace(e2, f3); + expect(e2.stack.split("\n")[1].includes("at f2")).toBe(true); + expect(e2.stack.split("\n")[2].includes("at f1")).toBe(true); + + let e3 = {}; + Error.captureStackTrace(e3, f9); + expect(e3.stack.split("\n").length).toBe(1); + + let e4 = { message: "exclude constructor!" }; + Error.captureStackTrace(e4, S.constructor); + expect(e4.stack.split("\n").length).toBe(1); + + let e5 = { message: "actually exclude constructor!" }; + Error.captureStackTrace(e5, S); + expect(e5.stack.split("\n")[1].includes("at f0")).toBe(true); + } + + function f9() { + // nothing + } + + f0(); +}); + +test("capture stack trace edge cases", () => { + let e1 = {}; + Error.captureStackTrace(e1, null); + expect(e1.stack !== undefined).toBe(true); + + let e2 = {}; + Error.captureStackTrace(e2, undefined); + expect(e2.stack !== undefined).toBe(true); + + let e3 = {}; + Error.captureStackTrace(e3, 1); + expect(e3.stack !== undefined).toBe(true); + + let e4 = {}; + Error.captureStackTrace(e4, "foo"); + expect(e4.stack !== undefined).toBe(true); + + let e5 = {}; + Error.captureStackTrace(e5, {}); + expect(e5.stack !== undefined).toBe(true); + + expect(Error.captureStackTrace({})).toBe(undefined); + expect(Error.captureStackTrace({}, () => {})).toBe(undefined); + expect(Error.captureStackTrace({}, undefined)).toBe(undefined); + expect(Error.captureStackTrace({}, null)).toBe(undefined); + expect(Error.captureStackTrace({}, 1)).toBe(undefined); + expect(Error.captureStackTrace({}, "foo")).toBe(undefined); + expect(Error.captureStackTrace({}, {})).toBe(undefined); + expect(Error.captureStackTrace({}, [])).toBe(undefined); + expect(Error.captureStackTrace({}, true)).toBe(undefined); +}); + +test("prepare stack trace call sites", () => { + function f1() { + f2(); + } + + function f2() { + f3(); + } + + function f3() { + let e = { message: "bad error!" }; + // let e = new Error("bad error!"); + let prevPrepareStackTrace = Error.prepareStackTrace; + Error.prepareStackTrace = (e, s) => { + expect(s[0].getThis !== undefined).toBe(true); + expect(s[0].getTypeName !== undefined).toBe(true); + expect(s[0].getFunction !== undefined).toBe(true); + expect(s[0].getFunctionName !== undefined).toBe(true); + expect(s[0].getMethodName !== undefined).toBe(true); + expect(s[0].getFileName !== undefined).toBe(true); + expect(s[0].getLineNumber !== undefined).toBe(true); + expect(s[0].getColumnNumber !== undefined).toBe(true); + expect(s[0].getEvalOrigin !== undefined).toBe(true); + expect(s[0].isToplevel !== undefined).toBe(true); + expect(s[0].isEval !== undefined).toBe(true); + expect(s[0].isNative !== undefined).toBe(true); + expect(s[0].isConstructor !== undefined).toBe(true); + expect(s[0].isAsync !== undefined).toBe(true); + expect(s[0].isPromiseAll !== undefined).toBe(true); + expect(s[0].getPromiseIndex !== undefined).toBe(true); + }; + Error.captureStackTrace(e); + expect(e.stack === undefined).toBe(true); + Error.prepareStackTrace = prevPrepareStackTrace; + } + + f1(); +}); diff --git a/test/js/node/zlib/fixture.html.gz b/test/js/node/zlib/fixture.html.gz new file mode 100644 index 000000000..0bb85d4cb Binary files /dev/null and b/test/js/node/zlib/fixture.html.gz differ diff --git a/test/js/node/zlib/zlib.test.js b/test/js/node/zlib/zlib.test.js new file mode 100644 index 000000000..60f3fe23a --- /dev/null +++ b/test/js/node/zlib/zlib.test.js @@ -0,0 +1,39 @@ +import { describe, it, expect } from "bun:test"; +import { gzipSync, deflateSync, inflateSync, gunzipSync } from "bun"; + +describe("zlib", () => { + it("should be able to deflate and inflate", () => { + const data = new TextEncoder().encode("Hello World!".repeat(1)); + const compressed = deflateSync(data); + const decompressed = inflateSync(compressed); + expect(decompressed.join("")).toBe(data.join("")); + }); + + it("should be able to gzip and gunzip", () => { + const data = new TextEncoder().encode("Hello World!".repeat(1)); + const compressed = gzipSync(data); + const decompressed = gunzipSync(compressed); + expect(decompressed.join("")).toBe(data.join("")); + }); +}); + +import * as zlib from "node:zlib"; +import * as fs from "node:fs"; +import * as buffer from "node:buffer"; + +describe("zlib.gunzip", () => { + it("should be able to unzip a Buffer and return an unzipped Buffer", async () => { + const content = fs.readFileSync(import.meta.dir + "/fixture.html.gz"); + return new Promise((resolve, reject) => { + zlib.gunzip(content, (error, data) => { + if (error) { + reject(error); + return; + } + expect(data !== null).toBe(true); + expect(buffer.Buffer.isBuffer(data)).toBe(true); + resolve(true); + }); + }); + }); +}); diff --git a/test/js/third_party/body-parser/bun.lockb b/test/js/third_party/body-parser/bun.lockb new file mode 100755 index 000000000..3986c6911 Binary files /dev/null and b/test/js/third_party/body-parser/bun.lockb differ diff --git a/test/js/third_party/body-parser/express-body-parser-test.test.ts b/test/js/third_party/body-parser/express-body-parser-test.test.ts new file mode 100644 index 000000000..1f95b318e --- /dev/null +++ b/test/js/third_party/body-parser/express-body-parser-test.test.ts @@ -0,0 +1,59 @@ +import { test, expect } from "bun:test"; +import express, { Application, Request, Response } from "express"; +import { json } from "body-parser"; + +// Express uses iconv-lite +test("iconv works", () => { + var iconv = require("iconv-lite"); + + // Convert from an encoded buffer to a js string. + var str = iconv.decode(Buffer.from([0x68, 0x65, 0x6c, 0x6c, 0x6f]), "win1251"); + + // Convert from a js string to an encoded buffer. + var buf = iconv.encode("Sample input string", "win1251"); + expect(str).toBe("hello"); + expect(iconv.decode(buf, "win1251")).toBe("Sample input string"); + + // Check if encoding is supported + expect(iconv.encodingExists("us-ascii")).toBe(true); +}); + +// https://github.com/oven-sh/bun/issues/1913 +test("httpServer", async done => { + // Constants + const PORT = 8412; + + // App handlers + const app: Application = express(); + const httpServer = require("http").createServer(app); + + app.on("error", err => { + console.error(err); + done(err); + }); + app.use(json()); + + var reached = false; + // This throws a TypeError since it uses body-parser.json + app.post("/ping", (request: Request, response: Response) => { + expect(request.body).toEqual({ hello: "world" }); + reached = true; + response.status(200).send("POST - pong"); + httpServer.close(); + done(); + }); + + httpServer.listen(PORT); + const resp = await fetch(`http://localhost:${PORT}/ping`, { + method: "POST", + body: JSON.stringify({ hello: "world" }), + headers: { + "Content-Type": "application/json", + }, + }); + expect(await resp.text()).toBe("POST - pong"); + expect(resp.status).toBe(200); + + expect(reached).toBe(true); + done(); +}); diff --git a/test/js/third_party/body-parser/package.json b/test/js/third_party/body-parser/package.json new file mode 100644 index 000000000..0dfa98c59 --- /dev/null +++ b/test/js/third_party/body-parser/package.json @@ -0,0 +1,9 @@ +{ + "name": "body-parser-test", + "version": "1.0.0", + "dependencies": { + "express": "4.18.2", + "body-parser": "1.20.1", + "iconv-lite": "0.6.3" + } +} diff --git a/test/js/third_party/esbuild/bun.lockb b/test/js/third_party/esbuild/bun.lockb new file mode 100755 index 000000000..f46c6aa5d Binary files /dev/null and b/test/js/third_party/esbuild/bun.lockb differ diff --git a/test/js/third_party/esbuild/esbuild-child_process.test.ts b/test/js/third_party/esbuild/esbuild-child_process.test.ts new file mode 100644 index 000000000..adda89f9b --- /dev/null +++ b/test/js/third_party/esbuild/esbuild-child_process.test.ts @@ -0,0 +1,17 @@ +import { spawnSync } from "bun"; +import { describe, it, expect, test } from "bun:test"; +import { bunExe } from "harness"; + +test("esbuild", () => { + const { exitCode, stderr, stdout } = spawnSync([bunExe(), import.meta.dir + "/esbuild-test.js"], { + env: { + BUN_DEBUG_QUIET_LOGS: "1", + }, + }); + const out = "" + stderr?.toString() + stdout?.toString(); + if (exitCode !== 0 && out?.length) { + throw new Error(out); + } + + expect(exitCode).toBe(0); +}); diff --git a/test/js/third_party/esbuild/esbuild-test.js b/test/js/third_party/esbuild/esbuild-test.js new file mode 100644 index 000000000..beb34b283 --- /dev/null +++ b/test/js/third_party/esbuild/esbuild-test.js @@ -0,0 +1,37 @@ +import { transform, transformSync } from "esbuild"; + +{ + const result = await transform("console.log('hello world')", { + loader: "js", + target: "node12", + }); + if (result.code !== 'console.log("hello world");\n') { + throw new Error("Test failed."); + } +} + +{ + const hugeString = `console.log(${JSON.stringify("a".repeat(1000000))});`; + + for (let i = 0; i < 2; i++) { + const result = await transform(hugeString, { + loader: "js", + target: "node12", + }); + if (result.code !== hugeString + "\n") { + throw new Error("Test failed."); + } + } +} + +{ + const result = transformSync("console.log('hello world')", { + loader: "js", + target: "node12", + }); + if (result.code !== 'console.log("hello world");\n') { + throw new Error("Test failed."); + } +} + +process.exit(0); diff --git a/test/js/third_party/esbuild/package.json b/test/js/third_party/esbuild/package.json new file mode 100644 index 000000000..ad6bc55f6 --- /dev/null +++ b/test/js/third_party/esbuild/package.json @@ -0,0 +1,6 @@ +{ + "type": "module", + "dependencies": { + "esbuild": "^0.17.11" + } +} \ No newline at end of file diff --git a/test/js/third_party/napi_create_external/bun.lockb b/test/js/third_party/napi_create_external/bun.lockb new file mode 100755 index 000000000..7f6d3e95d Binary files /dev/null and b/test/js/third_party/napi_create_external/bun.lockb differ diff --git a/test/js/third_party/napi_create_external/napi-create-external.test.ts b/test/js/third_party/napi_create_external/napi-create-external.test.ts new file mode 100644 index 000000000..c3fe5ad65 --- /dev/null +++ b/test/js/third_party/napi_create_external/napi-create-external.test.ts @@ -0,0 +1,194 @@ +import { test, it, describe, expect } from "bun:test"; +import { withoutAggressiveGC } from "harness"; +import * as _ from "lodash"; + +function rebase(str, inBase, outBase) { + const mapBase = b => (b === 2 ? 32 : b === 16 ? 8 : null); + const stride = mapBase(inBase); + const pad = mapBase(outBase); + if (!stride) throw new Error(`Bad inBase ${inBase}`); + if (!pad) throw new Error(`Bad outBase ${outBase}`); + if (str.length % stride) throw new Error(`Bad string length ${str.length}`); + const out = []; + for (let i = 0; i < str.length; i += stride) + out.push( + parseInt(str.slice(i, i + stride), inBase) + .toString(outBase) + .padStart(pad, "0"), + ); + return out.join(""); +} + +function expectDeepEqual(a, b) { + expect(a).toEqual(b); +} +class HashMaker { + constructor(length) { + this.length = length; + this._dist = {}; + } + length: number; + _dist: any; + + binToHex(binHash) { + if (binHash.length !== this.length) throw new Error(`Hash length mismatch ${this.length} != ${binHash.length}`); + return rebase(binHash, 2, 16); + } + + makeBits() { + const bits = []; + for (let i = 0; i < this.length; i++) bits.push(i); + return _.shuffle(bits); + } + + makeRandom() { + const bits = []; + for (let i = 0; i < this.length; i++) bits.push(Math.random() < 0.5 ? 1 : 0); + return bits; + } + + get keySet() { + return (this._set = this._set || new Set(this.data)); + } + + randomKey() { + while (true) { + const hash = this.binToHex(this.makeRandom().join("")); + if (!this.keySet.has(hash)) return hash; + } + } + + get data() { + return (this._data = + this._data || + (() => { + const bits = this.makeBits(); + const base = this.makeRandom(); + const data = []; + for (let stride = 0; bits.length; stride++) { + const flip = bits.splice(0, stride); + for (const bit of flip) base[bit] = 1 - base[bit]; + data.push(this.binToHex(base.join(""))); + } + return data; + })()); + } + + get random() { + const d = this.data; + return d[Math.floor(Math.random() * d.length)]; + } + + distance(a, b) { + const bitCount = n => { + n = n - ((n >> 1) & 0x55555555); + n = (n & 0x33333333) + ((n >> 2) & 0x33333333); + return (((n + (n >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; + }; + + if (a === b) return 0; + if (a > b) return this.distance(b, a); + const hash = a + "-" + b; + return (this._dist[hash] = + this._dist[hash] || + (() => { + let dist = 0; + for (let i = 0; i < a.length; i += 8) { + const va = parseInt(a.slice(i, i + 8), 16); + const vb = parseInt(b.slice(i, i + 8), 16); + dist += bitCount(va ^ vb); + } + return dist; + })()); + } + + query(baseKey, maxDist) { + const out = []; + for (const key of this.data) { + const distance = this.distance(key, baseKey); + if (distance <= maxDist) out.push({ key, distance }); + } + return out.sort((a, b) => a.distance - b.distance); + } +} + +const treeClass = require("bktree-fast/native"); + +withoutAggressiveGC(() => { + // this test is too slow + for (let keyLen = 64; keyLen <= 64; keyLen += 64) { + // for (let keyLen = 64; keyLen <= 512; keyLen += 64) { + const hm = new HashMaker(keyLen); + describe(`Key length: ${keyLen}`, () => { + it("should compute distance", () => { + const tree = new treeClass(keyLen); + for (const a of hm.data) for (const b of hm.data) expect(tree.distance(a, b)).toBe(hm.distance(a, b)); + }); + + it("should know which keys it has", () => { + const tree = new treeClass(keyLen).add(hm.data); + expectDeepEqual( + hm.data.map(hash => tree.has(hash)), + hm.data.map(() => true), + ); + // Not interested in the hash + for (const hash of hm.data) expect(tree.has(hm.randomKey())).toBe(false); + }); + + it("should know the tree size", () => { + const tree = new treeClass(keyLen, { foo: 1 }); + expect(tree.size).toBe(0); + tree.add(hm.data); + expect(tree.size).toBe(hm.data.length); + tree.add(hm.data); + expect(tree.size).toBe(hm.data.length); + }); + + it("should walk the tree", () => { + const tree = new treeClass(keyLen).add(hm.data); + const got = []; + tree.walk((hash, depth) => got.push(hash)); + expectDeepEqual(got.sort(), hm.data.slice(0).sort()); + }); + + it("should query", () => { + ((treeClass, expectDeepEqual) => { + const tree = new treeClass(keyLen).add(hm.data); + + for (let dist = 0; dist <= hm.length; dist++) { + for (const baseKey of [hm.random, hm.data[0]]) { + const baseKey = hm.random; + const got = []; + tree.query(baseKey, dist, (key, distance) => got.push({ key, distance })); + const want = hm.query(baseKey, dist); + expectDeepEqual( + got.sort((a, b) => a.distance - b.distance), + want, + ); + expectDeepEqual(tree.find(baseKey, dist), want); + } + } + })(treeClass, expectDeepEqual); + }); + }); + } + + describe("Misc functions", () => { + it("should pad keys", () => { + const tree = new treeClass(64); + expect(tree.padKey("1")).toBe("0000000000000001"); + tree.add(["1", "2", "3"]); + + const got = []; + tree.query("2", 3, (hash, distance) => got.push({ hash, distance })); + const res = got.sort((a, b) => a.distance - b.distance); + const want = [ + { hash: "0000000000000002", distance: 0 }, + { hash: "0000000000000003", distance: 1 }, + { hash: "0000000000000001", distance: 2 }, + ]; + + expectDeepEqual(res, want); + }); + }); +}); diff --git a/test/js/third_party/napi_create_external/package.json b/test/js/third_party/napi_create_external/package.json new file mode 100644 index 000000000..659b279b2 --- /dev/null +++ b/test/js/third_party/napi_create_external/package.json @@ -0,0 +1,13 @@ +{ + "name": "napi-create-external-test", + "type": "module", + "version": "1.0.0", + "description": "Test for napi_create_external", + "dependencies": { + "bktree-fast": "0.0.7", + "lodash": "^4.17.21" + }, + "scripts": { + "postinstall": "cd node_modules/bktree-fast && node-gyp configure" + } +} diff --git a/test/js/third_party/react-dom/react-dom-server.bun.cjs b/test/js/third_party/react-dom/react-dom-server.bun.cjs new file mode 100644 index 000000000..f67e54a8c --- /dev/null +++ b/test/js/third_party/react-dom/react-dom-server.bun.cjs @@ -0,0 +1,2670 @@ +/** + * @license React + * react-dom-server.bun.production.min.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +"use strict"; +var ba = require("react"), + ca = require("react-dom"); +function l(a, b) { + 0 !== b.length && a.write(b); +} +function da(a, b) { + "function" === typeof a.error ? a.error(b) : a.close(); +} +var r = Object.prototype.hasOwnProperty, + ea = + /^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/, + fa = {}, + ha = {}; +function ia(a) { + if (r.call(ha, a)) return !0; + if (r.call(fa, a)) return !1; + if (ea.test(a)) return (ha[a] = !0); + fa[a] = !0; + return !1; +} +function t(a, b, c, d, e, f, g) { + this.acceptsBooleans = 2 === b || 3 === b || 4 === b; + this.attributeName = d; + this.attributeNamespace = e; + this.mustUseProperty = c; + this.propertyName = a; + this.type = b; + this.sanitizeURL = f; + this.removeEmptyString = g; +} +var v = {}, + ja = + "children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split( + " ", + ); +ja.push("innerText", "textContent"); +ja.forEach(function (a) { + v[a] = new t(a, 0, !1, a, null, !1, !1); +}); +[ + ["acceptCharset", "accept-charset"], + ["className", "class"], + ["htmlFor", "for"], + ["httpEquiv", "http-equiv"], +].forEach(function (a) { + var b = a[0]; + v[b] = new t(b, 1, !1, a[1], null, !1, !1); +}); +["contentEditable", "draggable", "spellCheck", "value"].forEach(function (a) { + v[a] = new t(a, 2, !1, a.toLowerCase(), null, !1, !1); +}); +["autoReverse", "externalResourcesRequired", "focusable", "preserveAlpha"].forEach(function (a) { + v[a] = new t(a, 2, !1, a, null, !1, !1); +}); +"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope" + .split(" ") + .forEach(function (a) { + v[a] = new t(a, 3, !1, a.toLowerCase(), null, !1, !1); + }); +["checked", "multiple", "muted", "selected"].forEach(function (a) { + v[a] = new t(a, 3, !0, a, null, !1, !1); +}); +["capture", "download"].forEach(function (a) { + v[a] = new t(a, 4, !1, a, null, !1, !1); +}); +["cols", "rows", "size", "span"].forEach(function (a) { + v[a] = new t(a, 6, !1, a, null, !1, !1); +}); +["rowSpan", "start"].forEach(function (a) { + v[a] = new t(a, 5, !1, a.toLowerCase(), null, !1, !1); +}); +var ka = /[\-:]([a-z])/g; +function la(a) { + return a[1].toUpperCase(); +} +"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height" + .split(" ") + .forEach(function (a) { + var b = a.replace(ka, la); + v[b] = new t(b, 1, !1, a, null, !1, !1); + }); +"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function (a) { + var b = a.replace(ka, la); + v[b] = new t(b, 1, !1, a, "http://www.w3.org/1999/xlink", !1, !1); +}); +["xml:base", "xml:lang", "xml:space"].forEach(function (a) { + var b = a.replace(ka, la); + v[b] = new t(b, 1, !1, a, "http://www.w3.org/XML/1998/namespace", !1, !1); +}); +["tabIndex", "crossOrigin"].forEach(function (a) { + v[a] = new t(a, 1, !1, a.toLowerCase(), null, !1, !1); +}); +v.xlinkHref = new t("xlinkHref", 1, !1, "xlink:href", "http://www.w3.org/1999/xlink", !0, !1); +["src", "href", "action", "formAction"].forEach(function (a) { + v[a] = new t(a, 1, !1, a.toLowerCase(), null, !0, !0); +}); +var ma = { + animationIterationCount: !0, + aspectRatio: !0, + borderImageOutset: !0, + borderImageSlice: !0, + borderImageWidth: !0, + boxFlex: !0, + boxFlexGroup: !0, + boxOrdinalGroup: !0, + columnCount: !0, + columns: !0, + flex: !0, + flexGrow: !0, + flexPositive: !0, + flexShrink: !0, + flexNegative: !0, + flexOrder: !0, + gridArea: !0, + gridRow: !0, + gridRowEnd: !0, + gridRowSpan: !0, + gridRowStart: !0, + gridColumn: !0, + gridColumnEnd: !0, + gridColumnSpan: !0, + gridColumnStart: !0, + fontWeight: !0, + lineClamp: !0, + lineHeight: !0, + opacity: !0, + order: !0, + orphans: !0, + tabSize: !0, + widows: !0, + zIndex: !0, + zoom: !0, + fillOpacity: !0, + floodOpacity: !0, + stopOpacity: !0, + strokeDasharray: !0, + strokeDashoffset: !0, + strokeMiterlimit: !0, + strokeOpacity: !0, + strokeWidth: !0, + }, + na = ["Webkit", "ms", "Moz", "O"]; +Object.keys(ma).forEach(function (a) { + na.forEach(function (b) { + b = b + a.charAt(0).toUpperCase() + a.substring(1); + ma[b] = ma[a]; + }); +}); +var oa = /["'&<>]/; +function w(a) { + if ("boolean" === typeof a || "number" === typeof a) return "" + a; + a = "" + a; + var b = oa.exec(a); + if (b) { + var c = "", + d, + e = 0; + for (d = b.index; d < a.length; d++) { + switch (a.charCodeAt(d)) { + case 34: + b = """; + break; + case 38: + b = "&"; + break; + case 39: + b = "'"; + break; + case 60: + b = "<"; + break; + case 62: + b = ">"; + break; + default: + continue; + } + e !== d && (c += a.substring(e, d)); + e = d + 1; + c += b; + } + a = e !== d ? c + a.substring(e, d) : c; + } + return a; +} +var pa = /([A-Z])/g, + qa = /^ms-/, + ra = Array.isArray, + x = Object.assign, + y = null, + sa = [], + va = { preload: ta, preinit: ua }; +function ta(a, b) { + if (y) { + var c = y; + if ("string" === typeof a && a && "object" === typeof b && null !== b) { + var d = b.as, + e = c.preloadsMap.get(a); + e || + (e = z(c, a, d, { + href: a, + rel: "preload", + as: d, + crossOrigin: "font" === d ? "" : b.crossOrigin, + integrity: b.integrity, + })); + switch (d) { + case "font": + c.fontPreloads.add(e); + break; + case "style": + c.explicitStylePreloads.add(e); + break; + case "script": + c.explicitScriptPreloads.add(e); + } + } + } +} +function ua(a, b) { + if (y) { + var c = y; + if ("string" === typeof a && a && "object" === typeof b && null !== b) + switch (b.as) { + case "style": + var d = c.stylesMap.get(a); + d || + ((d = b.precedence || "default"), + (d = wa(c, a, d, { + rel: "stylesheet", + href: a, + "data-precedence": d, + crossOrigin: b.crossOrigin, + }))); + d.set.add(d); + c.explicitStylePreloads.add(d.hint); + break; + case "script": + (d = c.scriptsMap.get(a)), + d || + ((d = xa(c, a, { + src: a, + async: !0, + crossOrigin: b.crossOrigin, + integrity: b.integrity, + })), + c.scripts.add(d)); + } + } +} +function ya(a, b) { + return { + rel: "preload", + as: "style", + href: a, + crossOrigin: b.crossOrigin, + integrity: b.integrity, + media: b.media, + hrefLang: b.hrefLang, + referrerPolicy: b.referrerPolicy, + }; +} +function za(a, b) { + return { + rel: "preload", + as: "script", + href: a, + crossOrigin: b.crossOrigin, + integrity: b.integrity, + referrerPolicy: b.referrerPolicy, + }; +} +function z(a, b, c, d) { + c = { type: "preload", as: c, href: b, flushed: !1, props: d }; + a.preloadsMap.set(b, c); + return c; +} +function wa(a, b, c, d) { + var e = a.stylesMap, + f = a.preloadsMap, + g = a.precedences, + h = g.get(c); + h || ((h = new Set()), g.set(c, h)); + (f = f.get(b)) + ? ((a = f.props), + null == d.crossOrigin && (d.crossOrigin = a.crossOrigin), + null == d.referrerPolicy && (d.referrerPolicy = a.referrerPolicy), + null == d.title && (d.title = a.title)) + : ((f = ya(b, d)), (f = z(a, b, "style", f)), a.explicitStylePreloads.add(f)); + c = { + type: "style", + href: b, + precedence: c, + flushed: !1, + inShell: !1, + props: d, + hint: f, + set: h, + }; + e.set(b, c); + return c; +} +function xa(a, b, c) { + var d = a.scriptsMap, + e = a.preloadsMap.get(b); + e + ? ((a = e.props), + null == c.crossOrigin && (c.crossOrigin = a.crossOrigin), + null == c.referrerPolicy && (c.referrerPolicy = a.referrerPolicy), + null == c.integrity && (c.integrity = a.integrity)) + : ((e = za(b, c)), (e = z(a, b, "script", e)), a.explicitScriptPreloads.add(e)); + c = { type: "script", src: b, flushed: !1, props: c, hint: e }; + d.set(b, c); + return c; +} +function Aa(a, b) { + if (!y) throw Error('"currentResources" was expected to exist. This is a bug in React.'); + var c = y; + switch (a) { + case "title": + var d = b.children; + Array.isArray(d) && 1 === d.length && (d = d[0]); + if ("string" === typeof d || "number" === typeof d) { + var e = "title::" + d; + a = c.headsMap.get(e); + a || + ((b = x({}, b)), + (b.children = d), + (a = { type: "title", props: b, flushed: !1 }), + c.headsMap.set(e, a), + c.headResources.add(a)); + } + return !0; + case "meta": + if ("string" === typeof b.charSet) e = "charSet"; + else if ("string" === typeof b.content) + if (((a = "::" + b.content), "string" === typeof b.httpEquiv)) e = "httpEquiv::" + b.httpEquiv + a; + else if ("string" === typeof b.name) e = "name::" + b.name + a; + else if ("string" === typeof b.itemProp) e = "itemProp::" + b.itemProp + a; + else if ("string" === typeof b.property) { + var f = b.property; + e = "property::" + f + a; + d = f; + a = f.split(":").slice(0, -1).join(":"); + (a = c.structuredMetaKeys.get(a)) && (e = a.key + "::child::" + e); + } + e && + !c.headsMap.has(e) && + ((b = { type: "meta", key: e, props: x({}, b), flushed: !1 }), + c.headsMap.set(e, b), + "charSet" === e ? (c.charset = b) : (d && c.structuredMetaKeys.set(d, b), c.headResources.add(b))); + return !0; + case "base": + return ( + (e = b.target), + (d = b.href), + (e = + "base" + + ("string" === typeof d ? '[href="' + d + '"]' : ":not([href])") + + ("string" === typeof e ? '[target="' + e + '"]' : ":not([target])")), + c.headsMap.has(e) || + ((b = { type: "base", props: x({}, b), flushed: !1 }), c.headsMap.set(e, b), c.bases.add(b)), + !0 + ); + } + return !1; +} +function Ba(a) { + if (!y) throw Error('"currentResources" was expected to exist. This is a bug in React.'); + var b = y, + c = a.rel, + d = a.href; + if (!d || "string" !== typeof d || !c || "string" !== typeof c) return !1; + switch (c) { + case "stylesheet": + var e = a.onLoad, + f = a.onError; + c = a.precedence; + var g = a.disabled; + if ("string" !== typeof c || e || f || null != g) + return (c = b.preloadsMap.get(d)), c || ((c = z(b, d, "style", ya(d, a))), b.usedStylePreloads.add(c)), !1; + e = b.stylesMap.get(d); + e || + ((a = x({}, a)), + (a.href = d), + (a.rel = "stylesheet"), + (a["data-precedence"] = c), + delete a.precedence, + (e = wa(y, d, c, a)), + b.usedStylePreloads.add(e.hint)); + b.boundaryResources ? b.boundaryResources.add(e) : e.set.add(e); + return !0; + case "preload": + switch (((e = a.as), e)) { + case "script": + case "style": + case "font": + c = b.preloadsMap.get(d); + if (!c) + switch ( + ((a = x({}, a)), + (a.href = d), + (a.rel = "preload"), + (a.as = e), + "font" === e && (a.crossOrigin = ""), + (c = z(b, d, e, a)), + e) + ) { + case "script": + b.explicitScriptPreloads.add(c); + break; + case "style": + b.explicitStylePreloads.add(c); + break; + case "font": + b.fontPreloads.add(c); + } + return !0; + } + } + if (a.onLoad || a.onError) return !0; + d = + "rel:" + + c + + "::href:" + + d + + "::sizes:" + + ("string" === typeof a.sizes ? a.sizes : "") + + "::media:" + + ("string" === typeof a.media ? a.media : ""); + e = b.headsMap.get(d); + if (!e) + switch (((e = { type: "link", props: x({}, a), flushed: !1 }), b.headsMap.set(d, e), c)) { + case "preconnect": + case "dns-prefetch": + b.preconnects.add(e); + break; + default: + b.headResources.add(e); + } + return !0; +} +function Ca(a, b) { + var c = a.boundaryResources; + c && + (b.forEach(function (a) { + return c.add(a); + }), + b.clear()); +} +function Da(a, b) { + b.forEach(function (a) { + return a.set.add(a); + }); + b.clear(); +} +var Ea = ca.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Dispatcher, + Fa = /(<\/|<)(s)(cript)/gi; +function Ga(a, b, c, d) { + return "" + b + ("s" === c ? "\\u0073" : "\\u0053") + d; +} +function Ha(a, b, c, d, e) { + a = void 0 === a ? "" : a; + b = void 0 === b ? " + +

Hello {name}!

diff --git a/test/js/third_party/svelte/package.json b/test/js/third_party/svelte/package.json new file mode 100644 index 000000000..ae4958ccc --- /dev/null +++ b/test/js/third_party/svelte/package.json @@ -0,0 +1,4 @@ +{ + "name": "bun-loader-svelte", + "module": "./bun-loader-svelte.ts" +} diff --git a/test/js/third_party/svelte/svelte.test.ts b/test/js/third_party/svelte/svelte.test.ts new file mode 100644 index 000000000..44e36cce2 --- /dev/null +++ b/test/js/third_party/svelte/svelte.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "bun:test"; +import "./bun-loader-svelte"; + +describe("require", () => { + it("SSRs `

Hello world!

` with Svelte", () => { + const { default: App } = require("./hello.svelte"); + const { html } = App.render(); + + expect(html).toBe("

Hello world!

"); + }); +}); + +describe("dynamic import", () => { + it("SSRs `

Hello world!

` with Svelte", async () => { + const { default: App }: any = await import("./hello.svelte"); + + const { html } = App.render(); + + expect(html).toBe("

Hello world!

"); + }); +}); diff --git a/test/js/web/abort/abort-signal-timeout.test.js b/test/js/web/abort/abort-signal-timeout.test.js new file mode 100644 index 000000000..7d741b2ad --- /dev/null +++ b/test/js/web/abort/abort-signal-timeout.test.js @@ -0,0 +1,12 @@ +import { expect, test } from "bun:test"; + +test.skip("AbortSignal.timeout", done => { + const abort = AbortSignal.timeout(10); + abort.addEventListener("abort", event => { + done(); + }); + + // AbortSignal.timeout doesn't keep the event loop / process alive + // so we set a no-op timeout + setTimeout(() => {}, 11); +}); diff --git a/test/js/web/console/console-log.expected.txt b/test/js/web/console/console-log.expected.txt new file mode 100644 index 000000000..97191c8be --- /dev/null +++ b/test/js/web/console/console-log.expected.txt @@ -0,0 +1,46 @@ +Hello World! +123 +-123 +123.567 +-123.567 +true +false +null +undefined +Symbol(Symbol Description) +2000-06-27T02:24:34.304Z +[ 123, 456, 789 ] +{ + name: "foo" +} +{ + a: 123, + b: 456, + c: 789 +} +{ + a: { + b: { + c: 123 + }, + bacon: true + }, + name: "bar" +} +Promise { } +[Function] +[Function: Foo] +{} +[Function: foooo] +/FooRegex/ +Is it a bug or a feature that formatting numbers like 123 is colored +String 123 should be 2nd word, 456 == 456 and percent s %s == What okay +{ + foo: { + name: "baz" + }, + bar: [Circular] +} am +[ + {}, {}, {}, {} +] diff --git a/test/js/web/console/console-log.js b/test/js/web/console/console-log.js new file mode 100644 index 000000000..e23a3e9cb --- /dev/null +++ b/test/js/web/console/console-log.js @@ -0,0 +1,54 @@ +console.log("Hello World!"); +console.log(123); +console.log(-123); +console.log(123.567); +console.log(-123.567); +console.log(true); +console.log(false); +console.log(null); +console.log(undefined); +console.log(Symbol("Symbol Description")); +console.log(new Date(Math.pow(2, 34) * 56)); +console.log([123, 456, 789]); +console.log({ name: "foo" }); +console.log({ a: 123, b: 456, c: 789 }); +console.log({ + a: { + b: { + c: 123, + }, + bacon: true, + }, + name: "bar", +}); + +console.log(new Promise(() => {})); + +class Foo {} + +console.log(() => {}); +console.log(Foo); +console.log(new Foo()); +console.log(function foooo() {}); + +console.log(/FooRegex/); + +console.error("uh oh"); +console.time("Check"); + +console.log("Is it a bug or a feature that formatting numbers like %d is colored", 123); +//console.log(globalThis); + +console.log("String %s should be 2nd word, 456 == %s and percent s %s == %s", "123", "456", "%s", "What", "okay"); + +const infinteLoop = { + foo: { + name: "baz", + }, + bar: {}, +}; + +infinteLoop.bar = infinteLoop; +console.log(infinteLoop, "am"); + +console.log(new Array(4).fill({})); diff --git a/test/js/web/console/console-log.test.ts b/test/js/web/console/console-log.test.ts new file mode 100644 index 000000000..98c8370de --- /dev/null +++ b/test/js/web/console/console-log.test.ts @@ -0,0 +1,20 @@ +import { file, spawn } from "bun"; +import { expect, it } from "bun:test"; +import { bunExe } from "harness"; + +it("should log to console correctly", async () => { + const { stdout, stderr, exited } = spawn({ + cmd: [bunExe(), import.meta.dir + "/console-log.js"], + stdin: null, + stdout: "pipe", + stderr: "pipe", + env: { + BUN_DEBUG_QUIET_LOGS: "1", + }, + }); + expect(await exited).toBe(0); + expect(await new Response(stderr).text()).toBe("uh oh\n"); + expect(await new Response(stdout).text()).toBe( + await new Response(file(import.meta.dir + "/console-log.expected.txt")).text(), + ); +}); diff --git a/test/js/web/crypto/web-crypto.test.ts b/test/js/web/crypto/web-crypto.test.ts new file mode 100644 index 000000000..250282b96 --- /dev/null +++ b/test/js/web/crypto/web-crypto.test.ts @@ -0,0 +1,91 @@ +import { describe, expect, it } from "bun:test"; + +describe("Web Crypto", () => { + it("has globals", () => { + expect(crypto.subtle !== undefined).toBe(true); + expect(CryptoKey.name).toBe("CryptoKey"); + expect(SubtleCrypto.name).toBe("SubtleCrypto"); + }); + it("should encrypt and decrypt", async () => { + const key = await crypto.subtle.generateKey( + { + name: "AES-GCM", + length: 256, + }, + true, + ["encrypt", "decrypt"], + ); + const iv = crypto.getRandomValues(new Uint8Array(12)); + const data = new TextEncoder().encode("Hello World!"); + const encrypted = await crypto.subtle.encrypt( + { + name: "AES-GCM", + iv, + }, + key, + data, + ); + const decrypted = await crypto.subtle.decrypt( + { + name: "AES-GCM", + iv, + }, + key, + encrypted, + ); + expect(new TextDecoder().decode(decrypted)).toBe("Hello World!"); + }); + + it("should verify and sign", async () => { + async function importKey(secret) { + return await crypto.subtle.importKey( + "raw", + new TextEncoder().encode(secret), + { name: "HMAC", hash: "SHA-256" }, + false, + ["sign", "verify"], + ); + } + + async function signResponse(message, secret) { + const key = await importKey(secret); + const signature = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(message)); + + // Convert ArrayBuffer to Base64 + return btoa(String.fromCharCode(...new Uint8Array(signature))); + } + + async function verifySignature(message, signature, secret) { + const key = await importKey(secret); + + // Convert Base64 to Uint8Array + const sigBuf = Uint8Array.from(atob(signature), c => c.charCodeAt(0)); + + return await crypto.subtle.verify("HMAC", key, sigBuf, new TextEncoder().encode(message)); + } + + const msg = `hello world`; + const SECRET = "secret"; + const signature = await signResponse(msg, SECRET); + + const isSigValid = await verifySignature(msg, signature, SECRET); + expect(isSigValid).toBe(true); + }); +}); + +describe("Ed25519", () => { + describe("generateKey", () => { + it("should return CryptoKeys without namedCurve in algorithm field", async () => { + const { publicKey, privateKey } = (await crypto.subtle.generateKey("Ed25519", true, [ + "sign", + "verify", + ])) as CryptoKeyPair; + expect(publicKey.algorithm!.name).toBe("Ed25519"); + // @ts-ignore + expect(publicKey.algorithm!.namedCurve).toBe(undefined); + expect(privateKey.algorithm!.name).toBe("Ed25519"); + // @ts-ignore + expect(privateKey.algorithm!.namedCurve).toBe(undefined); + }); + }); +}); diff --git a/test/js/web/encoding/text-decoder.test.js b/test/js/web/encoding/text-decoder.test.js new file mode 100644 index 000000000..abd4c2a72 --- /dev/null +++ b/test/js/web/encoding/text-decoder.test.js @@ -0,0 +1,243 @@ +import { expect, it, describe } from "bun:test"; +import { gc as gcTrace, withoutAggressiveGC } from "harness"; + +const getByteLength = str => { + // returns the byte length of an utf8 string + var s = str.length; + for (var i = str.length - 1; i >= 0; i--) { + var code = str.charCodeAt(i); + if (code > 0x7f && code <= 0x7ff) s++; + else if (code > 0x7ff && code <= 0xffff) s += 2; + if (code >= 0xdc00 && code <= 0xdfff) i--; //trail surrogate + } + return s; +}; + +describe("TextDecoder", () => { + it("should not crash on empty text", () => { + const decoder = new TextDecoder(); + gcTrace(true); + const fixtures = [new Uint8Array(), new Uint8Array([]), new Buffer(0), new ArrayBuffer(0), new Uint16Array(0)]; + + for (let input of fixtures) { + expect(decoder.decode(input)).toBe(""); + } + + // Cause a de-opt + try { + decoder.decode([NaN, Symbol("s")]); + } catch (e) {} + + // DOMJIT test + for (let i = 0; i < 90000; i++) { + decoder.decode(fixtures[0]); + } + + gcTrace(true); + }); + it("should decode ascii text", () => { + const decoder = new TextDecoder("latin1"); + gcTrace(true); + expect(decoder.encoding).toBe("windows-1252"); + gcTrace(true); + expect(decoder.decode(new Uint8Array([0x41, 0x42, 0x43]))).toBe("ABC"); + gcTrace(true); + + // hit the SIMD code path + const result = [ + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + ]; + gcTrace(true); + expect(decoder.decode(Uint8Array.from(result))).toBe(String.fromCharCode(...result)); + gcTrace(true); + }); + + it("should decode unicode text", () => { + const decoder = new TextDecoder(); + gcTrace(true); + const inputBytes = [226, 157, 164, 239, 184, 143, 32, 82, 101, 100, 32, 72, 101, 97, 114, 116]; + for (var repeat = 1; repeat < 100; repeat++) { + var text = `❤️ Red Heart`.repeat(repeat); + + var bytes = Array.from({ length: repeat }, () => inputBytes).flat(); + var decoded = decoder.decode(Uint8Array.from(bytes)); + expect(decoder.encoding).toBe("utf-8"); + expect(decoded).toBe(text); + gcTrace(true); + } + }); + + describe("typedArrays", () => { + var text = `ABC DEF GHI JKL MNO PQR STU VWX YZ ABC DEF GHI JKL MNO PQR STU V`; + var bytes = new TextEncoder().encode(text); + var decoder = new TextDecoder(); + for (let TypedArray of [ + Uint8Array, + Uint16Array, + Uint32Array, + Int8Array, + Int16Array, + Int32Array, + Float32Array, + Float64Array, + DataView, + BigInt64Array, + BigUint64Array, + ]) { + it(`should decode ${TypedArray.name}`, () => { + const decoded = decoder.decode(new TypedArray(bytes.buffer)); + expect(decoded).toBe(text); + }); + } + + it("DOMJIT call", () => { + const array = new Uint8Array(bytes.buffer); + withoutAggressiveGC(() => { + for (let i = 0; i < 100_000; i++) { + const decoded = decoder.decode(array); + expect(decoded).toBe(text); + } + }); + }); + }); + + it("should decode unicode text with multiple consecutive emoji", () => { + const decoder = new TextDecoder(); + const encoder = new TextEncoder(); + gcTrace(true); + var text = `❤️❤️❤️❤️❤️❤️ Red Heart`; + + text += ` ✨ Sparkles 🔥 Fire 😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳 😏 😒 😞 😔 😟 😕 🙁 ☹️ 😣 😖 😫 😩 🥺 😢 😭 😤 😠 😡 🤬 🤯 😳 🥵 🥶 😱 😨 😰`; + gcTrace(true); + expect(decoder.decode(encoder.encode(text))).toBe(text); + gcTrace(true); + const bytes = new Uint8Array(getByteLength(text) * 8); + gcTrace(true); + const amount = encoder.encodeInto(text, bytes); + gcTrace(true); + expect(decoder.decode(bytes.subarray(0, amount.written))).toBe(text); + gcTrace(true); + }); +}); + +it("truncated sequences", () => { + const assert_equals = (a, b) => expect(a).toBe(b); + + // Truncated sequences + assert_equals(new TextDecoder().decode(new Uint8Array([0xf0])), "\uFFFD"); + assert_equals(new TextDecoder().decode(new Uint8Array([0xf0, 0x9f])), "\uFFFD"); + assert_equals(new TextDecoder().decode(new Uint8Array([0xf0, 0x9f, 0x92])), "\uFFFD"); + + // Errors near end-of-queue + assert_equals(new TextDecoder().decode(new Uint8Array([0xf0, 0x9f, 0x41])), "\uFFFDA"); + assert_equals(new TextDecoder().decode(new Uint8Array([0xf0, 0x41, 0x42])), "\uFFFDAB"); + assert_equals(new TextDecoder().decode(new Uint8Array([0xf0, 0x41, 0xf0])), "\uFFFDA\uFFFD"); + assert_equals(new TextDecoder().decode(new Uint8Array([0xf0, 0x8f, 0x92])), "\uFFFD\uFFFD\uFFFD"); +}); diff --git a/test/js/web/encoding/text-encoder.test.js b/test/js/web/encoding/text-encoder.test.js new file mode 100644 index 000000000..3d271026d --- /dev/null +++ b/test/js/web/encoding/text-encoder.test.js @@ -0,0 +1,281 @@ +import { expect, it, describe } from "bun:test"; +import { gc as gcTrace, withoutAggressiveGC } from "harness"; + +const getByteLength = str => { + // returns the byte length of an utf8 string + var s = str.length; + for (var i = str.length - 1; i >= 0; i--) { + var code = str.charCodeAt(i); + if (code > 0x7f && code <= 0x7ff) s++; + else if (code > 0x7ff && code <= 0xffff) s += 2; + if (code >= 0xdc00 && code <= 0xdfff) i--; //trail surrogate + } + return s; +}; + +describe("TextEncoder", () => { + it("should encode latin1 text with non-ascii latin1 characters", () => { + var text = "H©ell©o Wor©ld!"; + + gcTrace(true); + const encoder = new TextEncoder(); + const encoded = encoder.encode(text); + gcTrace(true); + const into = new Uint8Array(100); + const out = encoder.encodeInto(text, into); + gcTrace(true); + expect(out.read).toBe(text.length); + + expect(encoded instanceof Uint8Array).toBe(true); + const result = [72, 194, 169, 101, 108, 108, 194, 169, 111, 32, 87, 111, 114, 194, 169, 108, 100, 33]; + for (let i = 0; i < result.length; i++) { + expect(encoded[i]).toBe(result[i]); + expect(into[i]).toBe(result[i]); + } + expect(encoded.length).toBe(result.length); + expect(out.written).toBe(result.length); + + const repeatCOunt = 16; + text = "H©ell©o Wor©ld!".repeat(repeatCOunt); + const byteLength = getByteLength(text); + const encoded2 = encoder.encode(text); + expect(encoded2.length).toBe(byteLength); + const into2 = new Uint8Array(byteLength); + const out2 = encoder.encodeInto(text, into2); + expect(out2.read).toBe(text.length); + expect(out2.written).toBe(byteLength); + expect(into2).toEqual(encoded2); + const repeatedResult = new Uint8Array(byteLength); + for (let i = 0; i < repeatCOunt; i++) { + repeatedResult.set(result, i * result.length); + } + expect(into2).toEqual(repeatedResult); + }); + + it("should encode latin1 text", async () => { + gcTrace(true); + const text = "Hello World!"; + const encoder = new TextEncoder(); + gcTrace(true); + const encoded = encoder.encode(text); + gcTrace(true); + expect(encoded instanceof Uint8Array).toBe(true); + expect(encoded.length).toBe(text.length); + gcTrace(true); + const result = [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]; + for (let i = 0; i < result.length; i++) { + expect(encoded[i]).toBe(result[i]); + } + + let t = [ + { + str: "\u009c\u0097", + expected: [194, 156, 194, 151], + }, + { + str: "世", + expected: [228, 184, 150], + }, + // Less than 0, out of range. + { + str: -1, + expected: [45, 49], + }, + // Greater than 0x10FFFF, out of range. + { + str: 0x110000, + expected: [49, 49, 49, 52, 49, 49, 50], + }, + // The Unicode replacement character. + { + str: "\uFFFD", + expected: [239, 191, 189], + }, + ]; + for (let { str, expected } of t) { + let utf8 = new TextEncoder().encode(str); + expect([...utf8]).toEqual(expected); + } + + expect([...new TextEncoder().encode(String.fromCodePoint(0))]).toEqual([0]); + + const fixture = new Uint8Array(await Bun.file(import.meta.dir + "/utf8-encoding-fixture.bin").arrayBuffer()); + const length = 0x110000; + let textEncoder = new TextEncoder(); + let textDecoder = new TextDecoder(); + let encodeOut = new Uint8Array(length * 4); + let encodeIntoOut = new Uint8Array(length * 4); + let encodeIntoBuffer = new Uint8Array(4); + let encodeDecodedOut = new Uint8Array(length * 4); + for (let i = 0, offset = 0; i < length; i++, offset += 4) { + const s = String.fromCodePoint(i); + const u = textEncoder.encode(s); + encodeOut.set(u, offset); + + textEncoder.encodeInto(s, encodeIntoBuffer); + encodeIntoOut.set(encodeIntoBuffer, offset); + + const decoded = textDecoder.decode(encodeIntoBuffer); + const encoded = textEncoder.encode(decoded); + encodeDecodedOut.set(encoded, offset); + } + + expect(encodeOut).toEqual(fixture); + expect(encodeIntoOut).toEqual(fixture); + expect(encodeOut).toEqual(encodeIntoOut); + expect(encodeDecodedOut).toEqual(encodeOut); + expect(encodeDecodedOut).toEqual(encodeIntoOut); + expect(encodeDecodedOut).toEqual(fixture); + + expect(() => textEncoder.encode(String.fromCodePoint(length + 1))).toThrow(); + }); + + it("should encode long latin1 text", async () => { + const text = "Hello World!".repeat(1000); + const encoder = new TextEncoder(); + gcTrace(true); + const encoded = encoder.encode(text); + gcTrace(true); + expect(encoded instanceof Uint8Array).toBe(true); + expect(encoded.length).toBe(text.length); + gcTrace(true); + const decoded = new TextDecoder().decode(encoded); + expect(decoded).toBe(text); + gcTrace(); + await new Promise(resolve => setTimeout(resolve, 1)); + gcTrace(); + expect(decoded).toBe(text); + }); + + it("should encode latin1 rope text", () => { + var text = "Hello"; + text += " "; + text += "World!"; + + gcTrace(true); + const encoder = new TextEncoder(); + const encoded = encoder.encode(text); + gcTrace(true); + const into = new Uint8Array(100); + const out = encoder.encodeInto(text, into); + gcTrace(true); + expect(out.read).toBe(text.length); + expect(out.written).toBe(encoded.length); + expect(encoded instanceof Uint8Array).toBe(true); + const result = [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]; + for (let i = 0; i < result.length; i++) { + expect(encoded[i]).toBe(result[i]); + expect(encoded[i]).toBe(into[i]); + } + expect(encoded.length).toBe(getByteLength(text)); + }); + + it("should encode latin1 rope text with non-ascii latin1 characters", () => { + var text = "H©ell©o"; + text += " "; + text += "Wor©ld!"; + + gcTrace(true); + const encoder = new TextEncoder(); + const encoded = encoder.encode(text); + gcTrace(true); + const into = new Uint8Array(100); + const out = encoder.encodeInto(text, into); + gcTrace(true); + expect(out.read).toBe(text.length); + + expect(encoded instanceof Uint8Array).toBe(true); + const result = [72, 194, 169, 101, 108, 108, 194, 169, 111, 32, 87, 111, 114, 194, 169, 108, 100, 33]; + + for (let i = 0; i < result.length; i++) { + expect(encoded[i]).toBe(into[i]); + expect(encoded[i]).toBe(result[i]); + } + expect(encoded.length).toBe(result.length); + expect(out.written).toBe(encoded.length); + + withoutAggressiveGC(() => { + for (let i = 0; i < 10_000; i++) { + expect(encoder.encodeInto(text, into)).toEqual(out); + } + }); + }); + + it("should encode utf-16 text", () => { + var text = `❤️ Red Heart + ✨ Sparkles + 🔥 Fire + `; + var encoder = new TextEncoder(); + var decoder = new TextDecoder(); + gcTrace(true); + expect(decoder.decode(encoder.encode(text))).toBe(text); + gcTrace(true); + }); + + // this test is from a web platform test in WebKit + describe("should use a unicode replacement character for invalid surrogate pairs", () => { + var bad = [ + { + encoding: "utf-16le", + input: [0x00, 0xd8], + expected: "\uFFFD", + name: "lone surrogate lead", + }, + { + encoding: "utf-16le", + input: [0x00, 0xdc], + expected: "\uFFFD", + name: "lone surrogate trail", + }, + { + encoding: "utf-16le", + input: [0x00, 0xd8, 0x00, 0x00], + expected: "\uFFFD\u0000", + name: "unmatched surrogate lead", + }, + { + encoding: "utf-16le", + input: [0x00, 0xdc, 0x00, 0x00], + expected: "\uFFFD\u0000", + name: "unmatched surrogate trail", + }, + { + encoding: "utf-16le", + input: [0x00, 0xdc, 0x00, 0xd8], + expected: "\uFFFD\uFFFD", + name: "swapped surrogate pair", + }, + ]; + + bad.forEach(function (t) { + it(t.encoding + " - " + t.name, () => { + gcTrace(true); + expect(new TextDecoder(t.encoding).decode(new Uint8Array(t.input))).toBe(t.expected); + expect(new TextDecoder(t.encoding).decode(new Uint16Array(new Uint8Array(t.input).buffer))).toBe(t.expected); + gcTrace(true); + }); + // test(function () { + // assert_throws_js(TypeError, function () { + // new TextDecoder(t.encoding, { fatal: true }).decode( + // new Uint8Array(t.input) + // ); + // }); + // }, t.encoding + " - " + t.name + " (fatal flag set)"); + }); + }); + + it("should encode utf-16 rope text", () => { + gcTrace(true); + var textReal = `❤️ Red Heart ✨ Sparkles 🔥 Fire`; + + var a = textReal.split(""); + var text = ""; + for (let j of a) { + text += j; + } + + var encoder = new TextEncoder(); + expect(new TextDecoder().decode(encoder.encode(text))).toBe(textReal); + }); +}); diff --git a/test/js/web/encoding/utf8-encoding-fixture.bin b/test/js/web/encoding/utf8-encoding-fixture.bin new file mode 100644 index 000000000..1f9ecf34f Binary files /dev/null and b/test/js/web/encoding/utf8-encoding-fixture.bin differ diff --git a/test/js/web/fetch/body-mixin-errors.test.ts b/test/js/web/fetch/body-mixin-errors.test.ts new file mode 100644 index 000000000..f57bbc56c --- /dev/null +++ b/test/js/web/fetch/body-mixin-errors.test.ts @@ -0,0 +1,17 @@ +import { it, describe, expect } from "bun:test"; + +describe("body-mixin-errors", () => { + it("should fail when bodyUsed", async () => { + var res = new Response("a"); + expect(res.bodyUsed).toBe(false); + await res.text(); + expect(res.bodyUsed).toBe(true); + + try { + await res.text(); + throw new Error("should not get here"); + } catch (e: any) { + expect(e.message).toBe("Body already used"); + } + }); +}); diff --git a/test/js/web/fetch/body-stream.test.ts b/test/js/web/fetch/body-stream.test.ts new file mode 100644 index 000000000..1cd932ed9 --- /dev/null +++ b/test/js/web/fetch/body-stream.test.ts @@ -0,0 +1,451 @@ +// @ts-nocheck +import { file, gc, serve, ServeOptions } from "bun"; +import { afterAll, afterEach, describe, expect, it, test } from "bun:test"; +import { readFileSync } from "fs"; + +var port = 0; + +{ + const BodyMixin = [ + Request.prototype.arrayBuffer, + Request.prototype.blob, + Request.prototype.text, + Request.prototype.json, + ]; + const useRequestObjectValues = [true, false]; + + for (let RequestPrototypeMixin of BodyMixin) { + for (let useRequestObject of useRequestObjectValues) { + describe(`Request.prototoype.${RequestPrototypeMixin.name}() ${ + useRequestObject ? "fetch(req)" : "fetch(url)" + }`, () => { + const inputFixture = [ + [JSON.stringify("Hello World"), JSON.stringify("Hello World")], + [JSON.stringify("Hello World 123"), Buffer.from(JSON.stringify("Hello World 123")).buffer], + [JSON.stringify("Hello World 456"), Buffer.from(JSON.stringify("Hello World 456"))], + [ + JSON.stringify("EXTREMELY LONG VERY LONG STRING WOW SO LONG YOU WONT BELIEVE IT! ".repeat(100)), + Buffer.from( + JSON.stringify("EXTREMELY LONG VERY LONG STRING WOW SO LONG YOU WONT BELIEVE IT! ".repeat(100)), + ), + ], + [ + JSON.stringify("EXTREMELY LONG 🔥 UTF16 🔥 VERY LONG STRING WOW SO LONG YOU WONT BELIEVE IT! ".repeat(100)), + Buffer.from( + JSON.stringify( + "EXTREMELY LONG 🔥 UTF16 🔥 VERY LONG STRING WOW SO LONG YOU WONT BELIEVE IT! ".repeat(100), + ), + ), + ], + ]; + + for (const [name, input] of inputFixture) { + test(`${name.slice(0, Math.min(name.length ?? name.byteLength, 64))}`, async () => { + await runInServer( + { + async fetch(req) { + var result = await RequestPrototypeMixin.call(req); + if (RequestPrototypeMixin === Request.prototype.json) { + result = JSON.stringify(result); + } + if (typeof result === "string") { + expect(result.length).toBe(name.length); + expect(result).toBe(name); + } else if (result && result instanceof Blob) { + expect(result.size).toBe(new TextEncoder().encode(name).byteLength); + expect(await result.text()).toBe(name); + } else { + expect(result.byteLength).toBe(Buffer.from(input).byteLength); + expect(Bun.SHA1.hash(result, "base64")).toBe(Bun.SHA1.hash(input, "base64")); + } + return new Response(result, { + headers: req.headers, + }); + }, + }, + async url => { + var response; + + // once, then batch of 5 + + if (useRequestObject) { + response = await fetch( + new Request({ + body: input, + method: "POST", + url: url, + headers: { + "content-type": "text/plain", + }, + }), + ); + } else { + response = await fetch(url, { + body: input, + method: "POST", + headers: { + "content-type": "text/plain", + }, + }); + } + + expect(response.status).toBe(200); + expect(response.headers.get("content-length")).toBe(String(Buffer.from(input).byteLength)); + expect(response.headers.get("content-type")).toBe("text/plain"); + expect(await response.text()).toBe(name); + + var promises = new Array(5); + for (let i = 0; i < 5; i++) { + if (useRequestObject) { + promises[i] = await fetch( + new Request({ + body: input, + method: "POST", + url: url, + headers: { + "content-type": "text/plain", + "x-counter": i, + }, + }), + ); + } else { + promises[i] = await fetch(url, { + body: input, + method: "POST", + headers: { + "content-type": "text/plain", + "x-counter": i, + }, + }); + } + } + + const results = await Promise.all(promises); + for (let i = 0; i < 5; i++) { + const response = results[i]; + expect(response.status).toBe(200); + expect(response.headers.get("content-length")).toBe(String(Buffer.from(input).byteLength)); + expect(response.headers.get("content-type")).toBe("text/plain"); + expect(response.headers.get("x-counter")).toBe(String(i)); + expect(await response.text()).toBe(name); + } + }, + ); + }); + } + }); + } + } +} + +var existingServer; +async function runInServer(opts: ServeOptions, cb: (url: string) => void | Promise) { + var server; + const handler = { + ...opts, + port: port++, + fetch(req) { + try { + return opts.fetch(req); + } catch (e) { + console.error(e.message); + console.log(e.stack); + throw e; + } + }, + error(err) { + console.log(err.message); + console.log(err.stack); + throw err; + }, + }; + + if (!existingServer) { + existingServer = server = Bun.serve(handler); + } else { + server = existingServer; + server.reload(handler); + } + + try { + await cb(`http://${server.hostname}:${server.port}`); + } catch (e) { + throw e; + } finally { + } +} + +afterAll(() => { + existingServer && existingServer.close(); + existingServer = null; +}); + +function fillRepeating(dstBuffer, start, end) { + let len = dstBuffer.length, + sLen = end - start, + p = sLen; + while (p < len) { + if (p + sLen > len) sLen = len - p; + dstBuffer.copyWithin(p, start, sLen); + p += sLen; + sLen <<= 1; + } +} + +function gc() { + Bun.gc(true); +} + +describe("reader", function () { + try { + // - empty + // - 1 byte + // - less than the InlineBlob limit + // - multiple chunks + // - backpressure + for (let inputLength of [0, 1, 2, 12, 95, 1024, 1024 * 1024, 1024 * 1024 * 2]) { + var bytes = new Uint8Array(inputLength); + { + const chunk = Math.min(bytes.length, 256); + for (var i = 0; i < chunk; i++) { + bytes[i] = 255 - i; + } + } + + if (bytes.length > 255) fillRepeating(bytes, 0, bytes.length); + + for (const huge_ of [ + bytes, + bytes.buffer, + new DataView(bytes.buffer), + new Int8Array(bytes), + new Blob([bytes]), + + new Uint16Array(bytes), + new Uint32Array(bytes), + new Float64Array(bytes), + + new Int16Array(bytes), + new Int32Array(bytes), + new Float32Array(bytes), + + // make sure we handle subarray() as expected when reading + // typed arrays from native code + new Int16Array(bytes).subarray(1), + new Int16Array(bytes).subarray(0, new Int16Array(bytes).byteLength - 1), + new Int32Array(bytes).subarray(1), + new Int32Array(bytes).subarray(0, new Int32Array(bytes).byteLength - 1), + new Float32Array(bytes).subarray(1), + new Float32Array(bytes).subarray(0, new Float32Array(bytes).byteLength - 1), + new Int16Array(bytes).subarray(0, 1), + new Int32Array(bytes).subarray(0, 1), + new Float32Array(bytes).subarray(0, 1), + ]) { + gc(); + const thisArray = huge_; + it(`works with ${thisArray.constructor.name}(${ + thisArray.byteLength ?? thisArray.size + }:${inputLength}) via req.body.getReader() in chunks`, async () => { + var huge = thisArray; + var called = false; + gc(); + + const expectedHash = + huge instanceof Blob + ? Bun.SHA1.hash(new Uint8Array(await huge.arrayBuffer()), "base64") + : Bun.SHA1.hash(huge, "base64"); + const expectedSize = huge instanceof Blob ? huge.size : huge.byteLength; + + const out = await runInServer( + { + async fetch(req) { + try { + expect(req.headers.get("x-custom")).toBe("hello"); + expect(req.headers.get("content-type")).toBe("text/plain"); + expect(req.headers.get("user-agent")).toBe(navigator.userAgent); + + gc(); + expect(req.headers.get("x-custom")).toBe("hello"); + expect(req.headers.get("content-type")).toBe("text/plain"); + expect(req.headers.get("user-agent")).toBe(navigator.userAgent); + + var reader = req.body.getReader(); + called = true; + var buffers = []; + while (true) { + var { done, value } = await reader.read(); + if (done) break; + buffers.push(value); + } + const out = new Blob(buffers); + gc(); + expect(out.size).toBe(expectedSize); + expect(Bun.SHA1.hash(await out.arrayBuffer(), "base64")).toBe(expectedHash); + expect(req.headers.get("x-custom")).toBe("hello"); + expect(req.headers.get("content-type")).toBe("text/plain"); + expect(req.headers.get("user-agent")).toBe(navigator.userAgent); + gc(); + return new Response(out, { + headers: req.headers, + }); + } catch (e) { + console.error(e); + throw e; + } + }, + }, + async url => { + gc(); + const response = await fetch(url, { + body: huge, + method: "POST", + headers: { + "content-type": "text/plain", + "x-custom": "hello", + "x-typed-array": thisArray.constructor.name, + }, + }); + huge = undefined; + expect(response.status).toBe(200); + const response_body = new Uint8Array(await response.arrayBuffer()); + + expect(response_body.byteLength).toBe(expectedSize); + expect(Bun.SHA1.hash(response_body, "base64")).toBe(expectedHash); + + gc(); + expect(response.headers.get("content-type")).toBe("text/plain"); + gc(); + }, + ); + expect(called).toBe(true); + gc(); + return out; + }); + + for (let isDirectStream of [true, false]) { + const positions = ["begin", "end"]; + const inner = thisArray => { + for (let position of positions) { + it(`streaming back ${thisArray.constructor.name}(${ + thisArray.byteLength ?? thisArray.size + }:${inputLength}) starting request.body.getReader() at ${position}`, async () => { + var huge = thisArray; + var called = false; + gc(); + + const expectedHash = + huge instanceof Blob + ? Bun.SHA1.hash(new Uint8Array(await huge.arrayBuffer()), "base64") + : Bun.SHA1.hash(huge, "base64"); + const expectedSize = huge instanceof Blob ? huge.size : huge.byteLength; + + const out = await runInServer( + { + async fetch(req) { + try { + var reader; + + if (position === "begin") { + reader = req.body.getReader(); + } + + if (position === "end") { + await 1; + reader = req.body.getReader(); + } + + expect(req.headers.get("x-custom")).toBe("hello"); + expect(req.headers.get("content-type")).toBe("text/plain"); + expect(req.headers.get("user-agent")).toBe(navigator.userAgent); + + gc(); + expect(req.headers.get("x-custom")).toBe("hello"); + expect(req.headers.get("content-type")).toBe("text/plain"); + expect(req.headers.get("user-agent")).toBe(navigator.userAgent); + + const direct = { + type: "direct", + async pull(controller) { + while (true) { + const { done, value } = await reader.read(); + if (done) { + called = true; + controller.end(); + + return; + } + controller.write(value); + } + }, + }; + + const web = { + async pull(controller) { + while (true) { + const { done, value } = await reader.read(); + if (done) { + called = true; + controller.close(); + return; + } + controller.enqueue(value); + } + }, + }; + + return new Response(new ReadableStream(isDirectStream ? direct : web), { + headers: req.headers, + }); + } catch (e) { + console.error(e); + throw e; + } + }, + }, + async url => { + gc(); + const response = await fetch(url, { + body: huge, + method: "POST", + headers: { + "content-type": "text/plain", + "x-custom": "hello", + "x-typed-array": thisArray.constructor.name, + }, + }); + huge = undefined; + expect(response.status).toBe(200); + const response_body = new Uint8Array(await response.arrayBuffer()); + + expect(response_body.byteLength).toBe(expectedSize); + expect(Bun.SHA1.hash(response_body, "base64")).toBe(expectedHash); + + gc(); + if (!response.headers.has("content-type")) { + console.error(Object.fromEntries(response.headers.entries())); + } + + expect(response.headers.get("content-type")).toBe("text/plain"); + gc(); + }, + ); + expect(called).toBe(true); + gc(); + return out; + }); + } + }; + + if (isDirectStream) { + describe(" direct stream", () => inner(thisArray)); + } else { + describe("default stream", () => inner(thisArray)); + } + } + } + } + } catch (e) { + console.error(e); + throw e; + } +}); diff --git a/test/js/web/fetch/fetch-gzip.test.ts b/test/js/web/fetch/fetch-gzip.test.ts new file mode 100644 index 000000000..01eedc54a --- /dev/null +++ b/test/js/web/fetch/fetch-gzip.test.ts @@ -0,0 +1,181 @@ +import { concatArrayBuffers } from "bun"; +import { it, describe, expect } from "bun:test"; +import fs from "fs"; +import { gc, gcTick } from "harness"; + +it("fetch() with a buffered gzip response works (one chunk)", async () => { + var server = Bun.serve({ + port: 6025, + + async fetch(req) { + gcTick(true); + return new Response(require("fs").readFileSync(import.meta.dir + "/fixture.html.gz"), { + headers: { + "Content-Encoding": "gzip", + "Content-Type": "text/html; charset=utf-8", + }, + }); + }, + }); + gcTick(true); + + const res = await fetch(`http://${server.hostname}:${server.port}`, { verbose: true }); + gcTick(true); + const arrayBuffer = await res.arrayBuffer(); + const clone = new Buffer(arrayBuffer); + gcTick(true); + await (async function () { + const second = new Buffer(await Bun.file(import.meta.dir + "/fixture.html").arrayBuffer()); + gcTick(true); + expect(second.equals(clone)).toBe(true); + })(); + gcTick(true); + server.stop(); +}); + +it("fetch() with a redirect that returns a buffered gzip response works (one chunk)", async () => { + var server = Bun.serve({ + port: 6020, + + async fetch(req) { + if (req.url.endsWith("/redirect")) + return new Response(await Bun.file(import.meta.dir + "/fixture.html.gz").arrayBuffer(), { + headers: { + "Content-Encoding": "gzip", + "Content-Type": "text/html; charset=utf-8", + }, + }); + + return Response.redirect("/redirect"); + }, + }); + + const res = await fetch(`http://${server.hostname}:${server.port}/hey`, { verbose: true }); + const arrayBuffer = await res.arrayBuffer(); + expect( + new Buffer(arrayBuffer).equals(new Buffer(await Bun.file(import.meta.dir + "/fixture.html").arrayBuffer())), + ).toBe(true); + server.stop(); +}); + +it("fetch() with a protocol-relative redirect that returns a buffered gzip response works (one chunk)", async () => { + const server = Bun.serve({ + port: 5018, + + async fetch(req, server) { + if (req.url.endsWith("/redirect")) + return new Response(await Bun.file(import.meta.dir + "/fixture.html.gz").arrayBuffer(), { + headers: { + "Content-Encoding": "gzip", + "Content-Type": "text/html; charset=utf-8", + }, + }); + + return Response.redirect(`://${server.hostname}:${server.port}/redirect`); + }, + }); + + const res = await fetch(`http://${server.hostname}:${server.port}/hey`, { verbose: true }); + expect(res.url).toBe(`http://${server.hostname}:${server.port}/redirect`); + expect(res.redirected).toBe(true); + expect(res.status).toBe(200); + const arrayBuffer = await res.arrayBuffer(); + expect( + new Buffer(arrayBuffer).equals(new Buffer(await Bun.file(import.meta.dir + "/fixture.html").arrayBuffer())), + ).toBe(true); + + server.stop(); +}); + +it("fetch() with a gzip response works (one chunk, streamed, with a delay", async () => { + var server = Bun.serve({ + port: 6081, + + fetch(req) { + return new Response( + new ReadableStream({ + type: "direct", + async pull(controller) { + await 2; + + const buffer = await Bun.file(import.meta.dir + "/fixture.html.gz").arrayBuffer(); + controller.write(buffer); + controller.close(); + }, + }), + { + headers: { + "Content-Encoding": "gzip", + "Content-Type": "text/html; charset=utf-8", + "Content-Length": "1", + }, + }, + ); + }, + }); + + const res = await fetch(`http://${server.hostname}:${server.port}`, {}); + const arrayBuffer = await res.arrayBuffer(); + expect( + new Buffer(arrayBuffer).equals(new Buffer(await Bun.file(import.meta.dir + "/fixture.html").arrayBuffer())), + ).toBe(true); + server.stop(); +}); + +it("fetch() with a gzip response works (multiple chunks, TCP server", async done => { + const compressed = await Bun.file(import.meta.dir + "/fixture.html.gz").arrayBuffer(); + var socketToClose; + const server = Bun.listen({ + port: 4024, + hostname: "0.0.0.0", + socket: { + async open(socket) { + socketToClose = socket; + + var corked: any[] = []; + var cork = true; + async function write(chunk) { + await new Promise((resolve, reject) => { + if (cork) { + corked.push(chunk); + } + + if (!cork && corked.length) { + socket.write(corked.join("")); + corked.length = 0; + } + + if (!cork) { + socket.write(chunk); + } + + resolve(); + }); + } + await write("HTTP/1.1 200 OK\r\n"); + await write("Content-Encoding: gzip\r\n"); + await write("Content-Type: text/html; charset=utf-8\r\n"); + await write("Content-Length: " + compressed.byteLength + "\r\n"); + await write("X-WTF: " + "lol".repeat(1000) + "\r\n"); + await write("\r\n"); + for (var i = 100; i < compressed.byteLength; i += 100) { + cork = false; + await write(compressed.slice(i - 100, i)); + } + await write(compressed.slice(i - 100)); + socket.flush(); + }, + drain(socket) {}, + }, + }); + await 1; + + const res = await fetch(`http://${server.hostname}:${server.port}`, {}); + const arrayBuffer = await res.arrayBuffer(); + expect( + new Buffer(arrayBuffer).equals(new Buffer(await Bun.file(import.meta.dir + "/fixture.html").arrayBuffer())), + ).toBe(true); + socketToClose.end(); + server.stop(); + done(); +}); diff --git a/test/js/web/fetch/fetch.js.txt b/test/js/web/fetch/fetch.js.txt new file mode 100644 index 000000000..5a9b52fcf --- /dev/null +++ b/test/js/web/fetch/fetch.js.txt @@ -0,0 +1,46 @@ + + + + Example Domain + + + + + + + + +
+

Example Domain

+

This domain is for use in illustrative examples in documents. You may use this + domain in literature without prior coordination or asking for permission.

+

More information...

+
+ + diff --git a/test/js/web/fetch/fetch.test.ts b/test/js/web/fetch/fetch.test.ts new file mode 100644 index 000000000..1185dbd55 --- /dev/null +++ b/test/js/web/fetch/fetch.test.ts @@ -0,0 +1,935 @@ +import { serve, sleep } from "bun"; +import { afterAll, afterEach, beforeAll, describe, expect, it, beforeEach } from "bun:test"; +import { chmodSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "fs"; +import { mkfifo } from "mkfifo"; +import { tmpdir } from "os"; +import { join } from "path"; +import { gc, withoutAggressiveGC } from "harness"; + +const tmp_dir = mkdtempSync(join(realpathSync(tmpdir()), "fetch.test")); + +const fixture = readFileSync(join(import.meta.dir, "fetch.js.txt"), "utf8"); + +let server; +function startServer({ fetch, ...options }) { + server = serve({ + ...options, + fetch, + port: 0, + }); +} + +afterEach(() => { + server?.stop?.(true); +}); + +afterAll(() => { + rmSync(tmp_dir, { force: true, recursive: true }); +}); + +const payload = new Uint8Array(1024 * 1024 * 2); +crypto.getRandomValues(payload); + +describe("AbortSignal", () => { + beforeEach(() => { + startServer({ + async fetch(request) { + if (request.url.endsWith("/nodelay")) { + return new Response("Hello"); + } + if (request.url.endsWith("/stream")) { + const reader = request.body.getReader(); + const body = new ReadableStream({ + async pull(controller) { + if (!reader) controller.close(); + const { done, value } = await reader.read(); + // When no more data needs to be consumed, close the stream + if (done) { + controller.close(); + return; + } + // Enqueue the next data chunk into our target stream + controller.enqueue(value); + }, + }); + return new Response(body); + } + if (request.method.toUpperCase() === "POST") { + const body = await request.text(); + return new Response(body); + } + await sleep(15); + return new Response("Hello"); + }, + }); + }); + afterEach(() => { + server?.stop?.(true); + }); + + it("AbortError", async () => { + const controller = new AbortController(); + const signal = controller.signal; + + expect(async () => { + async function manualAbort() { + await sleep(1); + controller.abort(); + } + await Promise.all([ + fetch(`http://127.0.0.1:${server.port}`, { signal: signal }).then(res => res.text()), + manualAbort(), + ]); + }).toThrow(new DOMException("The operation was aborted.")); + }); + + it("AbortAfterFinish", async () => { + const controller = new AbortController(); + const signal = controller.signal; + + await fetch(`http://127.0.0.1:${server.port}/nodelay`, { signal: signal }).then(async res => + expect(await res.text()).toBe("Hello"), + ); + controller.abort(); + }); + + it("AbortErrorWithReason", async () => { + const controller = new AbortController(); + const signal = controller.signal; + + expect(async () => { + async function manualAbort() { + await sleep(10); + controller.abort(new Error("My Reason")); + } + await Promise.all([ + fetch(`http://127.0.0.1:${server.port}`, { signal: signal }).then(res => res.text()), + manualAbort(), + ]); + }).toThrow("My Reason"); + }); + + it("AbortErrorEventListener", async () => { + const controller = new AbortController(); + const signal = controller.signal; + signal.addEventListener("abort", ev => { + const target = ev.currentTarget; + expect(target).toBeDefined(); + expect(target.aborted).toBe(true); + expect(target.reason).toBeDefined(); + expect(target.reason.name).toBe("AbortError"); + }); + + expect(async () => { + async function manualAbort() { + await sleep(10); + controller.abort(); + } + await Promise.all([ + fetch(`http://127.0.0.1:${server.port}`, { signal: signal }).then(res => res.text()), + manualAbort(), + ]); + }).toThrow(new DOMException("The operation was aborted.")); + }); + + it("AbortErrorWhileUploading", async () => { + const controller = new AbortController(); + + expect(async () => { + await fetch(`http://localhost:${server.port}`, { + method: "POST", + body: new ReadableStream({ + pull(event_controller) { + event_controller.enqueue(new Uint8Array([1, 2, 3, 4])); + //this will abort immediately should abort before connected + controller.abort(); + }, + }), + signal: controller.signal, + }); + }).toThrow(new DOMException("The operation was aborted.")); + }); + + it("TimeoutError", async () => { + const signal = AbortSignal.timeout(10); + + try { + await fetch(`http://127.0.0.1:${server.port}`, { signal: signal }).then(res => res.text()); + expect(() => {}).toThrow(); + } catch (ex: any) { + expect(ex.name).toBe("TimeoutError"); + } + }); + + it("Request", async () => { + const controller = new AbortController(); + const signal = controller.signal; + async function manualAbort() { + await sleep(10); + controller.abort(); + } + + try { + const request = new Request(`http://127.0.0.1:${server.port}`, { signal }); + await Promise.all([fetch(request).then(res => res.text()), manualAbort()]); + expect(() => {}).toThrow(); + } catch (ex: any) { + expect(ex.name).toBe("AbortError"); + } + }); +}); + +describe("Headers", () => { + it(".toJSON", () => { + const headers = new Headers({ + "content-length": "123", + "content-type": "text/plain", + "x-another-custom-header": "Hello World", + "x-custom-header": "Hello World", + }); + expect(JSON.stringify(headers.toJSON(), null, 2)).toBe( + JSON.stringify(Object.fromEntries(headers.entries()), null, 2), + ); + }); + + it(".getSetCookie() with object", () => { + const headers = new Headers({ + "content-length": "123", + "content-type": "text/plain", + "x-another-custom-header": "Hello World", + "x-custom-header": "Hello World", + "Set-Cookie": "foo=bar; Path=/; HttpOnly", + }); + expect(headers.count).toBe(5); + expect(headers.getAll("set-cookie")).toEqual(["foo=bar; Path=/; HttpOnly"]); + }); + + it(".getSetCookie() with array", () => { + const headers = new Headers([ + ["content-length", "123"], + ["content-type", "text/plain"], + ["x-another-custom-header", "Hello World"], + ["x-custom-header", "Hello World"], + ["Set-Cookie", "foo=bar; Path=/; HttpOnly"], + ["Set-Cookie", "foo2=bar2; Path=/; HttpOnly"], + ]); + expect(headers.count).toBe(6); + expect(headers.getAll("set-cookie")).toEqual(["foo=bar; Path=/; HttpOnly", "foo2=bar2; Path=/; HttpOnly"]); + }); + + it("Set-Cookies init", () => { + const headers = new Headers([ + ["Set-Cookie", "foo=bar"], + ["Set-Cookie", "bar=baz"], + ["X-bun", "abc"], + ["X-bun", "def"], + ]); + const actual = [...headers]; + expect(actual).toEqual([ + ["set-cookie", "foo=bar"], + ["set-cookie", "bar=baz"], + ["x-bun", "abc, def"], + ]); + expect([...headers.values()]).toEqual(["foo=bar", "bar=baz", "abc, def"]); + }); + + it("Headers append multiple", () => { + const headers = new Headers([ + ["Set-Cookie", "foo=bar"], + ["X-bun", "foo"], + ]); + headers.append("Set-Cookie", "bar=baz"); + headers.append("x-bun", "bar"); + const actual = [...headers]; + + // we do not preserve the order + // which is kind of bad + expect(actual).toEqual([ + ["set-cookie", "foo=bar"], + ["set-cookie", "bar=baz"], + ["x-bun", "foo, bar"], + ]); + }); + + it("append duplicate set cookie key", () => { + const headers = new Headers([["Set-Cookie", "foo=bar"]]); + headers.append("set-Cookie", "foo=baz"); + headers.append("Set-cookie", "baz=bar"); + const actual = [...headers]; + expect(actual).toEqual([ + ["set-cookie", "foo=baz"], + ["set-cookie", "baz=bar"], + ]); + }); + + it("set duplicate cookie key", () => { + const headers = new Headers([["Set-Cookie", "foo=bar"]]); + headers.set("set-Cookie", "foo=baz"); + headers.set("set-cookie", "bar=qat"); + const actual = [...headers]; + expect(actual).toEqual([ + ["set-cookie", "foo=baz"], + ["set-cookie", "bar=qat"], + ]); + }); +}); + +describe("fetch", () => { + const urls = [ + "https://example.com", + "http://example.com", + new URL("https://example.com"), + new Request({ url: "https://example.com" }), + { toString: () => "https://example.com" }, + ]; + for (let url of urls) { + gc(); + let name; + if (url instanceof URL) { + name = "URL: " + url; + } else if (url instanceof Request) { + name = "Request: " + url.url; + } else if (url.hasOwnProperty("toString")) { + name = "Object: " + url.toString(); + } else { + name = url; + } + it(name, async () => { + gc(); + const response = await fetch(url, { verbose: true }); + gc(); + const text = await response.text(); + gc(); + expect(fixture).toBe(text); + }); + } + + it('redirect: "manual"', async () => { + startServer({ + fetch(req) { + return new Response(null, { + status: 302, + headers: { + Location: "https://example.com", + }, + }); + }, + }); + const response = await fetch(`http://${server.hostname}:${server.port}`, { + redirect: "manual", + }); + expect(response.status).toBe(302); + expect(response.headers.get("location")).toBe("https://example.com"); + expect(response.redirected).toBe(true); + }); + + it('redirect: "follow"', async () => { + startServer({ + fetch(req) { + return new Response(null, { + status: 302, + headers: { + Location: "https://example.com", + }, + }); + }, + }); + const response = await fetch(`http://${server.hostname}:${server.port}`, { + redirect: "follow", + }); + expect(response.status).toBe(200); + expect(response.headers.get("location")).toBe(null); + expect(response.redirected).toBe(true); + }); + + it("provide body", async () => { + startServer({ + fetch(req) { + return new Response(req.body); + }, + host: "localhost", + }); + + // POST with body + const url = `http://${server.hostname}:${server.port}`; + const response = await fetch(url, { method: "POST", body: "buntastic" }); + expect(response.status).toBe(200); + expect(await response.text()).toBe("buntastic"); + }); + + ["GET", "HEAD", "OPTIONS"].forEach(method => + it(`fail on ${method} with body`, async () => { + const url = `http://${server.hostname}:${server.port}`; + expect(async () => { + await fetch(url, { body: "buntastic" }); + }).toThrow("fetch() request with GET/HEAD/OPTIONS method cannot have body."); + }), + ); +}); + +it("simultaneous HTTPS fetch", async () => { + const urls = ["https://example.com", "https://www.example.com"]; + for (let batch = 0; batch < 4; batch++) { + const promises = new Array(20); + for (let i = 0; i < 20; i++) { + promises[i] = fetch(urls[i % 2]); + } + const result = await Promise.all(promises); + expect(result.length).toBe(20); + for (let i = 0; i < 20; i++) { + expect(result[i].status).toBe(200); + expect(await result[i].text()).toBe(fixture); + } + } +}); + +it("website with tlsextname", async () => { + // irony + await fetch("https://bun.sh", { method: "HEAD" }); +}); + +function testBlobInterface(blobbyConstructor, hasBlobFn?) { + for (let withGC of [false, true]) { + for (let jsonObject of [ + { hello: true }, + { + hello: "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳", + }, + ]) { + it(`${jsonObject.hello === true ? "latin1" : "utf16"} json${withGC ? " (with gc) " : ""}`, async () => { + if (withGC) gc(); + var response = blobbyConstructor(JSON.stringify(jsonObject)); + if (withGC) gc(); + expect(JSON.stringify(await response.json())).toBe(JSON.stringify(jsonObject)); + if (withGC) gc(); + }); + + it(`${jsonObject.hello === true ? "latin1" : "utf16"} arrayBuffer -> json${ + withGC ? " (with gc) " : "" + }`, async () => { + if (withGC) gc(); + var response = blobbyConstructor(new TextEncoder().encode(JSON.stringify(jsonObject))); + if (withGC) gc(); + expect(JSON.stringify(await response.json())).toBe(JSON.stringify(jsonObject)); + if (withGC) gc(); + }); + + it(`${jsonObject.hello === true ? "latin1" : "utf16"} arrayBuffer -> invalid json${ + withGC ? " (with gc) " : "" + }`, async () => { + if (withGC) gc(); + var response = blobbyConstructor( + new TextEncoder().encode(JSON.stringify(jsonObject) + " NOW WE ARE INVALID JSON"), + ); + if (withGC) gc(); + var failed = false; + try { + await response.json(); + } catch (e) { + failed = true; + } + expect(failed).toBe(true); + if (withGC) gc(); + }); + + it(`${jsonObject.hello === true ? "latin1" : "utf16"} text${withGC ? " (with gc) " : ""}`, async () => { + if (withGC) gc(); + var response = blobbyConstructor(JSON.stringify(jsonObject)); + if (withGC) gc(); + expect(await response.text()).toBe(JSON.stringify(jsonObject)); + if (withGC) gc(); + }); + + it(`${jsonObject.hello === true ? "latin1" : "utf16"} arrayBuffer -> text${ + withGC ? " (with gc) " : "" + }`, async () => { + if (withGC) gc(); + var response = blobbyConstructor(new TextEncoder().encode(JSON.stringify(jsonObject))); + if (withGC) gc(); + expect(await response.text()).toBe(JSON.stringify(jsonObject)); + if (withGC) gc(); + }); + + it(`${jsonObject.hello === true ? "latin1" : "utf16"} arrayBuffer${withGC ? " (with gc) " : ""}`, async () => { + if (withGC) gc(); + + var response = blobbyConstructor(JSON.stringify(jsonObject)); + if (withGC) gc(); + + const bytes = new TextEncoder().encode(JSON.stringify(jsonObject)); + if (withGC) gc(); + + const compare = new Uint8Array(await response.arrayBuffer()); + if (withGC) gc(); + + withoutAggressiveGC(() => { + for (let i = 0; i < compare.length; i++) { + if (withGC) gc(); + + expect(compare[i]).toBe(bytes[i]); + if (withGC) gc(); + } + }); + if (withGC) gc(); + }); + + it(`${jsonObject.hello === true ? "latin1" : "utf16"} arrayBuffer -> arrayBuffer${ + withGC ? " (with gc) " : "" + }`, async () => { + if (withGC) gc(); + + var response = blobbyConstructor(new TextEncoder().encode(JSON.stringify(jsonObject))); + if (withGC) gc(); + + const bytes = new TextEncoder().encode(JSON.stringify(jsonObject)); + if (withGC) gc(); + + const compare = new Uint8Array(await response.arrayBuffer()); + if (withGC) gc(); + + withoutAggressiveGC(() => { + for (let i = 0; i < compare.length; i++) { + if (withGC) gc(); + + expect(compare[i]).toBe(bytes[i]); + if (withGC) gc(); + } + }); + if (withGC) gc(); + }); + + hasBlobFn && + it(`${jsonObject.hello === true ? "latin1" : "utf16"} blob${withGC ? " (with gc) " : ""}`, async () => { + if (withGC) gc(); + const text = JSON.stringify(jsonObject); + var response = blobbyConstructor(text); + if (withGC) gc(); + const size = new TextEncoder().encode(text).byteLength; + if (withGC) gc(); + const blobed = await response.blob(); + if (withGC) gc(); + expect(blobed instanceof Blob).toBe(true); + if (withGC) gc(); + expect(blobed.size).toBe(size); + if (withGC) gc(); + blobed.type = ""; + if (withGC) gc(); + expect(blobed.type).toBe(""); + if (withGC) gc(); + blobed.type = "application/json"; + if (withGC) gc(); + expect(blobed.type).toBe("application/json"); + if (withGC) gc(); + const out = await blobed.text(); + expect(out).toBe(text); + if (withGC) gc(); + await new Promise(resolve => setTimeout(resolve, 1)); + if (withGC) gc(); + expect(out).toBe(text); + const first = await blobed.arrayBuffer(); + const initial = first[0]; + first[0] = 254; + const second = await blobed.arrayBuffer(); + expect(second[0]).toBe(initial); + expect(first[0]).toBe(254); + }); + } + } +} + +describe("Bun.file", () => { + let count = 0; + testBlobInterface(data => { + const blob = new Blob([data]); + const buffer = Bun.peek(blob.arrayBuffer()); + const path = join(tmp_dir, `tmp-${count++}.bytes`); + writeFileSync(path, buffer); + const file = Bun.file(path); + expect(blob.size).toBe(file.size); + return file; + }); + + it("size is Infinity on a fifo", () => { + const path = join(tmp_dir, "test-fifo"); + mkfifo(path); + const { size } = Bun.file(path); + expect(size).toBe(Infinity); + }); + + function forEachMethod(fn, skip?) { + const method = ["arrayBuffer", "text", "json"]; + for (const m of method) { + (skip ? it.skip : it)(m, fn(m)); + } + } + + describe("bad permissions throws", () => { + const path = join(tmp_dir, "my-new-file"); + beforeAll(async () => { + await Bun.write(path, "hey"); + chmodSync(path, 0o000); + }); + + forEachMethod( + m => () => { + const file = Bun.file(path); + expect(async () => await file[m]()).toThrow("Permission denied"); + }, + () => { + try { + readFileSync(path); + } catch { + return false; + } + return true; + }, + ); + }); + + describe("non-existent file throws", () => { + const path = join(tmp_dir, "does-not-exist"); + + forEachMethod(m => async () => { + const file = Bun.file(path); + expect(async () => await file[m]()).toThrow("No such file or directory"); + }); + }); +}); + +describe("Blob", () => { + testBlobInterface(data => new Blob([data])); + + var blobConstructorValues = [ + ["123", "456"], + ["123", 456], + ["123", "456", "789"], + ["123", 456, 789], + [1, 2, 3, 4, 5, 6, 7, 8, 9], + [Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 9])], + [Uint8Array.from([1, 2, 3, 4]), "5678", 9], + [new Blob([Uint8Array.from([1, 2, 3, 4])]), "5678", 9], + [ + new Blob([ + new TextEncoder().encode( + "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳", + ), + ]), + ], + [ + new TextEncoder().encode( + "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳", + ), + ], + ]; + + var expected = [ + "123456", + "123456", + "123456789", + "123456789", + "123456789", + "\x01\x02\x03\x04\x05\x06\x07\t", + "\x01\x02\x03\x0456789", + "\x01\x02\x03\x0456789", + "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳", + "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳", + ]; + + it(`blobConstructorValues`, async () => { + for (let i = 0; i < blobConstructorValues.length; i++) { + var response = new Blob(blobConstructorValues[i]); + const res = await response.text(); + if (res !== expected[i]) { + throw new Error( + `Failed: ${expected[i].split("").map(a => a.charCodeAt(0))}, received: ${res + .split("") + .map(a => a.charCodeAt(0))}`, + ); + } + + expect(res).toBe(expected[i]); + } + }); + + for (let withGC of [false, true]) { + it(`Blob.slice() ${withGC ? " with gc" : ""}`, async () => { + var parts = ["hello", " ", "world"]; + if (withGC) gc(); + var str = parts.join(""); + if (withGC) gc(); + var combined = new Blob(parts); + if (withGC) gc(); + for (let part of parts) { + if (withGC) gc(); + expect(await combined.slice(str.indexOf(part), str.indexOf(part) + part.length).text()).toBe(part); + if (withGC) gc(); + } + if (withGC) gc(); + for (let part of parts) { + if (withGC) gc(); + expect(await combined.slice(str.indexOf(part), str.indexOf(part) + part.length).text()).toBe(part); + if (withGC) gc(); + } + }); + } +}); + +{ + const sample = new TextEncoder().encode("Hello World!"); + const typedArrays = [ + Uint8Array, + Uint8ClampedArray, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + ]; + const Constructors = [Blob, Response, Request]; + + for (let withGC of [false, true]) { + for (let TypedArray of typedArrays) { + for (let Constructor of Constructors) { + it(`${Constructor.name} arrayBuffer() with ${TypedArray.name}${withGC ? " with gc" : ""}`, async () => { + const data = new TypedArray(sample); + if (withGC) gc(); + const input = Constructor === Blob ? [data] : Constructor === Request ? { body: data } : data; + if (withGC) gc(); + const blob = new Constructor(input); + if (withGC) gc(); + const out = await blob.arrayBuffer(); + if (withGC) gc(); + expect(out instanceof ArrayBuffer).toBe(true); + if (withGC) gc(); + expect(out.byteLength).toBe(data.byteLength); + if (withGC) gc(); + }); + } + } + } +} + +describe("Response", () => { + describe("Response.json", () => { + it("works", async () => { + const inputs = ["hellooo", [[123], 456, 789], { hello: "world" }, { ok: "😉 😌 😍 🥰 😘 " }]; + for (let input of inputs) { + const output = JSON.stringify(input); + expect(await Response.json(input).text()).toBe(output); + } + // JSON.stringify() returns undefined + expect(await Response.json().text()).toBe(""); + // JSON.stringify("") returns '""' + expect(await Response.json("").text()).toBe('""'); + }); + it("sets the content-type header", () => { + let response = Response.json("hello"); + expect(response.type).toBe("basic"); + expect(response.headers.get("content-type")).toBe("application/json;charset=utf-8"); + expect(response.status).toBe(200); + }); + it("supports number status code", () => { + let response = Response.json("hello", 407); + expect(response.type).toBe("basic"); + expect(response.headers.get("content-type")).toBe("application/json;charset=utf-8"); + expect(response.status).toBe(407); + }); + + it("supports headers", () => { + var response = Response.json("hello", { + headers: { + "content-type": "potato", + "x-hello": "world", + }, + status: 408, + }); + + expect(response.headers.get("x-hello")).toBe("world"); + expect(response.status).toBe(408); + }); + }); + describe("Response.redirect", () => { + it("works", () => { + const inputs = [ + "http://example.com", + "http://example.com/", + "http://example.com/hello", + "http://example.com/hello/", + "http://example.com/hello/world", + "http://example.com/hello/world/", + ]; + for (let input of inputs) { + expect(Response.redirect(input).headers.get("Location")).toBe(input); + } + }); + + it("supports headers", () => { + var response = Response.redirect("https://example.com", { + headers: { + "content-type": "potato", + "x-hello": "world", + Location: "https://wrong.com", + }, + status: 408, + }); + expect(response.headers.get("x-hello")).toBe("world"); + expect(response.headers.get("Location")).toBe("https://example.com"); + expect(response.status).toBe(302); + expect(response.type).toBe("basic"); + expect(response.ok).toBe(false); + }); + }); + describe("Response.error", () => { + it("works", () => { + expect(Response.error().type).toBe("error"); + expect(Response.error().ok).toBe(false); + expect(Response.error().status).toBe(0); + }); + }); + it("clone", async () => { + gc(); + var body = new Response("
hello
", { + headers: { + "content-type": "text/html; charset=utf-8", + }, + }); + gc(); + var clone = body.clone(); + gc(); + body.headers.set("content-type", "text/plain"); + gc(); + expect(clone.headers.get("content-type")).toBe("text/html; charset=utf-8"); + gc(); + expect(body.headers.get("content-type")).toBe("text/plain"); + gc(); + expect(await clone.text()).toBe("
hello
"); + gc(); + }); + it("invalid json", async () => { + gc(); + var body = new Response("
hello
", { + headers: { + "content-type": "text/html; charset=utf-8", + }, + }); + try { + await body.json(); + expect(false).toBe(true); + } catch (exception) { + expect(exception instanceof SyntaxError).toBe(true); + } + }); + + testBlobInterface(data => new Response(data), true); +}); + +describe("Request", () => { + it("clone", async () => { + gc(); + var body = new Request("https://hello.com", { + headers: { + "content-type": "text/html; charset=utf-8", + }, + body: "
hello
", + }); + gc(); + expect(body.signal).toBeDefined(); + gc(); + expect(body.headers.get("content-type")).toBe("text/html; charset=utf-8"); + gc(); + var clone = body.clone(); + gc(); + expect(clone.signal).toBeDefined(); + gc(); + body.headers.set("content-type", "text/plain"); + gc(); + expect(clone.headers.get("content-type")).toBe("text/html; charset=utf-8"); + gc(); + expect(body.headers.get("content-type")).toBe("text/plain"); + gc(); + expect(await clone.text()).toBe("
hello
"); + }); + + it("signal", async () => { + gc(); + const controller = new AbortController(); + const req = new Request("https://hello.com", { signal: controller.signal }); + expect(req.signal.aborted).toBe(false); + gc(); + controller.abort(); + gc(); + expect(req.signal.aborted).toBe(true); + }); + + it("cloned signal", async () => { + gc(); + const controller = new AbortController(); + const req = new Request("https://hello.com", { signal: controller.signal }); + expect(req.signal.aborted).toBe(false); + gc(); + controller.abort(); + gc(); + expect(req.signal.aborted).toBe(true); + gc(); + const cloned = req.clone(); + expect(cloned.signal.aborted).toBe(true); + }); + + testBlobInterface(data => new Request("https://hello.com", { body: data }), true); +}); + +describe("Headers", () => { + it("writes", async () => { + var headers = new Headers({ + "content-type": "text/html; charset=utf-8", + }); + gc(); + expect(headers.get("content-type")).toBe("text/html; charset=utf-8"); + gc(); + headers.delete("content-type"); + gc(); + expect(headers.get("content-type")).toBe(null); + gc(); + headers.append("content-type", "text/plain"); + gc(); + expect(headers.get("content-type")).toBe("text/plain"); + gc(); + headers.append("content-type", "text/plain"); + gc(); + expect(headers.get("content-type")).toBe("text/plain, text/plain"); + gc(); + headers.set("content-type", "text/html; charset=utf-8"); + gc(); + expect(headers.get("content-type")).toBe("text/html; charset=utf-8"); + + headers.delete("content-type"); + gc(); + expect(headers.get("content-type")).toBe(null); + gc(); + }); +}); + +it("body nullable", async () => { + gc(); + { + const req = new Request("https://hello.com", { body: null }); + expect(req.body).toBeNull(); + } + gc(); + { + const req = new Request("https://hello.com", { body: undefined }); + expect(req.body).toBeNull(); + } + gc(); + { + const req = new Request("https://hello.com"); + expect(req.body).toBeNull(); + } + gc(); + { + const req = new Request("https://hello.com", { body: "" }); + expect(req.body).not.toBeNull(); + } +}); diff --git a/test/js/web/fetch/fetch_headers.test.js b/test/js/web/fetch/fetch_headers.test.js new file mode 100644 index 000000000..cd2786c08 --- /dev/null +++ b/test/js/web/fetch/fetch_headers.test.js @@ -0,0 +1,66 @@ +import { describe, it, expect, beforeAll, afterAll } from "bun:test"; +const port = 3009; +const url = `http://localhost:${port}`; +let server; + +describe("Headers", async () => { + // Start up a single server and reuse it between tests + beforeAll(() => { + server = Bun.serve({ + fetch(req) { + const hdr = req.headers.get("x-test"); + return new Response(hdr); + }, + port: port, + }); + }); + afterAll(() => { + server.stop(); + }); + + it("Headers should work", async () => { + expect(await fetchContent({ "x-test": "header 1" })).toBe("header 1"); + }); + + it("Header names must be valid", async () => { + expect(() => fetch(url, { headers: { "a\tb:c": "foo" } })).toThrow("Invalid header name: 'a\tb:c'"); + expect(() => fetch(url, { headers: { "❤️": "foo" } })).toThrow("Invalid header name: '❤️'"); + }); + + it("Header values must be valid", async () => { + expect(() => fetch(url, { headers: { "x-test": "\0" } })).toThrow("Header 'x-test' has invalid value: '\0'"); + expect(() => fetch(url, { headers: { "x-test": "❤️" } })).toThrow("Header 'x-test' has invalid value: '❤️'"); + }); + + it("repro 1602", async () => { + const origString = "😂1234".slice(3); + + var encoder = new TextEncoder(); + var decoder = new TextDecoder(); + const roundTripString = decoder.decode(encoder.encode(origString)); + + expect(roundTripString).toBe(origString); + + // This one will pass + expect(await fetchContent({ "x-test": roundTripString })).toBe(roundTripString); + // This would hang + expect(await fetchContent({ "x-test": origString })).toBe(origString); + }); + + describe("toJSON()", () => { + it("should provide lowercase header names", () => { + const headers1 = new Headers({ "X-Test": "yep", "Content-Type": "application/json" }); + expect(headers1.toJSON()).toEqual({ "x-test": "yep", "content-type": "application/json" }); + + const headers2 = new Headers(); + headers2.append("X-Test", "yep"); + headers2.append("Content-Type", "application/json"); + expect(headers2.toJSON()).toEqual({ "x-test": "yep", "content-type": "application/json" }); + }); + }); +}); + +async function fetchContent(headers) { + const res = await fetch(url, { headers: headers }, { verbose: true }); + return await res.text(); +} diff --git a/test/js/web/fetch/fixture.html b/test/js/web/fetch/fixture.html new file mode 100644 index 000000000..081040506 --- /dev/null +++ b/test/js/web/fetch/fixture.html @@ -0,0 +1,1428 @@ + + + + + + + Bun is a fast all-in-one JavaScript runtime + + + + + + + + + + + + + + +
+
+ Bun + +
+
+
+
+
+

Bun is a fast all-in-one JavaScript runtime

+

+ Bundle, transpile, install and run JavaScript & TypeScript projects — all in Bun. Bun is a new + JavaScript runtime with a native bundler, transpiler, task runner and npm client built-in. +

+
+
+
+ Install Bun CLI + 0.2.1 + (beta) +
+
+ macOS x64 & Silicon, Linux x64, Windows Subsystem for Linux +
+
+
+
curl https://bun.sh/install | bash
+ +
+ Show script source +
+
+
+
+ +
+
+
+

Server-side rendering React

+

HTTP requests per second (Linux x64)

+
    +
  • +
    bun: 69,845 requests per second
    + +
  • +
  • +
    node: 16,288 requests per second
    + +
  • +
  • +
    deno: 12,926 requests per second
    + +
  • +
+ +
+
+

WebSocket server chat

+

Messages sent per second (Linux x64, 16 clients)

+
    +
  • +
    bun: 737,280 messages sent per second
    + +
  • +
  • +
    node: 107,457 messages sent per second
    + +
  • +
  • +
    deno: 82,097 messages sent per second
    + +
  • +
+ +
+
+

Load a huge table

+

Average queries per second

+
    +
  • +
    bun: 70.32 queries per second
    + +
  • +
  • +
    deno: 36.54 queries per second
    + +
  • +
  • +
    better-sqlite3: 23.28 queries per second
    + +
  • +
+ +
+
+
+
+
+
+ Install Bun CLI + 0.2.1 + (beta) +
+
macOS x64 & Silicon, Linux x64, Windows Subsystem for Linux
+
+
+
curl https://bun.sh/install | bash
+ +
+ Show script source +
+
+
+
+
+

Tell me more about Bun

+

+ Bun is a modern JavaScript runtime like Node or Deno. It was built from scratch to focus on three main things: +

+
    +
  • Start fast (it has the edge in mind).
  • +
  • New levels of performance (extending JavaScriptCore, the engine).
  • +
  • Being a great and complete tool (bundler, transpiler, package manager).
  • +
+

+ Bun is designed as a drop-in replacement for your current JavaScript & TypeScript apps or scripts — on + your local computer, server or on the edge. Bun natively implements hundreds of Node.js and Web APIs, + including ~90% of + Node-API + functions (native modules), fs, path, Buffer and more. +

+

+ The goal of Bun is to run most of the world's JavaScript outside of browsers, bringing performance and + complexity enhancements to your future infrastructure, as well as developer productivity through better, + simpler tooling. +

+

Batteries included

+
    +
  • + Web APIs like + fetch, + WebSocket, and + ReadableStream + are built-in +
  • +
  • + node_modules + bun implements Node.js' module resolution algorithm, so you can use npm packages in Bun. ESM and + CommonJS are supported, but Bun internally uses ESM +
  • +
  • + In Bun, every file is transpiled. + TypeScript + & JSX just work +
  • +
  • + Bun supports "paths", "jsxImportSource"and more from + tsconfig.json + files +
  • +
  • + Bun.Transpiler + Bun's JSX & TypeScript transpiler is available as an API in Bun +
  • +
  • + use the fastest system calls available with + Bun.write + to write, copy, pipe, send and clone files +
  • +
  • + Bun automatically loads environment variables from + .env + files. No more + require("dotenv").config() +
  • +
  • + Bun ships with a fast SQLite3 client built-in + bun:sqlite +
  • +
  • + Node-API + Bun implements most of + Node-API (N-API). Many Node.js + native modules just work +
  • +
  • + bun:ffi call native code from JavaScript with Bun's + low-overhead foreign function interface +
  • +
  • + node:fs + node:path Bun natively supports a growing list of + Node.js core modules along with globals like Buffer and process +
  • +
+

How does Bun work?

+

+ Bun uses the + JavaScriptCore + engine, which tends + to start + and perform a little faster than more traditional choices like V8. Bun is written in + + Zig + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + , a low-level programming language with manual memory management.

Most of Bun is written from + scratch including the JSX/TypeScript transpiler, npm client, bundler, SQLite client, HTTP client, WebSocket + client and more. +

+

Why is Bun fast?

+

+ An enormous amount of time spent profiling, benchmarking and optimizing things. The answer is different for + every part of Bun, but one general theme: + + Zig + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 's low-level control over memory and lack of hidden control flow makes it much simpler to write fast + software. + Sponsor the Zig Software Foundation. +

+

Getting started

+

+ To install Bun, run this + install script + in your terminal. It downloads Bun from GitHub. +

+
+
curl https://bun.sh/install | bash
+
+

+ Bun's HTTP server is built on web standards like + Request + and + Response +

+
+
// http.js
+export default {
+  port: 3000,
+  fetch(request) {
+    return new Response("Welcome to Bun!");
+  },
+};
+
+

Run it with Bun:

+
+
bun run http.js
+
+

+ Then open + http://localhost:3000 + in your browser.

See + more examples + and check out + the docs. If you have any questions or want help, join + Bun's Discord. +

+

Bun CLI

+
+ bun run +

+ The same command for running JavaScript & TypeScript files with bun's JavaScript runtime also runs + package.json + "scripts". +

+ Replace npm run with + bun run and save 160ms on every run.
+
+ Bun runs package.json scripts + 30x faster than npm run +
+
+
+ bun install +

+ bun install is an npm-compatible package manager. You probably will be + surprised by how much faster copying files can get. +

+ Replace yarn with + bun install and get 20x faster package installs.
+
bun install uses the fastest system calls available to copy files.
+
+
+ bun wiptest +

A Jest-like test runner for JavaScript & TypeScript projects built-in to Bun.

+ +
+

What is the license?

+

MIT License, excluding dependencies which have various licenses.

+

How do I see the source code?

+

Bun is on GitHub.

+
+
+
+ +
+ Built with Bun + 0.2.1 +
+ + diff --git a/test/js/web/fetch/fixture.html.gz b/test/js/web/fetch/fixture.html.gz new file mode 100644 index 000000000..0bb85d4cb Binary files /dev/null and b/test/js/web/fetch/fixture.html.gz differ diff --git a/test/js/web/html/FormData.test.ts b/test/js/web/html/FormData.test.ts new file mode 100644 index 000000000..9d0db4361 --- /dev/null +++ b/test/js/web/html/FormData.test.ts @@ -0,0 +1,410 @@ +import { afterAll, beforeAll, describe, expect, it, test } from "bun:test"; +import fs, { chmodSync, unlinkSync } from "fs"; +import { mkfifo } from "mkfifo"; +import { gc, withoutAggressiveGC } from "../../gc"; + +describe("FormData", () => { + it("should be able to append a string", () => { + const formData = new FormData(); + formData.append("foo", "bar"); + expect(formData.get("foo")).toBe("bar"); + expect(formData.getAll("foo")[0]).toBe("bar"); + }); + + it("should be able to append a Blob", async () => { + const formData = new FormData(); + formData.append("foo", new Blob(["bar"])); + expect(await formData.get("foo").text()).toBe("bar"); + expect(formData.getAll("foo")[0] instanceof Blob).toBe(true); + }); + + it("should be able to set a Blob", async () => { + const formData = new FormData(); + formData.set("foo", new Blob(["bar"])); + expect(await formData.get("foo").text()).toBe("bar"); + expect(formData.getAll("foo")[0] instanceof Blob).toBe(true); + }); + + it("should be able to set a string", async () => { + const formData = new FormData(); + formData.set("foo", "bar"); + expect(formData.get("foo")).toBe("bar"); + expect(formData.getAll("foo")[0]).toBe("bar"); + }); + + const multipartFormDataFixturesRawBody = [ + { + name: "simple", + body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo--\r\n', + headers: { + "Content-Type": "multipart/form-data; boundary=foo", + }, + expected: { + foo: "bar", + }, + }, + { + name: "simple with trailing CRLF", + body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo--\r\n\r\n', + headers: { + "Content-Type": "multipart/form-data; boundary=foo", + }, + expected: { + foo: "bar", + }, + }, + { + name: "simple with trailing CRLF and extra CRLF", + body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo--\r\n\r\n\r\n', + headers: { + "Content-Type": "multipart/form-data; boundary=foo", + }, + expected: { + foo: "bar", + }, + }, + { + name: "advanced", + body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo\r\nContent-Disposition: form-data; name="baz"\r\n\r\nqux\r\n--foo--\r\n', + headers: { + "Content-Type": "multipart/form-data; boundary=foo", + }, + expected: { + foo: "bar", + baz: "qux", + }, + }, + { + name: "advanced with multiple values", + body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbaz\r\n--foo--\r\n', + headers: { + "Content-Type": "multipart/form-data; boundary=foo", + }, + expected: { + foo: ["bar", "baz"], + }, + }, + { + name: "advanced with multiple values and trailing CRLF", + body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbaz\r\n--foo--\r\n\r\n', + headers: { + "Content-Type": "multipart/form-data; boundary=foo", + }, + expected: { + foo: ["bar", "baz"], + }, + }, + { + name: "extremely advanced", + body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo\r\nContent-Disposition: form-data; name="baz"\r\n\r\nqux\r\n--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbaz\r\n--foo--\r\n', + headers: { + "Content-Type": "multipart/form-data; boundary=foo", + }, + expected: { + foo: ["bar", "baz"], + baz: "qux", + }, + }, + { + name: "with name and filename", + body: '--foo\r\nContent-Disposition: form-data; name="foo"; filename="bar"\r\n\r\nbaz\r\n--foo--\r\n', + headers: { + "Content-Type": "multipart/form-data; boundary=foo", + }, + expected: { + foo: new Blob(["baz"]), + }, + }, + { + name: "with name and filename and trailing CRLF", + body: '--foo\r\nContent-Disposition: form-data; name="foo"; filename="bar"\r\n\r\nbaz\r\n--foo--\r\n\r\n', + headers: { + "Content-Type": "multipart/form-data; boundary=foo", + }, + expected: { + foo: new Blob(["baz"]), + }, + }, + ]; + + for (const { name, body, headers, expected: expected_ } of multipartFormDataFixturesRawBody) { + const Class = [Response, Request] as const; + for (const C of Class) { + it(`should parse multipart/form-data (${name}) with ${C.name}`, async () => { + const response = C === Response ? new Response(body, { headers }) : new Request({ headers, body }); + const formData = await response.formData(); + expect(formData instanceof FormData).toBe(true); + const entry = {}; + const expected = Object.assign({}, expected_); + + for (const key of formData.keys()) { + const values = formData.getAll(key); + if (values.length > 1) { + entry[key] = values; + } else { + entry[key] = values[0]; + if (entry[key] instanceof Blob) { + expect(expected[key] instanceof Blob).toBe(true); + + entry[key] = await entry[key].text(); + expected[key] = await expected[key].text(); + } else { + expect(typeof entry[key]).toBe(typeof expected[key]); + expect(expected[key] instanceof Blob).toBe(false); + } + } + } + + expect(entry).toEqual(expected); + }); + + it(`should roundtrip multipart/form-data (${name}) with ${C.name}`, async () => { + const response = C === Response ? new Response(body, { headers }) : new Request({ headers, body }); + const formData = await response.formData(); + expect(formData instanceof FormData).toBe(true); + + const request = await new Response(formData).formData(); + expect(request instanceof FormData).toBe(true); + + const aKeys = Array.from(formData.keys()); + const bKeys = Array.from(request.keys()); + expect(aKeys).toEqual(bKeys); + + for (const key of aKeys) { + const aValues = formData.getAll(key); + const bValues = request.getAll(key); + for (let i = 0; i < aValues.length; i++) { + const a = aValues[i]; + const b = bValues[i]; + if (a instanceof Blob) { + expect(b instanceof Blob).toBe(true); + expect(await a.text()).toBe(await b.text()); + } else { + expect(a).toBe(b); + } + } + } + + // Test that it also works with Blob. + const c = await new Blob([body], { type: headers["Content-Type"] }).formData(); + expect(c instanceof FormData).toBe(true); + const cKeys = Array.from(c.keys()); + expect(cKeys).toEqual(bKeys); + for (const key of cKeys) { + const cValues = c.getAll(key); + const bValues = request.getAll(key); + for (let i = 0; i < cValues.length; i++) { + const c = cValues[i]; + const b = bValues[i]; + if (c instanceof Blob) { + expect(b instanceof Blob).toBe(true); + expect(await c.text()).toBe(await b.text()); + } else { + expect(c).toBe(b); + } + } + } + }); + } + } + + it("should throw on missing final boundary", async () => { + const response = new Response('-foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n', { + headers: { + "Content-Type": "multipart/form-data; boundary=foo", + }, + }); + try { + await response.formData(); + throw "should have thrown"; + } catch (e) { + expect(typeof e.message).toBe("string"); + } + }); + + it("should throw on bad boundary", async () => { + const response = new Response('foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n', { + headers: { + "Content-Type": "multipart/form-data; boundary=foo", + }, + }); + try { + await response.formData(); + throw "should have thrown"; + } catch (e) { + expect(typeof e.message).toBe("string"); + } + }); + + it("should throw on bad header", async () => { + const response = new Response('foo\r\nContent-Disposition: form-data; name"foo"\r\n\r\nbar\r\n', { + headers: { + "Content-Type": "multipart/form-data; boundary=foo", + }, + }); + try { + await response.formData(); + throw "should have thrown"; + } catch (e) { + expect(typeof e.message).toBe("string"); + } + }); + + it("file upload on HTTP server (receive)", async () => { + const server = Bun.serve({ + port: 0, + development: false, + async fetch(req) { + const formData = await req.formData(); + return new Response(formData.get("foo")); + }, + }); + + const reqBody = new Request(`http://${server.hostname}:${server.port}`, { + body: '--foo\r\nContent-Disposition: form-data; name="foo"; filename="bar"\r\n\r\nbaz\r\n--foo--\r\n\r\n', + headers: { + "Content-Type": "multipart/form-data; boundary=foo", + }, + method: "POST", + }); + + const res = await fetch(reqBody); + const body = await res.text(); + expect(body).toBe("baz"); + server.stop(true); + }); + + it("file send on HTTP server (receive)", async () => { + const server = Bun.serve({ + port: 0, + development: false, + async fetch(req) { + const formData = await req.formData(); + return new Response(formData); + }, + }); + + const reqBody = new Request(`http://${server.hostname}:${server.port}`, { + body: '--foo\r\nContent-Disposition: form-data; name="foo"; filename="bar"\r\n\r\nbaz\r\n--foo--\r\n\r\n', + headers: { + "Content-Type": "multipart/form-data; boundary=foo", + }, + method: "POST", + }); + + const res = await fetch(reqBody); + const body = await res.formData(); + expect(await (body.get("foo") as Blob).text()).toBe("baz"); + server.stop(true); + }); + + describe("Bun.file support", () => { + describe("roundtrip", () => { + const path = import.meta.dir + "/form-data-fixture.txt"; + for (const C of [Request, Response]) { + it(`with ${C.name}`, async () => { + await Bun.write(path, "foo!"); + const formData = new FormData(); + formData.append("foo", Bun.file(path)); + const response = C === Response ? new Response(formData) : new Request({ body: formData }); + expect(response.headers.get("content-type")?.startsWith("multipart/form-data;")).toBe(true); + + const formData2 = await response.formData(); + expect(formData2 instanceof FormData).toBe(true); + expect(formData2.get("foo") instanceof Blob).toBe(true); + expect(await (formData2.get("foo") as Blob).text()).toBe("foo!"); + }); + } + }); + + it("doesnt crash when file is missing", async () => { + const formData = new FormData(); + formData.append("foo", Bun.file("missing")); + expect(() => new Response(formData)).toThrow(); + }); + }); + + it("Bun.inspect", () => { + const formData = new FormData(); + formData.append("foo", "bar"); + formData.append("foo", new Blob(["bar"])); + formData.append("bar", "baz"); + formData.append("boop", Bun.file("missing")); + expect(Bun.inspect(formData).length > 0).toBe(true); + }); + + describe("URLEncoded", () => { + test("should parse URL encoded", async () => { + const response = new Response("foo=bar&baz=qux", { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + }); + const formData = await response.formData(); + expect(formData instanceof FormData).toBe(true); + expect(formData.get("foo")).toBe("bar"); + expect(formData.get("baz")).toBe("qux"); + }); + + test("should parse URLSearchParams", async () => { + const searchParams = new URLSearchParams("foo=bar&baz=qux"); + const response = new Response(searchParams); + expect(response.headers.get("Content-Type")).toBe("application/x-www-form-urlencoded;charset=UTF-8"); + + expect(searchParams instanceof URLSearchParams).toBe(true); + expect(searchParams.get("foo")).toBe("bar"); + + const formData = await response.formData(); + expect(formData instanceof FormData).toBe(true); + expect(formData.get("foo")).toBe("bar"); + expect(formData.get("baz")).toBe("qux"); + }); + + test("should parse URL encoded with charset", async () => { + const response = new Response("foo=bar&baz=qux", { + headers: { + "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", + }, + }); + const formData = await response.formData(); + expect(formData instanceof FormData).toBe(true); + expect(formData.get("foo")).toBe("bar"); + expect(formData.get("baz")).toBe("qux"); + }); + + test("should parse URL encoded with charset and space", async () => { + const response = new Response("foo=bar&baz=qux+quux", { + headers: { + "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", + }, + }); + const formData = await response.formData(); + expect(formData instanceof FormData).toBe(true); + expect(formData.get("foo")).toBe("bar"); + expect(formData.get("baz")).toBe("qux quux"); + }); + + test("should parse URL encoded with charset and plus", async () => { + const response = new Response("foo=bar&baz=qux+quux", { + headers: { + "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", + }, + }); + const formData = await response.formData(); + expect(formData instanceof FormData).toBe(true); + expect(formData.get("foo")).toBe("bar"); + expect(formData.get("baz")).toBe("qux quux"); + }); + + it("should handle multiple values", async () => { + const response = new Response("foo=bar&foo=baz", { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + }); + const formData = await response.formData(); + expect(formData instanceof FormData).toBe(true); + expect(formData.getAll("foo")).toEqual(["bar", "baz"]); + }); + }); +}); diff --git a/test/js/web/html/form-data-fixture.txt b/test/js/web/html/form-data-fixture.txt new file mode 100644 index 000000000..a4d20dd78 --- /dev/null +++ b/test/js/web/html/form-data-fixture.txt @@ -0,0 +1 @@ +foo! \ No newline at end of file diff --git a/test/js/web/streams/bun-streams-test-fifo.sh b/test/js/web/streams/bun-streams-test-fifo.sh new file mode 100644 index 000000000..57650ba1d --- /dev/null +++ b/test/js/web/streams/bun-streams-test-fifo.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +echoerr() { echo "$@" 1>&2; } + +echoerr "bun-streams-test-fifo.sh: starting" +echo -e "$FIFO_TEST" >>${@: -1} +echoerr "bun-streams-test-fifo.sh: ending" +exit 0 diff --git a/test/js/web/streams/fetch.js.txt b/test/js/web/streams/fetch.js.txt new file mode 100644 index 000000000..5a9b52fcf --- /dev/null +++ b/test/js/web/streams/fetch.js.txt @@ -0,0 +1,46 @@ + + + + Example Domain + + + + + + + + +
+

Example Domain

+

This domain is for use in illustrative examples in documents. You may use this + domain in literature without prior coordination or asking for permission.

+

More information...

+
+ + diff --git a/test/js/web/streams/streams.test.js b/test/js/web/streams/streams.test.js new file mode 100644 index 000000000..c4af85e4f --- /dev/null +++ b/test/js/web/streams/streams.test.js @@ -0,0 +1,630 @@ +import { file, readableStreamToArrayBuffer, readableStreamToArray, readableStreamToText } from "bun"; +import { expect, it, beforeEach, afterEach, describe } from "bun:test"; +import { mkfifo } from "mkfifo"; +import { realpathSync, unlinkSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { tmpdir } from "os"; +import { gc } from "harness"; + +beforeEach(() => gc()); +afterEach(() => gc()); + +describe("WritableStream", () => { + it("works", async () => { + try { + var chunks = []; + var writable = new WritableStream({ + write(chunk, controller) { + chunks.push(chunk); + }, + close(er) { + console.log("closed"); + console.log(er); + }, + abort(reason) { + console.log("aborted!"); + console.log(reason); + }, + }); + + var writer = writable.getWriter(); + + writer.write(new Uint8Array([1, 2, 3])); + + writer.write(new Uint8Array([4, 5, 6])); + + await writer.close(); + + expect(JSON.stringify(Array.from(Buffer.concat(chunks)))).toBe(JSON.stringify([1, 2, 3, 4, 5, 6])); + } catch (e) { + console.log(e); + console.log(e.stack); + throw e; + } + }); + + it("pipeTo", async () => { + const rs = new ReadableStream({ + start(controller) { + controller.enqueue("hello world"); + controller.close(); + }, + }); + + let received; + const ws = new WritableStream({ + write(chunk, controller) { + received = chunk; + }, + }); + await rs.pipeTo(ws); + expect(received).toBe("hello world"); + }); +}); + +describe("ReadableStream.prototype.tee", () => { + it("class", () => { + const [a, b] = new ReadableStream().tee(); + expect(a instanceof ReadableStream).toBe(true); + expect(b instanceof ReadableStream).toBe(true); + }); + + describe("default stream", () => { + it("works", async () => { + var [a, b] = new ReadableStream({ + start(controller) { + controller.enqueue("a"); + controller.enqueue("b"); + controller.enqueue("c"); + controller.close(); + }, + }).tee(); + + expect(await readableStreamToText(a)).toBe("abc"); + expect(await readableStreamToText(b)).toBe("abc"); + }); + }); + + describe("direct stream", () => { + it("works", async () => { + try { + var [a, b] = new ReadableStream({ + pull(controller) { + controller.write("a"); + controller.write("b"); + controller.write("c"); + controller.close(); + }, + type: "direct", + }).tee(); + + expect(await readableStreamToText(a)).toBe("abc"); + expect(await readableStreamToText(b)).toBe("abc"); + } catch (e) { + console.log(e.message); + console.log(e.stack); + throw e; + } + }); + }); +}); + +it("ReadableStream.prototype[Symbol.asyncIterator]", async () => { + const stream = new ReadableStream({ + start(controller) { + controller.enqueue("hello"); + controller.enqueue("world"); + controller.close(); + }, + cancel(reason) {}, + }); + + const chunks = []; + try { + for await (const chunk of stream) { + chunks.push(chunk); + } + } catch (e) { + console.log(e.message); + console.log(e.stack); + } + + expect(chunks.join("")).toBe("helloworld"); +}); + +it("ReadableStream.prototype[Symbol.asyncIterator] pull", async () => { + const stream = new ReadableStream({ + pull(controller) { + controller.enqueue("hello"); + controller.enqueue("world"); + controller.close(); + }, + cancel(reason) {}, + }); + + const chunks = []; + for await (const chunk of stream) { + chunks.push(chunk); + } + expect(chunks.join("")).toBe("helloworld"); +}); + +it("ReadableStream.prototype[Symbol.asyncIterator] direct", async () => { + const stream = new ReadableStream({ + pull(controller) { + controller.write("hello"); + controller.write("world"); + controller.close(); + }, + type: "direct", + cancel(reason) {}, + }); + + const chunks = []; + try { + for await (const chunk of stream) { + chunks.push(chunk); + } + } catch (e) { + console.log(e.message); + console.log(e.stack); + } + + expect(Buffer.concat(chunks).toString()).toBe("helloworld"); +}); + +it("ReadableStream.prototype.values() cancel", async () => { + var cancelled = false; + const stream = new ReadableStream({ + pull(controller) { + controller.enqueue("hello"); + controller.enqueue("world"); + }, + cancel(reason) { + cancelled = true; + }, + }); + + for await (const chunk of stream.values({ preventCancel: false })) { + break; + } + expect(cancelled).toBe(true); +}); + +it("ReadableStream.prototype.values() preventCancel", async () => { + var cancelled = false; + const stream = new ReadableStream({ + pull(controller) { + controller.enqueue("hello"); + controller.enqueue("world"); + }, + cancel(reason) { + cancelled = true; + }, + }); + + for await (const chunk of stream.values({ preventCancel: true })) { + break; + } + expect(cancelled).toBe(false); +}); + +it("ReadableStream.prototype.values", async () => { + const stream = new ReadableStream({ + start(controller) { + controller.enqueue("hello"); + controller.enqueue("world"); + controller.close(); + }, + }); + + const chunks = []; + for await (const chunk of stream.values()) { + chunks.push(chunk); + } + expect(chunks.join("")).toBe("helloworld"); +}); + +it("Bun.file() read text from pipe", async () => { + try { + unlinkSync("/tmp/fifo"); + } catch (e) {} + + console.log("here"); + mkfifo("/tmp/fifo", 0o666); + + // 65k so its less than the max on linux + const large = "HELLO!".repeat((((1024 * 65) / "HELLO!".length) | 0) + 1); + + const chunks = []; + + const proc = Bun.spawn({ + cmd: ["bash", join(import.meta.dir + "/", "bun-streams-test-fifo.sh"), "/tmp/fifo"], + stderr: "inherit", + stdout: null, + stdin: null, + env: { + FIFO_TEST: large, + }, + }); + const exited = proc.exited; + proc.ref(); + + const prom = (async function () { + while (chunks.length === 0) { + var out = Bun.file("/tmp/fifo").stream(); + for await (const chunk of out) { + chunks.push(chunk); + } + } + return Buffer.concat(chunks).toString(); + })(); + + const [status, output] = await Promise.all([exited, prom]); + expect(output.length).toBe(large.length + 1); + expect(output).toBe(large + "\n"); + expect(status).toBe(0); +}); + +it("exists globally", () => { + expect(typeof ReadableStream).toBe("function"); + expect(typeof ReadableStreamBYOBReader).toBe("function"); + expect(typeof ReadableStreamBYOBRequest).toBe("function"); + expect(typeof ReadableStreamDefaultController).toBe("function"); + expect(typeof ReadableStreamDefaultReader).toBe("function"); + expect(typeof TransformStream).toBe("function"); + expect(typeof TransformStreamDefaultController).toBe("function"); + expect(typeof WritableStream).toBe("function"); + expect(typeof WritableStreamDefaultController).toBe("function"); + expect(typeof WritableStreamDefaultWriter).toBe("function"); + expect(typeof ByteLengthQueuingStrategy).toBe("function"); + expect(typeof CountQueuingStrategy).toBe("function"); +}); + +it("new Response(stream).body", async () => { + var stream = new ReadableStream({ + pull(controller) { + controller.enqueue("hello"); + controller.enqueue("world"); + controller.close(); + }, + cancel() {}, + }); + var response = new Response(stream); + expect(response.body).toBe(stream); + expect(await response.text()).toBe("helloworld"); +}); + +it("new Request({body: stream}).body", async () => { + var stream = new ReadableStream({ + pull(controller) { + controller.enqueue("hello"); + controller.enqueue("world"); + controller.close(); + }, + cancel() {}, + }); + var response = new Request({ body: stream }); + expect(response.body).toBe(stream); + expect(await response.text()).toBe("helloworld"); +}); + +it("ReadableStream (readMany)", async () => { + var stream = new ReadableStream({ + pull(controller) { + controller.enqueue("hello"); + controller.enqueue("world"); + controller.close(); + }, + cancel() {}, + }); + var reader = stream.getReader(); + const chunk = await reader.readMany(); + expect(chunk.value.join("")).toBe("helloworld"); + expect((await reader.read()).done).toBe(true); +}); + +it("ReadableStream (direct)", async () => { + var stream = new ReadableStream({ + pull(controller) { + controller.write("hello"); + controller.write("world"); + controller.close(); + }, + cancel() {}, + type: "direct", + }); + var reader = stream.getReader(); + const chunk = await reader.read(); + expect(chunk.value.join("")).toBe(Buffer.from("helloworld").join("")); + expect((await reader.read()).done).toBe(true); + expect((await reader.read()).done).toBe(true); +}); + +it("ReadableStream (bytes)", async () => { + var stream = new ReadableStream({ + start(controller) { + controller.enqueue(Buffer.from("abdefgh")); + }, + pull(controller) {}, + cancel() {}, + type: "bytes", + }); + const chunks = []; + const chunk = await stream.getReader().read(); + chunks.push(chunk.value); + expect(chunks[0].join("")).toBe(Buffer.from("abdefgh").join("")); +}); + +it("ReadableStream (default)", async () => { + var stream = new ReadableStream({ + start(controller) { + controller.enqueue(Buffer.from("abdefgh")); + controller.close(); + }, + pull(controller) {}, + cancel() {}, + }); + const chunks = []; + const chunk = await stream.getReader().read(); + chunks.push(chunk.value); + expect(chunks[0].join("")).toBe(Buffer.from("abdefgh").join("")); +}); + +it("readableStreamToArray", async () => { + var queue = [Buffer.from("abdefgh")]; + var stream = new ReadableStream({ + pull(controller) { + var chunk = queue.shift(); + if (chunk) { + controller.enqueue(chunk); + } else { + controller.close(); + } + }, + cancel() {}, + type: "bytes", + }); + + const chunks = await readableStreamToArray(stream); + + expect(chunks[0].join("")).toBe(Buffer.from("abdefgh").join("")); +}); + +it("readableStreamToArrayBuffer (bytes)", async () => { + var queue = [Buffer.from("abdefgh")]; + var stream = new ReadableStream({ + pull(controller) { + var chunk = queue.shift(); + if (chunk) { + controller.enqueue(chunk); + } else { + controller.close(); + } + }, + cancel() {}, + type: "bytes", + }); + const buffer = await readableStreamToArrayBuffer(stream); + expect(new TextDecoder().decode(new Uint8Array(buffer))).toBe("abdefgh"); +}); + +it("readableStreamToArrayBuffer (default)", async () => { + var queue = [Buffer.from("abdefgh")]; + var stream = new ReadableStream({ + pull(controller) { + var chunk = queue.shift(); + if (chunk) { + controller.enqueue(chunk); + } else { + controller.close(); + } + }, + cancel() {}, + }); + + const buffer = await readableStreamToArrayBuffer(stream); + expect(new TextDecoder().decode(new Uint8Array(buffer))).toBe("abdefgh"); +}); + +it("ReadableStream for Blob", async () => { + var blob = new Blob(["abdefgh", "ijklmnop"]); + expect(await blob.text()).toBe("abdefghijklmnop"); + var stream; + try { + stream = blob.stream(); + stream = blob.stream(); + } catch (e) { + console.error(e); + console.error(e.stack); + } + const chunks = []; + var reader; + reader = stream.getReader(); + + while (true) { + var chunk; + try { + chunk = await reader.read(); + } catch (e) { + console.error(e); + console.error(e.stack); + } + if (chunk.done) break; + chunks.push(new TextDecoder().decode(chunk.value)); + } + expect(chunks.join("")).toBe(new TextDecoder().decode(Buffer.from("abdefghijklmnop"))); +}); + +it("ReadableStream for File", async () => { + var blob = file(import.meta.dir + "/fetch.js.txt"); + var stream = blob.stream(); + const chunks = []; + var reader = stream.getReader(); + stream = undefined; + while (true) { + const chunk = await reader.read(); + if (chunk.done) break; + chunks.push(chunk.value); + } + reader = undefined; + const output = new Uint8Array(await blob.arrayBuffer()).join(""); + const input = chunks.map(a => a.join("")).join(""); + expect(output).toBe(input); +}); + +it("ReadableStream for File errors", async () => { + try { + var blob = file(import.meta.dir + "/fetch.js.txt.notfound"); + blob.stream().getReader(); + throw new Error("should not reach here"); + } catch (e) { + expect(e.code).toBe("ENOENT"); + expect(e.syscall).toBe("open"); + } +}); + +it("ReadableStream for empty blob closes immediately", async () => { + var blob = new Blob([]); + var stream = blob.stream(); + const chunks = []; + var reader = stream.getReader(); + while (true) { + const chunk = await reader.read(); + if (chunk.done) break; + chunks.push(chunk.value); + } + + expect(chunks.length).toBe(0); +}); + +it("ReadableStream for empty file closes immediately", async () => { + writeFileSync("/tmp/bun-empty-file-123456", ""); + var blob = file("/tmp/bun-empty-file-123456"); + var stream; + try { + stream = blob.stream(); + } catch (e) { + console.error(e.stack); + } + const chunks = []; + var reader = stream.getReader(); + while (true) { + const chunk = await reader.read(); + if (chunk.done) break; + chunks.push(chunk.value); + } + + expect(chunks.length).toBe(0); +}); + +it("new Response(stream).arrayBuffer() (bytes)", async () => { + var queue = [Buffer.from("abdefgh")]; + var stream = new ReadableStream({ + pull(controller) { + var chunk = queue.shift(); + if (chunk) { + controller.enqueue(chunk); + } else { + controller.close(); + } + }, + cancel() {}, + type: "bytes", + }); + const buffer = await new Response(stream).arrayBuffer(); + expect(new TextDecoder().decode(new Uint8Array(buffer))).toBe("abdefgh"); +}); + +it("new Response(stream).arrayBuffer() (default)", async () => { + var queue = [Buffer.from("abdefgh")]; + var stream = new ReadableStream({ + pull(controller) { + var chunk = queue.shift(); + if (chunk) { + controller.enqueue(chunk); + } else { + controller.close(); + } + }, + cancel() {}, + }); + const buffer = await new Response(stream).arrayBuffer(); + expect(new TextDecoder().decode(new Uint8Array(buffer))).toBe("abdefgh"); +}); + +it("new Response(stream).text() (default)", async () => { + var queue = [Buffer.from("abdefgh")]; + var stream = new ReadableStream({ + pull(controller) { + var chunk = queue.shift(); + if (chunk) { + controller.enqueue(chunk); + } else { + controller.close(); + } + }, + cancel() {}, + }); + const text = await new Response(stream).text(); + expect(text).toBe("abdefgh"); +}); + +it("new Response(stream).json() (default)", async () => { + var queue = [Buffer.from(JSON.stringify({ hello: true }))]; + var stream = new ReadableStream({ + pull(controller) { + var chunk = queue.shift(); + if (chunk) { + controller.enqueue(chunk); + } else { + controller.close(); + } + }, + cancel() {}, + }); + const json = await new Response(stream).json(); + expect(json.hello).toBe(true); +}); + +it("new Response(stream).blob() (default)", async () => { + var queue = [Buffer.from(JSON.stringify({ hello: true }))]; + var stream = new ReadableStream({ + pull(controller) { + var chunk = queue.shift(); + if (chunk) { + controller.enqueue(chunk); + } else { + controller.close(); + } + }, + cancel() {}, + }); + const response = new Response(stream); + const blob = await response.blob(); + expect(await blob.text()).toBe('{"hello":true}'); +}); + +it("Blob.stream() -> new Response(stream).text()", async () => { + var blob = new Blob(["abdefgh"]); + var stream = blob.stream(); + const text = await new Response(stream).text(); + expect(text).toBe("abdefgh"); +}); + +it("Bun.file().stream() read text from large file", async () => { + const hugely = "HELLO!".repeat(1024 * 1024 * 10); + const tmpfile = join(realpathSync(tmpdir()), "bun-streams-test.txt"); + writeFileSync(tmpfile, hugely); + try { + const chunks = []; + for await (const chunk of Bun.file(tmpfile).stream()) { + chunks.push(chunk); + } + const output = Buffer.concat(chunks).toString(); + expect(output).toHaveLength(hugely.length); + expect(output).toBe(hugely); + } finally { + unlinkSync(tmpfile); + } +}); diff --git a/test/js/web/timers/microtask.test.js b/test/js/web/timers/microtask.test.js new file mode 100644 index 000000000..f41159cfa --- /dev/null +++ b/test/js/web/timers/microtask.test.js @@ -0,0 +1,74 @@ +import { it } from "bun:test"; + +it("queueMicrotask", async () => { + // You can verify this test is correct by copy pasting this into a browser's console and checking it doesn't throw an error. + var run = 0; + + await new Promise((resolve, reject) => { + queueMicrotask(() => { + if (run++ != 0) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + queueMicrotask(() => { + if (run++ != 3) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + }); + }); + queueMicrotask(() => { + if (run++ != 1) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + queueMicrotask(() => { + if (run++ != 4) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + + queueMicrotask(() => { + if (run++ != 6) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + }); + }); + }); + queueMicrotask(() => { + if (run++ != 2) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + queueMicrotask(() => { + if (run++ != 5) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + + queueMicrotask(() => { + if (run++ != 7) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + resolve(true); + }); + }); + }); + }); + + { + var passed = false; + try { + queueMicrotask(1234); + } catch (exception) { + passed = exception instanceof TypeError; + } + + if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is not a function"); + } + + { + var passed = false; + try { + queueMicrotask(); + } catch (exception) { + passed = exception instanceof TypeError; + } + + if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is empty"); + } +}); diff --git a/test/js/web/timers/performance.test.js b/test/js/web/timers/performance.test.js new file mode 100644 index 000000000..dd50c4dc6 --- /dev/null +++ b/test/js/web/timers/performance.test.js @@ -0,0 +1,22 @@ +import { expect, it } from "bun:test"; + +it("performance.now() should be monotonic", () => { + const first = performance.now(); + const second = performance.now(); + const third = performance.now(); + const fourth = performance.now(); + const fifth = performance.now(); + const sixth = performance.now(); + expect(first < second).toBe(true); + expect(second < third).toBe(true); + expect(third < fourth).toBe(true); + expect(fourth < fifth).toBe(true); + expect(fifth < sixth).toBe(true); + expect(Bun.nanoseconds() > 0).toBe(true); + expect(Bun.nanoseconds() > sixth).toBe(true); + expect(typeof Bun.nanoseconds() === "number").toBe(true); +}); + +it("performance.timeOrigin + performance.now() should be similar to Date.now()", () => { + expect(Math.abs(performance.timeOrigin + performance.now() - Date.now()) < 1000).toBe(true); +}); diff --git a/test/js/web/timers/setImmediate.test.js b/test/js/web/timers/setImmediate.test.js new file mode 100644 index 000000000..9cd6fa1c9 --- /dev/null +++ b/test/js/web/timers/setImmediate.test.js @@ -0,0 +1,47 @@ +import { it, expect } from "bun:test"; + +it("setImmediate", async () => { + var lastID = -1; + const result = await new Promise((resolve, reject) => { + var numbers = []; + + for (let i = 0; i < 10; i++) { + const id = setImmediate((...args) => { + numbers.push(i); + if (i === 9) { + resolve(numbers); + } + try { + expect(args.length).toBe(1); + expect(args[0]).toBe(i); + } catch (err) { + reject(err); + } + }, i); + expect(id > lastID).toBe(true); + lastID = id; + } + }); + + for (let j = 0; j < result.length; j++) { + expect(result[j]).toBe(j); + } + expect(result.length).toBe(10); +}); + +it("clearImmediate", async () => { + var called = false; + const id = setImmediate(() => { + called = true; + expect(false).toBe(true); + }); + clearImmediate(id); + + // assert it doesn't crash if you call clearImmediate twice + clearImmediate(id); + + await new Promise((resolve, reject) => { + setImmediate(resolve); + }); + expect(called).toBe(false); +}); diff --git a/test/js/web/timers/setInterval.test.js b/test/js/web/timers/setInterval.test.js new file mode 100644 index 000000000..7b03afba5 --- /dev/null +++ b/test/js/web/timers/setInterval.test.js @@ -0,0 +1,61 @@ +import { it, expect } from "bun:test"; + +it("setInterval", async () => { + var counter = 0; + var start; + const result = await new Promise((resolve, reject) => { + start = performance.now(); + + var id = setInterval( + (...args) => { + counter++; + if (counter === 10) { + resolve(counter); + clearInterval(id); + } + try { + expect(args).toStrictEqual(["foo"]); + } catch (err) { + reject(err); + clearInterval(id); + } + }, + 1, + "foo", + ); + }); + + expect(result).toBe(10); + expect(performance.now() - start >= 10).toBe(true); +}); + +it("clearInterval", async () => { + var called = false; + const id = setInterval(() => { + called = true; + expect(false).toBe(true); + }, 1); + clearInterval(id); + await new Promise((resolve, reject) => { + setInterval(() => { + resolve(); + }, 10); + }); + expect(called).toBe(false); +}); + +it("async setInterval", async () => { + var remaining = 5; + await new Promise((resolve, reject) => { + queueMicrotask(() => { + var id = setInterval(async () => { + await 1; + remaining--; + if (remaining === 0) { + clearInterval(id); + resolve(); + } + }, 1); + }); + }); +}); diff --git a/test/js/web/timers/setTimeout.test.js b/test/js/web/timers/setTimeout.test.js new file mode 100644 index 000000000..88472adc7 --- /dev/null +++ b/test/js/web/timers/setTimeout.test.js @@ -0,0 +1,173 @@ +import { it, expect } from "bun:test"; + +it("setTimeout", async () => { + var lastID = -1; + const result = await new Promise((resolve, reject) => { + var numbers = []; + + for (let i = 0; i < 10; i++) { + const id = setTimeout( + (...args) => { + numbers.push(i); + if (i === 9) { + resolve(numbers); + } + try { + expect(args).toStrictEqual(["foo"]); + } catch (err) { + reject(err); + } + }, + i, + "foo", + ); + expect(+id > lastID).toBe(true); + lastID = id; + } + }); + + for (let j = 0; j < result.length; j++) { + expect(result[j]).toBe(j); + } + expect(result.length).toBe(10); +}); + +it("clearTimeout", async () => { + var called = false; + + // as object + { + const id = setTimeout(() => { + called = true; + expect(false).toBe(true); + }, 0); + clearTimeout(id); + + // assert it doesn't crash if you call clearTimeout twice + clearTimeout(id); + } + + // as number + { + const id = setTimeout(() => { + called = true; + expect(false).toBe(true); + }, 0); + clearTimeout(+id); + + // assert it doesn't crash if you call clearTimeout twice + clearTimeout(+id); + } + + await new Promise((resolve, reject) => { + setTimeout(resolve, 10); + }); + expect(called).toBe(false); +}); + +it("setTimeout(() => {}, 0)", async () => { + var called = false; + setTimeout(() => { + called = true; + }, 0); + await new Promise((resolve, reject) => { + setTimeout(() => { + resolve(); + }, 10); + }); + expect(called).toBe(true); + var ranFirst = -1; + setTimeout(() => { + if (ranFirst === -1) ranFirst = 1; + }, 1); + setTimeout(() => { + if (ranFirst === -1) ranFirst = 0; + }, 0); + + await new Promise((resolve, reject) => { + setTimeout(() => { + resolve(); + }, 10); + }); + expect(ranFirst).toBe(0); + + ranFirst = -1; + + const id = setTimeout(() => { + ranFirst = 0; + }, 0); + clearTimeout(id); + await new Promise((resolve, reject) => { + setTimeout(() => { + resolve(); + }, 10); + }); + expect(ranFirst).toBe(-1); +}); + +it("Bun.sleep", async () => { + var sleeps = 0; + await Bun.sleep(0); + const start = performance.now(); + sleeps++; + await Bun.sleep(1); + sleeps++; + await Bun.sleep(2); + sleeps++; + const end = performance.now(); + expect((end - start) * 1000).toBeGreaterThanOrEqual(3); + + expect(sleeps).toBe(3); +}); + +it("Bun.sleep propagates exceptions", async () => { + try { + await Bun.sleep(1).then(a => { + throw new Error("TestPassed"); + }); + throw "Should not reach here"; + } catch (err) { + expect(err.message).toBe("TestPassed"); + } +}); + +it("Bun.sleep works with a Date object", async () => { + var ten_ms = new Date(); + ten_ms.setMilliseconds(ten_ms.getMilliseconds() + 12); + const now = performance.now(); + await Bun.sleep(ten_ms); + expect(performance.now() - now).toBeGreaterThanOrEqual(10); +}); + +it("node.js timers/promises setTimeout propagates exceptions", async () => { + const { setTimeout } = require("timers/promises"); + try { + await setTimeout(1).then(a => { + throw new Error("TestPassed"); + }); + throw "Should not reach here"; + } catch (err) { + expect(err.message).toBe("TestPassed"); + } +}); + +it.skip("order of setTimeouts", done => { + var nums = []; + var maybeDone = cb => { + return () => { + cb(); + if (nums.length === 4) { + try { + expect(nums).toEqual([1, 2, 3, 4]); + done(); + } catch (e) { + done(e); + } + } + }; + }; + setTimeout(maybeDone(() => nums.push(2))); + setTimeout(maybeDone(() => nums.push(3), 0)); + setTimeout(maybeDone(() => nums.push(4), 1)); + Promise.resolve().then(maybeDone(() => nums.push(1))); +}); diff --git a/test/js/web/url/url.test.ts b/test/js/web/url/url.test.ts new file mode 100644 index 000000000..19e10b262 --- /dev/null +++ b/test/js/web/url/url.test.ts @@ -0,0 +1,137 @@ +import { describe, it, expect } from "bun:test"; + +describe("url", () => { + it("prints", () => { + expect(Bun.inspect(new URL("https://example.com"))).toBe(`URL { + href: "https://example.com/", + origin: "https://example.com", + protocol: "https:", + username: "", + password: "", + host: "example.com", + hostname: "example.com", + port: "", + pathname: "/", + hash: "", + search: "", + searchParams: URLSearchParams { + append: [Function: append], + delete: [Function: delete], + get: [Function: get], + getAll: [Function: getAll], + has: [Function: has], + set: [Function: set], + sort: [Function: sort], + entries: [Function: entries], + keys: [Function: keys], + values: [Function: values], + forEach: [Function: forEach], + toString: [Function: toString], + [Symbol(Symbol.iterator)]: [Function: entries] + }, + toJSON: [Function: toJSON], + toString: [Function: toString] +}`); + + expect( + Bun.inspect( + new URL("https://github.com/oven-sh/bun/issues/135?hello%20i%20have%20spaces%20thank%20you%20good%20night"), + ), + ).toBe(`URL { + href: "https://github.com/oven-sh/bun/issues/135?hello%20i%20have%20spaces%20thank%20you%20good%20night", + origin: "https://github.com", + protocol: "https:", + username: "", + password: "", + host: "github.com", + hostname: "github.com", + port: "", + pathname: "/oven-sh/bun/issues/135", + hash: "", + search: "?hello%20i%20have%20spaces%20thank%20you%20good%20night", + searchParams: URLSearchParams { + append: [Function: append], + delete: [Function: delete], + get: [Function: get], + getAll: [Function: getAll], + has: [Function: has], + set: [Function: set], + sort: [Function: sort], + entries: [Function: entries], + keys: [Function: keys], + values: [Function: values], + forEach: [Function: forEach], + toString: [Function: toString], + [Symbol(Symbol.iterator)]: [Function: entries] + }, + toJSON: [Function: toJSON], + toString: [Function: toString] +}`); + }); + it("works", () => { + const inputs = [ + [ + "https://username:password@api.foo.bar.com:9999/baz/okay/i/123?ran=out&of=things#to-use-as-a-placeholder", + { + hash: "#to-use-as-a-placeholder", + host: "api.foo.bar.com:9999", + hostname: "api.foo.bar.com", + href: "https://username:password@api.foo.bar.com:9999/baz/okay/i/123?ran=out&of=things#to-use-as-a-placeholder", + origin: "https://api.foo.bar.com:9999", + password: "password", + pathname: "/baz/okay/i/123", + port: "9999", + protocol: "https:", + search: "?ran=out&of=things", + username: "username", + }, + ], + [ + "https://url.spec.whatwg.org/#url-serializing", + { + hash: "#url-serializing", + host: "url.spec.whatwg.org", + hostname: "url.spec.whatwg.org", + href: "https://url.spec.whatwg.org/#url-serializing", + origin: "https://url.spec.whatwg.org", + password: "", + pathname: "/", + port: "", + protocol: "https:", + search: "", + username: "", + }, + ], + [ + "https://url.spec.whatwg.org#url-serializing", + { + hash: "#url-serializing", + host: "url.spec.whatwg.org", + hostname: "url.spec.whatwg.org", + href: "https://url.spec.whatwg.org/#url-serializing", + origin: "https://url.spec.whatwg.org", + password: "", + pathname: "/", + port: "", + protocol: "https:", + search: "", + username: "", + }, + ], + ] as const; + + for (let [url, values] of inputs) { + const result = new URL(url); + expect(result.hash).toBe(values.hash); + expect(result.host).toBe(values.host); + expect(result.hostname).toBe(values.hostname); + expect(result.href).toBe(values.href); + expect(result.password).toBe(values.password); + expect(result.pathname).toBe(values.pathname); + expect(result.port).toBe(values.port); + expect(result.protocol).toBe(values.protocol); + expect(result.search).toBe(values.search); + expect(result.username).toBe(values.username); + } + }); +}); diff --git a/test/js/web/util/atob.test.js b/test/js/web/util/atob.test.js new file mode 100644 index 000000000..4945829e1 --- /dev/null +++ b/test/js/web/util/atob.test.js @@ -0,0 +1,77 @@ +import { expect, it } from "bun:test"; + +function expectInvalidCharacters(val) { + try { + atob(val); + throw new Error("Expected error"); + } catch (error) { + expect(error.message).toBe("The string contains invalid characters."); + } +} + +it("atob", () => { + expect(atob("YQ==")).toBe("a"); + expect(atob("YWI=")).toBe("ab"); + expect(atob("YWJj")).toBe("abc"); + expect(atob("YWJjZA==")).toBe("abcd"); + expect(atob("YWJjZGU=")).toBe("abcde"); + expect(atob("YWJjZGVm")).toBe("abcdef"); + expect(atob("zzzz")).toBe("Ï<ó"); + expect(atob("")).toBe(""); + expect(atob(null)).toBe("žée"); + expect(atob("6ek=")).toBe("éé"); + expect(atob("6ek")).toBe("éé"); + expect(atob("gIE=")).toBe("€"); + expect(atob("zz")).toBe("Ï"); + expect(atob("zzz")).toBe("Ï<"); + expect(atob("zzz=")).toBe("Ï<"); + expect(atob(" YQ==")).toBe("a"); + expect(atob("YQ==\u000a")).toBe("a"); + + try { + atob(); + } catch (error) { + expect(error.name).toBe("TypeError"); + } + expectInvalidCharacters(undefined); + expectInvalidCharacters(" abcd==="); + expectInvalidCharacters("abcd=== "); + expectInvalidCharacters("abcd ==="); + expectInvalidCharacters("тест"); + expectInvalidCharacters("z"); + expectInvalidCharacters("zzz=="); + expectInvalidCharacters("zzz==="); + expectInvalidCharacters("zzz===="); + expectInvalidCharacters("zzz====="); + expectInvalidCharacters("zzzzz"); + expectInvalidCharacters("z=zz"); + expectInvalidCharacters("="); + expectInvalidCharacters("=="); + expectInvalidCharacters("==="); + expectInvalidCharacters("===="); + expectInvalidCharacters("====="); +}); + +it("btoa", () => { + expect(btoa("a")).toBe("YQ=="); + expect(btoa("ab")).toBe("YWI="); + expect(btoa("abc")).toBe("YWJj"); + expect(btoa("abcd")).toBe("YWJjZA=="); + expect(btoa("abcde")).toBe("YWJjZGU="); + expect(btoa("abcdef")).toBe("YWJjZGVm"); + expect(typeof btoa).toBe("function"); + try { + btoa(); + throw new Error("Expected error"); + } catch (error) { + expect(error.name).toBe("TypeError"); + } + var window = "[object Window]"; + expect(btoa("")).toBe(""); + expect(btoa(null)).toBe("bnVsbA=="); + expect(btoa(undefined)).toBe("dW5kZWZpbmVk"); + expect(btoa(window)).toBe("W29iamVjdCBXaW5kb3dd"); + expect(btoa("éé")).toBe("6ek="); + expect(btoa("\u0080\u0081")).toBe("gIE="); + expect(btoa(Bun)).toBe(btoa("[object Bun]")); +}); diff --git a/test/js/web/web-globals.test.js b/test/js/web/web-globals.test.js new file mode 100644 index 000000000..b7a243190 --- /dev/null +++ b/test/js/web/web-globals.test.js @@ -0,0 +1,156 @@ +import { unsafe } from "bun"; +import { expect, it, test } from "bun:test"; +import { withoutAggressiveGC } from "harness"; + +test("exists", () => { + expect(typeof URL !== "undefined").toBe(true); + expect(typeof URLSearchParams !== "undefined").toBe(true); + expect(typeof DOMException !== "undefined").toBe(true); + expect(typeof Event !== "undefined").toBe(true); + expect(typeof EventTarget !== "undefined").toBe(true); + expect(typeof AbortController !== "undefined").toBe(true); + expect(typeof AbortSignal !== "undefined").toBe(true); + expect(typeof CustomEvent !== "undefined").toBe(true); + expect(typeof Headers !== "undefined").toBe(true); + expect(typeof ErrorEvent !== "undefined").toBe(true); + expect(typeof CloseEvent !== "undefined").toBe(true); + expect(typeof MessageEvent !== "undefined").toBe(true); + expect(typeof TextEncoder !== "undefined").toBe(true); + expect(typeof WebSocket !== "undefined").toBe(true); + expect(typeof Blob !== "undefined").toBe(true); + expect(typeof FormData !== "undefined").toBe(true); +}); + +test("CloseEvent", () => { + var event = new CloseEvent("close", { reason: "world" }); + expect(event.type).toBe("close"); + const target = new EventTarget(); + var called = false; + target.addEventListener("close", ({ type, reason }) => { + expect(type).toBe("close"); + expect(reason).toBe("world"); + called = true; + }); + target.dispatchEvent(event); + expect(called).toBe(true); +}); + +test("MessageEvent", () => { + var event = new MessageEvent("message", { data: "world" }); + expect(event.type).toBe("message"); + const target = new EventTarget(); + var called = false; + target.addEventListener("message", ({ type, data }) => { + expect(type).toBe("message"); + expect(data).toBe("world"); + called = true; + }); + target.dispatchEvent(event); + expect(called).toBe(true); +}); + +it("crypto.getRandomValues", () => { + var foo = new Uint8Array(32); + + // run it once buffered and unbuffered + { + var array = crypto.getRandomValues(foo); + expect(array).toBe(foo); + expect(array.reduce((sum, a) => (sum += a === 0), 0) != foo.length).toBe(true); + } + + // disable it for this block because it tends to get stuck here running the GC forever + withoutAggressiveGC(() => { + // run it again to check that the fast path works + for (var i = 0; i < 9000; i++) { + var array = crypto.getRandomValues(foo); + expect(array).toBe(foo); + } + }); + + // run it on a large input + expect(!!crypto.getRandomValues(new Uint8Array(8096)).find(a => a > 0)).toBe(true); + + { + // any additional input into getRandomValues() makes it unbuffered + var array = crypto.getRandomValues(foo, "unbuffered"); + expect(array).toBe(foo); + expect(array.reduce((sum, a) => (sum += a === 0), 0) != foo.length).toBe(true); + } +}); + +// not actually a web global +it("crypto.timingSafeEqual", () => { + const crypto = import.meta.require("node:crypto"); + var uuidStr = crypto.randomUUID(); + expect(uuidStr.length).toBe(36); + expect(uuidStr[8]).toBe("-"); + expect(uuidStr[13]).toBe("-"); + expect(uuidStr[18]).toBe("-"); + expect(uuidStr[23]).toBe("-"); + const uuid = Buffer.from(uuidStr); + + expect(crypto.timingSafeEqual(uuid, uuid)).toBe(true); + expect(crypto.timingSafeEqual(uuid, uuid.slice())).toBe(true); + try { + crypto.timingSafeEqual(uuid, uuid.slice(1)); + expect(false).toBe(true); + } catch (e) {} + + try { + crypto.timingSafeEqual(uuid, uuid.slice(0, uuid.length - 2)); + expect(false).toBe(true); + } catch (e) { + expect(e.message).toBe("Input buffers must have the same length"); + } + + try { + expect(crypto.timingSafeEqual(uuid, crypto.randomUUID())).toBe(false); + expect(false).toBe(true); + } catch (e) { + expect(e.name).toBe("TypeError"); + } + + var shorter = uuid.slice(0, 1); + for (let i = 0; i < 9000; i++) { + if (!crypto.timingSafeEqual(shorter, shorter)) throw new Error("fail"); + } +}); + +it("crypto.randomUUID", () => { + var uuid = crypto.randomUUID(); + expect(uuid.length).toBe(36); + expect(uuid[8]).toBe("-"); + expect(uuid[13]).toBe("-"); + expect(uuid[18]).toBe("-"); + expect(uuid[23]).toBe("-"); + + withoutAggressiveGC(() => { + // check that the fast path works + for (let i = 0; i < 9000; i++) { + var uuid2 = crypto.randomUUID(); + expect(uuid2.length).toBe(36); + expect(uuid2[8]).toBe("-"); + expect(uuid2[13]).toBe("-"); + expect(uuid2[18]).toBe("-"); + expect(uuid2[23]).toBe("-"); + } + }); +}); + +it("URL.prototype.origin", () => { + const url = new URL("https://html.spec.whatwg.org/"); + const { origin, host, hostname } = url; + + expect(hostname).toBe("html.spec.whatwg.org"); + expect(host).toBe("html.spec.whatwg.org"); + expect(origin).toBe("https://html.spec.whatwg.org"); +}); + +test("navigator", () => { + expect(globalThis.navigator !== undefined).toBe(true); + const version = process.versions.bun; + const userAgent = `Bun/${version}`; + expect(navigator.hardwareConcurrency > 0).toBe(true); + expect(navigator.userAgent).toBe(userAgent); +}); diff --git a/test/js/web/websocket/websocket-subprocess.ts b/test/js/web/websocket/websocket-subprocess.ts new file mode 100644 index 000000000..fd25b7fd5 --- /dev/null +++ b/test/js/web/websocket/websocket-subprocess.ts @@ -0,0 +1,13 @@ +const host = process.argv[2]; + +const ws = new WebSocket(host); + +ws.onmessage = message => { + if (message.data === "hello websocket") { + ws.send("hello"); + } else if (message.data === "timeout") { + setTimeout(() => { + ws.send("close"); + }, 300); + } +}; diff --git a/test/js/web/websocket/websocket.test.js b/test/js/web/websocket/websocket.test.js new file mode 100644 index 000000000..f0f29c1c3 --- /dev/null +++ b/test/js/web/websocket/websocket.test.js @@ -0,0 +1,263 @@ +import { describe, it, expect } from "bun:test"; +import { unsafe, spawn, readableStreamToText } from "bun"; +import { bunExe, bunEnv, gc } from "harness"; + +const TEST_WEBSOCKET_HOST = process.env.TEST_WEBSOCKET_HOST || "wss://ws.postman-echo.com/raw"; + +describe("WebSocket", () => { + it("should connect", async () => { + const ws = new WebSocket(TEST_WEBSOCKET_HOST); + await new Promise((resolve, reject) => { + ws.onopen = resolve; + ws.onerror = reject; + }); + var closed = new Promise((resolve, reject) => { + ws.onclose = resolve; + }); + ws.close(); + await closed; + }); + + it("should connect over https", async () => { + const ws = new WebSocket(TEST_WEBSOCKET_HOST.replaceAll("wss:", "https:")); + await new Promise((resolve, reject) => { + ws.onopen = resolve; + ws.onerror = reject; + }); + var closed = new Promise((resolve, reject) => { + ws.onclose = resolve; + }); + ws.close(); + await closed; + }); + + it("supports headers", done => { + const server = Bun.serve({ + port: 8024, + fetch(req, server) { + expect(req.headers.get("X-Hello")).toBe("World"); + expect(req.headers.get("content-type")).toBe("lolwut"); + server.stop(); + done(); + return new Response(); + }, + websocket: { + open(ws) { + ws.close(); + }, + }, + }); + const ws = new WebSocket(`ws://${server.hostname}:${server.port}`, { + headers: { + "X-Hello": "World", + "content-type": "lolwut", + }, + }); + }); + + it("should connect over http", done => { + const server = Bun.serve({ + port: 8025, + fetch(req, server) { + server.stop(); + done(); + return new Response(); + }, + websocket: { + open(ws) { + ws.close(); + }, + }, + }); + const ws = new WebSocket(`http://${server.hostname}:${server.port}`, {}); + }); + + it("should send and receive messages", async () => { + const ws = new WebSocket(TEST_WEBSOCKET_HOST); + await new Promise((resolve, reject) => { + ws.onopen = resolve; + ws.onerror = reject; + ws.onclose = () => { + reject("WebSocket closed"); + }; + }); + const count = 10; + + // 10 messages in burst + var promise = new Promise((resolve, reject) => { + var remain = count; + ws.onmessage = event => { + gc(true); + expect(event.data).toBe("Hello World!"); + remain--; + + if (remain <= 0) { + ws.onmessage = () => {}; + resolve(); + } + }; + ws.onerror = reject; + }); + + for (let i = 0; i < count; i++) { + ws.send("Hello World!"); + gc(true); + } + + await promise; + var echo = 0; + + // 10 messages one at a time + function waitForEcho() { + return new Promise((resolve, reject) => { + gc(true); + const msg = `Hello World! ${echo++}`; + ws.onmessage = event => { + expect(event.data).toBe(msg); + resolve(); + }; + ws.onerror = reject; + ws.onclose = reject; + ws.send(msg); + gc(true); + }); + } + gc(true); + for (let i = 0; i < count; i++) await waitForEcho(); + ws.onclose = () => {}; + ws.onerror = () => {}; + ws.close(); + gc(true); + }); +}); + +describe("websocket in subprocess", () => { + var port = 8765; + it("should exit", async () => { + let messageReceived = false; + const server = Bun.serve({ + port: port++, + fetch(req, server) { + if (server.upgrade(req)) { + return; + } + + return new Response("http response"); + }, + websocket: { + open(ws) { + ws.send("hello websocket"); + }, + message(ws) { + messageReceived = true; + ws.close(); + }, + close(ws) {}, + }, + }); + const subprocess = Bun.spawn({ + cmd: [bunExe(), import.meta.dir + "/websocket-subprocess.ts", `http://${server.hostname}:${server.port}`], + stderr: "pipe", + stdin: "pipe", + stdout: "pipe", + env: bunEnv, + }); + + expect(await subprocess.exited).toBe(0); + expect(messageReceived).toBe(true); + server.stop(true); + }); + + it("should exit after killed", async () => { + const subprocess = Bun.spawn({ + cmd: [bunExe(), import.meta.dir + "/websocket-subprocess.ts", TEST_WEBSOCKET_HOST], + stderr: "pipe", + stdin: "pipe", + stdout: "pipe", + env: bunEnv, + }); + + subprocess.kill(); + + expect(await subprocess.exited).toBe("SIGHUP"); + }); + + it("should exit with invalid url", async () => { + const subprocess = Bun.spawn({ + cmd: [bunExe(), import.meta.dir + "/websocket-subprocess.ts", "invalid url"], + stderr: "pipe", + stdin: "pipe", + stdout: "pipe", + env: bunEnv, + }); + + expect(await subprocess.exited).toBe(1); + }); + + it("should exit after timeout", async () => { + let messageReceived = false; + let start = 0; + const server = Bun.serve({ + port: port++, + fetch(req, server) { + if (server.upgrade(req)) { + return; + } + + return new Response("http response"); + }, + websocket: { + open(ws) { + start = performance.now(); + ws.send("timeout"); + }, + message(ws, message) { + messageReceived = true; + expect(performance.now() - start >= 300).toBe(true); + ws.close(); + }, + close(ws) {}, + }, + }); + const subprocess = Bun.spawn({ + cmd: [bunExe(), import.meta.dir + "/websocket-subprocess.ts", `http://${server.hostname}:${server.port}`], + stderr: "pipe", + stdin: "pipe", + stdout: "pipe", + env: bunEnv, + }); + + expect(await subprocess.exited).toBe(0); + expect(messageReceived).toBe(true); + server.stop(true); + }); + + it("should exit after server stop and 0 messages", async () => { + const server = Bun.serve({ + port: port++, + fetch(req, server) { + if (server.upgrade(req)) { + return; + } + + return new Response("http response"); + }, + websocket: { + open(ws) {}, + message(ws, message) {}, + close(ws) {}, + }, + }); + + const subprocess = Bun.spawn({ + cmd: [bunExe(), import.meta.dir + "/websocket-subprocess.ts", `http://${server.hostname}:${server.port}`], + stderr: "pipe", + stdin: "pipe", + stdout: "pipe", + env: bunEnv, + }); + + server.stop(true); + expect(await subprocess.exited).toBe(0); + }); +}); diff --git a/test/js/workerd/html-rewriter.test.js b/test/js/workerd/html-rewriter.test.js new file mode 100644 index 000000000..b5db3fde2 --- /dev/null +++ b/test/js/workerd/html-rewriter.test.js @@ -0,0 +1,303 @@ +import { describe, it, expect } from "bun:test"; +import { gcTick } from "../gc"; + +var setTimeoutAsync = (fn, delay) => { + return new Promise((resolve, reject) => { + setTimeout(() => { + try { + resolve(fn()); + } catch (e) { + reject(e); + } + }, delay); + }); +}; + +describe("HTMLRewriter", () => { + it("HTMLRewriter: async replacement", async () => { + await gcTick(); + const res = new HTMLRewriter() + .on("div", { + async element(element) { + await setTimeoutAsync(() => { + element.setInnerContent("replace", { html: true }); + }, 5); + }, + }) + .transform(new Response("
example.com
")); + await gcTick(); + expect(await res.text()).toBe("
replace
"); + await gcTick(); + }); + + it("supports element handlers", async () => { + var rewriter = new HTMLRewriter(); + rewriter.on("div", { + element(element) { + element.setInnerContent("it worked!", { html: true }); + }, + }); + var input = new Response("
hello
"); + var output = rewriter.transform(input); + expect(await output.text()).toBe("
it worked!
"); + }); + + it("(from file) supports element handlers", async () => { + var rewriter = new HTMLRewriter(); + rewriter.on("div", { + element(element) { + element.setInnerContent("it worked!", { html: true }); + }, + }); + await Bun.write("/tmp/html-rewriter.txt.js", "
hello
"); + var input = new Response(Bun.file("/tmp/html-rewriter.txt.js")); + var output = rewriter.transform(input); + expect(await output.text()).toBe("
it worked!
"); + }); + + it("supports attribute iterator", async () => { + var rewriter = new HTMLRewriter(); + var expected = [ + ["first", ""], + ["second", "alrihgt"], + ["third", "123"], + ["fourth", "5"], + ["fifth", "helloooo"], + ]; + rewriter.on("div", { + element(element2) { + for (let attr of element2.attributes) { + const stack = expected.shift(); + expect(stack[0]).toBe(attr[0]); + expect(stack[1]).toBe(attr[1]); + } + }, + }); + var input = new Response('
hello
'); + var output = rewriter.transform(input); + expect(await output.text()).toBe('
hello
'); + expect(expected.length).toBe(0); + }); + + it("handles element specific mutations", async () => { + // prepend/append + let res = new HTMLRewriter() + .on("p", { + element(element) { + element.prepend("prepend"); + element.prepend("prepend html", { html: true }); + element.append("append"); + element.append("append html", { html: true }); + }, + }) + .transform(new Response("

test

")); + expect(await res.text()).toBe( + [ + "

", + "prepend html", + "<span>prepend</span>", + "test", + "<span>append</span>", + "append html", + "

", + ].join(""), + ); + + // setInnerContent + res = new HTMLRewriter() + .on("p", { + element(element) { + element.setInnerContent("replace"); + }, + }) + .transform(new Response("

test

")); + expect(await res.text()).toBe("

<span>replace</span>

"); + res = new HTMLRewriter() + .on("p", { + element(element) { + element.setInnerContent("replace", { html: true }); + }, + }) + .transform(new Response("

test

")); + expect(await res.text()).toBe("

replace

"); + + // removeAndKeepContent + res = new HTMLRewriter() + .on("p", { + element(element) { + element.removeAndKeepContent(); + }, + }) + .transform(new Response("

test

")); + expect(await res.text()).toBe("test"); + }); + + it("handles element class properties", async () => { + class Handler { + constructor(content) { + this.content = content; + } + + // noinspection JSUnusedGlobalSymbols + element(element) { + element.setInnerContent(this.content); + } + } + const res = new HTMLRewriter().on("p", new Handler("new")).transform(new Response("

test

")); + expect(await res.text()).toBe("

new

"); + }); + + const commentsMutationsInput = "

"; + const commentsMutationsExpected = { + beforeAfter: [ + "

", + "<span>before</span>", + "before html", + "", + "after html", + "<span>after</span>", + "

", + ].join(""), + replace: "

<span>replace</span>

", + replaceHtml: "

replace

", + remove: "

", + }; + + const commentPropertiesMacro = async func => { + const res = func(new HTMLRewriter(), comment => { + expect(comment.removed).toBe(false); + expect(comment.text).toBe("test"); + comment.text = "new"; + expect(comment.text).toBe("new"); + }).transform(new Response("

")); + expect(await res.text()).toBe("

"); + }; + + it("HTMLRewriter: handles comment properties", () => + commentPropertiesMacro((rw, comments) => { + rw.on("p", { comments }); + return rw; + })); + + it("selector tests", async () => { + const checkSelector = async (selector, input, expected) => { + const res = new HTMLRewriter() + .on(selector, { + element(element) { + element.setInnerContent("new"); + }, + }) + .transform(new Response(input)); + expect(await res.text()).toBe(expected); + }; + + await checkSelector("*", "

1

2

", "

new

new

"); + await checkSelector("p", "

1

2

", "

1

new

"); + await checkSelector( + "p:nth-child(2)", + "

1

2

3

", + "

1

new

3

", + ); + await checkSelector( + "p:first-child", + "

1

2

3

", + "

new

2

3

", + ); + await checkSelector( + "p:nth-of-type(2)", + "

1

2

3

4

5

", + "

1

2

new

4

5

", + ); + await checkSelector( + "p:first-of-type", + "

1

2

3

", + "

1

new

3

", + ); + await checkSelector( + "p:not(:first-child)", + "

1

2

3

", + "

1

new

new

", + ); + await checkSelector("p.red", '

1

2

', '

new

2

'); + await checkSelector("h1#header", '

1

2

', '

new

2

'); + await checkSelector("p[data-test]", "

1

2

", "

new

2

"); + await checkSelector( + 'p[data-test="one"]', + '

1

2

', + '

new

2

', + ); + await checkSelector( + 'p[data-test="one" i]', + '

1

2

3

', + '

new

new

3

', + ); + await checkSelector( + 'p[data-test="one" s]', + '

1

2

3

', + '

new

2

3

', + ); + await checkSelector( + 'p[data-test~="two"]', + '

1

2

3

', + '

new

new

3

', + ); + await checkSelector( + 'p[data-test^="a"]', + '

1

2

3

', + '

new

new

3

', + ); + await checkSelector( + 'p[data-test$="1"]', + '

1

2

3

', + '

new

2

new

', + ); + await checkSelector( + 'p[data-test*="b"]', + '

1

2

3

', + '

new

new

3

', + ); + await checkSelector( + 'p[data-test|="a"]', + '

1

2

3

', + '

new

new

3

', + ); + await checkSelector( + "div span", + "

1

23
", + "

new

new3
", + ); + await checkSelector( + "div > span", + "

1

23
", + "

1

new3
", + ); + }); + + it("supports deleting innerContent", async () => { + expect( + await new HTMLRewriter() + .on("div", { + element(elem) { + // https://github.com/oven-sh/bun/issues/2323 + elem.setInnerContent(""); + }, + }) + .transform(new Response("
content
")) + .text(), + ).toEqual("
"); + }); + + it("supports deleting innerHTML", async () => { + expect( + await new HTMLRewriter() + .on("div", { + element(elem) { + // https://github.com/oven-sh/bun/issues/2323 + elem.setInnerContent("", { html: true }); + }, + }) + .transform(new Response("
content
")) + .text(), + ).toEqual("
"); + }); +}); diff --git a/test/leaks/http-static-leak.ts b/test/leaks/http-static-leak.ts deleted file mode 100644 index 09935d24d..000000000 --- a/test/leaks/http-static-leak.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { heapStats } from "bun:jsc"; -var prevCounts; -export default { - fetch(req) { - const out = {}; - const counts = heapStats().objectTypeCounts; - for (const key in counts) { - if (prevCounts) { - if (prevCounts[key] && counts[key] > prevCounts[key]) { - out[key] = counts[key]; - } - } else { - if (counts[key] > 1) { - out[key] = counts[key]; - } - } - } - prevCounts = counts; - if (req.url.includes("gc")) { - Bun.gc(false); - } - - return new Response(JSON.stringify(out), { - headers: { - "content-type": "application/json", - }, - }); - }, -}; diff --git a/test/macro/assert.tsx b/test/macro/assert.tsx deleted file mode 100644 index 8a6e4d822..000000000 --- a/test/macro/assert.tsx +++ /dev/null @@ -1,4 +0,0 @@ -// This logs the result at build time -export function unreachable(call) { - throw new Error(call.arguments[0].toString() || "unreachable"); -} diff --git a/test/macro/fetchSync.tsx b/test/macro/fetchSync.tsx deleted file mode 100644 index 51d1ca4f5..000000000 --- a/test/macro/fetchSync.tsx +++ /dev/null @@ -1,9 +0,0 @@ -// @ts-nocheck -export async function fetchSync(ctx) { - const str = ctx.arguments[0].toString(); - - const response = await fetch(str); - const text = await response.text(); - - return ; -} diff --git a/test/macro/hello-fetch-macro.tsx b/test/macro/hello-fetch-macro.tsx deleted file mode 100644 index fa0de4a9d..000000000 --- a/test/macro/hello-fetch-macro.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { fetchSync } from "macro:./fetchSync.tsx"; - -const synchronousFetch = fetchSync(`https://example.com`); - -console.log(synchronousFetch); diff --git a/test/macro/loadMocks.tsx b/test/macro/loadMocks.tsx deleted file mode 100644 index 08e90dece..000000000 --- a/test/macro/loadMocks.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { unreachable } from "macro:./assert"; - -if (process.env.NODE_ENV !== "test") unreachable("This module should only be imported in tests"); - -export const mockData = { - Copilot: { - id: "Copilot", - name: "Copilot", - description: "Copilot", - icon: "https://s3.amazonaws.com/copilot-public/images/icons/Copilot.png", - color: "#00AEEF", - type: "service", - tags: ["copilot"], - categories: ["copilot"], - links: [ - { - id: "Copilot", - name: "Copilot", - url: "https://copilot.io", - description: "Copilot", - icon: "https://s3.amazonaws.com/copilot-public/images/icons/Copilot.png", - color: "#00AEEF", - type: "service", - tags: ["copilot"], - categories: ["copilot"], - }, - ], - }, -}; diff --git a/test/macro/macro.d.ts b/test/macro/macro.d.ts deleted file mode 100644 index ba02c03ed..000000000 --- a/test/macro/macro.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module "macro:*"; diff --git a/test/mkfifo.ts b/test/mkfifo.ts new file mode 100644 index 000000000..48471cbf5 --- /dev/null +++ b/test/mkfifo.ts @@ -0,0 +1,22 @@ +import { dlopen, ptr } from "bun:ffi"; + +var lazyMkfifo; +export function mkfifo(path: string, permissions: number = 0o666): void { + if (!lazyMkfifo) { + const suffix = process.platform === "darwin" ? "dylib" : "so.6"; + lazyMkfifo = dlopen(`libc.${suffix}`, { + mkfifo: { + args: ["ptr", "i32"], + returns: "i32", + }, + }).symbols.mkfifo; + } + + const buf = new Uint8Array(Buffer.byteLength(path) + 1); + new TextEncoder().encodeInto(path, buf); + const rc = lazyMkfifo(ptr(buf), permissions); + + if (rc < 0) { + throw new Error(`mkfifo failed`); + } +} diff --git a/test/package.json b/test/package.json new file mode 100644 index 000000000..dfeabcd42 --- /dev/null +++ b/test/package.json @@ -0,0 +1,19 @@ +{ + "private": true, + "name": "test", + "type": "module", + "devDependencies": { + "bun-types": "canary" + }, + "dependencies": { + "bktree-fast": "^0.0.7", + "body-parser": "^1.20.2", + "esbuild": "^0.17.11", + "express": "^4.18.2", + "iconv-lite": "^0.6.3", + "lodash": "^4.17.21", + "react-dom": "^18.2.0", + "svelte": "^3.55.1", + "undici": "^5.20.0" + } +} \ No newline at end of file diff --git a/test/regression/issue/00631.test.ts b/test/regression/issue/00631.test.ts new file mode 100644 index 000000000..691c3227c --- /dev/null +++ b/test/regression/issue/00631.test.ts @@ -0,0 +1,38 @@ +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 { join } from "path"; + +it("JSON strings escaped properly", async () => { + const testDir = mkdtempSync(join(tmpdir(), "issue631-")); + + // Clean up from prior runs if necessary + rmSync(testDir, { recursive: true, force: true }); + + // Create a directory with our test package file + mkdirSync(testDir, { recursive: true }); + writeFileSync(testDir + "package.json", String.raw`{"testRegex":"\\a\n\\b\\"}`); + + // Attempt to add a package, causing the package file to be parsed, modified, + // written, and reparsed. This verifies that escaped backslashes in JSON + // survive the roundtrip + const { exitCode, stderr } = Bun.spawnSync({ + cmd: [bunExe(), "add", "left-pad"], + env: bunEnv, + cwd: testDir, + }); + expect(exitCode).toBe(0); + + console.log(testDir); + const packageContents = readFileSync(testDir + "package.json", { encoding: "utf8" }); + expect(packageContents).toBe(String.raw`{ + "testRegex": "\\a\n\\b\\", + "dependencies": { + "left-pad": "^1.3.0" + } +}`); + + //// If successful clean up test artifacts + rmSync(testDir, { recursive: true }); +}); diff --git a/test/regression/issue/02005.test.ts b/test/regression/issue/02005.test.ts new file mode 100644 index 000000000..230be5301 --- /dev/null +++ b/test/regression/issue/02005.test.ts @@ -0,0 +1,11 @@ +import { it, expect } from "bun:test"; + +it("regex literal with non-latin1 should work", () => { + const text = "这是一段要替换的文字"; + + //Correct results: 这是一段的文字 + expect(text.replace(new RegExp("要替换"), "")).toBe("这是一段的文字"); + + //Incorrect result: 这是一段要替换的文字 + expect(text.replace(/要替换/, "")).toBe("这是一段的文字"); +}); diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 000000000..e879cd5f5 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "noEmit": true, + "lib": ["ESNext"], + "module": "ESNext", + "target": "ESNext", + "moduleResolution": "nodenext", + "strict": true, + "downlevelIteration": true, + "skipLibCheck": true, + "jsx": "preserve", + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "types": ["bun-types"], + "baseUrl": ".", + "paths": { + "harness": ["harness.ts"], + "mkfifo": ["mkfifo.ts"], + "node-harness": ["js/node/harness.ts"] + } + } +} -- cgit v1.2.3