Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions src/bun.js/test/jest.zig
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,61 @@ pub const Tag = enum(u3) {
skipped_because_label,
};
const debug = Output.scoped(.jest, false);

var max_test_id_for_debugger: u32 = 0;

const CurrentFile = struct {
title: string = "",
prefix: string = "",
repeat_info: struct {
count: u32 = 0,
index: u32 = 0,
} = .{},
has_printed_filename: bool = false,

pub fn set(this: *CurrentFile, title: string, prefix: string, repeat_count: u32, repeat_index: u32) void {
if (Output.isAIAgent()) {
this.freeAndClear();
this.title = bun.default_allocator.dupe(u8, title) catch bun.outOfMemory();
this.prefix = bun.default_allocator.dupe(u8, prefix) catch bun.outOfMemory();
this.repeat_info.count = repeat_count;
this.repeat_info.index = repeat_index;
this.has_printed_filename = false;
return;
}

this.has_printed_filename = true;
print(title, prefix, repeat_count, repeat_index);
}

fn freeAndClear(this: *CurrentFile) void {
bun.default_allocator.free(this.title);
bun.default_allocator.free(this.prefix);
}

fn print(title: string, prefix: string, repeat_count: u32, repeat_index: u32) void {
if (repeat_count > 0) {
if (repeat_count > 1) {
Output.prettyErrorln("<r>\n{s}{s}: <d>(run #{d})<r>\n", .{ prefix, title, repeat_index + 1 });
} else {
Output.prettyErrorln("<r>\n{s}{s}:\n", .{ prefix, title });
}
} else {
Output.prettyErrorln("<r>\n{s}{s}:\n", .{ prefix, title });
}

Output.flush();
}

pub fn printIfNeeded(this: *CurrentFile) void {
if (this.has_printed_filename) return;
this.has_printed_filename = true;
print(this.title, this.prefix, this.repeat_info.count, this.repeat_info.index);
}
};

