aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-03-19 14:08:20 -0700
committerGravatar GitHub <noreply@github.com> 2023-03-19 14:08:20 -0700
commit5a23d176208bb38483b65b9420b18c8597fabfef (patch)
treef163dc03fdc75b7827ee7f51e17355a9cc82329f
parent8f02ef829474cbd5453ffcb6485d40f93424ad26 (diff)
downloadbun-5a23d176208bb38483b65b9420b18c8597fabfef.tar.gz
bun-5a23d176208bb38483b65b9420b18c8597fabfef.tar.zst
bun-5a23d176208bb38483b65b9420b18c8597fabfef.zip
Several bug fixes (#2427)
* Fix test * Fix segfault when unexpected type is passed in `expect().toThrow` * Fix issues with request constructor * Don't bother cloning headers when its empty * woops * more tests * fix incorrect test * Make the fetch error messages better * Update response.zig * Fix test that failed on macOS * Fix test * Remove extra hash table lookups * Support running dummy registry directly cc @alexlamsl * Update test * Update test * fixup * Workaround crash in test runner * Fixup test * Fixup test * Update os.test.js --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
-rw-r--r--src/bun.js/bindings/bindings.cpp27
-rw-r--r--src/bun.js/bindings/bindings.zig81
-rw-r--r--src/bun.js/bindings/headers-cpp.h2
-rw-r--r--src/bun.js/bindings/headers.h4
-rw-r--r--src/bun.js/bindings/headers.zig2
-rw-r--r--src/bun.js/builtins/BunBuiltinNames.h2
-rw-r--r--src/bun.js/test/jest.zig178
-rw-r--r--src/bun.js/webcore/body.zig14
-rw-r--r--src/bun.js/webcore/request.zig300
-rw-r--r--src/bun.js/webcore/response.zig20
-rw-r--r--src/env_loader.zig2
-rw-r--r--src/install/extract_tarball.zig2
-rw-r--r--src/install/install.zig48
-rw-r--r--test/cli/install/dummy.registry.ts41
-rw-r--r--test/js/bun/globals.test.js2
-rw-r--r--test/js/bun/net/socket.test.ts10
-rw-r--r--test/js/bun/test/test-test.test.ts80
-rw-r--r--test/js/bun/util/filesystem_router.test.ts4
-rw-r--r--test/js/node/os/os.test.js2
-rw-r--r--test/js/web/fetch/fetch.test.ts30
-rw-r--r--test/js/web/html/FormData.test.ts13
-rw-r--r--test/js/web/streams/streams.test.js2
-rw-r--r--test/regression/issue/00631.test.ts5
-rw-r--r--test/regression/issue/02368.test.ts15
24 files changed, 635 insertions, 251 deletions
diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp
index 774c14d0c..a5af29128 100644
--- a/src/bun.js/bindings/bindings.cpp
+++ b/src/bun.js/bindings/bindings.cpp
@@ -700,6 +700,11 @@ bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2,
extern "C" {
+bool WebCore__FetchHeaders__isEmpty(WebCore__FetchHeaders* arg0)
+{
+ return arg0->size() == 0;
+}
+
void WebCore__FetchHeaders__toUWSResponse(WebCore__FetchHeaders* arg0, bool is_ssl, void* arg2)
{
if (is_ssl) {
@@ -3566,11 +3571,14 @@ enum class BuiltinNamesMap : uint8_t {
url,
body,
data,
+ toString,
+ redirect,
};
static JSC::Identifier builtinNameMap(JSC::JSGlobalObject* globalObject, unsigned char name)
{
- auto clientData = WebCore::clientData(globalObject->vm());
+ auto& vm = globalObject->vm();
+ auto clientData = WebCore::clientData(vm);
switch (static_cast<BuiltinNamesMap>(name)) {
case BuiltinNamesMap::method: {
return clientData->builtinNames().methodPublicName();
@@ -3590,7 +3598,24 @@ static JSC::Identifier builtinNameMap(JSC::JSGlobalObject* globalObject, unsigne
case BuiltinNamesMap::data: {
return clientData->builtinNames().dataPublicName();
}
+ case BuiltinNamesMap::toString: {
+ return vm.propertyNames->toString;
+ }
+ case BuiltinNamesMap::redirect: {
+ return clientData->builtinNames().redirectPublicName();
}
+ }
+}
+
+JSC__JSValue JSC__JSValue__fastGetDirect_(JSC__JSValue JSValue0, JSC__JSGlobalObject* globalObject, unsigned char arg2)
+{
+ JSC::JSValue value = JSC::JSValue::decode(JSValue0);
+ if (!value.isCell()) {
+ return JSValue::encode({});
+ }
+
+ return JSValue::encode(
+ value.getObject()->getDirect(globalObject->vm(), PropertyName(builtinNameMap(globalObject, arg2))));
}
JSC__JSValue JSC__JSValue__fastGet_(JSC__JSValue JSValue0, JSC__JSGlobalObject* globalObject, unsigned char arg2)
diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig
index 51f9c9fde..1bd045218 100644
--- a/src/bun.js/bindings/bindings.zig
+++ b/src/bun.js/bindings/bindings.zig
@@ -1041,6 +1041,12 @@ pub const FetchHeaders = opaque {
});
}
+ pub fn isEmpty(this: *FetchHeaders) bool {
+ return shim.cppFn("isEmpty", .{
+ this,
+ });
+ }
+
pub fn createFromUWS(
global: *JSGlobalObject,
uws_request: *anyopaque,
@@ -1422,6 +1428,7 @@ pub const FetchHeaders = opaque {
"remove",
"toJS",
"toUWSResponse",
+ "isEmpty",
};
};
@@ -3604,6 +3611,10 @@ pub const JSValue = enum(JSValueReprInt) {
return FFI.EncodedJSValue{ .asJSValue = this };
}
+ pub fn fromCell(ptr: *anyopaque) JSValue {
+ return (FFI.EncodedJSValue{ .asPtr = ptr }).asJSValue;
+ }
+
pub fn isInt32(this: JSValue) bool {
return FFI.JSVALUE_IS_INT32(.{ .asJSValue = this });
}
@@ -3773,6 +3784,13 @@ pub const JSValue = enum(JSValueReprInt) {
return str;
}
+ /// Convert a JSValue to a string, potentially calling `toString` on the
+ /// JSValue in JavaScript.
+ ///
+ /// This function can throw an exception in the `JSC::VM`. **If
+ /// the exception is not handled correctly, Bun will segfault**
+ ///
+ /// To handle exceptions, use `JSValue.toSliceOrNull`.
pub inline fn toSlice(this: JSValue, global: *JSGlobalObject, allocator: std.mem.Allocator) ZigString.Slice {
return getZigString(this, global).toSlice(allocator);
}
@@ -3786,11 +3804,39 @@ pub const JSValue = enum(JSValueReprInt) {
return cppFn("jsonStringify", .{ this, globalThis, indent, out });
}
- // On exception, this returns null, to make exception checks faster.
+ /// On exception, this returns null, to make exception checks clearer.
pub fn toStringOrNull(this: JSValue, globalThis: *JSGlobalObject) ?*JSString {
return cppFn("toStringOrNull", .{ this, globalThis });
}
+ /// Call `toString()` on the JSValue and clone the result.
+ /// On exception, this returns null.
+ pub fn toSliceOrNull(this: JSValue, globalThis: *JSGlobalObject) ?ZigString.Slice {
+ var str = this.toStringOrNull(globalThis) orelse return null;
+ return str.toSlice(globalThis, globalThis.allocator());
+ }
+
+ /// Call `toString()` on the JSValue and clone the result.
+ /// On exception or out of memory, this returns null.
+ ///
+ /// Remember that `Symbol` throws an exception when you call `toString()`.
+ pub fn toSliceClone(this: JSValue, globalThis: *JSGlobalObject) ?ZigString.Slice {
+ return this.toSliceCloneWithAllocator(globalThis, globalThis.allocator());
+ }
+
+ /// On exception or out of memory, this returns null, to make exception checks clearer.
+ pub fn toSliceCloneWithAllocator(
+ this: JSValue,
+ globalThis: *JSGlobalObject,
+ allocator: std.mem.Allocator,
+ ) ?ZigString.Slice {
+ var str = this.toStringOrNull(globalThis) orelse return null;
+ return str.toSlice(globalThis, allocator).cloneIfNeeded(allocator) catch {
+ globalThis.throwOutOfMemory();
+ return null;
+ };
+ }
+
pub fn toObject(this: JSValue, globalThis: *JSGlobalObject) *JSObject {
return cppFn("toObject", .{ this, globalThis });
}
@@ -3807,7 +3853,16 @@ pub const JSValue = enum(JSValueReprInt) {
return cppFn("eqlCell", .{ this, other });
}
- pub const BuiltinName = enum(u8) { method, headers, status, url, body, data };
+ pub const BuiltinName = enum(u8) {
+ method,
+ headers,
+ status,
+ url,
+ body,
+ data,
+ toString,
+ redirect,
+ };
// intended to be more lightweight than ZigString
pub fn fastGet(this: JSValue, global: *JSGlobalObject, builtin_name: BuiltinName) ?JSValue {
@@ -3819,11 +3874,24 @@ pub const JSValue = enum(JSValueReprInt) {
return result;
}
+ pub fn fastGetDirect(this: JSValue, global: *JSGlobalObject, builtin_name: BuiltinName) ?JSValue {
+ const result = fastGetDirect_(this, global, @enumToInt(builtin_name));
+ if (result == .zero) {
+ return null;
+ }
+
+ return result;
+ }
+
pub fn fastGet_(this: JSValue, global: *JSGlobalObject, builtin_name: u8) JSValue {
return cppFn("fastGet_", .{ this, global, builtin_name });
}
- // intended to be more lightweight than ZigString
+ pub fn fastGetDirect_(this: JSValue, global: *JSGlobalObject, builtin_name: u8) JSValue {
+ return cppFn("fastGetDirect_", .{ this, global, builtin_name });
+ }
+
+ /// Do not use this directly! Use `get` instead.
pub fn getIfPropertyExistsImpl(this: JSValue, global: *JSGlobalObject, ptr: [*]const u8, len: u32) JSValue {
return cppFn("getIfPropertyExistsImpl", .{ this, global, ptr, len });
}
@@ -3865,6 +3933,12 @@ pub const JSValue = enum(JSValueReprInt) {
return if (@enumToInt(value) != 0) value else return null;
}
+ pub fn implementsToString(this: JSValue, global: *JSGlobalObject) bool {
+ std.debug.assert(this.isCell());
+ const function = this.fastGet(global, BuiltinName.toString) orelse return false;
+ return function.isCell() and function.isCallable(global.vm());
+ }
+
pub fn getTruthy(this: JSValue, global: *JSGlobalObject, property: []const u8) ?JSValue {
if (get(this, global, property)) |prop| {
if (prop.isEmptyOrUndefinedOrNull()) return null;
@@ -4131,6 +4205,7 @@ pub const JSValue = enum(JSValueReprInt) {
"eqlCell",
"eqlValue",
"fastGet_",
+ "fastGetDirect_",
"forEach",
"forEachProperty",
"forEachPropertyOrdered",
diff --git a/src/bun.js/bindings/headers-cpp.h b/src/bun.js/bindings/headers-cpp.h
index 49391bb42..dafdaed17 100644
--- a/src/bun.js/bindings/headers-cpp.h
+++ b/src/bun.js/bindings/headers-cpp.h
@@ -1,4 +1,4 @@
-//-- AUTOGENERATED FILE -- 1679048516
+//-- AUTOGENERATED FILE -- 1679200292
// clang-format off
#pragma once
diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h
index baefaf0b9..8c9b5c1d0 100644
--- a/src/bun.js/bindings/headers.h
+++ b/src/bun.js/bindings/headers.h
@@ -1,5 +1,5 @@
// clang-format off
-//-- AUTOGENERATED FILE -- 1679083592
+//-- AUTOGENERATED FILE -- 1679200292
#pragma once
#include <stddef.h>
@@ -183,6 +183,7 @@ CPP_DECL bool WebCore__FetchHeaders__fastHas_(WebCore__FetchHeaders* arg0, unsig
CPP_DECL void WebCore__FetchHeaders__fastRemove_(WebCore__FetchHeaders* arg0, unsigned char arg1);
CPP_DECL void WebCore__FetchHeaders__get_(WebCore__FetchHeaders* arg0, const ZigString* arg1, ZigString* arg2, JSC__JSGlobalObject* arg3);
CPP_DECL bool WebCore__FetchHeaders__has(WebCore__FetchHeaders* arg0, const ZigString* arg1, JSC__JSGlobalObject* arg2);
+CPP_DECL bool WebCore__FetchHeaders__isEmpty(WebCore__FetchHeaders* arg0);
CPP_DECL void WebCore__FetchHeaders__put_(WebCore__FetchHeaders* arg0, const ZigString* arg1, const ZigString* arg2, JSC__JSGlobalObject* arg3);
CPP_DECL void WebCore__FetchHeaders__remove(WebCore__FetchHeaders* arg0, const ZigString* arg1, JSC__JSGlobalObject* arg2);
CPP_DECL JSC__JSValue WebCore__FetchHeaders__toJS(WebCore__FetchHeaders* arg0, JSC__JSGlobalObject* arg1);
@@ -300,6 +301,7 @@ CPP_DECL bool JSC__JSValue__deepEquals(JSC__JSValue JSValue0, JSC__JSValue JSVal
CPP_DECL bool JSC__JSValue__eqlCell(JSC__JSValue JSValue0, JSC__JSCell* arg1);
CPP_DECL bool JSC__JSValue__eqlValue(JSC__JSValue JSValue0, JSC__JSValue JSValue1);
CPP_DECL JSC__JSValue JSC__JSValue__fastGet_(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, unsigned char arg2);
+CPP_DECL JSC__JSValue JSC__JSValue__fastGetDirect_(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, unsigned char arg2);
CPP_DECL void JSC__JSValue__forEach(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, void* arg2, void(* ArgFn3)(JSC__VM* arg0, JSC__JSGlobalObject* arg1, void* arg2, JSC__JSValue JSValue3)) __attribute__((nonnull (3)));
CPP_DECL void JSC__JSValue__forEachProperty(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, void* arg2, void(* ArgFn3)(JSC__JSGlobalObject* arg0, void* arg1, ZigString* arg2, JSC__JSValue JSValue3, bool arg4)) __attribute__((nonnull (3)));
CPP_DECL void JSC__JSValue__forEachPropertyOrdered(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, void* arg2, void(* ArgFn3)(JSC__JSGlobalObject* arg0, void* arg1, ZigString* arg2, JSC__JSValue JSValue3, bool arg4)) __attribute__((nonnull (3)));
diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig
index a6a3b52fa..5779655fa 100644
--- a/src/bun.js/bindings/headers.zig
+++ b/src/bun.js/bindings/headers.zig
@@ -123,6 +123,7 @@ pub extern fn WebCore__FetchHeaders__fastHas_(arg0: ?*bindings.FetchHeaders, arg
pub extern fn WebCore__FetchHeaders__fastRemove_(arg0: ?*bindings.FetchHeaders, arg1: u8) void;
pub extern fn WebCore__FetchHeaders__get_(arg0: ?*bindings.FetchHeaders, arg1: [*c]const ZigString, arg2: [*c]ZigString, arg3: *bindings.JSGlobalObject) void;
pub extern fn WebCore__FetchHeaders__has(arg0: ?*bindings.FetchHeaders, arg1: [*c]const ZigString, arg2: *bindings.JSGlobalObject) bool;
+pub extern fn WebCore__FetchHeaders__isEmpty(arg0: ?*bindings.FetchHeaders) bool;
pub extern fn WebCore__FetchHeaders__put_(arg0: ?*bindings.FetchHeaders, arg1: [*c]const ZigString, arg2: [*c]const ZigString, arg3: *bindings.JSGlobalObject) void;
pub extern fn WebCore__FetchHeaders__remove(arg0: ?*bindings.FetchHeaders, arg1: [*c]const ZigString, arg2: *bindings.JSGlobalObject) void;
pub extern fn WebCore__FetchHeaders__toJS(arg0: ?*bindings.FetchHeaders, arg1: *bindings.JSGlobalObject) JSC__JSValue;
@@ -213,6 +214,7 @@ pub extern fn JSC__JSValue__deepEquals(JSValue0: JSC__JSValue, JSValue1: JSC__JS
pub extern fn JSC__JSValue__eqlCell(JSValue0: JSC__JSValue, arg1: [*c]bindings.JSCell) bool;
pub extern fn JSC__JSValue__eqlValue(JSValue0: JSC__JSValue, JSValue1: JSC__JSValue) bool;
pub extern fn JSC__JSValue__fastGet_(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject, arg2: u8) JSC__JSValue;
+pub extern fn JSC__JSValue__fastGetDirect_(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject, arg2: u8) JSC__JSValue;
pub extern fn JSC__JSValue__forEach(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject, arg2: ?*anyopaque, ArgFn3: ?*const fn (*bindings.VM, *bindings.JSGlobalObject, ?*anyopaque, JSC__JSValue) callconv(.C) void) void;
pub extern fn JSC__JSValue__forEachProperty(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject, arg2: ?*anyopaque, ArgFn3: ?*const fn (*bindings.JSGlobalObject, ?*anyopaque, [*c]ZigString, JSC__JSValue, bool) callconv(.C) void) void;
pub extern fn JSC__JSValue__forEachPropertyOrdered(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject, arg2: ?*anyopaque, ArgFn3: ?*const fn (*bindings.JSGlobalObject, ?*anyopaque, [*c]ZigString, JSC__JSValue, bool) callconv(.C) void) void;
diff --git a/src/bun.js/builtins/BunBuiltinNames.h b/src/bun.js/builtins/BunBuiltinNames.h
index b9a9dfe13..ebc2c2c05 100644
--- a/src/bun.js/builtins/BunBuiltinNames.h
+++ b/src/bun.js/builtins/BunBuiltinNames.h
@@ -129,7 +129,6 @@ using namespace JSC;
macro(isAbsolute) \
macro(isDisturbed) \
macro(isPaused) \
- macro(isSecureContext) \
macro(isWindows) \
macro(join) \
macro(kind) \
@@ -183,6 +182,7 @@ using namespace JSC;
macro(reader) \
macro(readyPromise) \
macro(readyPromiseCapability) \
+ macro(redirect) \
macro(relative) \
macro(releaseLock) \
macro(removeEventListener) \
diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig
index 883a3a48d..85bdb459e 100644
--- a/src/bun.js/test/jest.zig
+++ b/src/bun.js/test/jest.zig
@@ -2163,18 +2163,11 @@ pub const Expect = struct {
if (expected_value.isEmpty()) {
const signature_no_args = comptime getSignature("toThrow", "", true);
- if (result.isError()) {
- const name = result.getIfPropertyExistsImpl(globalObject, "name", 4);
- const message = result.getIfPropertyExistsImpl(globalObject, "message", 7);
+ if (result.toError()) |err| {
+ const name = err.get(globalObject, "name") orelse JSValue.undefined;
+ const message = err.get(globalObject, "message") orelse JSValue.undefined;
const fmt = signature_no_args ++ "\n\nError name: <red>{any}<r>\nError message: <red>{any}<r>\n";
- if (Output.enable_ansi_colors) {
- globalObject.throw(Output.prettyFmt(fmt, true), .{
- name.toFmt(globalObject, &formatter),
- message.toFmt(globalObject, &formatter),
- });
- return .zero;
- }
- globalObject.throw(Output.prettyFmt(fmt, false), .{
+ globalObject.throwPretty(fmt, .{
name.toFmt(globalObject, &formatter),
message.toFmt(globalObject, &formatter),
});
@@ -2183,41 +2176,25 @@ pub const Expect = struct {
// non error thrown
const fmt = signature_no_args ++ "\n\nThrown value: <red>{any}<r>\n";
- if (Output.enable_ansi_colors) {
- globalObject.throw(Output.prettyFmt(fmt, true), .{result.toFmt(globalObject, &formatter)});
- return .zero;
- }
- globalObject.throw(Output.prettyFmt(fmt, false), .{result.toFmt(globalObject, &formatter)});
+ globalObject.throwPretty(fmt, .{result.toFmt(globalObject, &formatter)});
return .zero;
}
if (expected_value.isString()) {
const received_message = result.getIfPropertyExistsImpl(globalObject, "message", 7);
+ // TODO: remove this allocation
// partial match
{
- var expected_string = ZigString.Empty;
- var received_string = ZigString.Empty;
- expected_value.toZigString(&expected_string, globalObject);
- received_message.toZigString(&received_string, globalObject);
- const expected_slice = expected_string.toSlice(default_allocator);
- const received_slice = received_string.toSlice(default_allocator);
- defer {
- expected_slice.deinit();
- received_slice.deinit();
- }
+ const expected_slice = expected_value.toSliceOrNull(globalObject) orelse return .zero;
+ defer expected_slice.deinit();
+ const received_slice = received_message.toSliceOrNull(globalObject) orelse return .zero;
+ defer received_slice.deinit();
if (!strings.contains(received_slice.slice(), expected_slice.slice())) return thisValue;
}
const fmt = signature ++ "\n\nExpected substring: not <green>{any}<r>\nReceived message: <red>{any}<r>\n";
- if (Output.enable_ansi_colors) {
- globalObject.throw(Output.prettyFmt(fmt, true), .{
- expected_value.toFmt(globalObject, &formatter),
- received_message.toFmt(globalObject, &formatter),
- });
- return .zero;
- }
- globalObject.throw(Output.prettyFmt(fmt, false), .{
+ globalObject.throwPretty(fmt, .{
expected_value.toFmt(globalObject, &formatter),
received_message.toFmt(globalObject, &formatter),
});
@@ -2227,20 +2204,14 @@ pub const Expect = struct {
if (expected_value.isRegExp()) {
const received_message = result.getIfPropertyExistsImpl(globalObject, "message", 7);
+ // TODO: REMOVE THIS GETTER! Expose a binding to call .test on the RegExp object directly.
if (expected_value.get(globalObject, "test")) |test_fn| {
const matches = test_fn.callWithThis(globalObject, expected_value, &.{received_message});
if (!matches.toBooleanSlow(globalObject)) return thisValue;
}
const fmt = signature ++ "\n\nExpected pattern: not <green>{any}<r>\nReceived message: <red>{any}<r>\n";
- if (Output.enable_ansi_colors) {
- globalObject.throw(Output.prettyFmt(fmt, true), .{
- expected_value.toFmt(globalObject, &formatter),
- received_message.toFmt(globalObject, &formatter),
- });
- return .zero;
- }
- globalObject.throw(Output.prettyFmt(fmt, false), .{
+ globalObject.throwPretty(fmt, .{
expected_value.toFmt(globalObject, &formatter),
received_message.toFmt(globalObject, &formatter),
});
@@ -2253,11 +2224,7 @@ pub const Expect = struct {
if (!expected_message.isSameValue(received_message, globalObject)) return thisValue;
const fmt = signature ++ "\n\nExpected message: not <green>{any}<r>\n";
- if (Output.enable_ansi_colors) {
- globalObject.throw(Output.prettyFmt(fmt, true), .{expected_message.toFmt(globalObject, &formatter)});
- return .zero;
- }
- globalObject.throw(Output.prettyFmt(fmt, false), .{expected_message.toFmt(globalObject, &formatter)});
+ globalObject.throwPretty(fmt, .{expected_message.toFmt(globalObject, &formatter)});
return .zero;
}
@@ -2279,22 +2246,26 @@ pub const Expect = struct {
if (did_throw) {
if (expected_value.isEmpty()) return thisValue;
- const result: JSValue = result_.?;
- const _received_message = result.get(globalObject, "message");
+ const result: JSValue = if (result_.?.toError()) |r|
+ r
+ else
+ result_.?;
+
+ const _received_message: ?JSValue = if (result.isObject())
+ result.get(globalObject, "message")
+ else if (result.toStringOrNull(globalObject)) |js_str|
+ JSC.JSValue.fromCell(js_str)
+ else
+ null;
if (expected_value.isString()) {
if (_received_message) |received_message| {
+ // TODO: remove this allocation
// partial match
- var expected_string = ZigString.Empty;
- var received_string = ZigString.Empty;
- expected_value.toZigString(&expected_string, globalObject);
- received_message.toZigString(&received_string, globalObject);
- const expected_slice = expected_string.toSlice(default_allocator);
- const received_slice = received_string.toSlice(default_allocator);
- defer {
- expected_slice.deinit();
- received_slice.deinit();
- }
+ const expected_slice = expected_value.toSliceOrNull(globalObject) orelse return .zero;
+ defer expected_slice.deinit();
+ const received_slice = received_message.toSlice(globalObject, globalObject.allocator());
+ defer received_slice.deinit();
if (strings.contains(received_slice.slice(), expected_slice.slice())) return thisValue;
}
@@ -2305,29 +2276,21 @@ pub const Expect = struct {
const expected_value_fmt = expected_value.toFmt(globalObject, &formatter);
const received_message_fmt = received_message.toFmt(globalObject, &formatter);
const fmt = signature ++ "\n\n" ++ "Expected substring: <green>{any}<r>\nReceived message: <red>{any}<r>\n";
- if (Output.enable_ansi_colors) {
- globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_value_fmt, received_message_fmt });
- return .zero;
- }
-
- globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_value_fmt, received_message_fmt });
+ globalObject.throwPretty(fmt, .{ expected_value_fmt, received_message_fmt });
return .zero;
}
const expected_fmt = expected_value.toFmt(globalObject, &formatter);
const received_fmt = result.toFmt(globalObject, &formatter);
const fmt = signature ++ "\n\n" ++ "Expected substring: <green>{any}<r>\nReceived value: <red>{any}<r>";
- if (Output.enable_ansi_colors) {
- globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_fmt, received_fmt });
- return .zero;
- }
+ globalObject.throwPretty(fmt, .{ expected_fmt, received_fmt });
- globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_fmt, received_fmt });
return .zero;
}
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 (expected_value.get(globalObject, "test")) |test_fn| {
const matches = test_fn.callWithThis(globalObject, expected_value, &.{received_message});
if (matches.toBooleanSlow(globalObject)) return thisValue;
@@ -2341,27 +2304,21 @@ pub const Expect = struct {
const expected_value_fmt = expected_value.toFmt(globalObject, &formatter);
const received_message_fmt = received_message.toFmt(globalObject, &formatter);
const fmt = signature ++ "\n\n" ++ "Expected pattern: <green>{any}<r>\nReceived message: <red>{any}<r>\n";
- if (Output.enable_ansi_colors) {
- globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_value_fmt, received_message_fmt });
- return .zero;
- }
+ globalObject.throwPretty(fmt, .{ expected_value_fmt, received_message_fmt });
- globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_value_fmt, received_message_fmt });
return .zero;
}
const expected_fmt = expected_value.toFmt(globalObject, &formatter);
const received_fmt = result.toFmt(globalObject, &formatter);
const fmt = signature ++ "\n\n" ++ "Expected pattern: <green>{any}<r>\nReceived value: <red>{any}<r>";
- if (Output.enable_ansi_colors) {
- globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_fmt, received_fmt });
- return .zero;
- }
-
- globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_fmt, received_fmt });
+ globalObject.throwPretty(fmt, .{ expected_fmt, received_fmt });
return .zero;
}
+ // If it's not an object, we are going to crash here.
+ std.debug.assert(expected_value.isObject());
+
if (expected_value.get(globalObject, "message")) |expected_message| {
if (_received_message) |received_message| {
if (received_message.isSameValue(expected_message, globalObject)) return thisValue;
@@ -2374,24 +2331,14 @@ pub const Expect = struct {
const expected_fmt = expected_message.toFmt(globalObject, &formatter);
const received_fmt = received_message.toFmt(globalObject, &formatter);
const fmt = signature ++ "\n\nExpected message: <green>{any}<r>\nReceived message: <red>{any}<r>\n";
- if (Output.enable_ansi_colors) {
- globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_fmt, received_fmt });
- return .zero;
- }
-
- globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_fmt, received_fmt });
+ globalObject.throwPretty(fmt, .{ expected_fmt, received_fmt });
return .zero;
}
const expected_fmt = expected_message.toFmt(globalObject, &formatter);
const received_fmt = result.toFmt(globalObject, &formatter);
const fmt = signature ++ "\n\nExpected message: <green>{any}<r>\nReceived value: <red>{any}<r>\n";
- if (Output.enable_ansi_colors) {
- globalObject.throw(Output.prettyFmt(fmt, true), .{ expected_fmt, received_fmt });
- return .zero;
- }
-
- globalObject.throw(Output.prettyFmt(fmt, false), .{ expected_fmt, received_fmt });
+ globalObject.throwPretty(fmt, .{ expected_fmt, received_fmt });
return .zero;
}
@@ -2408,16 +2355,8 @@ pub const Expect = struct {
if (_received_message) |received_message| {
const message_fmt = fmt ++ "Received message: <red>{any}<r>\n";
const received_message_fmt = received_message.toFmt(globalObject, &formatter);
- if (Output.enable_ansi_colors) {
- globalObject.throw(Output.prettyFmt(message_fmt, true), .{
- expected_class,
- received_class,
- received_message_fmt,
- });
- return .zero;
- }
- globalObject.throw(Output.prettyFmt(message_fmt, false), .{
+ globalObject.throwPretty(message_fmt, .{
expected_class,
received_class,
received_message_fmt,
@@ -2427,16 +2366,8 @@ pub const Expect = struct {
const received_fmt = result.toFmt(globalObject, &formatter);
const value_fmt = fmt ++ "Received value: <red>{any}<r>\n";
- if (Output.enable_ansi_colors) {
- globalObject.throw(Output.prettyFmt(value_fmt, true), .{
- expected_class,
- received_class,
- received_fmt,
- });
- return .zero;
- }
- globalObject.throw(Output.prettyFmt(value_fmt, false), .{
+ globalObject.throwPretty(value_fmt, .{
expected_class,
received_class,
received_fmt,
@@ -2824,7 +2755,7 @@ pub const Expect = struct {
}
unreachable;
};
-
+
if (not) pass = !pass;
if (pass) return thisValue;
@@ -3503,7 +3434,7 @@ pub const DescribeScope = struct {
.test_id = i,
.describe = this,
.globalThis = ctx,
- .source = source,
+ .source_file_path = source.path.text,
.value = JSC.Strong.create(this_object, ctx),
};
runner.ref.ref(ctx.bunVM());
@@ -3608,7 +3539,7 @@ pub const TestRunnerTask = struct {
test_id: TestRunner.Test.ID,
describe: *DescribeScope,
globalThis: *JSC.JSGlobalObject,
- source: logger.Source,
+ source_file_path: string = "",
value: JSC.Strong = .{},
needs_before_each: bool = true,
ref: JSC.Ref = JSC.Ref.init(),
@@ -3677,7 +3608,7 @@ pub const TestRunnerTask = struct {
const beforeEach = this.describe.runCallback(globalThis, .beforeEach);
if (!beforeEach.isEmpty()) {
- Jest.runner.?.reportFailure(test_id, this.source.path.text, label, 0, this.describe);
+ Jest.runner.?.reportFailure(test_id, this.source_file_path, label, 0, this.describe);
globalThis.bunVM().runErrorHandler(beforeEach, null);
return false;
}
@@ -3754,9 +3685,9 @@ pub const TestRunnerTask = struct {
fn processTestResult(this: *TestRunnerTask, globalThis: *JSC.JSGlobalObject, result: Result, test_: TestScope, test_id: u32, describe: *DescribeScope) void {
switch (result) {
- .pass => |count| Jest.runner.?.reportPass(test_id, this.source.path.text, test_.label, count, describe),
- .fail => |count| Jest.runner.?.reportFailure(test_id, this.source.path.text, test_.label, count, describe),
- .skip => Jest.runner.?.reportSkip(test_id, this.source.path.text, test_.label, describe),
+ .pass => |count| Jest.runner.?.reportPass(test_id, this.source_file_path, test_.label, count, describe),
+ .fail => |count| Jest.runner.?.reportFailure(test_id, this.source_file_path, test_.label, count, describe),
+ .skip => Jest.runner.?.reportSkip(test_id, this.source_file_path, test_.label, describe),
.pending => @panic("Unexpected pending test"),
}
describe.onTestComplete(globalThis, test_id, result == .skip);
@@ -3773,7 +3704,16 @@ pub const TestRunnerTask = struct {
this.value.deinit();
this.ref.unref(vm);
- default_allocator.destroy(this);
+
+ // there is a double free here involving async before/after callbacks
+ //
+ // Fortunately:
+ //
+ // - TestRunnerTask doesn't use much memory.
+ // - we don't have watch mode yet.
+ //
+ // TODO: fix this bug
+ // default_allocator.destroy(this);
}
};
diff --git a/src/bun.js/webcore/body.zig b/src/bun.js/webcore/body.zig
index 85a52bc3f..37a70343d 100644
--- a/src/bun.js/webcore/body.zig
+++ b/src/bun.js/webcore/body.zig
@@ -149,7 +149,9 @@ pub const Body = struct {
// we can skip calling JS getters
if (response_init.as(Request)) |req| {
if (req.headers) |headers| {
- result.headers = headers.cloneThis(ctx);
+ if (!headers.isEmpty()) {
+ result.headers = headers.cloneThis(ctx);
+ }
}
result.method = req.method;
@@ -163,7 +165,9 @@ pub const Body = struct {
if (response_init.fastGet(ctx, .headers)) |headers| {
if (headers.as(FetchHeaders)) |orig| {
- result.headers = orig.cloneThis(ctx);
+ if (!orig.isEmpty()) {
+ result.headers = orig.cloneThis(ctx);
+ }
} else {
result.headers = FetchHeaders.createFromJS(ctx.ptr(), headers);
}
@@ -185,7 +189,7 @@ pub const Body = struct {
}
} else if (status_value.isNumber()) {
const number = status_value.to(i32);
- if (100 <= number and number < 600)
+ if (100 <= number and number < 999)
result.status_code = @truncate(u16, @intCast(u32, number));
}
}
@@ -849,12 +853,12 @@ pub const Body = struct {
if (tag == .InternalBlob) {
this.InternalBlob.clearAndFree();
- this.* = Value{ .Null = {} }; //Value.empty;
+ this.* = Value{ .Null = {} };
}
if (tag == .Blob) {
this.Blob.deinit();
- this.* = Value{ .Null = {} }; //Value.empty;
+ this.* = Value{ .Null = {} };
}
if (tag == .Error) {
diff --git a/src/bun.js/webcore/request.zig b/src/bun.js/webcore/request.zig
index ede8cab5d..fe8fd9c9c 100644
--- a/src/bun.js/webcore/request.zig
+++ b/src/bun.js/webcore/request.zig
@@ -379,100 +379,242 @@ pub const Request = struct {
}
}
+ const Fields = enum {
+ method,
+ headers,
+ body,
+ // referrer,
+ // referrerPolicy,
+ // mode,
+ // credentials,
+ // redirect,
+ // integrity,
+ // keepalive,
+ signal,
+ // proxy,
+ // timeout,
+ url,
+ };
+
pub fn constructInto(
globalThis: *JSC.JSGlobalObject,
arguments: []const JSC.JSValue,
) ?Request {
- var request = Request{};
-
- switch (arguments.len) {
- 0 => {},
- 1 => {
- const urlOrObject = arguments[0];
- const url_or_object_type = urlOrObject.jsType();
- if (url_or_object_type.isStringLike()) {
- request.url = (arguments[0].toSlice(globalThis, bun.default_allocator).cloneIfNeeded(bun.default_allocator) catch {
- return null;
- }).slice();
- request.url_was_allocated = request.url.len > 0;
- request.body = .{
- .Null = {},
- };
- } else {
- if (Body.Init.init(getAllocator(globalThis), globalThis, arguments[0]) catch null) |req_init| {
- request.headers = req_init.headers;
- request.method = req_init.method;
+ var req = Request{};
+
+ if (arguments.len == 0) {
+ globalThis.throw("Failed to construct 'Request': 1 argument required, but only 0 present.", .{});
+ return null;
+ } else if (arguments[0].isEmptyOrUndefinedOrNull() or !arguments[0].isCell()) {
+ globalThis.throw("Failed to construct 'Request': expected non-empty string or object, got undefined", .{});
+ return null;
+ }
+
+ const url_or_object = arguments[0];
+ const url_or_object_type = url_or_object.jsType();
+ var fields = std.EnumSet(Fields).initEmpty();
+
+ const is_first_argument_a_url =
+ // fastest path:
+ url_or_object_type.isStringLike() or
+ // slower path:
+ url_or_object.as(JSC.DOMURL) != null;
+
+ if (is_first_argument_a_url) {
+ const slice = arguments[0].toSliceOrNull(globalThis) orelse {
+ req.finalizeWithoutDeinit();
+ return null;
+ };
+ req.url = (slice.cloneIfNeeded(globalThis.allocator()) catch {
+ req.finalizeWithoutDeinit();
+ return null;
+ }).slice();
+ req.url_was_allocated = req.url.len > 0;
+ if (req.url.len > 0)
+ fields.insert(.url);
+ } else if (!url_or_object_type.isObject()) {
+ globalThis.throw("Failed to construct 'Request': expected non-empty string or object", .{});
+ return null;
+ }
+
+ const values_to_try_ = [_]JSValue{
+ if (arguments.len > 1 and arguments[1].isObject())
+ arguments[1]
+ else if (is_first_argument_a_url)
+ JSValue.undefined
+ else
+ url_or_object,
+ if (is_first_argument_a_url) JSValue.undefined else url_or_object,
+ };
+ const values_to_try = values_to_try_[0 .. @as(usize, @boolToInt(!is_first_argument_a_url)) +
+ @as(usize, @boolToInt(arguments.len > 1 and arguments[1].isObject()))];
+
+ for (values_to_try) |value| {
+ const value_type = value.jsType();
+
+ if (value_type == .DOMWrapper) {
+ if (value.as(Request)) |request| {
+ if (values_to_try.len == 1) {
+ request.cloneInto(&req, globalThis.allocator(), globalThis);
+ if (req.url_was_allocated) {
+ req.url = req.url;
+ req.url_was_allocated = true;
+ }
+ return req;
}
- if (urlOrObject.fastGet(globalThis, .body)) |body_| {
- if (Body.Value.fromJS(globalThis, body_)) |body| {
- request.body = body;
- } else {
- request.finalizeWithoutDeinit();
- return null;
+ if (!fields.contains(.method)) {
+ req.method = request.method;
+ fields.insert(.method);
+ }
+
+ if (!fields.contains(.headers)) {
+ if (request.cloneHeaders(globalThis)) |headers| {
+ req.headers = headers;
+ fields.insert(.headers);
}
- } else {
- request.body = .{
- .Null = {},
- };
}
- if (urlOrObject.fastGet(globalThis, .url)) |url| {
- request.url = (url.toSlice(globalThis, bun.default_allocator).cloneIfNeeded(bun.default_allocator) catch {
- return null;
- }).slice();
- request.url_was_allocated = request.url.len > 0;
+ if (!fields.contains(.body)) {
+ switch (request.body) {
+ .Null, .Empty, .Used => {},
+ else => {
+ req.body = request.body.clone(globalThis);
+ fields.insert(.body);
+ },
+ }
}
}
- },
- else => {
- if (arguments[1].get(globalThis, "signal")) |signal_| {
- if (AbortSignal.fromJS(signal_)) |signal| {
- //Keep it alive
- signal_.ensureStillAlive();
- request.signal = signal.ref();
- } else {
- globalThis.throw("Failed to construct 'Request': member signal is not of type AbortSignal.", .{});
- request.finalizeWithoutDeinit();
- return null;
+ if (value.as(JSC.WebCore.Response)) |response| {
+ if (!fields.contains(.method)) {
+ req.method = response.body.init.method;
+ fields.insert(.method);
+ }
+
+ if (!fields.contains(.headers)) {
+ if (response.body.init.headers) |headers| {
+ req.headers = headers.cloneThis(globalThis);
+ fields.insert(.headers);
+ }
}
- }
- if (Body.Init.init(getAllocator(globalThis), globalThis, arguments[1]) catch null) |req_init| {
- request.headers = req_init.headers;
- request.method = req_init.method;
+ if (!fields.contains(.url)) {
+ if (response.url.len > 0) {
+ req.url = globalThis.allocator().dupe(u8, response.url) catch unreachable;
+ req.url_was_allocated = true;
+ fields.insert(.url);
+ }
+ }
+
+ if (!fields.contains(.body)) {
+ switch (response.body.value) {
+ .Null, .Empty, .Used => {},
+ else => {
+ req.body = response.body.value.clone(globalThis);
+ fields.insert(.body);
+ },
+ }
+ }
}
+ }
- if (arguments[1].fastGet(globalThis, .body)) |body_| {
+ if (!fields.contains(.body)) {
+ if (value.fastGet(globalThis, .body)) |body_| {
+ fields.insert(.body);
if (Body.Value.fromJS(globalThis, body_)) |body| {
- request.body = body;
+ req.body = body;
} else {
- request.finalizeWithoutDeinit();
+ req.finalizeWithoutDeinit();
return null;
}
- } else {
- request.body = .{
- .Null = {},
+ }
+ }
+
+ if (!fields.contains(.url)) {
+ if (value.fastGet(globalThis, .url)) |url| {
+ req.url = (url.toSlice(globalThis, bun.default_allocator).cloneIfNeeded(bun.default_allocator) catch {
+ return null;
+ }).slice();
+ req.url_was_allocated = req.url.len > 0;
+ if (req.url.len > 0)
+ fields.insert(.url);
+
+ // first value
+ } else if (@enumToInt(value) == @enumToInt(values_to_try[values_to_try.len - 1]) and !is_first_argument_a_url and
+ value.implementsToString(globalThis))
+ {
+ const slice = value.toSliceOrNull(globalThis) orelse {
+ req.finalizeWithoutDeinit();
+ return null;
};
+ req.url = (slice.cloneIfNeeded(globalThis.allocator()) catch {
+ req.finalizeWithoutDeinit();
+ return null;
+ }).slice();
+ req.url_was_allocated = req.url.len > 0;
+ if (req.url.len > 0)
+ fields.insert(.url);
}
+ }
- request.url = (arguments[0].toSlice(globalThis, bun.default_allocator).cloneIfNeeded(bun.default_allocator) catch {
- return null;
- }).slice();
- request.url_was_allocated = request.url.len > 0;
- },
+ if (!fields.contains(.signal)) {
+ if (value.get(globalThis, "signal")) |signal_| {
+ fields.insert(.signal);
+
+ if (AbortSignal.fromJS(signal_)) |signal| {
+ //Keep it alive
+ signal_.ensureStillAlive();
+ req.signal = signal.ref();
+ } else {
+ globalThis.throw("Failed to construct 'Request': signal is not of type AbortSignal.", .{});
+ req.finalizeWithoutDeinit();
+ return null;
+ }
+ }
+ }
+
+ if (!fields.contains(.method) or !fields.contains(.headers)) {
+ if (Body.Init.init(globalThis.allocator(), globalThis, value) catch null) |init| {
+ if (!fields.contains(.method)) {
+ req.method = init.method;
+ fields.insert(.method);
+ }
+
+ if (init.headers) |headers| {
+ if (!fields.contains(.headers)) {
+ req.headers = headers;
+ fields.insert(.headers);
+ } else {
+ headers.deref();
+ }
+ }
+ }
+ }
}
- if (request.body == .Blob and
- request.headers != null and
- request.body.Blob.content_type.len > 0 and
- !request.headers.?.fastHas(.ContentType))
+ if (req.url.len == 0) {
+ globalThis.throw("Failed to construct 'Request': url is required.", .{});
+ req.finalizeWithoutDeinit();
+ return null;
+ }
+
+ const parsed_url = ZigURL.parse(req.url);
+ if (parsed_url.hostname.len == 0) {
+ globalThis.throw("Failed to construct 'Request': Invalid URL (missing a hostname)", .{});
+ req.finalizeWithoutDeinit();
+ return null;
+ }
+
+ if (req.body == .Blob and
+ req.headers != null and
+ req.body.Blob.content_type.len > 0 and
+ !req.headers.?.fastHas(.ContentType))
{
- request.headers.?.put("content-type", request.body.Blob.content_type, globalThis);
+ req.headers.?.put("content-type", req.body.Blob.content_type, globalThis);
}
- return request;
+ return req;
}
pub fn constructor(
@@ -531,6 +673,24 @@ pub const Request = struct {
return this.headers.?.toJS(globalThis);
}
+ pub fn cloneHeaders(this: *Request, globalThis: *JSGlobalObject) ?*FetchHeaders {
+ if (this.headers == null) {
+ if (this.uws_request) |uws_req| {
+ this.headers = FetchHeaders.createFromUWS(globalThis, uws_req);
+ }
+ }
+
+ if (this.headers) |head| {
+ if (head.isEmpty()) {
+ return null;
+ }
+
+ return head.cloneThis(globalThis);
+ }
+
+ return null;
+ }
+
pub fn cloneInto(
this: *Request,
req: *Request,
@@ -546,15 +706,9 @@ pub const Request = struct {
return;
},
.method = this.method,
+ .headers = this.cloneHeaders(globalThis),
};
- if (this.headers) |head| {
- req.headers = head.cloneThis(globalThis);
- } else if (this.uws_request) |uws_req| {
- req.headers = FetchHeaders.createFromUWS(globalThis, uws_req);
- this.headers = req.headers.?.cloneThis(globalThis).?;
- }
-
if (this.signal) |signal| {
req.signal = signal.ref();
}
diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig
index 9dfee821a..a19ee9ca4 100644
--- a/src/bun.js/webcore/response.zig
+++ b/src/bun.js/webcore/response.zig
@@ -490,7 +490,14 @@ pub const Response = struct {
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) ?*Response {
- const args_list = callframe.arguments(2);
+ const args_list = brk: {
+ var args = callframe.arguments(2);
+ if (args.len > 1 and args.ptr[1].isEmptyOrUndefinedOrNull()) {
+ args.len = 1;
+ }
+ break :brk args;
+ };
+
const arguments = args_list.ptr[0..args_list.len];
const body: Body = @as(?Body, brk: {
switch (arguments.len) {
@@ -501,11 +508,12 @@ pub const Response = struct {
break :brk Body.extract(globalThis, arguments[0]);
},
else => {
- if (arguments[1].isUndefinedOrNull()) break :brk Body.extract(globalThis, arguments[0]);
if (arguments[1].isObject()) {
break :brk Body.extractWithInit(globalThis, arguments[0], arguments[1]);
}
+ std.debug.assert(!arguments[1].isEmptyOrUndefinedOrNull());
+
const err = globalThis.createTypeErrorInstance("Expected options to be one of: null, undefined, or object", .{});
globalThis.throwValue(err);
break :brk null;
@@ -731,7 +739,13 @@ pub const Fetch = struct {
const fetch_error = JSC.SystemError{
.code = ZigString.init(@errorName(this.result.fail)),
- .message = ZigString.init("fetch() failed"),
+ .message = switch (this.result.fail) {
+ error.ConnectionClosed => ZigString.init("The socket connection was closed unexpectedly. For more information, pass `verbose: true` in the second argument to fetch()"),
+ error.FailedToOpenSocket => ZigString.init("Was there a typo in the url or port?"),
+ error.TooManyRedirects => ZigString.init("The response redirected too many times. For more information, pass `verbose: true` in the second argument to fetch()"),
+ error.ConnectionRefused => ZigString.init("Unable to connect. Is the computer able to access the url?"),
+ else => ZigString.init("fetch() failed. For more information, pass `verbose: true` in the second argument to fetch()"),
+ },
.path = ZigString.init(this.http.?.url.href),
};
diff --git a/src/env_loader.zig b/src/env_loader.zig
index 41fb25fe5..38c0744a3 100644
--- a/src/env_loader.zig
+++ b/src/env_loader.zig
@@ -451,7 +451,7 @@ pub const Loader = struct {
}
}
- //NO_PROXY filter
+ // NO_PROXY filter
if (http_proxy != null) {
if (this.map.get("no_proxy") orelse this.map.get("NO_PROXY")) |no_proxy_text| {
if (no_proxy_text.len == 0) return http_proxy;
diff --git a/src/install/extract_tarball.zig b/src/install/extract_tarball.zig
index 0df11ffcd..bec5f5eff 100644
--- a/src/install/extract_tarball.zig
+++ b/src/install/extract_tarball.zig
@@ -110,7 +110,7 @@ pub fn buildURLWithPrinter(
var name = full_name;
if (name[0] == '@') {
- if (std.mem.indexOfScalar(u8, name, '/')) |i| {
+ if (strings.indexOfChar(name, '/')) |i| {
name = name[i + 1 ..];
}
}
diff --git a/src/install/install.zig b/src/install/install.zig
index 0c280ccaa..0927c9335 100644
--- a/src/install/install.zig
+++ b/src/install/install.zig
@@ -323,11 +323,21 @@ const NetworkTask = struct {
this.response_buffer = try MutableString.init(allocator, 0);
this.allocator = allocator;
- const env = this.package_manager.env;
- var url = URL.parse(this.url_buf);
- var http_proxy: ?URL = env.getHttpProxy(url);
- this.http = AsyncHTTP.init(allocator, .GET, url, header_builder.entries, header_builder.content.ptr.?[0..header_builder.content.len], &this.response_buffer, "", 0, this.getCompletionCallback(), http_proxy, null);
+ const url = URL.parse(this.url_buf);
+ this.http = AsyncHTTP.init(
+ allocator,
+ .GET,
+ url,
+ header_builder.entries,
+ header_builder.content.ptr.?[0..header_builder.content.len],
+ &this.response_buffer,
+ "",
+ 0,
+ this.getCompletionCallback(),
+ this.package_manager.httpProxy(url),
+ null,
+ );
this.http.max_retry_count = this.package_manager.options.max_retry_count;
this.callback = .{
.package_manifest = .{
@@ -389,12 +399,21 @@ const NetworkTask = struct {
header_buf = header_builder.content.ptr.?[0..header_builder.content.len];
}
- const env = this.package_manager.env;
-
- var url = URL.parse(this.url_buf);
- var http_proxy: ?URL = env.getHttpProxy(url);
+ const url = URL.parse(this.url_buf);
- this.http = AsyncHTTP.init(allocator, .GET, url, header_builder.entries, header_buf, &this.response_buffer, "", 0, this.getCompletionCallback(), http_proxy, null);
+ this.http = AsyncHTTP.init(
+ allocator,
+ .GET,
+ url,
+ header_builder.entries,
+ header_buf,
+ &this.response_buffer,
+ "",
+ 0,
+ this.getCompletionCallback(),
+ this.package_manager.httpProxy(url),
+ null,
+ );
this.http.max_retry_count = this.package_manager.options.max_retry_count;
this.callback = .{ .extract = tarball };
}
@@ -1590,6 +1609,10 @@ pub const PackageManager = struct {
80,
);
+ pub fn httpProxy(this: *PackageManager, url: URL) ?URL {
+ return this.env.getHttpProxy(url);
+ }
+
pub const WakeHandler = struct {
// handler: fn (ctx: *anyopaque, pm: *PackageManager) void = undefined,
// onDependencyError: fn (ctx: *anyopaque, Dependency, PackageID, anyerror) void = undefined,
@@ -6561,11 +6584,14 @@ pub const PackageManager = struct {
const cwd = std.fs.cwd();
while (iterator.nextNodeModulesFolder()) |node_modules| {
- try cwd.makePath(bun.span(node_modules.relative_path));
// We deliberately do not close this folder.
// If the package hasn't been downloaded, we will need to install it later
// We use this file descriptor to know where to put it.
- installer.node_modules_folder = try cwd.openIterableDir(node_modules.relative_path, .{});
+ installer.node_modules_folder = cwd.openIterableDir(node_modules.relative_path, .{}) catch brk: {
+ // Avoid extra mkdir() syscall
+ try cwd.makePath(bun.span(node_modules.relative_path));
+ break :brk try cwd.openIterableDir(node_modules.relative_path, .{});
+ };
var remaining = node_modules.dependencies;
diff --git a/test/cli/install/dummy.registry.ts b/test/cli/install/dummy.registry.ts
index b77d79167..7d3acb612 100644
--- a/test/cli/install/dummy.registry.ts
+++ b/test/cli/install/dummy.registry.ts
@@ -1,5 +1,12 @@
+/**
+ * This file can be directly run
+ *
+ * PACKAGE_DIR_TO_USE=(realpath .) bun test/cli/install/dummy.registry.ts
+ */
import { file, Server } from "bun";
-import { expect } from "bun:test";
+
+var expect: typeof import("bun:test")["expect"];
+
import { mkdtemp, readdir, realpath, rm, writeFile } from "fs/promises";
import { tmpdir } from "os";
import { basename, join } from "path";
@@ -7,7 +14,7 @@ import { basename, join } from "path";
type RequestHandler = (request: Request) => Response | Promise<Response>;
let handler: RequestHandler, server: Server;
export let package_dir: string, requested: number, root_url: string;
-
+let testCounter = 0;
export function dummyRegistry(urls: string[], info: any = { "0.0.2": {} }): RequestHandler {
return async request => {
urls.push(request.url);
@@ -75,10 +82,14 @@ export function dummyAfterAll() {
server.stop();
}
+var packageDirGetter: () => Promise<string> = async () => {
+ return await realpath(await mkdtemp(join(await realpath(tmpdir()), "bun-install-test-" + testCounter++ + "--")));
+};
export async function dummyBeforeEach() {
resetHanlder();
requested = 0;
- package_dir = await mkdtemp(join(await realpath(tmpdir()), "bun-install.test"));
+ package_dir = await packageDirGetter();
+
await writeFile(
join(package_dir, "bunfig.toml"),
`
@@ -93,3 +104,27 @@ export async function dummyAfterEach() {
resetHanlder();
await rm(package_dir, { force: true, recursive: true });
}
+
+if (Bun.main === import.meta.path) {
+ // @ts-expect-error
+ expect = value => {
+ return {
+ toBe(expected) {
+ if (value !== expected) {
+ throw new Error(`Expected ${value} to be ${expected}`);
+ }
+ },
+ };
+ };
+ if (process.env.PACKAGE_DIR_TO_USE) {
+ packageDirGetter = () => Promise.resolve(process.env.PACKAGE_DIR_TO_USE!);
+ }
+
+ await dummyBeforeAll();
+ await dummyBeforeEach();
+ setHandler(dummyRegistry([]));
+ console.log("Running dummy registry!\n\n URL: ", root_url!, "\n", "DIR: ", package_dir!);
+} else {
+ // @ts-expect-error
+ ({ expect } = Bun.jest(import.meta.path));
+}
diff --git a/test/js/bun/globals.test.js b/test/js/bun/globals.test.js
index 5d491cde7..ac2d40659 100644
--- a/test/js/bun/globals.test.js
+++ b/test/js/bun/globals.test.js
@@ -5,7 +5,7 @@ it("extendable", () => {
// None of these should error
for (let Class of classes) {
var Foo = class extends Class {};
- var bar = new Foo();
+ var bar = Class === Request ? new Request({ url: "https://example.com" }) : new Foo();
expect(bar instanceof Class).toBe(true);
expect(!!Class.prototype).toBe(true);
expect(typeof Class.prototype).toBe("object");
diff --git a/test/js/bun/net/socket.test.ts b/test/js/bun/net/socket.test.ts
index 9ee60bc63..7f509966a 100644
--- a/test/js/bun/net/socket.test.ts
+++ b/test/js/bun/net/socket.test.ts
@@ -1,5 +1,5 @@
import { expect, it } from "bun:test";
-import { bunExe, expectObjectTypeCount } from "harness";
+import { bunEnv, bunExe, expectMaxObjectTypeCount } from "harness";
import { connect, spawn } from "bun";
it("should keep process alive only when active", async () => {
@@ -9,9 +9,7 @@ it("should keep process alive only when active", async () => {
stdout: "pipe",
stdin: null,
stderr: "pipe",
- env: {
- BUN_DEBUG_QUIET_LOGS: 1,
- },
+ env: bunEnv,
});
expect(await exited).toBe(0);
@@ -92,7 +90,7 @@ it("should reject on connection error, calling both connectError() and rejecting
});
it("should not leak memory when connect() fails", async () => {
- await expectObjectTypeCount("TCPSocket", 1, 100);
+ await expectMaxObjectTypeCount("TCPSocket", 1, 100);
});
// this also tests we mark the promise as handled if connectError() is called
@@ -134,5 +132,5 @@ it("should handle connection error", done => {
});
it("should not leak memory when connect() fails again", async () => {
- await expectObjectTypeCount("TCPSocket", 1, 100);
+ await expectMaxObjectTypeCount("TCPSocket", 1, 100);
});
diff --git a/test/js/bun/test/test-test.test.ts b/test/js/bun/test/test-test.test.ts
index b834d2152..6dc40f97c 100644
--- a/test/js/bun/test/test-test.test.ts
+++ b/test/js/bun/test/test-test.test.ts
@@ -1,11 +1,52 @@
import { spawn, spawnSync } from "bun";
import { describe, expect, it, test } from "bun:test";
-import { bunExe, bunEnv } from "harness";
import { mkdirSync, realpathSync, rmSync, writeFileSync } from "fs";
import { mkdtemp, rm, writeFile } from "fs/promises";
+import { bunEnv, bunExe } from "harness";
import { tmpdir } from "os";
import { join } from "path";
+it("shouldn't crash when async test runner callback throws", async () => {
+ const code = `
+ beforeEach(async () => {
+ await 1;
+ throw "##123##";
+ });
+
+ afterEach(async () => {
+ await 1;
+ console.error("#[Test passed successfully]");
+ });
+
+ it("current", async () => {
+ await 1;
+ throw "##456##";
+ })
+`;
+
+ const test_dir = realpathSync(await mkdtemp(join(tmpdir(), "test")));
+ try {
+ await writeFile(join(test_dir, "bad.test.js"), code);
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "test", "bad.test.js"],
+ cwd: test_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env: bunEnv,
+ });
+ const err = await new Response(stderr).text();
+ expect(err).toContain("Test passed successfully");
+ expect(err).toContain("error: ##123##");
+ expect(err).toContain("error: ##456##");
+ expect(stdout).toBeDefined();
+ expect(await new Response(stdout).text()).toBe("");
+ expect(await exited).toBe(1);
+ } finally {
+ await rm(test_dir, { force: true, recursive: true });
+ }
+});
+
test("toStrictEqual() vs toEqual()", () => {
expect([1, , 3]).toEqual([1, , 3]);
expect({}).toEqual({});
@@ -176,6 +217,43 @@ test("toThrow", () => {
expect(() => {
return true;
}).not.toThrow(err);
+
+ const weirdThings = [
+ /watttt/g,
+ BigInt(123),
+ -42,
+ NaN,
+ Infinity,
+ -Infinity,
+ undefined,
+ null,
+ true,
+ false,
+ 0,
+ 1,
+ "",
+ "hello",
+ {},
+ [],
+ new Date(),
+ new Error(),
+ new RegExp("foo"),
+ new Map(),
+ new Set(),
+ Promise.resolve(),
+ Promise.reject(Symbol("123")).finally(() => {}),
+ Symbol("123"),
+ ];
+ for (const weirdThing of weirdThings) {
+ expect(() => {
+ throw weirdThing;
+ }).toThrow();
+ }
+
+ err.message = "null";
+ expect(() => {
+ throw null;
+ }).toThrow(err);
});
test("deepEquals derived strings and strings", () => {
diff --git a/test/js/bun/util/filesystem_router.test.ts b/test/js/bun/util/filesystem_router.test.ts
index b55e716c0..f5ee5c936 100644
--- a/test/js/bun/util/filesystem_router.test.ts
+++ b/test/js/bun/util/filesystem_router.test.ts
@@ -234,7 +234,7 @@ it("should support Request", async () => {
});
for (let current of [
- new Request({ url: "/posts/hello-world" }),
+ new Request({ url: "https://example.com123/posts/hello-world" }),
new Request({ url: "http://example.com/posts/hello-world" }),
]) {
const {
@@ -261,7 +261,7 @@ it("assetPrefix, src, and origin", async () => {
for (let current of [
// Reuqest
- new Request({ url: "/posts/hello-world" }),
+ new Request({ url: "http://helloooo.com/posts/hello-world" }),
new Request({ url: "https://nextjs.org/posts/hello-world" }),
]) {
const { name, src, filePath, checkThisDoesntCrash } = router.match(current);
diff --git a/test/js/node/os/os.test.js b/test/js/node/os/os.test.js
index ea685cdb7..d7cc41031 100644
--- a/test/js/node/os/os.test.js
+++ b/test/js/node/os/os.test.js
@@ -79,7 +79,7 @@ it("userInfo", () => {
if (process.platform !== "win32") {
expect(info.username).toBe(process.env.USER);
- expect(info.shell).toBe(process.env.SHELL);
+ expect(info.shell).toBe(process.env.SHELL || "unknown");
expect(info.uid >= 0).toBe(true);
expect(info.gid >= 0).toBe(true);
} else {
diff --git a/test/js/web/fetch/fetch.test.ts b/test/js/web/fetch/fetch.test.ts
index 065506fad..1f2345f85 100644
--- a/test/js/web/fetch/fetch.test.ts
+++ b/test/js/web/fetch/fetch.test.ts
@@ -718,7 +718,8 @@ describe("Blob", () => {
it(`${Constructor.name} arrayBuffer() with ${TypedArray.name}${withGC ? " with gc" : ""}`, async () => {
const data = new TypedArray(sample);
if (withGC) gc();
- const input = Constructor === Blob ? [data] : Constructor === Request ? { body: data } : data;
+ const input =
+ Constructor === Blob ? [data] : Constructor === Request ? { body: data, url: "http://example.com" } : data;
if (withGC) gc();
const blob = new Constructor(input);
if (withGC) gc();
@@ -1098,3 +1099,30 @@ it("body nullable", async () => {
expect(req.body).not.toBeNull();
}
});
+
+it("Request({}) throws", async () => {
+ expect(() => new Request({})).toThrow();
+});
+
+it("Request({toString() { throw 'wat'; } }) throws", async () => {
+ expect(
+ () =>
+ new Request({
+ toString() {
+ throw "wat";
+ },
+ }),
+ ).toThrow("wat");
+});
+
+it("should not be able to parse json from empty body", () => {
+ expect(async () => await new Response().json()).toThrow(SyntaxError);
+ expect(async () => await new Request("http://example.com/").json()).toThrow(SyntaxError);
+});
+
+it("#874", () => {
+ expect(new Request(new Request("https://example.com"), {}).url).toBe("https://example.com");
+ expect(new Request(new Request("https://example.com")).url).toBe("https://example.com");
+ // @ts-expect-error
+ expect(new Request({ url: "https://example.com" }).url).toBe("https://example.com");
+});
diff --git a/test/js/web/html/FormData.test.ts b/test/js/web/html/FormData.test.ts
index 9d0db4361..edefe8a53 100644
--- a/test/js/web/html/FormData.test.ts
+++ b/test/js/web/html/FormData.test.ts
@@ -14,14 +14,14 @@ describe("FormData", () => {
it("should be able to append a Blob", async () => {
const formData = new FormData();
formData.append("foo", new Blob(["bar"]));
- expect(await formData.get("foo").text()).toBe("bar");
+ expect(await formData.get("foo")!.text()).toBe("bar");
expect(formData.getAll("foo")[0] instanceof Blob).toBe(true);
});
it("should be able to set a Blob", async () => {
const formData = new FormData();
formData.set("foo", new Blob(["bar"]));
- expect(await formData.get("foo").text()).toBe("bar");
+ expect(await formData.get("foo")!.text()).toBe("bar");
expect(formData.getAll("foo")[0] instanceof Blob).toBe(true);
});
@@ -131,7 +131,8 @@ describe("FormData", () => {
const Class = [Response, Request] as const;
for (const C of Class) {
it(`should parse multipart/form-data (${name}) with ${C.name}`, async () => {
- const response = C === Response ? new Response(body, { headers }) : new Request({ headers, body });
+ const response =
+ C === Response ? new Response(body, { headers }) : new Request({ headers, body, url: "http://hello.com" });
const formData = await response.formData();
expect(formData instanceof FormData).toBe(true);
const entry = {};
@@ -159,7 +160,8 @@ describe("FormData", () => {
});
it(`should roundtrip multipart/form-data (${name}) with ${C.name}`, async () => {
- const response = C === Response ? new Response(body, { headers }) : new Request({ headers, body });
+ const response =
+ C === Response ? new Response(body, { headers }) : new Request({ headers, body, url: "http://hello.com" });
const formData = await response.formData();
expect(formData instanceof FormData).toBe(true);
@@ -306,7 +308,8 @@ describe("FormData", () => {
await Bun.write(path, "foo!");
const formData = new FormData();
formData.append("foo", Bun.file(path));
- const response = C === Response ? new Response(formData) : new Request({ body: formData });
+ const response =
+ C === Response ? new Response(formData) : new Request({ body: formData, url: "http://example.com" });
expect(response.headers.get("content-type")?.startsWith("multipart/form-data;")).toBe(true);
const formData2 = await response.formData();
diff --git a/test/js/web/streams/streams.test.js b/test/js/web/streams/streams.test.js
index 27b9b703f..5fc25b37c 100644
--- a/test/js/web/streams/streams.test.js
+++ b/test/js/web/streams/streams.test.js
@@ -304,7 +304,7 @@ it("new Request({body: stream}).body", async () => {
},
cancel() {},
});
- var response = new Request({ body: stream });
+ var response = new Request({ body: stream, url: "https://example.com" });
expect(response.body).toBe(stream);
expect(await response.text()).toBe("helloworld");
});
diff --git a/test/regression/issue/00631.test.ts b/test/regression/issue/00631.test.ts
index 691c3227c..c9303b5c1 100644
--- a/test/regression/issue/00631.test.ts
+++ b/test/regression/issue/00631.test.ts
@@ -12,7 +12,7 @@ it("JSON strings escaped properly", async () => {
// Create a directory with our test package file
mkdirSync(testDir, { recursive: true });
- writeFileSync(testDir + "package.json", String.raw`{"testRegex":"\\a\n\\b\\"}`);
+ writeFileSync(join(testDir, "package.json"), String.raw`{"testRegex":"\\a\n\\b\\"}`);
// Attempt to add a package, causing the package file to be parsed, modified,
// written, and reparsed. This verifies that escaped backslashes in JSON
@@ -24,8 +24,7 @@ it("JSON strings escaped properly", async () => {
});
expect(exitCode).toBe(0);
- console.log(testDir);
- const packageContents = readFileSync(testDir + "package.json", { encoding: "utf8" });
+ const packageContents = readFileSync(join(testDir, "package.json"), { encoding: "utf8" });
expect(packageContents).toBe(String.raw`{
"testRegex": "\\a\n\\b\\",
"dependencies": {
diff --git a/test/regression/issue/02368.test.ts b/test/regression/issue/02368.test.ts
index 7b151136e..5750d6fa4 100644
--- a/test/regression/issue/02368.test.ts
+++ b/test/regression/issue/02368.test.ts
@@ -1,6 +1,6 @@
import { test, expect } from "bun:test";
-test("can clone a response", () => {
+test("can clone a response", async () => {
const response = new Response("bun", {
status: 201,
headers: {
@@ -8,14 +8,14 @@ test("can clone a response", () => {
},
});
// @ts-ignore
- const clone = new Response(response);
+ const clone = response.clone();
expect(clone.status).toBe(201);
expect(clone.headers.get("content-type")).toBe("text/bun;charset=utf-8");
- expect(async () => await response.text()).toBe("bun");
- expect(async () => await clone.text()).toBe("bun");
+ expect(await response.text()).toBe("bun");
+ expect(await clone.text()).toBe("bun");
});
-test("can clone a request", () => {
+test("can clone a request", async () => {
const request = new Request("http://example.com/", {
method: "PUT",
headers: {
@@ -23,10 +23,11 @@ test("can clone a request", () => {
},
body: "bun",
});
+ expect(request.method).toBe("PUT");
// @ts-ignore
const clone = new Request(request);
expect(clone.method).toBe("PUT");
expect(clone.headers.get("content-type")).toBe("text/bun;charset=utf-8");
- expect(async () => await request.text()).toBe("bun");
- expect(async () => await clone.text()).toBe("bun");
+ expect(await request.text()).toBe("bun");
+ expect(await clone.text()).toBe("bun");
});