| ... | @@ -670,6 +670,252 @@ pub fn expectStringEndsWith(actual: []const u8, expected_ends_with: []const u8) | ... | @@ -670,6 +670,252 @@ pub fn expectStringEndsWith(actual: []const u8, expected_ends_with: []const u8) |
| 670 | return error.TestExpectedEndsWith; | 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 | fn printIndicatorLine(source: []const u8, indicator_index: usize) void { | 919 | fn printIndicatorLine(source: []const u8, indicator_index: usize) void { |
| 674 | const line_begin_index = if (std.mem.lastIndexOfScalar(u8, source[0..indicator_index], '\n')) |line_begin| | 920 | const line_begin_index = if (std.mem.lastIndexOfScalar(u8, source[0..indicator_index], '\n')) |line_begin| |
| 675 | line_begin + 1 | 921 | line_begin + 1 |