This commit is contained in:
pfg
2025-07-16 16:22:27 -07:00
parent c31c507559
commit 8073f7c473
5 changed files with 214 additions and 60 deletions

View File

@@ -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

View File

@@ -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<red>- Received<r>\n<green>+ Expected<r>\n\n", enable_ansi_colors), .{}),
}
const equal_fmt = "<d> {s}<r>";
const delete_fmt = "<red>- {s}<r>";
const insert_fmt = "<green>+ {s}<r>";
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

View File

@@ -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;

View File

@@ -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);
});

View File

@@ -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();
});