mirror of
https://github.com/oven-sh/bun
synced 2026-02-23 09:11:51 +00:00
Compare commits
16 Commits
pfg/7823
...
claude/pos
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7898b08a52 | ||
|
|
2ec98a0628 | ||
|
|
346c765147 | ||
|
|
bbe03a2d06 | ||
|
|
c478f6deee | ||
|
|
bbe7f81ebe | ||
|
|
33d4757321 | ||
|
|
5097b129c6 | ||
|
|
a2637497a4 | ||
|
|
504052d9b0 | ||
|
|
cf9761367e | ||
|
|
fac5e71a0c | ||
|
|
53b870af74 | ||
|
|
bf24d1b527 | ||
|
|
49f33c948a | ||
|
|
c106820a57 |
24
.github/workflows/auto-label-claude-prs.yml
vendored
Normal file
24
.github/workflows/auto-label-claude-prs.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Auto-label Claude PRs
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
auto-label:
|
||||
if: github.event.pull_request.user.login == 'robobun'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Add claude label to PRs from robobun
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: ['claude']
|
||||
});
|
||||
@@ -98,6 +98,11 @@ src/bun.js/api/bun/spawn.zig
|
||||
src/bun.js/api/bun/spawn/stdio.zig
|
||||
src/bun.js/api/bun/ssl_wrapper.zig
|
||||
src/bun.js/api/bun/subprocess.zig
|
||||
src/bun.js/api/bun/subprocess/Readable.zig
|
||||
src/bun.js/api/bun/subprocess/ResourceUsage.zig
|
||||
src/bun.js/api/bun/subprocess/StaticPipeWriter.zig
|
||||
src/bun.js/api/bun/subprocess/SubprocessPipeReader.zig
|
||||
src/bun.js/api/bun/subprocess/Writable.zig
|
||||
src/bun.js/api/bun/udp_socket.zig
|
||||
src/bun.js/api/bun/x509.zig
|
||||
src/bun.js/api/BunObject.zig
|
||||
@@ -279,6 +284,81 @@ src/bun.js/test/diff_format.zig
|
||||
src/bun.js/test/diff/diff_match_patch.zig
|
||||
src/bun.js/test/diff/printDiff.zig
|
||||
src/bun.js/test/expect.zig
|
||||
src/bun.js/test/expect/toBe.zig
|
||||
src/bun.js/test/expect/toBeArray.zig
|
||||
src/bun.js/test/expect/toBeArrayOfSize.zig
|
||||
src/bun.js/test/expect/toBeBoolean.zig
|
||||
src/bun.js/test/expect/toBeCloseTo.zig
|
||||
src/bun.js/test/expect/toBeDate.zig
|
||||
src/bun.js/test/expect/toBeDefined.zig
|
||||
src/bun.js/test/expect/toBeEmpty.zig
|
||||
src/bun.js/test/expect/toBeEmptyObject.zig
|
||||
src/bun.js/test/expect/toBeEven.zig
|
||||
src/bun.js/test/expect/toBeFalse.zig
|
||||
src/bun.js/test/expect/toBeFalsy.zig
|
||||
src/bun.js/test/expect/toBeFinite.zig
|
||||
src/bun.js/test/expect/toBeFunction.zig
|
||||
src/bun.js/test/expect/toBeGreaterThan.zig
|
||||
src/bun.js/test/expect/toBeGreaterThanOrEqual.zig
|
||||
src/bun.js/test/expect/toBeInstanceOf.zig
|
||||
src/bun.js/test/expect/toBeInteger.zig
|
||||
src/bun.js/test/expect/toBeLessThan.zig
|
||||
src/bun.js/test/expect/toBeLessThanOrEqual.zig
|
||||
src/bun.js/test/expect/toBeNaN.zig
|
||||
src/bun.js/test/expect/toBeNegative.zig
|
||||
src/bun.js/test/expect/toBeNil.zig
|
||||
src/bun.js/test/expect/toBeNull.zig
|
||||
src/bun.js/test/expect/toBeNumber.zig
|
||||
src/bun.js/test/expect/toBeObject.zig
|
||||
src/bun.js/test/expect/toBeOdd.zig
|
||||
src/bun.js/test/expect/toBeOneOf.zig
|
||||
src/bun.js/test/expect/toBePositive.zig
|
||||
src/bun.js/test/expect/toBeString.zig
|
||||
src/bun.js/test/expect/toBeSymbol.zig
|
||||
src/bun.js/test/expect/toBeTrue.zig
|
||||
src/bun.js/test/expect/toBeTruthy.zig
|
||||
src/bun.js/test/expect/toBeTypeOf.zig
|
||||
src/bun.js/test/expect/toBeUndefined.zig
|
||||
src/bun.js/test/expect/toBeValidDate.zig
|
||||
src/bun.js/test/expect/toBeWithin.zig
|
||||
src/bun.js/test/expect/toContain.zig
|
||||
src/bun.js/test/expect/toContainAllKeys.zig
|
||||
src/bun.js/test/expect/toContainAllValues.zig
|
||||
src/bun.js/test/expect/toContainAnyKeys.zig
|
||||
src/bun.js/test/expect/toContainAnyValues.zig
|
||||
src/bun.js/test/expect/toContainEqual.zig
|
||||
src/bun.js/test/expect/toContainKey.zig
|
||||
src/bun.js/test/expect/toContainKeys.zig
|
||||
src/bun.js/test/expect/toContainValue.zig
|
||||
src/bun.js/test/expect/toContainValues.zig
|
||||
src/bun.js/test/expect/toEndWith.zig
|
||||
src/bun.js/test/expect/toEqual.zig
|
||||
src/bun.js/test/expect/toEqualIgnoringWhitespace.zig
|
||||
src/bun.js/test/expect/toHaveBeenCalled.zig
|
||||
src/bun.js/test/expect/toHaveBeenCalledOnce.zig
|
||||
src/bun.js/test/expect/toHaveBeenCalledTimes.zig
|
||||
src/bun.js/test/expect/toHaveBeenCalledWith.zig
|
||||
src/bun.js/test/expect/toHaveBeenLastCalledWith.zig
|
||||
src/bun.js/test/expect/toHaveBeenNthCalledWith.zig
|
||||
src/bun.js/test/expect/toHaveLastReturnedWith.zig
|
||||
src/bun.js/test/expect/toHaveLength.zig
|
||||
src/bun.js/test/expect/toHaveNthReturnedWith.zig
|
||||
src/bun.js/test/expect/toHaveProperty.zig
|
||||
src/bun.js/test/expect/toHaveReturned.zig
|
||||
src/bun.js/test/expect/toHaveReturnedTimes.zig
|
||||
src/bun.js/test/expect/toHaveReturnedWith.zig
|
||||
src/bun.js/test/expect/toInclude.zig
|
||||
src/bun.js/test/expect/toIncludeRepeated.zig
|
||||
src/bun.js/test/expect/toMatch.zig
|
||||
src/bun.js/test/expect/toMatchInlineSnapshot.zig
|
||||
src/bun.js/test/expect/toMatchObject.zig
|
||||
src/bun.js/test/expect/toMatchSnapshot.zig
|
||||
src/bun.js/test/expect/toSatisfy.zig
|
||||
src/bun.js/test/expect/toStartWith.zig
|
||||
src/bun.js/test/expect/toStrictEqual.zig
|
||||
src/bun.js/test/expect/toThrow.zig
|
||||
src/bun.js/test/expect/toThrowErrorMatchingInlineSnapshot.zig
|
||||
src/bun.js/test/expect/toThrowErrorMatchingSnapshot.zig
|
||||
src/bun.js/test/jest.zig
|
||||
src/bun.js/test/pretty_format.zig
|
||||
src/bun.js/test/snapshot.zig
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
# `mock.module()`
|
||||
|
||||
The `mock.module()` function allows you to mock an entire module in Bun's test framework. This is useful when you want to replace a module's exports with mock implementations.
|
||||
|
||||
## Example
|
||||
|
||||
```typescript
|
||||
import { test, expect, mock } from "bun:test";
|
||||
import { foo } from "./some-module";
|
||||
|
||||
test("mock.module works", () => {
|
||||
// Original behavior
|
||||
expect(foo()).toBe("original");
|
||||
|
||||
// Mock the module
|
||||
mock.module("./some-module", () => ({
|
||||
foo: () => "mocked"
|
||||
}));
|
||||
|
||||
// Mocked behavior
|
||||
expect(foo()).toBe("mocked");
|
||||
});
|
||||
```
|
||||
|
||||
## Restoring mocked modules
|
||||
|
||||
When you use `mock.restore()` to restore a mocked module, it clears the mocked implementation but the imported module might still reference the mocked version. To fully restore the original module, you need to re-import it:
|
||||
|
||||
```typescript
|
||||
import { test, expect, mock } from "bun:test";
|
||||
import { foo } from "./some-module";
|
||||
|
||||
test("mock.restore works with mock.module", async () => {
|
||||
// Original behavior
|
||||
expect(foo()).toBe("original");
|
||||
|
||||
// Mock the module
|
||||
mock.module("./some-module", () => ({
|
||||
foo: () => "mocked"
|
||||
}));
|
||||
|
||||
// Mocked behavior
|
||||
expect(foo()).toBe("mocked");
|
||||
|
||||
// Restore all mocks
|
||||
mock.restore();
|
||||
|
||||
// Re-import the module to get the original behavior
|
||||
const module = await import("./some-module?timestamp=" + Date.now());
|
||||
const restoredFoo = module.foo;
|
||||
|
||||
// Original behavior is restored
|
||||
expect(restoredFoo()).toBe("original");
|
||||
});
|
||||
```
|
||||
|
||||
The query parameter (`?timestamp=...`) is added to bypass the module cache, forcing a fresh import of the original module.
|
||||
|
||||
## API
|
||||
|
||||
### `mock.module(specifier: string, factory: () => Record<string, any>): void`
|
||||
|
||||
- `specifier`: The module specifier to mock. This can be a relative path, package name, or absolute path.
|
||||
- `factory`: A function that returns an object with the mock exports. This object will replace the real exports of the module.
|
||||
|
||||
## Notes
|
||||
|
||||
- Mocked modules affect all imports of the module, even imports that occurred before the mock was set up.
|
||||
- Use `mock.restore()` to clear all mocks, including mocked modules.
|
||||
- You need to re-import the module after `mock.restore()` to get the original behavior.
|
||||
@@ -532,6 +532,74 @@ Hello World! pwd=C:\Users\Demo
|
||||
|
||||
Bun Shell is a small programming language in Bun that is implemented in Zig. It includes a handwritten lexer, parser, and interpreter. Unlike bash, zsh, and other shells, Bun Shell runs operations concurrently.
|
||||
|
||||
## Security in the Bun shell
|
||||
|
||||
By design, the Bun shell _does not invoke a system shell_ (like `/bin/sh`) and
|
||||
is instead a re-implementation of bash that runs in the same Bun process,
|
||||
designed with security in mind.
|
||||
|
||||
When parsing command arguments, it treats all _interpolated variables_ as single, literal strings.
|
||||
|
||||
This protects the Bun shell against **command injection**:
|
||||
|
||||
```js
|
||||
import { $ } from "bun";
|
||||
|
||||
const userInput = "my-file.txt; rm -rf /";
|
||||
|
||||
// SAFE: `userInput` is treated as a single quoted string
|
||||
await $`ls ${userInput}`;
|
||||
```
|
||||
|
||||
In the above example, `userInput` is treated as a single string. This causes
|
||||
the `ls` command to try to read the contents of a single directory named
|
||||
"my-file; rm -rf /".
|
||||
|
||||
### Security considerations
|
||||
|
||||
While command injection is prevented by default, developers are still
|
||||
responsible for security in certain scenarios.
|
||||
|
||||
Similar to the `Bun.spawn` or `node:child_process.exec()` APIs, you can intentionally
|
||||
execute a command which spawns a new shell (e.g. `bash -c`) with arguments.
|
||||
|
||||
When you do this, you hand off control, and Bun's built-in protections no
|
||||
longer apply to the string interpreted by that new shell.
|
||||
|
||||
```js
|
||||
import { $ } from "bun";
|
||||
|
||||
const userInput = "world; touch /tmp/pwned";
|
||||
|
||||
// UNSAFE: You have explicitly started a new shell process with `bash -c`.
|
||||
// This new shell will execute the `touch` command. Any user input
|
||||
// passed this way must be rigorously sanitized.
|
||||
await $`bash -c "echo ${userInput}"`;
|
||||
```
|
||||
|
||||
### Argument injection
|
||||
|
||||
The Bun shell cannot know how an external command interprets its own
|
||||
command-line arguments. An attacker can supply input that the target program
|
||||
recognizes as one of its own options or flags, leading to unintended behavior.
|
||||
|
||||
```js
|
||||
import { $ } from "bun";
|
||||
|
||||
// Malicious input formatted as a Git command-line flag
|
||||
const branch = "--upload-pack=echo pwned";
|
||||
|
||||
// UNSAFE: While Bun safely passes the string as a single argument,
|
||||
// the `git` program itself sees and acts upon the malicious flag.
|
||||
await $`git ls-remote origin ${branch}`;
|
||||
```
|
||||
|
||||
{% callout %}
|
||||
**Recommendation** — As is best practice in every language, always sanitize
|
||||
user-provided input before passing it as an argument to an external command.
|
||||
The responsibility for validating arguments rests with your application code.
|
||||
{% /callout %}
|
||||
|
||||
## Credits
|
||||
|
||||
Large parts of this API were inspired by [zx](https://github.com/google/zx), [dax](https://github.com/dsherret/dax), and [bnx](https://github.com/wobsoriano/bnx). Thank you to the authors of those projects.
|
||||
|
||||
@@ -48,6 +48,9 @@
|
||||
"css-properties": "bun run src/css/properties/generate_properties.ts",
|
||||
"uv-posix-stubs": "bun run src/bun.js/bindings/libuv/generate_uv_posix_stubs.ts",
|
||||
"bump": "bun ./scripts/bump.ts",
|
||||
"jsc:build": "bun ./scripts/build-jsc.ts release",
|
||||
"jsc:build:debug": "bun ./scripts/build-jsc.ts debug",
|
||||
"jsc:build:lto": "bun ./scripts/build-jsc.ts lto",
|
||||
"typecheck": "tsc --noEmit && cd test && bun run typecheck",
|
||||
"fmt": "bun run prettier",
|
||||
"fmt:cpp": "bun run clang-format",
|
||||
|
||||
215
scripts/build-jsc.ts
Executable file
215
scripts/build-jsc.ts
Executable file
@@ -0,0 +1,215 @@
|
||||
#!/usr/bin/env bun
|
||||
import { spawnSync } from "child_process";
|
||||
import { existsSync, mkdirSync } from "fs";
|
||||
import { arch, platform } from "os";
|
||||
import { join, resolve } from "path";
|
||||
|
||||
// Build configurations
|
||||
type BuildConfig = "debug" | "release" | "lto";
|
||||
|
||||
// Parse command line arguments
|
||||
const args = process.argv.slice(2);
|
||||
const buildConfig: BuildConfig = (args[0] as BuildConfig) || "debug";
|
||||
const validConfigs = ["debug", "release", "lto"];
|
||||
|
||||
if (!validConfigs.includes(buildConfig)) {
|
||||
console.error(`Invalid build configuration: ${buildConfig}`);
|
||||
console.error(`Valid configurations: ${validConfigs.join(", ")}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Detect platform
|
||||
const OS_NAME = platform().toLowerCase();
|
||||
const ARCH_NAME_RAW = arch();
|
||||
const IS_MAC = OS_NAME === "darwin";
|
||||
const IS_LINUX = OS_NAME === "linux";
|
||||
const IS_ARM64 = ARCH_NAME_RAW === "arm64" || ARCH_NAME_RAW === "aarch64";
|
||||
|
||||
// Paths
|
||||
const ROOT_DIR = resolve(import.meta.dir, "..");
|
||||
const WEBKIT_DIR = resolve(ROOT_DIR, "vendor/WebKit");
|
||||
const WEBKIT_BUILD_DIR = join(WEBKIT_DIR, "WebKitBuild");
|
||||
const WEBKIT_RELEASE_DIR = join(WEBKIT_BUILD_DIR, "Release");
|
||||
const WEBKIT_DEBUG_DIR = join(WEBKIT_BUILD_DIR, "Debug");
|
||||
const WEBKIT_RELEASE_DIR_LTO = join(WEBKIT_BUILD_DIR, "ReleaseLTO");
|
||||
|
||||
// Homebrew prefix detection
|
||||
const HOMEBREW_PREFIX = IS_ARM64 ? "/opt/homebrew/" : "/usr/local/";
|
||||
|
||||
// Compiler detection
|
||||
function findExecutable(names: string[]): string | null {
|
||||
for (const name of names) {
|
||||
const result = spawnSync("which", [name], { encoding: "utf8" });
|
||||
if (result.status === 0) {
|
||||
return result.stdout.trim();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const CC = findExecutable(["clang-19", "clang"]) || "clang";
|
||||
const CXX = findExecutable(["clang++-19", "clang++"]) || "clang++";
|
||||
|
||||
// Build directory based on config
|
||||
const getBuildDir = (config: BuildConfig) => {
|
||||
switch (config) {
|
||||
case "debug":
|
||||
return WEBKIT_DEBUG_DIR;
|
||||
case "lto":
|
||||
return WEBKIT_RELEASE_DIR_LTO;
|
||||
default:
|
||||
return WEBKIT_RELEASE_DIR;
|
||||
}
|
||||
};
|
||||
|
||||
// Common CMake flags
|
||||
const getCommonFlags = () => {
|
||||
const flags = [
|
||||
"-DPORT=JSCOnly",
|
||||
"-DENABLE_STATIC_JSC=ON",
|
||||
"-DALLOW_LINE_AND_COLUMN_NUMBER_IN_BUILTINS=ON",
|
||||
"-DUSE_THIN_ARCHIVES=OFF",
|
||||
"-DUSE_BUN_JSC_ADDITIONS=ON",
|
||||
"-DUSE_BUN_EVENT_LOOP=ON",
|
||||
"-DENABLE_FTL_JIT=ON",
|
||||
"-G",
|
||||
"Ninja",
|
||||
`-DCMAKE_C_COMPILER=${CC}`,
|
||||
`-DCMAKE_CXX_COMPILER=${CXX}`,
|
||||
];
|
||||
|
||||
if (IS_MAC) {
|
||||
flags.push(
|
||||
"-DENABLE_SINGLE_THREADED_VM_ENTRY_SCOPE=ON",
|
||||
"-DBUN_FAST_TLS=ON",
|
||||
"-DPTHREAD_JIT_PERMISSIONS_API=1",
|
||||
"-DUSE_PTHREAD_JIT_PERMISSIONS_API=ON",
|
||||
);
|
||||
} else if (IS_LINUX) {
|
||||
flags.push(
|
||||
"-DJSEXPORT_PRIVATE=WTF_EXPORT_DECLARATION",
|
||||
"-DUSE_VISIBILITY_ATTRIBUTE=1",
|
||||
"-DENABLE_REMOTE_INSPECTOR=ON",
|
||||
);
|
||||
}
|
||||
|
||||
return flags;
|
||||
};
|
||||
|
||||
// Build-specific CMake flags
|
||||
const getBuildFlags = (config: BuildConfig) => {
|
||||
const flags = [...getCommonFlags()];
|
||||
|
||||
switch (config) {
|
||||
case "debug":
|
||||
flags.push(
|
||||
"-DCMAKE_BUILD_TYPE=Debug",
|
||||
"-DENABLE_BUN_SKIP_FAILING_ASSERTIONS=ON",
|
||||
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
|
||||
"-DENABLE_REMOTE_INSPECTOR=ON",
|
||||
"-DUSE_VISIBILITY_ATTRIBUTE=1",
|
||||
);
|
||||
|
||||
if (IS_MAC) {
|
||||
// Enable address sanitizer by default on Mac debug builds
|
||||
flags.push("-DENABLE_SANITIZERS=address");
|
||||
// To disable asan, comment the line above and uncomment:
|
||||
// flags.push("-DENABLE_MALLOC_HEAP_BREAKDOWN=ON");
|
||||
}
|
||||
break;
|
||||
|
||||
case "lto":
|
||||
flags.push("-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_C_FLAGS=-flto=full", "-DCMAKE_CXX_FLAGS=-flto=full");
|
||||
break;
|
||||
|
||||
default: // release
|
||||
flags.push("-DCMAKE_BUILD_TYPE=RelWithDebInfo");
|
||||
break;
|
||||
}
|
||||
|
||||
return flags;
|
||||
};
|
||||
|
||||
// Environment variables for the build
|
||||
const getBuildEnv = () => {
|
||||
const env = { ...process.env };
|
||||
|
||||
const cflags = ["-ffat-lto-objects"];
|
||||
const cxxflags = ["-ffat-lto-objects"];
|
||||
|
||||
if (IS_LINUX && buildConfig !== "lto") {
|
||||
cflags.push("-Wl,--whole-archive");
|
||||
cxxflags.push("-Wl,--whole-archive", "-DUSE_BUN_JSC_ADDITIONS=ON", "-DUSE_BUN_EVENT_LOOP=ON");
|
||||
}
|
||||
|
||||
env.CFLAGS = (env.CFLAGS || "") + " " + cflags.join(" ");
|
||||
env.CXXFLAGS = (env.CXXFLAGS || "") + " " + cxxflags.join(" ");
|
||||
|
||||
if (IS_MAC) {
|
||||
env.ICU_INCLUDE_DIRS = `${HOMEBREW_PREFIX}opt/icu4c/include`;
|
||||
}
|
||||
|
||||
return env;
|
||||
};
|
||||
|
||||
// Run a command with proper error handling
|
||||
function runCommand(command: string, args: string[], options: any = {}) {
|
||||
console.log(`Running: ${command} ${args.join(" ")}`);
|
||||
const result = spawnSync(command, args, {
|
||||
stdio: "inherit",
|
||||
...options,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
console.error(`Failed to execute command: ${result.error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (result.status !== 0) {
|
||||
console.error(`Command failed with exit code ${result.status}`);
|
||||
process.exit(result.status || 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Main build function
|
||||
function buildJSC() {
|
||||
const buildDir = getBuildDir(buildConfig);
|
||||
const cmakeFlags = getBuildFlags(buildConfig);
|
||||
const env = getBuildEnv();
|
||||
|
||||
console.log(`Building JSC with configuration: ${buildConfig}`);
|
||||
console.log(`Build directory: ${buildDir}`);
|
||||
|
||||
// Create build directories
|
||||
if (!existsSync(buildDir)) {
|
||||
mkdirSync(buildDir, { recursive: true });
|
||||
}
|
||||
|
||||
if (!existsSync(WEBKIT_DIR)) {
|
||||
mkdirSync(WEBKIT_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
// Configure with CMake
|
||||
console.log("\n📦 Configuring with CMake...");
|
||||
runCommand("cmake", [...cmakeFlags, WEBKIT_DIR, buildDir], {
|
||||
cwd: buildDir,
|
||||
env,
|
||||
});
|
||||
|
||||
// Build with CMake
|
||||
console.log("\n🔨 Building JSC...");
|
||||
const buildType = buildConfig === "debug" ? "Debug" : buildConfig === "lto" ? "Release" : "RelWithDebInfo";
|
||||
|
||||
runCommand("cmake", ["--build", buildDir, "--config", buildType, "--target", "jsc"], {
|
||||
cwd: buildDir,
|
||||
env,
|
||||
});
|
||||
|
||||
console.log(`\n✅ JSC build completed successfully!`);
|
||||
console.log(`Build output: ${buildDir}`);
|
||||
}
|
||||
|
||||
// Entry point
|
||||
if (import.meta.main) {
|
||||
buildJSC();
|
||||
}
|
||||
@@ -2903,22 +2903,18 @@ fn encodeSerializedFailures(
|
||||
buf: *std.ArrayList(u8),
|
||||
inspector_agent: ?*BunFrontendDevServerAgent,
|
||||
) bun.OOM!void {
|
||||
var all_failures_len: usize = 0;
|
||||
for (failures) |fail| all_failures_len += fail.data.len;
|
||||
var all_failures = try std.ArrayListUnmanaged(u8).initCapacity(dev.allocator, all_failures_len);
|
||||
defer all_failures.deinit(dev.allocator);
|
||||
for (failures) |fail| all_failures.appendSliceAssumeCapacity(fail.data);
|
||||
|
||||
const failures_start_buf_pos = buf.items.len;
|
||||
for (failures) |fail| {
|
||||
const len = bun.base64.encodeLen(fail.data);
|
||||
|
||||
try buf.ensureUnusedCapacity(len);
|
||||
const start = buf.items.len;
|
||||
buf.items.len += len;
|
||||
const to_write_into = buf.items[start..];
|
||||
|
||||
var encoded = to_write_into[0..bun.base64.encode(to_write_into, fail.data)];
|
||||
while (encoded.len > 0 and encoded[encoded.len - 1] == '=') {
|
||||
encoded.len -= 1;
|
||||
}
|
||||
|
||||
buf.items.len = start + encoded.len;
|
||||
}
|
||||
const len = bun.base64.encodeLen(all_failures.items);
|
||||
try buf.ensureUnusedCapacity(len);
|
||||
const to_write_into = buf.unusedCapacitySlice();
|
||||
buf.items.len += bun.base64.encode(to_write_into, all_failures.items);
|
||||
|
||||
// Re-use the encoded buffer to avoid encoding failures more times than neccecary.
|
||||
if (inspector_agent) |agent| {
|
||||
|
||||
@@ -84,69 +84,7 @@ pub inline fn assertStdioResult(result: StdioResult) void {
|
||||
}
|
||||
}
|
||||
|
||||
pub const ResourceUsage = struct {
|
||||
pub const js = jsc.Codegen.JSResourceUsage;
|
||||
pub const toJS = ResourceUsage.js.toJS;
|
||||
pub const fromJS = ResourceUsage.js.fromJS;
|
||||
pub const fromJSDirect = ResourceUsage.js.fromJSDirect;
|
||||
|
||||
rusage: Rusage,
|
||||
|
||||
pub fn getCPUTime(this: *ResourceUsage, globalObject: *JSGlobalObject) bun.JSError!JSValue {
|
||||
var cpu = jsc.JSValue.createEmptyObjectWithNullPrototype(globalObject);
|
||||
const rusage = this.rusage;
|
||||
|
||||
const usrTime = try JSValue.fromTimevalNoTruncate(globalObject, rusage.utime.usec, rusage.utime.sec);
|
||||
const sysTime = try JSValue.fromTimevalNoTruncate(globalObject, rusage.stime.usec, rusage.stime.sec);
|
||||
|
||||
cpu.put(globalObject, jsc.ZigString.static("user"), usrTime);
|
||||
cpu.put(globalObject, jsc.ZigString.static("system"), sysTime);
|
||||
cpu.put(globalObject, jsc.ZigString.static("total"), JSValue.bigIntSum(globalObject, usrTime, sysTime));
|
||||
|
||||
return cpu;
|
||||
}
|
||||
|
||||
pub fn getMaxRSS(this: *ResourceUsage, _: *JSGlobalObject) JSValue {
|
||||
return jsc.JSValue.jsNumber(this.rusage.maxrss);
|
||||
}
|
||||
|
||||
pub fn getSharedMemorySize(this: *ResourceUsage, _: *JSGlobalObject) JSValue {
|
||||
return jsc.JSValue.jsNumber(this.rusage.ixrss);
|
||||
}
|
||||
|
||||
pub fn getSwapCount(this: *ResourceUsage, _: *JSGlobalObject) JSValue {
|
||||
return jsc.JSValue.jsNumber(this.rusage.nswap);
|
||||
}
|
||||
|
||||
pub fn getOps(this: *ResourceUsage, globalObject: *JSGlobalObject) JSValue {
|
||||
var ops = jsc.JSValue.createEmptyObjectWithNullPrototype(globalObject);
|
||||
ops.put(globalObject, jsc.ZigString.static("in"), jsc.JSValue.jsNumber(this.rusage.inblock));
|
||||
ops.put(globalObject, jsc.ZigString.static("out"), jsc.JSValue.jsNumber(this.rusage.oublock));
|
||||
return ops;
|
||||
}
|
||||
|
||||
pub fn getMessages(this: *ResourceUsage, globalObject: *JSGlobalObject) JSValue {
|
||||
var msgs = jsc.JSValue.createEmptyObjectWithNullPrototype(globalObject);
|
||||
msgs.put(globalObject, jsc.ZigString.static("sent"), jsc.JSValue.jsNumber(this.rusage.msgsnd));
|
||||
msgs.put(globalObject, jsc.ZigString.static("received"), jsc.JSValue.jsNumber(this.rusage.msgrcv));
|
||||
return msgs;
|
||||
}
|
||||
|
||||
pub fn getSignalCount(this: *ResourceUsage, _: *JSGlobalObject) JSValue {
|
||||
return jsc.JSValue.jsNumber(this.rusage.nsignals);
|
||||
}
|
||||
|
||||
pub fn getContextSwitches(this: *ResourceUsage, globalObject: *JSGlobalObject) JSValue {
|
||||
var ctx = jsc.JSValue.createEmptyObjectWithNullPrototype(globalObject);
|
||||
ctx.put(globalObject, jsc.ZigString.static("voluntary"), jsc.JSValue.jsNumber(this.rusage.nvcsw));
|
||||
ctx.put(globalObject, jsc.ZigString.static("involuntary"), jsc.JSValue.jsNumber(this.rusage.nivcsw));
|
||||
return ctx;
|
||||
}
|
||||
|
||||
pub fn finalize(this: *ResourceUsage) callconv(.C) void {
|
||||
bun.default_allocator.destroy(this);
|
||||
}
|
||||
};
|
||||
pub const ResourceUsage = @import("./subprocess/ResourceUsage.zig");
|
||||
|
||||
pub fn appendEnvpFromJS(globalThis: *jsc.JSGlobalObject, object: *jsc.JSObject, envp: *std.ArrayList(?[*:0]const u8), PATH: *[]const u8) bun.JSError!void {
|
||||
var object_iter = try jsc.JSPropertyIterator(.{ .skip_empty_name = false, .include_value = true }).init(globalThis, object);
|
||||
@@ -207,27 +145,24 @@ pub fn resourceUsage(
|
||||
return this.createResourceUsageObject(globalObject);
|
||||
}
|
||||
|
||||
pub fn createResourceUsageObject(this: *Subprocess, globalObject: *JSGlobalObject) JSValue {
|
||||
const pid_rusage = this.pid_rusage orelse brk: {
|
||||
if (Environment.isWindows) {
|
||||
if (this.process.poller == .uv) {
|
||||
this.pid_rusage = PosixSpawn.process.uv_getrusage(&this.process.poller.uv);
|
||||
break :brk this.pid_rusage.?;
|
||||
pub fn createResourceUsageObject(this: *Subprocess, globalObject: *JSGlobalObject) bun.JSError!JSValue {
|
||||
return ResourceUsage.create(
|
||||
brk: {
|
||||
if (this.pid_rusage != null) {
|
||||
break :brk &this.pid_rusage.?;
|
||||
}
|
||||
}
|
||||
|
||||
return .js_undefined;
|
||||
};
|
||||
if (Environment.isWindows) {
|
||||
if (this.process.poller == .uv) {
|
||||
this.pid_rusage = PosixSpawn.process.uv_getrusage(&this.process.poller.uv);
|
||||
break :brk &this.pid_rusage.?;
|
||||
}
|
||||
}
|
||||
|
||||
const resource_usage = ResourceUsage{
|
||||
.rusage = pid_rusage,
|
||||
};
|
||||
|
||||
var result = bun.default_allocator.create(ResourceUsage) catch {
|
||||
return globalObject.throwOutOfMemoryValue();
|
||||
};
|
||||
result.* = resource_usage;
|
||||
return result.toJS(globalObject);
|
||||
return .js_undefined;
|
||||
},
|
||||
globalObject,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn hasExited(this: *const Subprocess) bool {
|
||||
@@ -357,183 +292,8 @@ pub fn constructor(globalObject: *jsc.JSGlobalObject, _: *jsc.CallFrame) bun.JSE
|
||||
return globalObject.throw("Cannot construct Subprocess", .{});
|
||||
}
|
||||
|
||||
const Readable = union(enum) {
|
||||
fd: bun.FileDescriptor,
|
||||
memfd: bun.FileDescriptor,
|
||||
pipe: *PipeReader,
|
||||
inherit: void,
|
||||
ignore: void,
|
||||
closed: void,
|
||||
/// Eventually we will implement Readables created from blobs and array buffers.
|
||||
/// When we do that, `buffer` will be borrowed from those objects.
|
||||
///
|
||||
/// When a buffered `pipe` finishes reading from its file descriptor,
|
||||
/// the owning `Readable` will be convered into this variant and the pipe's
|
||||
/// buffer will be taken as an owned `CowString`.
|
||||
buffer: CowString,
|
||||
|
||||
pub fn memoryCost(this: *const Readable) usize {
|
||||
return switch (this.*) {
|
||||
.pipe => @sizeOf(PipeReader) + this.pipe.memoryCost(),
|
||||
.buffer => this.buffer.length(),
|
||||
else => 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn hasPendingActivity(this: *const Readable) bool {
|
||||
return switch (this.*) {
|
||||
.pipe => this.pipe.hasPendingActivity(),
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ref(this: *Readable) void {
|
||||
switch (this.*) {
|
||||
.pipe => {
|
||||
this.pipe.updateRef(true);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unref(this: *Readable) void {
|
||||
switch (this.*) {
|
||||
.pipe => {
|
||||
this.pipe.updateRef(false);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(stdio: Stdio, event_loop: *jsc.EventLoop, process: *Subprocess, result: StdioResult, allocator: std.mem.Allocator, max_size: ?*MaxBuf, is_sync: bool) Readable {
|
||||
_ = allocator; // autofix
|
||||
_ = is_sync; // autofix
|
||||
assertStdioResult(result);
|
||||
|
||||
if (comptime Environment.isPosix) {
|
||||
if (stdio == .pipe) {
|
||||
_ = bun.sys.setNonblocking(result.?);
|
||||
}
|
||||
}
|
||||
|
||||
return switch (stdio) {
|
||||
.inherit => Readable{ .inherit = {} },
|
||||
.ignore, .ipc, .path => Readable{ .ignore = {} },
|
||||
.fd => |fd| if (Environment.isPosix) Readable{ .fd = result.? } else Readable{ .fd = fd },
|
||||
.memfd => if (Environment.isPosix) Readable{ .memfd = stdio.memfd } else Readable{ .ignore = {} },
|
||||
.dup2 => |dup2| if (Environment.isPosix) Output.panic("TODO: implement dup2 support in Stdio readable", .{}) else Readable{ .fd = dup2.out.toFd() },
|
||||
.pipe => Readable{ .pipe = PipeReader.create(event_loop, process, result, max_size) },
|
||||
.array_buffer, .blob => Output.panic("TODO: implement ArrayBuffer & Blob support in Stdio readable", .{}),
|
||||
.capture => Output.panic("TODO: implement capture support in Stdio readable", .{}),
|
||||
.readable_stream => Readable{ .ignore = {} }, // ReadableStream is handled separately
|
||||
};
|
||||
}
|
||||
|
||||
pub fn onClose(this: *Readable, _: ?bun.sys.Error) void {
|
||||
this.* = .closed;
|
||||
}
|
||||
|
||||
pub fn onReady(_: *Readable, _: ?jsc.WebCore.Blob.SizeType, _: ?jsc.WebCore.Blob.SizeType) void {}
|
||||
|
||||
pub fn onStart(_: *Readable) void {}
|
||||
|
||||
pub fn close(this: *Readable) void {
|
||||
switch (this.*) {
|
||||
.memfd => |fd| {
|
||||
this.* = .{ .closed = {} };
|
||||
fd.close();
|
||||
},
|
||||
.fd => |_| {
|
||||
this.* = .{ .closed = {} };
|
||||
},
|
||||
.pipe => {
|
||||
this.pipe.close();
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize(this: *Readable) void {
|
||||
switch (this.*) {
|
||||
.memfd => |fd| {
|
||||
this.* = .{ .closed = {} };
|
||||
fd.close();
|
||||
},
|
||||
.fd => {
|
||||
this.* = .{ .closed = {} };
|
||||
},
|
||||
.pipe => |pipe| {
|
||||
defer pipe.detach();
|
||||
this.* = .{ .closed = {} };
|
||||
},
|
||||
.buffer => |*buf| {
|
||||
buf.deinit(bun.default_allocator);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toJS(this: *Readable, globalThis: *jsc.JSGlobalObject, exited: bool) bun.JSError!JSValue {
|
||||
_ = exited; // autofix
|
||||
switch (this.*) {
|
||||
// should only be reachable when the entire output is buffered.
|
||||
.memfd => return this.toBufferedValue(globalThis),
|
||||
|
||||
.fd => |fd| {
|
||||
return fd.toJS(globalThis);
|
||||
},
|
||||
.pipe => |pipe| {
|
||||
defer pipe.detach();
|
||||
this.* = .{ .closed = {} };
|
||||
return pipe.toJS(globalThis);
|
||||
},
|
||||
.buffer => |*buffer| {
|
||||
defer this.* = .{ .closed = {} };
|
||||
|
||||
if (buffer.length() == 0) {
|
||||
return jsc.WebCore.ReadableStream.empty(globalThis);
|
||||
}
|
||||
|
||||
const own = try buffer.takeSlice(bun.default_allocator);
|
||||
return jsc.WebCore.ReadableStream.fromOwnedSlice(globalThis, own, 0);
|
||||
},
|
||||
else => {
|
||||
return .js_undefined;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toBufferedValue(this: *Readable, globalThis: *jsc.JSGlobalObject) bun.JSError!JSValue {
|
||||
switch (this.*) {
|
||||
.fd => |fd| {
|
||||
return fd.toJS(globalThis);
|
||||
},
|
||||
.memfd => |fd| {
|
||||
if (comptime !Environment.isPosix) {
|
||||
Output.panic("memfd is only supported on Linux", .{});
|
||||
}
|
||||
this.* = .{ .closed = {} };
|
||||
return jsc.ArrayBuffer.toJSBufferFromMemfd(fd, globalThis);
|
||||
},
|
||||
.pipe => |pipe| {
|
||||
defer pipe.detach();
|
||||
this.* = .{ .closed = {} };
|
||||
return pipe.toBuffer(globalThis);
|
||||
},
|
||||
.buffer => |*buf| {
|
||||
defer this.* = .{ .closed = {} };
|
||||
const own = buf.takeSlice(bun.default_allocator) catch {
|
||||
return globalThis.throwOutOfMemory();
|
||||
};
|
||||
|
||||
return jsc.MarkedArrayBuffer.fromBytes(own, bun.default_allocator, .Uint8Array).toNodeBuffer(globalThis);
|
||||
},
|
||||
else => {
|
||||
return .js_undefined;
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
pub const PipeReader = @import("./subprocess/SubprocessPipeReader.zig");
|
||||
pub const Readable = @import("./subprocess/Readable.zig").Readable;
|
||||
|
||||
pub fn getStderr(this: *Subprocess, globalThis: *JSGlobalObject) bun.JSError!JSValue {
|
||||
this.observable_getters.insert(.stderr);
|
||||
@@ -810,670 +570,9 @@ pub const Source = union(enum) {
|
||||
}
|
||||
};
|
||||
|
||||
pub const NewStaticPipeWriter = @import("./subprocess/StaticPipeWriter.zig").NewStaticPipeWriter;
|
||||
pub const StaticPipeWriter = NewStaticPipeWriter(Subprocess);
|
||||
|
||||
pub fn NewStaticPipeWriter(comptime ProcessType: type) type {
|
||||
return struct {
|
||||
const This = @This();
|
||||
|
||||
ref_count: WriterRefCount,
|
||||
writer: IOWriter = .{},
|
||||
stdio_result: StdioResult,
|
||||
source: Source = .{ .detached = {} },
|
||||
process: *ProcessType = undefined,
|
||||
event_loop: jsc.EventLoopHandle,
|
||||
buffer: []const u8 = "",
|
||||
|
||||
// It seems there is a bug in the Zig compiler. We'll get back to this one later
|
||||
const WriterRefCount = bun.ptr.RefCount(@This(), "ref_count", _deinit, .{});
|
||||
pub const ref = WriterRefCount.ref;
|
||||
pub const deref = WriterRefCount.deref;
|
||||
|
||||
const print = bun.Output.scoped(.StaticPipeWriter, .visible);
|
||||
|
||||
pub const IOWriter = bun.io.BufferedWriter(@This(), struct {
|
||||
pub const onWritable = null;
|
||||
pub const getBuffer = This.getBuffer;
|
||||
pub const onClose = This.onClose;
|
||||
pub const onError = This.onError;
|
||||
pub const onWrite = This.onWrite;
|
||||
});
|
||||
pub const Poll = IOWriter;
|
||||
|
||||
pub fn updateRef(this: *This, add: bool) void {
|
||||
this.writer.updateRef(this.event_loop, add);
|
||||
}
|
||||
|
||||
pub fn getBuffer(this: *This) []const u8 {
|
||||
return this.buffer;
|
||||
}
|
||||
|
||||
pub fn close(this: *This) void {
|
||||
log("StaticPipeWriter(0x{x}) close()", .{@intFromPtr(this)});
|
||||
this.writer.close();
|
||||
}
|
||||
|
||||
pub fn flush(this: *This) void {
|
||||
if (this.buffer.len > 0)
|
||||
this.writer.write();
|
||||
}
|
||||
|
||||
pub fn create(event_loop: anytype, subprocess: *ProcessType, result: StdioResult, source: Source) *This {
|
||||
const this = bun.new(This, .{
|
||||
.ref_count = .init(),
|
||||
.event_loop = jsc.EventLoopHandle.init(event_loop),
|
||||
.process = subprocess,
|
||||
.stdio_result = result,
|
||||
.source = source,
|
||||
});
|
||||
if (Environment.isWindows) {
|
||||
this.writer.setPipe(this.stdio_result.buffer);
|
||||
}
|
||||
this.writer.setParent(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
pub fn start(this: *This) bun.sys.Maybe(void) {
|
||||
log("StaticPipeWriter(0x{x}) start()", .{@intFromPtr(this)});
|
||||
this.ref();
|
||||
this.buffer = this.source.slice();
|
||||
if (Environment.isWindows) {
|
||||
return this.writer.startWithCurrentPipe();
|
||||
}
|
||||
switch (this.writer.start(this.stdio_result.?, true)) {
|
||||
.err => |err| {
|
||||
return .{ .err = err };
|
||||
},
|
||||
.result => {
|
||||
if (comptime Environment.isPosix) {
|
||||
const poll = this.writer.handle.poll;
|
||||
poll.flags.insert(.socket);
|
||||
}
|
||||
|
||||
return .success;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn onWrite(this: *This, amount: usize, status: bun.io.WriteStatus) void {
|
||||
log("StaticPipeWriter(0x{x}) onWrite(amount={d} {})", .{ @intFromPtr(this), amount, status });
|
||||
this.buffer = this.buffer[@min(amount, this.buffer.len)..];
|
||||
if (status == .end_of_file or this.buffer.len == 0) {
|
||||
this.writer.close();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn onError(this: *This, err: bun.sys.Error) void {
|
||||
log("StaticPipeWriter(0x{x}) onError(err={any})", .{ @intFromPtr(this), err });
|
||||
this.source.detach();
|
||||
}
|
||||
|
||||
pub fn onClose(this: *This) void {
|
||||
log("StaticPipeWriter(0x{x}) onClose()", .{@intFromPtr(this)});
|
||||
this.source.detach();
|
||||
this.process.onCloseIO(.stdin);
|
||||
}
|
||||
|
||||
fn _deinit(this: *This) void {
|
||||
this.writer.end();
|
||||
this.source.detach();
|
||||
bun.destroy(this);
|
||||
}
|
||||
|
||||
pub fn memoryCost(this: *const This) usize {
|
||||
return @sizeOf(@This()) + this.source.memoryCost() + this.writer.memoryCost();
|
||||
}
|
||||
|
||||
pub fn loop(this: *This) *uws.Loop {
|
||||
return this.event_loop.loop();
|
||||
}
|
||||
|
||||
pub fn watch(this: *This) void {
|
||||
if (this.buffer.len > 0) {
|
||||
this.writer.watch();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eventLoop(this: *This) jsc.EventLoopHandle {
|
||||
return this.event_loop;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const PipeReader = struct {
|
||||
const RefCount = bun.ptr.RefCount(@This(), "ref_count", PipeReader.deinit, .{});
|
||||
pub const ref = PipeReader.RefCount.ref;
|
||||
pub const deref = PipeReader.RefCount.deref;
|
||||
|
||||
reader: IOReader = undefined,
|
||||
process: ?*Subprocess = null,
|
||||
event_loop: *jsc.EventLoop = undefined,
|
||||
ref_count: PipeReader.RefCount,
|
||||
state: union(enum) {
|
||||
pending: void,
|
||||
done: []u8,
|
||||
err: bun.sys.Error,
|
||||
} = .{ .pending = {} },
|
||||
stdio_result: StdioResult,
|
||||
pub const IOReader = bun.io.BufferedReader;
|
||||
pub const Poll = IOReader;
|
||||
|
||||
pub fn memoryCost(this: *const PipeReader) usize {
|
||||
return this.reader.memoryCost();
|
||||
}
|
||||
|
||||
pub fn hasPendingActivity(this: *const PipeReader) bool {
|
||||
if (this.state == .pending)
|
||||
return true;
|
||||
|
||||
return this.reader.hasPendingActivity();
|
||||
}
|
||||
|
||||
pub fn detach(this: *PipeReader) void {
|
||||
this.process = null;
|
||||
this.deref();
|
||||
}
|
||||
|
||||
pub fn create(event_loop: *jsc.EventLoop, process: *Subprocess, result: StdioResult, limit: ?*MaxBuf) *PipeReader {
|
||||
var this = bun.new(PipeReader, .{
|
||||
.ref_count = .init(),
|
||||
.process = process,
|
||||
.reader = IOReader.init(@This()),
|
||||
.event_loop = event_loop,
|
||||
.stdio_result = result,
|
||||
});
|
||||
MaxBuf.addToPipereader(limit, &this.reader.maxbuf);
|
||||
if (Environment.isWindows) {
|
||||
this.reader.source = .{ .pipe = this.stdio_result.buffer };
|
||||
}
|
||||
|
||||
this.reader.setParent(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
pub fn readAll(this: *PipeReader) void {
|
||||
if (this.state == .pending)
|
||||
this.reader.read();
|
||||
}
|
||||
|
||||
pub fn start(this: *PipeReader, process: *Subprocess, event_loop: *jsc.EventLoop) bun.sys.Maybe(void) {
|
||||
this.ref();
|
||||
this.process = process;
|
||||
this.event_loop = event_loop;
|
||||
if (Environment.isWindows) {
|
||||
return this.reader.startWithCurrentPipe();
|
||||
}
|
||||
|
||||
switch (this.reader.start(this.stdio_result.?, true)) {
|
||||
.err => |err| {
|
||||
return .{ .err = err };
|
||||
},
|
||||
.result => {
|
||||
if (comptime Environment.isPosix) {
|
||||
const poll = this.reader.handle.poll;
|
||||
poll.flags.insert(.socket);
|
||||
this.reader.flags.socket = true;
|
||||
this.reader.flags.nonblocking = true;
|
||||
this.reader.flags.pollable = true;
|
||||
poll.flags.insert(.nonblocking);
|
||||
}
|
||||
|
||||
return .success;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub const toJS = toReadableStream;
|
||||
|
||||
pub fn onReaderDone(this: *PipeReader) void {
|
||||
const owned = this.toOwnedSlice();
|
||||
this.state = .{ .done = owned };
|
||||
if (this.process) |process| {
|
||||
this.process = null;
|
||||
process.onCloseIO(this.kind(process));
|
||||
this.deref();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kind(reader: *const PipeReader, process: *const Subprocess) StdioKind {
|
||||
if (process.stdout == .pipe and process.stdout.pipe == reader) {
|
||||
return .stdout;
|
||||
}
|
||||
|
||||
if (process.stderr == .pipe and process.stderr.pipe == reader) {
|
||||
return .stderr;
|
||||
}
|
||||
|
||||
@panic("We should be either stdout or stderr");
|
||||
}
|
||||
|
||||
pub fn toOwnedSlice(this: *PipeReader) []u8 {
|
||||
if (this.state == .done) {
|
||||
return this.state.done;
|
||||
}
|
||||
// we do not use .toOwnedSlice() because we don't want to reallocate memory.
|
||||
const out = this.reader._buffer;
|
||||
this.reader._buffer.items = &.{};
|
||||
this.reader._buffer.capacity = 0;
|
||||
|
||||
if (out.capacity > 0 and out.items.len == 0) {
|
||||
out.deinit();
|
||||
return &.{};
|
||||
}
|
||||
|
||||
return out.items;
|
||||
}
|
||||
|
||||
pub fn updateRef(this: *PipeReader, add: bool) void {
|
||||
this.reader.updateRef(add);
|
||||
}
|
||||
|
||||
pub fn watch(this: *PipeReader) void {
|
||||
if (!this.reader.isDone())
|
||||
this.reader.watch();
|
||||
}
|
||||
|
||||
pub fn toReadableStream(this: *PipeReader, globalObject: *jsc.JSGlobalObject) bun.JSError!jsc.JSValue {
|
||||
defer this.detach();
|
||||
|
||||
switch (this.state) {
|
||||
.pending => {
|
||||
const stream = jsc.WebCore.ReadableStream.fromPipe(globalObject, this, &this.reader);
|
||||
this.state = .{ .done = &.{} };
|
||||
return stream;
|
||||
},
|
||||
.done => |bytes| {
|
||||
this.state = .{ .done = &.{} };
|
||||
return jsc.WebCore.ReadableStream.fromOwnedSlice(globalObject, bytes, 0);
|
||||
},
|
||||
.err => |err| {
|
||||
_ = err;
|
||||
const empty = try jsc.WebCore.ReadableStream.empty(globalObject);
|
||||
jsc.WebCore.ReadableStream.cancel(&(try jsc.WebCore.ReadableStream.fromJS(empty, globalObject)).?, globalObject);
|
||||
return empty;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toBuffer(this: *PipeReader, globalThis: *jsc.JSGlobalObject) jsc.JSValue {
|
||||
switch (this.state) {
|
||||
.done => |bytes| {
|
||||
defer this.state = .{ .done = &.{} };
|
||||
return jsc.MarkedArrayBuffer.fromBytes(bytes, bun.default_allocator, .Uint8Array).toNodeBuffer(globalThis);
|
||||
},
|
||||
else => {
|
||||
return .js_undefined;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn onReaderError(this: *PipeReader, err: bun.sys.Error) void {
|
||||
if (this.state == .done) {
|
||||
bun.default_allocator.free(this.state.done);
|
||||
}
|
||||
this.state = .{ .err = err };
|
||||
if (this.process) |process|
|
||||
process.onCloseIO(this.kind(process));
|
||||
}
|
||||
|
||||
pub fn close(this: *PipeReader) void {
|
||||
switch (this.state) {
|
||||
.pending => {
|
||||
this.reader.close();
|
||||
},
|
||||
.done => {},
|
||||
.err => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eventLoop(this: *PipeReader) *jsc.EventLoop {
|
||||
return this.event_loop;
|
||||
}
|
||||
|
||||
pub fn loop(this: *PipeReader) *uws.Loop {
|
||||
return this.event_loop.virtual_machine.uwsLoop();
|
||||
}
|
||||
|
||||
fn deinit(this: *PipeReader) void {
|
||||
if (comptime Environment.isPosix) {
|
||||
bun.assert(this.reader.isDone());
|
||||
}
|
||||
|
||||
if (comptime Environment.isWindows) {
|
||||
bun.assert(this.reader.source == null or this.reader.source.?.isClosed());
|
||||
}
|
||||
|
||||
if (this.state == .done) {
|
||||
bun.default_allocator.free(this.state.done);
|
||||
}
|
||||
|
||||
this.reader.deinit();
|
||||
bun.destroy(this);
|
||||
}
|
||||
};
|
||||
|
||||
const Writable = union(enum) {
|
||||
pipe: *jsc.WebCore.FileSink,
|
||||
fd: bun.FileDescriptor,
|
||||
buffer: *StaticPipeWriter,
|
||||
memfd: bun.FileDescriptor,
|
||||
inherit: void,
|
||||
ignore: void,
|
||||
|
||||
pub fn memoryCost(this: *const Writable) usize {
|
||||
return switch (this.*) {
|
||||
.pipe => |pipe| pipe.memoryCost(),
|
||||
.buffer => |buffer| buffer.memoryCost(),
|
||||
// TODO: memfd
|
||||
else => 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn hasPendingActivity(this: *const Writable) bool {
|
||||
return switch (this.*) {
|
||||
.pipe => false,
|
||||
|
||||
// we mark them as .ignore when they are closed, so this must be true
|
||||
.buffer => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ref(this: *Writable) void {
|
||||
switch (this.*) {
|
||||
.pipe => {
|
||||
this.pipe.updateRef(true);
|
||||
},
|
||||
.buffer => {
|
||||
this.buffer.updateRef(true);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unref(this: *Writable) void {
|
||||
switch (this.*) {
|
||||
.pipe => {
|
||||
this.pipe.updateRef(false);
|
||||
},
|
||||
.buffer => {
|
||||
this.buffer.updateRef(false);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
// When the stream has closed we need to be notified to prevent a use-after-free
|
||||
// We can test for this use-after-free by enabling hot module reloading on a file and then saving it twice
|
||||
pub fn onClose(this: *Writable, _: ?bun.sys.Error) void {
|
||||
const process: *Subprocess = @fieldParentPtr("stdin", this);
|
||||
|
||||
if (process.this_jsvalue != .zero) {
|
||||
if (js.stdinGetCached(process.this_jsvalue)) |existing_value| {
|
||||
jsc.WebCore.FileSink.JSSink.setDestroyCallback(existing_value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
switch (this.*) {
|
||||
.buffer => {
|
||||
this.buffer.deref();
|
||||
},
|
||||
.pipe => {
|
||||
this.pipe.deref();
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
process.onStdinDestroyed();
|
||||
|
||||
this.* = .{
|
||||
.ignore = {},
|
||||
};
|
||||
}
|
||||
pub fn onReady(_: *Writable, _: ?jsc.WebCore.Blob.SizeType, _: ?jsc.WebCore.Blob.SizeType) void {}
|
||||
pub fn onStart(_: *Writable) void {}
|
||||
|
||||
pub fn init(
|
||||
stdio: *Stdio,
|
||||
event_loop: *jsc.EventLoop,
|
||||
subprocess: *Subprocess,
|
||||
result: StdioResult,
|
||||
promise_for_stream: *jsc.JSValue,
|
||||
) !Writable {
|
||||
assertStdioResult(result);
|
||||
|
||||
if (Environment.isWindows) {
|
||||
switch (stdio.*) {
|
||||
.pipe, .readable_stream => {
|
||||
if (result == .buffer) {
|
||||
const pipe = jsc.WebCore.FileSink.createWithPipe(event_loop, result.buffer);
|
||||
|
||||
switch (pipe.writer.startWithCurrentPipe()) {
|
||||
.result => {},
|
||||
.err => |err| {
|
||||
_ = err; // autofix
|
||||
pipe.deref();
|
||||
if (stdio.* == .readable_stream) {
|
||||
stdio.readable_stream.cancel(event_loop.global);
|
||||
}
|
||||
return error.UnexpectedCreatingStdin;
|
||||
},
|
||||
}
|
||||
pipe.writer.setParent(pipe);
|
||||
subprocess.weak_file_sink_stdin_ptr = pipe;
|
||||
subprocess.ref();
|
||||
subprocess.flags.deref_on_stdin_destroyed = true;
|
||||
subprocess.flags.has_stdin_destructor_called = false;
|
||||
|
||||
if (stdio.* == .readable_stream) {
|
||||
const assign_result = pipe.assignToStream(&stdio.readable_stream, event_loop.global);
|
||||
if (assign_result.toError()) |err| {
|
||||
pipe.deref();
|
||||
subprocess.deref();
|
||||
return event_loop.global.throwValue(err);
|
||||
}
|
||||
promise_for_stream.* = assign_result;
|
||||
}
|
||||
|
||||
return Writable{
|
||||
.pipe = pipe,
|
||||
};
|
||||
}
|
||||
return Writable{ .inherit = {} };
|
||||
},
|
||||
|
||||
.blob => |blob| {
|
||||
return Writable{
|
||||
.buffer = StaticPipeWriter.create(event_loop, subprocess, result, .{ .blob = blob }),
|
||||
};
|
||||
},
|
||||
.array_buffer => |array_buffer| {
|
||||
return Writable{
|
||||
.buffer = StaticPipeWriter.create(event_loop, subprocess, result, .{ .array_buffer = array_buffer }),
|
||||
};
|
||||
},
|
||||
.fd => |fd| {
|
||||
return Writable{ .fd = fd };
|
||||
},
|
||||
.dup2 => |dup2| {
|
||||
return Writable{ .fd = dup2.to.toFd() };
|
||||
},
|
||||
.inherit => {
|
||||
return Writable{ .inherit = {} };
|
||||
},
|
||||
.memfd, .path, .ignore => {
|
||||
return Writable{ .ignore = {} };
|
||||
},
|
||||
.ipc, .capture => {
|
||||
return Writable{ .ignore = {} };
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (comptime Environment.isPosix) {
|
||||
if (stdio.* == .pipe) {
|
||||
_ = bun.sys.setNonblocking(result.?);
|
||||
}
|
||||
}
|
||||
|
||||
switch (stdio.*) {
|
||||
.dup2 => @panic("TODO dup2 stdio"),
|
||||
.pipe, .readable_stream => {
|
||||
const pipe = jsc.WebCore.FileSink.create(event_loop, result.?);
|
||||
|
||||
switch (pipe.writer.start(pipe.fd, true)) {
|
||||
.result => {},
|
||||
.err => |err| {
|
||||
_ = err; // autofix
|
||||
pipe.deref();
|
||||
if (stdio.* == .readable_stream) {
|
||||
stdio.readable_stream.cancel(event_loop.global);
|
||||
}
|
||||
|
||||
return error.UnexpectedCreatingStdin;
|
||||
},
|
||||
}
|
||||
|
||||
pipe.writer.handle.poll.flags.insert(.socket);
|
||||
|
||||
subprocess.weak_file_sink_stdin_ptr = pipe;
|
||||
subprocess.ref();
|
||||
subprocess.flags.has_stdin_destructor_called = false;
|
||||
subprocess.flags.deref_on_stdin_destroyed = true;
|
||||
|
||||
if (stdio.* == .readable_stream) {
|
||||
const assign_result = pipe.assignToStream(&stdio.readable_stream, event_loop.global);
|
||||
if (assign_result.toError()) |err| {
|
||||
pipe.deref();
|
||||
subprocess.deref();
|
||||
return event_loop.global.throwValue(err);
|
||||
}
|
||||
promise_for_stream.* = assign_result;
|
||||
}
|
||||
|
||||
return Writable{
|
||||
.pipe = pipe,
|
||||
};
|
||||
},
|
||||
|
||||
.blob => |blob| {
|
||||
return Writable{
|
||||
.buffer = StaticPipeWriter.create(event_loop, subprocess, result, .{ .blob = blob }),
|
||||
};
|
||||
},
|
||||
.array_buffer => |array_buffer| {
|
||||
return Writable{
|
||||
.buffer = StaticPipeWriter.create(event_loop, subprocess, result, .{ .array_buffer = array_buffer }),
|
||||
};
|
||||
},
|
||||
.memfd => |memfd| {
|
||||
bun.assert(memfd != bun.invalid_fd);
|
||||
return Writable{ .memfd = memfd };
|
||||
},
|
||||
.fd => {
|
||||
return Writable{ .fd = result.? };
|
||||
},
|
||||
.inherit => {
|
||||
return Writable{ .inherit = {} };
|
||||
},
|
||||
.path, .ignore => {
|
||||
return Writable{ .ignore = {} };
|
||||
},
|
||||
.ipc, .capture => {
|
||||
return Writable{ .ignore = {} };
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toJS(this: *Writable, globalThis: *jsc.JSGlobalObject, subprocess: *Subprocess) JSValue {
|
||||
return switch (this.*) {
|
||||
.fd => |fd| fd.toJS(globalThis),
|
||||
.memfd, .ignore => .js_undefined,
|
||||
.buffer, .inherit => .js_undefined,
|
||||
.pipe => |pipe| {
|
||||
this.* = .{ .ignore = {} };
|
||||
if (subprocess.process.hasExited() and !subprocess.flags.has_stdin_destructor_called) {
|
||||
// onAttachedProcessExit() can call deref on the
|
||||
// subprocess. Since we never called ref(), it would be
|
||||
// unbalanced to do so, leading to a use-after-free.
|
||||
// So, let's not do that.
|
||||
// https://github.com/oven-sh/bun/pull/14092
|
||||
bun.debugAssert(!subprocess.flags.deref_on_stdin_destroyed);
|
||||
const debug_ref_count = if (Environment.isDebug) subprocess.ref_count else 0;
|
||||
pipe.onAttachedProcessExit(&subprocess.process.status);
|
||||
if (Environment.isDebug) {
|
||||
bun.debugAssert(subprocess.ref_count.get() == debug_ref_count.get());
|
||||
}
|
||||
return pipe.toJS(globalThis);
|
||||
} else {
|
||||
subprocess.flags.has_stdin_destructor_called = false;
|
||||
subprocess.weak_file_sink_stdin_ptr = pipe;
|
||||
subprocess.ref();
|
||||
subprocess.flags.deref_on_stdin_destroyed = true;
|
||||
if (@intFromPtr(pipe.signal.ptr) == @intFromPtr(subprocess)) {
|
||||
pipe.signal.clear();
|
||||
}
|
||||
return pipe.toJSWithDestructor(
|
||||
globalThis,
|
||||
jsc.WebCore.Sink.DestructorPtr.init(subprocess),
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn finalize(this: *Writable) void {
|
||||
const subprocess: *Subprocess = @fieldParentPtr("stdin", this);
|
||||
if (subprocess.this_jsvalue != .zero) {
|
||||
if (jsc.Codegen.JSSubprocess.stdinGetCached(subprocess.this_jsvalue)) |existing_value| {
|
||||
jsc.WebCore.FileSink.JSSink.setDestroyCallback(existing_value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return switch (this.*) {
|
||||
.pipe => |pipe| {
|
||||
if (pipe.signal.ptr == @as(*anyopaque, @ptrCast(this))) {
|
||||
pipe.signal.clear();
|
||||
}
|
||||
|
||||
pipe.deref();
|
||||
|
||||
this.* = .{ .ignore = {} };
|
||||
},
|
||||
.buffer => {
|
||||
this.buffer.updateRef(false);
|
||||
this.buffer.deref();
|
||||
},
|
||||
.memfd => |fd| {
|
||||
fd.close();
|
||||
this.* = .{ .ignore = {} };
|
||||
},
|
||||
.ignore => {},
|
||||
.fd, .inherit => {},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn close(this: *Writable) void {
|
||||
switch (this.*) {
|
||||
.pipe => |pipe| {
|
||||
_ = pipe.end(null);
|
||||
},
|
||||
.memfd => |fd| {
|
||||
fd.close();
|
||||
this.* = .{ .ignore = {} };
|
||||
},
|
||||
.fd => {
|
||||
this.* = .{ .ignore = {} };
|
||||
},
|
||||
.buffer => {
|
||||
this.buffer.close();
|
||||
},
|
||||
.ignore => {},
|
||||
.inherit => {},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn memoryCost(this: *const Subprocess) usize {
|
||||
return @sizeOf(@This()) +
|
||||
this.process.memoryCost() +
|
||||
@@ -2618,7 +1717,7 @@ pub fn spawnMaybeSync(
|
||||
const exitCode = subprocess.getExitCode(globalThis);
|
||||
const stdout = try subprocess.stdout.toBufferedValue(globalThis);
|
||||
const stderr = try subprocess.stderr.toBufferedValue(globalThis);
|
||||
const resource_usage: JSValue = if (!globalThis.hasException()) subprocess.createResourceUsageObject(globalThis) else .zero;
|
||||
const resource_usage: JSValue = if (!globalThis.hasException()) try subprocess.createResourceUsageObject(globalThis) else .zero;
|
||||
const exitedDueToTimeout = subprocess.event_loop_timer.state == .FIRED;
|
||||
const exitedDueToMaxBuffer = subprocess.exited_due_to_maxbuf;
|
||||
const resultPid = jsc.JSValue.jsNumberFromInt32(subprocess.pid());
|
||||
@@ -2717,7 +1816,8 @@ pub fn getGlobalThis(this: *Subprocess) ?*jsc.JSGlobalObject {
|
||||
|
||||
const IPClog = Output.scoped(.IPC, .visible);
|
||||
|
||||
const StdioResult = if (Environment.isWindows) bun.spawn.WindowsSpawnResult.StdioResult else ?bun.FileDescriptor;
|
||||
pub const StdioResult = if (Environment.isWindows) bun.spawn.WindowsSpawnResult.StdioResult else ?bun.FileDescriptor;
|
||||
pub const Writable = @import("./subprocess/Writable.zig").Writable;
|
||||
|
||||
pub const MaxBuf = bun.io.MaxBuf;
|
||||
|
||||
|
||||
195
src/bun.js/api/bun/subprocess/Readable.zig
Normal file
195
src/bun.js/api/bun/subprocess/Readable.zig
Normal file
@@ -0,0 +1,195 @@
|
||||
pub const Readable = union(enum) {
|
||||
fd: bun.FileDescriptor,
|
||||
memfd: bun.FileDescriptor,
|
||||
pipe: *PipeReader,
|
||||
inherit: void,
|
||||
ignore: void,
|
||||
closed: void,
|
||||
/// Eventually we will implement Readables created from blobs and array buffers.
|
||||
/// When we do that, `buffer` will be borrowed from those objects.
|
||||
///
|
||||
/// When a buffered `pipe` finishes reading from its file descriptor,
|
||||
/// the owning `Readable` will be convered into this variant and the pipe's
|
||||
/// buffer will be taken as an owned `CowString`.
|
||||
buffer: CowString,
|
||||
|
||||
pub fn memoryCost(this: *const Readable) usize {
|
||||
return switch (this.*) {
|
||||
.pipe => @sizeOf(PipeReader) + this.pipe.memoryCost(),
|
||||
.buffer => this.buffer.length(),
|
||||
else => 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn hasPendingActivity(this: *const Readable) bool {
|
||||
return switch (this.*) {
|
||||
.pipe => this.pipe.hasPendingActivity(),
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ref(this: *Readable) void {
|
||||
switch (this.*) {
|
||||
.pipe => {
|
||||
this.pipe.updateRef(true);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unref(this: *Readable) void {
|
||||
switch (this.*) {
|
||||
.pipe => {
|
||||
this.pipe.updateRef(false);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(stdio: Stdio, event_loop: *jsc.EventLoop, process: *Subprocess, result: StdioResult, allocator: std.mem.Allocator, max_size: ?*MaxBuf, is_sync: bool) Readable {
|
||||
_ = allocator; // autofix
|
||||
_ = is_sync; // autofix
|
||||
Subprocess.assertStdioResult(result);
|
||||
|
||||
if (comptime Environment.isPosix) {
|
||||
if (stdio == .pipe) {
|
||||
_ = bun.sys.setNonblocking(result.?);
|
||||
}
|
||||
}
|
||||
|
||||
return switch (stdio) {
|
||||
.inherit => Readable{ .inherit = {} },
|
||||
.ignore, .ipc, .path => Readable{ .ignore = {} },
|
||||
.fd => |fd| if (Environment.isPosix) Readable{ .fd = result.? } else Readable{ .fd = fd },
|
||||
.memfd => if (Environment.isPosix) Readable{ .memfd = stdio.memfd } else Readable{ .ignore = {} },
|
||||
.dup2 => |dup2| if (Environment.isPosix) Output.panic("TODO: implement dup2 support in Stdio readable", .{}) else Readable{ .fd = dup2.out.toFd() },
|
||||
.pipe => Readable{ .pipe = PipeReader.create(event_loop, process, result, max_size) },
|
||||
.array_buffer, .blob => Output.panic("TODO: implement ArrayBuffer & Blob support in Stdio readable", .{}),
|
||||
.capture => Output.panic("TODO: implement capture support in Stdio readable", .{}),
|
||||
.readable_stream => Readable{ .ignore = {} }, // ReadableStream is handled separately
|
||||
};
|
||||
}
|
||||
|
||||
pub fn onClose(this: *Readable, _: ?bun.sys.Error) void {
|
||||
this.* = .closed;
|
||||
}
|
||||
|
||||
pub fn onReady(_: *Readable, _: ?jsc.WebCore.Blob.SizeType, _: ?jsc.WebCore.Blob.SizeType) void {}
|
||||
|
||||
pub fn onStart(_: *Readable) void {}
|
||||
|
||||
pub fn close(this: *Readable) void {
|
||||
switch (this.*) {
|
||||
.memfd => |fd| {
|
||||
this.* = .{ .closed = {} };
|
||||
fd.close();
|
||||
},
|
||||
.fd => |_| {
|
||||
this.* = .{ .closed = {} };
|
||||
},
|
||||
.pipe => {
|
||||
this.pipe.close();
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize(this: *Readable) void {
|
||||
switch (this.*) {
|
||||
.memfd => |fd| {
|
||||
this.* = .{ .closed = {} };
|
||||
fd.close();
|
||||
},
|
||||
.fd => {
|
||||
this.* = .{ .closed = {} };
|
||||
},
|
||||
.pipe => |pipe| {
|
||||
defer pipe.detach();
|
||||
this.* = .{ .closed = {} };
|
||||
},
|
||||
.buffer => |*buf| {
|
||||
buf.deinit(bun.default_allocator);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toJS(this: *Readable, globalThis: *jsc.JSGlobalObject, exited: bool) bun.JSError!JSValue {
|
||||
_ = exited; // autofix
|
||||
switch (this.*) {
|
||||
// should only be reachable when the entire output is buffered.
|
||||
.memfd => return this.toBufferedValue(globalThis),
|
||||
|
||||
.fd => |fd| {
|
||||
return fd.toJS(globalThis);
|
||||
},
|
||||
.pipe => |pipe| {
|
||||
defer pipe.detach();
|
||||
this.* = .{ .closed = {} };
|
||||
return pipe.toJS(globalThis);
|
||||
},
|
||||
.buffer => |*buffer| {
|
||||
defer this.* = .{ .closed = {} };
|
||||
|
||||
if (buffer.length() == 0) {
|
||||
return jsc.WebCore.ReadableStream.empty(globalThis);
|
||||
}
|
||||
|
||||
const own = try buffer.takeSlice(bun.default_allocator);
|
||||
return jsc.WebCore.ReadableStream.fromOwnedSlice(globalThis, own, 0);
|
||||
},
|
||||
else => {
|
||||
return .js_undefined;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toBufferedValue(this: *Readable, globalThis: *jsc.JSGlobalObject) bun.JSError!JSValue {
|
||||
switch (this.*) {
|
||||
.fd => |fd| {
|
||||
return fd.toJS(globalThis);
|
||||
},
|
||||
.memfd => |fd| {
|
||||
if (comptime !Environment.isPosix) {
|
||||
Output.panic("memfd is only supported on Linux", .{});
|
||||
}
|
||||
this.* = .{ .closed = {} };
|
||||
return jsc.ArrayBuffer.toJSBufferFromMemfd(fd, globalThis);
|
||||
},
|
||||
.pipe => |pipe| {
|
||||
defer pipe.detach();
|
||||
this.* = .{ .closed = {} };
|
||||
return pipe.toBuffer(globalThis);
|
||||
},
|
||||
.buffer => |*buf| {
|
||||
defer this.* = .{ .closed = {} };
|
||||
const own = buf.takeSlice(bun.default_allocator) catch {
|
||||
return globalThis.throwOutOfMemory();
|
||||
};
|
||||
|
||||
return jsc.MarkedArrayBuffer.fromBytes(own, bun.default_allocator, .Uint8Array).toNodeBuffer(globalThis);
|
||||
},
|
||||
else => {
|
||||
return .js_undefined;
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const bun = @import("bun");
|
||||
const Environment = bun.Environment;
|
||||
const Output = bun.Output;
|
||||
const default_allocator = bun.default_allocator;
|
||||
const CowString = bun.ptr.CowString;
|
||||
const Stdio = bun.spawn.Stdio;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const JSGlobalObject = jsc.JSGlobalObject;
|
||||
const JSValue = jsc.JSValue;
|
||||
|
||||
const Subprocess = jsc.API.Subprocess;
|
||||
const MaxBuf = Subprocess.MaxBuf;
|
||||
const PipeReader = Subprocess.PipeReader;
|
||||
const StdioResult = Subprocess.StdioResult;
|
||||
75
src/bun.js/api/bun/subprocess/ResourceUsage.zig
Normal file
75
src/bun.js/api/bun/subprocess/ResourceUsage.zig
Normal file
@@ -0,0 +1,75 @@
|
||||
const ResourceUsage = @This();
|
||||
|
||||
pub const js = jsc.Codegen.JSResourceUsage;
|
||||
pub const toJS = ResourceUsage.js.toJS;
|
||||
pub const fromJS = ResourceUsage.js.fromJS;
|
||||
pub const fromJSDirect = ResourceUsage.js.fromJSDirect;
|
||||
|
||||
rusage: Rusage,
|
||||
|
||||
pub fn create(rusage: *const Rusage, globalObject: *JSGlobalObject) bun.JSError!JSValue {
|
||||
return bun.new(ResourceUsage, .{ .rusage = rusage.* }).toJS(globalObject);
|
||||
}
|
||||
|
||||
pub fn getCPUTime(this: *ResourceUsage, globalObject: *JSGlobalObject) bun.JSError!JSValue {
|
||||
var cpu = jsc.JSValue.createEmptyObjectWithNullPrototype(globalObject);
|
||||
const rusage = this.rusage;
|
||||
|
||||
const usrTime = try JSValue.fromTimevalNoTruncate(globalObject, rusage.utime.usec, rusage.utime.sec);
|
||||
const sysTime = try JSValue.fromTimevalNoTruncate(globalObject, rusage.stime.usec, rusage.stime.sec);
|
||||
|
||||
cpu.put(globalObject, jsc.ZigString.static("user"), usrTime);
|
||||
cpu.put(globalObject, jsc.ZigString.static("system"), sysTime);
|
||||
cpu.put(globalObject, jsc.ZigString.static("total"), JSValue.bigIntSum(globalObject, usrTime, sysTime));
|
||||
|
||||
return cpu;
|
||||
}
|
||||
|
||||
pub fn getMaxRSS(this: *ResourceUsage, _: *JSGlobalObject) JSValue {
|
||||
return jsc.JSValue.jsNumber(this.rusage.maxrss);
|
||||
}
|
||||
|
||||
pub fn getSharedMemorySize(this: *ResourceUsage, _: *JSGlobalObject) JSValue {
|
||||
return jsc.JSValue.jsNumber(this.rusage.ixrss);
|
||||
}
|
||||
|
||||
pub fn getSwapCount(this: *ResourceUsage, _: *JSGlobalObject) JSValue {
|
||||
return jsc.JSValue.jsNumber(this.rusage.nswap);
|
||||
}
|
||||
|
||||
pub fn getOps(this: *ResourceUsage, globalObject: *JSGlobalObject) JSValue {
|
||||
var ops = jsc.JSValue.createEmptyObjectWithNullPrototype(globalObject);
|
||||
ops.put(globalObject, jsc.ZigString.static("in"), jsc.JSValue.jsNumber(this.rusage.inblock));
|
||||
ops.put(globalObject, jsc.ZigString.static("out"), jsc.JSValue.jsNumber(this.rusage.oublock));
|
||||
return ops;
|
||||
}
|
||||
|
||||
pub fn getMessages(this: *ResourceUsage, globalObject: *JSGlobalObject) JSValue {
|
||||
var msgs = jsc.JSValue.createEmptyObjectWithNullPrototype(globalObject);
|
||||
msgs.put(globalObject, jsc.ZigString.static("sent"), jsc.JSValue.jsNumber(this.rusage.msgsnd));
|
||||
msgs.put(globalObject, jsc.ZigString.static("received"), jsc.JSValue.jsNumber(this.rusage.msgrcv));
|
||||
return msgs;
|
||||
}
|
||||
|
||||
pub fn getSignalCount(this: *ResourceUsage, _: *JSGlobalObject) JSValue {
|
||||
return jsc.JSValue.jsNumber(this.rusage.nsignals);
|
||||
}
|
||||
|
||||
pub fn getContextSwitches(this: *ResourceUsage, globalObject: *JSGlobalObject) JSValue {
|
||||
var ctx = jsc.JSValue.createEmptyObjectWithNullPrototype(globalObject);
|
||||
ctx.put(globalObject, jsc.ZigString.static("voluntary"), jsc.JSValue.jsNumber(this.rusage.nvcsw));
|
||||
ctx.put(globalObject, jsc.ZigString.static("involuntary"), jsc.JSValue.jsNumber(this.rusage.nivcsw));
|
||||
return ctx;
|
||||
}
|
||||
|
||||
pub fn finalize(this: *ResourceUsage) callconv(.C) void {
|
||||
bun.default_allocator.destroy(this);
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const default_allocator = bun.default_allocator;
|
||||
const Rusage = bun.spawn.Rusage;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const JSGlobalObject = jsc.JSGlobalObject;
|
||||
const JSValue = jsc.JSValue;
|
||||
139
src/bun.js/api/bun/subprocess/StaticPipeWriter.zig
Normal file
139
src/bun.js/api/bun/subprocess/StaticPipeWriter.zig
Normal file
@@ -0,0 +1,139 @@
|
||||
pub fn NewStaticPipeWriter(comptime ProcessType: type) type {
|
||||
return struct {
|
||||
const This = @This();
|
||||
|
||||
ref_count: WriterRefCount,
|
||||
writer: IOWriter = .{},
|
||||
stdio_result: StdioResult,
|
||||
source: Source = .{ .detached = {} },
|
||||
process: *ProcessType = undefined,
|
||||
event_loop: jsc.EventLoopHandle,
|
||||
buffer: []const u8 = "",
|
||||
|
||||
// It seems there is a bug in the Zig compiler. We'll get back to this one later
|
||||
const WriterRefCount = bun.ptr.RefCount(@This(), "ref_count", _deinit, .{});
|
||||
pub const ref = WriterRefCount.ref;
|
||||
pub const deref = WriterRefCount.deref;
|
||||
|
||||
const print = bun.Output.scoped(.StaticPipeWriter, .visible);
|
||||
|
||||
pub const IOWriter = bun.io.BufferedWriter(@This(), struct {
|
||||
pub const onWritable = null;
|
||||
pub const getBuffer = This.getBuffer;
|
||||
pub const onClose = This.onClose;
|
||||
pub const onError = This.onError;
|
||||
pub const onWrite = This.onWrite;
|
||||
});
|
||||
pub const Poll = IOWriter;
|
||||
|
||||
pub fn updateRef(this: *This, add: bool) void {
|
||||
this.writer.updateRef(this.event_loop, add);
|
||||
}
|
||||
|
||||
pub fn getBuffer(this: *This) []const u8 {
|
||||
return this.buffer;
|
||||
}
|
||||
|
||||
pub fn close(this: *This) void {
|
||||
log("StaticPipeWriter(0x{x}) close()", .{@intFromPtr(this)});
|
||||
this.writer.close();
|
||||
}
|
||||
|
||||
pub fn flush(this: *This) void {
|
||||
if (this.buffer.len > 0)
|
||||
this.writer.write();
|
||||
}
|
||||
|
||||
pub fn create(event_loop: anytype, subprocess: *ProcessType, result: StdioResult, source: Source) *This {
|
||||
const this = bun.new(This, .{
|
||||
.ref_count = .init(),
|
||||
.event_loop = jsc.EventLoopHandle.init(event_loop),
|
||||
.process = subprocess,
|
||||
.stdio_result = result,
|
||||
.source = source,
|
||||
});
|
||||
if (Environment.isWindows) {
|
||||
this.writer.setPipe(this.stdio_result.buffer);
|
||||
}
|
||||
this.writer.setParent(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
pub fn start(this: *This) bun.sys.Maybe(void) {
|
||||
log("StaticPipeWriter(0x{x}) start()", .{@intFromPtr(this)});
|
||||
this.ref();
|
||||
this.buffer = this.source.slice();
|
||||
if (Environment.isWindows) {
|
||||
return this.writer.startWithCurrentPipe();
|
||||
}
|
||||
switch (this.writer.start(this.stdio_result.?, true)) {
|
||||
.err => |err| {
|
||||
return .{ .err = err };
|
||||
},
|
||||
.result => {
|
||||
if (comptime Environment.isPosix) {
|
||||
const poll = this.writer.handle.poll;
|
||||
poll.flags.insert(.socket);
|
||||
}
|
||||
|
||||
return .success;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn onWrite(this: *This, amount: usize, status: bun.io.WriteStatus) void {
|
||||
log("StaticPipeWriter(0x{x}) onWrite(amount={d} {})", .{ @intFromPtr(this), amount, status });
|
||||
this.buffer = this.buffer[@min(amount, this.buffer.len)..];
|
||||
if (status == .end_of_file or this.buffer.len == 0) {
|
||||
this.writer.close();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn onError(this: *This, err: bun.sys.Error) void {
|
||||
log("StaticPipeWriter(0x{x}) onError(err={any})", .{ @intFromPtr(this), err });
|
||||
this.source.detach();
|
||||
}
|
||||
|
||||
pub fn onClose(this: *This) void {
|
||||
log("StaticPipeWriter(0x{x}) onClose()", .{@intFromPtr(this)});
|
||||
this.source.detach();
|
||||
this.process.onCloseIO(.stdin);
|
||||
}
|
||||
|
||||
fn _deinit(this: *This) void {
|
||||
this.writer.end();
|
||||
this.source.detach();
|
||||
bun.destroy(this);
|
||||
}
|
||||
|
||||
pub fn memoryCost(this: *const This) usize {
|
||||
return @sizeOf(@This()) + this.source.memoryCost() + this.writer.memoryCost();
|
||||
}
|
||||
|
||||
pub fn loop(this: *This) *uws.Loop {
|
||||
return this.event_loop.loop();
|
||||
}
|
||||
|
||||
pub fn watch(this: *This) void {
|
||||
if (this.buffer.len > 0) {
|
||||
this.writer.watch();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eventLoop(this: *This) jsc.EventLoopHandle {
|
||||
return this.event_loop;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const log = Output.scoped(.StaticPipeWriter, .hidden);
|
||||
|
||||
const bun = @import("bun");
|
||||
const Environment = bun.Environment;
|
||||
const Output = bun.Output;
|
||||
const jsc = bun.jsc;
|
||||
const uws = bun.uws;
|
||||
|
||||
const Subprocess = jsc.API.Subprocess;
|
||||
const Source = Subprocess.Source;
|
||||
const StdioResult = Subprocess.StdioResult;
|
||||
225
src/bun.js/api/bun/subprocess/SubprocessPipeReader.zig
Normal file
225
src/bun.js/api/bun/subprocess/SubprocessPipeReader.zig
Normal file
@@ -0,0 +1,225 @@
|
||||
const PipeReader = @This();
|
||||
|
||||
const RefCount = bun.ptr.RefCount(@This(), "ref_count", PipeReader.deinit, .{});
|
||||
pub const ref = PipeReader.RefCount.ref;
|
||||
pub const deref = PipeReader.RefCount.deref;
|
||||
|
||||
reader: IOReader = undefined,
|
||||
process: ?*Subprocess = null,
|
||||
event_loop: *jsc.EventLoop = undefined,
|
||||
ref_count: PipeReader.RefCount,
|
||||
state: union(enum) {
|
||||
pending: void,
|
||||
done: []u8,
|
||||
err: bun.sys.Error,
|
||||
} = .{ .pending = {} },
|
||||
stdio_result: StdioResult,
|
||||
pub const IOReader = bun.io.BufferedReader;
|
||||
pub const Poll = IOReader;
|
||||
|
||||
pub fn memoryCost(this: *const PipeReader) usize {
|
||||
return this.reader.memoryCost();
|
||||
}
|
||||
|
||||
pub fn hasPendingActivity(this: *const PipeReader) bool {
|
||||
if (this.state == .pending)
|
||||
return true;
|
||||
|
||||
return this.reader.hasPendingActivity();
|
||||
}
|
||||
|
||||
pub fn detach(this: *PipeReader) void {
|
||||
this.process = null;
|
||||
this.deref();
|
||||
}
|
||||
|
||||
pub fn create(event_loop: *jsc.EventLoop, process: *Subprocess, result: StdioResult, limit: ?*MaxBuf) *PipeReader {
|
||||
var this = bun.new(PipeReader, .{
|
||||
.ref_count = .init(),
|
||||
.process = process,
|
||||
.reader = IOReader.init(@This()),
|
||||
.event_loop = event_loop,
|
||||
.stdio_result = result,
|
||||
});
|
||||
MaxBuf.addToPipereader(limit, &this.reader.maxbuf);
|
||||
if (Environment.isWindows) {
|
||||
this.reader.source = .{ .pipe = this.stdio_result.buffer };
|
||||
}
|
||||
|
||||
this.reader.setParent(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
pub fn readAll(this: *PipeReader) void {
|
||||
if (this.state == .pending)
|
||||
this.reader.read();
|
||||
}
|
||||
|
||||
pub fn start(this: *PipeReader, process: *Subprocess, event_loop: *jsc.EventLoop) bun.sys.Maybe(void) {
|
||||
this.ref();
|
||||
this.process = process;
|
||||
this.event_loop = event_loop;
|
||||
if (Environment.isWindows) {
|
||||
return this.reader.startWithCurrentPipe();
|
||||
}
|
||||
|
||||
switch (this.reader.start(this.stdio_result.?, true)) {
|
||||
.err => |err| {
|
||||
return .{ .err = err };
|
||||
},
|
||||
.result => {
|
||||
if (comptime Environment.isPosix) {
|
||||
const poll = this.reader.handle.poll;
|
||||
poll.flags.insert(.socket);
|
||||
this.reader.flags.socket = true;
|
||||
this.reader.flags.nonblocking = true;
|
||||
this.reader.flags.pollable = true;
|
||||
poll.flags.insert(.nonblocking);
|
||||
}
|
||||
|
||||
return .success;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub const toJS = toReadableStream;
|
||||
|
||||
pub fn onReaderDone(this: *PipeReader) void {
|
||||
const owned = this.toOwnedSlice();
|
||||
this.state = .{ .done = owned };
|
||||
if (this.process) |process| {
|
||||
this.process = null;
|
||||
process.onCloseIO(this.kind(process));
|
||||
this.deref();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kind(reader: *const PipeReader, process: *const Subprocess) StdioKind {
|
||||
if (process.stdout == .pipe and process.stdout.pipe == reader) {
|
||||
return .stdout;
|
||||
}
|
||||
|
||||
if (process.stderr == .pipe and process.stderr.pipe == reader) {
|
||||
return .stderr;
|
||||
}
|
||||
|
||||
@panic("We should be either stdout or stderr");
|
||||
}
|
||||
|
||||
pub fn toOwnedSlice(this: *PipeReader) []u8 {
|
||||
if (this.state == .done) {
|
||||
return this.state.done;
|
||||
}
|
||||
// we do not use .toOwnedSlice() because we don't want to reallocate memory.
|
||||
const out = this.reader._buffer;
|
||||
this.reader._buffer.items = &.{};
|
||||
this.reader._buffer.capacity = 0;
|
||||
|
||||
if (out.capacity > 0 and out.items.len == 0) {
|
||||
out.deinit();
|
||||
return &.{};
|
||||
}
|
||||
|
||||
return out.items;
|
||||
}
|
||||
|
||||
pub fn updateRef(this: *PipeReader, add: bool) void {
|
||||
this.reader.updateRef(add);
|
||||
}
|
||||
|
||||
pub fn watch(this: *PipeReader) void {
|
||||
if (!this.reader.isDone())
|
||||
this.reader.watch();
|
||||
}
|
||||
|
||||
pub fn toReadableStream(this: *PipeReader, globalObject: *jsc.JSGlobalObject) bun.JSError!jsc.JSValue {
|
||||
defer this.detach();
|
||||
|
||||
switch (this.state) {
|
||||
.pending => {
|
||||
const stream = jsc.WebCore.ReadableStream.fromPipe(globalObject, this, &this.reader);
|
||||
this.state = .{ .done = &.{} };
|
||||
return stream;
|
||||
},
|
||||
.done => |bytes| {
|
||||
this.state = .{ .done = &.{} };
|
||||
return jsc.WebCore.ReadableStream.fromOwnedSlice(globalObject, bytes, 0);
|
||||
},
|
||||
.err => |err| {
|
||||
_ = err;
|
||||
const empty = try jsc.WebCore.ReadableStream.empty(globalObject);
|
||||
jsc.WebCore.ReadableStream.cancel(&(try jsc.WebCore.ReadableStream.fromJS(empty, globalObject)).?, globalObject);
|
||||
return empty;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toBuffer(this: *PipeReader, globalThis: *jsc.JSGlobalObject) jsc.JSValue {
|
||||
switch (this.state) {
|
||||
.done => |bytes| {
|
||||
defer this.state = .{ .done = &.{} };
|
||||
return jsc.MarkedArrayBuffer.fromBytes(bytes, bun.default_allocator, .Uint8Array).toNodeBuffer(globalThis);
|
||||
},
|
||||
else => {
|
||||
return .js_undefined;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn onReaderError(this: *PipeReader, err: bun.sys.Error) void {
|
||||
if (this.state == .done) {
|
||||
bun.default_allocator.free(this.state.done);
|
||||
}
|
||||
this.state = .{ .err = err };
|
||||
if (this.process) |process|
|
||||
process.onCloseIO(this.kind(process));
|
||||
}
|
||||
|
||||
pub fn close(this: *PipeReader) void {
|
||||
switch (this.state) {
|
||||
.pending => {
|
||||
this.reader.close();
|
||||
},
|
||||
.done => {},
|
||||
.err => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eventLoop(this: *PipeReader) *jsc.EventLoop {
|
||||
return this.event_loop;
|
||||
}
|
||||
|
||||
pub fn loop(this: *PipeReader) *uws.Loop {
|
||||
return this.event_loop.virtual_machine.uwsLoop();
|
||||
}
|
||||
|
||||
fn deinit(this: *PipeReader) void {
|
||||
if (comptime Environment.isPosix) {
|
||||
bun.assert(this.reader.isDone());
|
||||
}
|
||||
|
||||
if (comptime Environment.isWindows) {
|
||||
bun.assert(this.reader.source == null or this.reader.source.?.isClosed());
|
||||
}
|
||||
|
||||
if (this.state == .done) {
|
||||
bun.default_allocator.free(this.state.done);
|
||||
}
|
||||
|
||||
this.reader.deinit();
|
||||
bun.destroy(this);
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const Environment = bun.Environment;
|
||||
const default_allocator = bun.default_allocator;
|
||||
const uws = bun.uws;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const JSGlobalObject = jsc.JSGlobalObject;
|
||||
const JSValue = jsc.JSValue;
|
||||
|
||||
const Subprocess = jsc.API.Subprocess;
|
||||
const MaxBuf = Subprocess.MaxBuf;
|
||||
const StdioKind = Subprocess.StdioKind;
|
||||
const StdioResult = Subprocess.StdioResult;
|
||||
334
src/bun.js/api/bun/subprocess/Writable.zig
Normal file
334
src/bun.js/api/bun/subprocess/Writable.zig
Normal file
@@ -0,0 +1,334 @@
|
||||
pub const Writable = union(enum) {
|
||||
pipe: *jsc.WebCore.FileSink,
|
||||
fd: bun.FileDescriptor,
|
||||
buffer: *StaticPipeWriter,
|
||||
memfd: bun.FileDescriptor,
|
||||
inherit: void,
|
||||
ignore: void,
|
||||
|
||||
pub fn memoryCost(this: *const Writable) usize {
|
||||
return switch (this.*) {
|
||||
.pipe => |pipe| pipe.memoryCost(),
|
||||
.buffer => |buffer| buffer.memoryCost(),
|
||||
// TODO: memfd
|
||||
else => 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn hasPendingActivity(this: *const Writable) bool {
|
||||
return switch (this.*) {
|
||||
.pipe => false,
|
||||
|
||||
// we mark them as .ignore when they are closed, so this must be true
|
||||
.buffer => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ref(this: *Writable) void {
|
||||
switch (this.*) {
|
||||
.pipe => {
|
||||
this.pipe.updateRef(true);
|
||||
},
|
||||
.buffer => {
|
||||
this.buffer.updateRef(true);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unref(this: *Writable) void {
|
||||
switch (this.*) {
|
||||
.pipe => {
|
||||
this.pipe.updateRef(false);
|
||||
},
|
||||
.buffer => {
|
||||
this.buffer.updateRef(false);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
// When the stream has closed we need to be notified to prevent a use-after-free
|
||||
// We can test for this use-after-free by enabling hot module reloading on a file and then saving it twice
|
||||
pub fn onClose(this: *Writable, _: ?bun.sys.Error) void {
|
||||
const process: *Subprocess = @fieldParentPtr("stdin", this);
|
||||
|
||||
if (process.this_jsvalue != .zero) {
|
||||
if (js.stdinGetCached(process.this_jsvalue)) |existing_value| {
|
||||
jsc.WebCore.FileSink.JSSink.setDestroyCallback(existing_value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
switch (this.*) {
|
||||
.buffer => {
|
||||
this.buffer.deref();
|
||||
},
|
||||
.pipe => {
|
||||
this.pipe.deref();
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
process.onStdinDestroyed();
|
||||
|
||||
this.* = .{
|
||||
.ignore = {},
|
||||
};
|
||||
}
|
||||
pub fn onReady(_: *Writable, _: ?jsc.WebCore.Blob.SizeType, _: ?jsc.WebCore.Blob.SizeType) void {}
|
||||
pub fn onStart(_: *Writable) void {}
|
||||
|
||||
pub fn init(
|
||||
stdio: *Stdio,
|
||||
event_loop: *jsc.EventLoop,
|
||||
subprocess: *Subprocess,
|
||||
result: StdioResult,
|
||||
promise_for_stream: *jsc.JSValue,
|
||||
) !Writable {
|
||||
Subprocess.assertStdioResult(result);
|
||||
|
||||
if (Environment.isWindows) {
|
||||
switch (stdio.*) {
|
||||
.pipe, .readable_stream => {
|
||||
if (result == .buffer) {
|
||||
const pipe = jsc.WebCore.FileSink.createWithPipe(event_loop, result.buffer);
|
||||
|
||||
switch (pipe.writer.startWithCurrentPipe()) {
|
||||
.result => {},
|
||||
.err => |err| {
|
||||
_ = err; // autofix
|
||||
pipe.deref();
|
||||
if (stdio.* == .readable_stream) {
|
||||
stdio.readable_stream.cancel(event_loop.global);
|
||||
}
|
||||
return error.UnexpectedCreatingStdin;
|
||||
},
|
||||
}
|
||||
pipe.writer.setParent(pipe);
|
||||
subprocess.weak_file_sink_stdin_ptr = pipe;
|
||||
subprocess.ref();
|
||||
subprocess.flags.deref_on_stdin_destroyed = true;
|
||||
subprocess.flags.has_stdin_destructor_called = false;
|
||||
|
||||
if (stdio.* == .readable_stream) {
|
||||
const assign_result = pipe.assignToStream(&stdio.readable_stream, event_loop.global);
|
||||
if (assign_result.toError()) |err| {
|
||||
pipe.deref();
|
||||
subprocess.deref();
|
||||
return event_loop.global.throwValue(err);
|
||||
}
|
||||
promise_for_stream.* = assign_result;
|
||||
}
|
||||
|
||||
return Writable{
|
||||
.pipe = pipe,
|
||||
};
|
||||
}
|
||||
return Writable{ .inherit = {} };
|
||||
},
|
||||
|
||||
.blob => |blob| {
|
||||
return Writable{
|
||||
.buffer = StaticPipeWriter.create(event_loop, subprocess, result, .{ .blob = blob }),
|
||||
};
|
||||
},
|
||||
.array_buffer => |array_buffer| {
|
||||
return Writable{
|
||||
.buffer = StaticPipeWriter.create(event_loop, subprocess, result, .{ .array_buffer = array_buffer }),
|
||||
};
|
||||
},
|
||||
.fd => |fd| {
|
||||
return Writable{ .fd = fd };
|
||||
},
|
||||
.dup2 => |dup2| {
|
||||
return Writable{ .fd = dup2.to.toFd() };
|
||||
},
|
||||
.inherit => {
|
||||
return Writable{ .inherit = {} };
|
||||
},
|
||||
.memfd, .path, .ignore => {
|
||||
return Writable{ .ignore = {} };
|
||||
},
|
||||
.ipc, .capture => {
|
||||
return Writable{ .ignore = {} };
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (comptime Environment.isPosix) {
|
||||
if (stdio.* == .pipe) {
|
||||
_ = bun.sys.setNonblocking(result.?);
|
||||
}
|
||||
}
|
||||
|
||||
switch (stdio.*) {
|
||||
.dup2 => @panic("TODO dup2 stdio"),
|
||||
.pipe, .readable_stream => {
|
||||
const pipe = jsc.WebCore.FileSink.create(event_loop, result.?);
|
||||
|
||||
switch (pipe.writer.start(pipe.fd, true)) {
|
||||
.result => {},
|
||||
.err => |err| {
|
||||
_ = err; // autofix
|
||||
pipe.deref();
|
||||
if (stdio.* == .readable_stream) {
|
||||
stdio.readable_stream.cancel(event_loop.global);
|
||||
}
|
||||
|
||||
return error.UnexpectedCreatingStdin;
|
||||
},
|
||||
}
|
||||
|
||||
pipe.writer.handle.poll.flags.insert(.socket);
|
||||
|
||||
subprocess.weak_file_sink_stdin_ptr = pipe;
|
||||
subprocess.ref();
|
||||
subprocess.flags.has_stdin_destructor_called = false;
|
||||
subprocess.flags.deref_on_stdin_destroyed = true;
|
||||
|
||||
if (stdio.* == .readable_stream) {
|
||||
const assign_result = pipe.assignToStream(&stdio.readable_stream, event_loop.global);
|
||||
if (assign_result.toError()) |err| {
|
||||
pipe.deref();
|
||||
subprocess.deref();
|
||||
return event_loop.global.throwValue(err);
|
||||
}
|
||||
promise_for_stream.* = assign_result;
|
||||
}
|
||||
|
||||
return Writable{
|
||||
.pipe = pipe,
|
||||
};
|
||||
},
|
||||
|
||||
.blob => |blob| {
|
||||
return Writable{
|
||||
.buffer = StaticPipeWriter.create(event_loop, subprocess, result, .{ .blob = blob }),
|
||||
};
|
||||
},
|
||||
.array_buffer => |array_buffer| {
|
||||
return Writable{
|
||||
.buffer = StaticPipeWriter.create(event_loop, subprocess, result, .{ .array_buffer = array_buffer }),
|
||||
};
|
||||
},
|
||||
.memfd => |memfd| {
|
||||
bun.assert(memfd != bun.invalid_fd);
|
||||
return Writable{ .memfd = memfd };
|
||||
},
|
||||
.fd => {
|
||||
return Writable{ .fd = result.? };
|
||||
},
|
||||
.inherit => {
|
||||
return Writable{ .inherit = {} };
|
||||
},
|
||||
.path, .ignore => {
|
||||
return Writable{ .ignore = {} };
|
||||
},
|
||||
.ipc, .capture => {
|
||||
return Writable{ .ignore = {} };
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toJS(this: *Writable, globalThis: *jsc.JSGlobalObject, subprocess: *Subprocess) JSValue {
|
||||
return switch (this.*) {
|
||||
.fd => |fd| fd.toJS(globalThis),
|
||||
.memfd, .ignore => .js_undefined,
|
||||
.buffer, .inherit => .js_undefined,
|
||||
.pipe => |pipe| {
|
||||
this.* = .{ .ignore = {} };
|
||||
if (subprocess.process.hasExited() and !subprocess.flags.has_stdin_destructor_called) {
|
||||
// onAttachedProcessExit() can call deref on the
|
||||
// subprocess. Since we never called ref(), it would be
|
||||
// unbalanced to do so, leading to a use-after-free.
|
||||
// So, let's not do that.
|
||||
// https://github.com/oven-sh/bun/pull/14092
|
||||
bun.debugAssert(!subprocess.flags.deref_on_stdin_destroyed);
|
||||
const debug_ref_count = if (Environment.isDebug) subprocess.ref_count else 0;
|
||||
pipe.onAttachedProcessExit(&subprocess.process.status);
|
||||
if (Environment.isDebug) {
|
||||
bun.debugAssert(subprocess.ref_count.get() == debug_ref_count.get());
|
||||
}
|
||||
return pipe.toJS(globalThis);
|
||||
} else {
|
||||
subprocess.flags.has_stdin_destructor_called = false;
|
||||
subprocess.weak_file_sink_stdin_ptr = pipe;
|
||||
subprocess.ref();
|
||||
subprocess.flags.deref_on_stdin_destroyed = true;
|
||||
if (@intFromPtr(pipe.signal.ptr) == @intFromPtr(subprocess)) {
|
||||
pipe.signal.clear();
|
||||
}
|
||||
return pipe.toJSWithDestructor(
|
||||
globalThis,
|
||||
jsc.WebCore.Sink.DestructorPtr.init(subprocess),
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn finalize(this: *Writable) void {
|
||||
const subprocess: *Subprocess = @fieldParentPtr("stdin", this);
|
||||
if (subprocess.this_jsvalue != .zero) {
|
||||
if (jsc.Codegen.JSSubprocess.stdinGetCached(subprocess.this_jsvalue)) |existing_value| {
|
||||
jsc.WebCore.FileSink.JSSink.setDestroyCallback(existing_value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return switch (this.*) {
|
||||
.pipe => |pipe| {
|
||||
if (pipe.signal.ptr == @as(*anyopaque, @ptrCast(this))) {
|
||||
pipe.signal.clear();
|
||||
}
|
||||
|
||||
pipe.deref();
|
||||
|
||||
this.* = .{ .ignore = {} };
|
||||
},
|
||||
.buffer => {
|
||||
this.buffer.updateRef(false);
|
||||
this.buffer.deref();
|
||||
},
|
||||
.memfd => |fd| {
|
||||
fd.close();
|
||||
this.* = .{ .ignore = {} };
|
||||
},
|
||||
.ignore => {},
|
||||
.fd, .inherit => {},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn close(this: *Writable) void {
|
||||
switch (this.*) {
|
||||
.pipe => |pipe| {
|
||||
_ = pipe.end(null);
|
||||
},
|
||||
.memfd => |fd| {
|
||||
fd.close();
|
||||
this.* = .{ .ignore = {} };
|
||||
},
|
||||
.fd => {
|
||||
this.* = .{ .ignore = {} };
|
||||
},
|
||||
.buffer => {
|
||||
this.buffer.close();
|
||||
},
|
||||
.ignore => {},
|
||||
.inherit => {},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const bun = @import("bun");
|
||||
const Environment = bun.Environment;
|
||||
const Stdio = bun.spawn.Stdio;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const JSGlobalObject = jsc.JSGlobalObject;
|
||||
const JSValue = jsc.JSValue;
|
||||
|
||||
const Subprocess = jsc.API.Subprocess;
|
||||
const StaticPipeWriter = Subprocess.StaticPipeWriter;
|
||||
const StdioResult = Subprocess.StdioResult;
|
||||
const js = Subprocess.js;
|
||||
@@ -394,15 +394,6 @@ void BunPlugin::OnLoad::addModuleMock(JSC::VM& vm, const String& path, JSC::JSOb
|
||||
virtualModules->set(path, JSC::Strong<JSC::JSObject> { vm, mockObject });
|
||||
}
|
||||
|
||||
void BunPlugin::OnLoad::clearModuleMocks()
|
||||
{
|
||||
if (virtualModules) {
|
||||
// Clear the virtual modules map
|
||||
// When code tries to import the module again, the original will be loaded
|
||||
virtualModules->clear();
|
||||
}
|
||||
}
|
||||
|
||||
class JSModuleMock final : public JSC::JSNonFinalObject {
|
||||
public:
|
||||
using Base = JSC::JSNonFinalObject;
|
||||
|
||||
@@ -76,7 +76,6 @@ public:
|
||||
bool hasVirtualModules() const { return virtualModules != nullptr; }
|
||||
|
||||
void addModuleMock(JSC::VM& vm, const String& path, JSC::JSObject* mock);
|
||||
void clearModuleMocks();
|
||||
|
||||
std::optional<String> resolveVirtualModule(const String& path, const String& from);
|
||||
|
||||
|
||||
@@ -1031,11 +1031,12 @@ JSC_DEFINE_CUSTOM_GETTER(jsMockFunctionGetter_protoImpl, (JSC::JSGlobalObject *
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
extern "C" JSC::EncodedJSValue JSMockFunction__getCalls(EncodedJSValue encodedValue)
|
||||
extern "C" [[ZIG_EXPORT(zero_is_throw)]] JSC::EncodedJSValue JSMockFunction__getCalls(JSC::JSGlobalObject* globalThis, EncodedJSValue encodedValue)
|
||||
{
|
||||
auto scope = DECLARE_THROW_SCOPE(globalThis->vm());
|
||||
JSValue value = JSValue::decode(encodedValue);
|
||||
if (auto* mock = tryJSDynamicCast<JSMockFunction*>(value)) {
|
||||
return JSValue::encode(mock->getCalls());
|
||||
RELEASE_AND_RETURN(scope, JSValue::encode(mock->getCalls()));
|
||||
}
|
||||
return encodedJSUndefined();
|
||||
}
|
||||
@@ -1100,25 +1101,8 @@ JSC_DEFINE_HOST_FUNCTION(jsMockFunctionMockRestore, (JSC::JSGlobalObject * globa
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
CHECK_IS_MOCK_FUNCTION(thisValue);
|
||||
|
||||
// First clear any function spies
|
||||
thisObject->clearSpy();
|
||||
|
||||
// Then reset module mocks
|
||||
// Get the GlobalObject as Zig::GlobalObject for access to our module mockery
|
||||
if (auto* zigGlobalObject = jsDynamicCast<Zig::GlobalObject*>(globalObject)) {
|
||||
// Clear the virtual modules map - removes module mocks
|
||||
zigGlobalObject->onLoadPlugins.clearModuleMocks();
|
||||
|
||||
// Call the reload method which will:
|
||||
// 1. Clear the ESM registry
|
||||
// 2. Clear the CommonJS require cache
|
||||
// 3. Run GC to clean up old references
|
||||
zigGlobalObject->reload();
|
||||
// Reset the internal reload count to ensure GC always runs on next reload
|
||||
// which helps modules get reloaded properly
|
||||
zigGlobalObject->reloadCount = 0;
|
||||
}
|
||||
|
||||
RELEASE_AND_RETURN(scope, JSValue::encode(thisObject));
|
||||
}
|
||||
JSC_DEFINE_HOST_FUNCTION(jsMockFunctionMockImplementation, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callframe))
|
||||
@@ -1468,24 +1452,7 @@ BUN_DEFINE_HOST_FUNCTION(JSMock__jsSetSystemTime, (JSC::JSGlobalObject * globalO
|
||||
|
||||
BUN_DEFINE_HOST_FUNCTION(JSMock__jsRestoreAllMocks, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
|
||||
{
|
||||
if (auto* zigGlobalObject = jsDynamicCast<Zig::GlobalObject*>(globalObject)) {
|
||||
// Reset all spies
|
||||
JSMock__resetSpies(zigGlobalObject);
|
||||
|
||||
// Clear module mocks
|
||||
zigGlobalObject->onLoadPlugins.clearModuleMocks();
|
||||
|
||||
// Call the reload method which will:
|
||||
// 1. Clear the ESM registry
|
||||
// 2. Clear the CommonJS require cache
|
||||
// 3. Run GC to clean up old references
|
||||
zigGlobalObject->reload();
|
||||
|
||||
// Reset the internal reload count to ensure GC always runs on next reload
|
||||
// which helps modules get reloaded properly
|
||||
zigGlobalObject->reloadCount = 0;
|
||||
}
|
||||
|
||||
JSMock__resetSpies(jsCast<Zig::GlobalObject*>(globalObject));
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
|
||||
@@ -1913,6 +1913,25 @@ const EVP_MD* getDigestByName(const WTF::StringView name, bool ignoreSHA512_224)
|
||||
return EVP_md5();
|
||||
}
|
||||
|
||||
if (WTF::startsWithIgnoringASCIICase(name, "rsa-sha"_s)) {
|
||||
auto bits = name.substring(7);
|
||||
if (WTF::equalIgnoringASCIICase(bits, "1"_s)) {
|
||||
return EVP_sha1();
|
||||
}
|
||||
if (WTF::equalIgnoringASCIICase(bits, "224"_s)) {
|
||||
return EVP_sha224();
|
||||
}
|
||||
if (WTF::equalIgnoringASCIICase(bits, "256"_s)) {
|
||||
return EVP_sha256();
|
||||
}
|
||||
if (WTF::equalIgnoringASCIICase(bits, "384"_s)) {
|
||||
return EVP_sha384();
|
||||
}
|
||||
if (WTF::equalIgnoringASCIICase(bits, "512"_s)) {
|
||||
return EVP_sha512();
|
||||
}
|
||||
}
|
||||
|
||||
if (WTF::startsWithIgnoringASCIICase(name, "sha"_s)) {
|
||||
auto remain = name.substring(3);
|
||||
if (remain.startsWith('-')) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
75
src/bun.js/test/expect/toBe.zig
Normal file
75
src/bun.js/test/expect/toBe.zig
Normal file
@@ -0,0 +1,75 @@
|
||||
/// Object.is()
|
||||
pub fn toBe(
|
||||
this: *Expect,
|
||||
globalThis: *JSGlobalObject,
|
||||
callframe: *CallFrame,
|
||||
) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
const thisValue = callframe.this();
|
||||
const arguments_ = callframe.arguments_old(2);
|
||||
const arguments = arguments_.slice();
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toBe() takes 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
const right = arguments[0];
|
||||
right.ensureStillAlive();
|
||||
const left = try this.getValue(globalThis, thisValue, "toBe", "<green>expected<r>");
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = try right.isSameValue(left, globalThis);
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
|
||||
switch (this.custom_label.isEmpty()) {
|
||||
inline else => |has_custom_label| {
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBe", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\nExpected: not <green>{any}<r>\n", .{right.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBe", "<green>expected<r>", false);
|
||||
if (try left.deepEquals(right, globalThis) or try left.strictDeepEquals(right, globalThis)) {
|
||||
const fmt =
|
||||
(if (!has_custom_label) "\n\n<d>If this test should pass, replace \"toBe\" with \"toEqual\" or \"toStrictEqual\"<r>" else "") ++
|
||||
"\n\nExpected: <green>{any}<r>\n" ++
|
||||
"Received: serializes to the same string\n";
|
||||
return this.throw(globalThis, signature, fmt, .{right.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
if (right.isString() and left.isString()) {
|
||||
const diff_format = DiffFormatter{
|
||||
.expected = right,
|
||||
.received = left,
|
||||
.globalThis = globalThis,
|
||||
.not = not,
|
||||
};
|
||||
return this.throw(globalThis, signature, "\n\n{any}\n", .{diff_format});
|
||||
}
|
||||
|
||||
return this.throw(globalThis, signature, "\n\nExpected: <green>{any}<r>\nReceived: <red>{any}<r>\n", .{
|
||||
right.toFmt(&formatter),
|
||||
left.toFmt(&formatter),
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const DiffFormatter = @import("../diff_format.zig").DiffFormatter;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
36
src/bun.js/test/expect/toBeArray.zig
Normal file
36
src/bun.js/test/expect/toBeArray.zig
Normal file
@@ -0,0 +1,36 @@
|
||||
pub fn toBeArray(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeArray", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
const pass = value.jsType().isArray() != not;
|
||||
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBeArray", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBeArray", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
51
src/bun.js/test/expect/toBeArrayOfSize.zig
Normal file
51
src/bun.js/test/expect/toBeArrayOfSize.zig
Normal file
@@ -0,0 +1,51 @@
|
||||
pub fn toBeArrayOfSize(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const _arguments = callFrame.arguments_old(1);
|
||||
const arguments = _arguments.ptr[0.._arguments.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toBeArrayOfSize() requires 1 argument", .{});
|
||||
}
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeArrayOfSize", "");
|
||||
|
||||
const size = arguments[0];
|
||||
size.ensureStillAlive();
|
||||
|
||||
if (!size.isAnyInt()) {
|
||||
return globalThis.throw("toBeArrayOfSize() requires the first argument to be a number", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = value.jsType().isArray() and @as(i32, @intCast(try value.getLength(globalThis))) == size.toInt32();
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBeArrayOfSize", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBeArrayOfSize", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
36
src/bun.js/test/expect/toBeBoolean.zig
Normal file
36
src/bun.js/test/expect/toBeBoolean.zig
Normal file
@@ -0,0 +1,36 @@
|
||||
pub fn toBeBoolean(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeBoolean", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
const pass = value.isBoolean() != not;
|
||||
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBeBoolean", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBeBoolean", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
88
src/bun.js/test/expect/toBeCloseTo.zig
Normal file
88
src/bun.js/test/expect/toBeCloseTo.zig
Normal file
@@ -0,0 +1,88 @@
|
||||
pub fn toBeCloseTo(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const thisArguments = callFrame.arguments_old(2);
|
||||
const arguments = thisArguments.ptr[0..thisArguments.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toBeCloseTo() requires at least 1 argument. Expected value must be a number", .{});
|
||||
}
|
||||
|
||||
const expected_ = arguments[0];
|
||||
if (!expected_.isNumber()) {
|
||||
return globalThis.throwInvalidArgumentType("toBeCloseTo", "expected", "number");
|
||||
}
|
||||
|
||||
var precision: f64 = 2.0;
|
||||
if (arguments.len > 1) {
|
||||
const precision_ = arguments[1];
|
||||
if (!precision_.isNumber()) {
|
||||
return globalThis.throwInvalidArgumentType("toBeCloseTo", "precision", "number");
|
||||
}
|
||||
|
||||
precision = precision_.asNumber();
|
||||
}
|
||||
|
||||
const received_: JSValue = try this.getValue(globalThis, thisValue, "toBeCloseTo", "<green>expected<r>, precision");
|
||||
if (!received_.isNumber()) {
|
||||
return globalThis.throwInvalidArgumentType("expect", "received", "number");
|
||||
}
|
||||
|
||||
var expected = expected_.asNumber();
|
||||
var received = received_.asNumber();
|
||||
|
||||
if (std.math.isNegativeInf(expected)) {
|
||||
expected = -expected;
|
||||
}
|
||||
|
||||
if (std.math.isNegativeInf(received)) {
|
||||
received = -received;
|
||||
}
|
||||
|
||||
if (std.math.isPositiveInf(expected) and std.math.isPositiveInf(received)) {
|
||||
return .js_undefined;
|
||||
}
|
||||
|
||||
const expected_diff = bun.pow(10, -precision) / 2;
|
||||
const actual_diff = @abs(received - expected);
|
||||
var pass = actual_diff < expected_diff;
|
||||
|
||||
const not = this.flags.not;
|
||||
if (not) pass = !pass;
|
||||
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
|
||||
const expected_fmt = expected_.toFmt(&formatter);
|
||||
const received_fmt = received_.toFmt(&formatter);
|
||||
|
||||
const expected_line = "Expected: <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const expected_precision = "Expected precision: {d}\n";
|
||||
const expected_difference = "Expected difference: \\< <green>{d}<r>\n";
|
||||
const received_difference = "Received difference: <red>{d}<r>\n";
|
||||
|
||||
const suffix_fmt = "\n\n" ++ expected_line ++ received_line ++ "\n" ++ expected_precision ++ expected_difference ++ received_difference;
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBeCloseTo", "<green>expected<r>, precision", true);
|
||||
return this.throw(globalThis, signature, suffix_fmt, .{ expected_fmt, received_fmt, precision, expected_diff, actual_diff });
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBeCloseTo", "<green>expected<r>, precision", false);
|
||||
return this.throw(globalThis, signature, suffix_fmt, .{ expected_fmt, received_fmt, precision, expected_diff, actual_diff });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const std = @import("std");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
36
src/bun.js/test/expect/toBeDate.zig
Normal file
36
src/bun.js/test/expect/toBeDate.zig
Normal file
@@ -0,0 +1,36 @@
|
||||
pub fn toBeDate(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeDate", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
const pass = value.isDate() != not;
|
||||
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBeDate", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBeDate", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
38
src/bun.js/test/expect/toBeDefined.zig
Normal file
38
src/bun.js/test/expect/toBeDefined.zig
Normal file
@@ -0,0 +1,38 @@
|
||||
pub fn toBeDefined(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeDefined", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = !value.isUndefined();
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
if (not) {
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeDefined", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ received_line, .{value_fmt});
|
||||
}
|
||||
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeDefined", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ received_line, .{value_fmt});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
89
src/bun.js/test/expect/toBeEmpty.zig
Normal file
89
src/bun.js/test/expect/toBeEmpty.zig
Normal file
@@ -0,0 +1,89 @@
|
||||
pub fn toBeEmpty(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeEmpty", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
|
||||
const actual_length = try value.getLengthIfPropertyExistsInternal(globalThis);
|
||||
|
||||
if (actual_length == std.math.inf(f64)) {
|
||||
if (value.jsTypeLoose().isObject()) {
|
||||
if (try value.isIterable(globalThis)) {
|
||||
var any_properties_in_iterator = false;
|
||||
try value.forEach(globalThis, &any_properties_in_iterator, struct {
|
||||
pub fn anythingInIterator(
|
||||
_: *jsc.VM,
|
||||
_: *JSGlobalObject,
|
||||
any_: ?*anyopaque,
|
||||
_: JSValue,
|
||||
) callconv(.C) void {
|
||||
bun.cast(*bool, any_.?).* = true;
|
||||
}
|
||||
}.anythingInIterator);
|
||||
pass = !any_properties_in_iterator;
|
||||
} else {
|
||||
const cell = value.toCell() orelse {
|
||||
return globalThis.throwTypeError("Expected value to be a string, object, or iterable", .{});
|
||||
};
|
||||
var props_iter = try jsc.JSPropertyIterator(.{
|
||||
.skip_empty_name = false,
|
||||
.own_properties_only = false,
|
||||
.include_value = true,
|
||||
// FIXME: can we do this?
|
||||
}).init(globalThis, cell.toObject(globalThis));
|
||||
defer props_iter.deinit();
|
||||
pass = props_iter.len == 0;
|
||||
}
|
||||
} else {
|
||||
const signature = comptime getSignature("toBeEmpty", "", false);
|
||||
const fmt = signature ++ "\n\nExpected value to be a string, object, or iterable" ++
|
||||
"\n\nReceived: <red>{any}<r>\n";
|
||||
return globalThis.throwPretty(fmt, .{value.toFmt(&formatter)});
|
||||
}
|
||||
} else if (std.math.isNan(actual_length)) {
|
||||
return globalThis.throw("Received value has non-number length property: {}", .{actual_length});
|
||||
} else {
|
||||
pass = actual_length == 0;
|
||||
}
|
||||
|
||||
if (not and pass) {
|
||||
const signature = comptime getSignature("toBeEmpty", "", true);
|
||||
const fmt = signature ++ "\n\nExpected value <b>not<r> to be a string, object, or iterable" ++
|
||||
"\n\nReceived: <red>{any}<r>\n";
|
||||
return globalThis.throwPretty(fmt, .{value.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBeEmpty", "", true);
|
||||
const fmt = signature ++ "\n\nExpected value <b>not<r> to be empty" ++
|
||||
"\n\nReceived: <red>{any}<r>\n";
|
||||
return globalThis.throwPretty(fmt, .{value.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBeEmpty", "", false);
|
||||
const fmt = signature ++ "\n\nExpected value to be empty" ++
|
||||
"\n\nReceived: <red>{any}<r>\n";
|
||||
return globalThis.throwPretty(fmt, .{value.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const std = @import("std");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
37
src/bun.js/test/expect/toBeEmptyObject.zig
Normal file
37
src/bun.js/test/expect/toBeEmptyObject.zig
Normal file
@@ -0,0 +1,37 @@
|
||||
pub fn toBeEmptyObject(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeEmptyObject", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = try value.isObjectEmpty(globalThis);
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return thisValue;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBeEmptyObject", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBeEmptyObject", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
63
src/bun.js/test/expect/toBeEven.zig
Normal file
63
src/bun.js/test/expect/toBeEven.zig
Normal file
@@ -0,0 +1,63 @@
|
||||
pub fn toBeEven(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeEven", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
|
||||
if (value.isAnyInt()) {
|
||||
const _value = value.toInt64();
|
||||
pass = @mod(_value, 2) == 0;
|
||||
if (_value == -0.0) { // negative zero is even
|
||||
pass = true;
|
||||
}
|
||||
} else if (value.isBigInt() or value.isBigInt32()) {
|
||||
const _value = value.toInt64();
|
||||
pass = switch (_value == -0.0) { // negative zero is even
|
||||
true => true,
|
||||
else => _value & 1 == 0,
|
||||
};
|
||||
} else if (value.isNumber()) {
|
||||
const _value = JSValue.asNumber(value);
|
||||
if (@mod(_value, 1) == 0 and @mod(_value, 2) == 0) { // if the fraction is all zeros and even
|
||||
pass = true;
|
||||
} else {
|
||||
pass = false;
|
||||
}
|
||||
} else {
|
||||
pass = false;
|
||||
}
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
if (not) {
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeEven", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ received_line, .{value_fmt});
|
||||
}
|
||||
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeEven", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ received_line, .{value_fmt});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
36
src/bun.js/test/expect/toBeFalse.zig
Normal file
36
src/bun.js/test/expect/toBeFalse.zig
Normal file
@@ -0,0 +1,36 @@
|
||||
pub fn toBeFalse(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeFalse", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
const pass = (value.isBoolean() and !value.toBoolean()) != not;
|
||||
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBeFalse", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBeFalse", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
43
src/bun.js/test/expect/toBeFalsy.zig
Normal file
43
src/bun.js/test/expect/toBeFalsy.zig
Normal file
@@ -0,0 +1,43 @@
|
||||
pub fn toBeFalsy(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeFalsy", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
|
||||
const truthy = value.toBoolean();
|
||||
if (!truthy) pass = true;
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
if (not) {
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeFalsy", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ received_line, .{value_fmt});
|
||||
}
|
||||
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeFalsy", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ received_line, .{value_fmt});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
43
src/bun.js/test/expect/toBeFinite.zig
Normal file
43
src/bun.js/test/expect/toBeFinite.zig
Normal file
@@ -0,0 +1,43 @@
|
||||
pub fn toBeFinite(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeFinite", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
var pass = value.isNumber();
|
||||
if (pass) {
|
||||
const num: f64 = value.asNumber();
|
||||
pass = std.math.isFinite(num) and !std.math.isNan(num);
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
if (not) pass = !pass;
|
||||
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBeFinite", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBeFinite", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const std = @import("std");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
36
src/bun.js/test/expect/toBeFunction.zig
Normal file
36
src/bun.js/test/expect/toBeFunction.zig
Normal file
@@ -0,0 +1,36 @@
|
||||
pub fn toBeFunction(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeFunction", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
const pass = value.isCallable() != not;
|
||||
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBeFunction", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBeFunction", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
70
src/bun.js/test/expect/toBeGreaterThan.zig
Normal file
70
src/bun.js/test/expect/toBeGreaterThan.zig
Normal file
@@ -0,0 +1,70 @@
|
||||
pub fn toBeGreaterThan(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const _arguments = callFrame.arguments_old(1);
|
||||
const arguments: []const JSValue = _arguments.ptr[0.._arguments.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toBeGreaterThan() requires 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const other_value = arguments[0];
|
||||
other_value.ensureStillAlive();
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeGreaterThan", "<green>expected<r>");
|
||||
|
||||
if ((!value.isNumber() and !value.isBigInt()) or (!other_value.isNumber() and !other_value.isBigInt())) {
|
||||
return globalThis.throw("Expected and actual values must be numbers or bigints", .{});
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
|
||||
if (!value.isBigInt() and !other_value.isBigInt()) {
|
||||
pass = value.asNumber() > other_value.asNumber();
|
||||
} else if (value.isBigInt()) {
|
||||
pass = switch (value.asBigIntCompare(globalThis, other_value)) {
|
||||
.greater_than => true,
|
||||
else => pass,
|
||||
};
|
||||
} else {
|
||||
pass = switch (other_value.asBigIntCompare(globalThis, value)) {
|
||||
.less_than => true,
|
||||
else => pass,
|
||||
};
|
||||
}
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
const expected_fmt = other_value.toFmt(&formatter);
|
||||
if (not) {
|
||||
const expected_line = "Expected: not \\> <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeGreaterThan", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const expected_line = "Expected: \\> <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeGreaterThan", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
70
src/bun.js/test/expect/toBeGreaterThanOrEqual.zig
Normal file
70
src/bun.js/test/expect/toBeGreaterThanOrEqual.zig
Normal file
@@ -0,0 +1,70 @@
|
||||
pub fn toBeGreaterThanOrEqual(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const _arguments = callFrame.arguments_old(1);
|
||||
const arguments: []const JSValue = _arguments.ptr[0.._arguments.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toBeGreaterThanOrEqual() requires 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const other_value = arguments[0];
|
||||
other_value.ensureStillAlive();
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeGreaterThanOrEqual", "<green>expected<r>");
|
||||
|
||||
if ((!value.isNumber() and !value.isBigInt()) or (!other_value.isNumber() and !other_value.isBigInt())) {
|
||||
return globalThis.throw("Expected and actual values must be numbers or bigints", .{});
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
|
||||
if (!value.isBigInt() and !other_value.isBigInt()) {
|
||||
pass = value.asNumber() >= other_value.asNumber();
|
||||
} else if (value.isBigInt()) {
|
||||
pass = switch (value.asBigIntCompare(globalThis, other_value)) {
|
||||
.greater_than, .equal => true,
|
||||
else => pass,
|
||||
};
|
||||
} else {
|
||||
pass = switch (other_value.asBigIntCompare(globalThis, value)) {
|
||||
.less_than, .equal => true,
|
||||
else => pass,
|
||||
};
|
||||
}
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
const expected_fmt = other_value.toFmt(&formatter);
|
||||
if (not) {
|
||||
const expected_line = "Expected: not \\>= <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeGreaterThanOrEqual", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const expected_line = "Expected: \\>= <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeGreaterThanOrEqual", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
54
src/bun.js/test/expect/toBeInstanceOf.zig
Normal file
54
src/bun.js/test/expect/toBeInstanceOf.zig
Normal file
@@ -0,0 +1,54 @@
|
||||
pub fn toBeInstanceOf(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const _arguments = callFrame.arguments_old(1);
|
||||
const arguments: []const JSValue = _arguments.ptr[0.._arguments.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toBeInstanceOf() requires 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
|
||||
const expected_value = arguments[0];
|
||||
if (!expected_value.isConstructor()) {
|
||||
return globalThis.throw("Expected value must be a function: {any}", .{expected_value.toFmt(&formatter)});
|
||||
}
|
||||
expected_value.ensureStillAlive();
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeInstanceOf", "<green>expected<r>");
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = value.isInstanceOf(globalThis, expected_value);
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
const expected_fmt = expected_value.toFmt(&formatter);
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
if (not) {
|
||||
const expected_line = "Expected constructor: not <green>{any}<r>\n";
|
||||
const received_line = "Received value: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeInstanceOf", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const expected_line = "Expected constructor: <green>{any}<r>\n";
|
||||
const received_line = "Received value: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeInstanceOf", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
36
src/bun.js/test/expect/toBeInteger.zig
Normal file
36
src/bun.js/test/expect/toBeInteger.zig
Normal file
@@ -0,0 +1,36 @@
|
||||
pub fn toBeInteger(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeInteger", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
const pass = value.isAnyInt() != not;
|
||||
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBeInteger", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBeInteger", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
70
src/bun.js/test/expect/toBeLessThan.zig
Normal file
70
src/bun.js/test/expect/toBeLessThan.zig
Normal file
@@ -0,0 +1,70 @@
|
||||
pub fn toBeLessThan(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const _arguments = callFrame.arguments_old(1);
|
||||
const arguments: []const JSValue = _arguments.ptr[0.._arguments.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toBeLessThan() requires 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const other_value = arguments[0];
|
||||
other_value.ensureStillAlive();
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeLessThan", "<green>expected<r>");
|
||||
|
||||
if ((!value.isNumber() and !value.isBigInt()) or (!other_value.isNumber() and !other_value.isBigInt())) {
|
||||
return globalThis.throw("Expected and actual values must be numbers or bigints", .{});
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
|
||||
if (!value.isBigInt() and !other_value.isBigInt()) {
|
||||
pass = value.asNumber() < other_value.asNumber();
|
||||
} else if (value.isBigInt()) {
|
||||
pass = switch (value.asBigIntCompare(globalThis, other_value)) {
|
||||
.less_than => true,
|
||||
else => pass,
|
||||
};
|
||||
} else {
|
||||
pass = switch (other_value.asBigIntCompare(globalThis, value)) {
|
||||
.greater_than => true,
|
||||
else => pass,
|
||||
};
|
||||
}
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
const expected_fmt = other_value.toFmt(&formatter);
|
||||
if (not) {
|
||||
const expected_line = "Expected: not \\< <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeLessThan", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const expected_line = "Expected: \\< <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeLessThan", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
70
src/bun.js/test/expect/toBeLessThanOrEqual.zig
Normal file
70
src/bun.js/test/expect/toBeLessThanOrEqual.zig
Normal file
@@ -0,0 +1,70 @@
|
||||
pub fn toBeLessThanOrEqual(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const _arguments = callFrame.arguments_old(1);
|
||||
const arguments: []const JSValue = _arguments.ptr[0.._arguments.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toBeLessThanOrEqual() requires 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const other_value = arguments[0];
|
||||
other_value.ensureStillAlive();
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeLessThanOrEqual", "<green>expected<r>");
|
||||
|
||||
if ((!value.isNumber() and !value.isBigInt()) or (!other_value.isNumber() and !other_value.isBigInt())) {
|
||||
return globalThis.throw("Expected and actual values must be numbers or bigints", .{});
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
|
||||
if (!value.isBigInt() and !other_value.isBigInt()) {
|
||||
pass = value.asNumber() <= other_value.asNumber();
|
||||
} else if (value.isBigInt()) {
|
||||
pass = switch (value.asBigIntCompare(globalThis, other_value)) {
|
||||
.less_than, .equal => true,
|
||||
else => pass,
|
||||
};
|
||||
} else {
|
||||
pass = switch (other_value.asBigIntCompare(globalThis, value)) {
|
||||
.greater_than, .equal => true,
|
||||
else => pass,
|
||||
};
|
||||
}
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
const expected_fmt = other_value.toFmt(&formatter);
|
||||
if (not) {
|
||||
const expected_line = "Expected: not \\<= <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeLessThanOrEqual", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const expected_line = "Expected: \\<= <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeLessThanOrEqual", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
43
src/bun.js/test/expect/toBeNaN.zig
Normal file
43
src/bun.js/test/expect/toBeNaN.zig
Normal file
@@ -0,0 +1,43 @@
|
||||
pub fn toBeNaN(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeNaN", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
if (value.isNumber()) {
|
||||
const number = value.asNumber();
|
||||
if (number != number) pass = true;
|
||||
}
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
if (not) {
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeNaN", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ received_line, .{value_fmt});
|
||||
}
|
||||
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeNaN", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ received_line, .{value_fmt});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
43
src/bun.js/test/expect/toBeNegative.zig
Normal file
43
src/bun.js/test/expect/toBeNegative.zig
Normal file
@@ -0,0 +1,43 @@
|
||||
pub fn toBeNegative(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeNegative", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
var pass = value.isNumber();
|
||||
if (pass) {
|
||||
const num: f64 = value.asNumber();
|
||||
pass = @round(num) < 0 and !std.math.isInf(num) and !std.math.isNan(num);
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
if (not) pass = !pass;
|
||||
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBeNegative", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBeNegative", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const std = @import("std");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
36
src/bun.js/test/expect/toBeNil.zig
Normal file
36
src/bun.js/test/expect/toBeNil.zig
Normal file
@@ -0,0 +1,36 @@
|
||||
pub fn toBeNil(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeNil", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
const pass = value.isUndefinedOrNull() != not;
|
||||
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBeNil", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBeNil", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
38
src/bun.js/test/expect/toBeNull.zig
Normal file
38
src/bun.js/test/expect/toBeNull.zig
Normal file
@@ -0,0 +1,38 @@
|
||||
pub fn toBeNull(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeNull", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = value.isNull();
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
if (not) {
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeNull", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ received_line, .{value_fmt});
|
||||
}
|
||||
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeNull", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ received_line, .{value_fmt});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
36
src/bun.js/test/expect/toBeNumber.zig
Normal file
36
src/bun.js/test/expect/toBeNumber.zig
Normal file
@@ -0,0 +1,36 @@
|
||||
pub fn toBeNumber(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeNumber", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
const pass = value.isNumber() != not;
|
||||
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBeNumber", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBeNumber", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
36
src/bun.js/test/expect/toBeObject.zig
Normal file
36
src/bun.js/test/expect/toBeObject.zig
Normal file
@@ -0,0 +1,36 @@
|
||||
pub fn toBeObject(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeObject", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
const pass = value.isObject() != not;
|
||||
|
||||
if (pass) return thisValue;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBeObject", "", true);
|
||||
return this.throw(globalThis, signature, "\n\nExpected value <b>not<r> to be an object" ++ "\n\nReceived: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBeObject", "", false);
|
||||
return this.throw(globalThis, signature, "\n\nExpected value to be an object" ++ "\n\nReceived: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
61
src/bun.js/test/expect/toBeOdd.zig
Normal file
61
src/bun.js/test/expect/toBeOdd.zig
Normal file
@@ -0,0 +1,61 @@
|
||||
pub fn toBeOdd(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeOdd", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
|
||||
if (value.isBigInt32()) {
|
||||
pass = value.toInt32() & 1 == 1;
|
||||
} else if (value.isBigInt()) {
|
||||
pass = value.toInt64() & 1 == 1;
|
||||
} else if (value.isInt32()) {
|
||||
const _value = value.toInt32();
|
||||
pass = @mod(_value, 2) == 1;
|
||||
} else if (value.isAnyInt()) {
|
||||
const _value = value.toInt64();
|
||||
pass = @mod(_value, 2) == 1;
|
||||
} else if (value.isNumber()) {
|
||||
const _value = JSValue.asNumber(value);
|
||||
if (@mod(_value, 1) == 0 and @mod(_value, 2) == 1) { // if the fraction is all zeros and odd
|
||||
pass = true;
|
||||
} else {
|
||||
pass = false;
|
||||
}
|
||||
} else {
|
||||
pass = false;
|
||||
}
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
if (not) {
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeOdd", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ received_line, .{value_fmt});
|
||||
}
|
||||
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeOdd", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ received_line, .{value_fmt});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
93
src/bun.js/test/expect/toBeOneOf.zig
Normal file
93
src/bun.js/test/expect/toBeOneOf.zig
Normal file
@@ -0,0 +1,93 @@
|
||||
pub fn toBeOneOf(
|
||||
this: *Expect,
|
||||
globalThis: *JSGlobalObject,
|
||||
callFrame: *CallFrame,
|
||||
) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
const thisValue = callFrame.this();
|
||||
const arguments_ = callFrame.arguments_old(1);
|
||||
const arguments = arguments_.slice();
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toBeOneOf() takes 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const expected = try this.getValue(globalThis, thisValue, "toBeOneOf", "<green>expected<r>");
|
||||
const list_value: JSValue = arguments[0];
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
|
||||
const ExpectedEntry = struct {
|
||||
globalThis: *JSGlobalObject,
|
||||
expected: JSValue,
|
||||
pass: *bool,
|
||||
};
|
||||
|
||||
if (list_value.jsTypeLoose().isArrayLike()) {
|
||||
var itr = try list_value.arrayIterator(globalThis);
|
||||
while (try itr.next()) |item| {
|
||||
// Confusingly, jest-extended uses `deepEqual`, instead of `toBe`
|
||||
if (try item.jestDeepEquals(expected, globalThis)) {
|
||||
pass = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (try list_value.isIterable(globalThis)) {
|
||||
var expected_entry = ExpectedEntry{
|
||||
.globalThis = globalThis,
|
||||
.expected = expected,
|
||||
.pass = &pass,
|
||||
};
|
||||
try list_value.forEach(globalThis, &expected_entry, struct {
|
||||
pub fn sameValueIterator(
|
||||
_: *jsc.VM,
|
||||
_: *JSGlobalObject,
|
||||
entry_: ?*anyopaque,
|
||||
item: JSValue,
|
||||
) callconv(.C) void {
|
||||
const entry = bun.cast(*ExpectedEntry, entry_.?);
|
||||
// Confusingly, jest-extended uses `deepEqual`, instead of `toBe`
|
||||
if (item.jestDeepEquals(entry.expected, entry.globalThis) catch return) {
|
||||
entry.pass.* = true;
|
||||
// TODO(perf): break out of the `forEach` when a match is found
|
||||
}
|
||||
}
|
||||
}.sameValueIterator);
|
||||
} else {
|
||||
return globalThis.throw("Received value must be an array type, or both received and expected values must be strings.", .{});
|
||||
}
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = list_value.toFmt(&formatter);
|
||||
const expected_fmt = expected.toFmt(&formatter);
|
||||
if (not) {
|
||||
const received_fmt = list_value.toFmt(&formatter);
|
||||
const expected_line = "Expected to not be one of: <green>{any}<r>\nReceived: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeOneOf", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line, .{ received_fmt, expected_fmt });
|
||||
}
|
||||
|
||||
const expected_line = "Expected to be one of: <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeOneOf", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ value_fmt, expected_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
43
src/bun.js/test/expect/toBePositive.zig
Normal file
43
src/bun.js/test/expect/toBePositive.zig
Normal file
@@ -0,0 +1,43 @@
|
||||
pub fn toBePositive(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBePositive", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
var pass = value.isNumber();
|
||||
if (pass) {
|
||||
const num: f64 = value.asNumber();
|
||||
pass = @round(num) > 0 and !std.math.isInf(num) and !std.math.isNan(num);
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
if (not) pass = !pass;
|
||||
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBePositive", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBePositive", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const std = @import("std");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
36
src/bun.js/test/expect/toBeString.zig
Normal file
36
src/bun.js/test/expect/toBeString.zig
Normal file
@@ -0,0 +1,36 @@
|
||||
pub fn toBeString(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeString", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
const pass = value.isString() != not;
|
||||
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBeString", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBeString", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
36
src/bun.js/test/expect/toBeSymbol.zig
Normal file
36
src/bun.js/test/expect/toBeSymbol.zig
Normal file
@@ -0,0 +1,36 @@
|
||||
pub fn toBeSymbol(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeSymbol", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
const pass = value.isSymbol() != not;
|
||||
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBeSymbol", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBeSymbol", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
36
src/bun.js/test/expect/toBeTrue.zig
Normal file
36
src/bun.js/test/expect/toBeTrue.zig
Normal file
@@ -0,0 +1,36 @@
|
||||
pub fn toBeTrue(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeTrue", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
const pass = (value.isBoolean() and value.toBoolean()) != not;
|
||||
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBeTrue", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBeTrue", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
41
src/bun.js/test/expect/toBeTruthy.zig
Normal file
41
src/bun.js/test/expect/toBeTruthy.zig
Normal file
@@ -0,0 +1,41 @@
|
||||
pub fn toBeTruthy(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeTruthy", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
|
||||
const truthy = value.toBoolean();
|
||||
if (truthy) pass = true;
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
if (not) {
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeTruthy", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ received_line, .{value_fmt});
|
||||
}
|
||||
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeTruthy", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ received_line, .{value_fmt});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
94
src/bun.js/test/expect/toBeTypeOf.zig
Normal file
94
src/bun.js/test/expect/toBeTypeOf.zig
Normal file
@@ -0,0 +1,94 @@
|
||||
const JSTypeOfMap = bun.ComptimeStringMap([]const u8, .{
|
||||
.{ "function", "function" },
|
||||
.{ "object", "object" },
|
||||
.{ "bigint", "bigint" },
|
||||
.{ "boolean", "boolean" },
|
||||
.{ "number", "number" },
|
||||
.{ "string", "string" },
|
||||
.{ "symbol", "symbol" },
|
||||
.{ "undefined", "undefined" },
|
||||
});
|
||||
|
||||
pub fn toBeTypeOf(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const _arguments = callFrame.arguments_old(1);
|
||||
const arguments = _arguments.ptr[0.._arguments.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toBeTypeOf() requires 1 argument", .{});
|
||||
}
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeTypeOf", "");
|
||||
|
||||
const expected = arguments[0];
|
||||
expected.ensureStillAlive();
|
||||
|
||||
if (!expected.isString()) {
|
||||
return globalThis.throwInvalidArguments("toBeTypeOf() requires a string argument", .{});
|
||||
}
|
||||
|
||||
const expected_type = try expected.toBunString(globalThis);
|
||||
defer expected_type.deref();
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const typeof = expected_type.inMap(JSTypeOfMap) orelse {
|
||||
return globalThis.throwInvalidArguments("toBeTypeOf() requires a valid type string argument ('function', 'object', 'bigint', 'boolean', 'number', 'string', 'symbol', 'undefined')", .{});
|
||||
};
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
var whatIsTheType: []const u8 = "";
|
||||
|
||||
// Checking for function/class should be done before everything else, or it will fail.
|
||||
if (value.isCallable()) {
|
||||
whatIsTheType = "function";
|
||||
} else if (value.isObject() or value.jsType().isArray() or value.isNull()) {
|
||||
whatIsTheType = "object";
|
||||
} else if (value.isBigInt()) {
|
||||
whatIsTheType = "bigint";
|
||||
} else if (value.isBoolean()) {
|
||||
whatIsTheType = "boolean";
|
||||
} else if (value.isNumber()) {
|
||||
whatIsTheType = "number";
|
||||
} else if (value.jsType().isString()) {
|
||||
whatIsTheType = "string";
|
||||
} else if (value.isSymbol()) {
|
||||
whatIsTheType = "symbol";
|
||||
} else if (value.isUndefined()) {
|
||||
whatIsTheType = "undefined";
|
||||
} else {
|
||||
return globalThis.throw("Internal consistency error: unknown JSValue type", .{});
|
||||
}
|
||||
|
||||
pass = strings.eql(typeof, whatIsTheType);
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received = value.toFmt(&formatter);
|
||||
const expected_str = expected.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBeTypeOf", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Expected type: not <green>{any}<r>\n" ++ "Received type: <red>\"{s}\"<r>\nReceived value: <red>{any}<r>\n", .{ expected_str, whatIsTheType, received });
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBeTypeOf", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Expected type: <green>{any}<r>\n" ++ "Received type: <red>\"{s}\"<r>\nReceived value: <red>{any}<r>\n", .{ expected_str, whatIsTheType, received });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const strings = bun.strings;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
39
src/bun.js/test/expect/toBeUndefined.zig
Normal file
39
src/bun.js/test/expect/toBeUndefined.zig
Normal file
@@ -0,0 +1,39 @@
|
||||
pub fn toBeUndefined(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeUndefined", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
if (value.isUndefined()) pass = true;
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
if (not) {
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeUndefined", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ received_line, .{value_fmt});
|
||||
}
|
||||
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeUndefined", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ received_line, .{value_fmt});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
37
src/bun.js/test/expect/toBeValidDate.zig
Normal file
37
src/bun.js/test/expect/toBeValidDate.zig
Normal file
@@ -0,0 +1,37 @@
|
||||
pub fn toBeValidDate(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeValidDate", "");
|
||||
|
||||
bun.jsc.Expect.active_test_expectation_counter.actual += 1;
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = (value.isDate() and !std.math.isNan(value.getUnixTimestamp()));
|
||||
if (not) pass = !pass;
|
||||
|
||||
if (pass) return thisValue;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toBeValidDate", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toBeValidDate", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Received: <red>{any}<r>\n", .{received});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const std = @import("std");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
69
src/bun.js/test/expect/toBeWithin.zig
Normal file
69
src/bun.js/test/expect/toBeWithin.zig
Normal file
@@ -0,0 +1,69 @@
|
||||
pub fn toBeWithin(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const _arguments = callFrame.arguments_old(2);
|
||||
const arguments = _arguments.ptr[0.._arguments.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toBeWithin() requires 2 arguments", .{});
|
||||
}
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toBeWithin", "<green>start<r><d>, <r><green>end<r>");
|
||||
|
||||
const startValue = arguments[0];
|
||||
startValue.ensureStillAlive();
|
||||
|
||||
if (!startValue.isNumber()) {
|
||||
return globalThis.throw("toBeWithin() requires the first argument to be a number", .{});
|
||||
}
|
||||
|
||||
const endValue = arguments[1];
|
||||
endValue.ensureStillAlive();
|
||||
|
||||
if (!endValue.isNumber()) {
|
||||
return globalThis.throw("toBeWithin() requires the second argument to be a number", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
var pass = value.isNumber();
|
||||
if (pass) {
|
||||
const num = value.asNumber();
|
||||
pass = num >= startValue.asNumber() and num < endValue.asNumber();
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
if (not) pass = !pass;
|
||||
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const start_fmt = startValue.toFmt(&formatter);
|
||||
const end_fmt = endValue.toFmt(&formatter);
|
||||
const received_fmt = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const expected_line = "Expected: not between <green>{any}<r> <d>(inclusive)<r> and <green>{any}<r> <d>(exclusive)<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeWithin", "<green>start<r><d>, <r><green>end<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ start_fmt, end_fmt, received_fmt });
|
||||
}
|
||||
|
||||
const expected_line = "Expected: between <green>{any}<r> <d>(inclusive)<r> and <green>{any}<r> <d>(exclusive)<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toBeWithin", "<green>start<r><d>, <r><green>end<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ start_fmt, end_fmt, received_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
107
src/bun.js/test/expect/toContain.zig
Normal file
107
src/bun.js/test/expect/toContain.zig
Normal file
@@ -0,0 +1,107 @@
|
||||
pub fn toContain(
|
||||
this: *Expect,
|
||||
globalThis: *JSGlobalObject,
|
||||
callFrame: *CallFrame,
|
||||
) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
const thisValue = callFrame.this();
|
||||
const arguments_ = callFrame.arguments_old(1);
|
||||
const arguments = arguments_.slice();
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toContain() takes 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const expected = arguments[0];
|
||||
expected.ensureStillAlive();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toContain", "<green>expected<r>");
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
|
||||
const ExpectedEntry = struct {
|
||||
globalThis: *JSGlobalObject,
|
||||
expected: JSValue,
|
||||
pass: *bool,
|
||||
};
|
||||
|
||||
if (value.jsTypeLoose().isArrayLike()) {
|
||||
var itr = try value.arrayIterator(globalThis);
|
||||
while (try itr.next()) |item| {
|
||||
if (try item.isSameValue(expected, globalThis)) {
|
||||
pass = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (value.isStringLiteral() and expected.isStringLiteral()) {
|
||||
const value_string = try value.toSlice(globalThis, default_allocator);
|
||||
defer value_string.deinit();
|
||||
const expected_string = try expected.toSlice(globalThis, default_allocator);
|
||||
defer expected_string.deinit();
|
||||
|
||||
if (expected_string.len == 0) { // edge case empty string is always contained
|
||||
pass = true;
|
||||
} else if (strings.contains(value_string.slice(), expected_string.slice())) {
|
||||
pass = true;
|
||||
} else if (value_string.len == 0 and expected_string.len == 0) { // edge case two empty strings are true
|
||||
pass = true;
|
||||
}
|
||||
} else if (try value.isIterable(globalThis)) {
|
||||
var expected_entry = ExpectedEntry{
|
||||
.globalThis = globalThis,
|
||||
.expected = expected,
|
||||
.pass = &pass,
|
||||
};
|
||||
try value.forEach(globalThis, &expected_entry, struct {
|
||||
pub fn sameValueIterator(
|
||||
_: *jsc.VM,
|
||||
_: *JSGlobalObject,
|
||||
entry_: ?*anyopaque,
|
||||
item: JSValue,
|
||||
) callconv(.C) void {
|
||||
const entry = bun.cast(*ExpectedEntry, entry_.?);
|
||||
if (item.isSameValue(entry.expected, entry.globalThis) catch return) {
|
||||
entry.pass.* = true;
|
||||
// TODO(perf): break out of the `forEach` when a match is found
|
||||
}
|
||||
}
|
||||
}.sameValueIterator);
|
||||
} else {
|
||||
return globalThis.throw("Received value must be an array type, or both received and expected values must be strings.", .{});
|
||||
}
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
const expected_fmt = expected.toFmt(&formatter);
|
||||
if (not) {
|
||||
const received_fmt = value.toFmt(&formatter);
|
||||
const expected_line = "Expected to not contain: <green>{any}<r>\nReceived: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toContain", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line, .{ expected_fmt, received_fmt });
|
||||
}
|
||||
|
||||
const expected_line = "Expected to contain: <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toContain", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const default_allocator = bun.default_allocator;
|
||||
const strings = bun.strings;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
75
src/bun.js/test/expect/toContainAllKeys.zig
Normal file
75
src/bun.js/test/expect/toContainAllKeys.zig
Normal file
@@ -0,0 +1,75 @@
|
||||
pub fn toContainAllKeys(
|
||||
this: *Expect,
|
||||
globalObject: *JSGlobalObject,
|
||||
callFrame: *CallFrame,
|
||||
) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalObject);
|
||||
const thisValue = callFrame.this();
|
||||
const arguments_ = callFrame.arguments_old(1);
|
||||
const arguments = arguments_.slice();
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalObject.throwInvalidArguments("toContainAllKeys() takes 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const expected = arguments[0];
|
||||
expected.ensureStillAlive();
|
||||
const value: JSValue = try this.getValue(globalObject, thisValue, "toContainAllKeys", "<green>expected<r>");
|
||||
|
||||
if (!expected.jsType().isArray()) {
|
||||
return globalObject.throwInvalidArgumentType("toContainAllKeys", "expected", "array");
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
|
||||
const count = try expected.getLength(globalObject);
|
||||
|
||||
var keys = try value.keys(globalObject);
|
||||
if (try keys.getLength(globalObject) == count) {
|
||||
var itr = try keys.arrayIterator(globalObject);
|
||||
outer: {
|
||||
while (try itr.next()) |item| {
|
||||
var i: u32 = 0;
|
||||
while (i < count) : (i += 1) {
|
||||
const key = try expected.getIndex(globalObject, i);
|
||||
if (try item.jestDeepEquals(key, globalObject)) break;
|
||||
} else break :outer;
|
||||
}
|
||||
pass = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return thisValue;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalObject, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = keys.toFmt(&formatter);
|
||||
const expected_fmt = expected.toFmt(&formatter);
|
||||
if (not) {
|
||||
const received_fmt = keys.toFmt(&formatter);
|
||||
const expected_line = "Expected to not contain all keys: <green>{any}<r>\nReceived: <red>{any}<r>\n";
|
||||
const fmt = "\n\n" ++ expected_line;
|
||||
return this.throw(globalObject, comptime getSignature("toContainAllKeys", "<green>expected<r>", true), fmt, .{ expected_fmt, received_fmt });
|
||||
}
|
||||
|
||||
const expected_line = "Expected to contain all keys: <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const fmt = "\n\n" ++ expected_line ++ received_line;
|
||||
return this.throw(globalObject, comptime getSignature("toContainAllKeys", "<green>expected<r>", false), fmt, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
80
src/bun.js/test/expect/toContainAllValues.zig
Normal file
80
src/bun.js/test/expect/toContainAllValues.zig
Normal file
@@ -0,0 +1,80 @@
|
||||
pub fn toContainAllValues(
|
||||
this: *Expect,
|
||||
globalObject: *JSGlobalObject,
|
||||
callFrame: *CallFrame,
|
||||
) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalObject);
|
||||
const thisValue = callFrame.this();
|
||||
const arguments_ = callFrame.arguments_old(1);
|
||||
const arguments = arguments_.slice();
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalObject.throwInvalidArguments("toContainAllValues() takes 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const expected = arguments[0];
|
||||
if (!expected.jsType().isArray()) {
|
||||
return globalObject.throwInvalidArgumentType("toContainAllValues", "expected", "array");
|
||||
}
|
||||
expected.ensureStillAlive();
|
||||
const value: JSValue = try this.getValue(globalObject, thisValue, "toContainAllValues", "<green>expected<r>");
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
|
||||
if (!value.isUndefinedOrNull()) {
|
||||
var values = try value.values(globalObject);
|
||||
var itr = try expected.arrayIterator(globalObject);
|
||||
const count = try values.getLength(globalObject);
|
||||
const expectedLength = try expected.getLength(globalObject);
|
||||
|
||||
if (count == expectedLength) {
|
||||
while (try itr.next()) |item| {
|
||||
var i: u32 = 0;
|
||||
while (i < count) : (i += 1) {
|
||||
const key = try values.getIndex(globalObject, i);
|
||||
if (try key.jestDeepEquals(item, globalObject)) {
|
||||
pass = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
pass = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return thisValue;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalObject, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
const expected_fmt = expected.toFmt(&formatter);
|
||||
if (not) {
|
||||
const received_fmt = value.toFmt(&formatter);
|
||||
const expected_line = "Expected to not contain all values: <green>{any}<r>\nReceived: <red>{any}<r>\n";
|
||||
const fmt = "\n\n" ++ expected_line;
|
||||
return this.throw(globalObject, comptime getSignature("toContainAllValues", "<green>expected<r>", true), fmt, .{ expected_fmt, received_fmt });
|
||||
}
|
||||
|
||||
const expected_line = "Expected to contain all values: <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const fmt = "\n\n" ++ expected_line ++ received_line;
|
||||
return this.throw(globalObject, comptime getSignature("toContainAllValues", "<green>expected<r>", false), fmt, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
71
src/bun.js/test/expect/toContainAnyKeys.zig
Normal file
71
src/bun.js/test/expect/toContainAnyKeys.zig
Normal file
@@ -0,0 +1,71 @@
|
||||
pub fn toContainAnyKeys(
|
||||
this: *Expect,
|
||||
globalThis: *JSGlobalObject,
|
||||
callFrame: *CallFrame,
|
||||
) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
const thisValue = callFrame.this();
|
||||
const arguments_ = callFrame.arguments_old(1);
|
||||
const arguments = arguments_.slice();
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toContainAnyKeys() takes 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const expected = arguments[0];
|
||||
expected.ensureStillAlive();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toContainAnyKeys", "<green>expected<r>");
|
||||
|
||||
if (!expected.jsType().isArray()) {
|
||||
return globalThis.throwInvalidArgumentType("toContainAnyKeys", "expected", "array");
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
|
||||
const count = try expected.getLength(globalThis);
|
||||
|
||||
var i: u32 = 0;
|
||||
|
||||
while (i < count) : (i += 1) {
|
||||
const key = try expected.getIndex(globalThis, i);
|
||||
|
||||
if (try value.hasOwnPropertyValue(globalThis, key)) {
|
||||
pass = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return thisValue;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
const expected_fmt = expected.toFmt(&formatter);
|
||||
if (not) {
|
||||
const received_fmt = value.toFmt(&formatter);
|
||||
const expected_line = "Expected to not contain: <green>{any}<r>\nReceived: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toContainAnyKeys", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line, .{ expected_fmt, received_fmt });
|
||||
}
|
||||
|
||||
const expected_line = "Expected to contain: <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toContainAnyKeys", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
74
src/bun.js/test/expect/toContainAnyValues.zig
Normal file
74
src/bun.js/test/expect/toContainAnyValues.zig
Normal file
@@ -0,0 +1,74 @@
|
||||
pub fn toContainAnyValues(
|
||||
this: *Expect,
|
||||
globalObject: *JSGlobalObject,
|
||||
callFrame: *CallFrame,
|
||||
) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalObject);
|
||||
const thisValue = callFrame.this();
|
||||
const arguments_ = callFrame.arguments_old(1);
|
||||
const arguments = arguments_.slice();
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalObject.throwInvalidArguments("toContainAnyValues() takes 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const expected = arguments[0];
|
||||
if (!expected.jsType().isArray()) {
|
||||
return globalObject.throwInvalidArgumentType("toContainAnyValues", "expected", "array");
|
||||
}
|
||||
expected.ensureStillAlive();
|
||||
const value: JSValue = try this.getValue(globalObject, thisValue, "toContainAnyValues", "<green>expected<r>");
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
|
||||
if (!value.isUndefinedOrNull()) {
|
||||
var values = try value.values(globalObject);
|
||||
var itr = try expected.arrayIterator(globalObject);
|
||||
const count = try values.getLength(globalObject);
|
||||
|
||||
outer: while (try itr.next()) |item| {
|
||||
var i: u32 = 0;
|
||||
while (i < count) : (i += 1) {
|
||||
const key = try values.getIndex(globalObject, i);
|
||||
if (try key.jestDeepEquals(item, globalObject)) {
|
||||
pass = true;
|
||||
break :outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return thisValue;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalObject, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
const expected_fmt = expected.toFmt(&formatter);
|
||||
if (not) {
|
||||
const received_fmt = value.toFmt(&formatter);
|
||||
const expected_line = "Expected to not contain any of the following values: <green>{any}<r>\nReceived: <red>{any}<r>\n";
|
||||
const fmt = "\n\n" ++ expected_line;
|
||||
return this.throw(globalObject, comptime getSignature("toContainAnyValues", "<green>expected<r>", true), fmt, .{ expected_fmt, received_fmt });
|
||||
}
|
||||
|
||||
const expected_line = "Expected to contain any of the following values: <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const fmt = "\n\n" ++ expected_line ++ received_line;
|
||||
return this.throw(globalObject, comptime getSignature("toContainAnyValues", "<green>expected<r>", false), fmt, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
113
src/bun.js/test/expect/toContainEqual.zig
Normal file
113
src/bun.js/test/expect/toContainEqual.zig
Normal file
@@ -0,0 +1,113 @@
|
||||
pub fn toContainEqual(
|
||||
this: *Expect,
|
||||
globalThis: *JSGlobalObject,
|
||||
callFrame: *CallFrame,
|
||||
) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
const thisValue = callFrame.this();
|
||||
const arguments_ = callFrame.arguments_old(1);
|
||||
const arguments = arguments_.slice();
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toContainEqual() takes 1 argument", .{});
|
||||
}
|
||||
|
||||
bun.jsc.Expect.active_test_expectation_counter.actual += 1;
|
||||
|
||||
const expected = arguments[0];
|
||||
expected.ensureStillAlive();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toContainEqual", "<green>expected<r>");
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
|
||||
const ExpectedEntry = struct {
|
||||
globalThis: *JSGlobalObject,
|
||||
expected: JSValue,
|
||||
pass: *bool,
|
||||
};
|
||||
|
||||
const value_type = value.jsType();
|
||||
const expected_type = expected.jsType();
|
||||
|
||||
if (value_type.isArrayLike()) {
|
||||
var itr = try value.arrayIterator(globalThis);
|
||||
while (try itr.next()) |item| {
|
||||
if (try item.jestDeepEquals(expected, globalThis)) {
|
||||
pass = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (value_type.isStringLike() and expected_type.isStringLike()) {
|
||||
if (expected_type.isStringObjectLike() and value_type.isString()) pass = false else {
|
||||
const value_string = try value.toSliceOrNull(globalThis);
|
||||
defer value_string.deinit();
|
||||
const expected_string = try expected.toSliceOrNull(globalThis);
|
||||
defer expected_string.deinit();
|
||||
|
||||
// jest does not have a `typeof === "string"` check for `toContainEqual`.
|
||||
// it immediately spreads the value into an array.
|
||||
|
||||
var expected_codepoint_cursor = strings.CodepointIterator.Cursor{};
|
||||
var expected_iter = strings.CodepointIterator.init(expected_string.slice());
|
||||
_ = expected_iter.next(&expected_codepoint_cursor);
|
||||
|
||||
pass = if (expected_iter.next(&expected_codepoint_cursor))
|
||||
false
|
||||
else
|
||||
strings.indexOf(value_string.slice(), expected_string.slice()) != null;
|
||||
}
|
||||
} else if (try value.isIterable(globalThis)) {
|
||||
var expected_entry = ExpectedEntry{
|
||||
.globalThis = globalThis,
|
||||
.expected = expected,
|
||||
.pass = &pass,
|
||||
};
|
||||
try value.forEach(globalThis, &expected_entry, struct {
|
||||
pub fn deepEqualsIterator(
|
||||
_: *jsc.VM,
|
||||
_: *JSGlobalObject,
|
||||
entry_: ?*anyopaque,
|
||||
item: JSValue,
|
||||
) callconv(.C) void {
|
||||
const entry = bun.cast(*ExpectedEntry, entry_.?);
|
||||
if (item.jestDeepEquals(entry.expected, entry.globalThis) catch return) {
|
||||
entry.pass.* = true;
|
||||
// TODO(perf): break out of the `forEach` when a match is found
|
||||
}
|
||||
}
|
||||
}.deepEqualsIterator);
|
||||
} else {
|
||||
return globalThis.throw("Received value must be an array type, or both received and expected values must be strings.", .{});
|
||||
}
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return thisValue;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
const expected_fmt = expected.toFmt(&formatter);
|
||||
if (not) {
|
||||
const expected_line = "Expected to not contain: <green>{any}<r>\n";
|
||||
const signature = comptime getSignature("toContainEqual", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line, .{expected_fmt});
|
||||
}
|
||||
|
||||
const expected_line = "Expected to contain: <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toContainEqual", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const strings = bun.strings;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
59
src/bun.js/test/expect/toContainKey.zig
Normal file
59
src/bun.js/test/expect/toContainKey.zig
Normal file
@@ -0,0 +1,59 @@
|
||||
pub fn toContainKey(
|
||||
this: *Expect,
|
||||
globalThis: *JSGlobalObject,
|
||||
callFrame: *CallFrame,
|
||||
) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
const thisValue = callFrame.this();
|
||||
const arguments_ = callFrame.arguments_old(1);
|
||||
const arguments = arguments_.slice();
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toContainKey() takes 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const expected = arguments[0];
|
||||
expected.ensureStillAlive();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toContainKey", "<green>expected<r>");
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
|
||||
const not = this.flags.not;
|
||||
if (!value.isObject()) {
|
||||
return globalThis.throwInvalidArguments("Expected value must be an object\nReceived: {}", .{value.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
var pass = try value.hasOwnPropertyValue(globalThis, expected);
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return thisValue;
|
||||
|
||||
// handle failure
|
||||
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
const expected_fmt = expected.toFmt(&formatter);
|
||||
if (not) {
|
||||
const received_fmt = value.toFmt(&formatter);
|
||||
const expected_line = "Expected to not contain: <green>{any}<r>\nReceived: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toContainKey", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line, .{ expected_fmt, received_fmt });
|
||||
}
|
||||
|
||||
const expected_line = "Expected to contain: <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toContainKey", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
76
src/bun.js/test/expect/toContainKeys.zig
Normal file
76
src/bun.js/test/expect/toContainKeys.zig
Normal file
@@ -0,0 +1,76 @@
|
||||
pub fn toContainKeys(
|
||||
this: *Expect,
|
||||
globalThis: *JSGlobalObject,
|
||||
callFrame: *CallFrame,
|
||||
) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
const thisValue = callFrame.this();
|
||||
const arguments_ = callFrame.arguments_old(1);
|
||||
const arguments = arguments_.slice();
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toContainKeys() takes 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const expected = arguments[0];
|
||||
expected.ensureStillAlive();
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toContainKeys", "<green>expected<r>");
|
||||
|
||||
if (!expected.jsType().isArray()) {
|
||||
return globalThis.throwInvalidArgumentType("toContainKeys", "expected", "array");
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = brk: {
|
||||
const count = try expected.getLength(globalThis);
|
||||
|
||||
// jest-extended checks for truthiness before calling hasOwnProperty
|
||||
// https://github.com/jest-community/jest-extended/blob/711fdcc54d68c2b2c1992c7cfbdf0d0bd6be0f4d/src/matchers/toContainKeys.js#L1-L6
|
||||
if (!value.toBoolean()) break :brk count == 0;
|
||||
|
||||
var i: u32 = 0;
|
||||
|
||||
while (i < count) : (i += 1) {
|
||||
const key = try expected.getIndex(globalThis, i);
|
||||
|
||||
if (!try value.hasOwnPropertyValue(globalThis, key)) {
|
||||
break :brk false;
|
||||
}
|
||||
}
|
||||
|
||||
break :brk true;
|
||||
};
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return thisValue;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
const expected_fmt = expected.toFmt(&formatter);
|
||||
if (not) {
|
||||
const received_fmt = value.toFmt(&formatter);
|
||||
const expected_line = "Expected to not contain: <green>{any}<r>\nReceived: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toContainKeys", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line, .{ expected_fmt, received_fmt });
|
||||
}
|
||||
|
||||
const expected_line = "Expected to contain: <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toContainKeys", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
65
src/bun.js/test/expect/toContainValue.zig
Normal file
65
src/bun.js/test/expect/toContainValue.zig
Normal file
@@ -0,0 +1,65 @@
|
||||
pub fn toContainValue(
|
||||
this: *Expect,
|
||||
globalObject: *JSGlobalObject,
|
||||
callFrame: *CallFrame,
|
||||
) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalObject);
|
||||
const thisValue = callFrame.this();
|
||||
const arguments_ = callFrame.arguments_old(1);
|
||||
const arguments = arguments_.slice();
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalObject.throwInvalidArguments("toContainValue() takes 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const expected = arguments[0];
|
||||
expected.ensureStillAlive();
|
||||
const value: JSValue = try this.getValue(globalObject, thisValue, "toContainValue", "<green>expected<r>");
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
|
||||
if (!value.isUndefinedOrNull()) {
|
||||
const values = try value.values(globalObject);
|
||||
var itr = try values.arrayIterator(globalObject);
|
||||
while (try itr.next()) |item| {
|
||||
if (try item.jestDeepEquals(expected, globalObject)) {
|
||||
pass = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return thisValue;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalObject, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
const expected_fmt = expected.toFmt(&formatter);
|
||||
if (not) {
|
||||
const received_fmt = value.toFmt(&formatter);
|
||||
const expected_line = "Expected to not contain: <green>{any}<r>\nReceived: <red>{any}<r>\n";
|
||||
const fmt = "\n\n" ++ expected_line;
|
||||
return this.throw(globalObject, comptime getSignature("toContainValue", "<green>expected<r>", true), fmt, .{ expected_fmt, received_fmt });
|
||||
}
|
||||
|
||||
const expected_line = "Expected to contain: <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const fmt = "\n\n" ++ expected_line ++ received_line;
|
||||
return this.throw(globalObject, comptime getSignature("toContainValue", "<green>expected<r>", false), fmt, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
74
src/bun.js/test/expect/toContainValues.zig
Normal file
74
src/bun.js/test/expect/toContainValues.zig
Normal file
@@ -0,0 +1,74 @@
|
||||
pub fn toContainValues(
|
||||
this: *Expect,
|
||||
globalObject: *JSGlobalObject,
|
||||
callFrame: *CallFrame,
|
||||
) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalObject);
|
||||
const thisValue = callFrame.this();
|
||||
const arguments_ = callFrame.arguments_old(1);
|
||||
const arguments = arguments_.slice();
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalObject.throwInvalidArguments("toContainValues() takes 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const expected = arguments[0];
|
||||
if (!expected.jsType().isArray()) {
|
||||
return globalObject.throwInvalidArgumentType("toContainValues", "expected", "array");
|
||||
}
|
||||
expected.ensureStillAlive();
|
||||
const value: JSValue = try this.getValue(globalObject, thisValue, "toContainValues", "<green>expected<r>");
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = true;
|
||||
|
||||
if (!value.isUndefinedOrNull()) {
|
||||
const values = try value.values(globalObject);
|
||||
var itr = try expected.arrayIterator(globalObject);
|
||||
const count = try values.getLength(globalObject);
|
||||
|
||||
while (try itr.next()) |item| {
|
||||
var i: u32 = 0;
|
||||
while (i < count) : (i += 1) {
|
||||
const key = try values.getIndex(globalObject, i);
|
||||
if (try key.jestDeepEquals(item, globalObject)) break;
|
||||
} else {
|
||||
pass = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return thisValue;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalObject, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
const expected_fmt = expected.toFmt(&formatter);
|
||||
if (not) {
|
||||
const received_fmt = value.toFmt(&formatter);
|
||||
const expected_line = "Expected to not contain: <green>{any}<r>\nReceived: <red>{any}<r>\n";
|
||||
const fmt = "\n\n" ++ expected_line;
|
||||
return this.throw(globalObject, comptime getSignature("toContainValues", "<green>expected<r>", true), fmt, .{ expected_fmt, received_fmt });
|
||||
}
|
||||
|
||||
const expected_line = "Expected to contain: <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const fmt = "\n\n" ++ expected_line ++ received_line;
|
||||
return this.throw(globalObject, comptime getSignature("toContainValues", "<green>expected<r>", false), fmt, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
65
src/bun.js/test/expect/toEndWith.zig
Normal file
65
src/bun.js/test/expect/toEndWith.zig
Normal file
@@ -0,0 +1,65 @@
|
||||
pub fn toEndWith(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const arguments_ = callFrame.arguments_old(1);
|
||||
const arguments = arguments_.slice();
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toEndWith() requires 1 argument", .{});
|
||||
}
|
||||
|
||||
const expected = arguments[0];
|
||||
expected.ensureStillAlive();
|
||||
|
||||
if (!expected.isString()) {
|
||||
return globalThis.throw("toEndWith() requires the first argument to be a string", .{});
|
||||
}
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toEndWith", "<green>expected<r>");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
var pass = value.isString();
|
||||
if (pass) {
|
||||
const value_string = try value.toSliceOrNull(globalThis);
|
||||
defer value_string.deinit();
|
||||
const expected_string = try expected.toSliceOrNull(globalThis);
|
||||
defer expected_string.deinit();
|
||||
pass = strings.endsWith(value_string.slice(), expected_string.slice()) or expected_string.len == 0;
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
if (not) pass = !pass;
|
||||
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
const expected_fmt = expected.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const expected_line = "Expected to not end with: <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toEndWith", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const expected_line = "Expected to end with: <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toEndWith", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const strings = bun.strings;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
50
src/bun.js/test/expect/toEqual.zig
Normal file
50
src/bun.js/test/expect/toEqual.zig
Normal file
@@ -0,0 +1,50 @@
|
||||
pub fn toEqual(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const _arguments = callFrame.arguments_old(1);
|
||||
const arguments: []const JSValue = _arguments.ptr[0.._arguments.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toEqual() requires 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const expected = arguments[0];
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toEqual", "<green>expected<r>");
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = try value.jestDeepEquals(expected, globalThis);
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
const diff_formatter = DiffFormatter{
|
||||
.received = value,
|
||||
.expected = expected,
|
||||
.globalThis = globalThis,
|
||||
.not = not,
|
||||
};
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toEqual", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n{any}\n", .{diff_formatter});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toEqual", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n{any}\n", .{diff_formatter});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const DiffFormatter = @import("../diff_format.zig").DiffFormatter;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
92
src/bun.js/test/expect/toEqualIgnoringWhitespace.zig
Normal file
92
src/bun.js/test/expect/toEqualIgnoringWhitespace.zig
Normal file
@@ -0,0 +1,92 @@
|
||||
pub fn toEqualIgnoringWhitespace(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const _arguments = callFrame.arguments_old(1);
|
||||
const arguments: []const JSValue = _arguments.ptr[0.._arguments.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toEqualIgnoringWhitespace() requires 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const expected = arguments[0];
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toEqualIgnoringWhitespace", "<green>expected<r>");
|
||||
|
||||
if (!expected.isString()) {
|
||||
return globalThis.throw("toEqualIgnoringWhitespace() requires argument to be a string", .{});
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = value.isString() and expected.isString();
|
||||
|
||||
if (pass) {
|
||||
const value_slice = try value.toSlice(globalThis, default_allocator);
|
||||
defer value_slice.deinit();
|
||||
const expected_slice = try expected.toSlice(globalThis, default_allocator);
|
||||
defer expected_slice.deinit();
|
||||
|
||||
const value_utf8 = value_slice.slice();
|
||||
const expected_utf8 = expected_slice.slice();
|
||||
|
||||
var left: usize = 0;
|
||||
var right: usize = 0;
|
||||
|
||||
// Skip leading whitespaces
|
||||
while (left < value_utf8.len and std.ascii.isWhitespace(value_utf8[left])) left += 1;
|
||||
while (right < expected_utf8.len and std.ascii.isWhitespace(expected_utf8[right])) right += 1;
|
||||
|
||||
while (left < value_utf8.len and right < expected_utf8.len) {
|
||||
const left_char = value_utf8[left];
|
||||
const right_char = expected_utf8[right];
|
||||
|
||||
if (left_char != right_char) {
|
||||
pass = false;
|
||||
break;
|
||||
}
|
||||
|
||||
left += 1;
|
||||
right += 1;
|
||||
|
||||
// Skip trailing whitespaces
|
||||
while (left < value_utf8.len and std.ascii.isWhitespace(value_utf8[left])) left += 1;
|
||||
while (right < expected_utf8.len and std.ascii.isWhitespace(expected_utf8[right])) right += 1;
|
||||
}
|
||||
|
||||
if (left < value_utf8.len or right < expected_utf8.len) {
|
||||
pass = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const expected_fmt = expected.toFmt(&formatter);
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toEqualIgnoringWhitespace", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Expected: not <green>{any}<r>\n" ++ "Received: <red>{any}<r>\n", .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toEqualIgnoringWhitespace", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Expected: <green>{any}<r>\n" ++ "Received: <red>{any}<r>\n", .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const bun = @import("bun");
|
||||
const default_allocator = bun.default_allocator;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
47
src/bun.js/test/expect/toHaveBeenCalled.zig
Normal file
47
src/bun.js/test/expect/toHaveBeenCalled.zig
Normal file
@@ -0,0 +1,47 @@
|
||||
pub fn toHaveBeenCalled(this: *Expect, globalThis: *JSGlobalObject, callframe: *CallFrame) bun.JSError!JSValue {
|
||||
jsc.markBinding(@src());
|
||||
const thisValue = callframe.this();
|
||||
const firstArgument = callframe.argumentsAsArray(1)[0];
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
if (!firstArgument.isUndefined()) {
|
||||
return globalThis.throwInvalidArguments("toHaveBeenCalled() must not have an argument", .{});
|
||||
}
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toHaveBeenCalled", "");
|
||||
|
||||
const calls = try bun.cpp.JSMockFunction__getCalls(globalThis, value);
|
||||
incrementExpectCallCounter();
|
||||
if (!calls.jsType().isArray()) {
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
return globalThis.throw("Expected value must be a mock function: {any}", .{value.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
const calls_length = try calls.getLength(globalThis);
|
||||
var pass = calls_length > 0;
|
||||
|
||||
const not = this.flags.not;
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toHaveBeenCalled", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Expected number of calls: <green>0<r>\n" ++ "Received number of calls: <red>{any}<r>\n", .{calls_length});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toHaveBeenCalled", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Expected number of calls: \\>= <green>1<r>\n" ++ "Received number of calls: <red>{any}<r>\n", .{calls_length});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
43
src/bun.js/test/expect/toHaveBeenCalledOnce.zig
Normal file
43
src/bun.js/test/expect/toHaveBeenCalledOnce.zig
Normal file
@@ -0,0 +1,43 @@
|
||||
pub fn toHaveBeenCalledOnce(this: *Expect, globalThis: *JSGlobalObject, callframe: *CallFrame) bun.JSError!JSValue {
|
||||
jsc.markBinding(@src());
|
||||
|
||||
const thisValue = callframe.this();
|
||||
defer this.postMatch(globalThis);
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toHaveBeenCalledOnce", "<green>expected<r>");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const calls = try bun.cpp.JSMockFunction__getCalls(globalThis, value);
|
||||
if (!calls.jsType().isArray()) {
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
return globalThis.throw("Expected value must be a mock function: {any}", .{value.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
const calls_length = try calls.getLength(globalThis);
|
||||
var pass = calls_length == 1;
|
||||
|
||||
const not = this.flags.not;
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toHaveBeenCalledOnce", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Expected number of calls: not <green>1<r>\n" ++ "Received number of calls: <red>{d}<r>\n", .{calls_length});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toHaveBeenCalledOnce", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Expected number of calls: <green>1<r>\n" ++ "Received number of calls: <red>{d}<r>\n", .{calls_length});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
50
src/bun.js/test/expect/toHaveBeenCalledTimes.zig
Normal file
50
src/bun.js/test/expect/toHaveBeenCalledTimes.zig
Normal file
@@ -0,0 +1,50 @@
|
||||
pub fn toHaveBeenCalledTimes(this: *Expect, globalThis: *JSGlobalObject, callframe: *CallFrame) bun.JSError!JSValue {
|
||||
jsc.markBinding(@src());
|
||||
|
||||
const thisValue = callframe.this();
|
||||
const arguments_ = callframe.arguments_old(1);
|
||||
const arguments: []const JSValue = arguments_.slice();
|
||||
defer this.postMatch(globalThis);
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toHaveBeenCalledTimes", "<green>expected<r>");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const calls = try bun.cpp.JSMockFunction__getCalls(globalThis, value);
|
||||
if (!calls.jsType().isArray()) {
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
return globalThis.throw("Expected value must be a mock function: {any}", .{value.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
if (arguments.len < 1 or !arguments[0].isUInt32AsAnyInt()) {
|
||||
return globalThis.throwInvalidArguments("toHaveBeenCalledTimes() requires 1 non-negative integer argument", .{});
|
||||
}
|
||||
|
||||
const times = try arguments[0].coerce(i32, globalThis);
|
||||
|
||||
var pass = @as(i32, @intCast(try calls.getLength(globalThis))) == times;
|
||||
|
||||
const not = this.flags.not;
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toHaveBeenCalledTimes", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Expected number of calls: not <green>{any}<r>\n" ++ "Received number of calls: <red>{any}<r>\n", .{ times, calls.getLength(globalThis) });
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toHaveBeenCalledTimes", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Expected number of calls: <green>{any}<r>\n" ++ "Received number of calls: <red>{any}<r>\n", .{ times, calls.getLength(globalThis) });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
129
src/bun.js/test/expect/toHaveBeenCalledWith.zig
Normal file
129
src/bun.js/test/expect/toHaveBeenCalledWith.zig
Normal file
@@ -0,0 +1,129 @@
|
||||
pub fn toHaveBeenCalledWith(this: *Expect, globalThis: *JSGlobalObject, callframe: *CallFrame) bun.JSError!JSValue {
|
||||
jsc.markBinding(@src());
|
||||
|
||||
const thisValue = callframe.this();
|
||||
const arguments = callframe.arguments();
|
||||
defer this.postMatch(globalThis);
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toHaveBeenCalledWith", "<green>...expected<r>");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const calls = try bun.cpp.JSMockFunction__getCalls(globalThis, value);
|
||||
if (!calls.jsType().isArray()) {
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
return this.throw(globalThis, comptime getSignature("toHaveBeenCalledWith", "<green>...expected<r>", false), "\n\nMatcher error: <red>received<r> value must be a mock function\nReceived: {any}", .{value.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
var pass = false;
|
||||
|
||||
const calls_count = @as(u32, @intCast(try calls.getLength(globalThis)));
|
||||
if (calls_count > 0) {
|
||||
var itr = try calls.arrayIterator(globalThis);
|
||||
while (try itr.next()) |callItem| {
|
||||
if (callItem == .zero or !callItem.jsType().isArray()) {
|
||||
// This indicates a malformed mock object, which is an internal error.
|
||||
return globalThis.throw("Internal error: expected mock call item to be an array of arguments.", .{});
|
||||
}
|
||||
|
||||
if (try callItem.getLength(globalThis) != arguments.len) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var callItr = try callItem.arrayIterator(globalThis);
|
||||
var match = true;
|
||||
while (try callItr.next()) |callArg| {
|
||||
if (!try callArg.jestDeepEquals(arguments[callItr.i - 1], globalThis)) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
pass = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pass != this.flags.not) {
|
||||
return .js_undefined;
|
||||
}
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
|
||||
const expected_args_js_array = try JSValue.createEmptyArray(globalThis, arguments.len);
|
||||
for (arguments, 0..) |arg, i| {
|
||||
try expected_args_js_array.putIndex(globalThis, @intCast(i), arg);
|
||||
}
|
||||
expected_args_js_array.ensureStillAlive();
|
||||
|
||||
if (this.flags.not) {
|
||||
const signature = comptime getSignature("toHaveBeenCalledWith", "<green>...expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\nExpected mock function not to have been called with: <green>{any}<r>\nBut it was.", .{
|
||||
expected_args_js_array.toFmt(&formatter),
|
||||
});
|
||||
}
|
||||
const signature = comptime getSignature("toHaveBeenCalledWith", "<green>...expected<r>", false);
|
||||
|
||||
if (calls_count == 0) {
|
||||
return this.throw(globalThis, signature, "\n\nExpected: <green>{any}<r>\nBut it was not called.", .{
|
||||
expected_args_js_array.toFmt(&formatter),
|
||||
});
|
||||
}
|
||||
|
||||
// If there's only one call, provide a nice diff.
|
||||
if (calls_count == 1) {
|
||||
const received_call_args = try calls.getIndex(globalThis, 0);
|
||||
const diff_format = DiffFormatter{
|
||||
.expected = expected_args_js_array,
|
||||
.received = received_call_args,
|
||||
.globalThis = globalThis,
|
||||
.not = false,
|
||||
};
|
||||
return this.throw(globalThis, signature, "\n\n{any}\n", .{diff_format});
|
||||
}
|
||||
|
||||
// If there are multiple calls, list them all to help debugging.
|
||||
const list_formatter = mock.AllCallsWithArgsFormatter{
|
||||
.globalThis = globalThis,
|
||||
.calls = calls,
|
||||
.formatter = &formatter,
|
||||
};
|
||||
|
||||
const fmt =
|
||||
\\ <green>Expected<r>: {any}
|
||||
\\ <red>Received<r>:
|
||||
\\{any}
|
||||
\\
|
||||
\\ Number of calls: {d}
|
||||
;
|
||||
|
||||
switch (Output.enable_ansi_colors) {
|
||||
inline else => |colors| {
|
||||
return this.throw(globalThis, signature, Output.prettyFmt("\n\n" ++ fmt ++ "\n", colors), .{
|
||||
expected_args_js_array.toFmt(&formatter),
|
||||
list_formatter,
|
||||
calls_count,
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const DiffFormatter = @import("../diff_format.zig").DiffFormatter;
|
||||
|
||||
const bun = @import("bun");
|
||||
const Output = bun.Output;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
const mock = bun.jsc.Expect.mock;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
92
src/bun.js/test/expect/toHaveBeenLastCalledWith.zig
Normal file
92
src/bun.js/test/expect/toHaveBeenLastCalledWith.zig
Normal file
@@ -0,0 +1,92 @@
|
||||
pub fn toHaveBeenLastCalledWith(this: *Expect, globalThis: *JSGlobalObject, callframe: *CallFrame) bun.JSError!JSValue {
|
||||
jsc.markBinding(@src());
|
||||
|
||||
const thisValue = callframe.this();
|
||||
const arguments = callframe.arguments();
|
||||
defer this.postMatch(globalThis);
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toHaveBeenLastCalledWith", "<green>...expected<r>");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const calls = try bun.cpp.JSMockFunction__getCalls(globalThis, value);
|
||||
if (!calls.jsType().isArray()) {
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
return this.throw(globalThis, comptime getSignature("toHaveBeenLastCalledWith", "<green>...expected<r>", false), "\n\nMatcher error: <red>received<r> value must be a mock function\nReceived: {any}", .{value.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
const totalCalls: u32 = @truncate(try calls.getLength(globalThis));
|
||||
var lastCallValue: JSValue = .zero;
|
||||
|
||||
var pass = totalCalls > 0;
|
||||
|
||||
if (pass) {
|
||||
lastCallValue = try calls.getIndex(globalThis, totalCalls - 1);
|
||||
|
||||
if (!lastCallValue.jsType().isArray()) {
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
return globalThis.throw("Expected value must be a mock function with calls: {any}", .{value.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
if (try lastCallValue.getLength(globalThis) != arguments.len) {
|
||||
pass = false;
|
||||
} else {
|
||||
var itr = try lastCallValue.arrayIterator(globalThis);
|
||||
while (try itr.next()) |callArg| {
|
||||
if (!try callArg.jestDeepEquals(arguments[itr.i - 1], globalThis)) {
|
||||
pass = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pass != this.flags.not) {
|
||||
return .js_undefined;
|
||||
}
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
|
||||
const expected_args_js_array = try JSValue.createEmptyArray(globalThis, arguments.len);
|
||||
for (arguments, 0..) |arg, i| {
|
||||
try expected_args_js_array.putIndex(globalThis, @intCast(i), arg);
|
||||
}
|
||||
expected_args_js_array.ensureStillAlive();
|
||||
|
||||
if (this.flags.not) {
|
||||
const signature = comptime getSignature("toHaveBeenLastCalledWith", "<green>...expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\nExpected last call not to be with: <green>{any}<r>\nBut it was.", .{
|
||||
expected_args_js_array.toFmt(&formatter),
|
||||
});
|
||||
}
|
||||
const signature = comptime getSignature("toHaveBeenLastCalledWith", "<green>...expected<r>", false);
|
||||
|
||||
if (totalCalls == 0) {
|
||||
return this.throw(globalThis, signature, "\n\nExpected: <green>{any}<r>\nBut it was not called.", .{
|
||||
expected_args_js_array.toFmt(&formatter),
|
||||
});
|
||||
}
|
||||
|
||||
const diff_format = DiffFormatter{
|
||||
.expected = expected_args_js_array,
|
||||
.received = lastCallValue,
|
||||
.globalThis = globalThis,
|
||||
.not = false,
|
||||
};
|
||||
return this.throw(globalThis, signature, "\n\n{any}\n", .{diff_format});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const DiffFormatter = @import("../diff_format.zig").DiffFormatter;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
106
src/bun.js/test/expect/toHaveBeenNthCalledWith.zig
Normal file
106
src/bun.js/test/expect/toHaveBeenNthCalledWith.zig
Normal file
@@ -0,0 +1,106 @@
|
||||
pub fn toHaveBeenNthCalledWith(this: *Expect, globalThis: *JSGlobalObject, callframe: *CallFrame) bun.JSError!JSValue {
|
||||
jsc.markBinding(@src());
|
||||
|
||||
const thisValue = callframe.this();
|
||||
const arguments = callframe.arguments();
|
||||
defer this.postMatch(globalThis);
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toHaveBeenNthCalledWith", "<green>n<r>, <green>...expected<r>");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const calls = try bun.cpp.JSMockFunction__getCalls(globalThis, value);
|
||||
if (!calls.jsType().isArray()) {
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
return this.throw(globalThis, comptime getSignature("toHaveBeenNthCalledWith", "<green>n<r>, <green>...expected<r>", false), "\n\nMatcher error: <red>received<r> value must be a mock function\nReceived: {any}", .{value.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
if (arguments.len == 0 or !arguments[0].isAnyInt()) {
|
||||
return globalThis.throwInvalidArguments("toHaveBeenNthCalledWith() requires a positive integer as the first argument", .{});
|
||||
}
|
||||
const nthCallNumI32 = arguments[0].toInt32();
|
||||
|
||||
if (nthCallNumI32 <= 0) {
|
||||
return globalThis.throwInvalidArguments("toHaveBeenNthCalledWith() first argument must be a positive integer", .{});
|
||||
}
|
||||
const nthCallNum: u32 = @intCast(nthCallNumI32);
|
||||
|
||||
const totalCalls = @as(u32, @intCast(try calls.getLength(globalThis)));
|
||||
var pass = totalCalls >= nthCallNum;
|
||||
var nthCallValue: JSValue = .zero;
|
||||
|
||||
if (pass) {
|
||||
nthCallValue = try calls.getIndex(globalThis, nthCallNum - 1);
|
||||
const expected_args = arguments[1..];
|
||||
|
||||
if (!nthCallValue.jsType().isArray()) {
|
||||
return globalThis.throw("Internal error: expected mock call item to be an array of arguments.", .{});
|
||||
}
|
||||
|
||||
if (try nthCallValue.getLength(globalThis) != expected_args.len) {
|
||||
pass = false;
|
||||
} else {
|
||||
var itr = try nthCallValue.arrayIterator(globalThis);
|
||||
while (try itr.next()) |callArg| {
|
||||
if (!try callArg.jestDeepEquals(expected_args[itr.i - 1], globalThis)) {
|
||||
pass = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pass != this.flags.not) {
|
||||
return .js_undefined;
|
||||
}
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
|
||||
const expected_args_slice = arguments[1..];
|
||||
const expected_args_js_array = try JSValue.createEmptyArray(globalThis, expected_args_slice.len);
|
||||
for (expected_args_slice, 0..) |arg, i| {
|
||||
try expected_args_js_array.putIndex(globalThis, @intCast(i), arg);
|
||||
}
|
||||
expected_args_js_array.ensureStillAlive();
|
||||
|
||||
if (this.flags.not) {
|
||||
const signature = comptime getSignature("toHaveBeenNthCalledWith", "<green>n<r>, <green>...expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\nExpected call #{d} not to be with: <green>{any}<r>\nBut it was.", .{
|
||||
nthCallNum,
|
||||
expected_args_js_array.toFmt(&formatter),
|
||||
});
|
||||
}
|
||||
const signature = comptime getSignature("toHaveBeenNthCalledWith", "<green>n<r>, <green>...expected<r>", false);
|
||||
|
||||
// Handle case where function was not called enough times
|
||||
if (totalCalls < nthCallNum) {
|
||||
return this.throw(globalThis, signature, "\n\nThe mock function was called {d} time{s}, but call {d} was requested.", .{
|
||||
totalCalls,
|
||||
if (totalCalls == 1) "" else "s",
|
||||
nthCallNum,
|
||||
});
|
||||
}
|
||||
|
||||
// The call existed but didn't match. Show a diff.
|
||||
const diff_format = DiffFormatter{
|
||||
.expected = expected_args_js_array,
|
||||
.received = nthCallValue,
|
||||
.globalThis = globalThis,
|
||||
.not = false,
|
||||
};
|
||||
return this.throw(globalThis, signature, "\n\nCall #{d}:\n{any}\n", .{ nthCallNum, diff_format });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const DiffFormatter = @import("../diff_format.zig").DiffFormatter;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
91
src/bun.js/test/expect/toHaveLastReturnedWith.zig
Normal file
91
src/bun.js/test/expect/toHaveLastReturnedWith.zig
Normal file
@@ -0,0 +1,91 @@
|
||||
pub fn toHaveLastReturnedWith(this: *Expect, globalThis: *JSGlobalObject, callframe: *CallFrame) bun.JSError!JSValue {
|
||||
jsc.markBinding(@src());
|
||||
|
||||
const thisValue = callframe.this();
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toHaveBeenLastReturnedWith", "<green>expected<r>");
|
||||
|
||||
const expected = callframe.argumentsAsArray(1)[0];
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const returns = try bun.cpp.JSMockFunction__getReturns(globalThis, value);
|
||||
if (!returns.jsType().isArray()) {
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
return globalThis.throw("Expected value must be a mock function: {any}", .{value.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
const calls_count = @as(u32, @intCast(try returns.getLength(globalThis)));
|
||||
var pass = false;
|
||||
var last_return_value: JSValue = .js_undefined;
|
||||
var last_call_threw = false;
|
||||
var last_error_value: JSValue = .js_undefined;
|
||||
|
||||
if (calls_count > 0) {
|
||||
const last_result = returns.getDirectIndex(globalThis, calls_count - 1);
|
||||
|
||||
if (last_result.isObject()) {
|
||||
const result_type = try last_result.get(globalThis, "type") orelse .js_undefined;
|
||||
if (result_type.isString()) {
|
||||
const type_str = try result_type.toBunString(globalThis);
|
||||
defer type_str.deref();
|
||||
|
||||
if (type_str.eqlComptime("return")) {
|
||||
last_return_value = try last_result.get(globalThis, "value") orelse .js_undefined;
|
||||
|
||||
if (try last_return_value.jestDeepEquals(expected, globalThis)) {
|
||||
pass = true;
|
||||
}
|
||||
} else if (type_str.eqlComptime("throw")) {
|
||||
last_call_threw = true;
|
||||
last_error_value = try last_result.get(globalThis, "value") orelse .js_undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pass != this.flags.not) {
|
||||
return .js_undefined;
|
||||
}
|
||||
|
||||
// Handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
|
||||
const signature = comptime getSignature("toHaveBeenLastReturnedWith", "<green>expected<r>", false);
|
||||
|
||||
if (this.flags.not) {
|
||||
return this.throw(globalThis, comptime getSignature("toHaveBeenLastReturnedWith", "<green>expected<r>", true), "\n\n" ++ "Expected mock function not to have last returned: <green>{any}<r>\n" ++ "But it did.\n", .{expected.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
if (calls_count == 0) {
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "The mock function was not called.", .{});
|
||||
}
|
||||
|
||||
if (last_call_threw) {
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "The last call threw an error: <red>{any}<r>\n", .{last_error_value.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
// Diff if possible
|
||||
if (expected.isString() and last_return_value.isString()) {
|
||||
const diff_format = DiffFormatter{ .expected = expected, .received = last_return_value, .globalThis = globalThis, .not = false };
|
||||
return this.throw(globalThis, signature, "\n\n{any}\n", .{diff_format});
|
||||
}
|
||||
|
||||
return this.throw(globalThis, signature, "\n\nExpected: <green>{any}<r>\nReceived: <red>{any}<r>", .{ expected.toFmt(&formatter), last_return_value.toFmt(&formatter) });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const DiffFormatter = @import("../diff_format.zig").DiffFormatter;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
const mock = bun.jsc.Expect.mock;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
78
src/bun.js/test/expect/toHaveLength.zig
Normal file
78
src/bun.js/test/expect/toHaveLength.zig
Normal file
@@ -0,0 +1,78 @@
|
||||
pub fn toHaveLength(
|
||||
this: *Expect,
|
||||
globalThis: *JSGlobalObject,
|
||||
callframe: *CallFrame,
|
||||
) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
const thisValue = callframe.this();
|
||||
const arguments_ = callframe.arguments_old(1);
|
||||
const arguments = arguments_.slice();
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toHaveLength() takes 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const expected: JSValue = arguments[0];
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toHaveLength", "<green>expected<r>");
|
||||
|
||||
if (!value.isObject() and !value.isString()) {
|
||||
var fmt = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
return globalThis.throw("Received value does not have a length property: {any}", .{value.toFmt(&fmt)});
|
||||
}
|
||||
|
||||
if (!expected.isNumber()) {
|
||||
var fmt = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
return globalThis.throw("Expected value must be a non-negative integer: {any}", .{expected.toFmt(&fmt)});
|
||||
}
|
||||
|
||||
const expected_length: f64 = expected.asNumber();
|
||||
if (@round(expected_length) != expected_length or std.math.isInf(expected_length) or std.math.isNan(expected_length) or expected_length < 0) {
|
||||
var fmt = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
return globalThis.throw("Expected value must be a non-negative integer: {any}", .{expected.toFmt(&fmt)});
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
|
||||
const actual_length = try value.getLengthIfPropertyExistsInternal(globalThis);
|
||||
|
||||
if (actual_length == std.math.inf(f64)) {
|
||||
var fmt = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
return globalThis.throw("Received value does not have a length property: {any}", .{value.toFmt(&fmt)});
|
||||
} else if (std.math.isNan(actual_length)) {
|
||||
return globalThis.throw("Received value has non-number length property: {}", .{actual_length});
|
||||
}
|
||||
|
||||
if (actual_length == expected_length) {
|
||||
pass = true;
|
||||
}
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
if (not) {
|
||||
const expected_line = "Expected length: not <green>{d}<r>\n";
|
||||
const signature = comptime getSignature("toHaveLength", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line, .{expected_length});
|
||||
}
|
||||
|
||||
const expected_line = "Expected length: <green>{d}<r>\n";
|
||||
const received_line = "Received length: <red>{d}<r>\n";
|
||||
const signature = comptime getSignature("toHaveLength", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_length, actual_length });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const std = @import("std");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
100
src/bun.js/test/expect/toHaveNthReturnedWith.zig
Normal file
100
src/bun.js/test/expect/toHaveNthReturnedWith.zig
Normal file
@@ -0,0 +1,100 @@
|
||||
pub fn toHaveNthReturnedWith(this: *Expect, globalThis: *JSGlobalObject, callframe: *CallFrame) bun.JSError!JSValue {
|
||||
jsc.markBinding(@src());
|
||||
const thisValue = callframe.this();
|
||||
defer this.postMatch(globalThis);
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toHaveNthReturnedWith", "<green>n<r>, <green>expected<r>");
|
||||
|
||||
const nth_arg, const expected = callframe.argumentsAsArray(2);
|
||||
|
||||
// Validate n is a number
|
||||
if (!nth_arg.isAnyInt()) {
|
||||
return globalThis.throwInvalidArguments("toHaveNthReturnedWith() first argument must be an integer", .{});
|
||||
}
|
||||
|
||||
const n = nth_arg.toInt32();
|
||||
if (n <= 0) {
|
||||
return globalThis.throwInvalidArguments("toHaveNthReturnedWith() n must be greater than 0", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
const returns = try bun.cpp.JSMockFunction__getReturns(globalThis, value);
|
||||
if (!returns.jsType().isArray()) {
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
return globalThis.throw("Expected value must be a mock function: {any}", .{value.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
const calls_count = @as(u32, @intCast(try returns.getLength(globalThis)));
|
||||
const index = @as(u32, @intCast(n - 1)); // Convert to 0-based index
|
||||
|
||||
var pass = false;
|
||||
var nth_return_value: JSValue = .js_undefined;
|
||||
var nth_call_threw = false;
|
||||
var nth_error_value: JSValue = .js_undefined;
|
||||
var nth_call_exists = false;
|
||||
|
||||
if (index < calls_count) {
|
||||
nth_call_exists = true;
|
||||
const nth_result = returns.getDirectIndex(globalThis, index);
|
||||
if (nth_result.isObject()) {
|
||||
const result_type = try nth_result.get(globalThis, "type") orelse .js_undefined;
|
||||
if (result_type.isString()) {
|
||||
const type_str = try result_type.toBunString(globalThis);
|
||||
defer type_str.deref();
|
||||
if (type_str.eqlComptime("return")) {
|
||||
nth_return_value = try nth_result.get(globalThis, "value") orelse .js_undefined;
|
||||
if (try nth_return_value.jestDeepEquals(expected, globalThis)) {
|
||||
pass = true;
|
||||
}
|
||||
} else if (type_str.eqlComptime("throw")) {
|
||||
nth_call_threw = true;
|
||||
nth_error_value = try nth_result.get(globalThis, "value") orelse .js_undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pass != this.flags.not) {
|
||||
return .js_undefined;
|
||||
}
|
||||
|
||||
// Handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
|
||||
const signature = comptime getSignature("toHaveNthReturnedWith", "<green>n<r>, <green>expected<r>", false);
|
||||
|
||||
if (this.flags.not) {
|
||||
return this.throw(globalThis, comptime getSignature("toHaveNthReturnedWith", "<green>n<r>, <green>expected<r>", true), "\n\n" ++ "Expected mock function not to have returned on call {d}: <green>{any}<r>\n" ++ "But it did.\n", .{ n, expected.toFmt(&formatter) });
|
||||
}
|
||||
|
||||
if (!nth_call_exists) {
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "The mock function was called {d} time{s}, but call {d} was requested.\n", .{ calls_count, if (calls_count == 1) "" else "s", n });
|
||||
}
|
||||
|
||||
if (nth_call_threw) {
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Call {d} threw an error: <red>{any}<r>\n", .{ n, nth_error_value.toFmt(&formatter) });
|
||||
}
|
||||
|
||||
// Diff if possible
|
||||
if (expected.isString() and nth_return_value.isString()) {
|
||||
const diff_format = DiffFormatter{ .expected = expected, .received = nth_return_value, .globalThis = globalThis, .not = false };
|
||||
return this.throw(globalThis, signature, "\n\nCall {d}:\n{any}\n", .{ n, diff_format });
|
||||
}
|
||||
|
||||
return this.throw(globalThis, signature, "\n\nCall {d}:\nExpected: <green>{any}<r>\nReceived: <red>{any}<r>", .{ n, expected.toFmt(&formatter), nth_return_value.toFmt(&formatter) });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const DiffFormatter = @import("../diff_format.zig").DiffFormatter;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
const mock = bun.jsc.Expect.mock;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
102
src/bun.js/test/expect/toHaveProperty.zig
Normal file
102
src/bun.js/test/expect/toHaveProperty.zig
Normal file
@@ -0,0 +1,102 @@
|
||||
pub fn toHaveProperty(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const _arguments = callFrame.arguments_old(2);
|
||||
const arguments: []const JSValue = _arguments.ptr[0.._arguments.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toHaveProperty() requires at least 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const expected_property_path = arguments[0];
|
||||
expected_property_path.ensureStillAlive();
|
||||
const expected_property: ?JSValue = if (arguments.len > 1) arguments[1] else null;
|
||||
if (expected_property) |ev| ev.ensureStillAlive();
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toHaveProperty", "<green>path<r><d>, <r><green>value<r>");
|
||||
|
||||
if (!expected_property_path.isString() and !try expected_property_path.isIterable(globalThis)) {
|
||||
return globalThis.throw("Expected path must be a string or an array", .{});
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
var path_string = ZigString.Empty;
|
||||
try expected_property_path.toZigString(&path_string, globalThis);
|
||||
|
||||
var pass = !value.isUndefinedOrNull();
|
||||
var received_property: JSValue = .zero;
|
||||
|
||||
if (pass) {
|
||||
received_property = try value.getIfPropertyExistsFromPath(globalThis, expected_property_path);
|
||||
pass = received_property != .zero;
|
||||
}
|
||||
|
||||
if (pass and expected_property != null) {
|
||||
pass = try received_property.jestDeepEquals(expected_property.?, globalThis);
|
||||
}
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
if (not) {
|
||||
if (expected_property != null) {
|
||||
const signature = comptime getSignature("toHaveProperty", "<green>path<r><d>, <r><green>value<r>", true);
|
||||
if (received_property != .zero) {
|
||||
return this.throw(globalThis, signature, "\n\nExpected path: <green>{any}<r>\n\nExpected value: not <green>{any}<r>\n", .{
|
||||
expected_property_path.toFmt(&formatter),
|
||||
expected_property.?.toFmt(&formatter),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toHaveProperty", "<green>path<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\nExpected path: not <green>{any}<r>\n\nReceived value: <red>{any}<r>\n", .{
|
||||
expected_property_path.toFmt(&formatter),
|
||||
received_property.toFmt(&formatter),
|
||||
});
|
||||
}
|
||||
|
||||
if (expected_property != null) {
|
||||
const signature = comptime getSignature("toHaveProperty", "<green>path<r><d>, <r><green>value<r>", false);
|
||||
if (received_property != .zero) {
|
||||
// deep equal case
|
||||
const diff_format = DiffFormatter{
|
||||
.received = received_property,
|
||||
.expected = expected_property.?,
|
||||
.globalThis = globalThis,
|
||||
};
|
||||
|
||||
return this.throw(globalThis, signature, "\n\n{any}\n", .{diff_format});
|
||||
}
|
||||
|
||||
const fmt = "\n\nExpected path: <green>{any}<r>\n\nExpected value: <green>{any}<r>\n\n" ++
|
||||
"Unable to find property\n";
|
||||
return this.throw(globalThis, signature, fmt, .{
|
||||
expected_property_path.toFmt(&formatter),
|
||||
expected_property.?.toFmt(&formatter),
|
||||
});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toHaveProperty", "<green>path<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\nExpected path: <green>{any}<r>\n\nUnable to find property\n", .{expected_property_path.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
const DiffFormatter = @import("../diff_format.zig").DiffFormatter;
|
||||
|
||||
const bun = @import("bun");
|
||||
const ZigString = bun.ZigString;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
92
src/bun.js/test/expect/toHaveReturned.zig
Normal file
92
src/bun.js/test/expect/toHaveReturned.zig
Normal file
@@ -0,0 +1,92 @@
|
||||
inline fn toHaveReturnedTimesFn(this: *Expect, globalThis: *JSGlobalObject, callframe: *CallFrame, comptime mode: enum { toHaveReturned, toHaveReturnedTimes }) bun.JSError!JSValue {
|
||||
jsc.markBinding(@src());
|
||||
|
||||
const thisValue = callframe.this();
|
||||
const arguments = callframe.arguments();
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, @tagName(mode), "<green>expected<r>");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
var returns = try mock.jestMockIterator(globalThis, value);
|
||||
|
||||
const expected_success_count: i32 = if (mode == .toHaveReturned) brk: {
|
||||
if (arguments.len > 0 and !arguments[0].isUndefined()) {
|
||||
return globalThis.throwInvalidArguments(@tagName(mode) ++ "() must not have an argument", .{});
|
||||
}
|
||||
break :brk 1;
|
||||
} else brk: {
|
||||
if (arguments.len < 1 or !arguments[0].isUInt32AsAnyInt()) {
|
||||
return globalThis.throwInvalidArguments(@tagName(mode) ++ "() requires 1 non-negative integer argument", .{});
|
||||
}
|
||||
|
||||
break :brk try arguments[0].coerce(i32, globalThis);
|
||||
};
|
||||
|
||||
var pass = false;
|
||||
|
||||
var actual_success_count: i32 = 0;
|
||||
var total_call_count: i32 = 0;
|
||||
while (try returns.next()) |item| {
|
||||
switch (try mock.jestMockReturnObject_type(globalThis, item)) {
|
||||
.@"return" => actual_success_count += 1,
|
||||
else => {},
|
||||
}
|
||||
total_call_count += 1;
|
||||
}
|
||||
|
||||
pass = switch (mode) {
|
||||
.toHaveReturned => actual_success_count >= expected_success_count,
|
||||
.toHaveReturnedTimes => actual_success_count == expected_success_count,
|
||||
};
|
||||
|
||||
const not = this.flags.not;
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
switch (not) {
|
||||
inline else => |is_not| {
|
||||
const signature = comptime getSignature(@tagName(mode), "<green>expected<r>", is_not);
|
||||
const str: []const u8, const spc: []const u8 = switch (mode) {
|
||||
.toHaveReturned => switch (not) {
|
||||
false => .{ ">= ", " " },
|
||||
true => .{ "< ", " " },
|
||||
},
|
||||
.toHaveReturnedTimes => switch (not) {
|
||||
false => .{ "== ", " " },
|
||||
true => .{ "!= ", " " },
|
||||
},
|
||||
};
|
||||
return this.throw(globalThis, signature,
|
||||
\\
|
||||
\\
|
||||
\\Expected number of succesful returns: {s}<green>{d}<r>
|
||||
\\Received number of succesful returns: {s}<red>{d}<r>
|
||||
\\Received number of calls: {s}<red>{d}<r>
|
||||
\\
|
||||
, .{ str, expected_success_count, spc, actual_success_count, spc, total_call_count });
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toHaveReturned(this: *Expect, globalThis: *JSGlobalObject, callframe: *CallFrame) bun.JSError!JSValue {
|
||||
return toHaveReturnedTimesFn(this, globalThis, callframe, .toHaveReturned);
|
||||
}
|
||||
|
||||
pub fn toHaveReturnedTimes(this: *Expect, globalThis: *JSGlobalObject, callframe: *CallFrame) bun.JSError!JSValue {
|
||||
return toHaveReturnedTimesFn(this, globalThis, callframe, .toHaveReturnedTimes);
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
const mock = bun.jsc.Expect.mock;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
1
src/bun.js/test/expect/toHaveReturnedTimes.zig
Normal file
1
src/bun.js/test/expect/toHaveReturnedTimes.zig
Normal file
@@ -0,0 +1 @@
|
||||
pub const toHaveReturnedTimes = @import("./toHaveReturned.zig").toHaveReturnedTimes;
|
||||
161
src/bun.js/test/expect/toHaveReturnedWith.zig
Normal file
161
src/bun.js/test/expect/toHaveReturnedWith.zig
Normal file
@@ -0,0 +1,161 @@
|
||||
pub fn toHaveReturnedWith(this: *Expect, globalThis: *JSGlobalObject, callframe: *CallFrame) bun.JSError!JSValue {
|
||||
jsc.markBinding(@src());
|
||||
|
||||
const thisValue = callframe.this();
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toHaveReturnedWith", "<green>expected<r>");
|
||||
|
||||
const expected = callframe.argumentsAsArray(1)[0];
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const returns = try bun.cpp.JSMockFunction__getReturns(globalThis, value);
|
||||
if (!returns.jsType().isArray()) {
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
return globalThis.throw("Expected value must be a mock function: {any}", .{value.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
const calls_count = @as(u32, @intCast(try returns.getLength(globalThis)));
|
||||
var pass = false;
|
||||
|
||||
var successful_returns = std.ArrayList(JSValue).init(globalThis.bunVM().allocator);
|
||||
defer successful_returns.deinit();
|
||||
|
||||
var has_errors = false;
|
||||
|
||||
// Check for a pass and collect info for error messages
|
||||
for (0..calls_count) |i| {
|
||||
const result = returns.getDirectIndex(globalThis, @truncate(i));
|
||||
|
||||
if (result.isObject()) {
|
||||
const result_type = try result.get(globalThis, "type") orelse .js_undefined;
|
||||
if (result_type.isString()) {
|
||||
const type_str = try result_type.toBunString(globalThis);
|
||||
defer type_str.deref();
|
||||
|
||||
if (type_str.eqlComptime("return")) {
|
||||
const result_value = try result.get(globalThis, "value") orelse .js_undefined;
|
||||
try successful_returns.append(result_value);
|
||||
|
||||
// Check for pass condition only if not already passed
|
||||
if (!pass) {
|
||||
if (try result_value.jestDeepEquals(expected, globalThis)) {
|
||||
pass = true;
|
||||
}
|
||||
}
|
||||
} else if (type_str.eqlComptime("throw")) {
|
||||
has_errors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pass != this.flags.not) {
|
||||
return .js_undefined;
|
||||
}
|
||||
|
||||
// Handle failure
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
|
||||
const signature = comptime getSignature("toHaveReturnedWith", "<green>expected<r>", false);
|
||||
|
||||
if (this.flags.not) {
|
||||
const not_signature = comptime getSignature("toHaveReturnedWith", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, not_signature, "\n\n" ++ "Expected mock function not to have returned: <green>{any}<r>\n", .{expected.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
// No match was found.
|
||||
const successful_returns_count = successful_returns.items.len;
|
||||
|
||||
// Case: Only one successful return, no errors
|
||||
if (calls_count == 1 and successful_returns_count == 1) {
|
||||
const received = successful_returns.items[0];
|
||||
if (expected.isString() and received.isString()) {
|
||||
const diff_format = DiffFormatter{
|
||||
.expected = expected,
|
||||
.received = received,
|
||||
.globalThis = globalThis,
|
||||
.not = false,
|
||||
};
|
||||
return this.throw(globalThis, signature, "\n\n{any}\n", .{diff_format});
|
||||
}
|
||||
|
||||
return this.throw(globalThis, signature, "\n\nExpected: <green>{any}<r>\nReceived: <red>{any}<r>", .{
|
||||
expected.toFmt(&formatter),
|
||||
received.toFmt(&formatter),
|
||||
});
|
||||
}
|
||||
|
||||
if (has_errors) {
|
||||
// Case: Some calls errored
|
||||
const list_formatter = mock.AllCallsFormatter{
|
||||
.globalThis = globalThis,
|
||||
.returns = returns,
|
||||
.formatter = &formatter,
|
||||
};
|
||||
const fmt =
|
||||
\\Some calls errored:
|
||||
\\
|
||||
\\ Expected: {any}
|
||||
\\ Received:
|
||||
\\{any}
|
||||
\\
|
||||
\\ Number of returns: {d}
|
||||
\\ Number of calls: {d}
|
||||
;
|
||||
|
||||
switch (Output.enable_ansi_colors) {
|
||||
inline else => |colors| {
|
||||
return this.throw(globalThis, signature, Output.prettyFmt("\n\n" ++ fmt ++ "\n", colors), .{
|
||||
expected.toFmt(&formatter),
|
||||
list_formatter,
|
||||
successful_returns_count,
|
||||
calls_count,
|
||||
});
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// Case: No errors, but no match (and multiple returns)
|
||||
const list_formatter = mock.SuccessfulReturnsFormatter{
|
||||
.globalThis = globalThis,
|
||||
.successful_returns = &successful_returns,
|
||||
.formatter = &formatter,
|
||||
};
|
||||
const fmt =
|
||||
\\ <green>Expected<r>: {any}
|
||||
\\ <red>Received<r>:
|
||||
\\{any}
|
||||
\\
|
||||
\\ Number of returns: {d}
|
||||
;
|
||||
|
||||
switch (Output.enable_ansi_colors) {
|
||||
inline else => |colors| {
|
||||
return this.throw(globalThis, signature, Output.prettyFmt("\n\n" ++ fmt ++ "\n", colors), .{
|
||||
expected.toFmt(&formatter),
|
||||
list_formatter,
|
||||
successful_returns_count,
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
const DiffFormatter = @import("../diff_format.zig").DiffFormatter;
|
||||
|
||||
const bun = @import("bun");
|
||||
const Output = bun.Output;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
const mock = bun.jsc.Expect.mock;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
65
src/bun.js/test/expect/toInclude.zig
Normal file
65
src/bun.js/test/expect/toInclude.zig
Normal file
@@ -0,0 +1,65 @@
|
||||
pub fn toInclude(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const arguments_ = callFrame.arguments_old(1);
|
||||
const arguments = arguments_.slice();
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toInclude() requires 1 argument", .{});
|
||||
}
|
||||
|
||||
const expected = arguments[0];
|
||||
expected.ensureStillAlive();
|
||||
|
||||
if (!expected.isString()) {
|
||||
return globalThis.throw("toInclude() requires the first argument to be a string", .{});
|
||||
}
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toInclude", "");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
var pass = value.isString();
|
||||
if (pass) {
|
||||
const value_string = try value.toSliceOrNull(globalThis);
|
||||
defer value_string.deinit();
|
||||
const expected_string = try expected.toSliceOrNull(globalThis);
|
||||
defer expected_string.deinit();
|
||||
pass = strings.contains(value_string.slice(), expected_string.slice()) or expected_string.len == 0;
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
if (not) pass = !pass;
|
||||
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
const expected_fmt = expected.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const expected_line = "Expected to not include: <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toInclude", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const expected_line = "Expected to include: <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toInclude", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const strings = bun.strings;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
111
src/bun.js/test/expect/toIncludeRepeated.zig
Normal file
111
src/bun.js/test/expect/toIncludeRepeated.zig
Normal file
@@ -0,0 +1,111 @@
|
||||
pub fn toIncludeRepeated(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const arguments_ = callFrame.arguments_old(2);
|
||||
const arguments = arguments_.slice();
|
||||
|
||||
if (arguments.len < 2) {
|
||||
return globalThis.throwInvalidArguments("toIncludeRepeated() requires 2 arguments", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const substring = arguments[0];
|
||||
substring.ensureStillAlive();
|
||||
|
||||
if (!substring.isString()) {
|
||||
return globalThis.throw("toIncludeRepeated() requires the first argument to be a string", .{});
|
||||
}
|
||||
|
||||
const count = arguments[1];
|
||||
count.ensureStillAlive();
|
||||
|
||||
if (!count.isAnyInt()) {
|
||||
return globalThis.throw("toIncludeRepeated() requires the second argument to be a number", .{});
|
||||
}
|
||||
|
||||
const countAsNum = count.toU32();
|
||||
|
||||
const expect_string = Expect.js.capturedValueGetCached(thisValue) orelse {
|
||||
return globalThis.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{});
|
||||
};
|
||||
|
||||
if (!expect_string.isString()) {
|
||||
return globalThis.throw("toIncludeRepeated() requires the expect(value) to be a string", .{});
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = false;
|
||||
|
||||
const _expectStringAsStr = try expect_string.toSliceOrNull(globalThis);
|
||||
const _subStringAsStr = try substring.toSliceOrNull(globalThis);
|
||||
|
||||
defer {
|
||||
_expectStringAsStr.deinit();
|
||||
_subStringAsStr.deinit();
|
||||
}
|
||||
|
||||
const expectStringAsStr = _expectStringAsStr.slice();
|
||||
const subStringAsStr = _subStringAsStr.slice();
|
||||
|
||||
if (subStringAsStr.len == 0) {
|
||||
return globalThis.throw("toIncludeRepeated() requires the first argument to be a non-empty string", .{});
|
||||
}
|
||||
|
||||
const actual_count = std.mem.count(u8, expectStringAsStr, subStringAsStr);
|
||||
pass = actual_count == countAsNum;
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const expect_string_fmt = expect_string.toFmt(&formatter);
|
||||
const substring_fmt = substring.toFmt(&formatter);
|
||||
const times_fmt = count.toFmt(&formatter);
|
||||
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
|
||||
if (not) {
|
||||
if (countAsNum == 0) {
|
||||
const expected_line = "Expected to include: <green>{any}<r> \n";
|
||||
const signature = comptime getSignature("toIncludeRepeated", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ substring_fmt, expect_string_fmt });
|
||||
} else if (countAsNum == 1) {
|
||||
const expected_line = "Expected not to include: <green>{any}<r> \n";
|
||||
const signature = comptime getSignature("toIncludeRepeated", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ substring_fmt, expect_string_fmt });
|
||||
} else {
|
||||
const expected_line = "Expected not to include: <green>{any}<r> <green>{any}<r> times \n";
|
||||
const signature = comptime getSignature("toIncludeRepeated", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ substring_fmt, times_fmt, expect_string_fmt });
|
||||
}
|
||||
}
|
||||
|
||||
if (countAsNum == 0) {
|
||||
const expected_line = "Expected to not include: <green>{any}<r>\n";
|
||||
const signature = comptime getSignature("toIncludeRepeated", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ substring_fmt, expect_string_fmt });
|
||||
} else if (countAsNum == 1) {
|
||||
const expected_line = "Expected to include: <green>{any}<r>\n";
|
||||
const signature = comptime getSignature("toIncludeRepeated", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ substring_fmt, expect_string_fmt });
|
||||
} else {
|
||||
const expected_line = "Expected to include: <green>{any}<r> <green>{any}<r> times \n";
|
||||
const signature = comptime getSignature("toIncludeRepeated", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ substring_fmt, times_fmt, expect_string_fmt });
|
||||
}
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const std = @import("std");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
70
src/bun.js/test/expect/toMatch.zig
Normal file
70
src/bun.js/test/expect/toMatch.zig
Normal file
@@ -0,0 +1,70 @@
|
||||
pub fn toMatch(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
jsc.markBinding(@src());
|
||||
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const _arguments = callFrame.arguments_old(1);
|
||||
const arguments: []const JSValue = _arguments.ptr[0.._arguments.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toMatch() requires 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
|
||||
const expected_value = arguments[0];
|
||||
if (!expected_value.isString() and !expected_value.isRegExp()) {
|
||||
return globalThis.throw("Expected value must be a string or regular expression: {any}", .{expected_value.toFmt(&formatter)});
|
||||
}
|
||||
expected_value.ensureStillAlive();
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toMatch", "<green>expected<r>");
|
||||
|
||||
if (!value.isString()) {
|
||||
return globalThis.throw("Received value must be a string: {any}", .{value.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass: bool = brk: {
|
||||
if (expected_value.isString()) {
|
||||
break :brk value.stringIncludes(globalThis, expected_value);
|
||||
} else if (expected_value.isRegExp()) {
|
||||
break :brk expected_value.toMatch(globalThis, value);
|
||||
}
|
||||
unreachable;
|
||||
};
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
const expected_fmt = expected_value.toFmt(&formatter);
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const expected_line = "Expected substring or pattern: not <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toMatch", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const expected_line = "Expected substring or pattern: <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toMatch", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
65
src/bun.js/test/expect/toMatchInlineSnapshot.zig
Normal file
65
src/bun.js/test/expect/toMatchInlineSnapshot.zig
Normal file
@@ -0,0 +1,65 @@
|
||||
pub fn toMatchInlineSnapshot(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
const thisValue = callFrame.this();
|
||||
const _arguments = callFrame.arguments_old(2);
|
||||
const arguments: []const JSValue = _arguments.ptr[0.._arguments.len];
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toMatchInlineSnapshot", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n<b>Matcher error<r>: Snapshot matchers cannot be used with <b>not<r>\n", .{});
|
||||
}
|
||||
|
||||
var has_expected = false;
|
||||
var expected_string: ZigString = ZigString.Empty;
|
||||
var property_matchers: ?JSValue = null;
|
||||
switch (arguments.len) {
|
||||
0 => {},
|
||||
1 => {
|
||||
if (arguments[0].isString()) {
|
||||
has_expected = true;
|
||||
try arguments[0].toZigString(&expected_string, globalThis);
|
||||
} else if (arguments[0].isObject()) {
|
||||
property_matchers = arguments[0];
|
||||
} else {
|
||||
return this.throw(globalThis, "", "\n\nMatcher error: Expected first argument to be a string or object\n", .{});
|
||||
}
|
||||
},
|
||||
else => {
|
||||
if (!arguments[0].isObject()) {
|
||||
const signature = comptime getSignature("toMatchInlineSnapshot", "<green>properties<r><d>, <r>hint", false);
|
||||
return this.throw(globalThis, signature, "\n\nMatcher error: Expected <green>properties<r> must be an object\n", .{});
|
||||
}
|
||||
|
||||
property_matchers = arguments[0];
|
||||
|
||||
if (arguments[1].isString()) {
|
||||
has_expected = true;
|
||||
try arguments[1].toZigString(&expected_string, globalThis);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var expected = expected_string.toSlice(default_allocator);
|
||||
defer expected.deinit();
|
||||
|
||||
const expected_slice: ?[]const u8 = if (has_expected) expected.slice() else null;
|
||||
|
||||
const value = try this.getValue(globalThis, thisValue, "toMatchInlineSnapshot", "<green>properties<r><d>, <r>hint");
|
||||
return this.inlineSnapshot(globalThis, callFrame, value, property_matchers, expected_slice, "toMatchInlineSnapshot");
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const ZigString = bun.ZigString;
|
||||
const default_allocator = bun.default_allocator;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
69
src/bun.js/test/expect/toMatchObject.zig
Normal file
69
src/bun.js/test/expect/toMatchObject.zig
Normal file
@@ -0,0 +1,69 @@
|
||||
pub fn toMatchObject(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
jsc.markBinding(@src());
|
||||
|
||||
defer this.postMatch(globalThis);
|
||||
const thisValue = callFrame.this();
|
||||
const args = callFrame.arguments_old(1).slice();
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
|
||||
const received_object: JSValue = try this.getValue(globalThis, thisValue, "toMatchObject", "<green>expected<r>");
|
||||
|
||||
if (!received_object.isObject()) {
|
||||
const matcher_error = "\n\n<b>Matcher error<r>: <red>received<r> value must be a non-null object\n";
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toMatchObject", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, matcher_error, .{});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toMatchObject", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, matcher_error, .{});
|
||||
}
|
||||
|
||||
if (args.len < 1 or !args[0].isObject()) {
|
||||
const matcher_error = "\n\n<b>Matcher error<r>: <green>expected<r> value must be a non-null object\n";
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toMatchObject", "", true);
|
||||
return this.throw(globalThis, signature, matcher_error, .{});
|
||||
}
|
||||
const signature = comptime getSignature("toMatchObject", "", false);
|
||||
return this.throw(globalThis, signature, matcher_error, .{});
|
||||
}
|
||||
|
||||
const property_matchers = args[0];
|
||||
|
||||
var pass = try received_object.jestDeepMatch(property_matchers, globalThis, true);
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
const diff_formatter = DiffFormatter{
|
||||
.received = received_object,
|
||||
.expected = property_matchers,
|
||||
.globalThis = globalThis,
|
||||
.not = not,
|
||||
};
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toMatchObject", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n{any}\n", .{diff_formatter});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toMatchObject", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n{any}\n", .{diff_formatter});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const DiffFormatter = @import("../diff_format.zig").DiffFormatter;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
68
src/bun.js/test/expect/toMatchSnapshot.zig
Normal file
68
src/bun.js/test/expect/toMatchSnapshot.zig
Normal file
@@ -0,0 +1,68 @@
|
||||
pub fn toMatchSnapshot(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
const thisValue = callFrame.this();
|
||||
const _arguments = callFrame.arguments_old(2);
|
||||
const arguments: []const JSValue = _arguments.ptr[0.._arguments.len];
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toMatchSnapshot", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n<b>Matcher error<r>: Snapshot matchers cannot be used with <b>not<r>\n", .{});
|
||||
}
|
||||
|
||||
if (this.testScope() == null) {
|
||||
const signature = comptime getSignature("toMatchSnapshot", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n<b>Matcher error<r>: Snapshot matchers cannot be used outside of a test\n", .{});
|
||||
}
|
||||
|
||||
var hint_string: ZigString = ZigString.Empty;
|
||||
var property_matchers: ?JSValue = null;
|
||||
switch (arguments.len) {
|
||||
0 => {},
|
||||
1 => {
|
||||
if (arguments[0].isString()) {
|
||||
try arguments[0].toZigString(&hint_string, globalThis);
|
||||
} else if (arguments[0].isObject()) {
|
||||
property_matchers = arguments[0];
|
||||
} else {
|
||||
return this.throw(globalThis, "", "\n\nMatcher error: Expected first argument to be a string or object\n", .{});
|
||||
}
|
||||
},
|
||||
else => {
|
||||
if (!arguments[0].isObject()) {
|
||||
const signature = comptime getSignature("toMatchSnapshot", "<green>properties<r><d>, <r>hint", false);
|
||||
return this.throw(globalThis, signature, "\n\nMatcher error: Expected <green>properties<r> must be an object\n", .{});
|
||||
}
|
||||
|
||||
property_matchers = arguments[0];
|
||||
|
||||
if (arguments[1].isString()) {
|
||||
try arguments[1].toZigString(&hint_string, globalThis);
|
||||
} else {
|
||||
return this.throw(globalThis, "", "\n\nMatcher error: Expected second argument to be a string\n", .{});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var hint = hint_string.toSlice(default_allocator);
|
||||
defer hint.deinit();
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toMatchSnapshot", "<green>properties<r><d>, <r>hint");
|
||||
|
||||
return this.snapshot(globalThis, value, property_matchers, hint.slice(), "toMatchSnapshot");
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const ZigString = bun.ZigString;
|
||||
const default_allocator = bun.default_allocator;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
63
src/bun.js/test/expect/toSatisfy.zig
Normal file
63
src/bun.js/test/expect/toSatisfy.zig
Normal file
@@ -0,0 +1,63 @@
|
||||
pub fn toSatisfy(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const arguments_ = callFrame.arguments_old(1);
|
||||
const arguments = arguments_.slice();
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toSatisfy() requires 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const predicate = arguments[0];
|
||||
predicate.ensureStillAlive();
|
||||
|
||||
if (!predicate.isCallable()) {
|
||||
return globalThis.throw("toSatisfy() argument must be a function", .{});
|
||||
}
|
||||
|
||||
const value = Expect.js.capturedValueGetCached(thisValue) orelse {
|
||||
return globalThis.throw("Internal consistency error: the expect(value) was garbage collected but it should not have been!", .{});
|
||||
};
|
||||
value.ensureStillAlive();
|
||||
|
||||
const result = predicate.call(globalThis, .js_undefined, &.{value}) catch |e| {
|
||||
const err = globalThis.takeException(e);
|
||||
const fmt = ZigString.init("toSatisfy() predicate threw an exception");
|
||||
return globalThis.throwValue(try globalThis.createAggregateError(&.{err}, &fmt));
|
||||
};
|
||||
|
||||
const not = this.flags.not;
|
||||
const pass = (result.isBoolean() and result.toBoolean()) != not;
|
||||
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toSatisfy", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\nExpected: not <green>{any}<r>\n", .{predicate.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toSatisfy", "<green>expected<r>", false);
|
||||
|
||||
return this.throw(globalThis, signature, "\n\nExpected: <green>{any}<r>\nReceived: <red>{any}<r>\n", .{
|
||||
predicate.toFmt(&formatter),
|
||||
value.toFmt(&formatter),
|
||||
});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const ZigString = bun.ZigString;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
65
src/bun.js/test/expect/toStartWith.zig
Normal file
65
src/bun.js/test/expect/toStartWith.zig
Normal file
@@ -0,0 +1,65 @@
|
||||
pub fn toStartWith(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const arguments_ = callFrame.arguments_old(1);
|
||||
const arguments = arguments_.slice();
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toStartWith() requires 1 argument", .{});
|
||||
}
|
||||
|
||||
const expected = arguments[0];
|
||||
expected.ensureStillAlive();
|
||||
|
||||
if (!expected.isString()) {
|
||||
return globalThis.throw("toStartWith() requires the first argument to be a string", .{});
|
||||
}
|
||||
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toStartWith", "<green>expected<r>");
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
var pass = value.isString();
|
||||
if (pass) {
|
||||
const value_string = try value.toSliceOrNull(globalThis);
|
||||
defer value_string.deinit();
|
||||
const expected_string = try expected.toSliceOrNull(globalThis);
|
||||
defer expected_string.deinit();
|
||||
pass = strings.startsWith(value_string.slice(), expected_string.slice()) or expected_string.len == 0;
|
||||
}
|
||||
|
||||
const not = this.flags.not;
|
||||
if (not) pass = !pass;
|
||||
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const value_fmt = value.toFmt(&formatter);
|
||||
const expected_fmt = expected.toFmt(&formatter);
|
||||
|
||||
if (not) {
|
||||
const expected_line = "Expected to not start with: <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toStartWith", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const expected_line = "Expected to start with: <green>{any}<r>\n";
|
||||
const received_line = "Received: <red>{any}<r>\n";
|
||||
const signature = comptime getSignature("toStartWith", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ expected_line ++ received_line, .{ expected_fmt, value_fmt });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const strings = bun.strings;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
45
src/bun.js/test/expect/toStrictEqual.zig
Normal file
45
src/bun.js/test/expect/toStrictEqual.zig
Normal file
@@ -0,0 +1,45 @@
|
||||
pub fn toStrictEqual(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const _arguments = callFrame.arguments_old(1);
|
||||
const arguments: []const JSValue = _arguments.ptr[0.._arguments.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throwInvalidArguments("toStrictEqual() requires 1 argument", .{});
|
||||
}
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const expected = arguments[0];
|
||||
const value: JSValue = try this.getValue(globalThis, thisValue, "toStrictEqual", "<green>expected<r>");
|
||||
|
||||
const not = this.flags.not;
|
||||
var pass = try value.jestStrictDeepEquals(expected, globalThis);
|
||||
|
||||
if (not) pass = !pass;
|
||||
if (pass) return .js_undefined;
|
||||
|
||||
// handle failure
|
||||
const diff_formatter = DiffFormatter{ .received = value, .expected = expected, .globalThis = globalThis, .not = not };
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toStrictEqual", "<green>expected<r>", true);
|
||||
return this.throw(globalThis, signature, "\n\n{any}\n", .{diff_formatter});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toStrictEqual", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n{any}\n", .{diff_formatter});
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const DiffFormatter = @import("../diff_format.zig").DiffFormatter;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
321
src/bun.js/test/expect/toThrow.zig
Normal file
321
src/bun.js/test/expect/toThrow.zig
Normal file
@@ -0,0 +1,321 @@
|
||||
pub fn toThrow(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
|
||||
const thisValue = callFrame.this();
|
||||
const arguments = callFrame.argumentsAsArray(1);
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const expected_value: JSValue = brk: {
|
||||
if (callFrame.argumentsCount() == 0) {
|
||||
break :brk .zero;
|
||||
}
|
||||
const value = arguments[0];
|
||||
if (value.isUndefinedOrNull() or !value.isObject() and !value.isString()) {
|
||||
var fmt = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
return globalThis.throw("Expected value must be string or Error: {any}", .{value.toFmt(&fmt)});
|
||||
}
|
||||
if (value.isObject()) {
|
||||
if (ExpectAny.fromJSDirect(value)) |_| {
|
||||
if (ExpectAny.js.constructorValueGetCached(value)) |innerConstructorValue| {
|
||||
break :brk innerConstructorValue;
|
||||
}
|
||||
}
|
||||
} else if (value.isString()) {
|
||||
// `.toThrow("") behaves the same as `.toThrow()`
|
||||
const s = value.toString(globalThis);
|
||||
if (s.length() == 0) break :brk .zero;
|
||||
}
|
||||
break :brk value;
|
||||
};
|
||||
expected_value.ensureStillAlive();
|
||||
|
||||
const not = this.flags.not;
|
||||
|
||||
const result_, const return_value_from_function = try this.getValueAsToThrow(globalThis, try this.getValue(globalThis, thisValue, "toThrow", "<green>expected<r>"));
|
||||
|
||||
const did_throw = result_ != null;
|
||||
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toThrow", "<green>expected<r>", true);
|
||||
|
||||
if (!did_throw) return .js_undefined;
|
||||
|
||||
const result: JSValue = result_.?;
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
|
||||
if (expected_value == .zero or expected_value.isUndefined()) {
|
||||
const signature_no_args = comptime getSignature("toThrow", "", true);
|
||||
if (result.toError()) |err| {
|
||||
const name: JSValue = try err.getTruthyComptime(globalThis, "name") orelse .js_undefined;
|
||||
const message: JSValue = try err.getTruthyComptime(globalThis, "message") orelse .js_undefined;
|
||||
const fmt = signature_no_args ++ "\n\nError name: <red>{any}<r>\nError message: <red>{any}<r>\n";
|
||||
return globalThis.throwPretty(fmt, .{
|
||||
name.toFmt(&formatter),
|
||||
message.toFmt(&formatter),
|
||||
});
|
||||
}
|
||||
|
||||
// non error thrown
|
||||
const fmt = signature_no_args ++ "\n\nThrown value: <red>{any}<r>\n";
|
||||
return globalThis.throwPretty(fmt, .{result.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
if (expected_value.isString()) {
|
||||
const received_message: JSValue = (if (result.isObject())
|
||||
try result.fastGet(globalThis, .message)
|
||||
else
|
||||
JSValue.fromCell(try result.toJSString(globalThis))) orelse .js_undefined;
|
||||
if (globalThis.hasException()) return .zero;
|
||||
|
||||
// TODO: remove this allocation
|
||||
// partial match
|
||||
{
|
||||
const expected_slice = try expected_value.toSliceOrNull(globalThis);
|
||||
defer expected_slice.deinit();
|
||||
const received_slice = try received_message.toSliceOrNull(globalThis);
|
||||
defer received_slice.deinit();
|
||||
if (!strings.contains(received_slice.slice(), expected_slice.slice())) return .js_undefined;
|
||||
}
|
||||
|
||||
return this.throw(globalThis, signature, "\n\nExpected substring: not <green>{any}<r>\nReceived message: <red>{any}<r>\n", .{
|
||||
expected_value.toFmt(&formatter),
|
||||
received_message.toFmt(&formatter),
|
||||
});
|
||||
}
|
||||
|
||||
if (expected_value.isRegExp()) {
|
||||
const received_message: JSValue = (if (result.isObject())
|
||||
try result.fastGet(globalThis, .message)
|
||||
else
|
||||
JSValue.fromCell(try result.toJSString(globalThis))) orelse .js_undefined;
|
||||
|
||||
if (globalThis.hasException()) return .zero;
|
||||
// TODO: REMOVE THIS GETTER! Expose a binding to call .test on the RegExp object directly.
|
||||
if (try expected_value.get(globalThis, "test")) |test_fn| {
|
||||
const matches = test_fn.call(globalThis, expected_value, &.{received_message}) catch |err| globalThis.takeException(err);
|
||||
if (!matches.toBoolean()) return .js_undefined;
|
||||
}
|
||||
|
||||
return this.throw(globalThis, signature, "\n\nExpected pattern: not <green>{any}<r>\nReceived message: <red>{any}<r>\n", .{
|
||||
expected_value.toFmt(&formatter),
|
||||
received_message.toFmt(&formatter),
|
||||
});
|
||||
}
|
||||
|
||||
if (try expected_value.fastGet(globalThis, .message)) |expected_message| {
|
||||
const received_message: JSValue = (if (result.isObject())
|
||||
try result.fastGet(globalThis, .message)
|
||||
else
|
||||
JSValue.fromCell(try result.toJSString(globalThis))) orelse .js_undefined;
|
||||
if (globalThis.hasException()) return .zero;
|
||||
|
||||
// no partial match for this case
|
||||
if (!try expected_message.isSameValue(received_message, globalThis)) return .js_undefined;
|
||||
|
||||
return this.throw(globalThis, signature, "\n\nExpected message: not <green>{any}<r>\n", .{expected_message.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
if (!result.isInstanceOf(globalThis, expected_value)) return .js_undefined;
|
||||
|
||||
var expected_class = ZigString.Empty;
|
||||
try expected_value.getClassName(globalThis, &expected_class);
|
||||
const received_message: JSValue = (try result.fastGet(globalThis, .message)) orelse .js_undefined;
|
||||
return this.throw(globalThis, signature, "\n\nExpected constructor: not <green>{s}<r>\n\nReceived message: <red>{any}<r>\n", .{ expected_class, received_message.toFmt(&formatter) });
|
||||
}
|
||||
|
||||
if (did_throw) {
|
||||
if (expected_value == .zero or expected_value.isUndefined()) return .js_undefined;
|
||||
|
||||
const result: JSValue = if (result_.?.toError()) |r|
|
||||
r
|
||||
else
|
||||
result_.?;
|
||||
|
||||
const _received_message: ?JSValue = if (result.isObject())
|
||||
try result.fastGet(globalThis, .message)
|
||||
else
|
||||
JSValue.fromCell(try result.toJSString(globalThis));
|
||||
|
||||
if (expected_value.isString()) {
|
||||
if (_received_message) |received_message| {
|
||||
// TODO: remove this allocation
|
||||
// partial match
|
||||
const expected_slice = try expected_value.toSliceOrNull(globalThis);
|
||||
defer expected_slice.deinit();
|
||||
const received_slice = try received_message.toSlice(globalThis, globalThis.allocator());
|
||||
defer received_slice.deinit();
|
||||
if (strings.contains(received_slice.slice(), expected_slice.slice())) return .js_undefined;
|
||||
}
|
||||
|
||||
// error: message from received error does not match expected string
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
|
||||
const signature = comptime getSignature("toThrow", "<green>expected<r>", false);
|
||||
|
||||
if (_received_message) |received_message| {
|
||||
const expected_value_fmt = expected_value.toFmt(&formatter);
|
||||
const received_message_fmt = received_message.toFmt(&formatter);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Expected substring: <green>{any}<r>\nReceived message: <red>{any}<r>\n", .{ expected_value_fmt, received_message_fmt });
|
||||
}
|
||||
|
||||
const expected_fmt = expected_value.toFmt(&formatter);
|
||||
const received_fmt = result.toFmt(&formatter);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Expected substring: <green>{any}<r>\nReceived value: <red>{any}<r>", .{ expected_fmt, received_fmt });
|
||||
}
|
||||
|
||||
if (expected_value.isRegExp()) {
|
||||
if (_received_message) |received_message| {
|
||||
// TODO: REMOVE THIS GETTER! Expose a binding to call .test on the RegExp object directly.
|
||||
if (try expected_value.get(globalThis, "test")) |test_fn| {
|
||||
const matches = test_fn.call(globalThis, expected_value, &.{received_message}) catch |err| globalThis.takeException(err);
|
||||
if (matches.toBoolean()) return .js_undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// error: message from received error does not match expected pattern
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
|
||||
if (_received_message) |received_message| {
|
||||
const expected_value_fmt = expected_value.toFmt(&formatter);
|
||||
const received_message_fmt = received_message.toFmt(&formatter);
|
||||
const signature = comptime getSignature("toThrow", "<green>expected<r>", false);
|
||||
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Expected pattern: <green>{any}<r>\nReceived message: <red>{any}<r>\n", .{ expected_value_fmt, received_message_fmt });
|
||||
}
|
||||
|
||||
const expected_fmt = expected_value.toFmt(&formatter);
|
||||
const received_fmt = result.toFmt(&formatter);
|
||||
const signature = comptime getSignature("toThrow", "<green>expected<r>", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ "Expected pattern: <green>{any}<r>\nReceived value: <red>{any}<r>", .{ expected_fmt, received_fmt });
|
||||
}
|
||||
|
||||
if (Expect.isAsymmetricMatcher(expected_value)) {
|
||||
const signature = comptime getSignature("toThrow", "<green>expected<r>", false);
|
||||
const is_equal = try result.jestStrictDeepEquals(expected_value, globalThis);
|
||||
|
||||
if (globalThis.hasException()) {
|
||||
return .zero;
|
||||
}
|
||||
|
||||
if (is_equal) {
|
||||
return .js_undefined;
|
||||
}
|
||||
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received_fmt = result.toFmt(&formatter);
|
||||
const expected_fmt = expected_value.toFmt(&formatter);
|
||||
return this.throw(globalThis, signature, "\n\nExpected value: <green>{any}<r>\nReceived value: <red>{any}<r>\n", .{ expected_fmt, received_fmt });
|
||||
}
|
||||
|
||||
// If it's not an object, we are going to crash here.
|
||||
assert(expected_value.isObject());
|
||||
|
||||
if (try expected_value.fastGet(globalThis, .message)) |expected_message| {
|
||||
const signature = comptime getSignature("toThrow", "<green>expected<r>", false);
|
||||
|
||||
if (_received_message) |received_message| {
|
||||
if (try received_message.isSameValue(expected_message, globalThis)) return .js_undefined;
|
||||
}
|
||||
|
||||
// error: message from received error does not match expected error message.
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
|
||||
if (_received_message) |received_message| {
|
||||
const expected_fmt = expected_message.toFmt(&formatter);
|
||||
const received_fmt = received_message.toFmt(&formatter);
|
||||
return this.throw(globalThis, signature, "\n\nExpected message: <green>{any}<r>\nReceived message: <red>{any}<r>\n", .{ expected_fmt, received_fmt });
|
||||
}
|
||||
|
||||
const expected_fmt = expected_message.toFmt(&formatter);
|
||||
const received_fmt = result.toFmt(&formatter);
|
||||
return this.throw(globalThis, signature, "\n\nExpected message: <green>{any}<r>\nReceived value: <red>{any}<r>\n", .{ expected_fmt, received_fmt });
|
||||
}
|
||||
|
||||
if (result.isInstanceOf(globalThis, expected_value)) return .js_undefined;
|
||||
|
||||
// error: received error not instance of received error constructor
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
var expected_class = ZigString.Empty;
|
||||
var received_class = ZigString.Empty;
|
||||
try expected_value.getClassName(globalThis, &expected_class);
|
||||
try result.getClassName(globalThis, &received_class);
|
||||
const signature = comptime getSignature("toThrow", "<green>expected<r>", false);
|
||||
const fmt = signature ++ "\n\nExpected constructor: <green>{s}<r>\nReceived constructor: <red>{s}<r>\n\n";
|
||||
|
||||
if (_received_message) |received_message| {
|
||||
const message_fmt = fmt ++ "Received message: <red>{any}<r>\n";
|
||||
const received_message_fmt = received_message.toFmt(&formatter);
|
||||
|
||||
return globalThis.throwPretty(message_fmt, .{
|
||||
expected_class,
|
||||
received_class,
|
||||
received_message_fmt,
|
||||
});
|
||||
}
|
||||
|
||||
const received_fmt = result.toFmt(&formatter);
|
||||
const value_fmt = fmt ++ "Received value: <red>{any}<r>\n";
|
||||
|
||||
return globalThis.throwPretty(value_fmt, .{
|
||||
expected_class,
|
||||
received_class,
|
||||
received_fmt,
|
||||
});
|
||||
}
|
||||
|
||||
// did not throw
|
||||
const result = return_value_from_function;
|
||||
var formatter = jsc.ConsoleObject.Formatter{ .globalThis = globalThis, .quote_strings = true };
|
||||
defer formatter.deinit();
|
||||
const received_line = "Received function did not throw\nReceived value: <red>{any}<r>\n";
|
||||
|
||||
if (expected_value == .zero or expected_value.isUndefined()) {
|
||||
const signature = comptime getSignature("toThrow", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n" ++ received_line, .{result.toFmt(&formatter)});
|
||||
}
|
||||
|
||||
const signature = comptime getSignature("toThrow", "<green>expected<r>", false);
|
||||
|
||||
if (expected_value.isString()) {
|
||||
const expected_fmt = "\n\nExpected substring: <green>{any}<r>\n\n" ++ received_line;
|
||||
return this.throw(globalThis, signature, expected_fmt, .{ expected_value.toFmt(&formatter), result.toFmt(&formatter) });
|
||||
}
|
||||
|
||||
if (expected_value.isRegExp()) {
|
||||
const expected_fmt = "\n\nExpected pattern: <green>{any}<r>\n\n" ++ received_line;
|
||||
return this.throw(globalThis, signature, expected_fmt, .{ expected_value.toFmt(&formatter), result.toFmt(&formatter) });
|
||||
}
|
||||
|
||||
if (try expected_value.fastGet(globalThis, .message)) |expected_message| {
|
||||
const expected_fmt = "\n\nExpected message: <green>{any}<r>\n\n" ++ received_line;
|
||||
return this.throw(globalThis, signature, expected_fmt, .{ expected_message.toFmt(&formatter), result.toFmt(&formatter) });
|
||||
}
|
||||
|
||||
const expected_fmt = "\n\nExpected constructor: <green>{s}<r>\n\n" ++ received_line;
|
||||
var expected_class = ZigString.Empty;
|
||||
try expected_value.getClassName(globalThis, &expected_class);
|
||||
return this.throw(globalThis, signature, expected_fmt, .{ expected_class, result.toFmt(&formatter) });
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const ZigString = bun.ZigString;
|
||||
const assert = bun.assert;
|
||||
const strings = bun.strings;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
|
||||
const ExpectAny = bun.jsc.Expect.ExpectAny;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
@@ -0,0 +1,54 @@
|
||||
pub fn toThrowErrorMatchingInlineSnapshot(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
const thisValue = callFrame.this();
|
||||
const _arguments = callFrame.arguments_old(2);
|
||||
const arguments: []const JSValue = _arguments.ptr[0.._arguments.len];
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toThrowErrorMatchingInlineSnapshot", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n<b>Matcher error<r>: Snapshot matchers cannot be used with <b>not<r>\n", .{});
|
||||
}
|
||||
|
||||
var has_expected = false;
|
||||
var expected_string: ZigString = ZigString.Empty;
|
||||
switch (arguments.len) {
|
||||
0 => {},
|
||||
1 => {
|
||||
if (arguments[0].isString()) {
|
||||
has_expected = true;
|
||||
try arguments[0].toZigString(&expected_string, globalThis);
|
||||
} else {
|
||||
return this.throw(globalThis, "", "\n\nMatcher error: Expected first argument to be a string\n", .{});
|
||||
}
|
||||
},
|
||||
else => return this.throw(globalThis, "", "\n\nMatcher error: Expected zero or one arguments\n", .{}),
|
||||
}
|
||||
|
||||
var expected = expected_string.toSlice(default_allocator);
|
||||
defer expected.deinit();
|
||||
|
||||
const expected_slice: ?[]const u8 = if (has_expected) expected.slice() else null;
|
||||
|
||||
const value: JSValue = (try this.fnToErrStringOrUndefined(globalThis, try this.getValue(globalThis, thisValue, "toThrowErrorMatchingInlineSnapshot", "<green>properties<r><d>, <r>hint"))) orelse {
|
||||
const signature = comptime getSignature("toThrowErrorMatchingInlineSnapshot", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n<b>Matcher error<r>: Received function did not throw\n", .{});
|
||||
};
|
||||
|
||||
return this.inlineSnapshot(globalThis, callFrame, value, null, expected_slice, "toThrowErrorMatchingInlineSnapshot");
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const ZigString = bun.ZigString;
|
||||
const default_allocator = bun.default_allocator;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
55
src/bun.js/test/expect/toThrowErrorMatchingSnapshot.zig
Normal file
55
src/bun.js/test/expect/toThrowErrorMatchingSnapshot.zig
Normal file
@@ -0,0 +1,55 @@
|
||||
pub fn toThrowErrorMatchingSnapshot(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
|
||||
defer this.postMatch(globalThis);
|
||||
const thisValue = callFrame.this();
|
||||
const _arguments = callFrame.arguments_old(2);
|
||||
const arguments: []const JSValue = _arguments.ptr[0.._arguments.len];
|
||||
|
||||
incrementExpectCallCounter();
|
||||
|
||||
const not = this.flags.not;
|
||||
if (not) {
|
||||
const signature = comptime getSignature("toThrowErrorMatchingSnapshot", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n<b>Matcher error<r>: Snapshot matchers cannot be used with <b>not<r>\n", .{});
|
||||
}
|
||||
|
||||
if (this.testScope() == null) {
|
||||
const signature = comptime getSignature("toThrowErrorMatchingSnapshot", "", true);
|
||||
return this.throw(globalThis, signature, "\n\n<b>Matcher error<r>: Snapshot matchers cannot be used outside of a test\n", .{});
|
||||
}
|
||||
|
||||
var hint_string: ZigString = ZigString.Empty;
|
||||
switch (arguments.len) {
|
||||
0 => {},
|
||||
1 => {
|
||||
if (arguments[0].isString()) {
|
||||
try arguments[0].toZigString(&hint_string, globalThis);
|
||||
} else {
|
||||
return this.throw(globalThis, "", "\n\nMatcher error: Expected first argument to be a string\n", .{});
|
||||
}
|
||||
},
|
||||
else => return this.throw(globalThis, "", "\n\nMatcher error: Expected zero or one arguments\n", .{}),
|
||||
}
|
||||
|
||||
var hint = hint_string.toSlice(default_allocator);
|
||||
defer hint.deinit();
|
||||
|
||||
const value: JSValue = (try this.fnToErrStringOrUndefined(globalThis, try this.getValue(globalThis, thisValue, "toThrowErrorMatchingSnapshot", "<green>properties<r><d>, <r>hint"))) orelse {
|
||||
const signature = comptime getSignature("toThrowErrorMatchingSnapshot", "", false);
|
||||
return this.throw(globalThis, signature, "\n\n<b>Matcher error<r>: Received function did not throw\n", .{});
|
||||
};
|
||||
|
||||
return this.snapshot(globalThis, value, null, hint.slice(), "toThrowErrorMatchingSnapshot");
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const ZigString = bun.ZigString;
|
||||
const default_allocator = bun.default_allocator;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const CallFrame = bun.jsc.CallFrame;
|
||||
const JSGlobalObject = bun.jsc.JSGlobalObject;
|
||||
const JSValue = bun.jsc.JSValue;
|
||||
const incrementExpectCallCounter = bun.jsc.Expect.incrementExpectCallCounter;
|
||||
|
||||
const Expect = bun.jsc.Expect.Expect;
|
||||
const getSignature = Expect.getSignature;
|
||||
@@ -3727,6 +3727,7 @@ pub const highway = @import("./highway.zig");
|
||||
|
||||
pub const mach_port = if (Environment.isMac) std.c.mach_port_t else u32;
|
||||
|
||||
/// Automatically generated C++ bindings for functions marked with `[[ZIG_EXPORT(...)]]`
|
||||
pub const cpp = @import("cpp").bindings;
|
||||
|
||||
pub const asan = @import("./asan.zig");
|
||||
|
||||
@@ -649,7 +649,7 @@ pub const UpgradeCommand = struct {
|
||||
} else |_| {}
|
||||
}
|
||||
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> Failed to verify Bun (code: {s})<r>)", .{@errorName(err)});
|
||||
Output.prettyErrorln("<r><red>error<r><d>:<r> Failed to verify Bun (code: {s})<r>", .{@errorName(err)});
|
||||
Global.exit(1);
|
||||
};
|
||||
|
||||
|
||||
@@ -99,6 +99,18 @@ pub const PackageJSON = struct {
|
||||
exports: ?ExportsMap = null,
|
||||
imports: ?ExportsMap = null,
|
||||
|
||||
/// Normalize path separators to forward slashes for glob matching
|
||||
/// This is needed because glob patterns use forward slashes but Windows uses backslashes
|
||||
fn normalizePathForGlob(allocator: std.mem.Allocator, path: []const u8) ![]u8 {
|
||||
const normalized = try allocator.dupe(u8, path);
|
||||
for (normalized) |*char| {
|
||||
if (char.* == '\\') {
|
||||
char.* = '/';
|
||||
}
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
pub const SideEffects = union(enum) {
|
||||
/// either `package.json` is missing "sideEffects", it is true, or some
|
||||
/// other unsupported value. Treat all files as side effects
|
||||
@@ -107,8 +119,10 @@ pub const PackageJSON = struct {
|
||||
false,
|
||||
/// "sideEffects": ["file.js", "other.js"]
|
||||
map: Map,
|
||||
// /// "sideEffects": ["side_effects/*.js"]
|
||||
// glob: TODO,
|
||||
/// "sideEffects": ["side_effects/*.js"]
|
||||
glob: GlobList,
|
||||
/// "sideEffects": ["file.js", "side_effects/*.js"] - mixed patterns
|
||||
mixed: MixedPatterns,
|
||||
|
||||
pub const Map = std.HashMapUnmanaged(
|
||||
bun.StringHashMapUnowned.Key,
|
||||
@@ -117,11 +131,46 @@ pub const PackageJSON = struct {
|
||||
80,
|
||||
);
|
||||
|
||||
pub const GlobList = std.ArrayListUnmanaged([]const u8);
|
||||
|
||||
pub const MixedPatterns = struct {
|
||||
exact: Map,
|
||||
globs: GlobList,
|
||||
};
|
||||
|
||||
pub fn hasSideEffects(side_effects: SideEffects, path: []const u8) bool {
|
||||
return switch (side_effects) {
|
||||
.unspecified => true,
|
||||
.false => false,
|
||||
.map => |map| map.contains(bun.StringHashMapUnowned.Key.init(path)),
|
||||
.glob => |glob_list| {
|
||||
// Normalize path for cross-platform glob matching
|
||||
const normalized_path = normalizePathForGlob(bun.default_allocator, path) catch return true;
|
||||
defer bun.default_allocator.free(normalized_path);
|
||||
|
||||
for (glob_list.items) |pattern| {
|
||||
if (glob.match(bun.default_allocator, pattern, normalized_path).matches()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
.mixed => |mixed| {
|
||||
// First check exact matches
|
||||
if (mixed.exact.contains(bun.StringHashMapUnowned.Key.init(path))) {
|
||||
return true;
|
||||
}
|
||||
// Then check glob patterns with normalized path
|
||||
const normalized_path = normalizePathForGlob(bun.default_allocator, path) catch return true;
|
||||
defer bun.default_allocator.free(normalized_path);
|
||||
|
||||
for (mixed.globs.items) |pattern| {
|
||||
if (glob.match(bun.default_allocator, pattern, normalized_path).matches()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -742,47 +791,110 @@ pub const PackageJSON = struct {
|
||||
}
|
||||
}
|
||||
|
||||
if (json.get("sideEffects")) |side_effects_field| outer: {
|
||||
if (json.get("sideEffects")) |side_effects_field| {
|
||||
if (side_effects_field.asBool()) |boolean| {
|
||||
if (!boolean)
|
||||
package_json.side_effects = .{ .false = {} };
|
||||
} else if (side_effects_field.asArray()) |array_| {
|
||||
var array = array_;
|
||||
// TODO: switch to only storing hashes
|
||||
var map = SideEffects.Map{};
|
||||
map.ensureTotalCapacity(allocator, array.array.items.len) catch unreachable;
|
||||
while (array.next()) |item| {
|
||||
if (item.asString(allocator)) |name| {
|
||||
// TODO: support RegExp using JavaScriptCore <> C++ bindings
|
||||
if (strings.containsChar(name, '*')) {
|
||||
// https://sourcegraph.com/search?q=context:global+file:package.json+sideEffects%22:+%5B&patternType=standard&sm=1&groupBy=repo
|
||||
// a lot of these seem to be css files which we don't care about for now anyway
|
||||
// so we can just skip them in here
|
||||
if (strings.eqlComptime(std.fs.path.extension(name), ".css"))
|
||||
continue;
|
||||
} else if (side_effects_field.data == .e_array) {
|
||||
// Handle arrays, including empty arrays
|
||||
if (side_effects_field.asArray()) |array_| {
|
||||
var array = array_;
|
||||
var map = SideEffects.Map{};
|
||||
var glob_list = SideEffects.GlobList{};
|
||||
var has_globs = false;
|
||||
var has_exact = false;
|
||||
|
||||
r.log.addWarning(
|
||||
&json_source,
|
||||
item.loc,
|
||||
"wildcard sideEffects are not supported yet, which means this package will be deoptimized",
|
||||
) catch unreachable;
|
||||
map.deinit(allocator);
|
||||
|
||||
package_json.side_effects = .{ .unspecified = {} };
|
||||
break :outer;
|
||||
// First pass: check if we have glob patterns and exact patterns
|
||||
while (array.next()) |item| {
|
||||
if (item.asString(allocator)) |name| {
|
||||
if (strings.containsChar(name, '*') or strings.containsChar(name, '?') or strings.containsChar(name, '[') or strings.containsChar(name, '{')) {
|
||||
has_globs = true;
|
||||
} else {
|
||||
has_exact = true;
|
||||
}
|
||||
}
|
||||
|
||||
var joined = [_]string{
|
||||
json_source.path.name.dirWithTrailingSlash(),
|
||||
name,
|
||||
};
|
||||
|
||||
_ = map.getOrPutAssumeCapacity(
|
||||
bun.StringHashMapUnowned.Key.init(r.fs.join(&joined)),
|
||||
);
|
||||
}
|
||||
|
||||
// Reset array for second pass
|
||||
array = array_;
|
||||
|
||||
// If the array is empty, treat it as false (no side effects)
|
||||
if (!has_globs and !has_exact) {
|
||||
package_json.side_effects = .{ .false = {} };
|
||||
} else if (has_globs and has_exact) {
|
||||
// Mixed patterns - use both exact and glob matching
|
||||
map.ensureTotalCapacity(allocator, array.array.items.len) catch unreachable;
|
||||
glob_list.ensureTotalCapacity(allocator, array.array.items.len) catch unreachable;
|
||||
|
||||
while (array.next()) |item| {
|
||||
if (item.asString(allocator)) |name| {
|
||||
// Skip CSS files as they're not relevant for tree-shaking
|
||||
if (strings.eqlComptime(std.fs.path.extension(name), ".css"))
|
||||
continue;
|
||||
|
||||
// Store the pattern relative to the package directory
|
||||
var joined = [_]string{
|
||||
json_source.path.name.dirWithTrailingSlash(),
|
||||
name,
|
||||
};
|
||||
|
||||
const pattern = r.fs.join(&joined);
|
||||
|
||||
if (strings.containsChar(name, '*') or strings.containsChar(name, '?') or strings.containsChar(name, '[') or strings.containsChar(name, '{')) {
|
||||
// Normalize pattern to use forward slashes for cross-platform compatibility
|
||||
const normalized_pattern = normalizePathForGlob(allocator, pattern) catch pattern;
|
||||
glob_list.appendAssumeCapacity(normalized_pattern);
|
||||
} else {
|
||||
_ = map.getOrPutAssumeCapacity(
|
||||
bun.StringHashMapUnowned.Key.init(pattern),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
package_json.side_effects = .{ .mixed = .{ .exact = map, .globs = glob_list } };
|
||||
} else if (has_globs) {
|
||||
// Only glob patterns
|
||||
glob_list.ensureTotalCapacity(allocator, array.array.items.len) catch unreachable;
|
||||
while (array.next()) |item| {
|
||||
if (item.asString(allocator)) |name| {
|
||||
// Skip CSS files as they're not relevant for tree-shaking
|
||||
if (strings.eqlComptime(std.fs.path.extension(name), ".css"))
|
||||
continue;
|
||||
|
||||
// Store the pattern relative to the package directory
|
||||
var joined = [_]string{
|
||||
json_source.path.name.dirWithTrailingSlash(),
|
||||
name,
|
||||
};
|
||||
|
||||
const pattern = r.fs.join(&joined);
|
||||
// Normalize pattern to use forward slashes for cross-platform compatibility
|
||||
const normalized_pattern = normalizePathForGlob(allocator, pattern) catch pattern;
|
||||
glob_list.appendAssumeCapacity(normalized_pattern);
|
||||
}
|
||||
}
|
||||
package_json.side_effects = .{ .glob = glob_list };
|
||||
} else {
|
||||
// Only exact matches
|
||||
map.ensureTotalCapacity(allocator, array.array.items.len) catch unreachable;
|
||||
while (array.next()) |item| {
|
||||
if (item.asString(allocator)) |name| {
|
||||
var joined = [_]string{
|
||||
json_source.path.name.dirWithTrailingSlash(),
|
||||
name,
|
||||
};
|
||||
|
||||
_ = map.getOrPutAssumeCapacity(
|
||||
bun.StringHashMapUnowned.Key.init(r.fs.join(&joined)),
|
||||
);
|
||||
}
|
||||
}
|
||||
package_json.side_effects = .{ .map = map };
|
||||
}
|
||||
} else {
|
||||
// Empty array - treat as false (no side effects)
|
||||
package_json.side_effects = .{ .false = {} };
|
||||
}
|
||||
package_json.side_effects = .{ .map = map };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2044,6 +2156,7 @@ const MainFieldMap = bun.StringMap;
|
||||
const Output = bun.Output;
|
||||
const StoredFileDescriptorType = bun.StoredFileDescriptorType;
|
||||
const default_allocator = bun.default_allocator;
|
||||
const glob = bun.glob;
|
||||
const js_ast = bun.ast;
|
||||
const js_lexer = bun.js_lexer;
|
||||
const logger = bun.logger;
|
||||
|
||||
@@ -939,12 +939,14 @@ pub const Resolver = struct {
|
||||
// if we don't have it here, they might put it in a sideEfffects
|
||||
// map of the parent package.json
|
||||
// TODO: check if webpack also does this parent lookup
|
||||
needs_side_effects = existing.side_effects == .unspecified;
|
||||
needs_side_effects = existing.side_effects == .unspecified or existing.side_effects == .glob or existing.side_effects == .mixed;
|
||||
|
||||
result.primary_side_effects_data = switch (existing.side_effects) {
|
||||
.unspecified => .has_side_effects,
|
||||
.false => .no_side_effects__package_json,
|
||||
.map => |map| if (map.contains(bun.StringHashMapUnowned.Key.init(path.text))) .has_side_effects else .no_side_effects__package_json,
|
||||
.glob => if (existing.side_effects.hasSideEffects(path.text)) .has_side_effects else .no_side_effects__package_json,
|
||||
.mixed => if (existing.side_effects.hasSideEffects(path.text)) .has_side_effects else .no_side_effects__package_json,
|
||||
};
|
||||
|
||||
if (existing.name.len == 0 or r.care_about_bin_folder) result.package_json = null;
|
||||
@@ -958,6 +960,8 @@ pub const Resolver = struct {
|
||||
.unspecified => .has_side_effects,
|
||||
.false => .no_side_effects__package_json,
|
||||
.map => |map| if (map.contains(bun.StringHashMapUnowned.Key.init(path.text))) .has_side_effects else .no_side_effects__package_json,
|
||||
.glob => if (package_json.side_effects.hasSideEffects(path.text)) .has_side_effects else .no_side_effects__package_json,
|
||||
.mixed => if (package_json.side_effects.hasSideEffects(path.text)) .has_side_effects else .no_side_effects__package_json,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,433 @@ pub fn decodeInternal(this: *@This(), comptime Container: type, reader: NewReade
|
||||
|
||||
pub const decode = DecoderWrap(ErrorResponse, decodeInternal).decode;
|
||||
|
||||
// PostgreSQL Error Code Constants
|
||||
const UNIQUE_VIOLATION = "23505";
|
||||
const NOT_NULL_VIOLATION = "23502";
|
||||
const FOREIGN_KEY_VIOLATION = "23503";
|
||||
const CHECK_VIOLATION = "23514";
|
||||
const SYNTAX_ERROR = "42601";
|
||||
const UNDEFINED_TABLE = "42P01";
|
||||
const UNDEFINED_COLUMN = "42703";
|
||||
|
||||
const ERROR_CODE_MAP = std.StaticStringMap([]const u8).initComptime(.{
|
||||
.{ "00000", "successful_completion" },
|
||||
.{ "01000", "warning" },
|
||||
.{ "0100C", "dynamic_result_sets_returned" },
|
||||
.{ "01008", "implicit_zero_bit_padding" },
|
||||
.{ "01003", "null_value_eliminated_in_set_function" },
|
||||
.{ "01007", "privilege_not_granted" },
|
||||
.{ "01006", "privilege_not_revoked" },
|
||||
.{ "01004", "string_data_right_truncation" },
|
||||
.{ "01P01", "deprecated_feature" },
|
||||
.{ "02000", "no_data" },
|
||||
.{ "02001", "no_additional_dynamic_result_sets_returned" },
|
||||
.{ "03000", "sql_statement_not_yet_complete" },
|
||||
.{ "08000", "connection_exception" },
|
||||
.{ "08003", "connection_does_not_exist" },
|
||||
.{ "08006", "connection_failure" },
|
||||
.{ "08001", "sqlclient_unable_to_establish_sqlconnection" },
|
||||
.{ "08004", "sqlserver_rejected_establishment_of_sqlconnection" },
|
||||
.{ "08007", "transaction_resolution_unknown" },
|
||||
.{ "08P01", "protocol_violation" },
|
||||
.{ "09000", "triggered_action_exception" },
|
||||
.{ "0A000", "feature_not_supported" },
|
||||
.{ "0B000", "invalid_transaction_initiation" },
|
||||
.{ "0F000", "locator_exception" },
|
||||
.{ "0F001", "invalid_locator_specification" },
|
||||
.{ "0L000", "invalid_grantor" },
|
||||
.{ "0LP01", "invalid_grant_operation" },
|
||||
.{ "0P000", "invalid_role_specification" },
|
||||
.{ "0Z000", "diagnostics_exception" },
|
||||
.{ "0Z002", "stacked_diagnostics_accessed_without_active_handler" },
|
||||
.{ "20000", "case_not_found" },
|
||||
.{ "21000", "cardinality_violation" },
|
||||
.{ "22000", "data_exception" },
|
||||
.{ "2202E", "array_subscript_error" },
|
||||
.{ "22021", "character_not_in_repertoire" },
|
||||
.{ "22008", "datetime_field_overflow" },
|
||||
.{ "22012", "division_by_zero" },
|
||||
.{ "22005", "error_in_assignment" },
|
||||
.{ "2200B", "escape_character_conflict" },
|
||||
.{ "22022", "indicator_overflow" },
|
||||
.{ "22015", "interval_field_overflow" },
|
||||
.{ "2201E", "invalid_argument_for_logarithm" },
|
||||
.{ "22014", "invalid_argument_for_ntile_function" },
|
||||
.{ "22016", "invalid_argument_for_nth_value_function" },
|
||||
.{ "2201F", "invalid_argument_for_power_function" },
|
||||
.{ "2201G", "invalid_argument_for_width_bucket_function" },
|
||||
.{ "22018", "invalid_character_value_for_cast" },
|
||||
.{ "22007", "invalid_datetime_format" },
|
||||
.{ "22019", "invalid_escape_character" },
|
||||
.{ "2200D", "invalid_escape_octet" },
|
||||
.{ "22025", "invalid_escape_sequence" },
|
||||
.{ "22P06", "nonstandard_use_of_escape_character" },
|
||||
.{ "22010", "invalid_indicator_parameter_value" },
|
||||
.{ "22023", "invalid_parameter_value" },
|
||||
.{ "2201B", "invalid_regular_expression" },
|
||||
.{ "2201W", "invalid_row_count_in_limit_clause" },
|
||||
.{ "2201X", "invalid_row_count_in_result_offset_clause" },
|
||||
.{ "2202H", "invalid_tablesample_argument" },
|
||||
.{ "2202G", "invalid_tablesample_repeat" },
|
||||
.{ "22009", "invalid_time_zone_displacement_value" },
|
||||
.{ "2200C", "invalid_use_of_escape_character" },
|
||||
.{ "2200G", "most_specific_type_mismatch" },
|
||||
.{ "22004", "null_value_not_allowed" },
|
||||
.{ "22002", "null_value_no_indicator_parameter" },
|
||||
.{ "22003", "numeric_value_out_of_range" },
|
||||
.{ "2200H", "sequence_generator_limit_exceeded" },
|
||||
.{ "22026", "string_data_length_mismatch" },
|
||||
.{ "22001", "string_data_right_truncation" },
|
||||
.{ "22011", "substring_error" },
|
||||
.{ "22027", "trim_error" },
|
||||
.{ "22024", "unterminated_c_string" },
|
||||
.{ "2200F", "zero_length_character_string" },
|
||||
.{ "22P01", "floating_point_exception" },
|
||||
.{ "22P02", "invalid_text_representation" },
|
||||
.{ "22P03", "invalid_binary_representation" },
|
||||
.{ "22P04", "bad_copy_file_format" },
|
||||
.{ "22P05", "untranslatable_character" },
|
||||
.{ "2200L", "not_an_xml_document" },
|
||||
.{ "2200M", "invalid_xml_document" },
|
||||
.{ "2200N", "invalid_xml_content" },
|
||||
.{ "2200S", "invalid_xml_comment" },
|
||||
.{ "2200T", "invalid_xml_processing_instruction" },
|
||||
.{ "23000", "integrity_constraint_violation" },
|
||||
.{ "23001", "restrict_violation" },
|
||||
.{ NOT_NULL_VIOLATION, "not_null_violation" },
|
||||
.{ FOREIGN_KEY_VIOLATION, "foreign_key_violation" },
|
||||
.{ UNIQUE_VIOLATION, "unique_violation" },
|
||||
.{ CHECK_VIOLATION, "check_violation" },
|
||||
.{ "23P01", "exclusion_violation" },
|
||||
.{ "24000", "invalid_cursor_state" },
|
||||
.{ "25000", "invalid_transaction_state" },
|
||||
.{ "25001", "active_sql_transaction" },
|
||||
.{ "25002", "branch_transaction_already_active" },
|
||||
.{ "25008", "held_cursor_requires_same_isolation_level" },
|
||||
.{ "25003", "inappropriate_access_mode_for_branch_transaction" },
|
||||
.{ "25004", "inappropriate_isolation_level_for_branch_transaction" },
|
||||
.{ "25005", "no_active_sql_transaction_for_branch_transaction" },
|
||||
.{ "25006", "read_only_sql_transaction" },
|
||||
.{ "25007", "schema_and_data_statement_mixing_not_supported" },
|
||||
.{ "25P01", "no_active_sql_transaction" },
|
||||
.{ "25P02", "in_failed_sql_transaction" },
|
||||
.{ "25P03", "idle_in_transaction_session_timeout" },
|
||||
.{ "26000", "invalid_sql_statement_name" },
|
||||
.{ "27000", "triggered_data_change_violation" },
|
||||
.{ "28000", "invalid_authorization_specification" },
|
||||
.{ "28P01", "invalid_password" },
|
||||
.{ "2B000", "dependent_privilege_descriptors_still_exist" },
|
||||
.{ "2BP01", "dependent_objects_still_exist" },
|
||||
.{ "2D000", "invalid_transaction_termination" },
|
||||
.{ "2F000", "sql_routine_exception" },
|
||||
.{ "2F005", "function_executed_no_return_statement" },
|
||||
.{ "2F002", "modifying_sql_data_not_permitted" },
|
||||
.{ "2F003", "prohibited_sql_statement_attempted" },
|
||||
.{ "2F004", "reading_sql_data_not_permitted" },
|
||||
.{ "34000", "invalid_cursor_name" },
|
||||
.{ "38000", "external_routine_exception" },
|
||||
.{ "38001", "containing_sql_not_permitted" },
|
||||
.{ "38002", "modifying_sql_data_not_permitted" },
|
||||
.{ "38003", "prohibited_sql_statement_attempted" },
|
||||
.{ "38004", "reading_sql_data_not_permitted" },
|
||||
.{ "39000", "external_routine_invocation_exception" },
|
||||
.{ "39001", "invalid_sqlstate_returned" },
|
||||
.{ "39004", "null_value_not_allowed" },
|
||||
.{ "39P01", "trigger_protocol_violated" },
|
||||
.{ "39P02", "srf_protocol_violated" },
|
||||
.{ "39P03", "event_trigger_protocol_violated" },
|
||||
.{ "3B000", "savepoint_exception" },
|
||||
.{ "3B001", "invalid_savepoint_specification" },
|
||||
.{ "3D000", "invalid_catalog_name" },
|
||||
.{ "3F000", "invalid_schema_name" },
|
||||
.{ "40000", "transaction_rollback" },
|
||||
.{ "40002", "transaction_integrity_constraint_violation" },
|
||||
.{ "40001", "serialization_failure" },
|
||||
.{ "40003", "statement_completion_unknown" },
|
||||
.{ "40P01", "deadlock_detected" },
|
||||
.{ "42000", "syntax_error_or_access_rule_violation" },
|
||||
.{ SYNTAX_ERROR, "syntax_error" },
|
||||
.{ "42501", "insufficient_privilege" },
|
||||
.{ "42846", "cannot_coerce" },
|
||||
.{ "42803", "grouping_error" },
|
||||
.{ "42P20", "windowing_error" },
|
||||
.{ "42P19", "invalid_recursion" },
|
||||
.{ "42830", "invalid_foreign_key" },
|
||||
.{ "42602", "invalid_name" },
|
||||
.{ "42622", "name_too_long" },
|
||||
.{ "42939", "reserved_name" },
|
||||
.{ "42804", "datatype_mismatch" },
|
||||
.{ "42P18", "indeterminate_datatype" },
|
||||
.{ "42P21", "collation_mismatch" },
|
||||
.{ "42P22", "indeterminate_collation" },
|
||||
.{ "42809", "wrong_object_type" },
|
||||
.{ "428C9", "generated_always" },
|
||||
.{ UNDEFINED_COLUMN, "undefined_column" },
|
||||
.{ "42883", "undefined_function" },
|
||||
.{ UNDEFINED_TABLE, "undefined_table" },
|
||||
.{ "42P02", "undefined_parameter" },
|
||||
.{ "42704", "undefined_object" },
|
||||
.{ "42701", "duplicate_column" },
|
||||
.{ "42P03", "duplicate_cursor" },
|
||||
.{ "42P04", "duplicate_database" },
|
||||
.{ "42723", "duplicate_function" },
|
||||
.{ "42P05", "duplicate_prepared_statement" },
|
||||
.{ "42P06", "duplicate_schema" },
|
||||
.{ "42P07", "duplicate_table" },
|
||||
.{ "42712", "duplicate_alias" },
|
||||
.{ "42710", "duplicate_object" },
|
||||
.{ "42702", "ambiguous_column" },
|
||||
.{ "42725", "ambiguous_function" },
|
||||
.{ "42P08", "ambiguous_parameter" },
|
||||
.{ "42P09", "ambiguous_alias" },
|
||||
.{ "42P10", "invalid_column_reference" },
|
||||
.{ "42611", "invalid_column_definition" },
|
||||
.{ "42P11", "invalid_cursor_definition" },
|
||||
.{ "42P12", "invalid_database_definition" },
|
||||
.{ "42P13", "invalid_function_definition" },
|
||||
.{ "42P14", "invalid_prepared_statement_definition" },
|
||||
.{ "42P15", "invalid_schema_definition" },
|
||||
.{ "42P16", "invalid_table_definition" },
|
||||
.{ "42P17", "invalid_object_definition" },
|
||||
.{ "44000", "with_check_option_violation" },
|
||||
.{ "53000", "insufficient_resources" },
|
||||
.{ "53100", "disk_full" },
|
||||
.{ "53200", "out_of_memory" },
|
||||
.{ "53300", "too_many_connections" },
|
||||
.{ "53400", "configuration_limit_exceeded" },
|
||||
.{ "54000", "program_limit_exceeded" },
|
||||
.{ "54001", "statement_too_complex" },
|
||||
.{ "54011", "too_many_columns" },
|
||||
.{ "54023", "too_many_arguments" },
|
||||
.{ "55000", "object_not_in_prerequisite_state" },
|
||||
.{ "55006", "object_in_use" },
|
||||
.{ "55P02", "cant_change_runtime_param" },
|
||||
.{ "55P03", "lock_not_available" },
|
||||
.{ "55P04", "unsafe_new_enum_value_usage" },
|
||||
.{ "57000", "operator_intervention" },
|
||||
.{ "57014", "query_canceled" },
|
||||
.{ "57P01", "admin_shutdown" },
|
||||
.{ "57P02", "crash_shutdown" },
|
||||
.{ "57P03", "cannot_connect_now" },
|
||||
.{ "57P04", "database_dropped" },
|
||||
.{ "58000", "system_error" },
|
||||
.{ "58030", "io_error" },
|
||||
.{ "58P01", "undefined_file" },
|
||||
.{ "58P02", "duplicate_file" },
|
||||
.{ "72000", "snapshot_too_old" },
|
||||
.{ "F0000", "config_file_error" },
|
||||
.{ "F0001", "lock_file_exists" },
|
||||
.{ "HV000", "fdw_error" },
|
||||
.{ "HV005", "fdw_column_name_not_found" },
|
||||
.{ "HV002", "fdw_dynamic_parameter_value_needed" },
|
||||
.{ "HV010", "fdw_function_sequence_error" },
|
||||
.{ "HV021", "fdw_inconsistent_descriptor_information" },
|
||||
.{ "HV024", "fdw_invalid_attribute_value" },
|
||||
.{ "HV007", "fdw_invalid_column_name" },
|
||||
.{ "HV008", "fdw_invalid_column_number" },
|
||||
.{ "HV004", "fdw_invalid_data_type" },
|
||||
.{ "HV006", "fdw_invalid_data_type_descriptors" },
|
||||
.{ "HV091", "fdw_invalid_descriptor_field_identifier" },
|
||||
.{ "HV00B", "fdw_invalid_handle" },
|
||||
.{ "HV00C", "fdw_invalid_option_index" },
|
||||
.{ "HV00D", "fdw_invalid_option_name" },
|
||||
.{ "HV090", "fdw_invalid_string_length_or_buffer_length" },
|
||||
.{ "HV00A", "fdw_invalid_string_format" },
|
||||
.{ "HV009", "fdw_invalid_use_of_null_pointer" },
|
||||
.{ "HV014", "fdw_too_many_handles" },
|
||||
.{ "HV001", "fdw_out_of_memory" },
|
||||
.{ "HV00P", "fdw_no_schemas" },
|
||||
.{ "HV00J", "fdw_option_name_not_found" },
|
||||
.{ "HV00K", "fdw_reply_handle" },
|
||||
.{ "HV00Q", "fdw_schema_not_found" },
|
||||
.{ "HV00R", "fdw_table_not_found" },
|
||||
.{ "HV00L", "fdw_unable_to_create_execution" },
|
||||
.{ "HV00M", "fdw_unable_to_create_reply" },
|
||||
.{ "HV00N", "fdw_unable_to_establish_connection" },
|
||||
.{ "P0000", "plpgsql_error" },
|
||||
.{ "P0001", "raise_exception" },
|
||||
.{ "P0002", "no_data_found" },
|
||||
.{ "P0003", "too_many_rows" },
|
||||
.{ "P0004", "assert_failure" },
|
||||
.{ "XX000", "internal_error" },
|
||||
.{ "XX001", "data_corrupted" },
|
||||
.{ "XX002", "index_corrupted" },
|
||||
});
|
||||
|
||||
fn getConditionName(error_code: String) ?[]const u8 {
|
||||
if (error_code.isEmpty()) return null;
|
||||
|
||||
const code_str = error_code.toUTF8WithoutRef(bun.default_allocator);
|
||||
defer code_str.deinit();
|
||||
|
||||
return ERROR_CODE_MAP.get(code_str.slice());
|
||||
}
|
||||
|
||||
const KeyValuePair = struct {
|
||||
key: []const u8,
|
||||
value: []const u8,
|
||||
};
|
||||
|
||||
const ErrorDetailInfo = struct {
|
||||
key_value: ?KeyValuePair = null,
|
||||
column: ?[]const u8 = null,
|
||||
table: ?[]const u8 = null,
|
||||
constraint: ?[]const u8 = null,
|
||||
referenced_table: ?[]const u8 = null,
|
||||
referenced_column: ?[]const u8 = null,
|
||||
check_constraint: ?[]const u8 = null,
|
||||
violating_value: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
fn parseDetailForErrorType(error_code: String, detail: String, allocator: std.mem.Allocator) ?ErrorDetailInfo {
|
||||
if (detail.isEmpty()) return null;
|
||||
|
||||
const detail_str = detail.toUTF8WithoutRef(allocator);
|
||||
defer detail_str.deinit();
|
||||
const detail_slice = detail_str.slice();
|
||||
|
||||
if (error_code.eqlComptime(UNIQUE_VIOLATION)) {
|
||||
// Parse unique constraint violation: "Key (column_name)=(value) already exists."
|
||||
return parseUniqueViolationDetail(detail_slice, allocator);
|
||||
} else if (error_code.eqlComptime(FOREIGN_KEY_VIOLATION)) {
|
||||
// Parse foreign key violation: "Key (column)=(value) is not present in table "table_name"."
|
||||
return parseForeignKeyViolationDetail(detail_slice, allocator);
|
||||
} else if (error_code.eqlComptime(NOT_NULL_VIOLATION)) {
|
||||
// Parse not null violation: "null value in column "column_name" violates not-null constraint"
|
||||
return parseNotNullViolationDetail(detail_slice, allocator);
|
||||
} else if (error_code.eqlComptime(CHECK_VIOLATION)) {
|
||||
// Parse check constraint violation: 'new row for relation "table" violates check constraint "constraint_name"'
|
||||
return parseCheckViolationDetail(detail_slice, allocator);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn parseUniqueViolationDetail(detail_slice: []const u8, allocator: std.mem.Allocator) ?ErrorDetailInfo {
|
||||
// Parse format: "Key (column_name)=(value) already exists."
|
||||
if (std.mem.indexOf(u8, detail_slice, "Key (")) |start| {
|
||||
const after_key = start + 5; // "Key (".len
|
||||
if (std.mem.indexOf(u8, detail_slice[after_key..], ")=(")) |end_key_relative| {
|
||||
const end_key = after_key + end_key_relative;
|
||||
const key = detail_slice[after_key..end_key];
|
||||
|
||||
const value_start = end_key + 3; // ")=(".len
|
||||
if (std.mem.indexOf(u8, detail_slice[value_start..], ") ")) |end_value_relative| {
|
||||
const end_value = value_start + end_value_relative;
|
||||
const value = detail_slice[value_start..end_value];
|
||||
|
||||
// Allocate and copy the strings
|
||||
const key_copy = allocator.dupe(u8, key) catch return null;
|
||||
const value_copy = allocator.dupe(u8, value) catch {
|
||||
allocator.free(key_copy);
|
||||
return null;
|
||||
};
|
||||
|
||||
return ErrorDetailInfo{
|
||||
.key_value = KeyValuePair{ .key = key_copy, .value = value_copy },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn parseForeignKeyViolationDetail(detail_slice: []const u8, allocator: std.mem.Allocator) ?ErrorDetailInfo {
|
||||
var result = ErrorDetailInfo{};
|
||||
|
||||
// Parse format: "Key (column)=(value) is not present in table "table_name"."
|
||||
if (std.mem.indexOf(u8, detail_slice, "Key (")) |start| {
|
||||
const after_key = start + 5; // "Key (".len
|
||||
if (std.mem.indexOf(u8, detail_slice[after_key..], ")=(")) |end_key_relative| {
|
||||
const end_key = after_key + end_key_relative;
|
||||
const key = detail_slice[after_key..end_key];
|
||||
|
||||
const value_start = end_key + 3; // ")=(".len
|
||||
if (std.mem.indexOf(u8, detail_slice[value_start..], ") ")) |end_value_relative| {
|
||||
const end_value = value_start + end_value_relative;
|
||||
const value = detail_slice[value_start..end_value];
|
||||
|
||||
// Allocate key/value
|
||||
const key_copy = allocator.dupe(u8, key) catch return null;
|
||||
const value_copy = allocator.dupe(u8, value) catch {
|
||||
allocator.free(key_copy);
|
||||
return null;
|
||||
};
|
||||
result.key_value = KeyValuePair{ .key = key_copy, .value = value_copy };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse referenced table: 'in table "table_name"'
|
||||
if (std.mem.indexOf(u8, detail_slice, "in table \"")) |table_start| {
|
||||
const table_name_start = table_start + 10; // "in table \"".len
|
||||
if (std.mem.indexOf(u8, detail_slice[table_name_start..], "\"")) |table_end_relative| {
|
||||
const table_end = table_name_start + table_end_relative;
|
||||
const table_name = detail_slice[table_name_start..table_end];
|
||||
result.referenced_table = allocator.dupe(u8, table_name) catch return result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fn parseNotNullViolationDetail(detail_slice: []const u8, allocator: std.mem.Allocator) ?ErrorDetailInfo {
|
||||
// Parse format: "null value in column "column_name" violates not-null constraint"
|
||||
if (std.mem.indexOf(u8, detail_slice, "null value in column \"")) |start| {
|
||||
const column_start = start + 22; // "null value in column \"".len
|
||||
if (std.mem.indexOf(u8, detail_slice[column_start..], "\"")) |column_end_relative| {
|
||||
const column_end = column_start + column_end_relative;
|
||||
const column_name = detail_slice[column_start..column_end];
|
||||
|
||||
const column_copy = allocator.dupe(u8, column_name) catch return null;
|
||||
return ErrorDetailInfo{
|
||||
.column = column_copy,
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn parseCheckViolationDetail(detail_slice: []const u8, allocator: std.mem.Allocator) ?ErrorDetailInfo {
|
||||
var result = ErrorDetailInfo{};
|
||||
|
||||
// Parse format: 'new row for relation "table_name" violates check constraint "constraint_name"'
|
||||
if (std.mem.indexOf(u8, detail_slice, "violates check constraint \"")) |constraint_start| {
|
||||
const constraint_name_start = constraint_start + 27; // "violates check constraint \"".len
|
||||
if (std.mem.indexOf(u8, detail_slice[constraint_name_start..], "\"")) |constraint_end_relative| {
|
||||
const constraint_end = constraint_name_start + constraint_end_relative;
|
||||
const constraint_name = detail_slice[constraint_name_start..constraint_end];
|
||||
result.check_constraint = allocator.dupe(u8, constraint_name) catch return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse table name: 'new row for relation "table_name"'
|
||||
if (std.mem.indexOf(u8, detail_slice, "new row for relation \"")) |table_start| {
|
||||
const table_name_start = table_start + 22; // "new row for relation \"".len
|
||||
if (std.mem.indexOf(u8, detail_slice[table_name_start..], "\"")) |table_end_relative| {
|
||||
const table_end = table_name_start + table_end_relative;
|
||||
const table_name = detail_slice[table_name_start..table_end];
|
||||
result.table = allocator.dupe(u8, table_name) catch return result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fn deinitErrorDetailInfo(info: ErrorDetailInfo, allocator: std.mem.Allocator) void {
|
||||
if (info.key_value) |kv| {
|
||||
allocator.free(kv.key);
|
||||
allocator.free(kv.value);
|
||||
}
|
||||
if (info.column) |col| allocator.free(col);
|
||||
if (info.table) |tbl| allocator.free(tbl);
|
||||
if (info.constraint) |cst| allocator.free(cst);
|
||||
if (info.referenced_table) |ref_tbl| allocator.free(ref_tbl);
|
||||
if (info.referenced_column) |ref_col| allocator.free(ref_col);
|
||||
if (info.check_constraint) |check_cst| allocator.free(check_cst);
|
||||
if (info.violating_value) |val| allocator.free(val);
|
||||
}
|
||||
|
||||
pub fn toJS(this: ErrorResponse, globalObject: *jsc.JSGlobalObject) JSValue {
|
||||
var b = bun.StringBuilder{};
|
||||
defer b.deinit(bun.default_allocator);
|
||||
@@ -106,6 +533,16 @@ pub fn toJS(this: ErrorResponse, globalObject: *jsc.JSGlobalObject) JSValue {
|
||||
}
|
||||
}
|
||||
|
||||
// Parse detailed error information from various PostgreSQL error types
|
||||
var error_detail_info: ?ErrorDetailInfo = null;
|
||||
defer if (error_detail_info) |info| {
|
||||
deinitErrorDetailInfo(info, bun.default_allocator);
|
||||
};
|
||||
|
||||
if (!code.isEmpty() and !detail.isEmpty()) {
|
||||
error_detail_info = parseDetailForErrorType(code, detail, bun.default_allocator);
|
||||
}
|
||||
|
||||
const possible_fields = .{
|
||||
.{ "detail", detail, void },
|
||||
.{ "hint", hint, void },
|
||||
@@ -121,7 +558,7 @@ pub fn toJS(this: ErrorResponse, globalObject: *jsc.JSGlobalObject) JSValue {
|
||||
};
|
||||
const error_code: jsc.Error =
|
||||
// https://www.postgresql.org/docs/8.1/errcodes-appendix.html
|
||||
if (code.eqlComptime("42601"))
|
||||
if (code.eqlComptime(SYNTAX_ERROR))
|
||||
.POSTGRES_SYNTAX_ERROR
|
||||
else
|
||||
.POSTGRES_SERVER_ERROR;
|
||||
@@ -143,6 +580,54 @@ pub fn toJS(this: ErrorResponse, globalObject: *jsc.JSGlobalObject) JSValue {
|
||||
}
|
||||
}
|
||||
|
||||
// Add condition name if we have an error code
|
||||
if (!code.isEmpty()) {
|
||||
if (getConditionName(code)) |condition_name| {
|
||||
err.put(globalObject, jsc.ZigString.static("condition"), jsc.ZigString.init(condition_name).toJS(globalObject));
|
||||
}
|
||||
}
|
||||
|
||||
// Add parsed detail information fields
|
||||
if (error_detail_info) |info| {
|
||||
// Add key and value fields (for unique/foreign key violations)
|
||||
if (info.key_value) |kv| {
|
||||
err.put(globalObject, jsc.ZigString.static("key"), jsc.ZigString.init(kv.key).toJS(globalObject));
|
||||
err.put(globalObject, jsc.ZigString.static("value"), jsc.ZigString.init(kv.value).toJS(globalObject));
|
||||
}
|
||||
|
||||
// Add column field (for not null violations, etc.)
|
||||
if (info.column) |col| {
|
||||
err.put(globalObject, jsc.ZigString.static("failing_column"), jsc.ZigString.init(col).toJS(globalObject));
|
||||
}
|
||||
|
||||
// Add referenced table field (for foreign key violations)
|
||||
if (info.referenced_table) |ref_tbl| {
|
||||
err.put(globalObject, jsc.ZigString.static("referenced_table"), jsc.ZigString.init(ref_tbl).toJS(globalObject));
|
||||
}
|
||||
|
||||
// Add other parsed fields as needed
|
||||
if (info.table) |tbl| {
|
||||
err.put(globalObject, jsc.ZigString.static("failing_table"), jsc.ZigString.init(tbl).toJS(globalObject));
|
||||
}
|
||||
|
||||
if (info.constraint) |cst| {
|
||||
err.put(globalObject, jsc.ZigString.static("failing_constraint"), jsc.ZigString.init(cst).toJS(globalObject));
|
||||
}
|
||||
|
||||
if (info.referenced_column) |ref_col| {
|
||||
err.put(globalObject, jsc.ZigString.static("referenced_column"), jsc.ZigString.init(ref_col).toJS(globalObject));
|
||||
}
|
||||
|
||||
// Add check constraint specific fields
|
||||
if (info.check_constraint) |check_cst| {
|
||||
err.put(globalObject, jsc.ZigString.static("check_constraint"), jsc.ZigString.init(check_cst).toJS(globalObject));
|
||||
}
|
||||
|
||||
if (info.violating_value) |val| {
|
||||
err.put(globalObject, jsc.ZigString.static("violating_value"), jsc.ZigString.init(val).toJS(globalObject));
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
@@ -320,7 +320,6 @@ describe("bundler", () => {
|
||||
},
|
||||
});
|
||||
itBundled("dce/PackageJsonSideEffectsArrayRemove", {
|
||||
todo: true,
|
||||
files: {
|
||||
"/Users/user/project/src/entry.js": /* js */ `
|
||||
import {foo} from "demo-pkg"
|
||||
@@ -596,7 +595,6 @@ describe("bundler", () => {
|
||||
},
|
||||
});
|
||||
itBundled("dce/PackageJsonSideEffectsArrayGlob", {
|
||||
todo: true,
|
||||
files: {
|
||||
"/Users/user/project/src/entry.js": /* js */ `
|
||||
import "demo-pkg/keep/this/file"
|
||||
@@ -619,6 +617,245 @@ describe("bundler", () => {
|
||||
stdout: "this should be kept",
|
||||
},
|
||||
});
|
||||
itBundled("dce/PackageJsonSideEffectsGlobBasicPattern", {
|
||||
files: {
|
||||
"/Users/user/project/src/entry.js": /* js */ `
|
||||
import { used } from "demo-pkg/lib/used.js";
|
||||
import { unused } from "demo-pkg/lib/unused.js";
|
||||
import { effect } from "demo-pkg/effects/effect.js";
|
||||
console.log(used);
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/lib/used.js": `export const used = "used";`,
|
||||
"/Users/user/project/node_modules/demo-pkg/lib/unused.js": /* js */ `
|
||||
export const unused = "unused";
|
||||
console.log("should be tree-shaken");
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/effects/effect.js": /* js */ `
|
||||
console.log("side effect preserved");
|
||||
export const effect = "effect";
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/package.json": /* json */ `
|
||||
{
|
||||
"sideEffects": ["./effects/*.js"]
|
||||
}
|
||||
`,
|
||||
},
|
||||
dce: true,
|
||||
run: {
|
||||
stdout: "side effect preserved\nused",
|
||||
},
|
||||
});
|
||||
itBundled("dce/PackageJsonSideEffectsGlobQuestionMark", {
|
||||
files: {
|
||||
"/Users/user/project/src/entry.js": /* js */ `
|
||||
import { file1 } from "demo-pkg/file1.js";
|
||||
import { file2 } from "demo-pkg/file2.js";
|
||||
import { fileAB } from "demo-pkg/fileAB.js";
|
||||
import { other } from "demo-pkg/other.js";
|
||||
console.log("done");
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/file1.js": /* js */ `
|
||||
console.log("file1 effect");
|
||||
export const file1 = "file1";
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/file2.js": /* js */ `
|
||||
console.log("file2 effect");
|
||||
export const file2 = "file2";
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/fileAB.js": /* js */ `
|
||||
console.log("fileAB effect");
|
||||
export const fileAB = "fileAB";
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/other.js": /* js */ `
|
||||
console.log("other effect");
|
||||
export const other = "other";
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/package.json": /* json */ `
|
||||
{
|
||||
"sideEffects": ["./file?.js"]
|
||||
}
|
||||
`,
|
||||
},
|
||||
dce: true,
|
||||
run: {
|
||||
stdout: "file1 effect\nfile2 effect\ndone",
|
||||
},
|
||||
});
|
||||
itBundled("dce/PackageJsonSideEffectsGlobBraceExpansion", {
|
||||
files: {
|
||||
"/Users/user/project/src/entry.js": /* js */ `
|
||||
import { comp } from "demo-pkg/components/comp.js";
|
||||
import { util } from "demo-pkg/utils/util.js";
|
||||
import { other } from "demo-pkg/other/file.js";
|
||||
console.log("done");
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/components/comp.js": /* js */ `
|
||||
console.log("component effect");
|
||||
export const comp = "comp";
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/utils/util.js": /* js */ `
|
||||
console.log("utility effect");
|
||||
export const util = "util";
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/other/file.js": /* js */ `
|
||||
console.log("other effect");
|
||||
export const other = "other";
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/package.json": /* json */ `
|
||||
{
|
||||
"sideEffects": ["./{components,utils}/*.js"]
|
||||
}
|
||||
`,
|
||||
},
|
||||
dce: true,
|
||||
run: {
|
||||
stdout: "component effect\nutility effect\ndone",
|
||||
},
|
||||
});
|
||||
itBundled("dce/PackageJsonSideEffectsGlobMixedPatterns", {
|
||||
files: {
|
||||
"/Users/user/project/src/entry.js": /* js */ `
|
||||
import { used } from "demo-pkg/lib/used.js";
|
||||
import { specific } from "demo-pkg/lib/specific.js";
|
||||
import { glob1 } from "demo-pkg/lib/glob/glob1.js";
|
||||
import { glob2 } from "demo-pkg/lib/glob/glob2.js";
|
||||
import { other } from "demo-pkg/lib/other.js";
|
||||
console.log(used);
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/lib/used.js": `export const used = "used";`,
|
||||
"/Users/user/project/node_modules/demo-pkg/lib/specific.js": /* js */ `
|
||||
console.log("specific side effect");
|
||||
export const specific = "specific";
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/lib/glob/glob1.js": /* js */ `
|
||||
console.log("glob1 side effect");
|
||||
export const glob1 = "glob1";
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/lib/glob/glob2.js": /* js */ `
|
||||
console.log("glob2 side effect");
|
||||
export const glob2 = "glob2";
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/lib/other.js": /* js */ `
|
||||
console.log("other side effect");
|
||||
export const other = "other";
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/package.json": /* json */ `
|
||||
{
|
||||
"sideEffects": ["./lib/specific.js", "./lib/glob/*.js"]
|
||||
}
|
||||
`,
|
||||
},
|
||||
dce: true,
|
||||
run: {
|
||||
stdout: "specific side effect\nglob1 side effect\nglob2 side effect\nused",
|
||||
},
|
||||
});
|
||||
itBundled("dce/PackageJsonSideEffectsGlobDeepPattern", {
|
||||
files: {
|
||||
"/Users/user/project/src/entry.js": /* js */ `
|
||||
import "demo-pkg/shallow.js";
|
||||
import "demo-pkg/components/effects/deep.js";
|
||||
import "demo-pkg/utils/helpers/effects/nested.js";
|
||||
console.log("done");
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/shallow.js": /* js */ `
|
||||
console.log("shallow side effect - should be tree shaken");
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/components/effects/deep.js": /* js */ `
|
||||
console.log("deep side effect - should be preserved");
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/utils/helpers/effects/nested.js": /* js */ `
|
||||
console.log("nested side effect - should be preserved");
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/package.json": /* json */ `
|
||||
{
|
||||
"sideEffects": ["./**/effects/*.js"]
|
||||
}
|
||||
`,
|
||||
},
|
||||
dce: true,
|
||||
run: {
|
||||
stdout: "deep side effect - should be preserved\nnested side effect - should be preserved\ndone",
|
||||
},
|
||||
});
|
||||
itBundled("dce/PackageJsonSideEffectsGlobExtensionPattern", {
|
||||
files: {
|
||||
"/Users/user/project/src/entry.js": /* js */ `
|
||||
import "demo-pkg/utils/util.js";
|
||||
import "demo-pkg/effects/main.effect.js";
|
||||
import "demo-pkg/effects/secondary.js";
|
||||
console.log("done");
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/utils/util.js": /* js */ `
|
||||
console.log("util side effect - should be tree shaken");
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/effects/main.effect.js": /* js */ `
|
||||
console.log("main effect - should be preserved");
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/effects/secondary.js": /* js */ `
|
||||
console.log("secondary effect - should be tree shaken");
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/package.json": /* json */ `
|
||||
{
|
||||
"sideEffects": ["./**/*.effect.js"]
|
||||
}
|
||||
`,
|
||||
},
|
||||
dce: true,
|
||||
run: {
|
||||
stdout: "main effect - should be preserved\ndone",
|
||||
},
|
||||
});
|
||||
itBundled("dce/PackageJsonSideEffectsGlobInvalidPattern", {
|
||||
files: {
|
||||
"/Users/user/project/src/entry.js": /* js */ `
|
||||
import { lib } from "demo-pkg/lib/lib.js";
|
||||
console.log(lib);
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/lib/lib.js": /* js */ `
|
||||
console.log("lib side effect");
|
||||
export const lib = "lib";
|
||||
`,
|
||||
"/Users/user/project/node_modules/demo-pkg/package.json": /* json */ `
|
||||
{
|
||||
"sideEffects": ["./[unclosed.js", "./lib/*.js"]
|
||||
}
|
||||
`,
|
||||
},
|
||||
dce: true,
|
||||
run: {
|
||||
stdout: "lib side effect\nlib",
|
||||
},
|
||||
});
|
||||
itBundled("dce/PackageJsonSideEffectsGlobNoMatches", {
|
||||
todo: true,
|
||||
files: {
|
||||
"/Users/user/project/src/entry.js": /* js */ `
|
||||
import "./components/comp.js";
|
||||
import "./utils/util.js";
|
||||
import "./root.js";
|
||||
console.log("done");
|
||||
`,
|
||||
"/Users/user/project/src/components/comp.js": /* js */ `
|
||||
console.log("component side effect");
|
||||
`,
|
||||
"/Users/user/project/src/utils/util.js": /* js */ `
|
||||
console.log("utility side effect - should be tree shaken");
|
||||
`,
|
||||
"/Users/user/project/src/root.js": /* js */ `
|
||||
console.log("root side effect - should be tree shaken");
|
||||
`,
|
||||
"/Users/user/project/package.json": /* json */ `
|
||||
{
|
||||
"sideEffects": ["src/components/*.js"]
|
||||
}
|
||||
`,
|
||||
},
|
||||
dce: true,
|
||||
run: {
|
||||
stdout: "component side effect\ndone",
|
||||
},
|
||||
});
|
||||
itBundled("dce/PackageJsonSideEffectsNestedDirectoryRemove", {
|
||||
todo: true,
|
||||
files: {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user