Compare commits

..

28 Commits

Author SHA1 Message Date
Ashcon Partovi
237a15851b Changes [no ci] 2024-12-11 18:48:10 -08:00
Ashcon Partovi
387800fe70 Changes [no ci] 2024-12-11 16:36:17 -08:00
Ashcon Partovi
d109a9eabe Changes [no ci] 2024-12-11 15:14:21 -08:00
Ashcon Partovi
83f430482c Changes [no ci] 2024-12-11 14:55:59 -08:00
Ashcon Partovi
b7959cc1e2 Changes [no ci] 2024-12-11 14:45:20 -08:00
Ashcon Partovi
ef092602c1 Changes [no ci] 2024-12-11 12:49:26 -08:00
Ashcon Partovi
38d55fface Changes [no ci] 2024-12-11 11:49:32 -08:00
Ashcon Partovi
06f9ea853e Changes [no ci] 2024-12-11 11:23:44 -08:00
Ashcon Partovi
da9e842014 Changes [no ci] 2024-12-11 11:08:02 -08:00
Ashcon Partovi
0f9f993d5e aarch64 [no ci] 2024-12-11 10:54:40 -08:00
Ashcon Partovi
1b9041d1ab Bump macos sdk [no ci] 2024-12-10 19:46:05 -08:00
Ashcon Partovi
23afa08cb0 Bump macos sdk [no ci] 2024-12-10 19:16:06 -08:00
Ashcon Partovi
90beb5350d Add toolchain 2024-12-10 18:57:09 -08:00
Ashcon Partovi
b0dd79e0a7 WIP 2024-12-10 18:42:20 -08:00
Ashcon Partovi
90a60c2c11 WIP 2024-12-10 17:38:11 -08:00
Don Isaac
af4f1c7d39 test: fix case to allow bun-debug (#15660)
Co-authored-by: Don Isaac <don@bun.sh>
2024-12-08 23:48:43 -08:00
github-actions[bot]
2c1dea818c deps: update sqlite to 3.470.200 (#15652)
Co-authored-by: Jarred-Sumner <Jarred-Sumner@users.noreply.github.com>
2024-12-08 02:16:41 -08:00
Kai Tamkun
cc125b475f Fix missing "readable" events (#15629) 2024-12-06 23:59:47 -08:00
Don Isaac
cbbf88f3a6 refactor: remove unused main_api.zig file (#15635)
Co-authored-by: Don Isaac <don@bun.sh>
2024-12-06 21:01:25 -08:00
Don Isaac
8064a55a48 test(bake): fix double free (#15634)
Co-authored-by: Don Isaac <don@bun.sh>
2024-12-06 20:06:26 -08:00
Jarred Sumner
0531d6756c Ci is doing too much 2024-12-06 19:28:28 -08:00
Ciro Spaciari
6135b3dec9 fix(CI) deflaky node-http.test.ts (#15625) 2024-12-06 19:16:59 -08:00
Don Isaac
b08dd8795e test(web): fix setTimeout refresh test (#15630)
Co-authored-by: Don Isaac <don@bun.sh>
2024-12-06 19:14:07 -08:00
Ciro Spaciari
c1eba5886f fix(net) signal should destroy the connection and propagate the error properly (#15624) 2024-12-06 16:10:33 -08:00
Ciro Spaciari
fcca2cc398 fix(fetch) fix redirect + Connection: close (#15623) 2024-12-06 15:06:11 -08:00
Yuto Ogino
dd32e6b416 Fix zsh auto-completion for package.json scripts with name containing colons (#15619) 2024-12-06 10:53:43 -08:00
Jarred Sumner
b453360dff Fixes #15480 (#15611) 2024-12-05 21:15:21 -08:00
pfg
1476e4c958 implement toThrowErrorMatchingSnapshot, toThrowErrorMatchingInlineSnapshot (#15607) 2024-12-05 19:07:18 -08:00
36 changed files with 1159 additions and 569 deletions

View File

@@ -743,6 +743,10 @@ function(register_cmake_command)
list(APPEND MAKE_EFFECTIVE_ARGS --fresh)
endif()
if(CMAKE_TOOLCHAIN_FILE)
list(APPEND MAKE_EFFECTIVE_ARGS --toolchain=${CMAKE_TOOLCHAIN_FILE})
endif()
register_command(
COMMENT "Configuring ${MAKE_TARGET}"
TARGET configure-${MAKE_TARGET}

View File

@@ -26,11 +26,31 @@ if(RELEASE)
list(APPEND LOLHTML_BUILD_ARGS --release)
endif()
if(CMAKE_CROSSCOMPILING)
if(ARCH STREQUAL "x64")
set(RUST_ARCH x86_64)
elseif(ARCH STREQUAL "arm64")
set(RUST_ARCH aarch64)
else()
unsupported(ARCH)
endif()
if(APPLE)
set(RUST_TARGET ${RUST_ARCH}-apple-darwin)
elseif(LINUX)
set(RUST_TARGET ${RUST_ARCH}-unknown-linux-gnu)
elseif(WIN32)
set(RUST_TARGET ${RUST_ARCH}-pc-windows-msvc)
else()
unsupported(CMAKE_SYSTEM_NAME)
endif()
list(APPEND LOLHTML_BUILD_ARGS --target=${RUST_TARGET})
endif()
# Windows requires unwind tables, apparently.
if (NOT WIN32)
# The encoded escape sequences are intentional. They're how you delimit multiple arguments in a single environment variable.
# Also add rust optimization flag for smaller binary size, but not huge speed penalty.
set(RUSTFLAGS "-Cpanic=abort-Cdebuginfo=0-Cforce-unwind-tables=no-Copt-level=s")
if(NOT WIN32)
set(RUST_FLAGS "-Cpanic=abort -Cdebuginfo=0 -Cforce-unwind-tables=no -Copt-level=s")
endif()
register_command(
@@ -48,12 +68,23 @@ register_command(
CARGO_TERM_COLOR=always
CARGO_TERM_VERBOSE=true
CARGO_TERM_DIAGNOSTIC=true
CARGO_ENCODED_RUSTFLAGS=${RUSTFLAGS}
CARGO_HOME=${CARGO_HOME}
RUSTUP_HOME=${RUSTUP_HOME}
CC=${CMAKE_C_COMPILER}
CFLAGS=${CMAKE_C_FLAGS}
CXX=${CMAKE_CXX_COMPILER}
CXXFLAGS=${CMAKE_CXX_FLAGS}
AR=${CMAKE_AR}
RUSTFLAGS=${RUST_FLAGS}
)
target_link_libraries(${bun} PRIVATE ${LOLHTML_LIBRARY})
if(BUN_LINK_ONLY)
target_sources(${bun} PRIVATE ${LOLHTML_LIBRARY})
endif()
# Notes for OSXCross, which doesn't work yet:
# CFLAGS += --sysroot=${CMAKE_OSX_SYSROOT}
# CXXFLAGS += --sysroot=${CMAKE_OSX_SYSROOT}
# LDFLAGS += -F${CMAKE_OSX_SYSROOT}/System/Library/Frameworks
# RUSTFLAGS += -C linker=${CMAKE_LINKER} -C link-arg=-F${CMAKE_OSX_SYSROOT}/System/Library/Frameworks -C link-arg=-L${CMAKE_OSX_SYSROOT}/usr/lib

View File

@@ -0,0 +1,28 @@
set(CMAKE_SYSTEM_NAME Darwin)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_C_COMPILER_WORKS ON)
set(CMAKE_CXX_COMPILER_WORKS ON)
set(OSXCROSS_TARGET_DIR "/opt/osxcross")
set(OSXCROSS_SDK "${OSXCROSS_TARGET_DIR}/SDK/MacOSX14.5.sdk")
set(OSXCROSS_HOST "aarch64-apple-darwin23.5")
set(CMAKE_C_COMPILER "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-clang" CACHE FILEPATH "clang")
set(CMAKE_CXX_COMPILER "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-clang++" CACHE FILEPATH "clang++")
set(CMAKE_LINKER "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-ld" CACHE FILEPATH "ld")
set(CMAKE_AR "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-ar" CACHE FILEPATH "ar")
set(CMAKE_STRIP "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-strip" CACHE FILEPATH "strip")
set(CMAKE_RANLIB "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-ranlib" CACHE FILEPATH "ranlib")
set(CMAKE_NM "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-nm" CACHE FILEPATH "nm")
set(CMAKE_DSYMUTIL "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-dsymutil" CACHE FILEPATH "dsymutil")
set(CMAKE_INSTALL_NAME_TOOL "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-install_name_tool" CACHE FILEPATH "install_name_tool")
set(CMAKE_FIND_ROOT_PATH "${CMAKE_FIND_ROOT_PATH}" "${OSXCROSS_SDK}" "${OSXCROSS_TARGET_DIR}/macports/pkgs/opt/local")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(ENV{PKG_CONFIG_LIBDIR} "${OSXCROSS_TARGET_DIR}/macports/pkgs/opt/local/lib/pkgconfig")
set(ENV{PKG_CONFIG_SYSROOT_DIR} "${OSXCROSS_TARGET_DIR}/macports/pkgs")

View File

@@ -0,0 +1,37 @@
set(CMAKE_SYSTEM_NAME Darwin)
set(CMAKE_SYSTEM_PROCESSOR x64)
set(CMAKE_OSX_ARCHITECTURES x86_64)
set(CMAKE_OSX_SDK_VERSION 14.5)
set(CMAKE_OSX_KERNEL_VERSION 23.5)
set(CMAKE_OSX_DEPLOYMENT_TARGET 13.0)
set(CMAKE_C_COMPILER_WORKS ON)
set(CMAKE_CXX_COMPILER_WORKS ON)
set(OSXCROSS_TARGET_DIR "/opt/osxcross")
set(OSXCROSS_SDK "${OSXCROSS_TARGET_DIR}/SDK/MacOSX${CMAKE_OSX_SDK_VERSION}.sdk")
set(OSXCROSS_HOST "x86_64-apple-darwin${CMAKE_OSX_KERNEL_VERSION}")
set(CMAKE_C_COMPILER "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-clang" CACHE FILEPATH "clang")
set(CMAKE_CXX_COMPILER "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-clang++" CACHE FILEPATH "clang++")
set(CMAKE_LINKER "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-ld" CACHE FILEPATH "ld")
set(CMAKE_AR "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-ar" CACHE FILEPATH "ar")
set(CMAKE_STRIP "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-strip" CACHE FILEPATH "strip")
set(CMAKE_RANLIB "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-ranlib" CACHE FILEPATH "ranlib")
set(CMAKE_NM "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-nm" CACHE FILEPATH "nm")
set(CMAKE_DSYMUTIL "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-dsymutil" CACHE FILEPATH "dsymutil")
set(CMAKE_INSTALL_NAME_TOOL "${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-install_name_tool" CACHE FILEPATH "install_name_tool")
set(CMAKE_SYSROOT "${OSXCROSS_SDK}")
set(CMAKE_OSX_SYSROOT "${OSXCROSS_SDK}")
set(CMAKE_FIND_ROOT_PATH "${CMAKE_FIND_ROOT_PATH}" "${OSXCROSS_SDK}" "${OSXCROSS_TARGET_DIR}/macports/pkgs/opt/local")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# This fixes an issue where pthreads is not found with c-ares
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
set(ENV{PKG_CONFIG_LIBDIR} "${OSXCROSS_TARGET_DIR}/macports/pkgs/opt/local/lib/pkgconfig")
set(ENV{PKG_CONFIG_SYSROOT_DIR} "${OSXCROSS_TARGET_DIR}/macports/pkgs")

View File

@@ -671,7 +671,7 @@ _bun() {
cmd)
local -a scripts_list
IFS=$'\n' scripts_list=($(SHELL=zsh bun getcompletes i))
scripts="scripts:scripts:(($scripts_list))"
scripts="scripts:scripts:((${scripts_list//:/\\\\:}))"
IFS=$'\n' files_list=($(SHELL=zsh bun getcompletes j))
main_commands=(
@@ -871,8 +871,8 @@ _bun_run_param_script_completion() {
IFS=$'\n' scripts_list=($(SHELL=zsh bun getcompletes s))
IFS=$'\n' bins=($(SHELL=zsh bun getcompletes b))
_alternative "scripts:scripts:(($scripts_list))"
_alternative "bin:bin:(($bins))"
_alternative "scripts:scripts:((${scripts_list//:/\\\\:}))"
_alternative "bin:bin:((${bins//:/\\\\:}))"
_alternative "files:file:_files -g '*.(js|ts|jsx|tsx|wasm)'"
}

View File

@@ -536,12 +536,12 @@ Bun implements the following matchers. Full Jest compatibility is on the roadmap
---
-
-
- [`.toThrowErrorMatchingSnapshot()`](https://jestjs.io/docs/expect#tothrowerrormatchingsnapshothint)
---
-
-
- [`.toThrowErrorMatchingInlineSnapshot()`](https://jestjs.io/docs/expect#tothrowerrormatchinginlinesnapshotinlinesnapshot)
{% /table %}

View File

@@ -1299,15 +1299,17 @@ declare module "bun:test" {
* Asserts that a value matches the most recent inline snapshot.
*
* @example
* expect("Hello").toMatchInlineSnapshot();
* expect("Hello").toMatchInlineSnapshot(`"Hello"`);
* @param value The latest snapshot value.
*
* @param value The latest automatically-updated snapshot value.
*/
toMatchInlineSnapshot(value?: string): void;
/**
* Asserts that a value matches the most recent inline snapshot.
*
* @example
* expect("Hello").toMatchInlineSnapshot(`"Hello"`);
* expect({ c: new Date() }).toMatchInlineSnapshot({ c: expect.any(Date) });
* expect({ c: new Date() }).toMatchInlineSnapshot({ c: expect.any(Date) }, `
* {
* "v": Any<Date>,
@@ -1315,9 +1317,35 @@ declare module "bun:test" {
* `);
*
* @param propertyMatchers Object containing properties to match against the value.
* @param hint Hint used to identify the snapshot in the snapshot file.
* @param value The latest automatically-updated snapshot value.
*/
toMatchInlineSnapshot(propertyMatchers?: object, value?: string): void;
/**
* Asserts that a function throws an error matching the most recent snapshot.
*
* @example
* function fail() {
* throw new Error("Oops!");
* }
* expect(fail).toThrowErrorMatchingSnapshot();
* expect(fail).toThrowErrorMatchingSnapshot("This one should say Oops!");
*
* @param value The latest automatically-updated snapshot value.
*/
toThrowErrorMatchingSnapshot(hint?: string): void;
/**
* Asserts that a function throws an error matching the most recent snapshot.
*
* @example
* function fail() {
* throw new Error("Oops!");
* }
* expect(fail).toThrowErrorMatchingInlineSnapshot();
* expect(fail).toThrowErrorMatchingInlineSnapshot(`"Oops!"`);
*
* @param value The latest automatically-updated snapshot value.
*/
toThrowErrorMatchingInlineSnapshot(value?: string): void;
/**
* Asserts that an object matches a subset of properties.
*

View File

@@ -965,8 +965,10 @@ int bsd_connect_udp_socket(LIBUS_SOCKET_DESCRIPTOR fd, const char *host, int por
char port_string[16];
snprintf(port_string, 16, "%d", port);
if (getaddrinfo(host, port_string, &hints, &result)) {
return -1;
int gai_error = getaddrinfo(host, port_string, &hints, &result);
if (gai_error != 0) {
return gai_error;
}
if (result == NULL) {

View File

@@ -29,7 +29,6 @@ endif()
if(CMAKE_C_COMPILER_ID MATCHES "GNU")
add_compile_options(
-fheinous-gnu-extensions
-Wno-string-plus-int
-Wno-deprecated-declarations
)
@@ -57,81 +56,100 @@ if(WIN32)
)
endif()
file(READ ${CMAKE_CURRENT_SOURCE_DIR}/VERSION TCC_VERSION)
option(BUILD_C2STR "Build the c2str utility" ON)
option(BUILD_TCC "Build the tcc compiler" ON)
add_compile_definitions(TCC_VERSION=\"${TCC_VERSION}\")
if(BUILD_C2STR)
if(CMAKE_CROSSCOMPILING)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/c2str)
add_custom_target(c2str.exe
COMMAND
${CMAKE_COMMAND}
-S ${CMAKE_CURRENT_SOURCE_DIR}
-B ${CMAKE_CURRENT_BINARY_DIR}/c2str
-G${CMAKE_GENERATOR}
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DBUILD_TCC=OFF
COMMAND
${CMAKE_COMMAND}
--build ${CMAKE_CURRENT_BINARY_DIR}/c2str
--target c2str.exe
WORKING_DIRECTORY
${CMAKE_CURRENT_BINARY_DIR}/c2str
)
else()
add_executable(c2str.exe conftest.c)
target_compile_options(c2str.exe PRIVATE -DC2STR)
execute_process(
COMMAND git rev-parse --short HEAD
OUTPUT_VARIABLE TCC_GITHASH
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if(TCC_GITHASH)
add_compile_definitions(TCC_GITHASH=\"${TCC_GITHASH}\")
add_custom_command(
TARGET
c2str.exe POST_BUILD
COMMAND
c2str.exe include/tccdefs.h tccdefs_.h
WORKING_DIRECTORY
${CMAKE_CURRENT_SOURCE_DIR}
)
endif()
endif()
set(TCC_SOURCES
libtcc.c
tccpp.c
tccgen.c
tccdbg.c
tccelf.c
tccasm.c
tccrun.c
)
if(BUILD_TCC)
file(READ ${CMAKE_CURRENT_SOURCE_DIR}/VERSION TCC_VERSION)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64|ARM64")
list(APPEND TCC_SOURCES
arm64-gen.c
arm64-link.c
arm64-asm.c
add_compile_definitions(TCC_VERSION=\"${TCC_VERSION}\")
execute_process(
COMMAND git rev-parse --short HEAD
OUTPUT_VARIABLE TCC_GITHASH
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|x64|amd64|AMD64")
list(APPEND TCC_SOURCES
x86_64-gen.c
x86_64-link.c
i386-asm.c
if(TCC_GITHASH)
add_compile_definitions(TCC_GITHASH=\"${TCC_GITHASH}\")
endif()
set(TCC_SOURCES
libtcc.c
tccpp.c
tccgen.c
tccdbg.c
tccelf.c
tccasm.c
tccrun.c
)
else()
message(FATAL_ERROR "Unsupported architecture: ${CMAKE_SYSTEM_PROCESSOR}")
endif()
if(APPLE)
list(APPEND TCC_SOURCES tccmacho.c)
endif()
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64|ARM64")
list(APPEND TCC_SOURCES
arm64-gen.c
arm64-link.c
arm64-asm.c
)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|x64|amd64|AMD64")
list(APPEND TCC_SOURCES
x86_64-gen.c
x86_64-link.c
i386-asm.c
)
else()
message(FATAL_ERROR "Unsupported architecture: ${CMAKE_SYSTEM_PROCESSOR}")
endif()
if(WIN32)
list(APPEND TCC_SOURCES tccpe.c)
endif()
if(APPLE)
list(APPEND TCC_SOURCES tccmacho.c)
endif()
add_executable(c2str.exe conftest.c)
target_compile_options(c2str.exe PRIVATE -DC2STR)
if(WIN32)
list(APPEND TCC_SOURCES tccpe.c)
endif()
add_custom_command(
TARGET
c2str.exe POST_BUILD
COMMAND
c2str.exe include/tccdefs.h tccdefs_.h
WORKING_DIRECTORY
add_library(tcc STATIC ${TCC_SOURCES})
if(BUILD_C2STR)
add_dependencies(tcc c2str.exe)
endif()
target_include_directories(tcc PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
add_library(tcc STATIC ${TCC_SOURCES})
add_custom_command(
TARGET
tcc PRE_BUILD
COMMAND
${CMAKE_COMMAND} -E touch config.h
WORKING_DIRECTORY
${CMAKE_CURRENT_SOURCE_DIR}
)
add_dependencies(tcc c2str.exe)
target_include_directories(tcc PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include
)
${CMAKE_CURRENT_SOURCE_DIR}/include
)
endif()

View File

@@ -1,5 +1,5 @@
#!/bin/sh
# Version: 7
# Version: 9
# A script that installs the dependencies needed to build and test Bun.
# This should work on macOS and Linux with a POSIX shell.
@@ -11,15 +11,17 @@
# increment the version comment to indicate that a new image should be built.
# Otherwise, the existing image will be retroactively updated.
pid=$$
pid="$$"
print() {
echo "$@"
}
error() {
echo "error: $@" >&2
kill -s TERM "$pid"
print "error: $@" >&2
if ! [ "$$" = "$pid" ]; then
kill -s TERM "$pid"
fi
exit 1
}
@@ -39,24 +41,44 @@ execute_sudo() {
}
execute_as_user() {
sh="$(require sh)"
if [ "$sudo" = "1" ] || [ "$can_sudo" = "1" ]; then
if [ -f "$(which sudo)" ]; then
execute sudo -n -u "$user" /bin/sh -c "$*"
execute sudo -n -u "$user" "$sh" -c "$*"
elif [ -f "$(which doas)" ]; then
execute doas -u "$user" /bin/sh -c "$*"
execute doas -u "$user" "$sh" -c "$*"
elif [ -f "$(which su)" ]; then
execute su -s /bin/sh "$user" -c "$*"
execute su -s "$sh" "$user" -c "$*"
else
execute /bin/sh -c "$*"
execute "$sh" -c "$*"
fi
else
execute /bin/sh -c "$*"
execute "$sh" -c "$*"
fi
}
grant_to_user() {
path="$1"
execute_sudo chown -R "$user:$group" "$path"
if ! [ -f "$path" ] && ! [ -d "$path" ]; then
error "Could not find file or directory: \"$path\""
fi
chown="$(require chown)"
execute_sudo "$chown" -R "$user:$group" "$path"
if ! [ "$user" = "$current_user" ] || ! [ "$group" = "$current_group" ]; then
execute_sudo "$chown" -R "$current_user:$current_group" "$path"
fi
}
grant_to_everyone() {
path="$1"
if ! [ -f "$path" ] && ! [ -d "$path" ]; then
error "Could not find file or directory: \"$path\""
fi
chmod="$(require chmod)"
execute_sudo "$chmod" 777 "$path"
}
which() {
@@ -68,15 +90,15 @@ require() {
if ! [ -f "$path" ]; then
error "Command \"$1\" is required, but is not installed."
fi
echo "$path"
print "$path"
}
fetch() {
curl=$(which curl)
curl="$(which curl)"
if [ -f "$curl" ]; then
execute "$curl" -fsSL "$1"
else
wget=$(which wget)
wget="$(which wget)"
if [ -f "$wget" ]; then
execute "$wget" -qO- "$1"
else
@@ -85,78 +107,115 @@ fetch() {
fi
}
download_file() {
url="$1"
filename="${2:-$(basename "$url")}"
tmp="$(execute mktemp -d)"
execute chmod 755 "$tmp"
compare_version() {
if [ "$1" = "$2" ]; then
print "0"
elif [ "$1" = "$(echo -e "$1\n$2" | sort -V | head -n1)" ]; then
print "-1"
else
print "1"
fi
}
path="$tmp/$filename"
fetch "$url" >"$path"
execute chmod 644 "$path"
create_directory() {
path="$1"
path_dir="$path"
while ! [ -d "$path_dir" ]; do
path_dir="$(dirname "$path_dir")"
done
path_needs_sudo="0"
if ! [ -r "$path_dir" ] || ! [ -w "$path_dir" ]; then
path_needs_sudo="1"
fi
mkdir="$(require mkdir)"
if [ "$path_needs_sudo" = "1" ]; then
execute_sudo "$mkdir" -p "$path"
else
execute "$mkdir" -p "$path"
fi
grant_to_user "$path"
}
create_tmp_directory() {
mktemp="$(require mktemp)"
path="$(execute "$mktemp" -d)"
grant_to_everyone "$path"
print "$path"
}
compare_version() {
if [ "$1" = "$2" ]; then
echo "0"
elif [ "$1" = "$(echo -e "$1\n$2" | sort -V | head -n1)" ]; then
echo "-1"
else
echo "1"
create_file() {
path="$1"
path_dir="$(dirname "$path")"
if ! [ -d "$path_dir" ]; then
create_directory "$path_dir"
fi
path_needs_sudo="0"
if ! [ -r "$path" ] || ! [ -w "$path" ]; then
path_needs_sudo="1"
fi
if [ "$path_needs_sudo" = "1" ]; then
execute_sudo touch "$path"
else
execute touch "$path"
fi
content="$2"
if [ -n "$content" ]; then
append_file "$path" "$content"
fi
grant_to_user "$path"
}
append_to_file() {
file="$1"
content="$2"
file_needs_sudo="0"
if [ -f "$file" ]; then
if ! [ -r "$file" ] || ! [ -w "$file" ]; then
file_needs_sudo="1"
fi
else
execute_as_user mkdir -p "$(dirname "$file")"
execute_as_user touch "$file"
append_file() {
path="$1"
if ! [ -f "$path" ]; then
create_file "$path"
fi
echo "$content" | while read -r line; do
if ! grep -q "$line" "$file"; then
if [ "$file_needs_sudo" = "1" ]; then
execute_sudo sh -c "echo '$line' >> '$file'"
path_needs_sudo="0"
if ! [ -r "$path" ] || ! [ -w "$path" ]; then
path_needs_sudo="1"
fi
content="$2"
print "$content" | while read -r line; do
if ! grep -q "$line" "$path"; then
sh="$(require sh)"
if [ "$path_needs_sudo" = "1" ]; then
execute_sudo "$sh" -c "echo '$line' >> '$path'"
else
echo "$line" >>"$file"
execute "$sh" -c "echo '$line' >> '$path'"
fi
fi
done
}
append_to_file_sudo() {
file="$1"
content="$2"
download_file() {
file_url="$1"
file_tmp_dir="$(create_tmp_directory)"
file_tmp_path="$file_tmp_dir/$(basename "$file_url")"
if ! [ -f "$file" ]; then
execute_sudo mkdir -p "$(dirname "$file")"
execute_sudo touch "$file"
fi
echo "$content" | while read -r line; do
if ! grep -q "$line" "$file"; then
echo "$line" | execute_sudo tee "$file" >/dev/null
fi
done
fetch "$file_url" >"$file_tmp_path"
grant_to_everyone "$file_tmp_path"
print "$file_tmp_path"
}
append_to_profile() {
content="$1"
profiles=".profile .zprofile .bash_profile .bashrc .zshrc"
for profile in $profiles; do
file="$home/$profile"
if [ "$ci" = "1" ] || [ -f "$file" ]; then
append_to_file "$file" "$content"
fi
for profile_path in "$current_home/$profile" "$home/$profile"; do
if [ "$ci" = "1" ] || [ -f "$profile_path" ]; then
append_file "$profile_path" "$content"
fi
done
done
}
@@ -167,7 +226,7 @@ append_to_path() {
fi
append_to_profile "export PATH=\"$path:\$PATH\""
export PATH="$path:$PATH"
# export PATH="$path:$PATH"
}
move_to_bin() {
@@ -190,19 +249,22 @@ move_to_bin() {
check_features() {
print "Checking features..."
case "$CI" in
true | 1)
ci=1
print "CI: enabled"
;;
esac
case "$@" in
*--ci*)
ci=1
print "CI: enabled"
;;
esac
for arg in "$@"; do
case "$arg" in
*--ci*)
ci=1
print "CI: enabled"
;;
*--osxcross*)
osxcross=1
print "Cross-compiling to macOS: enabled"
;;
*--gcc-13*)
gcc_version="13"
print "GCC 13: enabled"
;;
esac
done
}
check_operating_system() {
@@ -211,17 +273,29 @@ check_operating_system() {
os="$("$uname" -s)"
case "$os" in
Linux*) os="linux" ;;
Darwin*) os="darwin" ;;
*) error "Unsupported operating system: $os" ;;
Linux*)
os="linux"
;;
Darwin*)
os="darwin"
;;
*)
error "Unsupported operating system: $os"
;;
esac
print "Operating System: $os"
arch="$("$uname" -m)"
case "$arch" in
x86_64 | x64 | amd64) arch="x64" ;;
aarch64 | arm64) arch="aarch64" ;;
*) error "Unsupported architecture: $arch" ;;
x86_64 | x64 | amd64)
arch="x64"
;;
aarch64 | arm64)
arch="aarch64"
;;
*)
error "Unsupported architecture: $arch"
;;
esac
print "Architecture: $arch"
@@ -235,7 +309,7 @@ check_operating_system() {
abi="musl"
alpine="$(cat /etc/alpine-release)"
if [ "$alpine" ~ "_" ]; then
release="$(echo "$alpine" | cut -d_ -f1)-edge"
release="$(print "$alpine" | cut -d_ -f1)-edge"
else
release="$alpine"
fi
@@ -255,6 +329,7 @@ check_operating_system() {
distro="$("$sw_vers" -productName)"
release="$("$sw_vers" -productVersion)"
fi
case "$arch" in
x64)
sysctl="$(which sysctl)"
@@ -277,7 +352,7 @@ check_operating_system() {
ldd="$(which ldd)"
if [ -f "$ldd" ]; then
ldd_version="$($ldd --version 2>&1)"
abi_version="$(echo "$ldd_version" | grep -o -E '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -n 1)"
abi_version="$(print "$ldd_version" | grep -o -E '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -n 1)"
case "$ldd_version" in
*musl*)
abi="musl"
@@ -394,6 +469,10 @@ check_user() {
can_sudo=1
print "Sudo: can be used"
fi
current_user="$user"
current_group="$group"
current_home="$home"
}
check_ulimit() {
@@ -405,15 +484,12 @@ check_ulimit() {
systemd_conf="/etc/systemd/system.conf"
if [ -f "$systemd_conf" ]; then
limits_conf="/etc/security/limits.d/99-unlimited.conf"
if ! [ -f "$limits_conf" ]; then
execute_sudo mkdir -p "$(dirname "$limits_conf")"
execute_sudo touch "$limits_conf"
fi
create_file "$limits_conf"
fi
limits="core data fsize memlock nofile rss stack cpu nproc as locks sigpending msgqueue"
for limit in $limits; do
limit_upper="$(echo "$limit" | tr '[:lower:]' '[:upper:]')"
limit_upper="$(print "$limit" | tr '[:lower:]' '[:upper:]')"
limit_value="unlimited"
case "$limit" in
@@ -425,13 +501,13 @@ check_ulimit() {
if [ -f "$limits_conf" ]; then
limit_users="root *"
for limit_user in $limit_users; do
append_to_file "$limits_conf" "$limit_user soft $limit $limit_value"
append_to_file "$limits_conf" "$limit_user hard $limit $limit_value"
append_file "$limits_conf" "$limit_user soft $limit $limit_value"
append_file "$limits_conf" "$limit_user hard $limit $limit_value"
done
fi
if [ -f "$systemd_conf" ]; then
append_to_file "$systemd_conf" "DefaultLimit$limit_upper=$limit_value"
append_file "$systemd_conf" "DefaultLimit$limit_upper=$limit_value"
fi
done
@@ -448,13 +524,13 @@ check_ulimit() {
esac
rc_ulimit="$rc_ulimit -$limit_flag $limit_value"
done
append_to_file "$rc_conf" "rc_ulimit=\"$rc_ulimit\""
append_file "$rc_conf" "rc_ulimit=\"$rc_ulimit\""
fi
pam_confs="/etc/pam.d/common-session /etc/pam.d/common-session-noninteractive"
for pam_conf in $pam_confs; do
if [ -f "$pam_conf" ]; then
append_to_file "$pam_conf" "session optional pam_limits.so"
append_file "$pam_conf" "session optional pam_limits.so"
fi
done
@@ -557,7 +633,7 @@ install_brew() {
bash="$(require bash)"
script=$(download_file "https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh")
NONINTERACTIVE=1 execute_as_user "$bash" "$script"
execute_as_user "$bash" -c "NONINTERACTIVE=1 $script"
case "$arch" in
x64)
@@ -638,7 +714,7 @@ nodejs_version_exact() {
}
nodejs_version() {
echo "$(nodejs_version_exact)" | cut -d. -f1
print "$(nodejs_version_exact)" | cut -d. -f1
}
install_nodejs() {
@@ -674,14 +750,21 @@ install_nodejs() {
}
install_nodejs_headers() {
headers_tar="$(download_file "https://nodejs.org/download/release/v$(nodejs_version_exact)/node-v$(nodejs_version_exact)-headers.tar.gz")"
headers_dir="$(dirname "$headers_tar")"
execute tar -xzf "$headers_tar" -C "$headers_dir"
headers_include="$headers_dir/node-v$(nodejs_version_exact)/include"
execute_sudo cp -R "$headers_include/" "/usr"
nodejs_headers_tar="$(download_file "https://nodejs.org/download/release/v$(nodejs_version_exact)/node-v$(nodejs_version_exact)-headers.tar.gz")"
nodejs_headers_dir="$(dirname "$nodejs_headers_tar")"
execute tar -xzf "$nodejs_headers_tar" -C "$nodejs_headers_dir"
nodejs_headers_include="$nodejs_headers_dir/node-v$(nodejs_version_exact)/include"
execute_sudo cp -R "$nodejs_headers_include/" "/usr"
}
bun_version_exact() {
print "1.1.38"
}
install_bun() {
install_packages unzip
case "$pm" in
apk)
install_packages \
@@ -690,23 +773,28 @@ install_bun() {
;;
esac
bash="$(require bash)"
script=$(download_file "https://bun.sh/install")
version="${1:-"latest"}"
case "$version" in
latest)
execute_as_user "$bash" "$script"
case "$abi" in
musl)
bun_triplet="bun-$os-$arch-$abi"
;;
*)
execute_as_user "$bash" "$script" -s "$version"
bun_triplet="bun-$os-$arch"
;;
esac
move_to_bin "$home/.bun/bin/bun"
bun_path="$(which bun)"
bunx_path="$(dirname "$bun_path")/bunx"
execute_sudo ln -sf "$bun_path" "$bunx_path"
unzip="$(require unzip)"
bun_download_url="https://pub-5e11e972747a44bf9aaf9394f185a982.r2.dev/releases/bun-v$(bun_version_exact)/$bun_triplet.zip"
bun_zip="$(download_file "$bun_download_url")"
bun_tmpdir="$(dirname "$bun_zip")"
execute "$unzip" -o "$bun_zip" -d "$bun_tmpdir"
bun_path="/opt/bun"
create_directory "$bun_path/bin"
execute mv "$bun_tmpdir/$bun_triplet/bun" "$bun_path/bin/bun"
execute ln -sf "$bun_path/bin/bun" "$bun_path/bin/bunx"
append_to_path "$bun_path/bin"
append_to_profile "export BUN_INSTALL=$bun_path"
}
install_cmake() {
@@ -799,24 +887,19 @@ install_build_essentials() {
install_cmake
install_llvm
install_osxcross
install_gcc
install_ccache
install_rust
install_docker
}
llvm_version_exact() {
case "$os-$abi" in
darwin-* | windows-* | linux-musl)
print "18.1.8"
;;
linux-*)
print "16.0.6"
;;
esac
print "18.1.8"
}
llvm_version() {
echo "$(llvm_version_exact)" | cut -d. -f1
print "$(llvm_version_exact)" | cut -d. -f1
}
install_llvm() {
@@ -824,14 +907,7 @@ install_llvm() {
apt)
bash="$(require bash)"
llvm_script="$(download_file "https://apt.llvm.org/llvm.sh")"
case "$distro-$release" in
ubuntu-24*)
execute_sudo "$bash" "$llvm_script" "$(llvm_version)" all -njammy
;;
*)
execute_sudo "$bash" "$llvm_script" "$(llvm_version)" all
;;
esac
execute_sudo "$bash" "$llvm_script" "$(llvm_version)" all
;;
brew)
install_packages "llvm@$(llvm_version)"
@@ -849,6 +925,55 @@ install_llvm() {
esac
}
install_gcc() {
if ! [ "$os" = "linux" ] || ! [ "$distro" = "ubuntu" ] || [ -z "$gcc_version" ]; then
return
fi
# Taken from WebKit's Dockerfile.
# https://github.com/oven-sh/WebKit/blob/816a3c02e0f8b53f8eec06b5ed911192589b51e2/Dockerfile
execute_sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
execute_sudo apt update -y
install_packages \
"gcc-$gcc_version" \
"g++-$gcc_version" \
"libgcc-$gcc_version-dev" \
"libstdc++-$gcc_version-dev" \
libasan6 \
libubsan1 \
libatomic1 \
libtsan0 \
liblsan0 \
libgfortran5 \
libc6-dev
execute_sudo update-alternatives \
--install /usr/bin/gcc gcc "/usr/bin/gcc-$gcc_version" 130 \
--slave /usr/bin/g++ g++ "/usr/bin/g++-$gcc_version" \
--slave /usr/bin/gcc-ar gcc-ar "/usr/bin/gcc-ar-$gcc_version" \
--slave /usr/bin/gcc-nm gcc-nm "/usr/bin/gcc-nm-$gcc_version" \
--slave /usr/bin/gcc-ranlib gcc-ranlib "/usr/bin/gcc-ranlib-$gcc_version"
case "$arch" in
x64)
arch_path="x86_64-linux-gnu"
;;
aarch64)
arch_path="aarch64-linux-gnu"
;;
esac
gcc_path="/usr/lib/gcc/$arch_path/$gcc_version"
create_directory "$gcc_path"
execute_sudo ln -sf /usr/lib/$arch_path/libstdc++.so.6 "$gcc_path/libstdc++.so.6"
ld_conf_path="/etc/ld.so.conf.d/gcc-$gcc_version.conf"
append_file "$ld_conf_path" "$gcc_path"
append_file "$ld_conf_path" "/usr/lib/$arch_path"
execute_sudo ldconfig
}
install_ccache() {
case "$pm" in
apt | apk | brew)
@@ -865,9 +990,23 @@ install_rust() {
cargo
;;
*)
rust_home="/opt/rust"
create_directory "$rust_home"
append_to_profile "export RUSTUP_HOME=$rust_home"
append_to_profile "export CARGO_HOME=$rust_home"
sh="$(require sh)"
script=$(download_file "https://sh.rustup.rs")
execute_as_user "$sh" "$script" -y
rustup_script=$(download_file "https://sh.rustup.rs")
execute "$sh" -c "RUSTUP_HOME=$rust_home CARGO_HOME=$rust_home $rustup_script -y --no-modify-path"
append_to_path "$rust_home/bin"
;;
esac
case "$osxcross" in
1)
rustup="$(require rustup)"
execute_as_user "$rustup" target add aarch64-apple-darwin
execute_as_user "$rustup" target add x86_64-apple-darwin
;;
esac
}
@@ -910,6 +1049,46 @@ install_docker() {
fi
}
macos_sdk_version() {
# https://github.com/alexey-lysiuk/macos-sdk/releases
print "14.5"
}
install_osxcross() {
if ! [ "$os" = "linux" ] || ! [ "$osxcross" = "1" ]; then
return
fi
install_packages \
libssl-dev \
lzma-dev \
libxml2-dev \
zlib1g-dev \
bzip2 \
cpio
osxcross_path="/opt/osxcross"
create_directory "$osxcross_path"
osxcross_commit="29fe6dd35522073c9df5800f8cd1feb4b9a993a8"
osxcross_tar="$(download_file "https://github.com/tpoechtrager/osxcross/archive/$osxcross_commit.tar.gz")"
execute tar -xzf "$osxcross_tar" -C "$osxcross_path"
osxcross_build_path="$osxcross_path/build"
execute mv "$osxcross_path/osxcross-$osxcross_commit" "$osxcross_build_path"
osxcross_sdk_tar="$(download_file "https://github.com/alexey-lysiuk/macos-sdk/releases/download/$(macos_sdk_version)/MacOSX$(macos_sdk_version).tar.xz")"
execute mv "$osxcross_sdk_tar" "$osxcross_build_path/tarballs/MacOSX$(macos_sdk_version).sdk.tar.xz"
bash="$(require bash)"
execute_sudo ln -sf "$(which clang-$(llvm_version))" /usr/bin/clang
execute_sudo ln -sf "$(which clang++-$(llvm_version))" /usr/bin/clang++
execute_sudo "$bash" -c "UNATTENDED=1 TARGET_DIR='$osxcross_path' $osxcross_build_path/build.sh"
execute_sudo rm -rf "$osxcross_build_path"
grant_to_user "$osxcross_path"
}
install_tailscale() {
if [ "$docker" = "1" ]; then
return
@@ -975,14 +1154,12 @@ create_buildkite_user() {
buildkite_paths="$home /var/cache/buildkite-agent /var/log/buildkite-agent /var/run/buildkite-agent /var/run/buildkite-agent/buildkite-agent.sock"
for path in $buildkite_paths; do
execute_sudo mkdir -p "$path"
execute_sudo chown -R "$user:$group" "$path"
create_directory "$path"
done
buildkite_files="/var/run/buildkite-agent/buildkite-agent.pid"
for file in $buildkite_files; do
execute_sudo touch "$file"
execute_sudo chown "$user:$group" "$file"
create_file "$file"
done
}
@@ -992,27 +1169,22 @@ install_buildkite() {
fi
buildkite_version="3.87.0"
case "$os-$arch" in
linux-aarch64)
buildkite_filename="buildkite-agent-linux-arm64-$buildkite_version.tar.gz"
case "$arch" in
aarch64)
buildkite_arch="arm64"
;;
linux-x64)
buildkite_filename="buildkite-agent-linux-amd64-$buildkite_version.tar.gz"
;;
darwin-aarch64)
buildkite_filename="buildkite-agent-darwin-arm64-$buildkite_version.tar.gz"
;;
darwin-x64)
buildkite_filename="buildkite-agent-darwin-amd64-$buildkite_version.tar.gz"
x64)
buildkite_arch="amd64"
;;
esac
buildkite_url="https://github.com/buildkite/agent/releases/download/v$buildkite_version/$buildkite_filename"
buildkite_filepath="$(download_file "$buildkite_url" "$buildkite_filename")"
buildkite_tmpdir="$(dirname "$buildkite_filepath")"
execute tar -xzf "$buildkite_filepath" -C "$buildkite_tmpdir"
buildkite_filename="buildkite-agent-$os-$buildkite_arch-$buildkite_version.tar.gz"
buildkite_url="https://github.com/buildkite/agent/releases/download/v$buildkite_version/$buildkite_filename"
buildkite_tar="$(download_file "$buildkite_url")"
buildkite_tmpdir="$(dirname "$buildkite_tar")"
execute tar -xzf "$buildkite_tar" -C "$buildkite_tmpdir"
move_to_bin "$buildkite_tmpdir/buildkite-agent"
execute rm -rf "$buildkite_tmpdir"
}
install_chromium() {
@@ -1103,6 +1275,19 @@ install_chromium() {
esac
}
clean_system() {
if ! [ "$ci" = "1" ]; then
return
fi
print "Cleaning system..."
tmp_paths="/tmp /var/tmp"
for path in $tmp_paths; do
execute_sudo rm -rf "$path"/*
done
}
main() {
check_features "$@"
check_operating_system
@@ -1114,6 +1299,7 @@ main() {
install_common_software
install_build_essentials
install_chromium
clean_system
}
main "$@"

View File

@@ -2374,6 +2374,7 @@ function getCloud(name) {
* @typedef {"linux" | "darwin" | "windows"} Os
* @typedef {"aarch64" | "x64"} Arch
* @typedef {"macos" | "windowsserver" | "debian" | "ubuntu" | "alpine" | "amazonlinux"} Distro
* @typedef {"osxcross" | "gcc-13"} Feature
*/
/**
@@ -2383,6 +2384,7 @@ function getCloud(name) {
* @property {Distro} distro
* @property {string} release
* @property {string} [eol]
* @property {Feature[]} [features]
*/
/**
@@ -2416,6 +2418,7 @@ function getCloud(name) {
* @property {Arch} arch
* @property {Distro} distro
* @property {string} [release]
* @property {Feature[]} [features]
* @property {string} [name]
* @property {string} [instanceType]
* @property {string} [imageId]
@@ -2464,6 +2467,7 @@ async function main() {
"detached": { type: "boolean" },
"tag": { type: "string", multiple: true },
"ci": { type: "boolean" },
"feature": { type: "string", multiple: true },
"rdp": { type: "boolean" },
"vnc": { type: "boolean" },
"authorized-user": { type: "string", multiple: true },
@@ -2501,6 +2505,7 @@ async function main() {
arch: parseArch(args["arch"]),
distro: args["distro"],
release: args["release"],
features: args["feature"],
name: args["name"],
instanceType: args["instance-type"],
imageId: args["image-id"],
@@ -2517,7 +2522,7 @@ async function main() {
sshKeys,
};
const { detached, bootstrap, ci, os, arch, distro, release } = options;
const { detached, bootstrap, ci, os, arch, distro, release, features } = options;
const name = distro ? `${os}-${arch}-${distro}-${release}` : `${os}-${arch}-${release}`;
let bootstrapPath, agentPath;
@@ -2627,6 +2632,9 @@ async function main() {
} else {
const remotePath = "/tmp/bootstrap.sh";
const args = ci ? ["--ci"] : [];
for (const feature of features || []) {
args.push(`--${feature}`);
}
await startGroup("Running bootstrap...", async () => {
await machine.upload(bootstrapPath, remotePath);
await machine.spawnSafe(["sh", remotePath, ...args], { stdio: "inherit" });

View File

@@ -1232,7 +1232,6 @@ pub const JSFrameworkRouter = struct {
pub fn finalize(this: *JSFrameworkRouter) void {
this.files.deinit(bun.default_allocator);
this.router.deinit(bun.default_allocator);
bun.default_allocator.free(this.router.types);
for (this.stored_parse_errors.items) |i| bun.default_allocator.free(i.rel_path);
this.stored_parse_errors.deinit(bun.default_allocator);
bun.destroy(this);

View File

@@ -290,14 +290,6 @@ pub const UDPSocket = struct {
.vm = vm,
});
// also cleans up config
defer {
if (globalThis.hasException()) {
this.closed = true;
this.deinit();
}
}
if (uws.udp.Socket.create(
this.loop,
onData,
@@ -309,15 +301,26 @@ pub const UDPSocket = struct {
)) |socket| {
this.socket = socket;
} else {
this.closed = true;
this.deinit();
return globalThis.throw("Failed to bind socket", .{});
}
errdefer {
this.socket.close();
this.deinit();
}
if (config.connect) |connect| {
const ret = this.socket.connect(connect.address, connect.port);
if (ret != 0) {
if (JSC.Maybe(void).errnoSys(ret, .connect)) |err| {
return globalThis.throwValue(err.toJS(globalThis));
}
if (bun.c_ares.Error.initEAI(ret)) |err| {
return globalThis.throwValue(err.toJS(globalThis));
}
}
this.connect_info = .{ .port = connect.port };
}
@@ -645,7 +648,7 @@ pub const UDPSocket = struct {
// finalize is only called when js_refcount reaches 0
// js_refcount can only reach 0 when the socket is closed
bun.assert(this.closed);
this.poll_ref.disable();
this.config.deinit();
this.destroy();
}

View File

@@ -74,7 +74,7 @@ JSC::JSValue generateModule(JSC::JSGlobalObject* globalObject, JSC::VM& vm, cons
return result;
}
#if BUN_DYNAMIC_JS_LOAD_PATH
#ifdef BUN_DYNAMIC_JS_LOAD_PATH
JSValue initializeInternalModuleFromDisk(
JSGlobalObject* globalObject,
VM& vm,

View File

@@ -6032,11 +6032,10 @@ CPP_DECL bool Bun__CallFrame__isFromBunMain(JSC::CallFrame* callFrame, JSC::VM*
return source.string() == "builtin://bun/main"_s;
}
CPP_DECL void Bun__CallFrame__getCallerSrcLoc(JSC::CallFrame* callFrame, JSC::JSGlobalObject* globalObject, unsigned int* outSourceID, unsigned int* outLine, unsigned int* outColumn)
CPP_DECL void Bun__CallFrame__getCallerSrcLoc(JSC::CallFrame* callFrame, JSC::JSGlobalObject* globalObject, BunString* outSourceURL, unsigned int* outLine, unsigned int* outColumn)
{
JSC::VM& vm = globalObject->vm();
JSC::LineColumn lineColumn;
JSC::SourceID sourceID = 0;
String sourceURL;
ZigStackFrame remappedFrame = {};
@@ -6046,6 +6045,7 @@ CPP_DECL void Bun__CallFrame__getCallerSrcLoc(JSC::CallFrame* callFrame, JSC::JS
return WTF::IterationStatus::Continue;
if (visitor->hasLineAndColumnInfo()) {
lineColumn = visitor->computeLineAndColumn();
String sourceURLForFrame = visitor->sourceURL();
@@ -6071,8 +6071,6 @@ CPP_DECL void Bun__CallFrame__getCallerSrcLoc(JSC::CallFrame* callFrame, JSC::JS
sourceURLForFrame = origin.string();
}
}
sourceID = provider->asID();
}
}
@@ -6099,7 +6097,7 @@ CPP_DECL void Bun__CallFrame__getCallerSrcLoc(JSC::CallFrame* callFrame, JSC::JS
lineColumn.column = OrdinalNumber::fromZeroBasedInt(remappedFrame.position.column_zero_based).oneBasedInt();
}
*outSourceID = sourceID;
*outSourceURL = Bun::toStringRef(sourceURL);
*outLine = lineColumn.line;
*outColumn = lineColumn.column;
}

View File

@@ -6666,19 +6666,19 @@ pub const CallFrame = opaque {
return value;
}
extern fn Bun__CallFrame__getCallerSrcLoc(*const CallFrame, *JSGlobalObject, *c_uint, *c_uint, *c_uint) void;
extern fn Bun__CallFrame__getCallerSrcLoc(*const CallFrame, *JSGlobalObject, *bun.String, *c_uint, *c_uint) void;
pub const CallerSrcLoc = struct {
source_file_id: c_uint,
str: bun.String,
line: c_uint,
column: c_uint,
};
pub fn getCallerSrcLoc(call_frame: *const CallFrame, globalThis: *JSGlobalObject) CallerSrcLoc {
var source_id: c_uint = undefined;
var str: bun.String = undefined;
var line: c_uint = undefined;
var column: c_uint = undefined;
Bun__CallFrame__getCallerSrcLoc(call_frame, globalThis, &source_id, &line, &column);
Bun__CallFrame__getCallerSrcLoc(call_frame, globalThis, &str, &line, &column);
return .{
.source_file_id = source_id,
.str = str,
.line = line,
.column = column,
};

View File

@@ -1,7 +1,7 @@
// clang-format off
/******************************************************************************
** This file is an amalgamation of many separate C source files from SQLite
** version 3.47.1. By combining all the individual C code files into this
** version 3.47.2. By combining all the individual C code files into this
** single large file, the entire code can be compiled as a single translation
** unit. This allows many compilers to do optimizations that would not be
** possible if the files were compiled separately. Performance improvements
@@ -19,7 +19,7 @@
** separate file. This file contains only code for the core SQLite library.
**
** The content in this amalgamation comes from Fossil check-in
** b95d11e958643b969c47a8e5857f3793b9e6.
** 2aabe05e2e8cae4847a802ee2daddc1d7413.
*/
#define SQLITE_CORE 1
#define SQLITE_AMALGAMATION 1
@@ -463,9 +463,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.47.1"
#define SQLITE_VERSION_NUMBER 3047001
#define SQLITE_SOURCE_ID "2024-11-25 12:07:48 b95d11e958643b969c47a8e5857f3793b9e69700b8f1469371386369a26e577e"
#define SQLITE_VERSION "3.47.2"
#define SQLITE_VERSION_NUMBER 3047002
#define SQLITE_SOURCE_ID "2024-12-07 20:39:59 2aabe05e2e8cae4847a802ee2daddc1d7413d8fc560254d93ee3e72c14685b6c"
/*
** CAPI3REF: Run-Time Library Version Numbers
@@ -35698,8 +35698,8 @@ SQLITE_PRIVATE int sqlite3AtoF(const char *z, double *pResult, int length, u8 en
int eValid = 1; /* True exponent is either not used or is well-formed */
int nDigit = 0; /* Number of digits processed */
int eType = 1; /* 1: pure integer, 2+: fractional -1 or less: bad UTF16 */
u64 s2; /* round-tripped significand */
double rr[2];
u64 s2;
assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE );
*pResult = 0.0; /* Default return value, in case of an error */
@@ -35802,7 +35802,7 @@ do_atof_calc:
e = (e*esign) + d;
/* Try to adjust the exponent to make it smaller */
while( e>0 && s<(LARGEST_UINT64/10) ){
while( e>0 && s<((LARGEST_UINT64-0x7ff)/10) ){
s *= 10;
e--;
}
@@ -35812,11 +35812,16 @@ do_atof_calc:
}
rr[0] = (double)s;
s2 = (u64)rr[0];
#if defined(_MSC_VER) && _MSC_VER<1700
if( s2==0x8000000000000000LL ){ s2 = 2*(u64)(0.5*rr[0]); }
#endif
rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s);
assert( sizeof(s2)==sizeof(rr[0]) );
memcpy(&s2, &rr[0], sizeof(s2));
if( s2<=0x43efffffffffffffLL ){
s2 = (u64)rr[0];
rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s);
}else{
rr[1] = 0.0;
}
assert( rr[1]<=1.0e-10*rr[0] ); /* Equal only when rr[0]==0.0 */
if( e>0 ){
while( e>=100 ){
e -= 100;
@@ -147606,32 +147611,32 @@ static Expr *substExpr(
if( pSubst->isOuterJoin ){
ExprSetProperty(pNew, EP_CanBeNull);
}
if( pNew->op==TK_TRUEFALSE ){
pNew->u.iValue = sqlite3ExprTruthValue(pNew);
pNew->op = TK_INTEGER;
ExprSetProperty(pNew, EP_IntValue);
}
/* Ensure that the expression now has an implicit collation sequence,
** just as it did when it was a column of a view or sub-query. */
{
CollSeq *pNat = sqlite3ExprCollSeq(pSubst->pParse, pNew);
CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse,
pSubst->pCList->a[iColumn].pExpr
);
if( pNat!=pColl || (pNew->op!=TK_COLUMN && pNew->op!=TK_COLLATE) ){
pNew = sqlite3ExprAddCollateString(pSubst->pParse, pNew,
(pColl ? pColl->zName : "BINARY")
);
}
}
ExprClearProperty(pNew, EP_Collate);
if( ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) ){
sqlite3SetJoinExpr(pNew, pExpr->w.iJoin,
pExpr->flags & (EP_OuterON|EP_InnerON));
}
sqlite3ExprDelete(db, pExpr);
pExpr = pNew;
if( pExpr->op==TK_TRUEFALSE ){
pExpr->u.iValue = sqlite3ExprTruthValue(pExpr);
pExpr->op = TK_INTEGER;
ExprSetProperty(pExpr, EP_IntValue);
}
/* Ensure that the expression now has an implicit collation sequence,
** just as it did when it was a column of a view or sub-query. */
{
CollSeq *pNat = sqlite3ExprCollSeq(pSubst->pParse, pExpr);
CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse,
pSubst->pCList->a[iColumn].pExpr
);
if( pNat!=pColl || (pExpr->op!=TK_COLUMN && pExpr->op!=TK_COLLATE) ){
pExpr = sqlite3ExprAddCollateString(pSubst->pParse, pExpr,
(pColl ? pColl->zName : "BINARY")
);
}
}
ExprClearProperty(pExpr, EP_Collate);
}
}
}else{
@@ -254939,7 +254944,7 @@ static void fts5SourceIdFunc(
){
assert( nArg==0 );
UNUSED_PARAM2(nArg, apUnused);
sqlite3_result_text(pCtx, "fts5: 2024-11-25 12:07:48 b95d11e958643b969c47a8e5857f3793b9e69700b8f1469371386369a26e577e", -1, SQLITE_TRANSIENT);
sqlite3_result_text(pCtx, "fts5: 2024-12-07 20:39:59 2aabe05e2e8cae4847a802ee2daddc1d7413d8fc560254d93ee3e72c14685b6c", -1, SQLITE_TRANSIENT);
}
/*

View File

@@ -147,9 +147,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.47.1"
#define SQLITE_VERSION_NUMBER 3047001
#define SQLITE_SOURCE_ID "2024-11-25 12:07:48 b95d11e958643b969c47a8e5857f3793b9e69700b8f1469371386369a26e577e"
#define SQLITE_VERSION "3.47.2"
#define SQLITE_VERSION_NUMBER 3047002
#define SQLITE_SOURCE_ID "2024-12-07 20:39:59 2aabe05e2e8cae4847a802ee2daddc1d7413d8fc560254d93ee3e72c14685b6c"
/*
** CAPI3REF: Run-Time Library Version Numbers

View File

@@ -2152,7 +2152,6 @@ pub const Expect = struct {
pub fn toThrow(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
defer this.postMatch(globalThis);
const vm = globalThis.bunVM();
const thisValue = callFrame.this();
const arguments = callFrame.argumentsAsArray(1);
@@ -2178,63 +2177,9 @@ pub const Expect = struct {
};
expected_value.ensureStillAlive();
const value: JSValue = try this.getValue(globalThis, thisValue, "toThrow", "<green>expected<r>");
const not = this.flags.not;
var return_value_from_function: JSValue = .zero;
const result_: ?JSValue = brk: {
if (!value.jsType().isFunction()) {
if (this.flags.promise != .none) {
break :brk value;
}
return globalThis.throw("Expected value must be a function", .{});
}
var return_value: JSValue = .zero;
// Drain existing unhandled rejections
vm.global.handleRejectedPromises();
var scope = vm.unhandledRejectionScope();
const prev_unhandled_pending_rejection_to_capture = vm.unhandled_pending_rejection_to_capture;
vm.unhandled_pending_rejection_to_capture = &return_value;
vm.onUnhandledRejection = &VirtualMachine.onQuietUnhandledRejectionHandlerCaptureValue;
return_value_from_function = value.call(globalThis, .undefined, &.{}) catch |err| globalThis.takeException(err);
vm.unhandled_pending_rejection_to_capture = prev_unhandled_pending_rejection_to_capture;
vm.global.handleRejectedPromises();
if (return_value == .zero) {
return_value = return_value_from_function;
}
if (return_value.asAnyPromise()) |promise| {
vm.waitForPromise(promise);
scope.apply(vm);
switch (promise.unwrap(globalThis.vm(), .mark_handled)) {
.fulfilled => {
break :brk null;
},
.rejected => |rejected| {
// since we know for sure it rejected, we should always return the error
break :brk rejected.toError() orelse rejected;
},
.pending => unreachable,
}
}
if (return_value != return_value_from_function) {
if (return_value_from_function.asAnyPromise()) |existing| {
existing.setHandled(globalThis.vm());
}
}
scope.apply(vm);
break :brk return_value.toError() orelse return_value_from_function.toError();
};
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;
@@ -2506,10 +2451,152 @@ pub const Expect = struct {
expected_value.getClassName(globalThis, &expected_class);
return this.throw(globalThis, signature, expected_fmt, .{ expected_class, result.toFmt(&formatter) });
}
pub fn toMatchInlineSnapshot(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
// in jest, a failing inline snapshot does not block the rest from running
// not sure why - empty snapshots will autofill and with the `-u` flag none will fail
fn getValueAsToThrow(this: *Expect, globalThis: *JSGlobalObject, value: JSValue) bun.JSError!struct { ?JSValue, JSValue } {
const vm = globalThis.bunVM();
var return_value_from_function: JSValue = .zero;
if (!value.jsType().isFunction()) {
if (this.flags.promise != .none) {
return .{ value, return_value_from_function };
}
return globalThis.throw("Expected value must be a function", .{});
}
var return_value: JSValue = .zero;
// Drain existing unhandled rejections
vm.global.handleRejectedPromises();
var scope = vm.unhandledRejectionScope();
const prev_unhandled_pending_rejection_to_capture = vm.unhandled_pending_rejection_to_capture;
vm.unhandled_pending_rejection_to_capture = &return_value;
vm.onUnhandledRejection = &VirtualMachine.onQuietUnhandledRejectionHandlerCaptureValue;
return_value_from_function = value.call(globalThis, .undefined, &.{}) catch |err| globalThis.takeException(err);
vm.unhandled_pending_rejection_to_capture = prev_unhandled_pending_rejection_to_capture;
vm.global.handleRejectedPromises();
if (return_value == .zero) {
return_value = return_value_from_function;
}
if (return_value.asAnyPromise()) |promise| {
vm.waitForPromise(promise);
scope.apply(vm);
switch (promise.unwrap(globalThis.vm(), .mark_handled)) {
.fulfilled => {
return .{ null, return_value_from_function };
},
.rejected => |rejected| {
// since we know for sure it rejected, we should always return the error
return .{ rejected.toError() orelse rejected, return_value_from_function };
},
.pending => unreachable,
}
}
if (return_value != return_value_from_function) {
if (return_value_from_function.asAnyPromise()) |existing| {
existing.setHandled(globalThis.vm());
}
}
scope.apply(vm);
return .{ return_value.toError() orelse return_value_from_function.toError(), return_value_from_function };
}
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()) {
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"));
return this.snapshot(globalThis, value, null, hint.slice(), "toThrowErrorMatchingSnapshot");
}
fn fnToErrStringOrUndefined(this: *Expect, globalThis: *JSGlobalObject, value: JSValue) !JSValue {
const err_value, _ = try this.getValueAsToThrow(globalThis, value);
var err_value_res = err_value orelse JSValue.undefined;
if (err_value_res.isAnyError()) {
const message = try err_value_res.getTruthyComptime(globalThis, "message") orelse JSValue.undefined;
err_value_res = message;
} else {
err_value_res = JSValue.undefined;
}
return err_value_res;
}
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;
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"));
return this.inlineSnapshot(globalThis, callFrame, value, null, expected_slice, "toThrowErrorMatchingInlineSnapshot");
}
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);
@@ -2556,48 +2643,43 @@ pub const Expect = struct {
var expected = expected_string.toSlice(default_allocator);
defer expected.deinit();
const value: JSValue = try this.getValue(globalThis, thisValue, "toMatchInlineSnapshot", "<green>properties<r><d>, <r>hint");
const expected_slice: ?[]const u8 = if (has_expected) expected.slice() else null;
if (!value.isObject() and property_matchers != null) {
const signature = comptime getSignature("toMatchInlineSnapshot", "<green>properties<r><d>, <r>hint", false);
return this.throw(globalThis, signature, "\n\n<b>Matcher error: <red>received<r> values must be an object when the matcher has <green>properties<r>\n", .{});
}
if (property_matchers) |_prop_matchers| {
const prop_matchers = _prop_matchers;
if (!value.jestDeepMatch(prop_matchers, globalThis, true)) {
// TODO: print diff with properties from propertyMatchers
const signature = comptime getSignature("toMatchInlineSnapshot", "<green>propertyMatchers<r>", false);
const fmt = signature ++ "\n\nExpected <green>propertyMatchers<r> to match properties from received object" ++
"\n\nReceived: {any}\n";
var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis };
return globalThis.throwPretty(fmt, .{value.toFmt(&formatter)});
}
}
const result: ?[]const u8 = if (has_expected) expected.byteSlice() 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");
}
fn inlineSnapshot(
this: *Expect,
globalThis: *JSGlobalObject,
callFrame: *CallFrame,
value: JSValue,
property_matchers: ?JSValue,
result: ?[]const u8,
comptime fn_name: []const u8,
) bun.JSError!JSValue {
// jest counts inline snapshots towards the snapshot counter for some reason
_ = Jest.runner.?.snapshots.addCount(this, "") catch |e| switch (e) {
error.OutOfMemory => return error.OutOfMemory,
error.NoTest => {},
};
const update = Jest.runner.?.snapshots.update_snapshots;
var needs_write = false;
var pretty_value: MutableString = MutableString.init(default_allocator, 0) catch unreachable;
value.jestSnapshotPrettyFormat(&pretty_value, globalThis) catch {
var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis };
return globalThis.throw("Failed to pretty format value: {s}", .{value.toFmt(&formatter)});
};
var pretty_value: MutableString = try MutableString.init(default_allocator, 0);
defer pretty_value.deinit();
try this.matchAndFmtSnapshot(globalThis, value, property_matchers, &pretty_value, fn_name);
if (result) |saved_value| {
if (strings.eqlLong(pretty_value.slice(), saved_value, true)) {
Jest.runner.?.snapshots.passed += 1;
return .undefined;
} else if (update) {
Jest.runner.?.snapshots.passed += 1;
needs_write = true;
} else {
Jest.runner.?.snapshots.failed += 1;
const signature = comptime getSignature("toMatchInlineSnapshot", "<green>expected<r>", false);
const signature = comptime getSignature(fn_name, "<green>expected<r>", false);
const fmt = signature ++ "\n\n{any}\n";
const diff_format = DiffFormatter{
.received_string = pretty_value.slice(),
@@ -2613,24 +2695,40 @@ pub const Expect = struct {
if (needs_write) {
if (this.testScope() == null) {
const signature = comptime getSignature("toMatchSnapshot", "", true);
const signature = comptime getSignature(fn_name, "", true);
return this.throw(globalThis, signature, "\n\n<b>Matcher error<r>: Snapshot matchers cannot be used outside of a test\n", .{});
}
// 1. find the src loc of the snapshot
const srcloc = callFrame.getCallerSrcLoc(globalThis);
defer srcloc.str.deref();
const describe = this.testScope().?.describe;
const fget = Jest.runner.?.files.get(describe.file_id);
if (srcloc.source_file_id != this.testScope().?.describe.file_id) {
const signature = comptime getSignature("toMatchSnapshot", "", true);
return this.throw(globalThis, signature, "\n\n<b>Matcher error<r>: Inline snapshot matchers must be called from the same file as the test\n", .{});
if (!srcloc.str.eqlUTF8(fget.source.path.text)) {
const signature = comptime getSignature(fn_name, "", true);
return this.throw(globalThis, signature,
\\
\\
\\<b>Matcher error<r>: Inline snapshot matchers must be called from the test file:
\\ Expected to be called from file: <green>"{}"<r>
\\ {s} called from file: <red>"{}"<r>
\\
, .{
std.zig.fmtEscapes(fget.source.path.text),
fn_name,
std.zig.fmtEscapes(srcloc.str.toUTF8(Jest.runner.?.snapshots.allocator).slice()),
});
}
// 2. save to write later
try Jest.runner.?.snapshots.addInlineSnapshotToWrite(srcloc.source_file_id, .{
try Jest.runner.?.snapshots.addInlineSnapshotToWrite(describe.file_id, .{
.line = srcloc.line,
.col = srcloc.column,
.value = pretty_value.toOwnedSlice(),
.has_matchers = property_matchers != null,
.is_added = result == null,
.kind = fn_name,
});
}
@@ -2689,17 +2787,20 @@ pub const Expect = struct {
const value: JSValue = try this.getValue(globalThis, thisValue, "toMatchSnapshot", "<green>properties<r><d>, <r>hint");
if (!value.isObject() and property_matchers != null) {
const signature = comptime getSignature("toMatchSnapshot", "<green>properties<r><d>, <r>hint", false);
return this.throw(globalThis, signature, "\n\n<b>Matcher error: <red>received<r> values must be an object when the matcher has <green>properties<r>\n", .{});
}
return this.snapshot(globalThis, value, property_matchers, hint.slice(), "toMatchSnapshot");
}
fn matchAndFmtSnapshot(this: *Expect, globalThis: *JSGlobalObject, value: JSValue, property_matchers: ?JSValue, pretty_value: *MutableString, comptime fn_name: []const u8) bun.JSError!void {
if (property_matchers) |_prop_matchers| {
if (!value.isObject()) {
const signature = comptime getSignature(fn_name, "<green>properties<r><d>, <r>hint", false);
return this.throw(globalThis, signature, "\n\n<b>Matcher error: <red>received<r> values must be an object when the matcher has <green>properties<r>\n", .{});
}
const prop_matchers = _prop_matchers;
if (!value.jestDeepMatch(prop_matchers, globalThis, true)) {
// TODO: print diff with properties from propertyMatchers
const signature = comptime getSignature("toMatchSnapshot", "<green>propertyMatchers<r>", false);
const signature = comptime getSignature(fn_name, "<green>propertyMatchers<r>", false);
const fmt = signature ++ "\n\nExpected <green>propertyMatchers<r> to match properties from received object" ++
"\n\nReceived: {any}\n";
@@ -2708,14 +2809,17 @@ pub const Expect = struct {
}
}
var pretty_value: MutableString = MutableString.init(default_allocator, 0) catch unreachable;
value.jestSnapshotPrettyFormat(&pretty_value, globalThis) catch {
value.jestSnapshotPrettyFormat(pretty_value, globalThis) catch {
var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis };
return globalThis.throw("Failed to pretty format value: {s}", .{value.toFmt(&formatter)});
};
}
fn snapshot(this: *Expect, globalThis: *JSGlobalObject, value: JSValue, property_matchers: ?JSValue, hint: []const u8, comptime fn_name: []const u8) bun.JSError!JSValue {
var pretty_value: MutableString = try MutableString.init(default_allocator, 0);
defer pretty_value.deinit();
try this.matchAndFmtSnapshot(globalThis, value, property_matchers, &pretty_value, fn_name);
const existing_value = Jest.runner.?.snapshots.getOrPut(this, pretty_value.slice(), hint.slice()) catch |err| {
const existing_value = Jest.runner.?.snapshots.getOrPut(this, pretty_value.slice(), hint) catch |err| {
var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis };
const test_file_path = Jest.runner.?.files.get(this.testScope().?.describe.file_id).source.path.text;
return switch (err) {
@@ -2734,7 +2838,7 @@ pub const Expect = struct {
}
Jest.runner.?.snapshots.failed += 1;
const signature = comptime getSignature("toMatchSnapshot", "<green>expected<r>", false);
const signature = comptime getSignature(fn_name, "<green>expected<r>", false);
const fmt = signature ++ "\n\n{any}\n";
const diff_format = DiffFormatter{
.received_string = pretty_value.slice(),
@@ -4335,8 +4439,6 @@ pub const Expect = struct {
pub const toHaveReturnedWith = notImplementedJSCFn;
pub const toHaveLastReturnedWith = notImplementedJSCFn;
pub const toHaveNthReturnedWith = notImplementedJSCFn;
pub const toThrowErrorMatchingSnapshot = notImplementedJSCFn;
pub const toThrowErrorMatchingInlineSnapshot = notImplementedJSCFn;
pub fn getStaticNot(globalThis: *JSGlobalObject, _: JSValue, _: JSValue) JSValue {
return ExpectStatic.create(globalThis, .{ .not = true });

View File

@@ -37,8 +37,10 @@ pub const Snapshots = struct {
pub const InlineSnapshotToWrite = struct {
line: c_ulong,
col: c_ulong,
value: []const u8,
value: []const u8, // owned by Snapshots.allocator
has_matchers: bool,
is_added: bool,
kind: []const u8, // static lifetime
fn lessThanFn(_: void, a: InlineSnapshotToWrite, b: InlineSnapshotToWrite) bool {
if (a.line < b.line) return true;
@@ -53,6 +55,18 @@ pub const Snapshots = struct {
file: std.fs.File,
};
pub fn addCount(this: *Snapshots, expect: *Expect, hint: []const u8) !struct { []const u8, usize } {
this.total += 1;
const snapshot_name = try expect.getSnapshotName(this.allocator, hint);
const count_entry = try this.counts.getOrPut(snapshot_name);
if (count_entry.found_existing) {
this.allocator.free(snapshot_name);
count_entry.value_ptr.* += 1;
return .{ count_entry.key_ptr.*, count_entry.value_ptr.* };
}
count_entry.value_ptr.* = 1;
return .{ count_entry.key_ptr.*, count_entry.value_ptr.* };
}
pub fn getOrPut(this: *Snapshots, expect: *Expect, target_value: []const u8, hint: string) !?string {
switch (try this.getSnapshotFile(expect.testScope().?.describe.file_id)) {
.result => {},
@@ -65,21 +79,7 @@ pub const Snapshots = struct {
},
}
const snapshot_name = try expect.getSnapshotName(this.allocator, hint);
this.total += 1;
const count_entry = try this.counts.getOrPut(snapshot_name);
const counter = brk: {
if (count_entry.found_existing) {
this.allocator.free(snapshot_name);
count_entry.value_ptr.* += 1;
break :brk count_entry.value_ptr.*;
}
count_entry.value_ptr.* = 1;
break :brk count_entry.value_ptr.*;
};
const name = count_entry.key_ptr.*;
const name, const counter = try this.addCount(expect, hint);
var counter_string_buf = [_]u8{0} ** 32;
const counter_string = try std.fmt.bufPrint(&counter_string_buf, "{d}", .{counter});
@@ -276,7 +276,7 @@ pub const Snapshots = struct {
inline_snapshot_dbg("Finding byte for {}/{}", .{ ils.line, ils.col });
const byte_offset_add = logger.Source.lineColToByteOffset(file_text[last_byte..], last_line, last_col, ils.line, ils.col) orelse {
inline_snapshot_dbg("-> Could not find byte", .{});
try log.addErrorFmt(&source, .{ .start = @intCast(uncommitted_segment_end) }, arena, "Failed to update inline snapshot: Could not find byte for line/column: {d}/{d}", .{ ils.line, ils.col });
try log.addErrorFmt(&source, .{ .start = @intCast(uncommitted_segment_end) }, arena, "Failed to update inline snapshot: Ln {d}, Col {d} not found", .{ ils.line, ils.col });
continue;
};
@@ -296,7 +296,7 @@ pub const Snapshots = struct {
},
else => {},
};
const fn_name = "toMatchInlineSnapshot";
const fn_name = ils.kind;
if (!bun.strings.startsWith(file_text[next_start..], fn_name)) {
try log.addErrorFmt(&source, .{ .start = @intCast(next_start) }, arena, "Failed to update inline snapshot: Could not find 'toMatchInlineSnapshot' here", .{});
continue;
@@ -400,6 +400,8 @@ pub const Snapshots = struct {
try result_text.appendSlice("`");
try bun.js_printer.writePreQuotedString(ils.value, @TypeOf(result_text_writer), result_text_writer, '`', false, false, .utf8);
try result_text.appendSlice("`");
if (ils.is_added) Jest.runner.?.snapshots.added += 1;
}
// commit the last segment

View File

@@ -1603,7 +1603,12 @@ pub fn onClose(
tunnel.detachAndDeref();
}
const in_progress = client.state.stage != .done and client.state.stage != .fail and client.state.flags.is_redirect_pending == false;
if (client.state.flags.is_redirect_pending) {
// if the connection is closed and we are pending redirect just do the redirect
// in this case we will re-connect or go to a different socket if needed
client.doRedirect(is_ssl, if (is_ssl) &http_thread.https_context else &http_thread.http_context, socket);
return;
}
if (in_progress) {
// if the peer closed after a full chunk, treat this
// as if the transfer had complete, browsers appear to ignore
@@ -2888,6 +2893,7 @@ pub fn doRedirect(
ctx: *NewHTTPContext(is_ssl),
socket: NewHTTPContext(is_ssl).HTTPSocket,
) void {
log("doRedirect", .{});
if (this.state.original_request_body == .stream) {
// we cannot follow redirect from a stream right now
// NOTE: we can use .tee(), reset the readable stream and cancel/wait pending write requests before redirecting. node.js just errors here so we just closeAndFail too.
@@ -2932,6 +2938,7 @@ pub fn doRedirect(
return;
}
this.state.reset(this.allocator);
log("doRedirect state reset", .{});
// also reset proxy to redirect
this.flags.proxy_tunneling = false;
if (this.proxy_tunnel) |tunnel| {

View File

@@ -1246,53 +1246,10 @@ ServerResponse.prototype._implicitHeader = function () {
this.writeHead(this.statusCode);
};
function flushFirstWrite(self) {
// headersSent = already flushed the first write
if (self.headersSent) return;
self.headersSent = true;
let firstWrite = self[firstWriteSymbol];
// at this point, the user did not call end and we have not flushed the first write
// we need to flush it now and behave like chunked encoding
self._reply(
new Response(
new ReadableStream({
type: "direct",
pull: async controller => {
self[controllerSymbol] = controller;
if (firstWrite) {
controller.write(firstWrite);
await controller.flush(); // flush the first write
}
firstWrite = undefined;
if (!self[finishedSymbol]) {
const { promise, resolve } = $newPromiseCapability(GlobalPromise);
self[deferredSymbol] = resolve;
return await promise;
}
},
}),
{
headers: self[headersSymbol],
status: self.statusCode,
statusText: self.statusMessage ?? STATUS_CODES[self.statusCode],
},
),
);
}
ServerResponse.prototype._write = function (chunk, encoding, callback) {
if (this[firstWriteSymbol] === undefined && !this.headersSent) {
this[firstWriteSymbol] = chunk;
callback();
const headers = this[headersSymbol];
const hasContentLength = headers && headers.has("Content-Length");
if (hasContentLength) {
// wait for .end()
return;
}
// We still wanna to wait for more writes if the user call 2 consecutives writes
// but if the user delay it too much we need to flush
setTimeout(flushFirstWrite, 1, this);
return;
}
@@ -1306,17 +1263,6 @@ ServerResponse.prototype._writev = function (chunks, callback) {
if (chunks.length === 1 && !this.headersSent && this[firstWriteSymbol] === undefined) {
this[firstWriteSymbol] = chunks[0].chunk;
callback();
const headers = this[headersSymbol];
const hasContentLength = headers && headers.has("Content-Length");
if (hasContentLength) {
// wait for .end()
return;
}
// We still wanna to wait for more writes if the user call 2 consecutives writes
// but if the user delay it too much we need to flush
setTimeout(flushFirstWrite, 1, this);
return;
}
@@ -1324,6 +1270,7 @@ ServerResponse.prototype._writev = function (chunks, callback) {
for (const chunk of chunks) {
controller.write(chunk.chunk);
}
callback();
});
};

View File

@@ -99,6 +99,15 @@ function finishSocket(hasError) {
detachSocket(this);
this.emit("close", hasError);
}
function destroyNT(self, err) {
self.destroy(err);
}
function destroyWhenAborted(err) {
if (!this.destroyed) {
this.destroy(err.target.reason);
}
}
// Provide a better error message when we call end() as a result
// of the other side sending a FIN. The standard 'write after end'
// is overly vague, and makes it seem like the user's code is to blame.
@@ -479,9 +488,12 @@ const Socket = (function (InternalSocket) {
},
};
}
if (signal) {
signal.addEventListener("abort", () => this.destroy());
if (signal.aborted) {
process.nextTick(destroyNT, this, signal.reason);
} else {
signal.addEventListener("abort", destroyWhenAborted.bind(this));
}
}
}

View File

@@ -5371,14 +5371,18 @@ function createNativeStreamReadable(Readable) {
}
if (isClosed) {
nativeReadable.push(null);
ProcessNextTick(() => {
nativeReadable.push(null);
});
}
return remainder.byteLength > 0 ? remainder : undefined;
}
if (isClosed) {
nativeReadable.push(null);
ProcessNextTick(() => {
nativeReadable.push(null);
});
}
return view;
@@ -5390,7 +5394,9 @@ function createNativeStreamReadable(Readable) {
}
if (isClosed) {
nativeReadable.push(null);
ProcessNextTick(() => {
nativeReadable.push(null);
});
}
return view;

View File

@@ -1,11 +0,0 @@
const Api = @import("./api/schema.zig").Api;
const Options = @import("./options.zig");
var options: Options.BundleOptions = undefined;
export fn init() void {
if (!alloc.needs_setup) {
return;
}
}
export fn setOptions(options_ptr: [*c]u8, options_len: c_int) void {}

View File

@@ -605,7 +605,7 @@ it("should run with bun instead of npm even with leading spaces", async () => {
env: bunEnv,
});
expect(stderr.toString()).toBe("$ bun run other_script \n$ echo hi \n");
expect(stderr.toString()).toMatch(/\$ bun(-debug)? run other_script \n\$ echo hi \n/);
expect(stdout.toString()).toEndWith("hi\n");
expect(exitCode).toBe(0);
}

View File

@@ -1,3 +1,7 @@
// Bun Snapshot v1, https://goo.gl/fbAQLP
exports[`expect() toMatchSnapshot to return undefined 1`] = `"abc"`;
exports[`expect() toThrowErrorMatchingSnapshot to return undefined 1`] = `undefined`;
exports[`expect() toThrowErrorMatchingSnapshot to return undefined: undefined 1`] = `undefined`;

View File

@@ -4744,7 +4744,7 @@ describe("expect()", () => {
expect(expect("abc").toMatch("a")).toBeUndefined();
});
test.todo("toMatchInlineSnapshot to return undefined", () => {
expect(expect("abc").toMatchInlineSnapshot()).toBeUndefined();
expect(expect("abc").toMatchInlineSnapshot('"abc"')).toBeUndefined();
});
test("toMatchObject to return undefined", () => {
expect(expect({}).toMatchObject({})).toBeUndefined();
@@ -4768,11 +4768,19 @@ describe("expect()", () => {
}).toThrow(),
).toBeUndefined();
});
test.todo("toThrowErrorMatchingInlineSnapshot to return undefined", () => {
expect(expect(() => {}).toThrowErrorMatchingInlineSnapshot()).toBeUndefined();
test("toThrowErrorMatchingInlineSnapshot to return undefined", () => {
expect(
expect(() => {
throw 0;
}).toThrowErrorMatchingInlineSnapshot("undefined"),
).toBeUndefined();
});
test.todo("toThrowErrorMatchingSnapshot to return undefined", () => {
expect(expect(() => {}).toThrowErrorMatchingSnapshot()).toBeUndefined();
test("toThrowErrorMatchingSnapshot to return undefined", () => {
expect(
expect(() => {
throw 0;
}).toThrowErrorMatchingSnapshot("undefined"),
).toBeUndefined();
});
test(' " " to contain ""', () => {

View File

@@ -587,3 +587,23 @@ exports[`snapshots unicode surrogate halves 1`] = `
exports[\`abc 1\`] = \`"😊abc\\\`\\\${def} <20>, <20> "\`;
"
`;
exports[`error inline snapshots 1`] = `"hello"`;
exports[`error inline snapshots 2`] = `undefined`;
exports[`error inline snapshots 3`] = `undefined`;
exports[`error inline snapshots 4`] = `undefined`;
exports[`error inline snapshots: hint 1`] = `undefined`;
exports[`snapshot numbering 1`] = `"item one"`;
exports[`snapshot numbering 2`] = `"snap"`;
exports[`snapshot numbering 4`] = `"snap"`;
exports[`snapshot numbering 6`] = `"hello"`;
exports[`snapshot numbering: hinted 1`] = `"hello"`;

View File

@@ -483,13 +483,14 @@ class InlineSnapshotTester {
describe("inline snapshots", () => {
const bad = '"bad"';
const helper_js = /*js*/ `
import {expect} from "bun:test";
export function wrongFile(value) {
expect(value).toMatchInlineSnapshot();
}
`;
const tester = new InlineSnapshotTester({
"helper.js": /*js*/ `
import {expect} from "bun:test";
export function wrongFile(value) {
expect(value).toMatchInlineSnapshot();
}
`,
"helper.js": helper_js,
});
test("changing inline snapshot", () => {
tester.test(
@@ -718,7 +719,7 @@ describe("inline snapshots", () => {
tester.testError(
{
update: true,
msg: "Matcher error: Inline snapshot matchers must be called from the same file as the test",
msg: "Inline snapshot matchers must be called from the test file",
},
/*js*/ `
import {wrongFile} from "./helper";
@@ -727,5 +728,91 @@ describe("inline snapshots", () => {
});
`,
);
expect(readFileSync(tester.tmpdir + "/helper.js", "utf-8")).toBe(helper_js);
});
it("is right file", () => {
tester.test(
v => /*js*/ `
import {wrongFile} from "./helper";
test("cases", () => {
expect("rightfile").toMatchInlineSnapshot(${v("", '"9"', '`"rightfile"`')});
expect(wrongFile).toMatchInlineSnapshot(${v("", '"9"', "`[Function: wrongFile]`")});
});
`,
);
});
});
test("error snapshots", () => {
expect(() => {
throw new Error("hello");
}).toThrowErrorMatchingInlineSnapshot(`"hello"`);
expect(() => {
throw 0;
}).toThrowErrorMatchingInlineSnapshot(`undefined`);
expect(() => {
throw { a: "b" };
}).toThrowErrorMatchingInlineSnapshot(`undefined`);
expect(() => {
throw undefined; // this one doesn't work in jest because it doesn't think the function threw
}).toThrowErrorMatchingInlineSnapshot(`undefined`);
});
test("error inline snapshots", () => {
expect(() => {
throw new Error("hello");
}).toThrowErrorMatchingSnapshot();
expect(() => {
throw 0;
}).toThrowErrorMatchingSnapshot();
expect(() => {
throw { a: "b" };
}).toThrowErrorMatchingSnapshot();
expect(() => {
throw undefined;
}).toThrowErrorMatchingSnapshot();
expect(() => {
throw "abcdef";
}).toThrowErrorMatchingSnapshot("hint");
expect(() => {
throw new Error("😊");
}).toThrowErrorMatchingInlineSnapshot(`"😊"`);
});
test("snapshot numbering", () => {
function fails() {
throw new Error("snap");
}
expect("item one").toMatchSnapshot();
expect(fails).toThrowErrorMatchingSnapshot();
expect("1").toMatchInlineSnapshot(`"1"`);
expect(fails).toThrowErrorMatchingSnapshot();
expect(fails).toThrowErrorMatchingInlineSnapshot(`"snap"`);
expect("hello").toMatchSnapshot();
expect("hello").toMatchSnapshot("hinted");
});
test("write snapshot from filter", async () => {
const sver = (m: string, a: boolean) => /*js*/ `
test("mysnap", () => {
expect("${m}").toMatchInlineSnapshot(${a ? '`"' + m + '"`' : ""});
expect(() => {throw new Error("${m}!")}).toThrowErrorMatchingInlineSnapshot(${a ? '`"' + m + '!"`' : ""});
})
`;
const dir = tempDirWithFiles("writesnapshotfromfilter", {
"mytests": {
"snap.test.ts": sver("a", false),
"snap2.test.ts": sver("b", false),
"more": {
"testing.test.ts": sver("TEST", false),
},
},
});
await $`cd ${dir} && ${bunExe()} test mytests`;
expect(await Bun.file(dir + "/mytests/snap.test.ts").text()).toBe(sver("a", true));
expect(await Bun.file(dir + "/mytests/snap2.test.ts").text()).toBe(sver("b", true));
expect(await Bun.file(dir + "/mytests/more/testing.test.ts").text()).toBe(sver("TEST", true));
await $`cd ${dir} && ${bunExe()} test mytests`;
expect(await Bun.file(dir + "/mytests/snap.test.ts").text()).toBe(sver("a", true));
expect(await Bun.file(dir + "/mytests/snap2.test.ts").text()).toBe(sver("b", true));
expect(await Bun.file(dir + "/mytests/more/testing.test.ts").text()).toBe(sver("TEST", true));
});

View File

@@ -2,8 +2,16 @@ import { udpSocket } from "bun";
import { describe, expect, test } from "bun:test";
import { disableAggressiveGCScope, randomPort } from "harness";
import { dataCases, dataTypes } from "./testdata";
import { heapStats } from "bun:jsc";
describe("udpSocket()", () => {
test("connect with invalid hostname rejects", async () => {
expect(async () =>
udpSocket({
connect: { hostname: "example!!!!!.com", port: 443 },
}),
).toThrow();
});
test("can create a socket", async () => {
const socket = await udpSocket({});
expect(socket).toBeInstanceOf(Object);

View File

@@ -2347,22 +2347,6 @@ it("should emit close when connection is aborted", async () => {
}
});
it("must set headersSent to true after headers are sent #3458", async () => {
const server = createServer().listen(0);
try {
await once(server, "listening");
fetch(`http://localhost:${server.address().port}`).then(res => res.text());
const [req, res] = await once(server, "request");
expect(res.headersSent).toBe(false);
const { promise, resolve } = Promise.withResolvers();
res.end("OK", resolve);
await promise;
expect(res.headersSent).toBe(true);
} finally {
server.close();
}
});
it("should emit timeout event", async () => {
const server = http.createServer().listen(0);
try {
@@ -2441,54 +2425,6 @@ it("must set headersSent to true after headers are sent when using chunk encoded
server.close();
}
});
it("response body streaming is immediate (#13696)", async () => {
const totalChunks = 10;
const spacing = 50;
const acceptableDelay = 20;
let totalSize = 0;
let receivedSize = 0;
let server: Server;
try {
server = createServer(async (req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
for (let i = 0; i < totalChunks; i++) {
const payload = `${new Date().getTime().toString()}\n`;
totalSize += payload.length;
res.write(payload);
if (i + 1 < totalChunks) await Bun.sleep(spacing);
}
res.end();
});
const url = await listen(server);
const res = await fetch(url);
const reader = res.body.getReader();
const decoder = new TextDecoder();
let receivedChunks = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
receivedChunks++;
receivedSize += value.byteLength;
// Verify that chunks are not held up longer than necessary
// at the receiver. This is likely to be in the single digits.
//
// #13696: Bun would delay the initial chunks and then send multiple
// chunks before real-time streaming started working.
expect(new Date().getTime() - Number.parseInt(decoder.decode(value).trimEnd(), 10)).toBeLessThan(acceptableDelay);
}
// Verify that the correct number of chunks were sent (in case server
// decides to send no chunks at all).
expect(receivedChunks).toEqual(totalChunks);
// Also verify the total size in case some data was lost.
expect(receivedSize).toEqual(totalSize);
} finally {
server.close();
}
});
it("should work when sending https.request with agent:false", async () => {
const { promise, resolve, reject } = Promise.withResolvers();
@@ -2548,12 +2484,13 @@ it("client should use chunked encoded if more than one write is called", async (
req.on("error", reject);
// Write chunks to the request body
req.write("Hello");
req.write(" ");
await sleep(100);
req.write("World");
req.write(" ");
await sleep(100);
for (let i = 0; i < 4; i++) {
req.write("chunk");
await sleep(50);
req.write(" ");
await sleep(50);
}
req.write("BUN!");
// End the request and signal no more data will be sent
req.end();
@@ -2561,7 +2498,7 @@ it("client should use chunked encoded if more than one write is called", async (
const chunks = await promise;
expect(chunks.length).toBeGreaterThan(1);
expect(chunks[chunks.length - 1]?.toString()).toEndWith("BUN!");
expect(Buffer.concat(chunks).toString()).toBe("Hello World BUN!");
expect(Buffer.concat(chunks).toString()).toBe("chunk ".repeat(4) + "BUN!");
});
it("client should use content-length if only one write is called", async () => {

View File

@@ -563,6 +563,39 @@ it("should not hang after destroy", async () => {
}
});
it("should trigger error when aborted even if connection failed #13126", async () => {
const signal = AbortSignal.timeout(100);
const socket = createConnection({
host: "example.com",
port: 999,
signal: signal,
});
const { promise, resolve, reject } = Promise.withResolvers();
socket.on("connect", reject);
socket.on("error", resolve);
const err = (await promise) as Error;
expect(err.name).toBe("TimeoutError");
});
it("should trigger error when aborted even if connection failed, and the signal is already aborted #13126", async () => {
const signal = AbortSignal.timeout(1);
await Bun.sleep(10);
const socket = createConnection({
host: "example.com",
port: 999,
signal: signal,
});
const { promise, resolve, reject } = Promise.withResolvers();
socket.on("connect", reject);
socket.on("error", resolve);
const err = (await promise) as Error;
expect(err.name).toBe("TimeoutError");
});
it.if(isWindows)(
"should work with named pipes",
async () => {

View File

@@ -544,3 +544,26 @@ it("should emit prefinish on current tick", done => {
done();
});
});
for (const size of [0x10, 0xffff, 0x10000, 0x1f000, 0x20000, 0x20010, 0x7ffff, 0x80000, 0xa0000, 0xa0010]) {
it(`should emit 'readable' with null data and 'close' exactly once each, 0x${size.toString(16)} bytes`, async () => {
const path = `${tmpdir()}/${Date.now()}.readable_and_close.txt`;
writeFileSync(path, new Uint8Array(size));
const stream = createReadStream(path);
const close_resolvers = Promise.withResolvers();
const readable_resolvers = Promise.withResolvers();
stream.on("close", () => {
close_resolvers.resolve();
});
stream.on("readable", () => {
const data = stream.read();
if (data === null) {
readable_resolvers.resolve();
}
});
await Promise.all([close_resolvers.promise, readable_resolvers.promise]);
});
}

View File

@@ -7,6 +7,8 @@ import net from "net";
import { join } from "path";
import { gzipSync } from "zlib";
import { Readable } from "stream";
import { once } from "events";
import type { AddressInfo } from "net";
const tmp_dir = tmpdirSync();
const fixture = readFileSync(join(import.meta.dir, "fetch.js.txt"), "utf8").replaceAll("\r\n", "\n");
@@ -2259,3 +2261,59 @@ describe("fetch should allow duplex", () => {
}).not.toThrow();
});
});
it("should allow to follow redirect if connection is closed, abort should work even if the socket was closed before the redirect", async () => {
for (const type of ["normal", "delay"]) {
await using server = net.createServer(socket => {
let body = "";
socket.on("data", data => {
body += data.toString("utf8");
const headerEndIndex = body.indexOf("\r\n\r\n");
if (headerEndIndex !== -1) {
// headers received
const headers = body.split("\r\n\r\n")[0];
const path = headers.split("\r\n")[0].split(" ")[1];
if (path === "/redirect") {
socket.end(
"HTTP/1.1 308 Permanent Redirect\r\nCache-Control: public, max-age=0, must-revalidate\r\nContent-Type: text/plain\r\nLocation: /\r\nConnection: close\r\n\r\n",
);
} else {
if (type === "delay") {
setTimeout(() => {
if (!socket.destroyed)
socket.end(
"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 9\r\nConnection: close\r\n\r\nHello Bun",
);
}, 200);
} else {
socket.end(
"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 9\r\nConnection: close\r\n\r\nHello Bun",
);
}
}
}
});
});
await once(server.listen(0), "listening");
try {
const response = await fetch(`http://localhost:${(server.address() as AddressInfo).port}/redirect`, {
signal: AbortSignal.timeout(150),
});
if (type === "delay") {
console.error(response, type);
expect.unreachable();
} else {
expect(response.status).toBe(200);
expect(await response.text()).toBe("Hello Bun");
}
} catch (err) {
if (type === "delay") {
expect((err as Error).name).toBe("TimeoutError");
} else {
expect.unreachable();
}
}
}
});

View File

@@ -267,7 +267,7 @@ it("setTimeout if refreshed before run, should reschedule to run later", done =>
let start = Date.now();
let timer = setTimeout(() => {
let end = Date.now();
expect(end - start).toBeGreaterThan(149);
expect(end - start).toBeGreaterThanOrEqual(149);
done();
}, 100);