Artifact a324613efc60b71f7712a54c06ac074f98abbb95a106f5ea4415cb7f336923e4:
- File
src/fuzz_tests.zig
— part of check-in
[c57c16c5cd]
at
2026-07-29 13:21:29
on branch logstore-index
— crc32: export for the benchmark and the fuzz lane
Both consumers live in their own build roots and cannot reach crc32.zig by path, so the export is forced. Adds the differential fuzz lane against std — the only coverage that can see a self-consistent but numerically shifted implementation, which would orphan every existing .zit file.
The implementation brief prohibited this export, citing repocache as the precedent for unexported leaf modules. That precedent does not apply: repocache has no out-of-module consumer. storestat and repoimage, which do, are both exported. Overridden deliberately. (user: dmestas, size: 9177) [annotate] [blame] [check-ins using] [more...]
//! Seeded fuzz-target runner. //! //! `zig build fuzz -- <target> [--seed N] [--rounds N]` //! `zig build fuzz -- --list` //! One binary; targets are named-tuple fields dispatched through //! `std.meta.FieldEnum`, so a module registers a new target by adding one //! field to `targets` below. The seed is explicit (`--seed`, decimal or //! `0x`-prefixed) or drawn from the process's csprng; either way it is //! always printed as `reproduce with seed=0x...` before the target runs, so //! a failure replays exactly. `--rounds` bounds the run explicitly (default //! 10_000); an out-of-band runner sweeps higher counts. const std = @import("std"); const delta_fuzz = @import("delta_fuzz.zig"); const artifact_fuzz = @import("artifact_fuzz.zig"); const sync_fuzz = @import("sync_fuzz.zig"); const logstore_fuzz = @import("logstore_fuzz.zig"); const transport_fuzz = @import("transport_fuzz.zig"); const serve_fuzz = @import("serve_fuzz.zig"); const merge3_fuzz = @import("merge3_fuzz.zig"); const crc32_fuzz = @import("crc32_fuzz.zig"); /// One field per fuzz target: `pub fn(allocator, seed: u64, rounds: usize) !void`. /// The field name is the argv target name. `pub` so the registration pin at /// the foot of this file (aggregated into `zig build test` through dst.zig) /// can assert a target is actually reachable from the runner — a target that /// exists but is unregistered is invisible to every invocation. pub const targets = .{ .delta_apply_garbage = delta_fuzz.applyGarbage, .delta_round_trip = delta_fuzz.roundTrip, .delta_decode_int_bounded = delta_fuzz.decodeIntBounded, .delta_target_size_garbage = delta_fuzz.targetSizeGarbage, .delta_create_large_sizes = delta_fuzz.createLargeSizes, .artifact_parse_garbage = artifact_fuzz.parseGarbage, .artifact_parse_structured = artifact_fuzz.parseStructured, .artifact_card_line = artifact_fuzz.cardLine, .artifact_escapes = artifact_fuzz.escapes, .artifact_timestamp = artifact_fuzz.timestamp, .artifact_framing = artifact_fuzz.framing, .artifact_predicates = artifact_fuzz.predicates, .artifact_create_round_trip = artifact_fuzz.createRoundTrip, .sync_container_round_trip = sync_fuzz.containerRoundTrip, .sync_signal_cookie_round_trip = sync_fuzz.signalCookieRoundTrip, .sync_parsers_adversarial = sync_fuzz.parsersAdversarial, .sync_consume_reply_adversarial = sync_fuzz.consumeReplyAdversarial, .logstore_open_garbage = logstore_fuzz.openGarbage, .logstore_zlib_round_trip = logstore_fuzz.zlibRoundTrip, .logstore_delta_round_trip = logstore_fuzz.deltaRoundTrip, .logstore_pick_base_round_trip = logstore_fuzz.pickBaseRoundTrip, .logstore_pack_round_trip = logstore_fuzz.packRoundTrip, .logstore_pack_same_path_round_trip = logstore_fuzz.packSamePathRoundTrip, .logstore_parse_config = logstore_fuzz.parseConfig, // The two CLI-layer wire surfaces: bytes chosen by an untrusted peer. .transport_frame_garbage = transport_fuzz.frameGarbage, .serve_request_garbage = serve_fuzz.requestGarbage, // The merge engine's algebraic laws: no hostile input, but the worst // failure mode (silent corruption of merged content). .merge3_properties = merge3_fuzz.properties, // The checksum four on-disk formats are defined in terms of. Every other // lane round-trips within zit and so cannot see a numerically-shifted but // self-consistent implementation; these diff against std.hash.Crc32. .crc32_one_shot = crc32_fuzz.oneShot, .crc32_streaming_splits = crc32_fuzz.streamingSplits, .crc32_scan_chunking = crc32_fuzz.scanChunking, }; const Target = std.meta.FieldEnum(@TypeOf(targets)); const default_rounds: usize = 10_000; /// Most targets do microsecond-scale work per round and run at `default_rounds`. /// The two pack round-trip targets are ~100x heavier: each builds AND repacks a /// full store every round (dozens of deflate/inflate cycles plus the best-of-N /// `pickBase` search over the whole store), so `default_rounds` runs a single /// seed far past the 10-minute execution cap. They get reduced defaults sized to /// finish a seed with margin under that cap while still covering thousands of /// distinct stores; an explicit `--rounds` still sweeps higher out of band. /// /// `logstore_pack_round_trip` measures ~0.073 s/round in Debug (~2.4 min/seed at /// this count). Its same-path sibling adds a per-round manifest walk (F-card /// parse + `path → versions` grouping) on top of the same pack, making it ~4.4x /// heavier at ~0.31 s/round; sharing the 2_000 default pushed it to ~10.3 min/ /// seed — over the cap. It therefore gets its own lower default (~5.2 min/seed, /// ~1.9x headroom) rather than the shared one. const pack_round_trip_default_rounds: usize = 2_000; const pack_same_path_round_trip_default_rounds: usize = 1_000; /// The per-target default round count applied when `--rounds` is absent. fn defaultRoundsFor(target: Target) usize { return switch (target) { .logstore_pack_round_trip => pack_round_trip_default_rounds, .logstore_pack_same_path_round_trip => pack_same_path_round_trip_default_rounds, else => default_rounds, }; } pub fn main(init: std.process.Init) !void { const gpa = init.gpa; const args = try init.minimal.args.toSlice(init.arena.allocator()); if (args.len == 2 and std.mem.eql(u8, args[1], "--list")) { var stdout_buffer: [1024]u8 = undefined; var stdout_writer: std.Io.File.Writer = .init(.stdout(), init.io, &stdout_buffer); const out = &stdout_writer.interface; try writeTargets(out, ""); try out.flush(); return; } var stderr_buffer: [1024]u8 = undefined; var stderr_writer: std.Io.File.Writer = .init(.stderr(), init.io, &stderr_buffer); const err = &stderr_writer.interface; if (args.len < 2) { try usage(err); try err.flush(); std.process.exit(2); } const target = std.meta.stringToEnum(Target, args[1]) orelse { try err.print("fuzz: unknown target '{s}'\n\n", .{args[1]}); try usage(err); try err.flush(); std.process.exit(2); }; var seed: u64 = undefined; init.io.random(std.mem.asBytes(&seed)); var rounds: usize = defaultRoundsFor(target); try parseFlags(args[2..], &seed, &rounds); try err.print("reproduce with seed=0x{x}\n", .{seed}); try err.flush(); switch (target) { inline else => |tag| try @field(targets, @tagName(tag))(gpa, seed, rounds), } } /// Parse `[--seed N] [--rounds N]` in any order, overwriting the defaults /// already in `seed`/`rounds`. Each flag consumes exactly one value; an /// unknown flag or a missing value is an explicit error, never ignored. fn parseFlags(args: []const []const u8, seed: *u64, rounds: *usize) !void { var i: usize = 0; while (i < args.len) : (i += 1) { if (std.mem.eql(u8, args[i], "--seed")) { i += 1; if (i >= args.len) return error.MissingSeedValue; seed.* = try std.fmt.parseInt(u64, args[i], 0); } else if (std.mem.eql(u8, args[i], "--rounds")) { i += 1; if (i >= args.len) return error.MissingRoundsValue; rounds.* = try std.fmt.parseInt(usize, args[i], 0); } else { return error.UnknownArgument; } } } fn usage(w: *std.Io.Writer) !void { try w.writeAll("usage: zig build fuzz -- <target> [--seed N] [--rounds N]\n" ++ " zig build fuzz -- --list\n\ntargets:\n"); try writeTargets(w, " "); } fn writeTargets(w: *std.Io.Writer, prefix: []const u8) !void { inline for (@typeInfo(Target).@"enum".fields) |f| { try w.print("{s}{s}\n", .{ prefix, f.name }); } } // --------------------------------------------------------------------------- // Registration pin — the one test in this file. It runs in `zig build test` // via dst.zig's aggregation, not in the fuzz binary (which has no test step). // --------------------------------------------------------------------------- // Every fuzz target the release bar names must be *reachable*: an unregistered // target compiles, passes review, and is then never run by any invocation. // This pin is structural, so the registration cannot silently rot away. test "fuzz: every named release target is registered in the runner table" { const required = [_][]const u8{ // P4-F6 — the two surfaces an untrusted remote peer can reach. "transport_frame_garbage", "serve_request_garbage", // P4-F7 — the algebraic laws of the three-way line merge. Silent merge // corruption is the worst VCS failure mode, and it is invisible to the // wire-surface targets above: `merge3` never sees a byte from a peer, // only content the repository already accepted. "merge3_properties", }; inline for (required) |name| { if (!@hasField(@TypeOf(targets), name)) { std.debug.print("fuzz target '{s}' is not registered in fuzz_tests.zig targets\n", .{name}); return error.TargetNotRegistered; } } }