// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) // // Francis Bouvier // Pierre Tachoire // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . const std = @import("std"); pub const allocator = std.testing.allocator; pub const expectError = std.testing.expectError; pub const expectString = std.testing.expectEqualStrings; const App = @import("app.zig").App; // Merged std.testing.expectEqual and std.testing.expectString // can be useful when testing fields of an anytype an you don't know // exactly how to assert equality 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 expectString(expected, actual); } }, .@"struct" => |structType| { inline for (structType.fields) |field| { try expectEqual(@field(expected, field.name), @field(actual, field.name)); } return; }, .optional => { if (@typeInfo(@TypeOf(expected)) == .null) { return std.testing.expectEqual(null, actual); } return expectEqual(expected, actual.?); }, .@"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(expected, actual); } pub fn expectDelta(expected: anytype, actual: anytype, delta: anytype) !void { if (@typeInfo(@TypeOf(expected)) == .null) { return std.testing.expectEqual(null, actual); } switch (@typeInfo(@TypeOf(actual))) { .optional => { if (actual) |value| { return expectDelta(expected, value, delta); } return std.testing.expectEqual(null, expected); }, else => {}, } switch (@typeInfo(@TypeOf(expected))) { .optional => { if (expected) |value| { return expectDelta(value, actual, delta); } return std.testing.expectEqual(null, actual); }, else => {}, } 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; } 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 print(comptime fmt: []const u8, args: anytype) void { if (@inComptime()) { @compileError(std.fmt.comptimePrint(fmt, args)); } else { std.debug.print(fmt, args); } } // dummy opts incase we want to add something, and not have to break all the callers pub fn app(_: anytype) *App { return App.init(allocator, .serve) catch unreachable; } pub const Random = struct { var instance: ?std.Random.DefaultPrng = null; pub fn fill(buf: []u8) void { var r = random(); r.bytes(buf); } pub fn fillAtLeast(buf: []u8, min: usize) []u8 { var r = random(); const l = r.intRangeAtMost(usize, min, buf.len); r.bytes(buf[0..l]); return buf; } pub fn intRange(comptime T: type, min: T, max: T) T { var r = random(); return r.intRangeAtMost(T, min, max); } pub fn random() std.Random { if (instance == null) { var seed: u64 = undefined; std.posix.getrandom(std.mem.asBytes(&seed)) catch unreachable; instance = std.Random.DefaultPrng.init(seed); // instance = std.Random.DefaultPrng.init(0); } return instance.?.random(); } };