authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2022-11-30 11:48:06-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-11-30 18:57:37-05:00
log34fa6a1e0437ab7f08a2ccff2aff88aa77aeb037
tree7c8676addbd22e5bd40d5894f995039f3a4bb21d
parentcf7a4de7f1d023dbf8b6a782d68a4628cc0b7264

std.testing: Add expectEqualBytes that outputs hexdumps with diffs highlighted in red

The coloring is controlled by `std.debug.detectTTYConfig` so it will be disabled when appropriate.

1 files changed, 84 insertions(+), 0 deletions(-)

lib/std/testing.zig+84
......@@ -281,6 +281,7 @@ test "expectApproxEqRel" {
281281/// equal, prints diagnostics to stderr to show exactly how they are not equal,
282282/// then returns a test failure error.
283283/// If your inputs are UTF-8 encoded strings, consider calling `expectEqualStrings` instead.
284/// If your inputs are slices of bytes, consider calling `expectEqualBytes` instead.
284285pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const T) !void {
285286 // TODO better printing of the difference
286287 // If the arrays are small enough we could print the whole thing
......@@ -550,6 +551,89 @@ test {
550551 try expectEqualStrings("foo", "foo");
551552}
552553
554/// This function is intended to be used only in tests. When the two slices are not
555/// equal, prints hexdumps of the inputs with the differences highlighted in red to stderr,
556/// then returns a test failure error. The colorized output is optional and controlled
557/// by the return of `std.debug.detectTTYConfig()`.
558pub fn expectEqualBytes(expected: []const u8, actual: []const u8) !void {
559 std.testing.expectEqualSlices(u8, expected, actual) catch |err| {
560 var differ = BytesDiffer{
561 .expected = expected,
562 .actual = actual,
563 .ttyconf = std.debug.detectTTYConfig(),
564 };
565 const stderr = std.io.getStdErr();
566
567 std.debug.print("\n============ expected this output: =============\n\n", .{});
568 differ.write(stderr.writer()) catch {};
569
570 // now reverse expected/actual and print again
571 differ.expected = actual;
572 differ.actual = expected;
573 std.debug.print("\n============= instead found this: ==============\n\n", .{});
574 differ.write(stderr.writer()) catch {};
575 std.debug.print("\n================================================\n\n", .{});
576
577 return err;
578 };
579}
580
581const BytesDiffer = struct {
582 expected: []const u8,
583 actual: []const u8,
584 ttyconf: std.debug.TTY.Config,
585
586 pub fn write(self: BytesDiffer, writer: anytype) !void {
587 var expected_iterator = ChunkIterator{ .bytes = self.expected };
588 while (expected_iterator.next()) |chunk| {
589 // to avoid having to calculate diffs twice per chunk
590 var diffs: std.bit_set.IntegerBitSet(16) = .{ .mask = 0 };
591 for (chunk) |byte, i| {
592 var absolute_byte_index = (expected_iterator.index - chunk.len) + i;
593 const diff = if (absolute_byte_index < self.actual.len) self.actual[absolute_byte_index] != byte else true;
594 if (diff) diffs.set(i);
595 try self.writeByteDiff(writer, "{X:0>2} ", byte, diff);
596 if (i == 7) try writer.writeByte(' ');
597 }
598 try writer.writeByte(' ');
599 if (chunk.len < 16) {
600 var missing_columns = (16 - chunk.len) * 3;
601 if (chunk.len < 8) missing_columns += 1;
602 try writer.writeByteNTimes(' ', missing_columns);
603 }
604 for (chunk) |byte, i| {
605 const byte_to_print = if (std.ascii.isPrint(byte)) byte else '.';
606 try self.writeByteDiff(writer, "{c}", byte_to_print, diffs.isSet(i));
607 }
608 try writer.writeByte('\n');
609 }
610 }
611
612 fn writeByteDiff(self: BytesDiffer, writer: anytype, comptime fmt: []const u8, byte: u8, diff: bool) !void {
613 if (diff) self.ttyconf.setColor(writer, .Red);
614 try writer.print(fmt, .{byte});
615 if (diff) self.ttyconf.setColor(writer, .Reset);
616 }
617
618 const ChunkIterator = struct {
619 bytes: []const u8,
620 index: usize = 0,
621
622 pub fn next(self: *ChunkIterator) ?[]const u8 {
623 if (self.index == self.bytes.len) return null;
624
625 const start_index = self.index;
626 const end_index = @min(self.bytes.len, start_index + 16);
627 self.index = end_index;
628 return self.bytes[start_index..end_index];
629 }
630 };
631};
632
633test {
634 try expectEqualBytes("foo\x00", "foo\x00");
635}
636
553637/// Exhaustively check that allocation failures within `test_fn` are handled without
554638/// introducing memory leaks. If used with the `testing.allocator` as the `backing_allocator`,
555639/// it will also be able to detect double frees, etc (when runtime safety is enabled).