mirror of
https://github.com/oven-sh/bun
synced 2026-02-12 20:09:04 +00:00
Implement --test-name-pattern (#3998)
* Fix end not being emitted all the time * stuff * Implement -t * Undo js_printer changes * Undo http changes * Update InternalModuleRegistryConstants.h * Delete unrelated test --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
This commit is contained in:
35
src/bun.js/bindings/RegularExpression.cpp
Normal file
35
src/bun.js/bindings/RegularExpression.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#include "root.h"
|
||||
#include "headers-handwritten.h"
|
||||
#include "JavaScriptCore/RegularExpression.h"
|
||||
|
||||
using namespace JSC;
|
||||
using namespace JSC::Yarr;
|
||||
|
||||
extern "C" RegularExpression* Yarr__RegularExpression__init(BunString pattern, uint16_t flags)
|
||||
{
|
||||
return new RegularExpression(Bun::toWTFString(pattern), OptionSet<Flags>(static_cast<Flags>(flags)));
|
||||
}
|
||||
extern "C" void Yarr__RegularExpression__deinit(RegularExpression* re)
|
||||
{
|
||||
delete re;
|
||||
}
|
||||
extern "C" bool Yarr__RegularExpression__isValid(RegularExpression* re)
|
||||
{
|
||||
return re->isValid();
|
||||
}
|
||||
extern "C" int Yarr__RegularExpression__matchedLength(RegularExpression* re)
|
||||
{
|
||||
return re->matchedLength();
|
||||
}
|
||||
extern "C" int Yarr__RegularExpression__searchRev(RegularExpression* re, BunString string)
|
||||
{
|
||||
return re->searchRev(Bun::toWTFString(string));
|
||||
}
|
||||
// extern "C" int Yarr__RegularExpression__match(RegularExpression* re, BunString string, int32_t start, int32_t* matchLength)
|
||||
// {
|
||||
// return re->match(Bun::toWTFString(string), start, matchLength);
|
||||
// }
|
||||
extern "C" int Yarr__RegularExpression__matches(RegularExpression* re, BunString string)
|
||||
{
|
||||
return re->match(Bun::toWTFString(string), 0, 0);
|
||||
}
|
||||
57
src/bun.js/bindings/RegularExpression.zig
Normal file
57
src/bun.js/bindings/RegularExpression.zig
Normal file
@@ -0,0 +1,57 @@
|
||||
const bun = @import("root").bun;
|
||||
|
||||
pub const RegularExpression = opaque {
|
||||
pub const Flags = enum(u16) {
|
||||
none = 0,
|
||||
|
||||
hasIndices = 1 << 0,
|
||||
global = 1 << 1,
|
||||
ignoreCase = 1 << 2,
|
||||
multiline = 1 << 3,
|
||||
dotAll = 1 << 4,
|
||||
unicode = 1 << 5,
|
||||
unicodeSets = 1 << 6,
|
||||
sticky = 1 << 7,
|
||||
};
|
||||
|
||||
extern fn Yarr__RegularExpression__init(pattern: bun.String, flags: u16) *RegularExpression;
|
||||
extern fn Yarr__RegularExpression__deinit(pattern: *RegularExpression) void;
|
||||
extern fn Yarr__RegularExpression__isValid(this: *RegularExpression) bool;
|
||||
extern fn Yarr__RegularExpression__matchedLength(this: *RegularExpression) i32;
|
||||
extern fn Yarr__RegularExpression__searchRev(this: *RegularExpression) i32;
|
||||
extern fn Yarr__RegularExpression__matches(this: *RegularExpression, string: bun.String) i32;
|
||||
|
||||
pub inline fn init(pattern: bun.String, flags: Flags) !*RegularExpression {
|
||||
var regex = Yarr__RegularExpression__init(pattern, @intFromEnum(flags));
|
||||
if (!regex.isValid()) {
|
||||
regex.deinit();
|
||||
return error.InvalidRegex;
|
||||
}
|
||||
return regex;
|
||||
}
|
||||
|
||||
pub inline fn isValid(this: *RegularExpression) bool {
|
||||
return Yarr__RegularExpression__isValid(this);
|
||||
}
|
||||
|
||||
// Reserving `match` for a full match result.
|
||||
// pub inline fn match(this: *RegularExpression, str: bun.String, startFrom: i32) MatchResult {
|
||||
// }
|
||||
|
||||
// Simple boolean matcher
|
||||
pub inline fn matches(this: *RegularExpression, str: bun.String) bool {
|
||||
return Yarr__RegularExpression__matches(this, str) > 0;
|
||||
}
|
||||
|
||||
pub inline fn searchRev(this: *RegularExpression, str: bun.String) i32 {
|
||||
return Yarr__RegularExpression__searchRev(this, str);
|
||||
}
|
||||
|
||||
pub inline fn matchedLength(this: *RegularExpression) i32 {
|
||||
return Yarr__RegularExpression__matchedLength(this);
|
||||
}
|
||||
|
||||
pub inline fn deinit(this: *RegularExpression) void {
|
||||
Yarr__RegularExpression__deinit(this);
|
||||
}
|
||||
};
|
||||
@@ -34,6 +34,7 @@ const FeatureFlags = @import("root").bun.FeatureFlags;
|
||||
const ArrayBuffer = @import("../base.zig").ArrayBuffer;
|
||||
const Properties = @import("../base.zig").Properties;
|
||||
const getAllocator = @import("../base.zig").getAllocator;
|
||||
const RegularExpression = bun.RegularExpression;
|
||||
|
||||
const ZigString = JSC.ZigString;
|
||||
const JSInternalPromise = JSC.JSInternalPromise;
|
||||
@@ -96,6 +97,10 @@ pub const TestRunner = struct {
|
||||
afterAll: std.ArrayListUnmanaged(JSC.JSValue) = .{},
|
||||
} = .{},
|
||||
|
||||
// Used for --test-name-pattern to reduce allocations
|
||||
filter_regex: ?*RegularExpression,
|
||||
filter_buffer: MutableString,
|
||||
|
||||
pub const Drainer = JSC.AnyTask.New(TestRunner, drain);
|
||||
|
||||
pub fn onTestTimeout(timer: *bun.uws.Timer) callconv(.C) void {
|
||||
@@ -1441,6 +1446,17 @@ pub const Result = union(TestRunner.Test.Status) {
|
||||
}
|
||||
};
|
||||
|
||||
fn appendParentLabel(
|
||||
buffer: *bun.MutableString,
|
||||
parent: *DescribeScope,
|
||||
) !void {
|
||||
if (parent.parent) |par| {
|
||||
try appendParentLabel(buffer, par);
|
||||
}
|
||||
try buffer.append(parent.label);
|
||||
try buffer.append(" ");
|
||||
}
|
||||
|
||||
inline fn createScope(
|
||||
globalThis: *JSGlobalObject,
|
||||
callframe: *CallFrame,
|
||||
@@ -1516,11 +1532,26 @@ inline fn createScope(
|
||||
return .zero;
|
||||
}
|
||||
|
||||
const is_skip = tag == .skip or
|
||||
var is_skip = tag == .skip or
|
||||
(tag == .todo and (function == .zero or !Jest.runner.?.run_todo)) or
|
||||
(tag != .only and Jest.runner.?.only and parent.tag != .only);
|
||||
|
||||
var tag_to_use = tag;
|
||||
if (is_test) {
|
||||
if (!is_skip) {
|
||||
if (Jest.runner.?.filter_regex) |regex| {
|
||||
var buffer: bun.MutableString = Jest.runner.?.filter_buffer;
|
||||
buffer.reset();
|
||||
appendParentLabel(&buffer, parent) catch @panic("Bun ran out of memory while filtering tests");
|
||||
buffer.append(label) catch unreachable;
|
||||
var str = bun.String.fromBytes(buffer.toOwnedSliceLeaky());
|
||||
is_skip = !regex.matches(str);
|
||||
if (is_skip) {
|
||||
tag_to_use = .skip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_skip) {
|
||||
parent.skip_count += 1;
|
||||
function.unprotect();
|
||||
@@ -1531,7 +1562,7 @@ inline fn createScope(
|
||||
parent.tests.append(allocator, TestScope{
|
||||
.label = label,
|
||||
.parent = parent,
|
||||
.tag = tag,
|
||||
.tag = tag_to_use,
|
||||
.callback = if (is_skip) .zero else function,
|
||||
.timeout_millis = timeout_ms,
|
||||
}) catch unreachable;
|
||||
|
||||
0
src/bun.js/test/test.zig
Normal file
0
src/bun.js/test/test.zig
Normal file
@@ -1571,3 +1571,5 @@ pub const WTF = struct {
|
||||
pub const ArenaAllocator = @import("./ArenaAllocator.zig").ArenaAllocator;
|
||||
|
||||
pub const Wyhash = @import("./wyhash.zig").Wyhash;
|
||||
|
||||
pub const RegularExpression = @import("./bun.js/bindings/RegularExpression.zig").RegularExpression;
|
||||
|
||||
23
src/cli.zig
23
src/cli.zig
@@ -20,6 +20,7 @@ const json_parser = bun.JSON;
|
||||
const js_printer = bun.js_printer;
|
||||
const js_ast = bun.JSAst;
|
||||
const linker = @import("linker.zig");
|
||||
const RegularExpression = bun.RegularExpression;
|
||||
|
||||
const sync = @import("./sync.zig");
|
||||
const Api = @import("api/schema.zig").Api;
|
||||
@@ -219,6 +220,7 @@ pub const Arguments = struct {
|
||||
clap.parseParam("--only Only run tests that are marked with \"test.only()\"") catch unreachable,
|
||||
clap.parseParam("--todo Include tests that are marked with \"test.todo()\"") catch unreachable,
|
||||
clap.parseParam("--bail <NUMBER>? Exit the test suite after <NUMBER> failures. If you do not specify a number, it defaults to 1.") catch unreachable,
|
||||
clap.parseParam("-t, --test-name-pattern <STR> Run only tests with a name that matches the given regex.") catch unreachable,
|
||||
};
|
||||
|
||||
const build_params_public = public_params ++ build_only_params;
|
||||
@@ -388,12 +390,12 @@ pub const Arguments = struct {
|
||||
if (args.option("--bail")) |bail| {
|
||||
if (bail.len > 0) {
|
||||
ctx.test_options.bail = std.fmt.parseInt(u32, bail, 10) catch |e| {
|
||||
Output.prettyErrorln("--bail expects a number: {s}", .{@errorName(e)});
|
||||
Output.prettyErrorln("<r><red>error<r>: --bail expects a number: {s}", .{@errorName(e)});
|
||||
Global.exit(1);
|
||||
};
|
||||
|
||||
if (ctx.test_options.bail == 0) {
|
||||
Output.prettyErrorln("--bail expects a number greater than 0", .{});
|
||||
Output.prettyErrorln("<r><red>error<r>: --bail expects a number greater than 0", .{});
|
||||
Global.exit(1);
|
||||
}
|
||||
} else {
|
||||
@@ -403,11 +405,25 @@ pub const Arguments = struct {
|
||||
if (args.option("--rerun-each")) |repeat_count| {
|
||||
if (repeat_count.len > 0) {
|
||||
ctx.test_options.repeat_count = std.fmt.parseInt(u32, repeat_count, 10) catch |e| {
|
||||
Output.prettyErrorln("--rerun-each expects a number: {s}", .{@errorName(e)});
|
||||
Output.prettyErrorln("<r><red>error<r>: --rerun-each expects a number: {s}", .{@errorName(e)});
|
||||
Global.exit(1);
|
||||
};
|
||||
}
|
||||
}
|
||||
if (args.option("--test-name-pattern")) |namePattern| {
|
||||
const regex = RegularExpression.init(bun.String.fromBytes(namePattern), RegularExpression.Flags.none) catch {
|
||||
Output.prettyErrorln(
|
||||
"<r><red>error<r>: --test-name-pattern expects a valid regular expression but received {}",
|
||||
.{
|
||||
strings.QuotedFormatter{
|
||||
.text = namePattern,
|
||||
},
|
||||
},
|
||||
);
|
||||
Global.exit(1);
|
||||
};
|
||||
ctx.test_options.test_filter_regex = regex;
|
||||
}
|
||||
ctx.test_options.update_snapshots = args.flag("--update-snapshots");
|
||||
ctx.test_options.run_todo = args.flag("--todo");
|
||||
ctx.test_options.only = args.flag("--only");
|
||||
@@ -949,6 +965,7 @@ pub const Command = struct {
|
||||
run_todo: bool = false,
|
||||
only: bool = false,
|
||||
bail: u32 = 0,
|
||||
test_filter_regex: ?*RegularExpression = null,
|
||||
};
|
||||
|
||||
pub const Context = struct {
|
||||
|
||||
@@ -457,6 +457,8 @@ pub const TestCommand = struct {
|
||||
.run_todo = ctx.test_options.run_todo,
|
||||
.only = ctx.test_options.only,
|
||||
.bail = ctx.test_options.bail,
|
||||
.filter_regex = ctx.test_options.test_filter_regex,
|
||||
.filter_buffer = bun.MutableString.init(ctx.allocator, 0) catch unreachable,
|
||||
.snapshots = Snapshots{
|
||||
.allocator = ctx.allocator,
|
||||
.update_snapshots = ctx.test_options.update_snapshots,
|
||||
|
||||
@@ -228,7 +228,7 @@ pub const MutableString = struct {
|
||||
}
|
||||
|
||||
pub fn toOwnedSlice(self: *MutableString) string {
|
||||
return self.list.toOwnedSlice(self.allocator) catch @panic("TODO");
|
||||
return self.list.toOwnedSlice(self.allocator) catch @panic("Allocation Error"); // TODO
|
||||
}
|
||||
|
||||
pub fn toOwnedSliceLeaky(self: *MutableString) []u8 {
|
||||
@@ -255,7 +255,7 @@ pub const MutableString = struct {
|
||||
|
||||
pub fn toOwnedSliceLength(self: *MutableString, length: usize) string {
|
||||
self.list.shrinkAndFree(self.allocator, length);
|
||||
return self.list.toOwnedSlice(self.allocator) catch @panic("TODO");
|
||||
return self.list.toOwnedSlice(self.allocator) catch @panic("Allocation Error"); // TODO
|
||||
}
|
||||
|
||||
// pub fn deleteAt(self: *MutableString, i: usize) {
|
||||
|
||||
Reference in New Issue
Block a user