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