Something went wrong. Try again.
Native PostgreSQL driver / client for Zig
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308// This is for the Zig 0.16.
// See https://gist.github.com/karlseguin/c6bea5b35e4e8d26af6f81c22cb5d76b/eb15512d6ae49663fa9df6c7a9725b20dab43edd// for a version that workson Zig 0.15.2.
// See https://gist.github.com/karlseguin/c6bea5b35e4e8d26af6f81c22cb5d76b/1f317ebc9cd09bc50fd5591d09c34255e15d1d85// for a version that workson Zig 0.14.1.
// in your build.zig, you can specify a custom test runner:// const tests = b.addTest(.{// .root_module = $MODULE_BEING_TESTED,// .test_runner = .{ .path = b.path("test_runner.zig"), .mode = .simple },// });
// in your build.zig, you can specify a custom test runner:// const tests = b.addTest(.{// .root_module = $MODULE_BEING_TESTED,// .test_runner = .{ .path = b.path("test_runner.zig"), .mode = .simple },// });
const std = @import("std");const Io = std.Io;const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
const BORDER = "=" ** 80;
// use in custom panic handlervar current_test: ?[]const u8 = null;
pub fn main(init: std.process.Init) !void { var mem: [8192]u8 = undefined; var fba = std.heap.FixedBufferAllocator.init(&mem);
const allocator = fba.allocator();
const env = Env.init(init.environ_map);
std.testing.io_instance = .init(init.gpa, .{ .argv0 = .init(init.minimal.args), .environ = init.minimal.environ, }); defer std.testing.io_instance.deinit();
const io = std.testing.io;
var slowest = SlowTracker.init(io, allocator, 5); defer slowest.deinit();
var pass: usize = 0; var fail: usize = 0; var skip: usize = 0; var leak: usize = 0;
Printer.fmt("\r\x1b[0K", .{}); // beginning of line and clear to end of line
for (builtin.test_functions) |t| { if (isSetup(t)) { t.func() catch |err| { Printer.status(.fail, "\nsetup \"{s}\" failed: {}\n", .{ t.name, err }); return err; }; } }
for (builtin.test_functions) |t| { if (isSetup(t) or isTeardown(t)) { continue; }
var status = Status.pass; slowest.startTiming(io);
const is_unnamed_test = isUnnamed(t); if (env.filter) |f| { if (!is_unnamed_test and std.mem.indexOf(u8, t.name, f) == null) { continue; } }
const friendly_name = blk: { const name = t.name; var it = std.mem.splitScalar(u8, name, '.'); while (it.next()) |value| { if (std.mem.eql(u8, value, "test")) { const rest = it.rest(); break :blk if (rest.len > 0) rest else name; } } break :blk name; };
current_test = friendly_name; std.testing.allocator_instance = .{}; const result = t.func(); current_test = null;
const ns_taken = slowest.endTiming(io, friendly_name);
if (std.testing.allocator_instance.deinit() == .leak) { leak += 1; Printer.status(.fail, "\n{s}\n\"{s}\" - Memory Leak\n{s}\n", .{ BORDER, friendly_name, BORDER }); }
if (result) |_| { pass += 1; } else |err| switch (err) { error.SkipZigTest => { skip += 1; status = .skip; }, else => { status = .fail; fail += 1; Printer.status(.fail, "\n{s}\n\"{s}\" - {s}\n{s}\n", .{ BORDER, friendly_name, @errorName(err), BORDER }); if (@errorReturnTrace()) |trace| { std.debug.dumpErrorReturnTrace(trace); } if (env.fail_first) { break; } }, }
if (env.verbose) { const ms = @as(f64, @floatFromInt(ns_taken)) / 1_000_000.0; Printer.status(status, "{s} ({d:.2}ms)\n", .{ friendly_name, ms }); } else { Printer.status(status, ".", .{}); } }
for (builtin.test_functions) |t| { if (isTeardown(t)) { t.func() catch |err| { Printer.status(.fail, "\nteardown \"{s}\" failed: {}\n", .{ t.name, err }); return err; }; } }
const total_tests = pass + fail; const status = if (fail == 0) Status.pass else Status.fail; Printer.status(status, "\n{d} of {d} test{s} passed\n", .{ pass, total_tests, if (total_tests != 1) "s" else "" }); if (skip > 0) { Printer.status(.skip, "{d} test{s} skipped\n", .{ skip, if (skip != 1) "s" else "" }); } if (leak > 0) { Printer.status(.fail, "{d} test{s} leaked\n", .{ leak, if (leak != 1) "s" else "" }); } Printer.fmt("\n", .{}); try slowest.display(); Printer.fmt("\n", .{}); std.process.exit(if (fail == 0) 0 else 1);}
const Printer = struct { fn fmt(comptime format: []const u8, args: anytype) void { std.debug.print(format, args); }
fn status(s: Status, comptime format: []const u8, args: anytype) void { switch (s) { .pass => std.debug.print("\x1b[32m", .{}), .fail => std.debug.print("\x1b[31m", .{}), .skip => std.debug.print("\x1b[33m", .{}), else => {}, } std.debug.print(format ++ "\x1b[0m", args); }};
const Status = enum { pass, fail, skip, text,};
const SlowTracker = struct { max: usize, slowest: SlowestQueue, start: Io.Timestamp, allocator: Allocator,
const SlowestQueue = std.PriorityDequeue(TestInfo, void, compareTiming);
fn init(io: Io, allocator: Allocator, count: u32) SlowTracker { const timestamp = Io.Clock.awake.now(io); var slowest: SlowestQueue = .empty; slowest.ensureTotalCapacity(allocator, count) catch @panic("OOM"); return .{ .max = count, .start = timestamp, .slowest = slowest, .allocator = allocator, }; }
const TestInfo = struct { ns: u64, name: []const u8, };
fn deinit(self: *SlowTracker) void { self.slowest.deinit(self.allocator); }
fn startTiming(self: *SlowTracker, io: Io) void { self.start = Io.Clock.awake.now(io); }
fn endTiming(self: *SlowTracker, io: Io, test_name: []const u8) u64 { const timestamp = Io.Clock.awake.now(io); const start = self.start; self.start = timestamp; const ns: u64 = @intCast(start.durationTo(timestamp).toNanoseconds());
var slowest = &self.slowest;
if (slowest.count() < self.max) { // Capacity is fixed to the # of slow tests we want to track // If we've tracked fewer tests than this capacity, than always add slowest.push(self.allocator, TestInfo{ .ns = ns, .name = test_name }) catch @panic("failed to track test timing"); return ns; }
{ // Optimization to avoid shifting the dequeue for the common case // where the test isn't one of our slowest. const fastest_of_the_slow = slowest.peekMin() orelse unreachable; if (fastest_of_the_slow.ns > ns) { // the test was faster than our fastest slow test, don't add return ns; } }
// the previous fastest of our slow tests, has been pushed off. _ = slowest.popMin(); slowest.push(self.allocator, TestInfo{ .ns = ns, .name = test_name }) catch @panic("failed to track test timing"); return ns; }
fn display(self: *SlowTracker) !void { var slowest = self.slowest; const count = slowest.count(); Printer.fmt("Slowest {d} test{s}: \n", .{ count, if (count != 1) "s" else "" }); while (slowest.popMin()) |info| { const ms = @as(f64, @floatFromInt(info.ns)) / 1_000_000.0; Printer.fmt(" {d:.2}ms\t{s}\n", .{ ms, info.name }); } }
fn compareTiming(context: void, a: TestInfo, b: TestInfo) std.math.Order { _ = context; return std.math.order(a.ns, b.ns); }};
const Env = struct { verbose: bool, fail_first: bool, filter: ?[]const u8,
fn init(map: *const std.process.Environ.Map) Env { return .{ .verbose = readEnvBool(map, "TEST_VERBOSE", true), .fail_first = readEnvBool(map, "TEST_FAIL_FIRST", false), .filter = readEnv(map, "TEST_FILTER"), }; }
fn readEnv(map: *const std.process.Environ.Map, key: []const u8) ?[]const u8 { return map.get(key); }
fn readEnvBool(map: *const std.process.Environ.Map, key: []const u8, deflt: bool) bool { const value = readEnv(map, key) orelse return deflt; return std.ascii.eqlIgnoreCase(value, "true"); }};
pub const panic = std.debug.FullPanic(struct { pub fn panicFn(msg: []const u8, first_trace_addr: ?usize) noreturn { if (current_test) |ct| { std.debug.print("\x1b[31m{s}\npanic running \"{s}\"\n{s}\x1b[0m\n", .{ BORDER, ct, BORDER }); } std.debug.defaultPanic(msg, first_trace_addr); }}.panicFn);
fn isUnnamed(t: std.builtin.TestFn) bool { const marker = ".test_"; const test_name = t.name; const index = std.mem.indexOf(u8, test_name, marker) orelse return false; _ = std.fmt.parseInt(u32, test_name[index + marker.len ..], 10) catch return false; return true;}
fn isSetup(t: std.builtin.TestFn) bool { return std.mem.endsWith(u8, t.name, "tests:beforeAll");}
fn isTeardown(t: std.builtin.TestFn) bool { return std.mem.endsWith(u8, t.name, "tests:afterAll");}