forked from
karlseguin.tngl.sh/http.zig
An HTTP/1.1 server for zig
1const std = @import("std");
2const Io = std.Io;
3const builtin = @import("builtin");
4
5const Allocator = std.mem.Allocator;
6
7const BORDER = "=" ** 80;
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}