Native PostgreSQL driver / client for Zig
0

Configure Feed

Select the types of activity you want to include in your feed.

pg.zig / test_runner.zig
9.8 kB 307 lines
1// This is for the Zig 0.16. 2 3// See https://gist.github.com/karlseguin/c6bea5b35e4e8d26af6f81c22cb5d76b/eb15512d6ae49663fa9df6c7a9725b20dab43edd 4// for a version that workson Zig 0.15.2. 5 6// See https://gist.github.com/karlseguin/c6bea5b35e4e8d26af6f81c22cb5d76b/1f317ebc9cd09bc50fd5591d09c34255e15d1d85 7// for a version that workson Zig 0.14.1. 8 9// in your build.zig, you can specify a custom test runner: 10// const tests = b.addTest(.{ 11// .root_module = $MODULE_BEING_TESTED, 12// .test_runner = .{ .path = b.path("test_runner.zig"), .mode = .simple }, 13// }); 14 15// in your build.zig, you can specify a custom test runner: 16// const tests = b.addTest(.{ 17// .root_module = $MODULE_BEING_TESTED, 18// .test_runner = .{ .path = b.path("test_runner.zig"), .mode = .simple }, 19// }); 20 21const std = @import("std"); 22const Io = std.Io; 23const builtin = @import("builtin"); 24 25const Allocator = std.mem.Allocator; 26 27const BORDER = "=" ** 80; 28 29// use in custom panic handler 30var current_test: ?[]const u8 = null; 31 32pub fn main(init: std.process.Init) !void { 33 var mem: [8192]u8 = undefined; 34 var fba = std.heap.FixedBufferAllocator.init(&mem); 35 36 const allocator = fba.allocator(); 37 38 const env = Env.init(init.environ_map); 39 40 std.testing.io_instance = .init(init.gpa, .{ 41 .argv0 = .init(init.minimal.args), 42 .environ = init.minimal.environ, 43 }); 44 defer std.testing.io_instance.deinit(); 45 46 const io = std.testing.io; 47 48 var slowest = SlowTracker.init(io, allocator, 5); 49 defer slowest.deinit(); 50 51 var pass: usize = 0; 52 var fail: usize = 0; 53 var skip: usize = 0; 54 var leak: usize = 0; 55 56 Printer.fmt("\r\x1b[0K", .{}); // beginning of line and clear to end of line 57 58 for (builtin.test_functions) |t| { 59 if (isSetup(t)) { 60 t.func() catch |err| { 61 Printer.status(.fail, "\nsetup \"{s}\" failed: {}\n", .{ t.name, err }); 62 return err; 63 }; 64 } 65 } 66 67 for (builtin.test_functions) |t| { 68 if (isSetup(t) or isTeardown(t)) { 69 continue; 70 } 71 72 var status = Status.pass; 73 slowest.startTiming(io); 74 75 const is_unnamed_test = isUnnamed(t); 76 if (env.filter) |f| { 77 if (!is_unnamed_test and std.mem.indexOf(u8, t.name, f) == null) { 78 continue; 79 } 80 } 81 82 const friendly_name = blk: { 83 const name = t.name; 84 var it = std.mem.splitScalar(u8, name, '.'); 85 while (it.next()) |value| { 86 if (std.mem.eql(u8, value, "test")) { 87 const rest = it.rest(); 88 break :blk if (rest.len > 0) rest else name; 89 } 90 } 91 break :blk name; 92 }; 93 94 current_test = friendly_name; 95 std.testing.allocator_instance = .{}; 96 const result = t.func(); 97 current_test = null; 98 99 const ns_taken = slowest.endTiming(io, friendly_name); 100 101 if (std.testing.allocator_instance.deinit() == .leak) { 102 leak += 1; 103 Printer.status(.fail, "\n{s}\n\"{s}\" - Memory Leak\n{s}\n", .{ BORDER, friendly_name, BORDER }); 104 } 105 106 if (result) |_| { 107 pass += 1; 108 } else |err| switch (err) { 109 error.SkipZigTest => { 110 skip += 1; 111 status = .skip; 112 }, 113 else => { 114 status = .fail; 115 fail += 1; 116 Printer.status(.fail, "\n{s}\n\"{s}\" - {s}\n{s}\n", .{ BORDER, friendly_name, @errorName(err), BORDER }); 117 if (@errorReturnTrace()) |trace| { 118 std.debug.dumpErrorReturnTrace(trace); 119 } 120 if (env.fail_first) { 121 break; 122 } 123 }, 124 } 125 126 if (env.verbose) { 127 const ms = @as(f64, @floatFromInt(ns_taken)) / 1_000_000.0; 128 Printer.status(status, "{s} ({d:.2}ms)\n", .{ friendly_name, ms }); 129 } else { 130 Printer.status(status, ".", .{}); 131 } 132 } 133 134 for (builtin.test_functions) |t| { 135 if (isTeardown(t)) { 136 t.func() catch |err| { 137 Printer.status(.fail, "\nteardown \"{s}\" failed: {}\n", .{ t.name, err }); 138 return err; 139 }; 140 } 141 } 142 143 const total_tests = pass + fail; 144 const status = if (fail == 0) Status.pass else Status.fail; 145 Printer.status(status, "\n{d} of {d} test{s} passed\n", .{ pass, total_tests, if (total_tests != 1) "s" else "" }); 146 if (skip > 0) { 147 Printer.status(.skip, "{d} test{s} skipped\n", .{ skip, if (skip != 1) "s" else "" }); 148 } 149 if (leak > 0) { 150 Printer.status(.fail, "{d} test{s} leaked\n", .{ leak, if (leak != 1) "s" else "" }); 151 } 152 Printer.fmt("\n", .{}); 153 try slowest.display(); 154 Printer.fmt("\n", .{}); 155 std.process.exit(if (fail == 0) 0 else 1); 156} 157 158const Printer = struct { 159 fn fmt(comptime format: []const u8, args: anytype) void { 160 std.debug.print(format, args); 161 } 162 163 fn status(s: Status, comptime format: []const u8, args: anytype) void { 164 switch (s) { 165 .pass => std.debug.print("\x1b[32m", .{}), 166 .fail => std.debug.print("\x1b[31m", .{}), 167 .skip => std.debug.print("\x1b[33m", .{}), 168 else => {}, 169 } 170 std.debug.print(format ++ "\x1b[0m", args); 171 } 172}; 173 174const Status = enum { 175 pass, 176 fail, 177 skip, 178 text, 179}; 180 181const SlowTracker = struct { 182 max: usize, 183 slowest: SlowestQueue, 184 start: Io.Timestamp, 185 allocator: Allocator, 186 187 const SlowestQueue = std.PriorityDequeue(TestInfo, void, compareTiming); 188 189 fn init(io: Io, allocator: Allocator, count: u32) SlowTracker { 190 const timestamp = Io.Clock.awake.now(io); 191 var slowest: SlowestQueue = .empty; 192 slowest.ensureTotalCapacity(allocator, count) catch @panic("OOM"); 193 return .{ 194 .max = count, 195 .start = timestamp, 196 .slowest = slowest, 197 .allocator = allocator, 198 }; 199 } 200 201 const TestInfo = struct { 202 ns: u64, 203 name: []const u8, 204 }; 205 206 fn deinit(self: *SlowTracker) void { 207 self.slowest.deinit(self.allocator); 208 } 209 210 fn startTiming(self: *SlowTracker, io: Io) void { 211 self.start = Io.Clock.awake.now(io); 212 } 213 214 fn endTiming(self: *SlowTracker, io: Io, test_name: []const u8) u64 { 215 const timestamp = Io.Clock.awake.now(io); 216 const start = self.start; 217 self.start = timestamp; 218 const ns: u64 = @intCast(start.durationTo(timestamp).toNanoseconds()); 219 220 var slowest = &self.slowest; 221 222 if (slowest.count() < self.max) { 223 // Capacity is fixed to the # of slow tests we want to track 224 // If we've tracked fewer tests than this capacity, than always add 225 slowest.push(self.allocator, TestInfo{ .ns = ns, .name = test_name }) catch @panic("failed to track test timing"); 226 return ns; 227 } 228 229 { 230 // Optimization to avoid shifting the dequeue for the common case 231 // where the test isn't one of our slowest. 232 const fastest_of_the_slow = slowest.peekMin() orelse unreachable; 233 if (fastest_of_the_slow.ns > ns) { 234 // the test was faster than our fastest slow test, don't add 235 return ns; 236 } 237 } 238 239 // the previous fastest of our slow tests, has been pushed off. 240 _ = slowest.popMin(); 241 slowest.push(self.allocator, TestInfo{ .ns = ns, .name = test_name }) catch @panic("failed to track test timing"); 242 return ns; 243 } 244 245 fn display(self: *SlowTracker) !void { 246 var slowest = self.slowest; 247 const count = slowest.count(); 248 Printer.fmt("Slowest {d} test{s}: \n", .{ count, if (count != 1) "s" else "" }); 249 while (slowest.popMin()) |info| { 250 const ms = @as(f64, @floatFromInt(info.ns)) / 1_000_000.0; 251 Printer.fmt(" {d:.2}ms\t{s}\n", .{ ms, info.name }); 252 } 253 } 254 255 fn compareTiming(context: void, a: TestInfo, b: TestInfo) std.math.Order { 256 _ = context; 257 return std.math.order(a.ns, b.ns); 258 } 259}; 260 261const Env = struct { 262 verbose: bool, 263 fail_first: bool, 264 filter: ?[]const u8, 265 266 fn init(map: *const std.process.Environ.Map) Env { 267 return .{ 268 .verbose = readEnvBool(map, "TEST_VERBOSE", true), 269 .fail_first = readEnvBool(map, "TEST_FAIL_FIRST", false), 270 .filter = readEnv(map, "TEST_FILTER"), 271 }; 272 } 273 274 fn readEnv(map: *const std.process.Environ.Map, key: []const u8) ?[]const u8 { 275 return map.get(key); 276 } 277 278 fn readEnvBool(map: *const std.process.Environ.Map, key: []const u8, deflt: bool) bool { 279 const value = readEnv(map, key) orelse return deflt; 280 return std.ascii.eqlIgnoreCase(value, "true"); 281 } 282}; 283 284pub const panic = std.debug.FullPanic(struct { 285 pub fn panicFn(msg: []const u8, first_trace_addr: ?usize) noreturn { 286 if (current_test) |ct| { 287 std.debug.print("\x1b[31m{s}\npanic running \"{s}\"\n{s}\x1b[0m\n", .{ BORDER, ct, BORDER }); 288 } 289 std.debug.defaultPanic(msg, first_trace_addr); 290 } 291}.panicFn); 292 293fn isUnnamed(t: std.builtin.TestFn) bool { 294 const marker = ".test_"; 295 const test_name = t.name; 296 const index = std.mem.indexOf(u8, test_name, marker) orelse return false; 297 _ = std.fmt.parseInt(u32, test_name[index + marker.len ..], 10) catch return false; 298 return true; 299} 300 301fn isSetup(t: std.builtin.TestFn) bool { 302 return std.mem.endsWith(u8, t.name, "tests:beforeAll"); 303} 304 305fn isTeardown(t: std.builtin.TestFn) bool { 306 return std.mem.endsWith(u8, t.name, "tests:afterAll"); 307}