Artifact 0beb5ea7e7aab8fecae97335f55cc5366a0161b007e7d0b1fe562eab7631074b:
- File
build.zig
— part of check-in
[fa78377389]
at
2026-09-18 12:31:12
on branch trunk
— soak: a nightly deep DST and fuzz run
scripts/soak.sh passed the optimize flag this Zig rejects, so it has been unrunnable since the toolchain moved; it takes --release=safe now. A scheduled workflow runs it at 03:00 UTC on the x64 runner with the script's nightly budgets, overridable from the dispatch form, keeps the log and any reproduce line as an artifact, and is pinned to the ci toolchain by ci_test like the other workflows. (user: dmestas, size: 36076) [annotate] [blame] [check-ins using]
const std = @import("std"); /// Config threaded into each per-artifact builder: target/optimize choice plus /// the single `zit` library module every root imports. storage, logstore, /// checkout, and update are folded into that one module (re-exported from /// root.zig), so there are no separate store peers to wire. const Env = struct { b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, mod: *std.Build.Module, }; pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); // `--release` means ReleaseFast: zit SHIPS ReleaseFast, so that is the mode // the default has to name. This reverses an earlier ReleaseSafe pin, and the // reversal is deliberate rather than incidental — the cost that decided it // is real, but it is SMALLER than this comment used to claim, and the // correction matters more than the decision it supports. // // Measured on wapp (6.4 MB store), warm page cache, modes interleaved and // repeated so neither pays a cold-start penalty: // // zit pack fast ~8.5 s safe ~9.5 s small ~10.7 s // ls tip fast 0.31 s safe 0.33 s small 0.36 s (tcl, 300 MB) // leaves 0.01 s in all three // // So fast buys ~12% on the write path and nothing measurable on the read // path. The figure previously recorded here — "599 ms fast against 15.2 s // safe, ~25x" — DOES NOT REPRODUCE on current code, in either the ratio or // the absolute numbers, and should be treated as stale rather than as a // second opinion: the delta encoder's search was reworked since it was // taken. A ~25x claim would justify almost any correctness cost, which is // exactly why leaving it uncorrected would have been the expensive part. // // WHAT THIS COSTS, stated plainly because it is not free. src/ carries over // a thousand `std.debug.assert` calls, and ReleaseFast compiles every one of // them to nothing. TigerStyle's rule is that assertions "downgrade // catastrophic correctness bugs into liveness bugs"; that trade only exists // while the assertion does, so in this mode a guarded corruption becomes an // unguarded one — in an append-only store, where a bad frame is permanent // and a crash is not. // // `release_assert_test.zig` is what keeps that from being dangerous, and its // job is now load-bearing rather than belt-and-braces: it forbids a NEW // `std.debug.assert` from standing in for validation of untrusted input in a // `pub fn` that returns an error union. An assert is not a rejection, and in // this mode it is not even a crash — it is nothing at all, and the bad data // flows through. Validation of anything reaching zit from a peer or a // repository file must be a returned error. The guard is the enforcement. // // STATING A PREFERENCE MAKES IT THE ONLY REACHABLE RELEASE MODE, and that is // stronger than it reads. It replaces `-Doptimize=<mode>` with // `-Drelease=<bool>` in the option surface, so `-Doptimize=ReleaseSafe` no // longer parses — and the top-level `--release=safe` does not reach it // either: every `--release[=mode]` resolves to the preference. Verified by // building each and checking for the safety-check panic strings; all three // of `--release`, `--release=safe` and `--release=fast` produce one // byte-identical binary. // // The prior ReleaseSafe pin had the same property in the other direction, // which is worth recording because the comment here used to claim otherwise: // `--release=fast` was documented as "the explicit escape hatch" and was not // one. It produced a ReleaseSafe binary, carrying the safety strings to prove // it, for as long as the preference said ReleaseSafe. Anything measured // through that flag before this commit measured the checked build. // // So changing the shipped mode means editing this line. That is the intended // cost: the mode is a project decision, not a packaging accident. // // `zig build bench` builds its executables in this same mode. A benchmark // must measure optimized code, and it must measure the code that SHIPS. const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast }); // The zit library: a Zig implementation of the Fossil formats and protocol // as specified by the four RFC drafts in docs/rfcs/. root.zig re-exports the // store layers (storage/logstore/checkout/update) and the Repository facade; // one module is the whole embeddable surface. const mod = b.addModule("zit", .{ .root_source_file = b.path("src/root.zig"), .target = target, }); const env = Env{ .b = b, .target = target, .optimize = optimize, .mod = mod, }; // `zig build test` — library + CLI unit tests, and nothing else. The store // layers and their sibling tests aggregate into `mod_tests` via root.zig's // test block; build_cli wires the CLI's inline tests in below. Every other // lane hangs off `gate`, not here. const test_step = b.step("test", "Run unit tests"); const mod_tests = b.addTest(.{ .root_module = build_unit_test_module(b, target, optimize) }); test_step.dependOn(&b.addRunArtifact(mod_tests).step); const git_import_tests = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("src/git/git_import_test.zig"), .target = target, .optimize = optimize, .imports = &.{.{ .name = "zit", .module = mod }}, }) }); test_step.dependOn(&b.addRunArtifact(git_import_tests).step); // Task 9 refs/provenance coverage intentionally remains opt-in until the // finalization API exists, so translation tests still run independently. const git_import_refs_tests = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("src/git/git_import_refs_test.zig"), .target = target, .optimize = optimize, .imports = &.{.{ .name = "zit", .module = mod }}, }) }); const git_import_refs_run = b.addRunArtifact(git_import_refs_tests); const git_import_refs_step = b.step("git-import-refs", "Run git import refs tests"); git_import_refs_step.dependOn(&git_import_refs_run.step); test_step.dependOn(&git_import_refs_run.step); const git_import_pipe_helper = b.addExecutable(.{ .name = "git-import-cli-pipe-helper", .root_module = b.createModule(.{ .root_source_file = b.path("src/git/git_import_cli_pipe_helper.zig"), .target = target, .optimize = optimize, }), }); const git_import_cli_options = b.addOptions(); git_import_cli_options.addOptionPath("git_import_pipe_helper", git_import_pipe_helper.getEmittedBin()); // Task 10 transaction coverage guards the executable adapter as part of // the ordinary unit-test gate, and remains separately runnable for focus. const git_import_cli_tests = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("src/git/git_import_cli_test.zig"), .target = target, .optimize = optimize, .imports = &.{ .{ .name = "zit", .module = mod }, .{ .name = "git_import_cli_options", .module = git_import_cli_options.createModule() }, }, }) }); const git_export_cli_tests = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("src/git/git_export_cli_test.zig"), .target = target, .optimize = optimize, .imports = &.{.{ .name = "zit", .module = mod }}, }) }); test_step.dependOn(&b.addRunArtifact(git_export_cli_tests).step); const git_import_cli_run = b.addRunArtifact(git_import_cli_tests); git_import_cli_run.step.dependOn(&git_import_pipe_helper.step); const git_import_cli_step = b.step("git-import-cli", "Run git import CLI transaction tests"); git_import_cli_step.dependOn(&git_import_cli_run.step); test_step.dependOn(&git_import_cli_run.step); // `zig build gate` — everything `test` runs, plus the compile-only proofs // that the e2e and smoke harnesses still build against the current library. // // Those two compiles used to hang off `test`, and they are not cheap: both // rebuild the whole `zit` module in the pinned release mode. Measured at the // time of the split, `zig build test` was 9m13s wall while the tests it // actually RAN accounted for well under a minute of it -- the rest was // compilation, a large share of it these two artifacts that `test` builds // and then never executes. A suite that slow between edits stops being run // between edits, which is the opposite of what a unit lane is for. // // What did NOT move: the dst, bench and smoke *unit* tests. Those are unit // tests despite their siblings' names, and `zig build dst`/`smoke` run the // simulation and the interop check rather than them -- so moving them here // would have dropped them from CI entirely, since CI invokes the lanes // individually and never invokes `gate`. Cheap to run, and the only thing // covering that code. const gate_step = b.step("gate", "Full pre-merge gate: unit tests plus the e2e and smoke compile proofs"); gate_step.dependOn(test_step); // Formatting is a merge gate because the git mirror's CI checks it: an // unformatted commit passes every local lane and then fails in Actions, // which is exactly the discovery order a gate exists to prevent. const fmt_check = b.addFmt(.{ .paths = &.{ "src", "build.zig" }, .check = true }); gate_step.dependOn(&fmt_check.step); // Same reasoning as the fmt check, for a class it cannot see: this project // links no libc, and macOS links libc ALWAYS. So a call that demands libc // -- `std.c.getenv`, `std.c.environ` -- compiles on a developer's mac, // passes every local lane, and then refuses on both Linux runners. That is // the discovery order a gate exists to prevent, and it happened. // // Compiling for the two targets CI runs is enough to catch it: the error is // raised by the compiler at semantic analysis, so nothing needs to be run, // and a cross-compile of the CLI costs seconds against the minutes the // round trip through Actions costs. const cross_step = b.step("cross", "Compile the CLI for the targets CI runs (catches libc-linkage regressions)"); for ([_][]const u8{ "x86_64-linux", "aarch64-linux" }) |triple| { const resolved = b.resolveTargetQuery(std.Target.Query.parse(.{ .arch_os_abi = triple }) catch @panic("bad target triple")); const cross_mod = b.createModule(.{ .root_source_file = b.path("src/root.zig"), .target = resolved, }); const cross_exe = b.addExecutable(.{ .name = b.fmt("zit-{s}", .{triple}), .root_module = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = resolved, .optimize = optimize, .imports = &.{.{ .name = "zit", .module = cross_mod }}, }), }); // The binary is never installed or run -- only proven to compile. cross_step.dependOn(&cross_exe.step); } gate_step.dependOn(cross_step); const cli = build_cli(env, test_step); build_e2e(env, cli.exe, gate_step); build_git_import_e2e(env, cli.exe, gate_step); build_git_export_e2e(env, cli.exe, gate_step); build_fuzz(env); const dst_tests = build_dst(env, test_step); build_bench(env, cli.exe, test_step); build_git_import_bench(env, test_step); build_smoke(env, cli.exe, test_step, gate_step); build_muscle(env, cli.exe, test_step, gate_step); build_cov(env, &.{ mod_tests, cli.tests, dst_tests }); } /// The unit-test compile of root.zig. A *separate* module from the exported /// `zit` one, purely so the repository files that src/packaging_test.zig pins /// (LICENSE, the package manifest, the versioned ignore-glob) can be reached /// as anonymous imports without attaching them to the public module. /// /// Attaching them to `zit` itself made every consumer's build depend on files /// outside `build.zig.zon`'s `.paths` — a fetched package then failed with /// `failed to check cache: '.fossil-settings/ignore-glob' file_hash /// FileNotFound` before compiling a line. The need is test-only, so the scope /// is test-only. fn build_unit_test_module( b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, ) *std.Build.Module { const test_mod = b.createModule(.{ .root_source_file = b.path("src/root.zig"), .target = target, .optimize = optimize, }); test_mod.addAnonymousImport("license_text", .{ .root_source_file = b.path("LICENSE") }); test_mod.addAnonymousImport("build_manifest", .{ .root_source_file = b.path("build.zig.zon") }); test_mod.addAnonymousImport("ignore_glob", .{ .root_source_file = b.path(".fossil-settings/ignore-glob") }); test_mod.addAnonymousImport("build_source", .{ .root_source_file = b.path("build.zig") }); test_mod.addAnonymousImport("ci_workflow", .{ .root_source_file = b.path(".github/workflows/ci.yml") }); test_mod.addAnonymousImport("release_workflow", .{ .root_source_file = b.path(".github/workflows/release.yml") }); test_mod.addAnonymousImport("soak_workflow", .{ .root_source_file = b.path(".github/workflows/soak.yml") }); // The embedded command reference, pinned by src/reference_test.zig so a // verb cannot ship undocumented. Test-only, like the workflow above: the // exported `zit` module must not depend on a repo file outside `.paths`. test_mod.addAnonymousImport("reference_doc", .{ .root_source_file = b.path("www/reference.html") }); // The verb sources, pinned by src/readpath_test.zig so a read verb cannot // silently fall onto the `.full` derive. Test-only for the same // reason as the workflow above. test_mod.addAnonymousImport("cmd_history_source", .{ .root_source_file = b.path("src/cmd/history.zig") }); test_mod.addAnonymousImport("cmd_tracking_source", .{ .root_source_file = b.path("src/cmd/tracking.zig") }); // The repository root, handed to the tests as a constant. src/packaging_test.zig // asks fossil which files are version-controlled, and a test binary's working // directory is not the checkout; without this the query has nowhere to run. // Embedding a *file* cannot answer the question — whether a path is tracked is // a property of the repository, not of the working tree. // // A null `path` gets a panic rather than a "." fallback. "." is the test // binary's working directory, not the checkout, so the fallback would not // degrade the pin to a wrong answer — it would degrade it to a *silent // skip*, since `fossil ls` outside a checkout fails and the pin treats that // as "no fossil here". A configure-time panic naming the cause is the // legible failure; a pin that quietly stops checking is not. const repo = b.addOptions(); const repo_root = b.build_root.path orelse @panic("build.zig: build root has no path; src/packaging_test.zig's version-control " ++ "pin needs the checkout directory and would otherwise skip itself into silence"); repo.addOption([]const u8, "root", repo_root); test_mod.addOptions("repo_options", repo); return test_mod; } /// build_cli's two artifacts: the installed `zit` exe (the e2e harness spawns /// it) and its unit-test compile (the out-of-band cov step traces it). const Cli = struct { exe: *std.Build.Step.Compile, tests: *std.Build.Step.Compile, }; /// The `zit` command-line tool: `zig build` installs it, `zig build run -- /// <args>` runs it, and its own unit tests join `test_step`. fn build_cli(env: Env, test_step: *std.Build.Step) Cli { const exe = env.b.addExecutable(.{ .name = "zit", .root_module = env.b.createModule(.{ .root_source_file = env.b.path("src/main.zig"), .target = env.target, .optimize = env.optimize, .imports = &.{ .{ .name = "zit", .module = env.mod }, }, }), }); env.b.installArtifact(exe); // `zig build run -- <args>` const run_step = env.b.step("run", "Run the zit CLI"); const run_cmd = env.b.addRunArtifact(exe); run_step.dependOn(&run_cmd.step); run_cmd.step.dependOn(env.b.getInstallStep()); if (env.b.args) |args| run_cmd.addArgs(args); const exe_tests = env.b.addTest(.{ .root_module = exe.root_module }); test_step.dependOn(&env.b.addRunArtifact(exe_tests).step); return .{ .exe = exe, .tests = exe_tests }; } /// `zig build e2e` — end-to-end tests that spawn the REAL `zit` binary in an /// isolated, pinned environment and read the resulting repository back through /// the library. The exe's path is threaded in as a build option (a `LazyPath`, /// so the binary is always rebuilt before the tests run); the `zit` module is /// imported so the tests can decode `.zit`/`.zit-checkout` state. /// /// The subprocess-driven run stays out of `zig build test` (it spawns the real /// binary ~150 times), but the test binary is *compiled* by `test` so this /// 4.6k-line file cannot silently rot — a type error here fails `test`, at no /// execution cost. fn build_e2e(env: Env, exe: *std.Build.Step.Compile, gate_step: *std.Build.Step) void { const opts = env.b.addOptions(); opts.addOptionPath("zit_exe", exe.getEmittedBin()); const filter: ?[]const u8 = blk: { const args = env.b.args orelse break :blk null; if (args.len == 2 and std.mem.eql(u8, args[0], "--test-filter")) break :blk args[1]; if (args.len == 1 and std.mem.startsWith(u8, args[0], "--test-filter=")) break :blk args[0]["--test-filter=".len..]; break :blk null; }; const e2e_mod = env.b.createModule(.{ .root_source_file = env.b.path("src/e2e.zig"), .target = env.target, .optimize = env.optimize, .imports = &.{ .{ .name = "zit", .module = env.mod }, .{ .name = "build_options", .module = opts.createModule() }, }, }); // The embedded command reference, so the e2e lane can hold every verb's // real `--help` against what the reference documents. It lives here rather // than beside src/reference_test.zig's structural pins because the check // needs the help TEXT, which only the spawned binary can produce -- the // help constants live in main.zig, outside the library test module. e2e_mod.addAnonymousImport("reference_doc", .{ .root_source_file = env.b.path("www/reference.html") }); const e2e_tests = env.b.addTest(.{ .root_module = e2e_mod, .filters = if (filter) |value| &.{value} else &.{} }); // Compile-only gate: `test` builds the e2e binary but never runs it. gate_step.dependOn(&e2e_tests.step); const e2e_run = env.b.addRunArtifact(e2e_tests); const e2e_step = env.b.step("e2e", "End-to-end tests: spawn the real zit binary"); e2e_step.dependOn(&e2e_run.step); } /// `zig build git-import-e2e -Dgit-import-e2e-filter=<fixture>` — real-Git /// differential tests for `import-git`. Filtering is compiled into the Zig /// test artifact, rather than forwarded to its runtime argument parser. /// Like the general e2e binary its compile is proved by `gate` rather than by /// `test`, while process execution remains opt-in; the emitted CLI path is a /// LazyPath, so the test cannot accidentally use a stale binary. fn build_git_export_e2e(env: Env, exe: *std.Build.Step.Compile, gate_step: *std.Build.Step) void { const opts = env.b.addOptions(); opts.addOptionPath("zit_exe", exe.getEmittedBin()); const test_mod = env.b.createModule(.{ .root_source_file = env.b.path("src/git/git_export_e2e.zig"), .target = env.target, .optimize = env.optimize, .imports = &.{ .{ .name = "zit", .module = env.mod }, .{ .name = "build_options", .module = opts.createModule() }, }, }); const tests = env.b.addTest(.{ .root_module = test_mod }); gate_step.dependOn(&tests.step); const run = env.b.addRunArtifact(tests); const step = env.b.step("git-export-e2e", "Run real-Git export differential tests"); step.dependOn(&run.step); } fn build_git_import_e2e(env: Env, exe: *std.Build.Step.Compile, gate_step: *std.Build.Step) void { const opts = env.b.addOptions(); opts.addOptionPath("zit_exe", exe.getEmittedBin()); const filter = env.b.option([]const u8, "git-import-e2e-filter", "Compile only matching real-Git import E2E tests"); const test_mod = env.b.createModule(.{ .root_source_file = env.b.path("src/git/git_import_e2e.zig"), .target = env.target, .optimize = env.optimize, .imports = &.{ .{ .name = "zit", .module = env.mod }, .{ .name = "build_options", .module = opts.createModule() }, }, }); const tests = env.b.addTest(.{ .root_module = test_mod, .filters = if (filter) |value| &.{value} else &.{}, }); gate_step.dependOn(&tests.step); const run = env.b.addRunArtifact(tests); const step = env.b.step("git-import-e2e", "Run real-Git import differential tests"); step.dependOn(&run.step); } /// `zig build fuzz -- <target> [--seed N] [--rounds N]` — seeded deterministic /// fuzz targets, driven in a loop by an out-of-band runner; not part of `test`. fn build_fuzz(env: Env) void { const fuzz_exe = env.b.addExecutable(.{ .name = "fuzz", .root_module = env.b.createModule(.{ .root_source_file = env.b.path("src/fuzz_tests.zig"), .target = env.target, .optimize = env.optimize, .imports = &.{ .{ .name = "zit", .module = env.mod }, }, }), }); const fuzz_step = env.b.step("fuzz", "Run a seeded fuzz target (zig build fuzz -- <target> --seed N)"); const fuzz_run = env.b.addRunArtifact(fuzz_exe); if (env.b.args) |args| fuzz_run.addArgs(args); fuzz_step.dependOn(&fuzz_run.step); } /// `zig build dst [-- --seed N | --seeds K] [--steps M]` — deterministic /// simulation of the repostate derivation. No args runs the fixed smoke /// sweep (seeds 1..25 x 10_000 steps); deep sweeps pass `--seeds`. /// /// dst.zig's module reaches sim/*.zig (kept out of the public `zit` module); /// running it as a test binary is how those sibling harness files' inline unit /// tests join `zig build test`. fn build_dst(env: Env, test_step: *std.Build.Step) *std.Build.Step.Compile { const dst_exe = env.b.addExecutable(.{ .name = "dst", .root_module = env.b.createModule(.{ .root_source_file = env.b.path("src/dst.zig"), .target = env.target, .optimize = env.optimize, .imports = &.{ .{ .name = "zit", .module = env.mod }, }, }), }); const dst_step = env.b.step("dst", "Deterministic simulation (default smoke sweep; zig build dst -- --seed N)"); const dst_run = env.b.addRunArtifact(dst_exe); if (env.b.args) |args| dst_run.addArgs(args); dst_step.dependOn(&dst_run.step); const dst_tests = env.b.addTest(.{ .root_module = dst_exe.root_module }); test_step.dependOn(&env.b.addRunArtifact(dst_tests).step); return dst_tests; } /// `zig build bench -- <put|read|open|sync|submanifest> [flags]` — LogStore /// throughput, sync timing, and reproducible clone-shape measurement. Pinned to /// ReleaseFast: the mode that ships, so a measurement describes the real binary. /// A ReleaseSafe number would describe one nobody runs. The margin is modest -- /// ~12% on `pack`, nothing measurable on the read path -- but a benchmark's job /// is to describe the shipped binary, not to be worth the difference. /// /// HOW TO MEASURE THIS PROJECT WITHOUT BEING LIED TO. Every rule here was paid /// for by a wrong answer that looked convincing, and they live beside the build /// because each is only true of THIS build script -- a wiki page would rot the /// day one of these lines changes, silently, while nothing failed. /// /// * BARE `zig build` IS DEBUG, and Debug is ~150x slower than an optimized /// build on history-walking work (`zit log tip` over tcl: 58.5 s Debug /// against 0.33 s optimized, same store, byte-identical output). An issue /// was once filed against this repository proposing an architectural change /// on the strength of a 58 s figure that was simply a Debug number. Build /// with `--release` before believing any timing. /// * TRUST THE BINARY, NOT THE FLAG. `preferred_optimize_mode` above makes /// the preference the ONLY reachable release mode: `--release`, /// `--release=safe` and `--release=fast` all resolve to it and produce one /// byte-identical binary. The `~25x pack` figure this file used to carry /// came from a `--release=fast` that was quietly building ReleaseSafe. /// Check which mode you actually got by looking for safety-check panic /// strings in the binary ("integer overflow", "index out of bounds"): /// present means ReleaseSafe, absent means Fast or Small. /// * REPORT THE MINIMUM OF N, NEVER THE MEAN. Timing noise is one-sided -- a /// run can be delayed by another process but never finish faster than the /// machine allows -- so the minimum estimates the true cost and the mean /// mostly measures how busy the box was. A single run once showed a 20% /// REGRESSION for a change that was a 3.7x improvement. /// * INTERLEAVE THE THINGS BEING COMPARED inside each cell, and warm the /// store first. Run in blocks and whichever went first pays the cold read; /// that alone reversed a fast-vs-safe verdict here. /// * DIFF THE OUTPUT EVERY TIME. An optimization that "won" by 50x turned out /// to be erroring out on the first artifact and printing nothing. Compare /// against the previous binary's output before believing a speedup. /// The executable reaches the store layers through the `zit` module; its pure /// corpus tests join `zig build test` in Debug (no timing loop ever runs under /// `test`). /// /// The `serve` subcommand spawns the REAL `zit` binary (the listen-before- /// prime probe), so the exe's path is threaded in as a build option — a /// `LazyPath`, so the binary is always rebuilt before the bench runs — exactly /// as e2e/smoke do. The option module is shared by the bench exe and its test /// compile so `serve_benchmark.zig` resolves `build_options.zit_exe` in both. fn build_bench(env: Env, _: *std.Build.Step.Compile, test_step: *std.Build.Step) void { const bench_zit = env.b.addExecutable(.{ .name = "zit-bench", .root_module = env.b.createModule(.{ .root_source_file = env.b.path("src/main.zig"), .target = env.target, .optimize = .ReleaseFast, .imports = &.{.{ .name = "zit", .module = env.mod }}, }), }); const opts = env.b.addOptions(); opts.addOptionPath("zit_exe", bench_zit.getEmittedBin()); const bench_imports = [_]std.Build.Module.Import{ .{ .name = "zit", .module = env.mod }, .{ .name = "build_options", .module = opts.createModule() }, }; const bench_exe = env.b.addExecutable(.{ .name = "bench", .root_module = env.b.createModule(.{ .root_source_file = env.b.path("src/logstore_benchmark.zig"), .target = env.target, .optimize = .ReleaseFast, .imports = &bench_imports, }), }); const bench_step = env.b.step("bench", "LogStore + clone benchmarks (zig build bench -- <put|read|open|sync|submanifest> [flags])"); const bench_run = env.b.addRunArtifact(bench_exe); if (env.b.args) |args| bench_run.addArgs(args); bench_step.dependOn(&bench_run.step); const bench_tests = env.b.addTest(.{ .root_module = env.b.createModule(.{ .root_source_file = env.b.path("src/logstore_benchmark.zig"), .target = env.target, .optimize = env.optimize, .imports = &bench_imports, }) }); test_step.dependOn(&env.b.addRunArtifact(bench_tests).step); } /// `zig build import-git-bench -- [flags]` — bounded native fast-export /// translation controls. Deliberately opt-in: its full-corpus mode is a /// qualification workload, not an ordinary unit-test dependency. fn build_git_import_bench(env: Env, test_step: *std.Build.Step) void { const opts = env.b.addOptions(); opts.addOption([]const u8, "revision", env.b.option([]const u8, "revision", "Source revision recorded by import-git-bench") orelse "unknown"); const exe = env.b.addExecutable(.{ .name = "import-git-bench", .root_module = env.b.createModule(.{ .root_source_file = env.b.path("src/git/git_import_benchmark.zig"), .target = env.target, .optimize = .ReleaseFast, .imports = &.{ .{ .name = "build_options", .module = opts.createModule() }, .{ .name = "zit", .module = env.mod }, }, }), }); const step = env.b.step("import-git-bench", "Bounded native Git import scaling benchmark"); const run = env.b.addRunArtifact(exe); if (env.b.args) |args| run.addArgs(args); step.dependOn(&run.step); const tests = env.b.addTest(.{ .root_module = env.b.createModule(.{ .root_source_file = env.b.path("src/git/git_import_benchmark.zig"), .target = env.target, .optimize = env.optimize, .imports = &.{.{ .name = "zit", .module = env.mod }}, }) }); test_step.dependOn(&env.b.addRunArtifact(tests).step); } /// `zig build smoke` — the one sanctioned use of the real `fossil` binary: /// confirms zit-produced artifacts/deltas against fossil's own /// parser/hasher/store. Confined to this step; never part of `test` or /// pre-commit. `zig build test` still compiles smoke.zig (catching type /// errors) without running its fossil-driving scenarios. fn build_smoke(env: Env, exe: *std.Build.Step.Compile, test_step: *std.Build.Step, gate_step: *std.Build.Step) void { // Thread the real `zit` binary's path in as a build option (a `LazyPath`, so // it is always rebuilt first), same as e2e: S5 spawns `zit serve`/`pack` as // subprocesses and clones from them with the real `fossil` client. const opts = env.b.addOptions(); opts.addOptionPath("zit_exe", exe.getEmittedBin()); const smoke_exe = env.b.addExecutable(.{ .name = "smoke", .root_module = env.b.createModule(.{ .root_source_file = env.b.path("src/smoke.zig"), .target = env.target, .optimize = env.optimize, .imports = &.{ .{ .name = "zit", .module = env.mod }, .{ .name = "build_options", .module = opts.createModule() }, }, }), }); gate_step.dependOn(&smoke_exe.step); // The scenarios need a real `fossil` and stay out of `test`, but smoke.zig's // pure REPORT ORACLES (the helpers that decide pass/fail from fossil's stdout) // are ordinary string code and are unit-tested here: an oracle that cannot // fail is worse than no oracle, and only a test says which it is. Runs no // subprocess — only the `test` blocks in this file execute. const smoke_tests = env.b.addTest(.{ .root_module = env.b.createModule(.{ .root_source_file = env.b.path("src/smoke.zig"), .target = env.target, .optimize = env.optimize, .imports = &.{ .{ .name = "zit", .module = env.mod }, .{ .name = "build_options", .module = opts.createModule() }, }, }) }); test_step.dependOn(&env.b.addRunArtifact(smoke_tests).step); const smoke_step = env.b.step("smoke", "Fossil interop smoke check (milestone-manual)"); const smoke_run = env.b.addRunArtifact(smoke_exe); if (env.b.args) |args| smoke_run.addArgs(args); smoke_step.dependOn(&smoke_run.step); } /// `zig build muscle` — the fossil-muscle-memory differential transcript ///: one scripted session through the real `fossil` and the real /// `zit`, compared step for step. Milestone-manual like smoke; its pure /// judge is unit-tested under `test`. fn build_muscle(env: Env, exe: *std.Build.Step.Compile, test_step: *std.Build.Step, gate_step: *std.Build.Step) void { const opts = env.b.addOptions(); opts.addOptionPath("zit_exe", exe.getEmittedBin()); const muscle_exe = env.b.addExecutable(.{ .name = "muscle", .root_module = env.b.createModule(.{ .root_source_file = env.b.path("src/muscle.zig"), .target = env.target, .optimize = env.optimize, .imports = &.{ .{ .name = "build_options", .module = opts.createModule() }, }, }), }); gate_step.dependOn(&muscle_exe.step); const muscle_tests = env.b.addTest(.{ .root_module = env.b.createModule(.{ .root_source_file = env.b.path("src/muscle.zig"), .target = env.target, .optimize = env.optimize, .imports = &.{ .{ .name = "build_options", .module = opts.createModule() }, }, }) }); test_step.dependOn(&env.b.addRunArtifact(muscle_tests).step); const muscle_step = env.b.step("muscle", "fossil-muscle-memory differential transcript (milestone-manual)"); const muscle_run = env.b.addRunArtifact(muscle_exe); if (env.b.args) |args| muscle_run.addArgs(args); muscle_step.dependOn(&muscle_run.step); // `zig build ux` — the same session, with every step's exact bytes kept // under zig-out/ux for the fossil-vs-zit reading page. const ux_step = env.b.step("ux", "muscle session with full transcripts under zig-out/ux"); const ux_run = env.b.addRunArtifact(muscle_exe); ux_run.setEnvironmentVariable("MUSCLE_TRANSCRIPT", env.b.pathJoin(&.{ env.b.install_path, "ux" })); ux_step.dependOn(&ux_run.step); } /// Reads kcov's merged `coverage.json` and prints a `src/`-only per-file line /// summary, ascending by coverage so the least-tested regions surface first. const cov_summary_py = \\import json,sys \\d=json.load(open(sys.argv[1])) \\for f in sorted(d["files"],key=lambda f:float(f["percent_covered"])): \\ print(f"{float(f['percent_covered']):6.2f}% {f['covered_lines']:>5}/{f['total_lines']:<5} {f['file'].split('/zit/')[-1]}") \\print(f"total {d['percent_covered']}% html: zig-out/cov/kcov-merged/index.html") ; /// `zig build cov` — merged line coverage over every unit-test binary via /// kcov (`brew install kcov`). Out-of-band: never part of `test`/pre-commit, /// so untested regions surface mechanically without gating a commit. fn build_cov(env: Env, test_bins: []const *std.Build.Step.Compile) void { const b = env.b; const merge = b.addSystemCommand(&.{ "kcov", "--merge" }); const merged = merge.addOutputDirectoryArg("cov"); for (test_bins) |t| { const run = b.addSystemCommand(&.{"kcov"}); run.addArg(b.fmt("--include-pattern={s}/", .{b.pathFromRoot("src")})); merge.addDirectoryArg(run.addOutputDirectoryArg("kcov")); run.addArtifactArg(t); } const install = b.addInstallDirectory(.{ .source_dir = merged, .install_dir = .prefix, .install_subdir = "cov", }); const summary = b.addSystemCommand(&.{ "python3", "-c", cov_summary_py }); summary.addFileArg(merged.path(b, "kcov-merged/coverage.json")); summary.step.dependOn(&install.step); b.step("cov", "Merged kcov line coverage (out-of-band)").dependOn(&summary.step); }