╭ test_runner.zig ──────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭ find file ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ ╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │ │ │http│ ssh │ │ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1 //! Default test runner for unit tests. 2 const builtin = @import("builtin"); 3 4 const std = @import("std"); 5 const Io = std.Io; 6 const fatal = std.process.fatal; 7 const testing = std.testing; 8 const assert = std.debug.assert; 9 const panic = std.debug.panic; 10 const fuzz_abi = std.Build.abi.fuzz; 11 12 pub const std_options: std.Options = .{ 13 .logFn = log, 14 }; 15 16 var log_err_count: usize = 0; 17 var fba: std.heap.FixedBufferAllocator = .init(&fba_buffer); 18 var fba_buffer: [8192]u8 = undefined; 19 var stdin_buffer: [4096]u8 = undefined; 20 var stdout_buffer: [4096]u8 = undefined; 21 var stdin_reader: Io.File.Reader = undefined; 22 var stdout_writer: Io.File.Writer = undefined; 23 const runner_threaded_io: Io = Io.Threaded.global_single_threaded.io(); 24 25 /// Keep in sync with logic in `std.Build.addRunArtifact` which decides whether 26 /// the test runner will communicate with the build runner via `std.zig.Server`. 27 const need_simple = switch (builtin.zig_backend) { 28 .stage2_aarch64, 29 .stage2_loongarch, 30 .stage2_powerpc, 31 .stage2_riscv64, 32 => true, 33 else => false, 34 }; 35 36 pub fn main(init: std.process.Init.Minimal) void { 37 @disableInstrumentation(); 38 39 if (builtin.cpu.arch.isSpirV()) { 40 // SPIR-V needs an special test-runner 41 return; 42 } 43 44 if (need_simple) { 45 return mainSimple() catch |err| panic("test failure: {t}", .{err}); 46 } 47 48 const args = init.args.toSlice(fba.allocator()) catch |err| panic("unable to parse command line args: {t}", .{err}); 49 50 var listen = false; 51 var opt_cache_dir: ?[]const u8 = null; 52 53 for (args[1..]) |arg| { 54 if (std.mem.eql(u8, arg, "--listen=-")) { 55 listen = true; 56 } else if (std.mem.startsWith(u8, arg, "--seed=")) { 57 testing.random_seed = std.fmt.parseUnsigned(u32, arg["--seed=".len..], 0) catch 58 @panic("unable to parse --seed command line argument"); 59 } else if (std.mem.startsWith(u8, arg, "--cache-dir")) { 60 opt_cache_dir = arg["--cache-dir=".len..]; 61 } else { 62 panic("unrecognized command line argument: {s}", .{arg}); 63 } 64 } 65 66 if (builtin.fuzz) { 67 const cache_dir = opt_cache_dir orelse @panic("missing --cache-dir=[path] argument"); 68 fuzz_abi.fuzzer_init(.fromSlice(cache_dir)); 69 } 70 71 if (listen) { 72 return mainServer(init) catch |err| panic("internal test runner failure: {t}", .{err}); 73 } else { 74 return mainTerminal(init); 75 } 76 } 77 78 fn mainServer(init: std.process.Init.Minimal) !void { 79 @disableInstrumentation(); 80 stdin_reader = .initStreaming(.stdin(), runner_threaded_io, &stdin_buffer); 81 stdout_writer = .initStreaming(.stdout(), runner_threaded_io, &stdout_buffer); 82 var server: std.zig.Server = .{ 83 .in = &stdin_reader.interface, 84 .out = &stdout_writer.interface, 85 }; 86 try server.serveStringMessage(.zig_version, builtin.zig_version_string); 87 88 while (true) { 89 const hdr = try server.receiveMessage(); 90 switch (hdr.tag) { 91 .exit => { 92 return std.process.exit(0); 93 }, 94 .query_test_metadata => { 95 var sa: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); 96 defer if (sa.deinit() != 0) @panic("internal test runner memory leak"); 97 const gpa = sa.allocator(); 98 99 var string_bytes: std.ArrayList(u8) = .empty; 100 defer string_bytes.deinit(gpa); 101 try string_bytes.append(gpa, 0); // Reserve 0 for null. 102 103 const test_fns = builtin.test_functions; 104 const names = try gpa.alloc(u32, test_fns.len); 105 defer gpa.free(names); 106 const expected_panic_msgs = try gpa.alloc(u32, test_fns.len); 107 defer gpa.free(expected_panic_msgs); 108 109 for (test_fns, names, expected_panic_msgs) |test_fn, *name, *expected_panic_msg| { 110 name.* = @intCast(string_bytes.items.len); 111 try string_bytes.ensureUnusedCapacity(gpa, test_fn.name.len + 1); 112 string_bytes.appendSliceAssumeCapacity(test_fn.name); 113 string_bytes.appendAssumeCapacity(0); 114 expected_panic_msg.* = 0; 115 } 116 117 try server.serveTestMetadata(.{ 118 .names = names, 119 .expected_panic_msgs = expected_panic_msgs, 120 .string_bytes = string_bytes.items, 121 }); 122 }, 123 124 .run_test => { 125 testing.environ = init.environ; 126 testing.allocator_instance = .init(std.heap.page_allocator, .{ 127 .canary = 0xc3a701ba, 128 .check_write_after_free = true, 129 }); 130 testing.io_instance = .init(testing.allocator, .{ 131 .argv0 = .init(init.args), 132 .environ = init.environ, 133 }); 134 log_err_count = 0; 135 const index = try server.receiveBody_u32(); 136 const test_fn = builtin.test_functions[index]; 137 is_fuzz_test = false; 138 139 // let the build server know we're starting the test now 140 try server.serveStringMessage(.test_started, &.{}); 141 142 const TestResults = std.zig.Server.Message.TestResults; 143 const status: TestResults.Status = if (test_fn.func()) |v| s: { 144 v; 145 break :s .pass; 146 } else |err| switch (err) { 147 error.SkipZigTest => .skip, 148 else => s: { 149 if (@errorReturnTrace()) |trace| { 150 std.debug.dumpErrorReturnTrace(trace); 151 } 152 break :s .fail; 153 }, 154 }; 155 testing.io_instance.deinit(); 156 const leak_count = testing.allocator_instance.deinit(); 157 try server.serveTestResults(.{ 158 .index = index, 159 .flags = .{ 160 .status = status, 161 .fuzz = is_fuzz_test, 162 .log_err_count = std.math.lossyCast( 163 @FieldType(TestResults.Flags, "log_err_count"), 164 log_err_count, 165 ), 166 .leak_count = std.math.lossyCast( 167 @FieldType(TestResults.Flags, "leak_count"), 168 leak_count, 169 ), 170 }, 171 }); 172 }, 173 .start_fuzzing => { 174 // This ensures that this code won't be analyzed and hence reference fuzzer symbols 175 // since they are not present. 176 if (!builtin.fuzz) unreachable; 177 178 var gpa_instance: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); 179 defer if (gpa_instance.deinit() != 0) { 180 @panic("internal test runner memory leak"); 181 }; 182 const gpa = gpa_instance.allocator(); 183 var io_instance: Io.Threaded = .init(gpa, .{ 184 .argv0 = .init(init.args), 185 .environ = init.environ, 186 }); 187 defer io_instance.deinit(); 188 189 const mode: fuzz_abi.LimitKind = @fromBackingInt(@intCast(try server.receiveBody_u8())); 190 const amount_or_instance = try server.receiveBody_u64(); 191 const main_instance = mode == .iterations or amount_or_instance == 0; 192 193 if (main_instance) { 194 const coverage = fuzz_abi.fuzzer_coverage(); 195 try server.serveCoverageIdMessage( 196 coverage.id, 197 coverage.runs, 198 coverage.unique, 199 coverage.seen, 200 ); 201 } 202 203 const n_tests: u32 = try server.receiveBody_u32(); 204 const test_indexes = try gpa.alloc(u32, n_tests); 205 defer gpa.free(test_indexes); 206 fuzz_runner = .{ 207 .indexes = test_indexes, 208 .server = &server, 209 .gpa = gpa, 210 .threaded_io = &io_instance, 211 .input_poller = undefined, 212 }; 213 214 { 215 var large_name_buf: std.ArrayList(u8) = .empty; 216 defer large_name_buf.deinit(gpa); 217 for (test_indexes) |*i| { 218 const name_len = try server.receiveBody_u32(); 219 const name = if (name_len <= server.in.buffer.len) 220 try server.in.take(name_len) 221 else large_name: { 222 try large_name_buf.resize(gpa, name_len); 223 try server.in.readSliceAll(large_name_buf.items); 224 break :large_name large_name_buf.items; 225 }; 226 227 for (0.., builtin.test_functions) |test_i, test_fn| { 228 if (std.mem.eql(u8, name, test_fn.name)) { 229 i.* = @intCast(test_i); 230 break; 231 } 232 } else { 233 panic("fuzz test {s} no longer exists", .{name}); 234 } 235 236 if (main_instance) { 237 const relocated_entry_addr = @intFromPtr(builtin.test_functions[i.*].func); 238 const entry_addr = fuzz_abi.fuzzer_unslide_address(relocated_entry_addr); 239 try server.serveU64Message(.fuzz_start_addr, entry_addr); 240 } 241 } 242 } 243 244 fuzz_abi.fuzzer_main(n_tests, testing.random_seed, mode, amount_or_instance); 245 246 assert(mode != .forever); 247 std.process.exit(0); 248 }, 249 250 else => { 251 std.debug.print("unsupported message: {x}\n", .{@backingInt(hdr.tag)}); 252 std.process.exit(1); 253 }, 254 } 255 } 256 } 257 258 fn mainTerminal(init: std.process.Init.Minimal) void { 259 @disableInstrumentation(); 260 if (builtin.fuzz) @panic("fuzz test requires server"); 261 262 const test_fn_list = builtin.test_functions; 263 var ok_count: usize = 0; 264 var skip_count: usize = 0; 265 var fail_count: usize = 0; 266 var fuzz_count: usize = 0; 267 const root_node = if (builtin.fuzz) std.Progress.Node.none else std.Progress.start(runner_threaded_io, .{ 268 .root_name = "Test", 269 .estimated_total_items = test_fn_list.len, 270 }); 271 const have_tty = Io.File.stderr().isTty(runner_threaded_io) catch unreachable; 272 273 var leaks: usize = 0; 274 for (test_fn_list, 0..) |test_fn, i| { 275 testing.allocator_instance = .init(std.heap.page_allocator, .{ 276 .canary = 0xc3a701ba, 277 .check_write_after_free = true, 278 }); 279 testing.io_instance = .init(testing.allocator, .{ 280 .argv0 = .init(init.args), 281 .environ = init.environ, 282 }); 283 defer { 284 testing.io_instance.deinit(); 285 if (testing.allocator_instance.deinit() != 0) leaks += 1; 286 } 287 testing.log_level = .warn; 288 testing.environ = init.environ; 289 290 const test_node = root_node.start(test_fn.name, 0); 291 if (!have_tty) { 292 std.debug.print("{d}/{d} {s}...", .{ i + 1, test_fn_list.len, test_fn.name }); 293 } 294 is_fuzz_test = false; 295 if (test_fn.func()) |_| { 296 ok_count += 1; 297 test_node.end(); 298 if (!have_tty) std.debug.print("OK\n", .{}); 299 } else |err| switch (err) { 300 error.SkipZigTest => { 301 skip_count += 1; 302 if (have_tty) { 303 std.debug.print("{d}/{d} {s}...SKIP\n", .{ i + 1, test_fn_list.len, test_fn.name }); 304 } else { 305 std.debug.print("SKIP\n", .{}); 306 } 307 test_node.end(); 308 }, 309 else => { 310 fail_count += 1; 311 if (have_tty) { 312 std.debug.print("{d}/{d} {s}...FAIL ({t})\n", .{ 313 i + 1, test_fn_list.len, test_fn.name, err, 314 }); 315 } else { 316 std.debug.print("FAIL ({t})\n", .{err}); 317 } 318 if (@errorReturnTrace()) |trace| { 319 std.debug.dumpErrorReturnTrace(trace); 320 } 321 test_node.end(); 322 }, 323 } 324 fuzz_count += @intFromBool(is_fuzz_test); 325 } 326 root_node.end(); 327 if (ok_count == test_fn_list.len) { 328 std.debug.print("All {d} tests passed.\n", .{ok_count}); 329 } else { 330 std.debug.print("{d} passed; {d} skipped; {d} failed.\n", .{ ok_count, skip_count, fail_count }); 331 } 332 if (log_err_count != 0) { 333 std.debug.print("{d} errors were logged.\n", .{log_err_count}); 334 } 335 if (leaks != 0) { 336 std.debug.print("{d} tests leaked memory.\n", .{leaks}); 337 } 338 if (fuzz_count != 0) { 339 std.debug.print("{d} fuzz tests found.\n", .{fuzz_count}); 340 } 341 if (leaks != 0 or log_err_count != 0 or fail_count != 0) { 342 std.process.exit(1); 343 } 344 } 345 346 pub fn log( 347 comptime message_level: std.log.Level, 348 comptime scope: @EnumLiteral(), 349 comptime format: []const u8, 350 args: anytype, 351 ) void { 352 @disableInstrumentation(); 353 if (@backingInt(message_level) <= @backingInt(std.log.Level.err)) { 354 log_err_count +|= 1; 355 } 356 if (@backingInt(message_level) <= @backingInt(testing.log_level)) { 357 std.debug.print( 358 "[" ++ @tagName(scope) ++ "] (" ++ @tagName(message_level) ++ "): " ++ format ++ "\n", 359 args, 360 ); 361 } 362 } 363 364 /// Simpler main(), exercising fewer language features, so that 365 /// work-in-progress backends can handle it. 366 pub fn mainSimple() anyerror!void { 367 @disableInstrumentation(); 368 // is the backend capable of calling `Io.File.writeAll`? 369 const enable_write = switch (builtin.zig_backend) { 370 .stage2_aarch64, .stage2_riscv64 => true, 371 else => false, 372 }; 373 // is the backend capable of calling `Io.Writer.print`? 374 const enable_print = switch (builtin.zig_backend) { 375 .stage2_aarch64, .stage2_riscv64 => true, 376 else => false, 377 }; 378 379 testing.allocator_instance = .init(std.heap.page_allocator, .{}); 380 testing.io_instance = .init(testing.allocator, .{}); 381 382 var passed: u64 = 0; 383 var skipped: u64 = 0; 384 var failed: u64 = 0; 385 386 // we don't want to bring in File and Writer if the backend doesn't support it 387 const stdout = if (enable_write) Io.File.stdout() else {}; 388 389 for (builtin.test_functions) |test_fn| { 390 if (enable_write) { 391 stdout.writeStreamingAll(runner_threaded_io, test_fn.name) catch {}; 392 stdout.writeStreamingAll(runner_threaded_io, "... ") catch {}; 393 } 394 if (test_fn.func()) |_| { 395 if (enable_write) stdout.writeStreamingAll(runner_threaded_io, "PASS\n") catch {}; 396 } else |err| { 397 if (err != error.SkipZigTest) { 398 if (enable_write) stdout.writeStreamingAll(runner_threaded_io, "FAIL\n") catch {}; 399 failed += 1; 400 if (!enable_write) return err; 401 continue; 402 } 403 if (enable_write) stdout.writeStreamingAll(runner_threaded_io, "SKIP\n") catch {}; 404 skipped += 1; 405 continue; 406 } 407 passed += 1; 408 } 409 if (enable_print) { 410 var unbuffered_stdout_writer = stdout.writer(runner_threaded_io, &.{}); 411 unbuffered_stdout_writer.interface.print( 412 "{} passed, {} skipped, {} failed\n", 413 .{ passed, skipped, failed }, 414 ) catch {}; 415 } 416 if (failed != 0) std.process.exit(1); 417 } 418 419 var is_fuzz_test: bool = undefined; 420 var fuzz_runner: if (builtin.fuzz) struct { 421 indexes: []u32, 422 server: *std.zig.Server, 423 gpa: std.mem.Allocator, 424 threaded_io: *Io.Threaded, 425 input_poller: Io.Future(Io.Cancelable!void), 426 427 comptime { 428 assert(builtin.fuzz); // `fuzz_runner` was analyzed in non-fuzzing compilation 429 } 430 431 export fn runner_test_run(i: u32) void { 432 @disableInstrumentation(); 433 434 fuzz_runner.server.serveU32Message(.fuzz_test_change, i) catch |e| switch (e) { 435 error.WriteFailed => panic("failed to write to stdout: {t}", .{stdout_writer.err.?}), 436 }; 437 438 testing.allocator_instance = .init(std.heap.page_allocator, .{ 439 .canary = 0xc3a701ba, 440 .check_write_after_free = true, 441 }); 442 defer if (testing.allocator_instance.deinit() != 0) std.process.exit(1); 443 is_fuzz_test = false; 444 445 testing.io_instance = .init(testing.allocator, .{ 446 .argv0 = fuzz_runner.threaded_io.argv0, 447 .environ = fuzz_runner.threaded_io.environ.process_environ, 448 }); 449 defer testing.io_instance.deinit(); 450 451 builtin.test_functions[fuzz_runner.indexes[i]].func() catch |err| switch (err) { 452 error.SkipZigTest => return, 453 else => { 454 if (@errorReturnTrace()) |trace| { 455 std.debug.dumpErrorReturnTrace(trace); 456 } 457 std.debug.print("failed with error.{t}\n", .{err}); 458 std.process.exit(1); 459 }, 460 }; 461 462 if (!is_fuzz_test) @panic("missed call to std.testing.fuzz"); 463 if (log_err_count != 0) @panic("error logs detected"); 464 } 465 466 export fn runner_test_name(i: u32) fuzz_abi.Slice { 467 @disableInstrumentation(); 468 return .fromSlice(builtin.test_functions[fuzz_runner.indexes[i]].name); 469 } 470 471 export fn runner_broadcast_input(test_i: u32, bytes_slice: fuzz_abi.Slice) void { 472 @disableInstrumentation(); 473 const bytes = bytes_slice.toSlice(); 474 fuzz_runner.server.serveBroadcastFuzzInputMessage(test_i, bytes) catch |e| switch (e) { 475 error.WriteFailed => panic("failed to write to stdout: {t}", .{stdout_writer.err.?}), 476 }; 477 } 478 479 export fn runner_start_input_poller() void { 480 @disableInstrumentation(); 481 const io = fuzz_runner.threaded_io.io(); 482 const future = io.concurrent(inputPoller, .{}) catch |e| switch (e) { 483 error.ConcurrencyUnavailable => @panic("failed to spawn concurrent fuzz input poller"), 484 }; 485 fuzz_runner.input_poller = future; 486 } 487 488 export fn runner_stop_input_poller() void { 489 @disableInstrumentation(); 490 const io = fuzz_runner.threaded_io.io(); 491 assert(fuzz_runner.input_poller.cancel(io) == error.Canceled); 492 } 493 494 export fn runner_futex_wait(ptr: *const u32, expected: u32) bool { 495 @disableInstrumentation(); 496 const io = fuzz_runner.threaded_io.io(); 497 return io.futexWait(u32, ptr, expected) == error.Canceled; 498 } 499 500 export fn runner_futex_wake(ptr: *const u32, waiters: u32) void { 501 @disableInstrumentation(); 502 const io = fuzz_runner.threaded_io.io(); 503 io.futexWake(u32, ptr, waiters); 504 } 505 506 fn inputPoller() Io.Cancelable!void { 507 @disableInstrumentation(); 508 switch (inputPollerInner()) { 509 error.Canceled => |e| return e, 510 error.ReadFailed => { 511 if (stdin_reader.err.? == error.Canceled) return error.Canceled; 512 panic("failed to read from stdin: {t}", .{stdin_reader.err.?}); 513 }, 514 error.EndOfStream => @panic("unexpected end of stdin"), 515 } 516 } 517 518 fn inputPollerInner() (Io.Cancelable || Io.Reader.Error) { 519 @disableInstrumentation(); 520 const server = fuzz_runner.server; 521 var large_bytes_list: std.ArrayList(u8) = .empty; 522 defer large_bytes_list.deinit(fuzz_runner.gpa); 523 while (true) { 524 const hdr = try server.receiveMessage(); 525 if (hdr.tag != .new_fuzz_input) { 526 panic("unexpected message: {x}\n", .{@backingInt(hdr.tag)}); 527 } 528 const test_i = try server.receiveBody_u32(); 529 const input_len = hdr.bytes_len - 4; 530 const bytes = if (input_len <= server.in.buffer.len) 531 try server.in.take(input_len) 532 else bytes: { 533 large_bytes_list.resize(fuzz_runner.gpa, @intCast(input_len)) catch @panic("OOM"); 534 try server.in.readSliceAll(large_bytes_list.items); 535 break :bytes large_bytes_list.items; 536 }; 537 if (fuzz_abi.fuzzer_receive_input(test_i, .fromSlice(bytes))) { 538 return error.Canceled; 539 } 540 } 541 } 542 } else void = undefined; 543 544 pub fn fuzz( 545 context: anytype, 546 comptime testOne: fn (context: @TypeOf(context), *std.testing.Smith) anyerror!void, 547 options: testing.FuzzInputOptions, 548 ) anyerror!void { 549 // Prevent this function from confusing the fuzzer by omitting its own code 550 // coverage from being considered. 551 @disableInstrumentation(); 552 553 // Some compiler backends are not capable of handling fuzz testing yet but 554 // we still want CI test coverage enabled. 555 if (need_simple) return; 556 557 // Smoke test to ensure the test did not use conditional compilation to 558 // contradict itself by making it not actually be a fuzz test when the test 559 // is built in fuzz mode. 560 is_fuzz_test = true; 561 562 // Ensure no test failure occurred before starting fuzzing. 563 if (log_err_count != 0) @panic("error logs detected"); 564 565 // libfuzzer is in a separate compilation unit so that its own code can be 566 // excluded from code coverage instrumentation. It needs a function pointer 567 // it can call for checking exactly one input. Inside this function we do 568 // our standard unit test checks such as memory leaks, and interaction with 569 // error logs. 570 const global = struct { 571 var ctx: @TypeOf(context) = undefined; 572 573 fn test_one() callconv(.c) bool { 574 @disableInstrumentation(); 575 testing.allocator_instance = .init(std.heap.page_allocator, .{ 576 .canary = 0xcacce5e0, 577 .check_write_after_free = true, 578 }); 579 defer if (testing.allocator_instance.deinit() != 0) std.process.exit(1); 580 log_err_count = 0; 581 testOne(ctx, @constCast(&testing.Smith{ .in = null })) catch |err| switch (err) { 582 error.SkipZigTest => return true, 583 else => { 584 const stderr = std.debug.lockStderr(&.{}).terminal(); 585 p: { 586 if (@errorReturnTrace()) |trace| { 587 std.debug.writeErrorReturnTrace(trace, stderr) catch break :p; 588 } 589 stderr.writer.print("failed with error.{t}\n", .{err}) catch break :p; 590 } 591 std.process.exit(1); 592 }, 593 }; 594 if (log_err_count != 0) { 595 const stderr = std.debug.lockStderr(&.{}).terminal(); 596 stderr.writer.print("error logs detected\n", .{}) catch {}; 597 std.process.exit(1); 598 } 599 return false; 600 } 601 }; 602 603 if (builtin.fuzz) { 604 // Preserve the calling test's allocator state 605 const prev_allocator_state = testing.allocator_instance; 606 defer testing.allocator_instance = prev_allocator_state; 607 608 global.ctx = context; 609 fuzz_abi.fuzzer_set_test(&global.test_one); 610 for (options.corpus) |elem| 611 fuzz_abi.fuzzer_new_input(.fromSlice(elem)); 612 fuzz_abi.fuzzer_start_test(); 613 return; 614 } 615 616 // When the unit test executable is not built in fuzz mode, only run the 617 // provided corpus. 618 for (options.corpus) |input| { 619 var smith: testing.Smith = .{ .in = input }; 620 try testOne(context, &smith); 621 } 622 623 // In case there is no provided corpus, also use an empty 624 // string as a smoke test. 625 var smith: testing.Smith = .{ .in = "" }; 626 try testOne(context, &smith); 627 } 628