| ... | ... | @@ -670,6 +670,252 @@ pub fn expectStringEndsWith(actual: []const u8, expected_ends_with: []const u8) |
| 670 | 670 | return error.TestExpectedEndsWith; |
| 671 | 671 | } |
| 672 | 672 | |
| 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) |
| 685 | pub 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 | |
| 857 | test "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 | |
| 882 | test "expectEqualDeep pointer" { |
| 883 | const a = 1; |
| 884 | const b = 1; |
| 885 | try expectEqualDeep(&a, &b); |
| 886 | } |
| 887 | |
| 888 | test "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 | |
| 673 | 919 | fn printIndicatorLine(source: []const u8, indicator_index: usize) void { |
| 674 | 920 | const line_begin_index = if (std.mem.lastIndexOfScalar(u8, source[0..indicator_index], '\n')) |line_begin| |
| 675 | 921 | line_begin + 1 |