ReviewOS

also looking at this

zig-utils/zig-test-framework

Define stable JSON event protocol

#27
Merged glennmichael123 wants to merge codex/issue-10-json-events into main
11 files +442 -177
src/reporter.zigmodified+44-58
Changes to src/reporter.zig
@@ -1,6 +1,7 @@
11const std = ;
22const suite = ;
33const compat = ;
4const protocol = ;
45
56pub const ReporterType = enum {
67 spec,
@@ -464,7 +465,7 @@ pub const JsonReporter = struct {
464465 writer: std.Io.Writer,
465466 writer_ref: ?*std.Io.Writer = null,
466467 suites: std.ArrayList([]const u8),
467 emitted_tests: bool = false,
468 emitted_events: bool = false,
468469
469470 const Self = @This();
470471
@@ -508,87 +509,72 @@ pub const JsonReporter = struct {
508509
509510 fn onRunStart(reporter: *Reporter, total_tests: usize) !void {
510511 const s = self(reporter);
511 try s.output().print("{{\"totalTests\":{d},\"tests\":[\n", .{total_tests});
512 s.emitted_events = false;
513 s.suites.clearRetainingCapacity();
514 try s.output().writeAll("[\n");
515 try s.emit(.{ .run_start = .{ .total = total_tests } });
512516 }
513517
514518 fn onRunEnd(reporter: *Reporter, results: *TestResults) !void {
515519 const s = self(reporter);
516 const total_time_ms = @as(f64, @floatFromInt(results.total_time_ns)) / 1_000_000.0;
517
518 try s.output().print("\n],\"summary\":{{\"total\":{d},\"passed\":{d},\"flaky\":{d},\"failed\":{d},\"skipped\":{d},\"time\":{d:.2}}}}}\n", .{
519 results.total,
520 results.passed,
521 results.flaky,
522 results.failed,
523 results.skipped,
524 total_time_ms,
525 });
520 try s.emit(.{ .run_end = .{
521 .total = results.total,
522 .passed = results.passed,
523 .flaky = results.flaky,
524 .failed = results.failed,
525 .skipped = results.skipped,
526 .duration_ns = results.total_time_ns,
527 } });
528 try s.output().writeAll("\n]\n");
526529 }
527530
528531 fn onSuiteStart(reporter: *Reporter, suite_name: []const u8) !void {
529532 const s = self(reporter);
530533 try s.suites.append(reporter.allocator, suite_name);
534 try s.emit(.{ .suite_start = .{ .name = suite_name } });
531535 }
532536
533537 fn onSuiteEnd(reporter: *Reporter, suite_name: []const u8) !void {
534 _ = suite_name;
535538 const s = self(reporter);
539 try s.emit(.{ .suite_end = .{ .name = suite_name } });
536540 if (s.suites.items.len > 0) {
537541 _ = s.suites.pop();
538542 }
539543 }
540544
541545 fn onTestStart(reporter: *Reporter, test_name: []const u8) !void {
542 _ = reporter;
543 _ = test_name;
546 const s = self(reporter);
547 try s.emit(.{ .test_start = .{
548 .name = test_name,
549 .suite = if (s.suites.getLastOrNull()) |name| name else null,
550 } });
544551 }
545552
546553 fn onTestEnd(reporter: *Reporter, test_case: *const suite.TestCase) !void {
547554 const s = self(reporter);
548 const time_ms = @as(f64, @floatFromInt(test_case.execution_time_ns)) / 1_000_000.0;
549
550 const status_str = switch (test_case.status) {
551 .passed => "passed",
552 .flaky => "flaky",
553 .failed => "failed",
554 .skipped => "skipped",
555 else => "unknown",
556 };
557
558 if (s.emitted_tests) try s.output().print(",\n", .{});
559 s.emitted_tests = true;
560
561 // Note: In a real implementation, you'd want to properly escape JSON strings
562 try s.output().print(" {{\"name\":\"{s}\",\"status\":\"{s}\",\"time\":{d:.2}", .{
563 test_case.name,
564 status_str,
565 time_ms,
566 });
567
568 if (test_case.error_message) |msg| {
569 try s.output().print(",\"error\":\"{s}\"", .{msg});
570 }
571
572 try s.output().print(",\"attempts\":[", .{});
573 for (test_case.attempts.items, 0..) |attempt, index| {
574 if (index > 0) try s.output().print(",", .{});
575 const attempt_status = switch (attempt.status) {
576 .passed => "passed",
577 .flaky => "flaky",
578 .failed => "failed",
579 .skipped => "skipped",
580 else => "unknown",
581 };
582 try s.output().print(
583 "{{\"number\":{d},\"repetition\":{d},\"status\":\"{s}\",\"durationNs\":{d}",
584 .{ attempt.number, attempt.repetition, attempt_status, attempt.duration_ns },
585 );
586 if (attempt.error_message) |message| {
587 try s.output().print(",\"error\":\"{s}\"", .{message});
588 }
589 try s.output().print("}}", .{});
555 for (test_case.attempts.items) |attempt| {
556 try s.emit(.{ .retry = .{
557 .name = test_case.name,
558 .attempt = attempt.number,
559 .repetition = attempt.repetition,
560 .status = protocol.status(attempt.status),
561 .duration_ns = attempt.duration_ns,
562 .error_message = attempt.error_message,
563 } });
590564 }
591 try s.output().print("]}}", .{});
565 try s.emit(.{ .test_end = .{
566 .name = test_case.name,
567 .suite = if (s.suites.getLastOrNull()) |name| name else null,
568 .status = protocol.status(test_case.status),
569 .duration_ns = test_case.execution_time_ns,
570 .error_message = test_case.error_message,
571 } });
572 }
573
574 fn emit(s: *Self, event: protocol.Event) !void {
575 if (s.emitted_events) try s.output().writeAll(",\n");
576 s.emitted_events = true;
577 try std.json.Stringify.value(event, .{}, s.output());
592578 }
593579};
594580