authorgravatar for carl@astholm.seCarl Åstholm <carl@astholm.se> 2023-10-07 21:53:10+02:00
committergravatar for carl@astholm.seCarl Åstholm <carl@astholm.se> 2024-01-03 21:20:48+01:00
loge5994f5f573a51de4e604824355cac28b5ab4eba
tree750920f64a1c75bf07a521d1ad820c6b8be37946
parent9a56228c2b90d94d01bb76784c77fdec5710cf0a

Update `std.testing.expectEqual` and friends to use peer type resolution

This commit changes the type of the second parameter to `anytype`, which should make it easier to pass literals to these functions. This change shouldn't *silently* break existing code (the assertions themselves should retain the same behavior as before) but it may result in some new compile errors when struct/union/array literals or builtins like `@bitCast` are used for the second argument. These compile errors can be fixed by explicitly coercing these expressions to the correct type using `@as`.

1 files changed, 27 insertions(+), 9 deletions(-)

lib/std/testing.zig+27-9
...@@ -52,8 +52,13 @@ pub fn expectError(expected_error: anyerror, actual_error_union: anytype) !void...@@ -52,8 +52,13 @@ pub fn expectError(expected_error: anyerror, actual_error_union: anytype) !void
52/// This function is intended to be used only in tests. When the two values are not52/// This function is intended to be used only in tests. When the two values are not
53/// equal, prints diagnostics to stderr to show exactly how they are not equal,53/// equal, prints diagnostics to stderr to show exactly how they are not equal,
54/// then returns a test failure error.54/// then returns a test failure error.
55/// `actual` is casted to the type of `expected`.55/// `actual` and `expected` are coerced to a common type using peer type resolution.
56pub fn expectEqual(expected: anytype, actual: @TypeOf(expected)) !void {56pub inline fn expectEqual(expected: anytype, actual: anytype) !void {
57 const T = @TypeOf(expected, actual);
58 return expectEqualInner(T, expected, actual);
59}
60
61fn expectEqualInner(comptime T: type, expected: T, actual: T) !void {
57 switch (@typeInfo(@TypeOf(actual))) {62 switch (@typeInfo(@TypeOf(actual))) {
58 .NoReturn,63 .NoReturn,
59 .Opaque,64 .Opaque,
...@@ -224,9 +229,13 @@ pub fn expectFmt(expected: []const u8, comptime template: []const u8, args: anyt...@@ -224,9 +229,13 @@ pub fn expectFmt(expected: []const u8, comptime template: []const u8, args: anyt
224/// to show exactly how they are not equal, then returns a test failure error.229/// to show exactly how they are not equal, then returns a test failure error.
225/// See `math.approxEqAbs` for more information on the tolerance parameter.230/// See `math.approxEqAbs` for more information on the tolerance parameter.
226/// The types must be floating-point.231/// The types must be floating-point.
227pub fn expectApproxEqAbs(expected: anytype, actual: @TypeOf(expected), tolerance: @TypeOf(expected)) !void {232/// `actual` and `expected` are coerced to a common type using peer type resolution.
228 const T = @TypeOf(expected);233pub inline fn expectApproxEqAbs(expected: anytype, actual: anytype, tolerance: anytype) !void {
234 const T = @TypeOf(expected, actual, tolerance);
235 return expectApproxEqAbsInner(T, expected, actual, tolerance);
236}
229237
238fn expectApproxEqAbsInner(comptime T: type, expected: T, actual: T, tolerance: T) !void {
230 switch (@typeInfo(T)) {239 switch (@typeInfo(T)) {
231 .Float => if (!math.approxEqAbs(T, expected, actual, tolerance)) {240 .Float => if (!math.approxEqAbs(T, expected, actual, tolerance)) {
232 print("actual {}, not within absolute tolerance {} of expected {}\n", .{ actual, tolerance, expected });241 print("actual {}, not within absolute tolerance {} of expected {}\n", .{ actual, tolerance, expected });
...@@ -256,9 +265,13 @@ test "expectApproxEqAbs" {...@@ -256,9 +265,13 @@ test "expectApproxEqAbs" {
256/// to show exactly how they are not equal, then returns a test failure error.265/// to show exactly how they are not equal, then returns a test failure error.
257/// See `math.approxEqRel` for more information on the tolerance parameter.266/// See `math.approxEqRel` for more information on the tolerance parameter.
258/// The types must be floating-point.267/// The types must be floating-point.
259pub fn expectApproxEqRel(expected: anytype, actual: @TypeOf(expected), tolerance: @TypeOf(expected)) !void {268/// `actual` and `expected` are coerced to a common type using peer type resolution.
260 const T = @TypeOf(expected);269pub inline fn expectApproxEqRel(expected: anytype, actual: anytype, tolerance: anytype) !void {
270 const T = @TypeOf(expected, actual, tolerance);
271 return expectApproxEqRelInner(T, expected, actual, tolerance);
272}
261273
274fn expectApproxEqRelInner(comptime T: type, expected: T, actual: T, tolerance: T) !void {
262 switch (@typeInfo(T)) {275 switch (@typeInfo(T)) {
263 .Float => if (!math.approxEqRel(T, expected, actual, tolerance)) {276 .Float => if (!math.approxEqRel(T, expected, actual, tolerance)) {
264 print("actual {}, not within relative tolerance {} of expected {}\n", .{ actual, tolerance, expected });277 print("actual {}, not within relative tolerance {} of expected {}\n", .{ actual, tolerance, expected });
...@@ -653,17 +666,22 @@ pub fn expectStringEndsWith(actual: []const u8, expected_ends_with: []const u8)...@@ -653,17 +666,22 @@ pub fn expectStringEndsWith(actual: []const u8, expected_ends_with: []const u8)
653/// This function is intended to be used only in tests. When the two values are not666/// This function is intended to be used only in tests. When the two values are not
654/// deeply equal, prints diagnostics to stderr to show exactly how they are not equal,667/// deeply equal, prints diagnostics to stderr to show exactly how they are not equal,
655/// then returns a test failure error.668/// then returns a test failure error.
656/// `actual` is casted to the type of `expected`.669/// `actual` and `expected` are coerced to a common type using peer type resolution.
657///670///
658/// Deeply equal is defined as follows:671/// Deeply equal is defined as follows:
659/// Primitive types are deeply equal if they are equal using `==` operator.672/// Primitive types are deeply equal if they are equal using `==` operator.
660/// Struct values are deeply equal if their corresponding fields are deeply equal.673/// Struct values are deeply equal if their corresponding fields are deeply equal.
661/// Container types(like Array/Slice/Vector) deeply equal when their corresponding elements are deeply equal.674/// Container types(like Array/Slice/Vector) deeply equal when their corresponding elements are deeply equal.
662/// Pointer values are deeply equal if values they point to are deeply equal.675/// Pointer values are deeply equal if values they point to are deeply equal.
663///676///
664/// Note: Self-referential structs are supported (e.g. things like std.SinglyLinkedList)677/// Note: Self-referential structs are supported (e.g. things like std.SinglyLinkedList)
665/// but may cause infinite recursion or stack overflow when a container has a pointer to itself.678/// but may cause infinite recursion or stack overflow when a container has a pointer to itself.
666pub fn expectEqualDeep(expected: anytype, actual: @TypeOf(expected)) error{TestExpectedEqual}!void {679pub inline fn expectEqualDeep(expected: anytype, actual: anytype) error{TestExpectedEqual}!void {
680 const T = @TypeOf(expected, actual);
681 return expectEqualDeepInner(T, expected, actual);
682}
683
684fn expectEqualDeepInner(comptime T: type, expected: T, actual: T) error{TestExpectedEqual}!void {
667 switch (@typeInfo(@TypeOf(actual))) {685 switch (@typeInfo(@TypeOf(actual))) {
668 .NoReturn,686 .NoReturn,
669 .Opaque,687 .Opaque,