| ... | @@ -339,7 +339,8 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const | ... | @@ -339,7 +339,8 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const |
| 339 | const actual_window = actual[window_start..@min(actual.len, window_start + max_window_size)]; | 339 | const actual_window = actual[window_start..@min(actual.len, window_start + max_window_size)]; |
| 340 | const actual_truncated = window_start + actual_window.len < actual.len; | 340 | const actual_truncated = window_start + actual_window.len < actual.len; |
| 341 | | 341 | |
| 342 | const ttyconf = std.io.tty.detectConfig(std.io.getStdErr()); | 342 | const stderr = std.io.getStdErr(); |
| | 343 | const ttyconf = std.io.tty.detectConfig(stderr); |
| 343 | var differ = if (T == u8) BytesDiffer{ | 344 | var differ = if (T == u8) BytesDiffer{ |
| 344 | .expected = expected_window, | 345 | .expected = expected_window, |
| 345 | .actual = actual_window, | 346 | .actual = actual_window, |
| ... | @@ -350,7 +351,6 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const | ... | @@ -350,7 +351,6 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const |
| 350 | .actual = actual_window, | 351 | .actual = actual_window, |
| 351 | .ttyconf = ttyconf, | 352 | .ttyconf = ttyconf, |
| 352 | }; | 353 | }; |
| 353 | const stderr = std.io.getStdErr(); | | |
| 354 | | 354 | |
| 355 | // Print indexes as hex for slices of u8 since it's more likely to be binary data where | 355 | // Print indexes as hex for slices of u8 since it's more likely to be binary data where |
| 356 | // that is usually useful. | 356 | // that is usually useful. |
| ... | @@ -432,16 +432,17 @@ const BytesDiffer = struct { | ... | @@ -432,16 +432,17 @@ const BytesDiffer = struct { |
| 432 | ttyconf: std.io.tty.Config, | 432 | ttyconf: std.io.tty.Config, |
| 433 | | 433 | |
| 434 | pub fn write(self: BytesDiffer, writer: anytype) !void { | 434 | pub fn write(self: BytesDiffer, writer: anytype) !void { |
| 435 | var expected_iterator = ChunkIterator{ .bytes = self.expected }; | 435 | var expected_iterator = std.mem.window(u8, self.expected, 16, 16); |
| | 436 | var row: usize = 0; |
| 436 | while (expected_iterator.next()) |chunk| { | 437 | while (expected_iterator.next()) |chunk| { |
| 437 | // to avoid having to calculate diffs twice per chunk | 438 | // to avoid having to calculate diffs twice per chunk |
| 438 | var diffs: std.bit_set.IntegerBitSet(16) = .{ .mask = 0 }; | 439 | var diffs: std.bit_set.IntegerBitSet(16) = .{ .mask = 0 }; |
| 439 | for (chunk, 0..) |byte, i| { | 440 | for (chunk, 0..) |byte, col| { |
| 440 | const absolute_byte_index = (expected_iterator.index - chunk.len) + i; | 441 | const absolute_byte_index = col + row * 16; |
| 441 | const diff = if (absolute_byte_index < self.actual.len) self.actual[absolute_byte_index] != byte else true; | 442 | const diff = if (absolute_byte_index < self.actual.len) self.actual[absolute_byte_index] != byte else true; |
| 442 | if (diff) diffs.set(i); | 443 | if (diff) diffs.set(col); |
| 443 | try self.writeByteDiff(writer, "{X:0>2} ", byte, diff); | 444 | try self.writeDiff(writer, "{X:0>2} ", .{byte}, diff); |
| 444 | if (i == 7) try writer.writeByte(' '); | 445 | if (col == 7) try writer.writeByte(' '); |
| 445 | } | 446 | } |
| 446 | try writer.writeByte(' '); | 447 | try writer.writeByte(' '); |
| 447 | if (chunk.len < 16) { | 448 | if (chunk.len < 16) { |
| ... | @@ -449,33 +450,38 @@ const BytesDiffer = struct { | ... | @@ -449,33 +450,38 @@ const BytesDiffer = struct { |
| 449 | if (chunk.len < 8) missing_columns += 1; | 450 | if (chunk.len < 8) missing_columns += 1; |
| 450 | try writer.writeByteNTimes(' ', missing_columns); | 451 | try writer.writeByteNTimes(' ', missing_columns); |
| 451 | } | 452 | } |
| 452 | for (chunk, 0..) |byte, i| { | 453 | for (chunk, 0..) |byte, col| { |
| 453 | const byte_to_print = if (std.ascii.isPrint(byte)) byte else '.'; | 454 | const diff = diffs.isSet(col); |
| 454 | try self.writeByteDiff(writer, "{c}", byte_to_print, diffs.isSet(i)); | 455 | if (std.ascii.isPrint(byte)) { |
| | 456 | try self.writeDiff(writer, "{c}", .{byte}, diff); |
| | 457 | } else { |
| | 458 | // TODO: remove this `if` when https://github.com/ziglang/zig/issues/7600 is fixed |
| | 459 | if (self.ttyconf == .windows_api) { |
| | 460 | try self.writeDiff(writer, ".", .{}, diff); |
| | 461 | continue; |
| | 462 | } |
| | 463 | |
| | 464 | // Let's print some common control codes as graphical Unicode symbols. |
| | 465 | // We don't want to do this for all control codes because most control codes apart from |
| | 466 | // the ones that Zig has escape sequences for are likely not very useful to print as symbols. |
| | 467 | switch (byte) { |
| | 468 | '\n' => try self.writeDiff(writer, "␊", .{}, diff), |
| | 469 | '\r' => try self.writeDiff(writer, "␍", .{}, diff), |
| | 470 | '\t' => try self.writeDiff(writer, "␉", .{}, diff), |
| | 471 | else => try self.writeDiff(writer, ".", .{}, diff), |
| | 472 | } |
| | 473 | } |
| 455 | } | 474 | } |
| 456 | try writer.writeByte('\n'); | 475 | try writer.writeByte('\n'); |
| | 476 | row += 1; |
| 457 | } | 477 | } |
| 458 | } | 478 | } |
| 459 | | 479 | |
| 460 | fn writeByteDiff(self: BytesDiffer, writer: anytype, comptime fmt: []const u8, byte: u8, diff: bool) !void { | 480 | fn writeDiff(self: BytesDiffer, writer: anytype, comptime fmt: []const u8, args: anytype, diff: bool) !void { |
| 461 | if (diff) try self.ttyconf.setColor(writer, .red); | 481 | if (diff) try self.ttyconf.setColor(writer, .red); |
| 462 | try writer.print(fmt, .{byte}); | 482 | try writer.print(fmt, args); |
| 463 | if (diff) try self.ttyconf.setColor(writer, .reset); | 483 | if (diff) try self.ttyconf.setColor(writer, .reset); |
| 464 | } | 484 | } |
| 465 | | | |
| 466 | const ChunkIterator = struct { | | |
| 467 | bytes: []const u8, | | |
| 468 | index: usize = 0, | | |
| 469 | | | |
| 470 | pub fn next(self: *ChunkIterator) ?[]const u8 { | | |
| 471 | if (self.index == self.bytes.len) return null; | | |
| 472 | | | |
| 473 | const start_index = self.index; | | |
| 474 | const end_index = @min(self.bytes.len, start_index + 16); | | |
| 475 | self.index = end_index; | | |
| 476 | return self.bytes[start_index..end_index]; | | |
| 477 | } | | |
| 478 | }; | | |
| 479 | }; | 485 | }; |
| 480 | | 486 | |
| 481 | test { | 487 | test { |
| ... | @@ -926,11 +932,8 @@ fn printIndicatorLine(source: []const u8, indicator_index: usize) void { | ... | @@ -926,11 +932,8 @@ fn printIndicatorLine(source: []const u8, indicator_index: usize) void { |
| 926 | source.len; | 932 | source.len; |
| 927 | | 933 | |
| 928 | printLine(source[line_begin_index..line_end_index]); | 934 | printLine(source[line_begin_index..line_end_index]); |
| 929 | { | 935 | for (line_begin_index..indicator_index) |_| |
| 930 | var i: usize = line_begin_index; | 936 | print(" ", .{}); |
| 931 | while (i < indicator_index) : (i += 1) | | |
| 932 | print(" ", .{}); | | |
| 933 | } | | |
| 934 | if (indicator_index >= source.len) | 937 | if (indicator_index >= source.len) |
| 935 | print("^ (end of string)\n", .{}) | 938 | print("^ (end of string)\n", .{}) |
| 936 | else | 939 | else |
| ... | @@ -947,7 +950,7 @@ fn printWithVisibleNewlines(source: []const u8) void { | ... | @@ -947,7 +950,7 @@ fn printWithVisibleNewlines(source: []const u8) void { |
| 947 | | 950 | |
| 948 | fn printLine(line: []const u8) void { | 951 | fn printLine(line: []const u8) void { |
| 949 | if (line.len != 0) switch (line[line.len - 1]) { | 952 | if (line.len != 0) switch (line[line.len - 1]) { |
| 950 | ' ', '\t' => return print("{s}⏎\n", .{line}), // Carriage return symbol, | 953 | ' ', '\t' => return print("{s}⏎\n", .{line}), // Return symbol |
| 951 | else => {}, | 954 | else => {}, |
| 952 | }; | 955 | }; |
| 953 | print("{s}\n", .{line}); | 956 | print("{s}\n", .{line}); |