const std = @import("std"); const builtin = @import("builtin"); const DateTime = @import("root.zig").DateTime; // std.testing.expectEqual won't coerce expected to actual, which is a problem // when expected is frequently a comptime. // https://github.com/ziglang/zig/issues/4437 pub fn expectEqual(expected: anytype, actual: anytype) !void { switch (@typeInfo(@TypeOf(actual))) { .array => |arr| if (arr.child == u8) { return std.testing.expectEqualStrings(expected, &actual); }, .pointer => |ptr| if (ptr.child == u8) { return std.testing.expectEqualStrings(expected, actual); } else if (comptime isStringArray(ptr.child)) { return std.testing.expectEqualStrings(expected, actual); } else if (ptr.child == []u8 or ptr.child == []const u8) { return expectStrings(expected, actual); }, .@"struct" => |structType| { inline for (structType.fields) |field| { try expectEqual(@field(expected, field.name), @field(actual, field.name)); } return; }, .@"union" => |union_info| { if (union_info.tag_type == null) { @compileError("Unable to compare untagged union values"); } const Tag = std.meta.Tag(@TypeOf(expected)); const expectedTag = @as(Tag, expected); const actualTag = @as(Tag, actual); try expectEqual(expectedTag, actualTag); inline for (std.meta.fields(@TypeOf(actual))) |fld| { if (std.mem.eql(u8, fld.name, @tagName(actualTag))) { try expectEqual(@field(expected, fld.name), @field(actual, fld.name)); return; } } unreachable; }, else => {}, } return std.testing.expectEqual(@as(@TypeOf(actual), expected), actual); } fn expectStrings(expected: []const []const u8, actual: anytype) !void { try t.expectEqual(expected.len, actual.len); for (expected, actual) |e, a| { try std.testing.expectEqualStrings(e, a); } } fn isStringArray(comptime T: type) bool { if (!is(.array)(T) and !isPtrTo(.array)(T)) { return false; } return std.meta.Elem(T) == u8; } pub const TraitFn = fn (type) bool; pub fn is(comptime id: std.builtin.TypeId) TraitFn { const Closure = struct { pub fn trait(comptime T: type) bool { return id == @typeInfo(T); } }; return Closure.trait; } pub fn isPtrTo(comptime id: std.builtin.TypeId) TraitFn { const Closure = struct { pub fn trait(comptime T: type) bool { if (!comptime isSingleItemPtr(T)) return false; return id == @typeInfo(std.meta.Child(T)); } }; return Closure.trait; } pub fn isSingleItemPtr(comptime T: type) bool { if (comptime is(.pointer)(T)) { return @typeInfo(T).pointer.size == .one; } return false; } pub fn expectDelta(expected: anytype, actual: anytype, delta: anytype) !void { var diff = expected - actual; if (diff < 0) { diff = -diff; } if (diff <= delta) { return; } print("Expected {} to be within {} of {}. Actual diff: {}", .{ expected, delta, actual, diff }); return error.NotWithinDelta; } pub fn print(comptime fmt: []const u8, args: anytype) void { if (@inComptime()) { @compileError(std.fmt.comptimePrint(fmt, args)); } else { std.debug.print(fmt, args); } } // Re-expose these as-is so that more cases can rely on zul.testing exclusively. // Else, it's a pain to have both std.testing and zul.testing in a test. pub const expect = std.testing.expect; pub const expectFmt = std.testing.expectFmt; pub const expectError = std.testing.expectError; pub const expectEqualSlices = std.testing.expectEqualSlices; pub const expectEqualStrings = std.testing.expectEqualStrings; pub const expectEqualSentinel = std.testing.expectEqualSentinel; pub const expectApproxEqAbs = std.testing.expectApproxEqAbs; pub const expectApproxEqRel = std.testing.expectApproxEqRel; pub const io = std.testing.io; pub const allocator = std.testing.allocator; pub var arena = std.heap.ArenaAllocator.init(allocator); pub fn reset() void { _ = arena.reset(.free_all); } const t = @This(); fn expectDateTime(expected: []const u8, dt: DateTime) !void { var buf: [30]u8 = undefined; const actual = try std.fmt.bufPrint(&buf, "{f}", .{dt}); try t.expectEqual(expected, actual); } test "testing: doc example" { // clear's the arena allocator defer t.reset(); // In addition to exposing std.testing.allocator as zul.testing.allocator // zul.testing.arena is an ArenaAllocator. An ArenaAllocator can // make managing test-specific allocations a lot simpler. // Just stick a `defer zul.testing.reset()` atop your test. var buf = try t.arena.allocator().alloc(u8, 5); // unlike std.testing.expectEqual, zul's expectEqual // will coerce expected to actual's type, so this is valid: try t.expectEqual(5, buf.len); @memcpy(buf[0..5], "hello"); // zul's expectEqual also works with strings. try t.expectEqual("hello", buf); }