mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 22:16:08 +00:00
278 lines
9.2 KiB
Zig
278 lines
9.2 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const jetzig = @import("jetzig");
|
|
pub const static = @import("static");
|
|
|
|
pub const std_options = std.Options{
|
|
.logFn = log,
|
|
};
|
|
|
|
pub fn log(
|
|
comptime message_level: std.log.Level,
|
|
comptime scope: @Type(.enum_literal),
|
|
comptime format: []const u8,
|
|
args: anytype,
|
|
) void {
|
|
jetzig.testing.logger.log(message_level, scope, format, args);
|
|
}
|
|
|
|
const Test = struct {
|
|
name: []const u8,
|
|
function: TestFn,
|
|
module: ?[]const u8 = null,
|
|
leaked: bool = false,
|
|
result: Result = .success,
|
|
stack_trace_buf: [8192]u8 = undefined,
|
|
duration: usize = 0,
|
|
|
|
pub const TestFn = *const fn () anyerror!void;
|
|
pub const Result = union(enum) {
|
|
success: void,
|
|
failure: Failure,
|
|
skipped: void,
|
|
};
|
|
|
|
const Failure = struct {
|
|
err: anyerror,
|
|
trace: ?[]const u8,
|
|
};
|
|
|
|
const name_template = jetzig.colors.blue("{s}") ++ jetzig.colors.yellow("::") ++ "\"" ++ jetzig.colors.cyan("{s}") ++ "\" ";
|
|
|
|
pub fn init(test_fn: std.builtin.TestFn) Test {
|
|
return if (std.mem.indexOf(u8, test_fn.name, ".test.")) |index|
|
|
.{
|
|
.function = test_fn.func,
|
|
.module = test_fn.name[0..index],
|
|
.name = test_fn.name[index + ".test.".len ..],
|
|
}
|
|
else
|
|
.{ .function = test_fn.func, .name = test_fn.name };
|
|
}
|
|
|
|
pub fn run(self: *Test, allocator: std.mem.Allocator) !void {
|
|
std.testing.allocator_instance = .{};
|
|
const start = std.time.nanoTimestamp();
|
|
|
|
self.function() catch |err| {
|
|
switch (err) {
|
|
error.SkipZigTest => self.result = .skipped,
|
|
else => self.result = .{ .failure = .{
|
|
.err = err,
|
|
.trace = if (try self.formatStackTrace(@errorReturnTrace())) |trace|
|
|
try allocator.dupe(u8, trace)
|
|
else
|
|
null,
|
|
} },
|
|
}
|
|
};
|
|
|
|
self.duration = @intCast(std.time.nanoTimestamp() - start);
|
|
|
|
if (std.testing.allocator_instance.deinit() == .leak) self.leaked = true;
|
|
}
|
|
|
|
fn formatStackTrace(self: *Test, maybe_trace: ?*std.builtin.StackTrace) !?[]const u8 {
|
|
return if (maybe_trace) |trace| blk: {
|
|
var stream = std.io.fixedBufferStream(&self.stack_trace_buf);
|
|
const writer = stream.writer();
|
|
try trace.format("", .{}, writer);
|
|
break :blk stream.getWritten();
|
|
} else null;
|
|
}
|
|
|
|
pub fn print(self: Test, stream: anytype) !void {
|
|
const writer = stream.writer();
|
|
|
|
switch (self.result) {
|
|
.success => {
|
|
try self.printPassed(writer);
|
|
if (self.leaked) try self.printLeaked(writer);
|
|
try self.printDuration(writer);
|
|
},
|
|
.failure => |failure| {
|
|
try self.printFailure(failure, writer);
|
|
if (self.leaked) try self.printLeaked(writer);
|
|
},
|
|
.skipped => try self.printSkipped(writer),
|
|
}
|
|
|
|
try writer.writeByte('\n');
|
|
}
|
|
|
|
fn printPassed(self: Test, writer: anytype) !void {
|
|
try writer.print(
|
|
jetzig.colors.green("[PASS] ") ++ name_template,
|
|
.{ self.module orelse "tests", self.name },
|
|
);
|
|
}
|
|
|
|
fn printFailure(self: Test, failure: Failure, writer: anytype) !void {
|
|
try writer.print(
|
|
jetzig.colors.red("[FAIL] ") ++ name_template ++ jetzig.colors.yellow("({s})"),
|
|
.{ self.module orelse "tests", self.name, @errorName(failure.err) },
|
|
);
|
|
}
|
|
|
|
fn printFailureDetail(self: Test, index: usize, failure: Failure, writer: anytype) !void {
|
|
try writer.print("\n", .{});
|
|
|
|
const count = " FAILURE: ".len + (self.module orelse "tests").len + ":".len + self.name.len + 4;
|
|
|
|
try writer.writeAll(jetzig.colors.red("┌"));
|
|
for (0..count) |_| try writer.writeAll(jetzig.colors.red("─"));
|
|
try writer.writeAll(jetzig.colors.red("┐"));
|
|
|
|
try writer.print(
|
|
jetzig.colors.red("\n│ FAILURE: ") ++ name_template ++ jetzig.colors.red("│") ++ "\n",
|
|
.{ self.module orelse "tests", self.name },
|
|
);
|
|
try writer.writeAll(jetzig.colors.red("├"));
|
|
for (0..count) |_| try writer.writeAll(jetzig.colors.red("─"));
|
|
try writer.writeAll(jetzig.colors.red("┘"));
|
|
try writer.writeByte('\n');
|
|
|
|
const maybe_log_events = jetzig.testing.logger.logs.get(index);
|
|
if (maybe_log_events) |log_events| {
|
|
for (log_events.items) |log_event| try indent(log_event.output, jetzig.colors.red("│ "), writer);
|
|
}
|
|
if (failure.trace) |trace| {
|
|
try writer.writeAll(jetzig.colors.red("┆\n"));
|
|
try indent(trace, jetzig.colors.red("┆ "), writer);
|
|
}
|
|
}
|
|
|
|
fn printSkipped(self: Test, writer: anytype) !void {
|
|
try writer.print(
|
|
jetzig.colors.yellow("[SKIP]") ++ name_template,
|
|
.{ self.module orelse "tests", self.name },
|
|
);
|
|
}
|
|
|
|
fn printLeaked(self: Test, writer: anytype) !void {
|
|
_ = self;
|
|
try writer.print(jetzig.colors.red(" [LEAKED]"), .{});
|
|
}
|
|
|
|
fn printDuration(self: Test, writer: anytype) !void {
|
|
var buf: [256]u8 = undefined;
|
|
try writer.print(
|
|
"[" ++ jetzig.colors.cyan("{s}") ++ "]",
|
|
.{try jetzig.colors.duration(&buf, @intCast(self.duration), true)},
|
|
);
|
|
}
|
|
};
|
|
|
|
pub fn main() !void {
|
|
const start = std.time.nanoTimestamp();
|
|
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
const allocator = gpa.allocator();
|
|
var tests = std.ArrayList(Test).init(allocator);
|
|
defer tests.deinit();
|
|
|
|
var mime_map = jetzig.http.mime.MimeMap.init(allocator);
|
|
try mime_map.build();
|
|
jetzig.testing.mime_map = &mime_map;
|
|
|
|
try std.io.getStdErr().writer().writeAll("\n[jetzig] Launching Test Runner...\n\n");
|
|
|
|
jetzig.testing.logger = jetzig.testing.Logger.init(allocator);
|
|
jetzig.testing.state = .ready;
|
|
|
|
for (builtin.test_functions, 0..) |test_function, index| {
|
|
jetzig.testing.logger.index = index;
|
|
var t = Test.init(test_function);
|
|
try t.run(allocator);
|
|
try t.print(std.io.getStdErr());
|
|
try tests.append(t);
|
|
}
|
|
|
|
try printSummary(tests.items, start);
|
|
}
|
|
|
|
fn printSummary(tests: []const Test, start: i128) !void {
|
|
var success: usize = 0;
|
|
var failure: usize = 0;
|
|
var leaked: usize = 0;
|
|
var skipped: usize = 0;
|
|
|
|
for (tests) |t| {
|
|
switch (t.result) {
|
|
.success => success += 1,
|
|
.failure => failure += 1,
|
|
.skipped => skipped += 1,
|
|
}
|
|
if (t.leaked) leaked += 1;
|
|
}
|
|
const tick = jetzig.colors.green("✔");
|
|
const cross = jetzig.colors.red("✗");
|
|
|
|
const writer = std.io.getStdErr().writer();
|
|
|
|
var total_duration_buf: [256]u8 = undefined;
|
|
const total_duration = try jetzig.colors.duration(
|
|
&total_duration_buf,
|
|
@intCast(std.time.nanoTimestamp() - start),
|
|
false,
|
|
);
|
|
|
|
for (tests, 0..) |t, index| {
|
|
switch (t.result) {
|
|
.success, .skipped => {},
|
|
.failure => |capture| try t.printFailureDetail(index, capture, writer),
|
|
}
|
|
}
|
|
|
|
try writer.print(
|
|
"\n {s}{s}{}" ++
|
|
"\n {s}{s}{}" ++
|
|
"\n {s}{}" ++
|
|
"\n " ++ jetzig.colors.cyan(" tests ") ++ "{}" ++
|
|
"\n " ++ jetzig.colors.cyan(" duration ") ++ "{s}" ++ "\n\n",
|
|
.{
|
|
if (failure == 0) tick else cross,
|
|
if (failure == 0) jetzig.colors.blue(" failed ") else jetzig.colors.red(" failed "),
|
|
failure,
|
|
if (leaked == 0) tick else cross,
|
|
if (leaked == 0) jetzig.colors.blue(" leaked ") else jetzig.colors.red(" leaked "),
|
|
leaked,
|
|
if (skipped == 0) jetzig.colors.blue(" skipped ") else jetzig.colors.yellow(" skipped "),
|
|
skipped,
|
|
success + failure,
|
|
total_duration,
|
|
},
|
|
);
|
|
|
|
if (failure == 0 and leaked == 0) {
|
|
try writer.print(jetzig.colors.green(" PASS ") ++ "\n", .{});
|
|
try writer.print(jetzig.colors.green(" ▔▔▔▔") ++ "\n", .{});
|
|
std.process.exit(0);
|
|
} else {
|
|
try writer.print(jetzig.colors.red(" FAIL ") ++ "\n", .{});
|
|
try writer.print(jetzig.colors.red(" ▔▔▔▔") ++ "\n", .{});
|
|
std.process.exit(1);
|
|
}
|
|
}
|
|
|
|
fn indent(message: []const u8, comptime indent_sequence: []const u8, writer: anytype) !void {
|
|
var it = std.mem.tokenizeScalar(u8, message, '\n');
|
|
var color: ?[]const u8 = null;
|
|
|
|
const escape = jetzig.colors.codes.escape;
|
|
|
|
while (it.next()) |line| {
|
|
try writer.print(indent_sequence ++ "{s}{s}\n", .{ color orelse "", line });
|
|
|
|
// Preserve last color used in previous line (including reset) in case indent changes color.
|
|
if (std.mem.lastIndexOf(u8, line, escape)) |index| {
|
|
inline for (std.meta.fields(@TypeOf(jetzig.colors.codes))) |field| {
|
|
const code = @field(jetzig.colors.codes, field.name);
|
|
if (std.mem.startsWith(u8, line[index..], escape ++ code)) {
|
|
color = escape ++ code;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|