authorgravatar for dev@liujiacai.netJiacai Liu <dev@liujiacai.net> 2023-01-30 06:00:14+08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-01-29 22:00:14+00:00
logd8c3c11c6c5288118b3529c6a99810a4c4add9e9
tree63b64a1f15497c0114c1a2e929d144a4dc83b268
parent23b7d28896609e3f01765730599119baf53a56c9
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

std: add expectEqualDeep (#13995)


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

lib/std/testing.zig+246
......@@ -670,6 +670,252 @@ pub fn expectStringEndsWith(actual: []const u8, expected_ends_with: []const u8)
670670 return error.TestExpectedEndsWith;
671671}
672672
673/// This function is intended to be used only in tests. When the two values are not
674/// deeply equal, prints diagnostics to stderr to show exactly how they are not equal,
675/// then returns a test failure error.
676/// `actual` is casted to the type of `expected`.
677///
678/// Deeply equal is defined as follows:
679/// Primitive types are deeply equal if they are equal using `==` operator.
680/// Struct values are deeply equal if their corresponding fields are deeply equal.
681/// Container types(like Array/Slice/Vector) deeply equal when their corresponding elements are deeply equal.
682/// Pointer values are deeply equal if values they point to are deeply equal.
683///
684/// Note: Self-referential structs are not supported (e.g. things like std.SinglyLinkedList)
685pub fn expectEqualDeep(expected: anytype, actual: @TypeOf(expected)) !void {
686 switch (@typeInfo(@TypeOf(actual))) {
687 .NoReturn,
688 .Opaque,
689 .Frame,
690 .AnyFrame,
691 => @compileError("value of type " ++ @typeName(@TypeOf(actual)) ++ " encountered"),
692
693 .Undefined,
694 .Null,
695 .Void,
696 => return,
697
698 .Type => {
699 if (actual != expected) {
700 std.debug.print("expected type {s}, found type {s}\n", .{ @typeName(expected), @typeName(actual) });
701 return error.TestExpectedEqual;
702 }
703 },
704
705 .Bool,
706 .Int,
707 .Float,
708 .ComptimeFloat,
709 .ComptimeInt,
710 .EnumLiteral,
711 .Enum,
712 .Fn,
713 .ErrorSet,
714 => {
715 if (actual != expected) {
716 std.debug.print("expected {}, found {}\n", .{ expected, actual });
717 return error.TestExpectedEqual;
718 }
719 },
720
721 .Pointer => |pointer| {
722 switch (pointer.size) {
723 // We have no idea what is behind those pointers, so the best we can do is `==` check.
724 .C, .Many => {
725 if (actual != expected) {
726 std.debug.print("expected {*}, found {*}\n", .{ expected, actual });
727 return error.TestExpectedEqual;
728 }
729 },
730 .One => {
731 // Length of those pointers are runtime value, so the best we can do is `==` check.
732 switch (@typeInfo(pointer.child)) {
733 .Fn, .Opaque => {
734 if (actual != expected) {
735 std.debug.print("expected {*}, found {*}\n", .{ expected, actual });
736 return error.TestExpectedEqual;
737 }
738 },
739 else => try expectEqualDeep(expected.*, actual.*),
740 }
741 },
742 .Slice => {
743 if (expected.len != actual.len) {
744 std.debug.print("Slice len not the same, expected {d}, found {d}\n", .{ expected.len, actual.len });
745 return error.TestExpectedEqual;
746 }
747 var i: usize = 0;
748 while (i < expected.len) : (i += 1) {
749 expectEqualDeep(expected[i], actual[i]) catch |e| {
750 std.debug.print("index {d} incorrect. expected {any}, found {any}\n", .{
751 i, expected[i], actual[i],
752 });
753 return e;
754 };
755 }
756 },
757 }
758 },
759
760 .Array => |_| {
761 if (expected.len != actual.len) {
762 std.debug.print("Array len not the same, expected {d}, found {d}\n", .{ expected.len, actual.len });
763 return error.TestExpectedEqual;
764 }
765 var i: usize = 0;
766 while (i < expected.len) : (i += 1) {
767 expectEqualDeep(expected[i], actual[i]) catch |e| {
768 std.debug.print("index {d} incorrect. expected {any}, found {any}\n", .{
769 i, expected[i], actual[i],
770 });
771 return e;
772 };
773 }
774 },
775
776 .Vector => |info| {
777 if (info.len != @typeInfo(@TypeOf(actual)).Vector.len) {
778 std.debug.print("Vector len not the same, expected {d}, found {d}\n", .{ info.len, @typeInfo(@TypeOf(actual)).Vector.len });
779 return error.TestExpectedEqual;
780 }
781 var i: usize = 0;
782 while (i < info.len) : (i += 1) {
783 expectEqualDeep(expected[i], actual[i]) catch |e| {
784 std.debug.print("index {d} incorrect. expected {any}, found {any}\n", .{
785 i, expected[i], actual[i],
786 });
787 return e;
788 };
789 }
790 },
791
792 .Struct => |structType| {
793 inline for (structType.fields) |field| {
794 expectEqualDeep(@field(expected, field.name), @field(actual, field.name)) catch |e| {
795 std.debug.print("Field {s} incorrect. expected {any}, found {any}\n", .{ field.name, @field(expected, field.name), @field(actual, field.name) });
796 return e;
797 };
798 }
799 },
800
801 .Union => |union_info| {
802 if (union_info.tag_type == null) {
803 @compileError("Unable to compare untagged union values");
804 }
805
806 const Tag = std.meta.Tag(@TypeOf(expected));
807
808 const expectedTag = @as(Tag, expected);
809 const actualTag = @as(Tag, actual);
810
811 try expectEqual(expectedTag, actualTag);
812
813 // we only reach this loop if the tags are equal
814 switch (expected) {
815 inline else => |val, tag| {
816 try expectEqualDeep(val, @field(actual, @tagName(tag)));
817 },
818 }
819 },
820
821 .Optional => {
822 if (expected) |expected_payload| {
823 if (actual) |actual_payload| {
824 try expectEqualDeep(expected_payload, actual_payload);
825 } else {
826 std.debug.print("expected {any}, found null\n", .{expected_payload});
827 return error.TestExpectedEqual;
828 }
829 } else {
830 if (actual) |actual_payload| {
831 std.debug.print("expected null, found {any}\n", .{actual_payload});
832 return error.TestExpectedEqual;
833 }
834 }
835 },
836
837 .ErrorUnion => {
838 if (expected) |expected_payload| {
839 if (actual) |actual_payload| {
840 try expectEqualDeep(expected_payload, actual_payload);
841 } else |actual_err| {
842 std.debug.print("expected {any}, found {any}\n", .{ expected_payload, actual_err });
843 return error.TestExpectedEqual;
844 }
845 } else |expected_err| {
846 if (actual) |actual_payload| {
847 std.debug.print("expected {any}, found {any}\n", .{ expected_err, actual_payload });
848 return error.TestExpectedEqual;
849 } else |actual_err| {
850 try expectEqualDeep(expected_err, actual_err);
851 }
852 }
853 },
854 }
855}
856
857test "expectEqualDeep primitive type" {
858 try expectEqualDeep(1, 1);
859 try expectEqualDeep(true, true);
860 try expectEqualDeep(1.5, 1.5);
861 try expectEqualDeep(u8, u8);
862 try expectEqualDeep(error.Bad, error.Bad);
863
864 // optional
865 {
866 const foo: ?u32 = 1;
867 const bar: ?u32 = 1;
868 try expectEqualDeep(foo, bar);
869 try expectEqualDeep(?u32, ?u32);
870 }
871 // function type
872 {
873 const fnType = struct {
874 fn foo() void {
875 unreachable;
876 }
877 }.foo;
878 try expectEqualDeep(fnType, fnType);
879 }
880}
881
882test "expectEqualDeep pointer" {
883 const a = 1;
884 const b = 1;
885 try expectEqualDeep(&a, &b);
886}
887
888test "expectEqualDeep composite type" {
889 try expectEqualDeep("abc", "abc");
890 const s1: []const u8 = "abc";
891 const s2 = "abcd";
892 const s3: []const u8 = s2[0..3];
893 try expectEqualDeep(s1, s3);
894
895 const TestStruct = struct { s: []const u8 };
896 try expectEqualDeep(TestStruct{ .s = "abc" }, TestStruct{ .s = "abc" });
897 try expectEqualDeep([_][]const u8{ "a", "b", "c" }, [_][]const u8{ "a", "b", "c" });
898
899 // vector
900 try expectEqualDeep(@splat(4, @as(u32, 4)), @splat(4, @as(u32, 4)));
901
902 // nested array
903 {
904 const a = [2][2]f32{
905 [_]f32{ 1.0, 0.0 },
906 [_]f32{ 0.0, 1.0 },
907 };
908
909 const b = [2][2]f32{
910 [_]f32{ 1.0, 0.0 },
911 [_]f32{ 0.0, 1.0 },
912 };
913
914 try expectEqualDeep(a, b);
915 try expectEqualDeep(&a, &b);
916 }
917}
918
673919fn printIndicatorLine(source: []const u8, indicator_index: usize) void {
674920 const line_begin_index = if (std.mem.lastIndexOfScalar(u8, source[0..indicator_index], '\n')) |line_begin|
675921 line_begin + 1