From e632941c520e9346fc706bb12d0434974c3f5a98 Mon Sep 17 00:00:00 2001 From: Ashcon Partovi Date: Wed, 31 May 2023 23:12:04 -0700 Subject: Small improvements to `bun test` (#3071) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Change status icon for skipped tests from "-" to "ยป" * Show file path instead of filename in `bun test` * Emit collapsable logs when running `bun test` in Github Actions https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#grouping-log-lines * Add fallback for test icons when emojis are not available * Only check for GITHUB_ACTIONS when running `bun test` * Emit error annotations when running `bun test` in Github Actions https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message * Remove ANSI output from Github annotation, it doesn't work * Remove outdated code from internal test runner * Add GithubActionFormatter to handle cases where error name or message is already ANSI * Fix formatting of test * Fix #3070 * Implement `bun test --run-todo` By default, `test.todo()` is no longer run, unless `--run-todo` is specified. * Fix test that relies on test.todo() being run * Support vitest-style test options * Disable GITHUB_ACTION in test harness * Add types for TestOptions * Fix bug where test.skip() actually ran * Implement `test.skipIf()` and `describe.skipIf()` * Implement `test.runIf()` * Move DiffFormatter to its own file * Fix bug where Bun.inspect() would emit a Github annotation * Introduce `bun test --only`, rename `--run-todo` to `--todo` * Implement `test.if()`, `describe.if()`, and other test fixes * Remove unwanted files from last commit * Fix last reference to --run-todo * Fix memory issues with printing github actions text * Update bindings.zig * Fix bug with `test.only()` * Remove debug test * Make the github annotations better * Improve .vscode/launch.json * Implement `expect().toBeNil()` * Remove .only() from test * Implement toBeBoolean(), toBeTrue(), toBeFalse() * Add lots of matchers * toBeNil() * toBeBoolean() * toBeTrue() * toBeFalse() * toBeNumber() * toBeInteger() * toBeFinite() * toBePositive() * toBeNegative() * toBeWithin() * toBeSymbol() * toBeFunction() * toBeDate() * toBeString() * toInclude() * toStartWith() * toEndWith() * Fix #3135 * Reduce verbosity of test * Fix snapshot bug --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> --- src/string_immutable.zig | 97 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) (limited to 'src/string_immutable.zig') diff --git a/src/string_immutable.zig b/src/string_immutable.zig index f7e7a3657..25d4fb01a 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -38,7 +38,7 @@ pub fn toUTF16Literal(comptime str: []const u8) []const u16 { }; } -const OptionalUsize = std.meta.Int(.unsigned, @bitSizeOf(usize) - 1); +pub const OptionalUsize = std.meta.Int(.unsigned, @bitSizeOf(usize) - 1); pub fn indexOfAny(self: string, comptime str: anytype) ?OptionalUsize { inline for (str) |a| { if (indexOfChar(self, a)) |i| { @@ -689,6 +689,63 @@ pub fn endsWithAny(self: string, str: string) bool { return false; } +// Formats a string to be safe to output in a Github action. +// - Encodes "\n" as "%0A" to support multi-line strings. +// https://github.com/actions/toolkit/issues/193#issuecomment-605394935 +// - Strips ANSI output as it will appear malformed. +pub fn githubActionWriter(writer: anytype, self: string) !void { + var offset: usize = 0; + const end = @truncate(u32, self.len); + while (offset < end) { + if (indexOfNewlineOrNonASCIIOrANSI(self, @truncate(u32, offset))) |i| { + const byte = self[i]; + if (byte > 0x7F) { + offset += @max(wtf8ByteSequenceLength(byte), 1); + continue; + } + if (i > 0) { + try writer.writeAll(self[offset..i]); + } + var n: usize = 1; + if (byte == '\n') { + try writer.writeAll("%0A"); + } else if (i + 1 < end) { + const next = self[i + 1]; + if (byte == '\r' and next == '\n') { + n += 1; + try writer.writeAll("%0A"); + } else if (byte == '\x1b' and next == '[') { + n += 1; + if (i + 2 < end) { + const remain = self[(i + 2)..@min(i + 5, end)]; + if (indexOfChar(remain, 'm')) |j| { + n += j + 1; + } + } + } + } + offset = i + n; + } else { + try writer.writeAll(self[offset..end]); + break; + } + } +} + +pub const GithubActionFormatter = struct { + text: string, + + pub fn format(this: GithubActionFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try githubActionWriter(writer, this.text); + } +}; + +pub fn githubAction(self: string) strings.GithubActionFormatter { + return GithubActionFormatter{ + .text = self, + }; +} + pub fn quotedWriter(writer: anytype, self: string) !void { var remain = self; if (strings.containsNewlineOrNonASCIIOrQuote(remain)) { @@ -3179,6 +3236,44 @@ pub fn firstNonASCIIWithType(comptime Type: type, slice: Type) ?u32 { return null; } +pub fn indexOfNewlineOrNonASCIIOrANSI(slice_: []const u8, offset: u32) ?u32 { + const slice = slice_[offset..]; + var remaining = slice; + + if (remaining.len == 0) + return null; + + if (comptime Environment.enableSIMD) { + while (remaining.len >= ascii_vector_size) { + const vec: AsciiVector = remaining[0..ascii_vector_size].*; + const cmp = @bitCast(AsciiVectorU1, (vec > max_16_ascii)) | @bitCast(AsciiVectorU1, (vec < min_16_ascii)) | + @bitCast(AsciiVectorU1, vec == @splat(ascii_vector_size, @as(u8, '\r'))) | + @bitCast(AsciiVectorU1, vec == @splat(ascii_vector_size, @as(u8, '\n'))) | + @bitCast(AsciiVectorU1, vec == @splat(ascii_vector_size, @as(u8, '\x1b'))); + + if (@reduce(.Max, cmp) > 0) { + const bitmask = @bitCast(AsciiVectorInt, cmp); + const first = @ctz(bitmask); + + return @as(u32, first) + @intCast(u32, slice.len - remaining.len) + offset; + } + + remaining = remaining[ascii_vector_size..]; + } + + if (comptime Environment.allow_assert) std.debug.assert(remaining.len < ascii_vector_size); + } + + for (remaining) |*char_| { + const char = char_.*; + if (char > 127 or char < 0x20 or char == '\n' or char == '\r' or char == '\x1b') { + return @truncate(u32, (@ptrToInt(char_) - @ptrToInt(slice.ptr))) + offset; + } + } + + return null; +} + pub fn indexOfNewlineOrNonASCII(slice_: []const u8, offset: u32) ?u32 { return indexOfNewlineOrNonASCIICheckStart(slice_, offset, true); } -- cgit v1.2.3