An HTTP/1.1 server for zig
0

Configure Feed

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

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