mirror of
https://github.com/ziglang/zig.git
synced 2026-01-06 21:43:25 +00:00
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.
This commit is contained in:
parent
cf7a4de7f1
commit
34fa6a1e04
@ -281,6 +281,7 @@ test "expectApproxEqRel" {
|
||||
/// equal, prints diagnostics to stderr to show exactly how they are not equal,
|
||||
/// then returns a test failure error.
|
||||
/// If your inputs are UTF-8 encoded strings, consider calling `expectEqualStrings` instead.
|
||||
/// If your inputs are slices of bytes, consider calling `expectEqualBytes` instead.
|
||||
pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const T) !void {
|
||||
// TODO better printing of the difference
|
||||
// If the arrays are small enough we could print the whole thing
|
||||
@ -550,6 +551,89 @@ test {
|
||||
try expectEqualStrings("foo", "foo");
|
||||
}
|
||||
|
||||
/// This function is intended to be used only in tests. When the two slices are not
|
||||
/// equal, prints hexdumps of the inputs with the differences highlighted in red to stderr,
|
||||
/// then returns a test failure error. The colorized output is optional and controlled
|
||||
/// by the return of `std.debug.detectTTYConfig()`.
|
||||
pub fn expectEqualBytes(expected: []const u8, actual: []const u8) !void {
|
||||
std.testing.expectEqualSlices(u8, expected, actual) catch |err| {
|
||||
var differ = BytesDiffer{
|
||||
.expected = expected,
|
||||
.actual = actual,
|
||||
.ttyconf = std.debug.detectTTYConfig(),
|
||||
};
|
||||
const stderr = std.io.getStdErr();
|
||||
|
||||
std.debug.print("\n============ expected this output: =============\n\n", .{});
|
||||
differ.write(stderr.writer()) catch {};
|
||||
|
||||
// now reverse expected/actual and print again
|
||||
differ.expected = actual;
|
||||
differ.actual = expected;
|
||||
std.debug.print("\n============= instead found this: ==============\n\n", .{});
|
||||
differ.write(stderr.writer()) catch {};
|
||||
std.debug.print("\n================================================\n\n", .{});
|
||||
|
||||
return err;
|
||||
};
|
||||
}
|
||||
|
||||
const BytesDiffer = struct {
|
||||
expected: []const u8,
|
||||
actual: []const u8,
|
||||
ttyconf: std.debug.TTY.Config,
|
||||
|
||||
pub fn write(self: BytesDiffer, writer: anytype) !void {
|
||||
var expected_iterator = ChunkIterator{ .bytes = self.expected };
|
||||
while (expected_iterator.next()) |chunk| {
|
||||
// to avoid having to calculate diffs twice per chunk
|
||||
var diffs: std.bit_set.IntegerBitSet(16) = .{ .mask = 0 };
|
||||
for (chunk) |byte, i| {
|
||||
var absolute_byte_index = (expected_iterator.index - chunk.len) + i;
|
||||
const diff = if (absolute_byte_index < self.actual.len) self.actual[absolute_byte_index] != byte else true;
|
||||
if (diff) diffs.set(i);
|
||||
try self.writeByteDiff(writer, "{X:0>2} ", byte, diff);
|
||||
if (i == 7) try writer.writeByte(' ');
|
||||
}
|
||||
try writer.writeByte(' ');
|
||||
if (chunk.len < 16) {
|
||||
var missing_columns = (16 - chunk.len) * 3;
|
||||
if (chunk.len < 8) missing_columns += 1;
|
||||
try writer.writeByteNTimes(' ', missing_columns);
|
||||
}
|
||||
for (chunk) |byte, i| {
|
||||
const byte_to_print = if (std.ascii.isPrint(byte)) byte else '.';
|
||||
try self.writeByteDiff(writer, "{c}", byte_to_print, diffs.isSet(i));
|
||||
}
|
||||
try writer.writeByte('\n');
|
||||
}
|
||||
}
|
||||
|
||||
fn writeByteDiff(self: BytesDiffer, writer: anytype, comptime fmt: []const u8, byte: u8, diff: bool) !void {
|
||||
if (diff) self.ttyconf.setColor(writer, .Red);
|
||||
try writer.print(fmt, .{byte});
|
||||
if (diff) self.ttyconf.setColor(writer, .Reset);
|
||||
}
|
||||
|
||||
const ChunkIterator = struct {
|
||||
bytes: []const u8,
|
||||
index: usize = 0,
|
||||
|
||||
pub fn next(self: *ChunkIterator) ?[]const u8 {
|
||||
if (self.index == self.bytes.len) return null;
|
||||
|
||||
const start_index = self.index;
|
||||
const end_index = @min(self.bytes.len, start_index + 16);
|
||||
self.index = end_index;
|
||||
return self.bytes[start_index..end_index];
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
test {
|
||||
try expectEqualBytes("foo\x00", "foo\x00");
|
||||
}
|
||||
|
||||
/// Exhaustively check that allocation failures within `test_fn` are handled without
|
||||
/// introducing memory leaks. If used with the `testing.allocator` as the `backing_allocator`,
|
||||
/// it will also be able to detect double frees, etc (when runtime safety is enabled).
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user