| ... | @@ -281,6 +281,7 @@ test "expectApproxEqRel" { | ... | @@ -281,6 +281,7 @@ test "expectApproxEqRel" { |
| 281 | /// equal, prints diagnostics to stderr to show exactly how they are not equal, | 281 | /// equal, prints diagnostics to stderr to show exactly how they are not equal, |
| 282 | /// then returns a test failure error. | 282 | /// then returns a test failure error. |
| 283 | /// If your inputs are UTF-8 encoded strings, consider calling `expectEqualStrings` instead. | 283 | /// If your inputs are UTF-8 encoded strings, consider calling `expectEqualStrings` instead. |
| | 284 | /// If your inputs are slices of bytes, consider calling `expectEqualBytes` instead. |
| 284 | pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const T) !void { | 285 | pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const T) !void { |
| 285 | // TODO better printing of the difference | 286 | // TODO better printing of the difference |
| 286 | // If the arrays are small enough we could print the whole thing | 287 | // If the arrays are small enough we could print the whole thing |
| ... | @@ -550,6 +551,89 @@ test { | ... | @@ -550,6 +551,89 @@ test { |
| 550 | try expectEqualStrings("foo", "foo"); | 551 | try expectEqualStrings("foo", "foo"); |
| 551 | } | 552 | } |
| 552 | | 553 | |
| | 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()`. |
| | 558 | pub 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 | |
| | 581 | const 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 | |
| | 633 | test { |
| | 634 | try expectEqualBytes("foo\x00", "foo\x00"); |
| | 635 | } |
| | 636 | |
| 553 | /// Exhaustively check that allocation failures within `test_fn` are handled without | 637 | /// Exhaustively check that allocation failures within `test_fn` are handled without |
| 554 | /// introducing memory leaks. If used with the `testing.allocator` as the `backing_allocator`, | 638 | /// introducing memory leaks. If used with the `testing.allocator` as the `backing_allocator`, |
| 555 | /// it will also be able to detect double frees, etc (when runtime safety is enabled). | 639 | /// it will also be able to detect double frees, etc (when runtime safety is enabled). |