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