@@ -12,6 +12,21 @@ var log_err_count: usize = 0;
12
12
var cmdline_buffer : [4096 ]u8 = undefined ;
13
13
var fba = std .heap .FixedBufferAllocator .init (& cmdline_buffer );
14
14
15
+ const Mode = enum {
16
+ listen ,
17
+ terminal ,
18
+ panic_test ,
19
+ };
20
+
21
+ fn callError (args : [][:0 ]u8 ) noreturn {
22
+ std .debug .print ("invalid cli arguments:\n " , .{});
23
+ for (args ) | arg | {
24
+ std .debug .print ("{s} " , .{arg });
25
+ }
26
+ std .debug .print ("\n " , .{});
27
+ @panic ("call error" );
28
+ }
29
+
15
30
pub fn main () void {
16
31
if (builtin .zig_backend == .stage2_aarch64 ) {
17
32
return mainSimple () catch @panic ("test failure" );
@@ -20,20 +35,33 @@ pub fn main() void {
20
35
const args = std .process .argsAlloc (fba .allocator ()) catch
21
36
@panic ("unable to parse command line args" );
22
37
23
- var listen = false ;
38
+ var i : u32 = 1 ;
39
+ var test_i : ? u64 = null ;
40
+ var mode : Mode = .terminal ;
24
41
25
- for (args [1.. ]) | arg | {
26
- if (std .mem .eql (u8 , arg , "--listen=-" )) {
27
- listen = true ;
42
+ while (i < args .len ) : (i += 1 ) {
43
+ if (std .mem .eql (u8 , args [i ], "--listen=-" )) {
44
+ mode = .listen ;
45
+ } else if (std .mem .eql (u8 , args [i ], "--test_panic_index" )) {
46
+ i += 1 ;
47
+ if (i < args .len ) {
48
+ test_i = std .fmt .parseInt (u64 , args [i ], 10 ) catch {
49
+ callError (args );
50
+ };
51
+ mode = .panic_test ;
52
+ std .testing .is_panic_parentproc = false ;
53
+ } else {
54
+ callError (args );
55
+ }
28
56
} else {
29
- @panic ( "unrecognized command line argument" );
57
+ callError ( args );
30
58
}
31
59
}
32
60
33
- if ( listen ) {
34
- return mainServer () catch @panic ("internal test runner failure" );
35
- } else {
36
- return mainTerminal ();
61
+ switch ( mode ) {
62
+ .listen = > return mainServer () catch @panic ("internal test runner failure" ),
63
+ .terminal = > return mainTerminal ( args ),
64
+ .panic_test = > return panicTest ( test_i .? ),
37
65
}
38
66
}
39
67
@@ -130,7 +158,18 @@ fn mainServer() !void {
130
158
}
131
159
}
132
160
133
- fn mainTerminal () void {
161
+ // TODO
162
+ // - [ ] has test_i:
163
+ // * spawn and compare specific function
164
+ // * compare result: if returning from execution => @panic("FoundNoPanicInTest");
165
+ // - [ ] not test_i:
166
+ // * iterate through all functions
167
+ // * compare result: compare execution result with special case for panic msg "FoundNoPanicInTest"
168
+
169
+ fn mainTerminal (args : [][:0 ]const u8 ) void {
170
+ var test_i_buf : [20 ]u8 = undefined ;
171
+ // TODO make environment buffer size configurable and use a sane default
172
+ // Tradeoff: waste stack space or allocate on every panic test
134
173
const test_fn_list = builtin .test_functions ;
135
174
var ok_count : usize = 0 ;
136
175
var skip_count : usize = 0 ;
@@ -146,7 +185,6 @@ fn mainTerminal() void {
146
185
// TODO this is on the next line (using `undefined` above) because otherwise zig incorrectly
147
186
// ignores the alignment of the slice.
148
187
async_frame_buffer = &[_ ]u8 {};
149
-
150
188
var leaks : usize = 0 ;
151
189
for (test_fn_list , 0.. ) | test_fn , i | {
152
190
std .testing .allocator_instance = .{};
@@ -189,9 +227,134 @@ fn mainTerminal() void {
189
227
progress .log ("SKIP\n " , .{});
190
228
test_node .end ();
191
229
},
230
+ error .SpawnZigTest = > {
231
+ progress .log ("error.SpawnZigTest\n " , .{});
232
+ if (! std .testing .can_panic_test )
233
+ @panic ("Found error.SpawnZigTest without panic test capabilities." );
234
+ if (std .testing .panic_msg == null )
235
+ @panic ("Panic test expects `panic_msg` to be set. Use std.testing.spawnExpectPanic()." );
236
+
237
+ const test_i_written = std .fmt .bufPrint (& test_i_buf , "{d}" , .{i }) catch unreachable ;
238
+ var child_proc = std .ChildProcess .init (
239
+ &.{ args [0 ], "--test_panic_index" , test_i_written },
240
+ std .testing .allocator ,
241
+ );
242
+ progress .log ("spawning '{s} {s} {s}'\n " , .{ args [0 ], "--test_panic_index" , test_i_written });
243
+
244
+ child_proc .stdin_behavior = .Ignore ;
245
+ child_proc .stdout_behavior = .Pipe ;
246
+ child_proc .stderr_behavior = .Pipe ;
247
+ child_proc .spawn () catch | spawn_err | {
248
+ progress .log ("FAIL spawn ({s})\n " , .{@errorName (spawn_err )});
249
+ fail_count += 1 ;
250
+ test_node .end ();
251
+ continue ;
252
+ };
253
+
254
+ var stdout = std .ArrayList (u8 ).init (std .testing .allocator );
255
+ defer stdout .deinit ();
256
+ var stderr = std .ArrayList (u8 ).init (std .testing .allocator );
257
+ defer stderr .deinit ();
258
+ // child_process.zig: max_output_bytes: usize = 50 * 1024,
259
+ child_proc .collectOutput (& stdout , & stderr , 50 * 1024 ) catch | collect_err | {
260
+ progress .log ("FAIL collect ({s})\n " , .{@errorName (collect_err )});
261
+ fail_count += 1 ;
262
+ test_node .end ();
263
+ continue ;
264
+ };
265
+ const term = child_proc .wait () catch | wait_err | {
266
+ child_proc .cleanupStreams ();
267
+ progress .log ("FAIL wait_error (exit_status: {d})\n " , .{@errorName (wait_err )});
268
+ fail_count += 1 ;
269
+ test_node .end ();
270
+ continue ;
271
+ };
272
+ switch (term ) {
273
+ .Exited = > | code | {
274
+ progress .log ("FAIL term exited, status: {})\n stdout: ({s})\n stderr: ({s})\n " , .{ code , stdout .items , stderr .items });
275
+ fail_count += 1 ;
276
+ test_node .end ();
277
+ continue ;
278
+ },
279
+ .Signal = > | code | {
280
+ progress .log ("Signal: {d}\n " , .{code });
281
+ // assert: panic message format: 'XYZ thread thread_id panic: msg'
282
+ // Any signal can be returned on panic, if a custom signal
283
+ // or panic handler was installed as part of the unit test.
284
+ var pos_eol : usize = 0 ;
285
+ var found_eol : bool = false ;
286
+ while (pos_eol < stderr .items .len ) : (pos_eol += 1 ) {
287
+ if (stderr .items [pos_eol ] == '\n ' ) {
288
+ found_eol = true ;
289
+ break ;
290
+ }
291
+ }
292
+
293
+ if (! found_eol ) {
294
+ progress .log ("FAIL no end of line in panic format\n stdout: ({s})\n stderr: ({s})\n " , .{ stdout .items , stderr .items });
295
+ fail_count += 1 ;
296
+ test_node .end ();
297
+ continue ;
298
+ }
299
+
300
+ var it = std .mem .tokenize (u8 , stderr .items [0.. pos_eol ], " " );
301
+ var parsed_panic_msg = false ;
302
+ while (it .next ()) | word | { // 'thread thread_id panic: msg'
303
+ if (! std .mem .eql (u8 , word , "thread" )) continue ;
304
+ const thread_id = it .next ();
305
+ if (thread_id == null ) continue ;
306
+ _ = std .fmt .parseInt (u64 , thread_id .? , 10 ) catch continue ;
307
+ const panic_txt = it .next ();
308
+ if (panic_txt == null ) continue ;
309
+ if (! std .mem .eql (u8 , panic_txt .? , "panic:" )) continue ;
310
+ const panic_msg = it .next ();
311
+ if (panic_msg == null ) continue ;
312
+ const panic_msg_start = it .index - panic_msg .? .len ;
313
+ const len_exp_panic_msg = std .mem .len (@as ([* :0 ]u8 , std .testing .panic_msg .? [0.. ]));
314
+ const expected_panic_msg = std .testing .panic_msg .? [0.. len_exp_panic_msg ];
315
+ const panic_msg_end = panic_msg_start + expected_panic_msg .len ;
316
+ if (panic_msg_end > pos_eol ) break ;
317
+
318
+ parsed_panic_msg = true ;
319
+ const current_panic_msg = stderr .items [panic_msg_start .. panic_msg_end ];
320
+
321
+ if (! std .mem .eql (u8 , "SKIP (async test)" , current_panic_msg ) and ! std .mem .eql (u8 , expected_panic_msg , current_panic_msg )) {
322
+ progress .log ("FAIL expected_panic_msg: '{s}', got: '{s}'\n " , .{ expected_panic_msg , current_panic_msg });
323
+ std .testing .panic_msg = null ;
324
+ fail_count += 1 ;
325
+ test_node .end ();
326
+ break ;
327
+ }
328
+ std .testing .panic_msg = null ;
329
+ ok_count += 1 ;
330
+ test_node .end ();
331
+ if (! have_tty ) std .debug .print ("OK\n " , .{});
332
+ break ;
333
+ }
334
+ if (! parsed_panic_msg ) {
335
+ progress .log ("FAIL invalid panic_msg format expect 'XYZ thread thread_id panic: msg'\n stdout: ({s})\n stderr: ({s})\n " , .{ stdout .items , stderr .items });
336
+ fail_count += 1 ;
337
+ test_node .end ();
338
+ continue ;
339
+ }
340
+ },
341
+ .Stopped = > | code | {
342
+ fail_count += 1 ;
343
+ progress .log ("FAIL stopped, status: ({d})\n stdout: ({s})\n stderr: ({s})\n " , .{ code , stdout .items , stderr .items });
344
+ test_node .end ();
345
+ continue ;
346
+ },
347
+ .Unknown = > | code | {
348
+ fail_count += 1 ;
349
+ progress .log ("FAIL unknown, status: ({d})\n stdout: ({s})\n stderr: ({s})\n " , .{ code , stdout .items , stderr .items });
350
+ test_node .end ();
351
+ continue ;
352
+ },
353
+ }
354
+ },
192
355
else = > {
193
356
fail_count += 1 ;
194
- progress .log ("FAIL ({s})\n " , .{@errorName (err )});
357
+ progress .log ("FAIL unexpected error ({s})\n " , .{@errorName (err )});
195
358
if (@errorReturnTrace ()) | trace | {
196
359
std .debug .dumpStackTrace (trace .* );
197
360
}
@@ -216,6 +379,45 @@ fn mainTerminal() void {
216
379
}
217
380
}
218
381
382
+ fn panicTest (test_i : u64 ) void {
383
+ const test_fn_list = builtin .test_functions ;
384
+ var async_frame_buffer : []align (std .Target .stack_align ) u8 = undefined ;
385
+ // TODO this is on the next line (using `undefined` above) because otherwise zig incorrectly
386
+ // ignores the alignment of the slice.
387
+ async_frame_buffer = &[_ ]u8 {};
388
+ {
389
+ std .testing .allocator_instance = .{};
390
+ // custom panic handler to restore to save state and prevent memory
391
+ // leakage is out of scope, so ignore memory leaks
392
+ defer {
393
+ if (std .testing .allocator_instance .deinit () == .leak ) {
394
+ @panic ("internal test runner memory leak" );
395
+ }
396
+ }
397
+ std .testing .log_level = .warn ;
398
+ const result = if (test_fn_list [test_i ].async_frame_size ) | size | switch (std .options .io_mode ) {
399
+ .evented = > blk : {
400
+ if (async_frame_buffer .len < size ) {
401
+ std .heap .page_allocator .free (async_frame_buffer );
402
+ async_frame_buffer = std .heap .page_allocator .alignedAlloc (u8 , std .Target .stack_align , size ) catch @panic ("out of memory" );
403
+ }
404
+ const casted_fn = @ptrCast (fn () callconv (.Async ) anyerror ! void , test_fn_list [test_i ].func );
405
+ break :blk await @asyncCall (async_frame_buffer , {}, casted_fn , .{});
406
+ },
407
+ .blocking = > @panic ("SKIP (async test)" ),
408
+ } else test_fn_list [test_i ].func ();
409
+
410
+ if (result ) {
411
+ std .os .exit (0 );
412
+ } else | err | {
413
+ std .debug .print ("FAIL unexpected error ({s})\n " , .{@errorName (err )});
414
+ if (@errorReturnTrace ()) | trace | {
415
+ std .debug .dumpStackTrace (trace .* );
416
+ }
417
+ }
418
+ }
419
+ }
420
+
219
421
pub fn log (
220
422
comptime message_level : std.log.Level ,
221
423
comptime scope : @Type (.EnumLiteral ),
0 commit comments