mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
test: get zig build test working (#18207)
### What does this PR do? Lets us write and run unit tests directly in Zig. Running Zig unit tests in CI is blocked by https://github.com/ziglang/zig/issues/23281. We can un-comment relevant code once this is fixed. #### Workflow > I'll finish writing this up later, but some initial points are below. > Tl;Dr: `bun build:test` Test binaries can be made for any kind of build. They are called `<bun>-test` and live next to their corresponding `bun` bin. For example, debug tests compile to `build/debug/bun-debug-test`. Test binaries re-use most cmake/zig build steps from normal bun binaries, so building one after a normal bun build is pretty fast. ### How did you verify your code works? I tested that my tests run tests.
This commit is contained in:
41
build.zig
41
build.zig
@@ -285,6 +285,40 @@ pub fn build(b: *Build) !void {
|
||||
step.dependOn(addInstallObjectFile(b, bun_obj, "bun-zig", obj_format));
|
||||
}
|
||||
|
||||
// zig build test
|
||||
{
|
||||
var step = b.step("test", "Build Bun's unit test suite");
|
||||
var o = build_options;
|
||||
var unit_tests = b.addTest(.{
|
||||
.name = "bun-test",
|
||||
.optimize = build_options.optimize,
|
||||
.root_source_file = b.path("src/unit_test.zig"),
|
||||
.test_runner = .{ .path = b.path("src/main_test.zig"), .mode = .simple },
|
||||
.target = build_options.target,
|
||||
.use_llvm = !build_options.no_llvm,
|
||||
.use_lld = if (build_options.os == .mac) false else !build_options.no_llvm,
|
||||
.omit_frame_pointer = false,
|
||||
.strip = false,
|
||||
});
|
||||
configureObj(b, &o, unit_tests);
|
||||
// Setting `linker_allow_shlib_undefined` causes the linker to ignore
|
||||
// all undefined symbols. We want this because all we care about is the
|
||||
// object file Zig creates; we perform our own linking later. There is
|
||||
// currently no way to make a test build that only creates an object
|
||||
// file w/o creating an executable.
|
||||
//
|
||||
// See: https://github.com/ziglang/zig/issues/23374
|
||||
unit_tests.linker_allow_shlib_undefined = true;
|
||||
unit_tests.link_function_sections = true;
|
||||
unit_tests.link_data_sections = true;
|
||||
unit_tests.bundle_ubsan_rt = false;
|
||||
|
||||
const bin = unit_tests.getEmittedBin();
|
||||
const obj = bin.dirname().path(b, "bun-test.o");
|
||||
const cpy_obj = b.addInstallFile(obj, "bun-test.o");
|
||||
step.dependOn(&cpy_obj.step);
|
||||
}
|
||||
|
||||
// zig build windows-shim
|
||||
{
|
||||
var step = b.step("windows-shim", "Build the Windows shim (bun_shim_impl.exe + bun_shim_debug.exe)");
|
||||
@@ -456,6 +490,11 @@ pub fn addBunObject(b: *Build, opts: *BunBuildOptions) *Compile {
|
||||
.omit_frame_pointer = false,
|
||||
.strip = false, // stripped at the end
|
||||
});
|
||||
configureObj(b, opts, obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
fn configureObj(b: *Build, opts: *BunBuildOptions, obj: *Compile) void {
|
||||
if (opts.enable_asan) {
|
||||
if (@hasField(Build.Module, "sanitize_address")) {
|
||||
obj.root_module.sanitize_address = true;
|
||||
@@ -495,8 +534,6 @@ pub fn addBunObject(b: *Build, opts: *BunBuildOptions) *Compile {
|
||||
|
||||
const translate_c = getTranslateC(b, opts.target, opts.optimize);
|
||||
obj.root_module.addImport("translated-c-headers", translate_c.createModule());
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
const ObjectFormat = enum {
|
||||
|
||||
@@ -26,6 +26,15 @@ else()
|
||||
setx(DEBUG OFF)
|
||||
endif()
|
||||
|
||||
optionx(BUN_TEST BOOL "Build Bun's unit test suite instead of the normal build" DEFAULT OFF)
|
||||
|
||||
if (BUN_TEST)
|
||||
setx(TEST ON)
|
||||
else()
|
||||
setx(TEST OFF)
|
||||
endif()
|
||||
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES "MinSizeRel")
|
||||
setx(ENABLE_SMOL ON)
|
||||
endif()
|
||||
@@ -62,7 +71,14 @@ if(ARCH STREQUAL "x64")
|
||||
optionx(ENABLE_BASELINE BOOL "If baseline features should be used for older CPUs (e.g. disables AVX, AVX2)" DEFAULT OFF)
|
||||
endif()
|
||||
|
||||
optionx(ENABLE_LOGS BOOL "If debug logs should be enabled" DEFAULT ${DEBUG})
|
||||
# Disabling logs by default for tests yields faster builds
|
||||
if (DEBUG AND NOT TEST)
|
||||
set(DEFAULT_ENABLE_LOGS ON)
|
||||
else()
|
||||
set(DEFAULT_ENABLE_LOGS OFF)
|
||||
endif()
|
||||
|
||||
optionx(ENABLE_LOGS BOOL "If debug logs should be enabled" DEFAULT ${DEFAULT_ENABLE_LOGS})
|
||||
optionx(ENABLE_ASSERTIONS BOOL "If debug assertions should be enabled" DEFAULT ${DEBUG})
|
||||
|
||||
optionx(ENABLE_CANARY BOOL "If canary features should be enabled" DEFAULT ON)
|
||||
|
||||
@@ -29,6 +29,9 @@ else()
|
||||
endif()
|
||||
|
||||
set(ZIG_NAME bootstrap-${ZIG_ARCH}-${ZIG_OS_ABI})
|
||||
if(ZIG_COMPILER_SAFE)
|
||||
set(ZIG_NAME ${ZIG_NAME}-ReleaseSafe)
|
||||
endif()
|
||||
set(ZIG_FILENAME ${ZIG_NAME}.zip)
|
||||
|
||||
if(CMAKE_HOST_WIN32)
|
||||
|
||||
@@ -12,6 +12,10 @@ else()
|
||||
set(bunStrip bun)
|
||||
endif()
|
||||
|
||||
if(TEST)
|
||||
set(bun ${bun}-test)
|
||||
endif()
|
||||
|
||||
set(bunExe ${bun}${CMAKE_EXECUTABLE_SUFFIX})
|
||||
|
||||
if(bunStrip)
|
||||
@@ -528,7 +532,6 @@ file(GLOB_RECURSE BUN_ZIG_SOURCES ${CONFIGURE_DEPENDS}
|
||||
|
||||
list(APPEND BUN_ZIG_SOURCES
|
||||
${CWD}/build.zig
|
||||
${CWD}/src/main.zig
|
||||
${BUN_BINDGEN_ZIG_OUTPUTS}
|
||||
)
|
||||
|
||||
@@ -550,7 +553,13 @@ else()
|
||||
list(APPEND BUN_ZIG_GENERATED_SOURCES ${BUN_BAKE_RUNTIME_OUTPUTS})
|
||||
endif()
|
||||
|
||||
set(BUN_ZIG_OUTPUT ${BUILD_PATH}/bun-zig.o)
|
||||
if (TEST)
|
||||
set(BUN_ZIG_OUTPUT ${BUILD_PATH}/bun-test.o)
|
||||
set(ZIG_STEPS test)
|
||||
else()
|
||||
set(BUN_ZIG_OUTPUT ${BUILD_PATH}/bun-zig.o)
|
||||
set(ZIG_STEPS obj)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm|ARM|arm64|ARM64|aarch64|AARCH64")
|
||||
if(APPLE)
|
||||
@@ -579,10 +588,10 @@ register_command(
|
||||
GROUP
|
||||
console
|
||||
COMMENT
|
||||
"Building src/*.zig for ${ZIG_TARGET}"
|
||||
"Building src/*.zig into ${BUN_ZIG_OUTPUT} for ${ZIG_TARGET}"
|
||||
COMMAND
|
||||
${ZIG_EXECUTABLE}
|
||||
build obj
|
||||
build ${ZIG_STEPS}
|
||||
${CMAKE_ZIG_FLAGS}
|
||||
--prefix ${BUILD_PATH}
|
||||
-Dobj_format=${ZIG_OBJECT_FORMAT}
|
||||
@@ -596,6 +605,7 @@ register_command(
|
||||
-Dcodegen_path=${CODEGEN_PATH}
|
||||
-Dcodegen_embed=$<IF:$<BOOL:${CODEGEN_EMBED}>,true,false>
|
||||
--prominent-compile-errors
|
||||
--summary all
|
||||
${ZIG_FLAGS_BUN}
|
||||
ARTIFACTS
|
||||
${BUN_ZIG_OUTPUT}
|
||||
|
||||
@@ -50,6 +50,7 @@ optionx(ZIG_OBJECT_FORMAT "obj|bc" "Output file format for Zig object files" DEF
|
||||
|
||||
optionx(ZIG_LOCAL_CACHE_DIR FILEPATH "The path to local the zig cache directory" DEFAULT ${CACHE_PATH}/zig/local)
|
||||
optionx(ZIG_GLOBAL_CACHE_DIR FILEPATH "The path to the global zig cache directory" DEFAULT ${CACHE_PATH}/zig/global)
|
||||
optionx(ZIG_COMPILER_SAFE BOOL "Download a ReleaseSafe build of the Zig compiler. Only availble on macos aarch64." DEFAULT OFF)
|
||||
|
||||
setenv(ZIG_LOCAL_CACHE_DIR ${ZIG_LOCAL_CACHE_DIR})
|
||||
setenv(ZIG_GLOBAL_CACHE_DIR ${ZIG_GLOBAL_CACHE_DIR})
|
||||
@@ -78,6 +79,7 @@ register_command(
|
||||
-DZIG_PATH=${ZIG_PATH}
|
||||
-DZIG_COMMIT=${ZIG_COMMIT}
|
||||
-DENABLE_ASAN=${ENABLE_ASAN}
|
||||
-DZIG_COMPILER_SAFE=${ZIG_COMPILER_SAFE}
|
||||
-P ${CWD}/cmake/scripts/DownloadZig.cmake
|
||||
SOURCES
|
||||
${CWD}/cmake/scripts/DownloadZig.cmake
|
||||
|
||||
@@ -57,6 +57,9 @@
|
||||
"test:release": "node scripts/runner.node.mjs --exec-path ./build/release/bun",
|
||||
"banned": "bun test test/internal/ban-words.test.ts",
|
||||
"zig": "vendor/zig/zig.exe",
|
||||
"zig:test": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Debug -DBUN_TEST=ON -B build/debug",
|
||||
"zig:test:release": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Release -DBUNTEST=ON -B build/release",
|
||||
"zig:test:ci": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Release -DBUN_TEST=ON -DZIG_OPTIMIZE=ReleaseSafe -DCMAKE_VERBOSE_MAKEFILE=ON -DCI=true -B build/release-ci --verbose --fresh",
|
||||
"zig:fmt": "bun run zig-format",
|
||||
"zig:check": "bun run zig build check --summary new",
|
||||
"zig:check-all": "bun run zig build check-all --summary new",
|
||||
@@ -76,6 +79,7 @@
|
||||
"prettier:check": "bun run analysis:no-llvm --target prettier-check",
|
||||
"prettier:extra": "bun run analysis:no-llvm --target prettier-extra",
|
||||
"prettier:diff": "bun run analysis:no-llvm --target prettier-diff",
|
||||
"node:test": "node ./scripts/runner.node.mjs --quiet --exec-path=$npm_execpath --node-tests "
|
||||
"node:test": "node ./scripts/runner.node.mjs --quiet --exec-path=$npm_execpath --node-tests ",
|
||||
"clean:zig": "rm -rf build/debug/cache/zig build/debug/CMakeCache.txt 'build/debug/*.o' .zig-cache zig-out || true"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,7 +626,7 @@ fn BakeRegisterProductionChunk(global: *JSC.JSGlobalObject, key: bun.String, sou
|
||||
return result;
|
||||
}
|
||||
|
||||
export fn BakeProdResolve(global: *JSC.JSGlobalObject, a_str: bun.String, specifier_str: bun.String) callconv(.C) bun.String {
|
||||
pub export fn BakeProdResolve(global: *JSC.JSGlobalObject, a_str: bun.String, specifier_str: bun.String) callconv(.C) bun.String {
|
||||
var sfa = std.heap.stackFallback(@sizeOf(bun.PathBuffer) * 2, bun.default_allocator);
|
||||
const alloc = sfa.get();
|
||||
|
||||
@@ -836,7 +836,7 @@ pub const PerThread = struct {
|
||||
};
|
||||
|
||||
/// Given a key, returns the source code to load.
|
||||
export fn BakeProdLoad(pt: *PerThread, key: bun.String) bun.String {
|
||||
pub export fn BakeProdLoad(pt: *PerThread, key: bun.String) bun.String {
|
||||
var sfa = std.heap.stackFallback(4096, bun.default_allocator);
|
||||
const allocator = sfa.get();
|
||||
const utf8 = key.toUTF8(allocator);
|
||||
@@ -866,3 +866,8 @@ const OpaqueFileId = FrameworkRouter.OpaqueFileId;
|
||||
const JSC = bun.JSC;
|
||||
const JSValue = JSC.JSValue;
|
||||
const VirtualMachine = JSC.VirtualMachine;
|
||||
|
||||
fn @"export"() void {
|
||||
_ = BakeProdResolve;
|
||||
_ = BakeProdLoad;
|
||||
}
|
||||
|
||||
@@ -776,10 +776,6 @@ pub const RefString = struct {
|
||||
}
|
||||
};
|
||||
|
||||
comptime {
|
||||
std.testing.refAllDecls(RefString);
|
||||
}
|
||||
|
||||
pub export fn MarkedArrayBuffer_deallocator(bytes_: *anyopaque, _: *anyopaque) void {
|
||||
const mimalloc = @import("../allocators/mimalloc.zig");
|
||||
// zig's memory allocator interface won't work here
|
||||
|
||||
@@ -11,7 +11,6 @@ const mem = std.mem;
|
||||
const Allocator = mem.Allocator;
|
||||
const stackFallback = std.heap.stackFallback;
|
||||
const assert = std.debug.assert;
|
||||
const print = std.debug.print;
|
||||
|
||||
/// Comptime diff configuration. Defaults are usually sufficient.
|
||||
pub const Options = struct {
|
||||
@@ -487,8 +486,8 @@ const StrDiffer = Differ([]const u8, .{ .check_comma_disparity = true });
|
||||
test StrDiffer {
|
||||
const a = t.allocator;
|
||||
inline for (.{
|
||||
// .{ "foo", "foo" },
|
||||
// .{ "foo", "bar" },
|
||||
.{ "foo", "foo" },
|
||||
.{ "foo", "bar" },
|
||||
.{
|
||||
// actual
|
||||
\\[
|
||||
@@ -512,85 +511,85 @@ test StrDiffer {
|
||||
\\ 7
|
||||
\\]
|
||||
},
|
||||
// // remove line
|
||||
// .{
|
||||
// \\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||||
// \\incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
|
||||
// \\nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
// \\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
||||
// \\fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
|
||||
// \\culpa qui officia deserunt mollit anim id est laborum.
|
||||
// ,
|
||||
// \\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||||
// \\incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
|
||||
// \\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
||||
// \\fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
|
||||
// \\culpa qui officia deserunt mollit anim id est laborum.
|
||||
// ,
|
||||
// },
|
||||
// // add some line
|
||||
// .{
|
||||
// \\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||||
// \\incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
|
||||
// \\nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
// \\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
||||
// \\fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
|
||||
// \\culpa qui officia deserunt mollit anim id est laborum.
|
||||
// ,
|
||||
// \\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||||
// \\incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
|
||||
// \\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||||
// \\nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
// \\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
||||
// \\fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
|
||||
// \\culpa qui officia deserunt mollit anim id est laborum.
|
||||
// \\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
||||
// ,
|
||||
// },
|
||||
// // modify lines
|
||||
// .{
|
||||
// \\foo
|
||||
// \\bar
|
||||
// \\baz
|
||||
// ,
|
||||
// \\foo
|
||||
// \\barrr
|
||||
// \\baz
|
||||
// },
|
||||
// .{
|
||||
// \\foooo
|
||||
// \\bar
|
||||
// \\baz
|
||||
// ,
|
||||
// \\foo
|
||||
// \\bar
|
||||
// \\baz
|
||||
// },
|
||||
// .{
|
||||
// \\foo
|
||||
// \\bar
|
||||
// \\baz
|
||||
// ,
|
||||
// \\foo
|
||||
// \\bar
|
||||
// \\baz
|
||||
// },
|
||||
// .{
|
||||
// \\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||||
// \\incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
|
||||
// \\nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
// \\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
||||
// \\fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
|
||||
// \\culpa qui officia deserunt mollit anim id est laborum.
|
||||
// ,
|
||||
// \\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor modified
|
||||
// \\incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
|
||||
// \\nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
// \\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
||||
// \\fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in also modified
|
||||
// \\culpa qui officia deserunt mollit anim id est laborum.
|
||||
// ,
|
||||
// },
|
||||
// remove line
|
||||
.{
|
||||
\\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||||
\\incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
|
||||
\\nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
\\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
||||
\\fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
|
||||
\\culpa qui officia deserunt mollit anim id est laborum.
|
||||
,
|
||||
\\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||||
\\incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
|
||||
\\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
||||
\\fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
|
||||
\\culpa qui officia deserunt mollit anim id est laborum.
|
||||
,
|
||||
},
|
||||
// add some line
|
||||
.{
|
||||
\\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||||
\\incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
|
||||
\\nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
\\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
||||
\\fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
|
||||
\\culpa qui officia deserunt mollit anim id est laborum.
|
||||
,
|
||||
\\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||||
\\incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
|
||||
\\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||||
\\nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
\\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
||||
\\fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
|
||||
\\culpa qui officia deserunt mollit anim id est laborum.
|
||||
\\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
||||
,
|
||||
},
|
||||
// modify lines
|
||||
.{
|
||||
\\foo
|
||||
\\bar
|
||||
\\baz
|
||||
,
|
||||
\\foo
|
||||
\\barrr
|
||||
\\baz
|
||||
},
|
||||
.{
|
||||
\\foooo
|
||||
\\bar
|
||||
\\baz
|
||||
,
|
||||
\\foo
|
||||
\\bar
|
||||
\\baz
|
||||
},
|
||||
.{
|
||||
\\foo
|
||||
\\bar
|
||||
\\baz
|
||||
,
|
||||
\\foo
|
||||
\\bar
|
||||
\\baz
|
||||
},
|
||||
.{
|
||||
\\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||||
\\incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
|
||||
\\nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
\\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
||||
\\fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
|
||||
\\culpa qui officia deserunt mollit anim id est laborum.
|
||||
,
|
||||
\\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor modified
|
||||
\\incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
|
||||
\\nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
\\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
||||
\\fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in also modified
|
||||
\\culpa qui officia deserunt mollit anim id est laborum.
|
||||
,
|
||||
},
|
||||
}) |thing| {
|
||||
var actual = try split(u8, a, thing[0]);
|
||||
var expected = try split(u8, a, thing[1]);
|
||||
@@ -600,9 +599,6 @@ test StrDiffer {
|
||||
}
|
||||
var d = try StrDiffer.diff(a, actual.items, expected.items);
|
||||
defer d.deinit();
|
||||
for (d.items) |diff| {
|
||||
std.debug.print("{}\n", .{diff});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1905,6 +1905,7 @@ pub const Process = struct {
|
||||
|
||||
// TODO: switch this to using *bun.wtf.String when it is added
|
||||
pub fn Bun__Process__editWindowsEnvVar(k: bun.String, v: bun.String) callconv(.C) void {
|
||||
comptime bun.assert(bun.Environment.isWindows);
|
||||
if (k.tag == .Empty) return;
|
||||
const wtf1 = k.value.WTFStringImpl;
|
||||
var fixed_stack_allocator = std.heap.stackFallback(1025, bun.default_allocator);
|
||||
@@ -1988,10 +1989,6 @@ pub const PathOrBlob = union(enum) {
|
||||
}
|
||||
};
|
||||
|
||||
comptime {
|
||||
std.testing.refAllDecls(Process);
|
||||
}
|
||||
|
||||
pub const uid_t = if (Environment.isPosix) std.posix.uid_t else bun.windows.libuv.uv_uid_t;
|
||||
pub const gid_t = if (Environment.isPosix) std.posix.gid_t else bun.windows.libuv.uv_gid_t;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
const std = @import("std");
|
||||
const bun = @import("root").bun;
|
||||
const testing = std.testing;
|
||||
const String = if (@import("builtin").is_test) TestString else bun.String;
|
||||
const JSValue = if (@import("builtin").is_test) usize else bun.JSC.JSValue;
|
||||
const String = bun.String;
|
||||
const JSValue = bun.JSC.JSValue;
|
||||
|
||||
pub const OptionValueType = enum { boolean, string };
|
||||
|
||||
@@ -74,10 +74,12 @@ pub fn isOptionLikeValue(value: String) bool {
|
||||
/// Find the long option associated with a short option. Looks for a configured
|
||||
/// `short` and returns the short option itself if a long option is not found.
|
||||
/// Example:
|
||||
/// ```zig
|
||||
/// findOptionByShortName('a', {}) // returns 'a'
|
||||
/// findOptionByShortName('b', {
|
||||
/// options: { bar: { short: 'b' } }
|
||||
/// }) // returns "bar"
|
||||
/// ```
|
||||
pub fn findOptionByShortName(short_name: String, options: []const OptionDefinition) ?usize {
|
||||
var long_option_index: ?usize = null;
|
||||
for (options, 0..) |option, i| {
|
||||
@@ -90,366 +92,3 @@ pub fn findOptionByShortName(short_name: String, options: []const OptionDefiniti
|
||||
}
|
||||
return long_option_index;
|
||||
}
|
||||
|
||||
//
|
||||
// TESTS
|
||||
//
|
||||
|
||||
var no_options: []const OptionDefinition = &[_]OptionDefinition{};
|
||||
|
||||
/// Used only for tests, as lightweight substitute for bun.String
|
||||
const TestString = struct {
|
||||
str: []const u8,
|
||||
fn length(this: TestString) usize {
|
||||
return this.str.len;
|
||||
}
|
||||
fn hasPrefixComptime(this: TestString, comptime prefix: []const u8) bool {
|
||||
return std.mem.startsWith(u8, this.str, prefix);
|
||||
}
|
||||
fn charAtU8(this: TestString, i: usize) u8 {
|
||||
return this.str[i];
|
||||
}
|
||||
fn indexOfCharU8(this: TestString, chr: u8) ?usize {
|
||||
return std.mem.indexOfScalar(u8, this.str, chr);
|
||||
}
|
||||
};
|
||||
fn s(str: []const u8) TestString {
|
||||
return TestString{ .str = str };
|
||||
}
|
||||
|
||||
//
|
||||
// misc
|
||||
//
|
||||
|
||||
test "classifyToken: is option terminator" {
|
||||
try testing.expectEqual(classifyToken(s("--"), no_options), .option_terminator);
|
||||
}
|
||||
|
||||
test "classifyToken: is positional" {
|
||||
try testing.expectEqual(classifyToken(s("abc"), no_options), .positional);
|
||||
}
|
||||
|
||||
//
|
||||
// isLoneLongOption
|
||||
//
|
||||
|
||||
pub fn isLoneLongOption(value: String) bool {
|
||||
return classifyToken(value, no_options) == .lone_long_option;
|
||||
}
|
||||
|
||||
test "isLoneLongOption: when passed short option then returns false" {
|
||||
try testing.expectEqual(isLoneLongOption(s("-s")), false);
|
||||
}
|
||||
|
||||
test "isLoneLongOption: when passed short option group then returns false" {
|
||||
try testing.expectEqual(isLoneLongOption(s("-abc")), false);
|
||||
}
|
||||
|
||||
test "isLoneLongOption: when passed lone long option then returns true" {
|
||||
try testing.expectEqual(isLoneLongOption(s("--foo")), true);
|
||||
}
|
||||
|
||||
test "isLoneLongOption: when passed single character long option then returns true" {
|
||||
try testing.expectEqual(isLoneLongOption(s("--f")), true);
|
||||
}
|
||||
|
||||
test "isLoneLongOption: when passed long option and value then returns false" {
|
||||
try testing.expectEqual(isLoneLongOption(s("--foo=bar")), false);
|
||||
}
|
||||
|
||||
test "isLoneLongOption: when passed empty string then returns false" {
|
||||
try testing.expectEqual(isLoneLongOption(s("")), false);
|
||||
}
|
||||
|
||||
test "isLoneLongOption: when passed plain text then returns false" {
|
||||
try testing.expectEqual(isLoneLongOption(s("foo")), false);
|
||||
}
|
||||
|
||||
test "isLoneLongOption: when passed single dash then returns false" {
|
||||
try testing.expectEqual(isLoneLongOption(s("-")), false);
|
||||
}
|
||||
|
||||
test "isLoneLongOption: when passed double dash then returns false" {
|
||||
try testing.expectEqual(isLoneLongOption(s("--")), false);
|
||||
}
|
||||
|
||||
// This is a bit bogus, but simple consistent behaviour: long option follows double dash.
|
||||
test "isLoneLongOption: when passed arg starting with triple dash then returns true" {
|
||||
try testing.expectEqual(isLoneLongOption(s("---foo")), true);
|
||||
}
|
||||
|
||||
// This is a bit bogus, but simple consistent behaviour: long option follows double dash.
|
||||
test "isLoneLongOption: when passed '--=' then returns true" {
|
||||
try testing.expectEqual(isLoneLongOption(s("--=")), true);
|
||||
}
|
||||
|
||||
//
|
||||
// isLoneShortOption
|
||||
//
|
||||
|
||||
pub fn isLoneShortOption(value: String) bool {
|
||||
return classifyToken(value, no_options) == .lone_short_option;
|
||||
}
|
||||
|
||||
test "isLoneShortOption: when passed short option then returns true" {
|
||||
try testing.expectEqual(isLoneShortOption(s("-s")), true);
|
||||
}
|
||||
|
||||
test "isLoneShortOption: when passed short option group (or might be short and value) then returns false" {
|
||||
try testing.expectEqual(isLoneShortOption(s("-abc")), false);
|
||||
}
|
||||
|
||||
test "isLoneShortOption: when passed long option then returns false" {
|
||||
try testing.expectEqual(isLoneShortOption(s("--foo")), false);
|
||||
}
|
||||
|
||||
test "isLoneShortOption: when passed long option with value then returns false" {
|
||||
try testing.expectEqual(isLoneShortOption(s("--foo=bar")), false);
|
||||
}
|
||||
|
||||
test "isLoneShortOption: when passed empty string then returns false" {
|
||||
try testing.expectEqual(isLoneShortOption(s("")), false);
|
||||
}
|
||||
|
||||
test "isLoneShortOption: when passed plain text then returns false" {
|
||||
try testing.expectEqual(isLoneShortOption(s("foo")), false);
|
||||
}
|
||||
|
||||
test "isLoneShortOption: when passed single dash then returns false" {
|
||||
try testing.expectEqual(isLoneShortOption(s("-")), false);
|
||||
}
|
||||
|
||||
test "isLoneShortOption: when passed double dash then returns false" {
|
||||
try testing.expectEqual(isLoneShortOption(s("--")), false);
|
||||
}
|
||||
|
||||
//
|
||||
// isLongOptionAndValue
|
||||
//
|
||||
|
||||
pub fn isLongOptionAndValue(value: String) bool {
|
||||
return classifyToken(value, no_options) == .long_option_and_value;
|
||||
}
|
||||
|
||||
test "isLongOptionAndValue: when passed short option then returns false" {
|
||||
try testing.expectEqual(isLongOptionAndValue(s("-s")), false);
|
||||
}
|
||||
|
||||
test "isLongOptionAndValue: when passed short option group then returns false" {
|
||||
try testing.expectEqual(isLongOptionAndValue(s("-abc")), false);
|
||||
}
|
||||
|
||||
test "isLongOptionAndValue: when passed lone long option then returns false" {
|
||||
try testing.expectEqual(isLongOptionAndValue(s("--foo")), false);
|
||||
}
|
||||
|
||||
test "isLongOptionAndValue: when passed long option and value then returns true" {
|
||||
try testing.expectEqual(isLongOptionAndValue(s("--foo=bar")), true);
|
||||
}
|
||||
|
||||
test "isLongOptionAndValue: when passed single character long option and value then returns true" {
|
||||
try testing.expectEqual(isLongOptionAndValue(s("--f=bar")), true);
|
||||
}
|
||||
|
||||
test "isLongOptionAndValue: when passed empty string then returns false" {
|
||||
try testing.expectEqual(isLongOptionAndValue(s("")), false);
|
||||
}
|
||||
|
||||
test "isLongOptionAndValue: when passed plain text then returns false" {
|
||||
try testing.expectEqual(isLongOptionAndValue(s("foo")), false);
|
||||
}
|
||||
|
||||
test "isLongOptionAndValue: when passed single dash then returns false" {
|
||||
try testing.expectEqual(isLongOptionAndValue(s("-")), false);
|
||||
}
|
||||
|
||||
test "isLongOptionAndValue: when passed double dash then returns false" {
|
||||
try testing.expectEqual(isLongOptionAndValue(s("--")), false);
|
||||
}
|
||||
|
||||
// This is a bit bogus, but simple consistent behaviour: long option follows double dash.
|
||||
test "isLongOptionAndValue: when passed arg starting with triple dash and value then returns true" {
|
||||
try testing.expectEqual(isLongOptionAndValue(s("---foo=bar")), true);
|
||||
}
|
||||
|
||||
// This is a bit bogus, but simple consistent behaviour: long option follows double dash.
|
||||
test "isLongOptionAndValue: when passed '--=' then returns false" {
|
||||
try testing.expectEqual(isLongOptionAndValue(s("--=")), false);
|
||||
}
|
||||
|
||||
//
|
||||
// isOptionLikeValue
|
||||
//
|
||||
// Basically rejecting values starting with a dash, but run through the interesting possibilities.
|
||||
|
||||
test "isOptionLikeValue: when passed plain text then returns false" {
|
||||
try testing.expectEqual(isOptionLikeValue(s("abc")), false);
|
||||
}
|
||||
|
||||
//test "isOptionLikeValue: when passed digits then returns false" {
|
||||
// try testing.expectEqual(isOptionLikeValue(123), false);
|
||||
//}
|
||||
|
||||
test "isOptionLikeValue: when passed empty string then returns false" {
|
||||
try testing.expectEqual(isOptionLikeValue(s("")), false);
|
||||
}
|
||||
|
||||
// Special case, used as stdin/stdout et al and not reason to reject
|
||||
test "isOptionLikeValue: when passed dash then returns false" {
|
||||
try testing.expectEqual(isOptionLikeValue(s("-")), false);
|
||||
}
|
||||
|
||||
test "isOptionLikeValue: when passed -- then returns true" {
|
||||
// Not strictly option-like, but is supect
|
||||
try testing.expectEqual(isOptionLikeValue(s("--")), true);
|
||||
}
|
||||
|
||||
// Supporting undefined so can pass element off end of array without checking
|
||||
//test "isOptionLikeValue: when passed undefined then returns false" {
|
||||
// try testing.expectEqual(isOptionLikeValue(undefined), false);
|
||||
//}
|
||||
|
||||
test "isOptionLikeValue: when passed short option then returns true" {
|
||||
try testing.expectEqual(isOptionLikeValue(s("-a")), true);
|
||||
}
|
||||
|
||||
test "isOptionLikeValue: when passed short option digit then returns true" {
|
||||
try testing.expectEqual(isOptionLikeValue(s("-1")), true);
|
||||
}
|
||||
|
||||
test "isOptionLikeValue: when passed negative number then returns true" {
|
||||
try testing.expectEqual(isOptionLikeValue(s("-123")), true);
|
||||
}
|
||||
|
||||
test "isOptionLikeValue: when passed short option group of short option with value then returns true" {
|
||||
try testing.expectEqual(isOptionLikeValue(s("-abd")), true);
|
||||
}
|
||||
|
||||
test "isOptionLikeValue: when passed long option then returns true" {
|
||||
try testing.expectEqual(isOptionLikeValue(s("--foo")), true);
|
||||
}
|
||||
|
||||
test "isOptionLikeValue: when passed long option with value then returns true" {
|
||||
try testing.expectEqual(isOptionLikeValue(s("--foo=bar")), true);
|
||||
}
|
||||
|
||||
//
|
||||
// isShortOptionAndValue
|
||||
//
|
||||
|
||||
pub fn isShortOptionAndValue(value: String, options: []const OptionDefinition) bool {
|
||||
return classifyToken(value, options) == .short_option_and_value;
|
||||
}
|
||||
|
||||
test "isShortOptionAndValue: when passed lone short option then returns false" {
|
||||
try testing.expectEqual(isShortOptionAndValue(s("-s"), no_options), false);
|
||||
}
|
||||
|
||||
test "isShortOptionAndValue: when passed group with leading zero-config boolean then returns false" {
|
||||
try testing.expectEqual(isShortOptionAndValue(s("-ab"), no_options), false);
|
||||
}
|
||||
|
||||
test "isShortOptionAndValue: when passed group with leading configured implicit boolean then returns false" {
|
||||
const options = &[_]OptionDefinition{.{ .long_name = s("aaa"), .short_name = 'a' }};
|
||||
try testing.expectEqual(isShortOptionAndValue(s("-ab"), options), false);
|
||||
}
|
||||
|
||||
test "isShortOptionAndValue: when passed group with leading configured explicit boolean then returns false" {
|
||||
const options = &[_]OptionDefinition{.{ .long_name = s("aaa"), .short_name = 'a', .type = .boolean }};
|
||||
try testing.expectEqual(isShortOptionAndValue(s("-ab"), options), false);
|
||||
}
|
||||
|
||||
test "isShortOptionAndValue: when passed group with leading configured string then returns true" {
|
||||
const options = &[_]OptionDefinition{.{ .long_name = s("aaa"), .short_name = 'a', .type = .string }};
|
||||
try testing.expectEqual(isShortOptionAndValue(s("-ab"), options), true);
|
||||
}
|
||||
|
||||
test "isShortOptionAndValue: when passed long option then returns false" {
|
||||
try testing.expectEqual(isShortOptionAndValue(s("--foo"), no_options), false);
|
||||
}
|
||||
|
||||
test "isShortOptionAndValue: when passed long option with value then returns false" {
|
||||
try testing.expectEqual(isShortOptionAndValue(s("--foo=bar"), no_options), false);
|
||||
}
|
||||
|
||||
test "isShortOptionAndValue: when passed empty string then returns false" {
|
||||
try testing.expectEqual(isShortOptionAndValue(s(""), no_options), false);
|
||||
}
|
||||
|
||||
test "isShortOptionAndValue: when passed plain text then returns false" {
|
||||
try testing.expectEqual(isShortOptionAndValue(s("foo"), no_options), false);
|
||||
}
|
||||
|
||||
test "isShortOptionAndValue: when passed single dash then returns false" {
|
||||
try testing.expectEqual(isShortOptionAndValue(s("-"), no_options), false);
|
||||
}
|
||||
|
||||
test "isShortOptionAndValue: when passed double dash then returns false" {
|
||||
try testing.expectEqual(isShortOptionAndValue(s("--"), no_options), false);
|
||||
}
|
||||
|
||||
//
|
||||
// isShortOptionGroup
|
||||
//
|
||||
|
||||
pub fn isShortOptionGroup(value: String, options: []const OptionDefinition) bool {
|
||||
return classifyToken(value, options) == .short_option_group;
|
||||
}
|
||||
|
||||
test "isShortOptionGroup: when passed lone short option then returns false" {
|
||||
try testing.expectEqual(isShortOptionGroup(s("-s"), no_options), false);
|
||||
}
|
||||
|
||||
test "isShortOptionGroup: when passed group with leading zero-config boolean then returns true" {
|
||||
try testing.expectEqual(isShortOptionGroup(s("-ab"), no_options), true);
|
||||
}
|
||||
|
||||
test "isShortOptionGroup: when passed group with leading configured implicit boolean then returns true" {
|
||||
const options = &[_]OptionDefinition{.{ .long_name = s("aaa"), .short_name = 'a' }};
|
||||
try testing.expectEqual(isShortOptionGroup(s("-ab"), options), true);
|
||||
}
|
||||
|
||||
test "isShortOptionGroup: when passed group with leading configured explicit boolean then returns true" {
|
||||
const options = &[_]OptionDefinition{.{ .long_name = s("aaa"), .short_name = 'a', .type = .boolean }};
|
||||
try testing.expectEqual(isShortOptionGroup(s("-ab"), options), true);
|
||||
}
|
||||
|
||||
test "isShortOptionGroup: when passed group with leading configured string then returns false" {
|
||||
const options = &[_]OptionDefinition{.{ .long_name = s("aaa"), .short_name = 'a', .type = .string }};
|
||||
try testing.expectEqual(isShortOptionGroup(s("-ab"), options), false);
|
||||
}
|
||||
|
||||
test "isShortOptionGroup: when passed group with trailing configured string then returns true" {
|
||||
const options = &[_]OptionDefinition{.{ .long_name = s("bbb"), .short_name = 'b', .type = .string }};
|
||||
try testing.expectEqual(isShortOptionGroup(s("-ab"), options), true);
|
||||
}
|
||||
|
||||
// This one is dubious, but leave it to caller to handle.
|
||||
test "isShortOptionGroup: when passed group with middle configured string then returns true" {
|
||||
const options = &[_]OptionDefinition{.{ .long_name = s("bbb"), .short_name = 'b', .type = .string }};
|
||||
try testing.expectEqual(isShortOptionGroup(s("-abc"), options), true);
|
||||
}
|
||||
|
||||
test "isShortOptionGroup: when passed long option then returns false" {
|
||||
try testing.expectEqual(isShortOptionGroup(s("--foo"), no_options), false);
|
||||
}
|
||||
|
||||
test "isShortOptionGroup: when passed long option with value then returns false" {
|
||||
try testing.expectEqual(isShortOptionGroup(s("--foo=bar"), no_options), false);
|
||||
}
|
||||
|
||||
test "isShortOptionGroup: when passed empty string then returns false" {
|
||||
try testing.expectEqual(isShortOptionGroup(s(""), no_options), false);
|
||||
}
|
||||
|
||||
test "isShortOptionGroup: when passed plain text then returns false" {
|
||||
try testing.expectEqual(isShortOptionGroup(s("foo"), no_options), false);
|
||||
}
|
||||
|
||||
test "isShortOptionGroup: when passed single dash then returns false" {
|
||||
try testing.expectEqual(isShortOptionGroup(s("-"), no_options), false);
|
||||
}
|
||||
|
||||
test "isShortOptionGroup: when passed double dash then returns false" {
|
||||
try testing.expectEqual(isShortOptionGroup(s("--"), no_options), false);
|
||||
}
|
||||
|
||||
@@ -1801,3 +1801,7 @@ fn handleTopLevelTestErrorBeforeJavaScriptStart(err: anyerror) noreturn {
|
||||
}
|
||||
Global.exit(1);
|
||||
}
|
||||
|
||||
pub fn @"export"() void {
|
||||
_ = &Scanner.BunTest__shouldGenerateCodeCoverage;
|
||||
}
|
||||
|
||||
@@ -113,53 +113,9 @@ pub const Version = struct {
|
||||
return strings.eqlComptime(this.tag, current_version);
|
||||
}
|
||||
|
||||
comptime {
|
||||
_ = Bun__githubURL;
|
||||
}
|
||||
};
|
||||
|
||||
pub const UpgradeCheckerThread = struct {
|
||||
pub fn spawn(env_loader: *DotEnv.Loader) void {
|
||||
if (env_loader.map.get("BUN_DISABLE_UPGRADE_CHECK") != null or
|
||||
env_loader.map.get("CI") != null or
|
||||
strings.eqlComptime(env_loader.get("BUN_CANARY") orelse "0", "1"))
|
||||
return;
|
||||
var update_checker_thread = std.Thread.spawn(.{}, run, .{env_loader}) catch return;
|
||||
update_checker_thread.detach();
|
||||
}
|
||||
|
||||
fn _run(env_loader: *DotEnv.Loader) anyerror!void {
|
||||
var rand = std.rand.DefaultPrng.init(@as(u64, @intCast(@max(std.time.milliTimestamp(), 0))));
|
||||
const delay = rand.random().intRangeAtMost(u64, 100, 10000);
|
||||
std.time.sleep(std.time.ns_per_ms * delay);
|
||||
|
||||
Output.Source.configureThread();
|
||||
HTTP.HTTPThread.init(&.{});
|
||||
|
||||
defer {
|
||||
js_ast.Expr.Data.Store.deinit();
|
||||
js_ast.Stmt.Data.Store.deinit();
|
||||
}
|
||||
|
||||
var version = (try UpgradeCommand.getLatestVersion(default_allocator, env_loader, null, null, false, true)) orelse return;
|
||||
|
||||
if (!version.isCurrent()) {
|
||||
if (version.name()) |name| {
|
||||
Output.prettyErrorln("\n<r><d>Bun v{s} is out. Run <b><cyan>bun upgrade<r> to upgrade.\n", .{name});
|
||||
Output.flush();
|
||||
}
|
||||
}
|
||||
|
||||
version.buf.deinit();
|
||||
}
|
||||
|
||||
fn run(env_loader: *DotEnv.Loader) void {
|
||||
_run(env_loader) catch |err| {
|
||||
if (Environment.isDebug) {
|
||||
Output.prettyError("\n[UpgradeChecker] ERROR: {s}\n", .{@errorName(err)});
|
||||
Output.flush();
|
||||
}
|
||||
};
|
||||
pub fn @"export"() void {
|
||||
_ = &Bun__githubURL;
|
||||
_ = &Bun__githubBaselineURL;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1058,3 +1014,8 @@ pub const upgrade_js_bindings = struct {
|
||||
return .undefined;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn @"export"() void {
|
||||
_ = &upgrade_js_bindings;
|
||||
Version.@"export"();
|
||||
}
|
||||
|
||||
@@ -10,10 +10,6 @@ const Output = @import("../../output.zig");
|
||||
|
||||
pub const args = @import("clap/args.zig");
|
||||
|
||||
test "clap" {
|
||||
testing.refAllDecls(@This());
|
||||
}
|
||||
|
||||
pub const ComptimeClap = @import("clap/comptime.zig").ComptimeClap;
|
||||
pub const StreamingClap = @import("clap/streaming.zig").StreamingClap;
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ pub const allow_assert = isDebug or isTest or std.builtin.Mode.ReleaseSafe == @i
|
||||
/// All calls to `@export` should be gated behind this check, so that code
|
||||
/// generators that compile Zig code know not to reference and compile a ton of
|
||||
/// unused code.
|
||||
pub const export_cpp_apis = @import("builtin").output_mode == .Obj;
|
||||
pub const export_cpp_apis = @import("builtin").output_mode == .Obj or isTest;
|
||||
|
||||
pub const build_options = @import("build_options");
|
||||
|
||||
@@ -45,7 +45,6 @@ pub const canary_revision = if (is_canary) build_options.canary_revision else ""
|
||||
pub const dump_source = isDebug and !isTest;
|
||||
pub const base_path = build_options.base_path;
|
||||
pub const enable_logs = build_options.enable_logs;
|
||||
|
||||
pub const codegen_path = build_options.codegen_path;
|
||||
pub const codegen_embed = build_options.codegen_embed;
|
||||
|
||||
|
||||
204
src/main_test.zig
Normal file
204
src/main_test.zig
Normal file
@@ -0,0 +1,204 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
pub const bun = @import("./bun.zig");
|
||||
const recover = @import("test/recover.zig");
|
||||
|
||||
const TestFn = std.builtin.TestFn;
|
||||
const Output = bun.Output;
|
||||
const Environment = bun.Environment;
|
||||
|
||||
// pub const panic = bun.crash_handler.panic;
|
||||
pub const panic = recover.panic;
|
||||
pub const std_options = std.Options{
|
||||
.enable_segfault_handler = false,
|
||||
};
|
||||
|
||||
pub const io_mode = .blocking;
|
||||
|
||||
comptime {
|
||||
bun.assert(builtin.target.cpu.arch.endian() == .little);
|
||||
}
|
||||
|
||||
pub extern "C" var _environ: ?*anyopaque;
|
||||
pub extern "C" var environ: ?*anyopaque;
|
||||
|
||||
pub fn main() void {
|
||||
// This should appear before we make any calls at all to libuv.
|
||||
// So it's safest to put it very early in the main function.
|
||||
if (Environment.isWindows) {
|
||||
_ = bun.windows.libuv.uv_replace_allocator(
|
||||
@ptrCast(&bun.Mimalloc.mi_malloc),
|
||||
@ptrCast(&bun.Mimalloc.mi_realloc),
|
||||
@ptrCast(&bun.Mimalloc.mi_calloc),
|
||||
@ptrCast(&bun.Mimalloc.mi_free),
|
||||
);
|
||||
environ = @ptrCast(std.os.environ.ptr);
|
||||
_environ = @ptrCast(std.os.environ.ptr);
|
||||
}
|
||||
|
||||
bun.initArgv(bun.default_allocator) catch |err| {
|
||||
Output.panic("Failed to initialize argv: {s}\n", .{@errorName(err)});
|
||||
};
|
||||
|
||||
Output.Source.Stdio.init();
|
||||
defer Output.flush();
|
||||
bun.StackCheck.configureThread();
|
||||
const exit_code = runTests();
|
||||
bun.Global.exit(exit_code);
|
||||
}
|
||||
|
||||
const Stats = struct {
|
||||
pass: u32,
|
||||
fail: u32,
|
||||
leak: u32,
|
||||
panic: u32,
|
||||
start: i64,
|
||||
|
||||
fn init() Stats {
|
||||
var stats = std.mem.zeroes(Stats);
|
||||
stats.start = std.time.milliTimestamp();
|
||||
return stats;
|
||||
}
|
||||
|
||||
/// Time elapsed since start in milliseconds
|
||||
fn elapsed(this: *const Stats) i64 {
|
||||
return std.time.milliTimestamp() - this.start;
|
||||
}
|
||||
|
||||
/// Total number of tests run
|
||||
fn total(this: *const Stats) u32 {
|
||||
return this.pass + this.fail + this.leak + this.panic;
|
||||
}
|
||||
|
||||
fn exitCode(this: *const Stats) u8 {
|
||||
var result: u8 = 0;
|
||||
if (this.fail > 0) result |= 1;
|
||||
if (this.leak > 0) result |= 2;
|
||||
if (this.panic > 0) result |= 4;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
fn runTests() u8 {
|
||||
var stats = Stats.init();
|
||||
var stderr = std.io.getStdErr();
|
||||
|
||||
namebuf = std.heap.page_allocator.alloc(u8, namebuf_size) catch {
|
||||
Output.panic("Failed to allocate name buffer", .{});
|
||||
};
|
||||
defer std.heap.page_allocator.free(namebuf);
|
||||
|
||||
const tests: []const TestFn = builtin.test_functions;
|
||||
for (tests) |t| {
|
||||
std.testing.allocator_instance = .{};
|
||||
|
||||
var did_lock = true;
|
||||
stderr.lock(.exclusive) catch {
|
||||
did_lock = false;
|
||||
};
|
||||
defer if (did_lock) stderr.unlock();
|
||||
|
||||
const start = std.time.milliTimestamp();
|
||||
const result = recover.callForTest(t.func);
|
||||
const elapsed = std.time.milliTimestamp() - start;
|
||||
|
||||
const name = extractName(t);
|
||||
const memory_check = std.testing.allocator_instance.deinit();
|
||||
|
||||
if (result) |_| {
|
||||
if (memory_check == .leak) {
|
||||
Output.pretty("<yellow>leak</r> - {s} <i>({d}ms)</r>\n", .{ name, elapsed });
|
||||
stats.leak += 1;
|
||||
} else {
|
||||
Output.pretty("<green>pass</r> - {s} <i>({d}ms)</r>\n", .{ name, elapsed });
|
||||
stats.pass += 1;
|
||||
}
|
||||
} else |err| {
|
||||
switch (err) {
|
||||
error.Panic => {
|
||||
Output.pretty("<magenta><b>panic</r> - {s} <i>({d}ms)</r>\n{s}", .{ t.name, elapsed, @errorName(err) });
|
||||
stats.panic += 1;
|
||||
},
|
||||
else => {
|
||||
Output.pretty("<red>fail</r> - {s} <i>({d}ms)</r>\n{s}", .{ t.name, elapsed, @errorName(err) });
|
||||
stats.fail += 1;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const total = stats.total();
|
||||
const total_time = stats.elapsed();
|
||||
|
||||
if (total == stats.pass) {
|
||||
Output.pretty("\n<green>All tests passed</r>\n", .{});
|
||||
} else {
|
||||
Output.pretty("\n<green>{d}</r> passed", .{stats.pass});
|
||||
if (stats.fail > 0)
|
||||
Output.pretty(", <red>{d}</r> failed", .{stats.fail})
|
||||
else
|
||||
Output.pretty(", 0 failed", .{});
|
||||
if (stats.leak > 0) Output.pretty(", <yellow>{d}</r> leaked", .{stats.leak});
|
||||
if (stats.panic > 0) Output.pretty(", <magenta>{d}</r> panicked", .{stats.panic});
|
||||
}
|
||||
|
||||
Output.pretty("\n\tRan <b>{d}</r> tests in <b>{d}</r>ms\n\n", .{ total, total_time });
|
||||
return stats.exitCode();
|
||||
}
|
||||
|
||||
// heap-allocated on start to avoid increasing binary size
|
||||
threadlocal var namebuf: []u8 = undefined;
|
||||
const namebuf_size = 4096;
|
||||
comptime {
|
||||
std.debug.assert(std.math.isPowerOfTwo(namebuf_size));
|
||||
}
|
||||
|
||||
fn extractName(t: TestFn) []const u8 {
|
||||
inline for (.{ ".test.", ".decltest." }) |test_sep| {
|
||||
if (std.mem.lastIndexOf(u8, t.name, test_sep)) |marker| {
|
||||
const prefix = t.name[0..marker];
|
||||
const test_name = t.name[marker + test_sep.len ..];
|
||||
const full_name = std.fmt.bufPrint(namebuf, "{s}\t{s}", .{ prefix, test_name }) catch @panic("name buffer too small");
|
||||
return full_name;
|
||||
}
|
||||
}
|
||||
|
||||
return t.name;
|
||||
}
|
||||
|
||||
pub const overrides = struct {
|
||||
pub const mem = struct {
|
||||
extern "C" fn wcslen(s: [*:0]const u16) usize;
|
||||
|
||||
pub fn indexOfSentinel(comptime T: type, comptime sentinel: T, p: [*:sentinel]const T) usize {
|
||||
if (comptime T == u16 and sentinel == 0 and Environment.isWindows) {
|
||||
return wcslen(p);
|
||||
}
|
||||
|
||||
if (comptime T == u8 and sentinel == 0) {
|
||||
return bun.C.strlen(p);
|
||||
}
|
||||
|
||||
var i: usize = 0;
|
||||
while (p[i] != sentinel) {
|
||||
i += 1;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
pub export fn Bun__panic(msg: [*]const u8, len: usize) noreturn {
|
||||
Output.panic("{s}", .{msg[0..len]});
|
||||
}
|
||||
|
||||
comptime {
|
||||
_ = bun.bake.production.BakeProdResolve;
|
||||
_ = bun.bake.production.BakeProdLoad;
|
||||
|
||||
_ = bun.bun_js.Bun__onRejectEntryPointResult;
|
||||
_ = bun.bun_js.Bun__onResolveEntryPointResult;
|
||||
_ = &@import("bun.js/node/buffer.zig").BufferVectorized;
|
||||
@import("cli/upgrade_command.zig").@"export"();
|
||||
@import("cli/test_command.zig").@"export"();
|
||||
}
|
||||
@@ -870,7 +870,7 @@ pub const color_map = ComptimeStringMap(string, .{
|
||||
const RESET: string = "\x1b[0m";
|
||||
pub fn prettyFmt(comptime fmt: string, comptime is_enabled: bool) [:0]const u8 {
|
||||
if (comptime bun.fast_debug_build_mode)
|
||||
return fmt;
|
||||
return fmt ++ "\x00";
|
||||
|
||||
comptime var new_fmt: [fmt.len * 4]u8 = undefined;
|
||||
comptime var new_fmt_i: usize = 0;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
const tester = @import("../test/tester.zig");
|
||||
const std = @import("std");
|
||||
const strings = @import("../string_immutable.zig");
|
||||
const FeatureFlags = @import("../feature_flags.zig");
|
||||
const default_allocator = @import("../allocators/memory_allocator.zig").c_allocator;
|
||||
const bun = @import("root").bun;
|
||||
const Fs = @import("../fs.zig");
|
||||
|
||||
|
||||
@@ -1027,7 +1027,6 @@ pub const Test = struct {
|
||||
}
|
||||
|
||||
pub fn make(comptime testName: string, data: anytype) !Router {
|
||||
std.testing.refAllDecls(@import("./bun.js/bindings/exports.zig"));
|
||||
try makeTest(testName, data);
|
||||
const JSAst = bun.JSAst;
|
||||
JSAst.Expr.Data.Store.create(default_allocator);
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
const bun = @import("root").bun;
|
||||
const ArrayList = std.ArrayList;
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const ArrayList = std.ArrayList;
|
||||
const Arena = std.heap.ArenaAllocator;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const SmolStr = @import("../string.zig").SmolStr;
|
||||
|
||||
/// Using u16 because anymore tokens than that results in an unreasonably high
|
||||
/// amount of brace expansion (like around 32k variants to expand)
|
||||
pub const ExpansionVariant = packed struct {
|
||||
start: u16 = 0,
|
||||
end: u16 = 0,
|
||||
};
|
||||
|
||||
const assert = bun.assert;
|
||||
const log = bun.Output.scoped(.BRACES, false);
|
||||
|
||||
const TokenTag = enum { open, comma, text, close, eof };
|
||||
const Token = union(TokenTag) {
|
||||
/// Using u16 because anymore tokens than that results in an unreasonably high
|
||||
/// amount of brace expansion (like around 32k variants to expand)
|
||||
const ExpansionVariant = packed struct {
|
||||
start: u16 = 0,
|
||||
end: u16 = 0, // must be >= start
|
||||
};
|
||||
|
||||
const Token = union(enum) {
|
||||
open: ExpansionVariants,
|
||||
comma,
|
||||
text: SmolStr,
|
||||
close,
|
||||
eof,
|
||||
const Tag = @typeInfo(Token).@"union".tag_type.?;
|
||||
|
||||
const ExpansionVariants = struct {
|
||||
idx: u16 = 0,
|
||||
@@ -58,33 +59,33 @@ pub const AST = struct {
|
||||
|
||||
const MAX_NESTED_BRACES = 10;
|
||||
|
||||
const StackError = error{
|
||||
StackFull,
|
||||
};
|
||||
|
||||
/// A stack on the stack
|
||||
pub fn StackStack(comptime T: type, comptime SizeType: type, comptime N: SizeType) type {
|
||||
fn StackStack(comptime T: type, comptime SizeType: type, comptime N: SizeType) type {
|
||||
return struct {
|
||||
items: [N]T = undefined,
|
||||
len: SizeType = 0,
|
||||
|
||||
pub const Error = error{
|
||||
StackFull,
|
||||
};
|
||||
|
||||
pub fn top(this: *@This()) ?T {
|
||||
fn top(this: *@This()) ?T {
|
||||
if (this.len == 0) return null;
|
||||
return this.items[this.len - 1];
|
||||
}
|
||||
|
||||
pub fn topPtr(this: *@This()) ?*T {
|
||||
fn topPtr(this: *@This()) ?*T {
|
||||
if (this.len == 0) return null;
|
||||
return &this.items[this.len - 1];
|
||||
}
|
||||
|
||||
pub fn push(this: *@This(), value: T) Error!void {
|
||||
if (this.len == N) return Error.StackFull;
|
||||
fn push(this: *@This(), value: T) StackError!void {
|
||||
if (this.len == N) return StackError.StackFull;
|
||||
this.items[this.len] = value;
|
||||
this.len += 1;
|
||||
}
|
||||
|
||||
pub fn pop(this: *@This()) ?T {
|
||||
fn pop(this: *@This()) ?T {
|
||||
if (this.top()) |v| {
|
||||
this.len -= 1;
|
||||
return v;
|
||||
@@ -95,10 +96,7 @@ pub fn StackStack(comptime T: type, comptime SizeType: type, comptime N: SizeTyp
|
||||
}
|
||||
|
||||
/// This may have false positives but it is fast
|
||||
pub fn fastDetect(src: []const u8) bool {
|
||||
const Quote = enum { single, double };
|
||||
_ = Quote;
|
||||
|
||||
fn fastDetect(src: []const u8) bool {
|
||||
var has_open = false;
|
||||
var has_close = false;
|
||||
if (src.len < 16) {
|
||||
@@ -151,13 +149,15 @@ pub fn fastDetect(src: []const u8) bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ExpandError = StackError || ParserError;
|
||||
|
||||
/// `out` is preallocated by using the result from `calculateExpandedAmount`
|
||||
pub fn expand(
|
||||
allocator: Allocator,
|
||||
tokens: []Token,
|
||||
out: []std.ArrayList(u8),
|
||||
contains_nested: bool,
|
||||
) (error{StackFull} || ParserError)!void {
|
||||
) ExpandError!void {
|
||||
var out_key_counter: u16 = 1;
|
||||
if (!contains_nested) {
|
||||
var expansions_table = try buildExpansionTableAlloc(allocator, tokens);
|
||||
@@ -176,7 +176,7 @@ fn expandNested(
|
||||
out_key: u16,
|
||||
out_key_counter: *u16,
|
||||
start: u32,
|
||||
) !void {
|
||||
) ExpandError!void {
|
||||
if (root.atoms == .single) {
|
||||
if (start > 0) {
|
||||
if (root.bubble_up) |bubble_up| {
|
||||
@@ -302,9 +302,7 @@ fn expandFlat(
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn expandNested()
|
||||
|
||||
pub fn calculateVariantsAmount(tokens: []const Token) u32 {
|
||||
fn calculateVariantsAmount(tokens: []const Token) u32 {
|
||||
var brace_count: u32 = 0;
|
||||
var count: u32 = 0;
|
||||
for (tokens) |tok| {
|
||||
@@ -415,8 +413,8 @@ pub const Parser = struct {
|
||||
return self.peek() == .eof;
|
||||
}
|
||||
|
||||
fn expect(self: *Parser, toktag: TokenTag) Token {
|
||||
assert(toktag == @as(TokenTag, self.peek()));
|
||||
fn expect(self: *Parser, toktag: Token.Tag) Token {
|
||||
assert(toktag == @as(Token.Tag, self.peek()));
|
||||
if (self.check(toktag)) {
|
||||
return self.advance();
|
||||
}
|
||||
@@ -424,15 +422,15 @@ pub const Parser = struct {
|
||||
}
|
||||
|
||||
/// Consumes token if it matches
|
||||
fn match(self: *Parser, toktag: TokenTag) bool {
|
||||
if (@as(TokenTag, self.peek()) == toktag) {
|
||||
fn match(self: *Parser, toktag: Token.Tag) bool {
|
||||
if (@as(Token.Tag, self.peek()) == toktag) {
|
||||
_ = self.advance();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn match_any2(self: *Parser, comptime toktags: []const TokenTag) ?Token {
|
||||
fn match_any2(self: *Parser, comptime toktags: []const Token.Tag) ?Token {
|
||||
const peeked = self.peek();
|
||||
inline for (toktags) |tag| {
|
||||
if (peeked == tag) {
|
||||
@@ -443,8 +441,8 @@ pub const Parser = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
fn match_any(self: *Parser, comptime toktags: []const TokenTag) bool {
|
||||
const peeked = @as(TokenTag, self.peek());
|
||||
fn match_any(self: *Parser, comptime toktags: []const Token.Tag) bool {
|
||||
const peeked = @as(Token.Tag, self.peek());
|
||||
inline for (toktags) |tag| {
|
||||
if (peeked == tag) {
|
||||
_ = self.advance();
|
||||
@@ -454,8 +452,8 @@ pub const Parser = struct {
|
||||
return false;
|
||||
}
|
||||
|
||||
fn check(self: *Parser, toktag: TokenTag) bool {
|
||||
return @as(TokenTag, self.peek()) == @as(TokenTag, toktag);
|
||||
fn check(self: *Parser, toktag: Token.Tag) bool {
|
||||
return @as(Token.Tag, self.peek()) == @as(Token.Tag, toktag);
|
||||
}
|
||||
|
||||
fn peek(self: *Parser) Token {
|
||||
@@ -480,18 +478,15 @@ pub const Parser = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub fn calculateExpandedAmount(tokens: []const Token) !u32 {
|
||||
pub fn calculateExpandedAmount(tokens: []const Token) StackError!u32 {
|
||||
var nested_brace_stack = StackStack(u8, u8, MAX_NESTED_BRACES){};
|
||||
var variant_count: u32 = 0;
|
||||
var i: usize = 0;
|
||||
|
||||
var prev_comma: bool = false;
|
||||
while (i < tokens.len) : (i += 1) {
|
||||
|
||||
for (tokens) |tok| {
|
||||
prev_comma = false;
|
||||
switch (tokens[i]) {
|
||||
.open => {
|
||||
try nested_brace_stack.push(0);
|
||||
},
|
||||
switch (tok) {
|
||||
.open => try nested_brace_stack.push(0),
|
||||
.comma => {
|
||||
const val = nested_brace_stack.topPtr().?;
|
||||
val.* += 1;
|
||||
@@ -518,16 +513,13 @@ pub fn calculateExpandedAmount(tokens: []const Token) !u32 {
|
||||
return variant_count;
|
||||
}
|
||||
|
||||
pub fn buildExpansionTableAlloc(alloc: Allocator, tokens: []Token) !std.ArrayList(ExpansionVariant) {
|
||||
fn buildExpansionTableAlloc(alloc: Allocator, tokens: []Token) !std.ArrayList(ExpansionVariant) {
|
||||
var table = std.ArrayList(ExpansionVariant).init(alloc);
|
||||
try buildExpansionTable(tokens, &table);
|
||||
return table;
|
||||
}
|
||||
|
||||
pub fn buildExpansionTable(
|
||||
tokens: []Token,
|
||||
table: *std.ArrayList(ExpansionVariant),
|
||||
) !void {
|
||||
fn buildExpansionTable(tokens: []Token, table: *std.ArrayList(ExpansionVariant)) !void {
|
||||
const BraceState = struct {
|
||||
tok_idx: u16,
|
||||
variants: u16,
|
||||
@@ -594,7 +586,7 @@ const NewChars = @import("./shell.zig").ShellCharIter;
|
||||
|
||||
pub const Lexer = NewLexer(.ascii);
|
||||
|
||||
pub fn NewLexer(comptime encoding: Encoding) type {
|
||||
fn NewLexer(comptime encoding: Encoding) type {
|
||||
const Chars = NewChars(encoding);
|
||||
return struct {
|
||||
chars: Chars,
|
||||
@@ -803,4 +795,32 @@ pub fn NewLexer(comptime encoding: Encoding) type {
|
||||
};
|
||||
}
|
||||
|
||||
const assert = bun.assert;
|
||||
const t = std.testing;
|
||||
test Lexer {
|
||||
var arena = std.heap.ArenaAllocator.init(t.allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
const TestCase = struct { []const u8, []const Token };
|
||||
const test_cases: []const TestCase = &[_]TestCase{
|
||||
.{
|
||||
"{}",
|
||||
&[_]Token{ .{ .open = .{} }, .close, .eof },
|
||||
},
|
||||
.{
|
||||
"{foo}",
|
||||
&[_]Token{ .{ .open = .{} }, .{ .text = try SmolStr.fromSlice(arena.allocator(), "foo") }, .close, .eof },
|
||||
},
|
||||
};
|
||||
|
||||
for (test_cases) |test_case| {
|
||||
const src, const expected = test_case;
|
||||
// NOTE: don't use arena here so that we can test for memory leaks
|
||||
var result = try Lexer.tokenize(t.allocator, src);
|
||||
defer result.tokens.deinit();
|
||||
try t.expectEqualSlices(
|
||||
Token,
|
||||
expected,
|
||||
result.tokens.items,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ pub const SmolStr = packed struct {
|
||||
cap: u32,
|
||||
__ptr: [*]u8,
|
||||
|
||||
const Tag: usize = 0x8000000000000000;
|
||||
const Tag: usize = 0x8000000000000000; // NOTE: only works on little endian systems
|
||||
const NegatedTag: usize = ~Tag;
|
||||
|
||||
pub fn jsonStringify(self: *const SmolStr, writer: anytype) !void {
|
||||
@@ -21,7 +21,30 @@ pub const SmolStr = packed struct {
|
||||
__len: u7,
|
||||
_tag: u1,
|
||||
|
||||
pub fn len(this: Inlined) u8 {
|
||||
const max_len: comptime_int = @bitSizeOf(@FieldType(Inlined, "data")) / 8;
|
||||
const empty: Inlined = .{
|
||||
.data = 0,
|
||||
.__len = 0,
|
||||
._tag = 1,
|
||||
};
|
||||
|
||||
/// ## Errors
|
||||
/// if `str` is longer than `max_len`
|
||||
pub fn init(str: []const u8) !Inlined {
|
||||
if (str.len > max_len) {
|
||||
@branchHint(.unlikely);
|
||||
return error.StringTooLong;
|
||||
}
|
||||
var inlined = Inlined.empty;
|
||||
|
||||
if (str.len > 0) {
|
||||
@memcpy(inlined.allChars()[0..str.len], str[0..str.len]);
|
||||
inlined.setLen(@intCast(str.len));
|
||||
}
|
||||
return inlined;
|
||||
}
|
||||
|
||||
pub inline fn len(this: Inlined) u8 {
|
||||
return @intCast(this.__len);
|
||||
}
|
||||
|
||||
@@ -29,12 +52,20 @@ pub const SmolStr = packed struct {
|
||||
this.__len = new_len;
|
||||
}
|
||||
|
||||
pub fn slice(this: *Inlined) []const u8 {
|
||||
return this.allChars()[0..this.__len];
|
||||
pub fn slice(this: *const Inlined) []const u8 {
|
||||
return @constCast(this).ptr()[0..this.__len];
|
||||
}
|
||||
|
||||
pub fn allChars(this: *Inlined) *[15]u8 {
|
||||
return @as([*]u8, @ptrCast(@as(*u128, @ptrCast(this))))[0..15];
|
||||
pub fn sliceMut(this: *Inlined) []u8 {
|
||||
return this.ptr()[0..this.__len];
|
||||
}
|
||||
|
||||
pub fn allChars(this: *Inlined) *[max_len]u8 {
|
||||
return this.ptr()[0..max_len];
|
||||
}
|
||||
|
||||
inline fn ptr(this: *Inlined) [*]u8 {
|
||||
return @as([*]u8, @ptrCast(@as(*u128, @ptrCast(this))));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -43,12 +74,7 @@ pub const SmolStr = packed struct {
|
||||
}
|
||||
|
||||
pub fn empty() SmolStr {
|
||||
const inlined = Inlined{
|
||||
.data = 0,
|
||||
.__len = 0,
|
||||
._tag = 1,
|
||||
};
|
||||
return SmolStr.fromInlined(inlined);
|
||||
return SmolStr.fromInlined(Inlined.empty);
|
||||
}
|
||||
|
||||
pub fn len(this: *const SmolStr) u32 {
|
||||
@@ -79,7 +105,10 @@ pub const SmolStr = packed struct {
|
||||
return @as(usize, @intFromPtr(this.__ptr)) & Tag != 0;
|
||||
}
|
||||
|
||||
/// ## Panics
|
||||
/// if `this` is too long to fit in an inlined string
|
||||
pub fn toInlined(this: *const SmolStr) Inlined {
|
||||
assert(this.len() <= Inlined.max_len);
|
||||
var inlined: Inlined = @bitCast(@as(u128, @bitCast(this.*)));
|
||||
inlined._tag = 1;
|
||||
return inlined;
|
||||
@@ -113,25 +142,21 @@ pub const SmolStr = packed struct {
|
||||
|
||||
return SmolStr.fromInlined(inlined);
|
||||
}
|
||||
pub fn deinit(this: *SmolStr, allocator: Allocator) void {
|
||||
if (!this.isInlined()) {
|
||||
allocator.free(this.slice());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fromSlice(allocator: Allocator, values: []const u8) Allocator.Error!SmolStr {
|
||||
if (values.len > 15) {
|
||||
if (values.len > Inlined.max_len) {
|
||||
var baby_list = try BabyList(u8).initCapacity(allocator, values.len);
|
||||
baby_list.appendSliceAssumeCapacity(values);
|
||||
return SmolStr.fromBabyList(baby_list);
|
||||
}
|
||||
|
||||
var inlined = Inlined{
|
||||
.data = 0,
|
||||
.__len = 0,
|
||||
._tag = 1,
|
||||
};
|
||||
|
||||
if (values.len > 0) {
|
||||
@memcpy(inlined.allChars()[0..values.len], values[0..values.len]);
|
||||
inlined.setLen(@intCast(values.len));
|
||||
}
|
||||
|
||||
// SAFETY: we already checked that `values` can fit in an inlined string
|
||||
const inlined = Inlined.init(values) catch unreachable;
|
||||
return SmolStr.fromInlined(inlined);
|
||||
}
|
||||
|
||||
@@ -146,11 +171,10 @@ pub const SmolStr = packed struct {
|
||||
pub fn appendChar(this: *SmolStr, allocator: Allocator, char: u8) Allocator.Error!void {
|
||||
if (this.isInlined()) {
|
||||
var inlined = this.toInlined();
|
||||
if (inlined.len() + 1 > 15) {
|
||||
if (inlined.len() + 1 > Inlined.max_len) {
|
||||
var baby_list = try BabyList(u8).initCapacity(allocator, inlined.len() + 1);
|
||||
baby_list.appendSliceAssumeCapacity(inlined.slice());
|
||||
try baby_list.push(allocator, char);
|
||||
// this.* = SmolStr.fromBabyList(baby_list);
|
||||
this.__len = baby_list.len;
|
||||
this.__ptr = baby_list.ptr;
|
||||
this.cap = baby_list.cap;
|
||||
@@ -159,7 +183,6 @@ pub const SmolStr = packed struct {
|
||||
}
|
||||
inlined.allChars()[inlined.len()] = char;
|
||||
inlined.setLen(@intCast(inlined.len() + 1));
|
||||
// this.* = SmolStr.fromInlined(inlined);
|
||||
this.* = @bitCast(inlined);
|
||||
this.markInlined();
|
||||
return;
|
||||
@@ -172,7 +195,6 @@ pub const SmolStr = packed struct {
|
||||
};
|
||||
try baby_list.push(allocator, char);
|
||||
|
||||
// this.* = SmolStr.fromBabyList(baby_list);
|
||||
this.__len = baby_list.len;
|
||||
this.__ptr = baby_list.ptr;
|
||||
this.cap = baby_list.cap;
|
||||
@@ -182,7 +204,7 @@ pub const SmolStr = packed struct {
|
||||
pub fn appendSlice(this: *SmolStr, allocator: Allocator, values: []const u8) Allocator.Error!void {
|
||||
if (this.isInlined()) {
|
||||
var inlined = this.toInlined();
|
||||
if (inlined.len() + values.len > 15) {
|
||||
if (inlined.len() + values.len > Inlined.max_len) {
|
||||
var baby_list = try BabyList(u8).initCapacity(allocator, inlined.len() + values.len);
|
||||
baby_list.appendSliceAssumeCapacity(inlined.slice());
|
||||
baby_list.appendSliceAssumeCapacity(values);
|
||||
@@ -206,3 +228,47 @@ pub const SmolStr = packed struct {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const t = std.testing;
|
||||
|
||||
test SmolStr {
|
||||
// large strings are heap-allocated
|
||||
{
|
||||
var str = try SmolStr.fromSlice(t.allocator, "oh wow this is a long string");
|
||||
defer str.deinit(t.allocator);
|
||||
try t.expectEqualStrings("oh wow this is a long string", str.slice());
|
||||
try t.expect(!str.isInlined());
|
||||
}
|
||||
|
||||
// small strings are inlined
|
||||
{
|
||||
var str = try SmolStr.fromSlice(t.allocator, "hello");
|
||||
defer str.deinit(t.allocator);
|
||||
try t.expectEqualStrings("hello", str.slice());
|
||||
try t.expect(str.isInlined());
|
||||
|
||||
// operations that grow a string beyond the inlined capacity force an allocation.
|
||||
try str.appendSlice(t.allocator, " world, this makes it too long to be inlined");
|
||||
try t.expectEqualStrings("hello world, this makes it too long to be inlined", str.slice());
|
||||
try t.expect(!str.isInlined());
|
||||
}
|
||||
}
|
||||
|
||||
test "SmolStr.Inlined.init" {
|
||||
var hello = try SmolStr.Inlined.init("hello");
|
||||
try t.expectEqualStrings("hello", hello.slice());
|
||||
try t.expectEqual(5, hello.len());
|
||||
try t.expectEqual(1, hello._tag); // 1 = inlined
|
||||
|
||||
try t.expectError(error.StringTooLong, SmolStr.Inlined.init("this string is too long to be inlined within a u120"));
|
||||
|
||||
const empty = try SmolStr.Inlined.init("");
|
||||
try t.expectEqual(empty, SmolStr.Inlined.empty);
|
||||
}
|
||||
|
||||
test "Creating an inlined SmolStr does not allocate" {
|
||||
var hello = try SmolStr.fromSlice(t.allocator, "hello");
|
||||
// no `defer hello.deinit()` to ensure fromSlice does not allocate
|
||||
try t.expectEqual(5, hello.len());
|
||||
try t.expect(hello.isInlined());
|
||||
}
|
||||
|
||||
1605
src/test/project.zig
1605
src/test/project.zig
File diff suppressed because it is too large
Load Diff
131
src/test/recover.zig
Normal file
131
src/test/recover.zig
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright © 2024 Dimitris Dinodimos.
|
||||
|
||||
//! Panic recover.
|
||||
//! Regains control of the calling thread when the function panics or behaves
|
||||
//! undefined.
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const Context = if (builtin.os.tag == .windows)
|
||||
std.os.windows.CONTEXT
|
||||
else if (builtin.os.tag == .linux and builtin.abi == .musl)
|
||||
musl.jmp_buf
|
||||
else
|
||||
std.c.ucontext_t;
|
||||
|
||||
threadlocal var top_ctx: ?*const Context = null;
|
||||
|
||||
/// Returns if there was no recover call in current thread.
|
||||
/// Otherwise, does not return and execution continues from the current thread
|
||||
/// recover call.
|
||||
/// Call from root source file panic handler.
|
||||
pub fn panicked() void {
|
||||
if (top_ctx) |ctx| {
|
||||
setContext(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// comptime function that extends T by combining its error set with error.Panic
|
||||
fn ExtErrType(T: type) type {
|
||||
const E = error{Panic};
|
||||
const info = @typeInfo(T);
|
||||
if (info != .error_union) {
|
||||
return E!T;
|
||||
}
|
||||
return (info.error_union.error_set || E)!(info.error_union.payload);
|
||||
}
|
||||
|
||||
// comptime function that returns the return type of function `func`
|
||||
fn ReturnType(func: anytype) type {
|
||||
const ti = @typeInfo(@TypeOf(func));
|
||||
return ti.@"fn".return_type.?;
|
||||
}
|
||||
|
||||
pub fn callForTest(
|
||||
test_func: *const fn () anyerror!void,
|
||||
) anyerror!void {
|
||||
const prev_ctx: ?*const Context = top_ctx;
|
||||
var ctx: Context = std.mem.zeroes(Context);
|
||||
getContext(&ctx);
|
||||
if (top_ctx != prev_ctx) {
|
||||
top_ctx = prev_ctx;
|
||||
return error.Panic;
|
||||
}
|
||||
top_ctx = &ctx;
|
||||
defer top_ctx = prev_ctx;
|
||||
return @call(.auto, test_func, .{});
|
||||
}
|
||||
|
||||
/// Calls `func` with `args`, guarding from runtime errors.
|
||||
/// Returns `error.Panic` when recovers from runtime error.
|
||||
/// Otherwise returns the return value of func.
|
||||
pub fn call(
|
||||
func: anytype,
|
||||
args: anytype,
|
||||
) ExtErrType(ReturnType(func)) {
|
||||
const prev_ctx: ?*const Context = top_ctx;
|
||||
var ctx: Context = std.mem.zeroes(Context);
|
||||
getContext(&ctx);
|
||||
if (top_ctx != prev_ctx) {
|
||||
top_ctx = prev_ctx;
|
||||
return error.Panic;
|
||||
}
|
||||
top_ctx = &ctx;
|
||||
defer top_ctx = prev_ctx;
|
||||
return @call(.auto, func, args);
|
||||
}
|
||||
|
||||
// windows
|
||||
const CONTEXT = std.os.windows.CONTEXT;
|
||||
const EXCEPTION_RECORD = std.os.windows.EXCEPTION_RECORD;
|
||||
const WINAPI = std.os.windows.WINAPI;
|
||||
extern "ntdll" fn RtlRestoreContext(
|
||||
ContextRecord: *const CONTEXT,
|
||||
ExceptionRecord: ?*const EXCEPTION_RECORD,
|
||||
) callconv(WINAPI) noreturn;
|
||||
|
||||
// darwin, bsd, gnu linux
|
||||
extern "c" fn setcontext(ucp: *const std.c.ucontext_t) noreturn;
|
||||
|
||||
// linux musl
|
||||
const musl = struct {
|
||||
const jmp_buf = @cImport(@cInclude("setjmp.h")).jmp_buf;
|
||||
extern fn setjmp(env: *jmp_buf) c_int;
|
||||
extern fn longjmp(env: *const jmp_buf, val: c_int) noreturn;
|
||||
};
|
||||
|
||||
inline fn getContext(ctx: *Context) void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
std.os.windows.ntdll.RtlCaptureContext(ctx);
|
||||
} else if (builtin.os.tag == .linux and builtin.abi == .musl) {
|
||||
_ = musl.setjmp(ctx);
|
||||
} else {
|
||||
_ = std.debug.getContext(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
inline fn setContext(ctx: *const Context) noreturn {
|
||||
if (builtin.os.tag == .windows) {
|
||||
RtlRestoreContext(ctx, null);
|
||||
} else if (builtin.os.tag == .linux and builtin.abi == .musl) {
|
||||
musl.longjmp(ctx, 1);
|
||||
} else {
|
||||
setcontext(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Panic handler that if there is a recover call in current thread continues
|
||||
/// from recover call. Otherwise calls the default panic.
|
||||
/// Install at root source file as `pub const panic = @import("recover").panic;`
|
||||
pub const panic: type = std.debug.FullPanic(
|
||||
struct {
|
||||
pub fn panic(
|
||||
msg: []const u8,
|
||||
first_trace_addr: ?usize,
|
||||
) noreturn {
|
||||
panicked();
|
||||
std.debug.defaultPanic(msg, first_trace_addr);
|
||||
}
|
||||
}.panic,
|
||||
);
|
||||
@@ -1,157 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
const string = []const u8;
|
||||
|
||||
const RED = "\x1b[31;1m";
|
||||
const GREEN = "\x1b[32;1m";
|
||||
const CYAN = "\x1b[36;1m";
|
||||
const WHITE = "\x1b[37;1m";
|
||||
const DIM = "\x1b[2m";
|
||||
const RESET = "\x1b[0m";
|
||||
|
||||
pub const Tester = struct {
|
||||
pass: std.ArrayList(Expectation),
|
||||
fail: std.ArrayList(Expectation),
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
pub fn t(allocator: std.mem.Allocator) Tester {
|
||||
return Tester{
|
||||
.allocator = allocator,
|
||||
.pass = std.ArrayList(Expectation).init(allocator),
|
||||
.fail = std.ArrayList(Expectation).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub const Expectation = struct {
|
||||
expected: string,
|
||||
result: string,
|
||||
source: std.builtin.SourceLocation,
|
||||
|
||||
pub fn init(expected: string, result: string, src: std.builtin.SourceLocation) Expectation {
|
||||
return Expectation{
|
||||
.expected = expected,
|
||||
.result = result,
|
||||
.source = src,
|
||||
};
|
||||
}
|
||||
const PADDING = 0;
|
||||
pub fn print(self: *const @This()) void {
|
||||
const pad = &([_]u8{' '} ** PADDING);
|
||||
var stderr = std.io.getStdErr();
|
||||
|
||||
stderr.writeAll(RESET) catch unreachable;
|
||||
stderr.writeAll(pad) catch unreachable;
|
||||
stderr.writeAll(DIM) catch unreachable;
|
||||
std.fmt.format(stderr.writer(), "{s}:{d}:{d}", .{ self.source.file, self.source.line, self.source.column }) catch unreachable;
|
||||
stderr.writeAll(RESET) catch unreachable;
|
||||
stderr.writeAll("\n") catch unreachable;
|
||||
|
||||
stderr.writeAll(pad) catch unreachable;
|
||||
stderr.writeAll("Expected: ") catch unreachable;
|
||||
stderr.writeAll(RESET) catch unreachable;
|
||||
stderr.writeAll(GREEN) catch unreachable;
|
||||
std.fmt.format(stderr.writer(), "\"{s}\"", .{self.expected}) catch unreachable;
|
||||
stderr.writeAll(GREEN) catch unreachable;
|
||||
stderr.writeAll(RESET) catch unreachable;
|
||||
|
||||
stderr.writeAll("\n") catch unreachable;
|
||||
stderr.writeAll(pad) catch unreachable;
|
||||
stderr.writeAll("Received: ") catch unreachable;
|
||||
stderr.writeAll(RESET) catch unreachable;
|
||||
stderr.writeAll(RED) catch unreachable;
|
||||
std.fmt.format(stderr.writer(), "\"{s}\"", .{self.result}) catch unreachable;
|
||||
stderr.writeAll(RED) catch unreachable;
|
||||
stderr.writeAll(RESET) catch unreachable;
|
||||
stderr.writeAll("\n") catch unreachable;
|
||||
}
|
||||
const strings = @import("../string_immutable.zig");
|
||||
pub fn evaluate_outcome(self: *const @This()) Outcome {
|
||||
if (strings.eql(self.expected, self.result)) {
|
||||
return .pass;
|
||||
} else {
|
||||
return .fail;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Outcome = enum {
|
||||
pass,
|
||||
fail,
|
||||
};
|
||||
|
||||
pub inline fn expect(tester: *Tester, expected: string, result: string, src: std.builtin.SourceLocation) bool {
|
||||
var expectation = Expectation.init(expected, result, src);
|
||||
switch (expectation.evaluate_outcome()) {
|
||||
.pass => {
|
||||
tester.pass.append(expectation) catch unreachable;
|
||||
return true;
|
||||
},
|
||||
.fail => {
|
||||
tester.fail.append(expectation) catch unreachable;
|
||||
return false;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const ReportType = enum {
|
||||
none,
|
||||
pass,
|
||||
fail,
|
||||
some_fail,
|
||||
|
||||
pub fn init(tester: *Tester) ReportType {
|
||||
if (tester.fail.items.len == 0 and tester.pass.items.len == 0) {
|
||||
return .none;
|
||||
} else if (tester.fail.items.len == 0) {
|
||||
return .pass;
|
||||
} else if (tester.pass.items.len == 0) {
|
||||
return .fail;
|
||||
} else {
|
||||
return .some_fail;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn report(tester: *Tester, src: std.builtin.SourceLocation) void {
|
||||
var stderr = std.io.getStdErr();
|
||||
|
||||
if (tester.fail.items.len > 0) {
|
||||
std.fmt.format(stderr.writer(), "\n\n", .{}) catch unreachable;
|
||||
}
|
||||
|
||||
for (tester.fail.items) |item| {
|
||||
item.print();
|
||||
std.fmt.format(stderr.writer(), "\n", .{}) catch unreachable;
|
||||
}
|
||||
|
||||
switch (ReportType.init(tester)) {
|
||||
.none => {
|
||||
std.log.info("No expectations.\n\n", .{});
|
||||
},
|
||||
.pass => {
|
||||
std.fmt.format(stderr.writer(), "{s}All {d} expectations passed.{s}\n", .{ GREEN, tester.pass.items.len, GREEN }) catch unreachable;
|
||||
std.fmt.format(stderr.writer(), RESET, .{}) catch unreachable;
|
||||
std.testing.expect(true) catch std.debug.panic("Test failure", .{});
|
||||
},
|
||||
.fail => {
|
||||
std.fmt.format(stderr.writer(), "{s}All {d} expectations failed.{s}\n\n", .{ RED, tester.fail.items.len, RED }) catch unreachable;
|
||||
std.fmt.format(stderr.writer(), RESET, .{}) catch unreachable;
|
||||
std.testing.expect(false) catch std.debug.panic("Test failure", .{});
|
||||
},
|
||||
.some_fail => {
|
||||
std.fmt.format(stderr.writer(), "{s}{d} failed{s} and {s}{d} passed{s} of {d} expectations{s}\n\n", .{
|
||||
RED,
|
||||
tester.fail.items.len,
|
||||
RED ++ RESET,
|
||||
GREEN,
|
||||
tester.pass.items.len,
|
||||
GREEN ++ RESET,
|
||||
tester.fail.items.len + tester.pass.items.len,
|
||||
RESET,
|
||||
}) catch unreachable;
|
||||
std.fmt.format(stderr.writer(), RESET, .{}) catch unreachable;
|
||||
std.testing.expect(false) catch std.debug.panic("Test failure in {s}: {s}:{d}:{d}", .{ src.fn_name, src.file, src.line, src.column });
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
16
src/unit_test.zig
Normal file
16
src/unit_test.zig
Normal file
@@ -0,0 +1,16 @@
|
||||
const std = @import("std");
|
||||
const bun = @import("root").bun;
|
||||
const t = std.testing;
|
||||
|
||||
test {
|
||||
_ = @import("shell/braces.zig");
|
||||
_ = @import("bun.js/node/assert/myers_diff.zig");
|
||||
}
|
||||
|
||||
test "basic string usage" {
|
||||
var s = bun.String.createUTF8("hi");
|
||||
defer s.deref();
|
||||
try t.expect(s.tag != .Dead and s.tag != .Empty);
|
||||
try t.expectEqual(s.length(), 2);
|
||||
try t.expectEqualStrings(s.asUTF8().?, "hi");
|
||||
}
|
||||
@@ -6,9 +6,9 @@ const words: Record<string, { reason: string; limit?: number; regex?: boolean }>
|
||||
" != undefined": { reason: "This is by definition Undefined Behavior." },
|
||||
" == undefined": { reason: "This is by definition Undefined Behavior." },
|
||||
'@import("root").bun.': { reason: "Only import 'bun' once" },
|
||||
"std.debug.assert": { reason: "Use bun.assert instead", limit: 25 },
|
||||
"std.debug.assert": { reason: "Use bun.assert instead", limit: 26 },
|
||||
"std.debug.dumpStackTrace": { reason: "Use bun.handleErrorReturnTrace or bun.crash_handler.dumpStackTrace instead" },
|
||||
"std.debug.print": { reason: "Don't let this be committed", limit: 2 },
|
||||
"std.debug.print": { reason: "Don't let this be committed", limit: 0 },
|
||||
"std.mem.indexOfAny(u8": { reason: "Use bun.strings.indexOfAny", limit: 3 },
|
||||
"undefined != ": { reason: "This is by definition Undefined Behavior." },
|
||||
"undefined == ": { reason: "This is by definition Undefined Behavior." },
|
||||
|
||||
Reference in New Issue
Block a user