pub const TestRunner = struct {
current_file: CurrentFile = CurrentFile{},
tests: TestRunner.Test.List = .{},
log: *logger.Log,
files: File.List = .{},
Expand Down Expand Up @@ -1325,6 +1378,10 @@ pub const TestRunnerTask = struct {
deduped = true;
} else {
if (is_unhandled and Jest.runner != null) {
if (Output.isAIAgent()) {
Jest.runner.?.current_file.printIfNeeded();
}

Output.prettyErrorln(
\\<r>
\\<b><d>#<r> <red><b>Unhandled error<r><d> between tests<r>
Expand All @@ -1333,7 +1390,12 @@ pub const TestRunnerTask = struct {
, .{});

Output.flush();
} else if (!is_unhandled and Jest.runner != null) {
if (Output.isAIAgent()) {
Jest.runner.?.current_file.printIfNeeded();
}
}

jsc_vm.runErrorHandlerWithDedupe(rejection, jsc_vm.onUnhandledRejectionExceptionList);
if (is_unhandled and Jest.runner != null) {
Output.prettyError("<r><d>-------------------------------<r>\n\n", .{});
Expand Down
126 changes: 70 additions & 56 deletions src/cli/test_command.zig
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ fn fmtStatusTextLine(comptime status: @Type(.enum_literal), comptime emoji_or_co
}

fn writeTestStatusLine(comptime status: @Type(.enum_literal), writer: anytype) void {
// When using AI agents, only print failures
if (Output.isAIAgent() and status != .fail) {
return;
}

if (Output.enable_ansi_colors_stderr)
writer.print(fmtStatusTextLine(status, true), .{}) catch unreachable
else
Expand Down Expand Up @@ -653,51 +658,53 @@ pub const CommandLineReporter = struct {
}

const scopes: []*jest.DescribeScope = scopes_stack.slice();

const display_label = if (label.len > 0) label else "test";

const color_code = comptime if (skip) "<d>" else "";

if (Output.enable_ansi_colors_stderr) {
for (scopes, 0..) |_, i| {
const index = (scopes.len - 1) - i;
const scope = scopes[index];
if (scope.label.len == 0) continue;
writer.writeAll(" ") catch unreachable;

writer.print(comptime Output.prettyFmt("<r>" ++ color_code, true), .{}) catch unreachable;
writer.writeAll(scope.label) catch unreachable;
writer.print(comptime Output.prettyFmt("<d>", true), .{}) catch unreachable;
writer.writeAll(" >") catch unreachable;
}
} else {
for (scopes, 0..) |_, i| {
const index = (scopes.len - 1) - i;
const scope = scopes[index];
if (scope.label.len == 0) continue;
writer.writeAll(" ") catch unreachable;
writer.writeAll(scope.label) catch unreachable;
writer.writeAll(" >") catch unreachable;
// Quieter output when claude code is in use.
if (!Output.isAIAgent() or status == .fail) {
const color_code = comptime if (skip) "<d>" else "";

if (Output.enable_ansi_colors_stderr) {
for (scopes, 0..) |_, i| {
const index = (scopes.len - 1) - i;
const scope = scopes[index];
if (scope.label.len == 0) continue;
writer.writeAll(" ") catch unreachable;

writer.print(comptime Output.prettyFmt("<r>" ++ color_code, true), .{}) catch unreachable;
writer.writeAll(scope.label) catch unreachable;
writer.print(comptime Output.prettyFmt("<d>", true), .{}) catch unreachable;
writer.writeAll(" >") catch unreachable;
}
} else {
for (scopes, 0..) |_, i| {
const index = (scopes.len - 1) - i;
const scope = scopes[index];
if (scope.label.len == 0) continue;
writer.writeAll(" ") catch unreachable;
writer.writeAll(scope.label) catch unreachable;
writer.writeAll(" >") catch unreachable;
}
}
}

const line_color_code = if (comptime skip) "<r><d>" else "<r><b>";
const line_color_code = if (comptime skip) "<r><d>" else "<r><b>";

if (Output.enable_ansi_colors_stderr)
writer.print(comptime Output.prettyFmt(line_color_code ++ " {s}<r>", true), .{display_label}) catch unreachable
else
writer.print(comptime Output.prettyFmt(" {s}", false), .{display_label}) catch unreachable;
if (Output.enable_ansi_colors_stderr)
writer.print(comptime Output.prettyFmt(line_color_code ++ " {s}<r>", true), .{display_label}) catch unreachable
else
writer.print(comptime Output.prettyFmt(" {s}", false), .{display_label}) catch unreachable;

if (elapsed_ns > (std.time.ns_per_us * 10)) {
writer.print(" {any}", .{
Output.ElapsedFormatter{
.colors = Output.enable_ansi_colors_stderr,
.duration_ns = elapsed_ns,
},
}) catch unreachable;
}
if (elapsed_ns > (std.time.ns_per_us * 10)) {
writer.print(" {any}", .{
Output.ElapsedFormatter{
.colors = Output.enable_ansi_colors_stderr,
.duration_ns = elapsed_ns,
},
}) catch unreachable;
}

writer.writeAll("\n") catch unreachable;
writer.writeAll("\n") catch unreachable;
}

if (file_reporter) |reporter| {
switch (reporter) {
Expand Down Expand Up @@ -844,6 +851,8 @@ pub const CommandLineReporter = struct {
defer Output.flush();
var this: *CommandLineReporter = @fieldParentPtr("callback", cb);

this.jest.current_file.printIfNeeded();

// when the tests fail, we want to repeat the failures at the end
// so that you can see them better when there are lots of tests that ran
const initial_length = this.failures_to_repeat_buf.items.len;
Expand Down Expand Up @@ -1530,7 +1539,7 @@ pub const TestCommand = struct {
const write_snapshots_success = try jest.Jest.runner.?.snapshots.writeInlineSnapshots();
try jest.Jest.runner.?.snapshots.writeSnapshotFile();
var coverage_options = ctx.test_options.coverage;
if (reporter.summary().pass > 20) {
if (reporter.summary().pass > 20 and !Output.isAIAgent()) {
if (reporter.summary().skip > 0) {
Output.prettyError("\n<r><d>{d} tests skipped:<r>\n", .{reporter.summary().skip});
Output.flush();
Expand Down Expand Up @@ -1571,16 +1580,24 @@ pub const TestCommand = struct {
if (test_files.len == 0) {
failed_to_find_any_tests = true;

if (ctx.positionals.len == 0) {
Output.prettyErrorln(
\\<yellow>No tests found!<r>
\\Tests need ".test", "_test_", ".spec" or "_spec_" in the filename <d>(ex: "MyApp.test.ts")<r>
\\
, .{});
// "bun test" - positionals[0] == "test"
// Therefore positionals starts at [1].
if (ctx.positionals.len < 2) {
if (Output.isAIAgent()) {
// Be very clear to ai.
Output.errGeneric("0 test files matching **{{.test,.spec,_test_,_spec_}}.{{js,ts,jsx,tsx}} in --cwd={}", .{bun.fmt.quote(bun.fs.FileSystem.instance.top_level_dir)});
} else {
// Be friendlier to humans.
Output.prettyErrorln(
\\<yellow>No tests found!<r>
\\
\\Tests need ".test", "_test_", ".spec" or "_spec_" in the filename <d>(ex: "MyApp.test.ts")<r>
\\
, .{});
}
} else {
Output.prettyErrorln("<yellow>The following filters did not match any test files:<r>", .{});
var has_file_like: ?usize = null;
Output.prettyError(" ", .{});
for (ctx.positionals[1..], 1..) |filter, i| {
Output.prettyError(" {s}", .{filter});

Expand Down Expand Up @@ -1611,10 +1628,12 @@ pub const TestCommand = struct {
, .{ ctx.positionals[i], ctx.positionals[i] });
}
}
Output.prettyError(
\\
\\Learn more about the test runner: <magenta>https://bun.com/docs/cli/test<r>
, .{});
if (!Output.isAIAgent()) {
Output.prettyError(
\\
\\Learn more about bun test: <magenta>https://bun.com/docs/cli/test<r>
, .{});
}
} else {
Output.prettyError("\n", .{});

Expand Down Expand Up @@ -1841,12 +1860,7 @@ pub const TestCommand = struct {
vm.onUnhandledRejection = jest.TestRunnerTask.onUnhandledRejection;

while (repeat_index < repeat_count) : (repeat_index += 1) {
if (repeat_count > 1) {
Output.prettyErrorln("<r>\n{s}{s}: <d>(run #{d})<r>\n", .{ file_prefix, file_title, repeat_index + 1 });
} else {
Output.prettyErrorln("<r>\n{s}{s}:\n", .{ file_prefix, file_title });
}
Output.flush();
reporter.jest.current_file.set(file_title, file_prefix, repeat_count, repeat_index);

var promise = try vm.loadEntryPointForTestRunner(file_path);
reporter.summary().files += 1;
Expand Down
53 changes: 52 additions & 1 deletion src/output.zig
Original file line number Diff line number Diff line change
Expand Up @@ -457,11 +457,62 @@ pub inline fn isEmojiEnabled() bool {

pub fn isGithubAction() bool {
if (bun.getenvZ("GITHUB_ACTIONS")) |value| {
return strings.eqlComptime(value, "true");
return strings.eqlComptime(value, "true") and
// Do not print github annotations for AI agents because that wastes the context window.
!isAIAgent();
}
return false;
}

pub fn isAIAgent() bool {
const get_is_agent = struct {
var value = false;
fn evaluate() bool {
if (bun.getenvZ("IS_CODE_AGENT")) |env| {
return strings.eqlComptime(env, "1");
}

if (isVerbose()) {
return false;
}

// Claude Code.
if (bun.getenvTruthy("CLAUDECODE")) {
return true;
}

// Replit.
if (bun.getenvTruthy("REPL_ID")) {
return true;
}

// TODO: add environment variable for Gemini
// Gemini does not appear to add any environment variables to identify it.

// TODO: add environment variable for Codex
// codex does not appear to add any environment variables to identify it.

// TODO: add environment variable for Cursor Background Agents
// cursor does not appear to add any environment variables to identify it.

return false;
}

fn setValue() void {
value = evaluate();
}

var once = std.once(setValue);

pub fn isEnabled() bool {
once.call();
return value;
}
};

return get_is_agent.isEnabled();
}

pub fn isVerbose() bool {
// Set by Github Actions when a workflow is run using debug mode.
if (bun.getenvZ("RUNNER_DEBUG")) |value| {
Expand Down
Loading
Loading