@@ -112,7 +112,7 @@ pub fn main() !void {
112112 var steps_menu = false ;
113113 var output_tmp_nonce : ? [16 ]u8 = null ;
114114 var watch = false ;
115- var fuzz = false ;
115+ var fuzz : ? std.Build.Fuzz.Mode = null ;
116116 var debounce_interval_ms : u16 = 50 ;
117117 var webui_listen : ? std.net.Address = null ;
118118
@@ -274,10 +274,44 @@ pub fn main() !void {
274274 webui_listen = std .net .Address .parseIp ("::1" , 0 ) catch unreachable ;
275275 }
276276 } else if (mem .eql (u8 , arg , "--fuzz" )) {
277- fuzz = true ;
277+ fuzz = .{ . forever = undefined } ;
278278 if (webui_listen == null ) {
279279 webui_listen = std .net .Address .parseIp ("::1" , 0 ) catch unreachable ;
280280 }
281+ } else if (mem .startsWith (u8 , arg , "--fuzz=" )) {
282+ const value = arg ["--fuzz=" .len .. ];
283+ if (value .len == 0 ) fatal ("missing argument to --fuzz\n " , .{});
284+
285+ const unit : u8 = value [value .len - 1 ];
286+ const digits = switch (value [value .len - 1 ]) {
287+ '0' ... '9' = > value ,
288+ 'K' , 'M' , 'G' = > value [0 .. value .len - 1 ],
289+ else = > fatal (
290+ "invalid argument to --fuzz, expected a positive number optionally suffixed by one of: [KMG]\n " ,
291+ .{},
292+ ),
293+ };
294+
295+ const amount = std .fmt .parseInt (u64 , digits , 10 ) catch {
296+ fatal (
297+ "invalid argument to --fuzz, expected a positive number optionally suffixed by one of: [KMG]\n " ,
298+ .{},
299+ );
300+ };
301+
302+ const normalized_amount = std .math .mul (u64 , amount , switch (unit ) {
303+ else = > unreachable ,
304+ '0' ... '9' = > 1 ,
305+ 'K' = > 1000 ,
306+ 'M' = > 1_000_000 ,
307+ 'G' = > 1_000_000_000 ,
308+ }) catch fatal ("fuzzing limit amount overflows u64\n " , .{});
309+
310+ fuzz = .{
311+ .limit = .{
312+ .amount = normalized_amount ,
313+ },
314+ };
281315 } else if (mem .eql (u8 , arg , "-fincremental" )) {
282316 graph .incremental = true ;
283317 } else if (mem .eql (u8 , arg , "-fno-incremental" )) {
@@ -476,6 +510,7 @@ pub fn main() !void {
476510 targets .items ,
477511 main_progress_node ,
478512 & run ,
513+ fuzz ,
479514 ) catch | err | switch (err ) {
480515 error .UncleanExit = > {
481516 assert (! run .watch and run .web_server == null );
@@ -485,7 +520,8 @@ pub fn main() !void {
485520 };
486521
487522 if (run .web_server ) | * web_server | {
488- web_server .finishBuild (.{ .fuzz = fuzz });
523+ if (fuzz ) | mode | assert (mode == .forever );
524+ web_server .finishBuild (.{ .fuzz = fuzz != null });
489525 }
490526
491527 if (! watch and run .web_server == null ) {
@@ -651,6 +687,7 @@ fn runStepNames(
651687 step_names : []const []const u8 ,
652688 parent_prog_node : std.Progress.Node ,
653689 run : * Run ,
690+ fuzz : ? std.Build.Fuzz.Mode ,
654691) ! void {
655692 const gpa = run .gpa ;
656693 const step_stack = & run .step_stack ;
@@ -676,6 +713,7 @@ fn runStepNames(
676713 });
677714 }
678715 }
716+
679717 assert (run .memory_blocked_steps .items .len == 0 );
680718
681719 var test_skip_count : usize = 0 ;
@@ -724,6 +762,45 @@ fn runStepNames(
724762 }
725763 }
726764
765+ const ttyconf = run .ttyconf ;
766+
767+ if (fuzz ) | mode | blk : {
768+ switch (builtin .os .tag ) {
769+ // Current implementation depends on two things that need to be ported to Windows:
770+ // * Memory-mapping to share data between the fuzzer and build runner.
771+ // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving
772+ // many addresses to source locations).
773+ .windows = > fatal ("--fuzz not yet implemented for {s}" , .{@tagName (builtin .os .tag )}),
774+ else = > {},
775+ }
776+ if (@bitSizeOf (usize ) != 64 ) {
777+ // Current implementation depends on posix.mmap()'s second parameter, `length: usize`,
778+ // being compatible with `std.fs.getEndPos() u64`'s return value. This is not the case
779+ // on 32-bit platforms.
780+ // Affects or affected by issues #5185, #22523, and #22464.
781+ fatal ("--fuzz not yet implemented on {d}-bit platforms" , .{@bitSizeOf (usize )});
782+ }
783+
784+ switch (mode ) {
785+ .forever = > break :blk ,
786+ .limit = > {},
787+ }
788+
789+ assert (mode == .limit );
790+ var f = std .Build .Fuzz .init (
791+ gpa ,
792+ thread_pool ,
793+ step_stack .keys (),
794+ parent_prog_node ,
795+ ttyconf ,
796+ mode ,
797+ ) catch | err | fatal ("failed to start fuzzer: {s}" , .{@errorName (err )});
798+ defer f .deinit ();
799+
800+ f .start ();
801+ f .waitAndPrintReport ();
802+ }
803+
727804 // A proper command line application defaults to silently succeeding.
728805 // The user may request verbose mode if they have a different preference.
729806 const failures_only = switch (run .summary ) {
@@ -737,8 +814,6 @@ fn runStepNames(
737814 std .Progress .setStatus (.failure );
738815 }
739816
740- const ttyconf = run .ttyconf ;
741-
742817 if (run .summary != .none ) {
743818 const w = std .debug .lockStderrWriter (& stdio_buffer_allocation );
744819 defer std .debug .unlockStderrWriter ();
@@ -1366,7 +1441,10 @@ fn printUsage(b: *std.Build, w: *Writer) !void {
13661441 \\ --watch Continuously rebuild when source files are modified
13671442 \\ --debounce <ms> Delay before rebuilding after changed file detected
13681443 \\ --webui[=ip] Enable the web interface on the given IP address
1369- \\ --fuzz Continuously search for unit test failures (implies '--webui')
1444+ \\ --fuzz[=limit] Continuously search for unit test failures with an optional
1445+ \\ limit to the max number of iterations. The argument supports
1446+ \\ an optional 'K', 'M', or 'G' suffix (e.g. '10K'). Implies
1447+ \\ '--webui' when no limit is specified.
13701448 \\ --time-report Force full rebuild and provide detailed information on
13711449 \\ compilation time of Zig source code (implies '--webui')
13721450 \\ -fincremental Enable incremental compilation
0 commit comments