also looking at this
Unify discovery and programmatic test execution
#23
12 files
+656
-169
| @@ -2,6 +2,14 @@ const std = ; | ||
| 2 | 2 | const suite = ; |
| 3 | 3 | const compat = ; |
| 4 | 4 | |
| 5 | pub const ReporterType = enum { | |
| 6 | spec, | |
| 7 | dot, | |
| 8 | json, | |
| 9 | tap, | |
| 10 | junit, | |
| 11 | }; | |
| 12 | ||
| 5 | 13 | /// ANSI color codes |
| 6 | 14 | pub const Colors = struct { |
| 7 | 15 | pub const reset = "\x1b[0m"; |
| @@ -101,6 +109,7 @@ pub const SpecReporter = struct { | ||
| 101 | 109 | reporter: Reporter, |
| 102 | 110 | indent_level: usize = 0, |
| 103 | 111 | writer: std.Io.Writer, |
| 112 | writer_ref: ?*std.Io.Writer = null, | |
| 104 | 113 | |
| 105 | 114 | const Self = @This(); |
| 106 | 115 | |
| @@ -114,6 +123,16 @@ pub const SpecReporter = struct { | ||
| 114 | 123 | }; |
| 115 | 124 | } |
| 116 | 125 | |
| 126 | pub fn initRef(allocator: std.mem.Allocator, writer: *std.Io.Writer) Self { | |
| 127 | var result = init(allocator, writer.*); | |
| 128 | result.writer_ref = writer; | |
| 129 | return result; | |
| 130 | } | |
| 131 | ||
| 132 | fn output(instance: *Self) *std.Io.Writer { | |
| 133 | return instance.writer_ref orelse &instance.writer; | |
| 134 | } | |
| 135 | ||
| 117 | 136 | const vtable = Reporter.VTable{ |
| 118 | 137 | .onRunStart = onRunStart, |
| 119 | 138 | .onRunEnd = onRunEnd, |
| @@ -129,64 +148,64 @@ pub const SpecReporter = struct { | ||
| 129 | 148 | |
| 130 | 149 | fn onRunStart(reporter: *Reporter, total_tests: usize) !void { |
| 131 | 150 | const s = self(reporter); |
| 132 | try s.writer.print("\n", .{}); | |
| 151 | try s.output().print("\n", .{}); | |
| 133 | 152 | if (reporter.use_colors) { |
| 134 | try s.writer.print("{s}Running {d} test(s)...{s}\n\n", .{ Colors.bold, total_tests, Colors.reset }); | |
| 153 | try s.output().print("{s}Running {d} test(s)...{s}\n\n", .{ Colors.bold, total_tests, Colors.reset }); | |
| 135 | 154 | } else { |
| 136 | try s.writer.print("Running {d} test(s)...\n\n", .{total_tests}); | |
| 155 | try s.output().print("Running {d} test(s)...\n\n", .{total_tests}); | |
| 137 | 156 | } |
| 138 | 157 | } |
| 139 | 158 | |
| 140 | 159 | fn onRunEnd(reporter: *Reporter, results: *TestResults) !void { |
| 141 | 160 | const s = self(reporter); |
| 142 | try s.writer.print("\n", .{}); | |
| 161 | try s.output().print("\n", .{}); | |
| 143 | 162 | |
| 144 | 163 | // Print failed tests details |
| 145 | 164 | if (results.failed > 0) { |
| 146 | 165 | if (reporter.use_colors) { |
| 147 | try s.writer.print("{s}Failed Tests:{s}\n\n", .{ Colors.bold ++ Colors.red, Colors.reset }); | |
| 166 | try s.output().print("{s}Failed Tests:{s}\n\n", .{ Colors.bold ++ Colors.red, Colors.reset }); | |
| 148 | 167 | } else { |
| 149 | try s.writer.print("Failed Tests:\n\n", .{}); | |
| 168 | try s.output().print("Failed Tests:\n\n", .{}); | |
| 150 | 169 | } |
| 151 | 170 | |
| 152 | 171 | for (results.failed_tests.items) |test_case| { |
| 153 | 172 | if (reporter.use_colors) { |
| 154 | try s.writer.print(" {s}✗{s} {s}\n", .{ Colors.red, Colors.reset, test_case.name }); | |
| 173 | try s.output().print(" {s}✗{s} {s}\n", .{ Colors.red, Colors.reset, test_case.name }); | |
| 155 | 174 | } else { |
| 156 | try s.writer.print(" ✗ {s}\n", .{test_case.name}); | |
| 175 | try s.output().print(" ✗ {s}\n", .{test_case.name}); | |
| 157 | 176 | } |
| 158 | 177 | if (test_case.error_message) |msg| { |
| 159 | try s.writer.print(" {s}\n", .{msg}); | |
| 178 | try s.output().print(" {s}\n", .{msg}); | |
| 160 | 179 | } |
| 161 | 180 | } |
| 162 | try s.writer.print("\n", .{}); | |
| 181 | try s.output().print("\n", .{}); | |
| 163 | 182 | } |
| 164 | 183 | |
| 165 | 184 | // Print summary |
| 166 | 185 | const total_time_ms = @as(f64, @floatFromInt(results.total_time_ns)) / 1_000_000.0; |
| 167 | 186 | |
| 168 | 187 | if (reporter.use_colors) { |
| 169 | try s.writer.print("{s}Test Summary:{s}\n", .{ Colors.bold, Colors.reset }); | |
| 170 | try s.writer.print(" Total: {d}\n", .{results.total}); | |
| 171 | try s.writer.print(" {s}Passed: {d}{s}\n", .{ Colors.green, results.passed, Colors.reset }); | |
| 188 | try s.output().print("{s}Test Summary:{s}\n", .{ Colors.bold, Colors.reset }); | |
| 189 | try s.output().print(" Total: {d}\n", .{results.total}); | |
| 190 | try s.output().print(" {s}Passed: {d}{s}\n", .{ Colors.green, results.passed, Colors.reset }); | |
| 172 | 191 | if (results.failed > 0) { |
| 173 | try s.writer.print(" {s}Failed: {d}{s}\n", .{ Colors.red, results.failed, Colors.reset }); | |
| 192 | try s.output().print(" {s}Failed: {d}{s}\n", .{ Colors.red, results.failed, Colors.reset }); | |
| 174 | 193 | } |
| 175 | 194 | if (results.skipped > 0) { |
| 176 | try s.writer.print(" {s}Skipped: {d}{s}\n", .{ Colors.yellow, results.skipped, Colors.reset }); | |
| 195 | try s.output().print(" {s}Skipped: {d}{s}\n", .{ Colors.yellow, results.skipped, Colors.reset }); | |
| 177 | 196 | } |
| 178 | try s.writer.print(" Time: {d:.2}ms\n", .{total_time_ms}); | |
| 197 | try s.output().print(" Time: {d:.2}ms\n", .{total_time_ms}); | |
| 179 | 198 | } else { |
| 180 | try s.writer.print("Test Summary:\n", .{}); | |
| 181 | try s.writer.print(" Total: {d}\n", .{results.total}); | |
| 182 | try s.writer.print(" Passed: {d}\n", .{results.passed}); | |
| 199 | try s.output().print("Test Summary:\n", .{}); | |
| 200 | try s.output().print(" Total: {d}\n", .{results.total}); | |
| 201 | try s.output().print(" Passed: {d}\n", .{results.passed}); | |
| 183 | 202 | if (results.failed > 0) { |
| 184 | try s.writer.print(" Failed: {d}\n", .{results.failed}); | |
| 203 | try s.output().print(" Failed: {d}\n", .{results.failed}); | |
| 185 | 204 | } |
| 186 | 205 | if (results.skipped > 0) { |
| 187 | try s.writer.print(" Skipped: {d}\n", .{results.skipped}); | |
| 206 | try s.output().print(" Skipped: {d}\n", .{results.skipped}); | |
| 188 | 207 | } |
| 189 | try s.writer.print(" Time: {d:.2}ms\n", .{total_time_ms}); | |
| 208 | try s.output().print(" Time: {d:.2}ms\n", .{total_time_ms}); | |
| 190 | 209 | } |
| 191 | 210 | } |
| 192 | 211 | |
| @@ -196,13 +215,13 @@ pub const SpecReporter = struct { | ||
| 196 | 215 | // Print indent |
| 197 | 216 | var i: usize = 0; |
| 198 | 217 | while (i < s.indent_level) : (i += 1) { |
| 199 | try s.writer.print(" ", .{}); | |
| 218 | try s.output().print(" ", .{}); | |
| 200 | 219 | } |
| 201 | 220 | |
| 202 | 221 | if (reporter.use_colors) { |
| 203 | try s.writer.print("{s}{s}{s}\n", .{ Colors.bold, suite_name, Colors.reset }); | |
| 222 | try s.output().print("{s}{s}{s}\n", .{ Colors.bold, suite_name, Colors.reset }); | |
| 204 | 223 | } else { |
| 205 | try s.writer.print("{s}\n", .{suite_name}); | |
| 224 | try s.output().print("{s}\n", .{suite_name}); | |
| 206 | 225 | } |
| 207 | 226 | s.indent_level += 1; |
| 208 | 227 | } |
| @@ -226,7 +245,7 @@ pub const SpecReporter = struct { | ||
| 226 | 245 | // Print indent |
| 227 | 246 | var i: usize = 0; |
| 228 | 247 | while (i < s.indent_level) : (i += 1) { |
| 229 | try s.writer.print(" ", .{}); | |
| 248 | try s.output().print(" ", .{}); | |
| 230 | 249 | } |
| 231 | 250 | |
| 232 | 251 | const time_ms = @as(f64, @floatFromInt(test_case.execution_time_ns)) / 1_000_000.0; |
| @@ -234,7 +253,7 @@ pub const SpecReporter = struct { | ||
| 234 | 253 | switch (test_case.status) { |
| 235 | 254 | .passed => { |
| 236 | 255 | if (reporter.use_colors) { |
| 237 | try s.writer.print("{s}✓{s} {s} {s}({d:.2}ms){s}\n", .{ | |
| 256 | try s.output().print("{s}✓{s} {s} {s}({d:.2}ms){s}\n", .{ | |
| 238 | 257 | Colors.green, |
| 239 | 258 | Colors.reset, |
| 240 | 259 | test_case.name, |
| @@ -243,12 +262,12 @@ pub const SpecReporter = struct { | ||
| 243 | 262 | Colors.reset, |
| 244 | 263 | }); |
| 245 | 264 | } else { |
| 246 | try s.writer.print("✓ {s} ({d:.2}ms)\n", .{ test_case.name, time_ms }); | |
| 265 | try s.output().print("✓ {s} ({d:.2}ms)\n", .{ test_case.name, time_ms }); | |
| 247 | 266 | } |
| 248 | 267 | }, |
| 249 | 268 | .failed => { |
| 250 | 269 | if (reporter.use_colors) { |
| 251 | try s.writer.print("{s}✗{s} {s} {s}({d:.2}ms){s}\n", .{ | |
| 270 | try s.output().print("{s}✗{s} {s} {s}({d:.2}ms){s}\n", .{ | |
| 252 | 271 | Colors.red, |
| 253 | 272 | Colors.reset, |
| 254 | 273 | test_case.name, |
| @@ -257,12 +276,12 @@ pub const SpecReporter = struct { | ||
| 257 | 276 | Colors.reset, |
| 258 | 277 | }); |
| 259 | 278 | } else { |
| 260 | try s.writer.print("✗ {s} ({d:.2}ms)\n", .{ test_case.name, time_ms }); | |
| 279 | try s.output().print("✗ {s} ({d:.2}ms)\n", .{ test_case.name, time_ms }); | |
| 261 | 280 | } |
| 262 | 281 | }, |
| 263 | 282 | .skipped => { |
| 264 | 283 | if (reporter.use_colors) { |
| 265 | try s.writer.print("{s}⊘{s} {s} {s}(skipped){s}\n", .{ | |
| 284 | try s.output().print("{s}⊘{s} {s} {s}(skipped){s}\n", .{ | |
| 266 | 285 | Colors.yellow, |
| 267 | 286 | Colors.reset, |
| 268 | 287 | test_case.name, |
| @@ -270,7 +289,7 @@ pub const SpecReporter = struct { | ||
| 270 | 289 | Colors.reset, |
| 271 | 290 | }); |
| 272 | 291 | } else { |
| 273 | try s.writer.print("⊘ {s} (skipped)\n", .{test_case.name}); | |
| 292 | try s.output().print("⊘ {s} (skipped)\n", .{test_case.name}); | |
| 274 | 293 | } |
| 275 | 294 | }, |
| 276 | 295 | else => {}, |
| @@ -282,6 +301,7 @@ pub const SpecReporter = struct { | ||
| 282 | 301 | pub const DotReporter = struct { |
| 283 | 302 | reporter: Reporter, |
| 284 | 303 | writer: std.Io.Writer, |
| 304 | writer_ref: ?*std.Io.Writer = null, | |
| 285 | 305 | tests_per_line: usize = 80, |
| 286 | 306 | current_line_count: usize = 0, |
| 287 | 307 | |
| @@ -297,6 +317,16 @@ pub const DotReporter = struct { | ||
| 297 | 317 | }; |
| 298 | 318 | } |
| 299 | 319 | |
| 320 | pub fn initRef(allocator: std.mem.Allocator, writer: *std.Io.Writer) Self { | |
| 321 | var result = init(allocator, writer.*); | |
| 322 | result.writer_ref = writer; | |
| 323 | return result; | |
| 324 | } | |
| 325 | ||
| 326 | fn output(instance: *Self) *std.Io.Writer { | |
| 327 | return instance.writer_ref orelse &instance.writer; | |
| 328 | } | |
| 329 | ||
| 300 | 330 | const vtable = Reporter.VTable{ |
| 301 | 331 | .onRunStart = onRunStart, |
| 302 | 332 | .onRunEnd = onRunEnd, |
| @@ -312,21 +342,21 @@ pub const DotReporter = struct { | ||
| 312 | 342 | |
| 313 | 343 | fn onRunStart(reporter: *Reporter, total_tests: usize) !void { |
| 314 | 344 | const s = self(reporter); |
| 315 | try s.writer.print("\nRunning {d} tests:\n", .{total_tests}); | |
| 345 | try s.output().print("\nRunning {d} tests:\n", .{total_tests}); | |
| 316 | 346 | } |
| 317 | 347 | |
| 318 | 348 | fn onRunEnd(reporter: *Reporter, results: *TestResults) !void { |
| 319 | 349 | const s = self(reporter); |
| 320 | try s.writer.print("\n\n", .{}); | |
| 350 | try s.output().print("\n\n", .{}); | |
| 321 | 351 | |
| 322 | 352 | const total_time_ms = @as(f64, @floatFromInt(results.total_time_ns)) / 1_000_000.0; |
| 323 | 353 | |
| 324 | 354 | if (reporter.use_colors) { |
| 325 | try s.writer.print("{s}Passed: {d}{s}, ", .{ Colors.green, results.passed, Colors.reset }); | |
| 326 | try s.writer.print("{s}Failed: {d}{s}, ", .{ Colors.red, results.failed, Colors.reset }); | |
| 327 | try s.writer.print("Total: {d} ({d:.2}ms)\n", .{ results.total, total_time_ms }); | |
| 355 | try s.output().print("{s}Passed: {d}{s}, ", .{ Colors.green, results.passed, Colors.reset }); | |
| 356 | try s.output().print("{s}Failed: {d}{s}, ", .{ Colors.red, results.failed, Colors.reset }); | |
| 357 | try s.output().print("Total: {d} ({d:.2}ms)\n", .{ results.total, total_time_ms }); | |
| 328 | 358 | } else { |
| 329 | try s.writer.print("Passed: {d}, Failed: {d}, Total: {d} ({d:.2}ms)\n", .{ | |
| 359 | try s.output().print("Passed: {d}, Failed: {d}, Total: {d} ({d:.2}ms)\n", .{ | |
| 330 | 360 | results.passed, |
| 331 | 361 | results.failed, |
| 332 | 362 | results.total, |
| @@ -356,23 +386,23 @@ pub const DotReporter = struct { | ||
| 356 | 386 | switch (test_case.status) { |
| 357 | 387 | .passed => { |
| 358 | 388 | if (reporter.use_colors) { |
| 359 | try s.writer.print("{s}.{s}", .{ Colors.green, Colors.reset }); | |
| 389 | try s.output().print("{s}.{s}", .{ Colors.green, Colors.reset }); | |
| 360 | 390 | } else { |
| 361 | try s.writer.print(".", .{}); | |
| 391 | try s.output().print(".", .{}); | |
| 362 | 392 | } |
| 363 | 393 | }, |
| 364 | 394 | .failed => { |
| 365 | 395 | if (reporter.use_colors) { |
| 366 | try s.writer.print("{s}F{s}", .{ Colors.red, Colors.reset }); | |
| 396 | try s.output().print("{s}F{s}", .{ Colors.red, Colors.reset }); | |
| 367 | 397 | } else { |
| 368 | try s.writer.print("F", .{}); | |
| 398 | try s.output().print("F", .{}); | |
| 369 | 399 | } |
| 370 | 400 | }, |
| 371 | 401 | .skipped => { |
| 372 | 402 | if (reporter.use_colors) { |
| 373 | try s.writer.print("{s}S{s}", .{ Colors.yellow, Colors.reset }); | |
| 403 | try s.output().print("{s}S{s}", .{ Colors.yellow, Colors.reset }); | |
| 374 | 404 | } else { |
| 375 | try s.writer.print("S", .{}); | |
| 405 | try s.output().print("S", .{}); | |
| 376 | 406 | } |
| 377 | 407 | }, |
| 378 | 408 | else => {}, |
| @@ -380,7 +410,7 @@ pub const DotReporter = struct { | ||
| 380 | 410 | |
| 381 | 411 | s.current_line_count += 1; |
| 382 | 412 | if (s.current_line_count >= s.tests_per_line) { |
| 383 | try s.writer.print("\n", .{}); | |
| 413 | try s.output().print("\n", .{}); | |
| 384 | 414 | s.current_line_count = 0; |
| 385 | 415 | } |
| 386 | 416 | } |
| @@ -390,6 +420,7 @@ pub const DotReporter = struct { | ||
| 390 | 420 | pub const JsonReporter = struct { |
| 391 | 421 | reporter: Reporter, |
| 392 | 422 | writer: std.Io.Writer, |
| 423 | writer_ref: ?*std.Io.Writer = null, | |
| 393 | 424 | suites: std.ArrayList([]const u8), |
| 394 | 425 | |
| 395 | 426 | const Self = @This(); |
| @@ -405,6 +436,16 @@ pub const JsonReporter = struct { | ||
| 405 | 436 | }; |
| 406 | 437 | } |
| 407 | 438 | |
| 439 | pub fn initRef(allocator: std.mem.Allocator, writer: *std.Io.Writer) Self { | |
| 440 | var result = init(allocator, writer.*); | |
| 441 | result.writer_ref = writer; | |
| 442 | return result; | |
| 443 | } | |
| 444 | ||
| 445 | fn output(instance: *Self) *std.Io.Writer { | |
| 446 | return instance.writer_ref orelse &instance.writer; | |
| 447 | } | |
| 448 | ||
| 408 | 449 | pub fn deinit(s: *Self) void { |
| 409 | 450 | s.suites.deinit(s.reporter.allocator); |
| 410 | 451 | } |
| @@ -424,14 +465,14 @@ pub const JsonReporter = struct { | ||
| 424 | 465 | |
| 425 | 466 | fn onRunStart(reporter: *Reporter, total_tests: usize) !void { |
| 426 | 467 | const s = self(reporter); |
| 427 | try s.writer.print("{{\"totalTests\":{d},\"tests\":[\n", .{total_tests}); | |
| 468 | try s.output().print("{{\"totalTests\":{d},\"tests\":[\n", .{total_tests}); | |
| 428 | 469 | } |
| 429 | 470 | |
| 430 | 471 | fn onRunEnd(reporter: *Reporter, results: *TestResults) !void { |
| 431 | 472 | const s = self(reporter); |
| 432 | 473 | const total_time_ms = @as(f64, @floatFromInt(results.total_time_ns)) / 1_000_000.0; |
| 433 | 474 | |
| 434 | try s.writer.print("],\"summary\":{{\"total\":{d},\"passed\":{d},\"failed\":{d},\"skipped\":{d},\"time\":{d:.2}}}}}\n", .{ | |
| 475 | try s.output().print("],\"summary\":{{\"total\":{d},\"passed\":{d},\"failed\":{d},\"skipped\":{d},\"time\":{d:.2}}}}}\n", .{ | |
| 435 | 476 | results.total, |
| 436 | 477 | results.passed, |
| 437 | 478 | results.failed, |
| @@ -470,17 +511,17 @@ pub const JsonReporter = struct { | ||
| 470 | 511 | }; |
| 471 | 512 | |
| 472 | 513 | // Note: In a real implementation, you'd want to properly escape JSON strings |
| 473 | try s.writer.print(" {{\"name\":\"{s}\",\"status\":\"{s}\",\"time\":{d:.2}", .{ | |
| 514 | try s.output().print(" {{\"name\":\"{s}\",\"status\":\"{s}\",\"time\":{d:.2}", .{ | |
| 474 | 515 | test_case.name, |
| 475 | 516 | status_str, |
| 476 | 517 | time_ms, |
| 477 | 518 | }); |
| 478 | 519 | |
| 479 | 520 | if (test_case.error_message) |msg| { |
| 480 | try s.writer.print(",\"error\":\"{s}\"", .{msg}); | |
| 521 | try s.output().print(",\"error\":\"{s}\"", .{msg}); | |
| 481 | 522 | } |
| 482 | 523 | |
| 483 | try s.writer.print("}},\n", .{}); | |
| 524 | try s.output().print("}},\n", .{}); | |
| 484 | 525 | } |
| 485 | 526 | }; |
| 486 | 527 | |
| @@ -488,6 +529,7 @@ pub const JsonReporter = struct { | ||
| 488 | 529 | pub const TAPReporter = struct { |
| 489 | 530 | reporter: Reporter, |
| 490 | 531 | writer: std.Io.Writer, |
| 532 | writer_ref: ?*std.Io.Writer = null, | |
| 491 | 533 | test_count: usize = 0, |
| 492 | 534 | |
| 493 | 535 | const Self = @This(); |
| @@ -510,9 +552,19 @@ pub const TAPReporter = struct { | ||
| 510 | 552 | }; |
| 511 | 553 | } |
| 512 | 554 | |
| 555 | pub fn initRef(allocator: std.mem.Allocator, writer: *std.Io.Writer) Self { | |
| 556 | var result = init(allocator, writer.*); | |
| 557 | result.writer_ref = writer; | |
| 558 | return result; | |
| 559 | } | |
| 560 | ||
| 561 | fn output(instance: *Self) *std.Io.Writer { | |
| 562 | return instance.writer_ref orelse &instance.writer; | |
| 563 | } | |
| 564 | ||
| 513 | 565 | fn onRunStart(reporter: *Reporter, total: usize) !void { |
| 514 | 566 | const self: *Self = @fieldParentPtr("reporter", reporter); |
| 515 | try self.writer.print("TAP version 14\n1..{d}\n", .{total}); | |
| 567 | try self.output().print("TAP version 14\n1..{d}\n", .{total}); | |
| 516 | 568 | } |
| 517 | 569 | |
| 518 | 570 | fn onRunEnd(reporter: *Reporter, results: *TestResults) !void { |
| @@ -522,7 +574,7 @@ pub const TAPReporter = struct { | ||
| 522 | 574 | |
| 523 | 575 | fn onSuiteStart(reporter: *Reporter, suite_name: []const u8) !void { |
| 524 | 576 | const self: *Self = @fieldParentPtr("reporter", reporter); |
| 525 | try self.writer.print("# Subtest: {s}\n", .{suite_name}); | |
| 577 | try self.output().print("# Subtest: {s}\n", .{suite_name}); | |
| 526 | 578 | } |
| 527 | 579 | |
| 528 | 580 | fn onSuiteEnd(reporter: *Reporter, suite_name: []const u8) !void { |
| @@ -546,14 +598,14 @@ pub const TAPReporter = struct { | ||
| 546 | 598 | else => "not ok", |
| 547 | 599 | }; |
| 548 | 600 | |
| 549 | try self.writer.print("{s} {d} - {s}", .{ status, self.test_count, test_case.name }); | |
| 601 | try self.output().print("{s} {d} - {s}", .{ status, self.test_count, test_case.name }); | |
| 550 | 602 | |
| 551 | 603 | if (test_case.status == .skipped) { |
| 552 | try self.writer.print(" # SKIP\n", .{}); | |
| 604 | try self.output().print(" # SKIP\n", .{}); | |
| 553 | 605 | } else if (test_case.status == .failed and test_case.error_message != null) { |
| 554 | try self.writer.print("\n ---\n message: {s}\n ...\n", .{test_case.error_message.?}); | |
| 606 | try self.output().print("\n ---\n message: {s}\n ...\n", .{test_case.error_message.?}); | |
| 555 | 607 | } else { |
| 556 | try self.writer.print("\n", .{}); | |
| 608 | try self.output().print("\n", .{}); | |
| 557 | 609 | } |
| 558 | 610 | } |
| 559 | 611 | }; |
| @@ -735,6 +787,80 @@ pub const JUnitReporter = struct { | ||
| 735 | 787 | } |
| 736 | 788 | }; |
| 737 | 789 | |
| 790 | /// Owns every built-in reporter and selects one through a shared interface. | |
| 791 | /// Keeping construction here lets every executor expose identical reporters. | |
| 792 | pub const ReporterSet = struct { | |
| 793 | kind: ReporterType, | |
| 794 | spec: SpecReporter, | |
| 795 | dot: DotReporter, | |
| 796 | json: JsonReporter, | |
| 797 | tap: TAPReporter, | |
| 798 | junit: JUnitReporter, | |
| 799 | ||
| 800 | pub fn init( | |
| 801 | allocator: std.mem.Allocator, | |
| 802 | writer: std.Io.Writer, | |
| 803 | kind: ReporterType, | |
| 804 | junit_output: []const u8, | |
| 805 | use_colors: bool, | |
| 806 | ) ReporterSet { | |
| 807 | var reporters = ReporterSet{ | |
| 808 | .kind = kind, | |
| 809 | .spec = SpecReporter.init(allocator, writer), | |
| 810 | .dot = DotReporter.init(allocator, writer), | |
| 811 | .json = JsonReporter.init(allocator, writer), | |
| 812 | .tap = TAPReporter.init(allocator, writer), | |
| 813 | .junit = JUnitReporter.init(allocator, junit_output), | |
| 814 | }; | |
| 815 | reporters.selected().use_colors = use_colors; | |
| 816 | return reporters; | |
| 817 | } | |
| 818 | ||
| 819 | pub fn initRef( | |
| 820 | allocator: std.mem.Allocator, | |
| 821 | writer: *std.Io.Writer, | |
| 822 | kind: ReporterType, | |
| 823 | junit_output: []const u8, | |
| 824 | use_colors: bool, | |
| 825 | ) ReporterSet { | |
| 826 | var reporters = ReporterSet{ | |
| 827 | .kind = kind, | |
| 828 | .spec = SpecReporter.initRef(allocator, writer), | |
| 829 | .dot = DotReporter.initRef(allocator, writer), | |
| 830 | .json = JsonReporter.initRef(allocator, writer), | |
| 831 | .tap = TAPReporter.initRef(allocator, writer), | |
| 832 | .junit = JUnitReporter.init(allocator, junit_output), | |
| 833 | }; | |
| 834 | reporters.selected().use_colors = use_colors; | |
| 835 | return reporters; | |
| 836 | } | |
| 837 | ||
| 838 | pub fn deinit(self: *ReporterSet) void { | |
| 839 | self.json.deinit(); | |
| 840 | self.junit.deinit(); | |
| 841 | } | |
| 842 | ||
| 843 | pub fn selected(self: *ReporterSet) *Reporter { | |
| 844 | return switch (self.kind) { | |
| 845 | .spec => &self.spec.reporter, | |
| 846 | .dot => &self.dot.reporter, | |
| 847 | .json => &self.json.reporter, | |
| 848 | .tap => &self.tap.reporter, | |
| 849 | .junit => &self.junit.reporter, | |
| 850 | }; | |
| 851 | } | |
| 852 | ||
| 853 | pub fn flush(self: *ReporterSet) !void { | |
| 854 | switch (self.kind) { | |
| 855 | .spec => try self.spec.output().flush(), | |
| 856 | .dot => try self.dot.output().flush(), | |
| 857 | .json => try self.json.output().flush(), | |
| 858 | .tap => try self.tap.output().flush(), | |
| 859 | .junit => {}, | |
| 860 | } | |
| 861 | } | |
| 862 | }; | |
| 863 | ||
| 738 | 864 | // Tests |
| 739 | 865 | test "Colors constants exist" { |
| 740 | 866 | // Just verify the constants are defined |