also looking at this
Define stable JSON event protocol
#27
11 files
+442
-177
| @@ -3,6 +3,7 @@ const reporter_mod = ; | ||
| 3 | 3 | const suite = ; |
| 4 | 4 | const test_history = ; |
| 5 | 5 | const compat = ; |
| 6 | const protocol = ; | |
| 6 | 7 | |
| 7 | 8 | /// Options for the UI server |
| 8 | 9 | pub const UIServerOptions = struct { |
| @@ -282,13 +283,19 @@ pub const UIServer = struct { | ||
| 282 | 283 | index += 1; |
| 283 | 284 | } |
| 284 | 285 | } |
| 286 | ||
| 287 | /// Serialize and broadcast one versioned protocol event. | |
| 288 | pub fn broadcastEvent(self: *Self, allocator: std.mem.Allocator, event: protocol.Event) !void { | |
| 289 | const encoded = try protocol.encodeAlloc(allocator, event); | |
| 290 | defer allocator.free(encoded); | |
| 291 | try self.broadcast(event.eventName(), encoded); | |
| 292 | } | |
| 285 | 293 | }; |
| 286 | 294 | |
| 287 | 295 | /// UI Reporter - sends test events to the UI server |
| 288 | 296 | pub const UIReporter = struct { |
| 289 | 297 | reporter: reporter_mod.Reporter, |
| 290 | 298 | server: *UIServer, |
| 291 | buffer: std.ArrayList(u8), | |
| 292 | 299 | |
| 293 | 300 | const Self = @This(); |
| 294 | 301 | |
| @@ -307,71 +314,66 @@ pub const UIReporter = struct { | ||
| 307 | 314 | .use_colors = false, |
| 308 | 315 | }, |
| 309 | 316 | .server = server, |
| 310 | .buffer = .empty, | |
| 311 | 317 | }; |
| 312 | 318 | } |
| 313 | 319 | |
| 314 | pub fn deinit(self: *Self) void { | |
| 315 | self.buffer.deinit(self.reporter.allocator); | |
| 316 | } | |
| 317 | ||
| 318 | fn encode(self: *Self, value: anytype) !void { | |
| 319 | self.buffer.clearRetainingCapacity(); | |
| 320 | var writer: std.Io.Writer.Allocating = .fromArrayList(self.reporter.allocator, &self.buffer); | |
| 321 | defer self.buffer = writer.toArrayList(); | |
| 322 | try std.json.Stringify.value(value, .{}, &writer.writer); | |
| 323 | } | |
| 320 | pub fn deinit(_: *Self) void {} | |
| 324 | 321 | |
| 325 | 322 | fn onRunStart(reporter: *reporter_mod.Reporter, total: usize) !void { |
| 326 | 323 | const self: *Self = @fieldParentPtr("reporter", reporter); |
| 327 | try self.encode(.{ .total = total }); | |
| 328 | try self.server.broadcast("run_start", self.buffer.items); | |
| 324 | try self.server.broadcastEvent(reporter.allocator, .{ .run_start = .{ .total = total } }); | |
| 329 | 325 | } |
| 330 | 326 | |
| 331 | 327 | fn onRunEnd(reporter: *reporter_mod.Reporter, results: *reporter_mod.TestResults) !void { |
| 332 | 328 | const self: *Self = @fieldParentPtr("reporter", reporter); |
| 333 | 329 | |
| 334 | try self.encode(.{ | |
| 330 | try self.server.broadcastEvent(reporter.allocator, .{ .run_end = .{ | |
| 335 | 331 | .total = results.total, |
| 336 | 332 | .passed = results.passed, |
| 337 | 333 | .flaky = results.flaky, |
| 338 | 334 | .failed = results.failed, |
| 339 | 335 | .skipped = results.skipped, |
| 340 | }); | |
| 341 | try self.server.broadcast("run_end", self.buffer.items); | |
| 336 | .duration_ns = results.total_time_ns, | |
| 337 | } }); | |
| 342 | 338 | } |
| 343 | 339 | |
| 344 | 340 | fn onSuiteStart(reporter: *reporter_mod.Reporter, suite_name: []const u8) !void { |
| 345 | 341 | const self: *Self = @fieldParentPtr("reporter", reporter); |
| 346 | 342 | |
| 347 | try self.encode(.{ .name = suite_name }); | |
| 348 | try self.server.broadcast("suite_start", self.buffer.items); | |
| 343 | try self.server.broadcastEvent(reporter.allocator, .{ .suite_start = .{ .name = suite_name } }); | |
| 349 | 344 | } |
| 350 | 345 | |
| 351 | 346 | fn onSuiteEnd(reporter: *reporter_mod.Reporter, suite_name: []const u8) !void { |
| 352 | 347 | const self: *Self = @fieldParentPtr("reporter", reporter); |
| 353 | 348 | |
| 354 | try self.encode(.{ .name = suite_name }); | |
| 355 | try self.server.broadcast("suite_end", self.buffer.items); | |
| 349 | try self.server.broadcastEvent(reporter.allocator, .{ .suite_end = .{ .name = suite_name } }); | |
| 356 | 350 | } |
| 357 | 351 | |
| 358 | 352 | fn onTestStart(reporter: *reporter_mod.Reporter, test_name: []const u8) !void { |
| 359 | 353 | const self: *Self = @fieldParentPtr("reporter", reporter); |
| 360 | 354 | |
| 361 | try self.encode(.{ .name = test_name }); | |
| 362 | try self.server.broadcast("test_start", self.buffer.items); | |
| 355 | try self.server.broadcastEvent(reporter.allocator, .{ .test_start = .{ .name = test_name } }); | |
| 363 | 356 | } |
| 364 | 357 | |
| 365 | 358 | fn onTestEnd(reporter: *reporter_mod.Reporter, test_case: *const suite.TestCase) !void { |
| 366 | 359 | const self: *Self = @fieldParentPtr("reporter", reporter); |
| 367 | 360 | |
| 368 | try self.encode(.{ | |
| 361 | for (test_case.attempts.items) |attempt| { | |
| 362 | try self.server.broadcastEvent(reporter.allocator, .{ .retry = .{ | |
| 363 | .name = test_case.name, | |
| 364 | .attempt = attempt.number, | |
| 365 | .repetition = attempt.repetition, | |
| 366 | .status = protocol.status(attempt.status), | |
| 367 | .duration_ns = attempt.duration_ns, | |
| 368 | .error_message = attempt.error_message, | |
| 369 | } }); | |
| 370 | } | |
| 371 | try self.server.broadcastEvent(reporter.allocator, .{ .test_end = .{ | |
| 369 | 372 | .name = test_case.name, |
| 370 | .status = @tagName(test_case.status), | |
| 371 | .execution_time_ns = test_case.execution_time_ns, | |
| 372 | .error_message = test_case.error_message orelse "", | |
| 373 | }); | |
| 374 | try self.server.broadcast("test_end", self.buffer.items); | |
| 373 | .status = protocol.status(test_case.status), | |
| 374 | .duration_ns = test_case.execution_time_ns, | |
| 375 | .error_message = test_case.error_message, | |
| 376 | } }); | |
| 375 | 377 | } |
| 376 | 378 | }; |
| 377 | 379 | |
| @@ -471,10 +473,7 @@ test "UIReporter initialization" { | ||
| 471 | 473 | defer ui_reporter.deinit(); |
| 472 | 474 | |
| 473 | 475 | try std.testing.expectEqual(false, ui_reporter.reporter.use_colors); |
| 474 | try std.testing.expectEqual(@as(usize, 0), ui_reporter.buffer.items.len); | |
| 475 | ||
| 476 | 476 | try ui_reporter.reporter.onSuiteStart("quoted \"suite\"\nname"); |
| 477 | try std.testing.expectEqualStrings("{\"name\":\"quoted \\\"suite\\\"\\nname\"}", ui_reporter.buffer.items); | |
| 478 | 477 | } |
| 479 | 478 | |
| 480 | 479 | fn writeTestRequest(io: std.Io, stream: std.Io.net.Stream, target: []const u8) !void { |
| @@ -535,9 +534,9 @@ test "UI server streams broadcast events over SSE" { | ||
| 535 | 534 | try expectLine(&stream_reader.interface, "data: {}"); |
| 536 | 535 | try expectLine(&stream_reader.interface, ""); |
| 537 | 536 | |
| 538 | try server.broadcast("run_start", "{\"total\":2}"); | |
| 537 | try server.broadcastEvent(std.testing.allocator, .{ .run_start = .{ .total = 2 } }); | |
| 539 | 538 | try expectLine(&stream_reader.interface, "event: run_start"); |
| 540 | try expectLine(&stream_reader.interface, "data: {\"total\":2}"); | |
| 539 | try expectLine(&stream_reader.interface, "data: {\"protocol_version\":1,\"type\":\"run_start\",\"data\":{\"total\":2}}"); | |
| 541 | 540 | try expectLine(&stream_reader.interface, ""); |
| 542 | 541 | } |
| 543 | 542 | |