From 8073f7c47327cfd93becfef102198cfa77895176 Mon Sep 17 00:00:00 2001 From: pfg Date: Wed, 16 Jul 2025 16:22:27 -0700 Subject: [PATCH] upd --- cmake/sources/ZigSources.txt | 1 + src/bun.js/test/diff_format.zig | 35 +--- src/bun.js/test/printDiff.zig | 154 ++++++++++++++++++ .../bun/test/printing/diffexample.fixture.ts | 32 ++++ test/js/bun/test/printing/diffexample.test.ts | 52 +++--- 5 files changed, 214 insertions(+), 60 deletions(-) create mode 100644 src/bun.js/test/printDiff.zig create mode 100644 test/js/bun/test/printing/diffexample.fixture.ts diff --git a/cmake/sources/ZigSources.txt b/cmake/sources/ZigSources.txt index a7ce947952..66a8a01e8a 100644 --- a/cmake/sources/ZigSources.txt +++ b/cmake/sources/ZigSources.txt @@ -240,6 +240,7 @@ src/bun.js/test/diff_format.zig src/bun.js/test/expect.zig src/bun.js/test/jest.zig src/bun.js/test/pretty_format.zig +src/bun.js/test/printDiff.zig src/bun.js/test/snapshot.zig src/bun.js/test/test.zig src/bun.js/unbounded_queue.zig diff --git a/src/bun.js/test/diff_format.zig b/src/bun.js/test/diff_format.zig index 51e2a714ed..1b5fe4f69a 100644 --- a/src/bun.js/test/diff_format.zig +++ b/src/bun.js/test/diff_format.zig @@ -107,40 +107,7 @@ fn printDiff(allocator: std.mem.Allocator, not: bool, received_slice: string, ex inline else => |enable_ansi_colors| try writer.print(Output.prettyFmt("Difference:\n\n- Received\n+ Expected\n\n", enable_ansi_colors), .{}), } - const equal_fmt = " {s}"; - const delete_fmt = "- {s}"; - const insert_fmt = "+ {s}"; - - for (diffs.items) |diff| { - const text = diff.text; - if (text.len == 0) continue; - - switch (diff.operation) { - inline else => |operation| { - const fmt: []const u8 = comptime switch (operation) { - .equal => equal_fmt, - .insert => insert_fmt, - .delete => delete_fmt, - }; - - var rest = text; - while (std.mem.indexOfScalar(u8, rest, '\n')) |i| { - const line = rest[0..i]; - switch (Output.enable_ansi_colors) { - inline else => |enable_ansi_colors| try writer.print(Output.prettyFmt(fmt, enable_ansi_colors), .{line}), - } - try writer.print("\n", .{}); - rest = rest[i + 1 ..]; - } - - if (rest.len > 0) { - switch (Output.enable_ansi_colors) { - inline else => |enable_ansi_colors| try writer.print(Output.prettyFmt(fmt, enable_ansi_colors), .{rest}), - } - } - }, - } - } + try @import("printDiff.zig").printDiff(allocator, writer, diffs.items, Output.enable_ansi_colors); } // @sortImports diff --git a/src/bun.js/test/printDiff.zig b/src/bun.js/test/printDiff.zig new file mode 100644 index 0000000000..99b8558181 --- /dev/null +++ b/src/bun.js/test/printDiff.zig @@ -0,0 +1,154 @@ +const colors = struct { + const red = "\x1b[31m"; + const green = "\x1b[32m"; + const red_bg = "\x1b[41m"; + const green_bg = "\x1b[42m"; + const dim = "\x1b[2m"; + const reset = "\x1b[0m"; +}; + +pub fn printDiff(arena: std.mem.Allocator, writer: anytype, segments: []const Diff, enable_ansi_colors: bool) !void { + var line_buffer = std.ArrayList(Diff).init(arena); + defer line_buffer.deinit(); + + var before_line_number: usize = 1; + var after_line_number: usize = 1; + + for (segments) |segment| { + var text_iterator = std.mem.splitScalar(u8, segment.text, '\n'); + + // The first part of a split always belongs to the current line being built. + // It's safe to call next() because even for an empty string, it returns one empty slice. + const first_part = text_iterator.next().?; + if (first_part.len > 0) { + try line_buffer.append(.{ .operation = segment.operation, .text = first_part }); + } + + // Any subsequent parts mean we've crossed a newline. + // Each part (except the very last one in the stream) is a complete line. + while (text_iterator.next()) |part| { + switch (segment.operation) { + .delete => before_line_number += 1, + .insert => after_line_number += 1, + .equal => {}, + } + + // Process the completed line in the buffer. + try printLine(writer, line_buffer.items, enable_ansi_colors); + line_buffer.clearRetainingCapacity(); + + // The new part becomes the start of the next line. + if (part.len > 0) { + try line_buffer.append(.{ .operation = segment.operation, .text = part }); + } + } + } + + // After the loop, print the last line + try printLine(writer, line_buffer.items, enable_ansi_colors); +} + +const ModifiedMode = enum { + added, + removed, + modified, + equal, +}; +fn getMode(line_segments: []const Diff) ModifiedMode { + var has_inserts = false; + var has_deletes = false; + var has_equal = false; + for (line_segments) |segment| { + switch (segment.operation) { + .insert => has_inserts = true, + .delete => has_deletes = true, + .equal => has_equal = true, + } + } + + if (has_inserts and !has_deletes and !has_equal) return .added; + if (has_deletes and !has_inserts and !has_equal) return .removed; + if (has_equal and !has_inserts and !has_deletes) return .equal; + return .modified; +} + +// Helper function to format and print a single logical line. +fn printLine(writer: anytype, line_segments: []const Diff, enable_ansi_colors: bool) !void { + const mode = getMode(line_segments); + const insert_line = switch (enable_ansi_colors) { + true => colors.green ++ "+" ++ colors.reset ++ " ", + false => "+ ", + }; + const delete_line = switch (enable_ansi_colors) { + true => colors.red ++ "-" ++ colors.reset ++ " ", + false => "- ", + }; + const red_bg = switch (enable_ansi_colors) { + true => colors.red_bg, + false => "", + }; + const green_bg = switch (enable_ansi_colors) { + true => colors.green_bg, + false => "", + }; + const dim = switch (enable_ansi_colors) { + true => colors.dim, + false => "", + }; + const reset = switch (enable_ansi_colors) { + true => colors.reset, + false => "", + }; + + switch (mode) { + .modified => { + // Modified line: print delete line, then insert line + try writer.writeAll(delete_line); + for (line_segments) |s| switch (s.operation) { + .delete => try writer.print("{s}{s}{s}", .{ red_bg, s.text, reset }), + .equal => try writer.print("{s}{s}{s}", .{ dim, s.text, reset }), + .insert => {}, // Skip inserts on the delete line + }; + try writer.writeAll("\n"); + + try writer.writeAll(insert_line); + for (line_segments) |s| switch (s.operation) { + .insert => try writer.print("{s}{s}{s}", .{ green_bg, s.text, reset }), + .equal => try writer.print("{s}{s}{s}", .{ dim, s.text, reset }), + .delete => {}, // Skip deletes on the insert line + }; + try writer.writeAll("\n"); + }, + .added => { + // Line added + try writer.writeAll(insert_line); + try writer.writeAll(green_bg); + for (line_segments) |s| try writer.writeAll(s.text); + try writer.writeAll(reset); + try writer.writeAll("\n"); + }, + .removed => { + // Line removed + try writer.writeAll(delete_line); + try writer.writeAll(red_bg); + for (line_segments) |s| try writer.writeAll(s.text); + try writer.writeAll(reset); + try writer.writeAll("\n"); + }, + .equal => { + // Equal-only line + try writer.writeAll(" "); + try writer.writeAll(dim); + for (line_segments) |s| try writer.writeAll(s.text); + try writer.writeAll(reset); + try writer.writeAll("\n"); + }, + } +} + +// @sortImports + +const std = @import("std"); + +const DiffMatchPatch = @import("../../deps/diffz/DiffMatchPatch.zig"); +const Diff = DiffMatchPatch.Diff; diff --git a/test/js/bun/test/printing/diffexample.fixture.ts b/test/js/bun/test/printing/diffexample.fixture.ts new file mode 100644 index 0000000000..119571ba35 --- /dev/null +++ b/test/js/bun/test/printing/diffexample.fixture.ts @@ -0,0 +1,32 @@ +import { test, expect } from "bun:test"; + +test("example 1", () => { + expect("a\nb\nc\n d\ne").toEqual("a\nd\nc\nd\ne"); +}); +test("example 2", () => { + expect({ + object1: "a", + object2: "b", + object3: "c\nd\ne", + }).toEqual({ + object1: "a", + object2: " b", + object3: "c\nd", + }); +}); + +test("example 3 - very long string with few changes", () => { + // Create a 1000 line string with only a few differences + const lines = Array.from({ length: 1000 }, (_, i) => `line ${i + 1}`); + const originalString = lines.join("\n"); + + // Create expected string with only a few changes + const expectedLines = [...lines]; + expectedLines[499] = "line 500 - CHANGED"; // Change line 500 + expectedLines[750] = "line 751 - MODIFIED"; // Change line 751 + expectedLines[900] = "line 901 - DIFFERENT"; // Change line 901 + expectedLines.splice(100, 0, "line 101 - INSERTED"); + const expectedString = expectedLines.join("\n"); + + expect(originalString).toEqual(expectedString); +}); diff --git a/test/js/bun/test/printing/diffexample.test.ts b/test/js/bun/test/printing/diffexample.test.ts index 119571ba35..8ce19e1309 100644 --- a/test/js/bun/test/printing/diffexample.test.ts +++ b/test/js/bun/test/printing/diffexample.test.ts @@ -1,32 +1,32 @@ import { test, expect } from "bun:test"; +import { bunEnv, bunExe } from "harness"; -test("example 1", () => { - expect("a\nb\nc\n d\ne").toEqual("a\nd\nc\nd\ne"); -}); -test("example 2", () => { - expect({ - object1: "a", - object2: "b", - object3: "c\nd\ne", - }).toEqual({ - object1: "a", - object2: " b", - object3: "c\nd", +test("color", async () => { + const spawn = Bun.spawn({ + cmd: [bunExe(), import.meta.resolve("diffexample.fixture.ts")], + stdio: ["inherit", "pipe", "pipe"], + env: { + ...bunEnv, + FORCE_COLOR: "1", + }, }); + await spawn.exited; + expect(await spawn.stderr.text()).toBe(""); + expect(spawn.exitCode).toBe(0); + expect(await spawn.stdout.text()).toMatchInlineSnapshot(); }); -test("example 3 - very long string with few changes", () => { - // Create a 1000 line string with only a few differences - const lines = Array.from({ length: 1000 }, (_, i) => `line ${i + 1}`); - const originalString = lines.join("\n"); - - // Create expected string with only a few changes - const expectedLines = [...lines]; - expectedLines[499] = "line 500 - CHANGED"; // Change line 500 - expectedLines[750] = "line 751 - MODIFIED"; // Change line 751 - expectedLines[900] = "line 901 - DIFFERENT"; // Change line 901 - expectedLines.splice(100, 0, "line 101 - INSERTED"); - const expectedString = expectedLines.join("\n"); - - expect(originalString).toEqual(expectedString); +test("no color", async () => { + const spawn = Bun.spawn({ + cmd: [bunExe(), import.meta.resolve("diffexample.fixture.ts")], + stdio: ["inherit", "pipe", "pipe"], + env: { + ...bunEnv, + FORCE_COLOR: "0", + }, + }); + await spawn.exited; + expect(await spawn.stderr.text()).toBe(""); + expect(spawn.exitCode).toBe(0); + expect(await spawn.stdout.text()).toMatchInlineSnapshot(); });