Artifact 64d51fe54bfe299263b531d2326e31ae9476681bfb76af48057e3ba2e9b56238:
- File
src/smoke.zig
— part of check-in
[a2672203c0]
at
2026-07-20 03:49:44
on branch h4-interop
— Smoke: push-direction fossil interop and a recorded fossil version
Every smoke scenario drove pull/clone only -- fossil pulling from zit, zit pulling from fossil -- so zit's push wire (its igot/gimme ordering and capability negotiation as the sync client uploading to a server) never met the reference implementation.
Two scenarios in src/smoke.zig close that gap under the file's standing policy: every compared byte is zit-authored, fossil only confirms. S7 has zit push a grown check-in to a live fossil server, then reads it back from a fossil checkout byte-for-byte (sync 3.3/3.7). S8 lets a fossil commit extend the shared repo with our bytes, has zit pull it, and confirms convergence through the library reader -- Repository.blob re-verifying content against its artifact name (sync 3.2/3.7, artifact 7.2). Both share a prelude that seeds a zit repo, has fossil clone it for a common project-code, grants the anonymous loopback sync user check-in, and disables the autosync the clone inherits.
Smoke also prints the fossil version verbatim at the head of its log, so the exact reference implementation the scenarios confirmed against is recorded wherever smoke runs. (user: dmestas, size: 55028) [annotate] [blame] [check-ins using] [more...]
//! Fossil interop smoke check — the one sanctioned use of the real `fossil` //! binary. `fossil` is invoked nowhere else in this tree. //! //! Policy, enforced by this file's shape, not by convention: //! - Every byte handed to fossil is zit's OWN output (`artifact.create` / //! `delta.create` over an RFC fixture) -- never the raw RFC transcription, //! never anything fossil produced. //! - fossil may only CONFIRM an RFC-cited zit expectation. A scenario that //! would require comparing zit bytes to fossil-AUTHORED bytes (e.g. //! `fossil test-delta-create`) does not belong here. //! - On any mismatch, print the diff and the RFC citation and exit 1. This //! file never adjusts zit to match fossil -- a mismatch is an operator //! decision (RFC misread vs RFC wrong), recorded in the untracked //! ambiguities ledger, not "fixed" in place. //! //! Milestone-manual: this executable lives solely behind `zig build smoke`. //! `zig build test` compiles it (catching type errors) but never runs its //! scenarios; it is not imported by `root.zig`'s test aggregation. const std = @import("std"); const zit = @import("zit"); const artifact = zit.artifact; const delta = zit.delta; const build_options = @import("build_options"); const net = std.Io.net; /// The compiled `zit` binary, threaded in by build.zig (a `LazyPath`, always /// rebuilt first). `build_options.zit_exe` is relative to the build root, which /// is this process's cwd; S5 resolves it to an absolute path before spawning it /// as a subprocess (`zit init/add/commit/pack/serve`). Confined to S5 — the /// other scenarios drive only the `zit` LIBRARY, never the binary. const zit_exe: []const u8 = build_options.zit_exe; // --------------------------------------------------------------------------- // RFC fixtures -- the same vectors src/artifact.zig's own tests cite. // --------------------------------------------------------------------------- const a1_1_manifest = @embedFile("rfc_vectors/artifact_a1_1_sha1_manifest.txt"); const a1_2_technote = @embedFile("rfc_vectors/artifact_a1_2_sha3_technote.txt"); const a3_manifest = @embedFile("rfc_vectors/artifact_a3_rcard_manifest.txt"); /// §5/Appendix B.1 delta worked-example pair (102/151-byte sizes), reused /// verbatim from delta.zig's own "create() then apply() round-trips the /// target" test so S4 exercises the same RFC-cited content. const delta_source = "The quick brown fox jumps over the lazy dog. A Fossil delta encodes byte-level differences compactly.\n"; const delta_target = "The quick brown fox jumps over the sleeping fox. A Fossil delta encodes byte-level differences compactly.\n An example delta target with appended tail.\n"; const ManifestScenario = struct { name: []const u8, citation: []const u8, fixture: []const u8, algo: artifact.HashAlgo, }; const manifest_scenarios = [_]ManifestScenario{ .{ .name = "S1", .citation = "§A.1.1/§A.2", .fixture = a1_1_manifest, .algo = .sha1 }, .{ .name = "S2", .citation = "§A.1.2", .fixture = a1_2_technote, .algo = .sha3_256 }, .{ .name = "S3", .citation = "§A.3", .fixture = a3_manifest, .algo = .sha3_256 }, }; pub fn main(init: std.process.Init) !void { const gpa = init.gpa; const io = init.io; var stdout_buffer: [4096]u8 = undefined; var stdout_writer: std.Io.File.Writer = .init(.stdout(), io, &stdout_buffer); const out = &stdout_writer.interface; // Record the exact fossil the scenarios confirm against, verbatim, at the // head of the smoke log: the version line and the PASS/FAIL results then sit // in one block the release notes cite (readiness item S7). A null return is // the milestone-manual skip — fossil absent, nothing to confirm against. if (try fossilVersion(gpa, io)) |ver| { defer gpa.free(ver); try out.print("fossil version: {s}\n", .{std.mem.trim(u8, ver, " \t\r\n")}); } else { try out.writeAll("fossil not found — smoke is milestone-manual\n"); try out.flush(); return; } var arena_state = std.heap.ArenaAllocator.init(gpa); defer arena_state.deinit(); const arena = arena_state.allocator(); const tmp_root = try tempRoot(arena, init.environ_map.*); std.Io.Dir.cwd().deleteTree(io, tmp_root) catch {}; try std.Io.Dir.cwd().createDirPath(io, tmp_root); defer std.Io.Dir.cwd().deleteTree(io, tmp_root) catch {}; var env = try init.environ_map.clone(gpa); defer env.deinit(); const home_dir = try std.fs.path.join(arena, &.{ tmp_root, "home" }); try std.Io.Dir.cwd().createDirPath(io, home_dir); try env.put("FOSSIL_HOME", home_dir); var all_pass = true; for (manifest_scenarios) |sc| { const pass = runManifestScenario(arena, gpa, io, tmp_root, &env, out, sc) catch |err| blk: { try out.print("FAIL {s} ({s}): unexpected error running scenario: {s}\n", .{ sc.name, sc.citation, @errorName(err) }); break :blk false; }; all_pass = all_pass and pass; } const s4_pass = runDeltaScenario(arena, gpa, io, tmp_root, &env, out) catch |err| blk: { try out.print("FAIL S4 (§5/§Appendix B.1): unexpected error running scenario: {s}\n", .{@errorName(err)}); break :blk false; }; all_pass = all_pass and s4_pass; const s5_pass = runCloneFromRepackedScenario(arena, gpa, io, tmp_root, &env, out) catch |err| blk: { try out.print("FAIL S5 (§7.5/§3): unexpected error running scenario: {s}\n", .{@errorName(err)}); break :blk false; }; all_pass = all_pass and s5_pass; const s6_pass = runOpenFromFossilServerScenario(arena, gpa, io, tmp_root, &env, out) catch |err| blk: { try out.print("FAIL S6 (§3.7/§4): unexpected error running scenario: {s}\n", .{@errorName(err)}); break :blk false; }; all_pass = all_pass and s6_pass; const s7_pass = runPushToFossilServerScenario(arena, gpa, io, tmp_root, &env, out) catch |err| blk: { try out.print("FAIL S7 (§3.3/§3.7): unexpected error running scenario: {s}\n", .{@errorName(err)}); break :blk false; }; all_pass = all_pass and s7_pass; const s8_pass = runPullFossilExtensionScenario(arena, gpa, io, tmp_root, &env, out) catch |err| blk: { try out.print("FAIL S8 (§3.2/§3.7): unexpected error running scenario: {s}\n", .{@errorName(err)}); break :blk false; }; all_pass = all_pass and s8_pass; try out.flush(); if (!all_pass) std.process.exit(1); } // --------------------------------------------------------------------------- // Preflight // --------------------------------------------------------------------------- /// The runner's `fossil version` output, captured verbatim, or null when fossil /// is absent or errors. Doubles as the availability gate (null ⇒ skip) and the /// version record printed at the head of the smoke log. Caller frees a non-null /// return. fn fossilVersion(gpa: std.mem.Allocator, io: std.Io) !?[]u8 { const res = std.process.run(gpa, io, .{ .argv = &.{ "fossil", "version" } }) catch return null; defer gpa.free(res.stderr); if (!okExit(res.term)) { gpa.free(res.stdout); return null; } return res.stdout; } fn tempRoot(arena: std.mem.Allocator, env: std.process.Environ.Map) ![]const u8 { const base = env.get("TMPDIR") orelse "/tmp"; const trimmed = std.mem.trimEnd(u8, base, "/"); return std.fs.path.join(arena, &.{ trimmed, "zit-smoke" }); } // --------------------------------------------------------------------------- // fossil process helper // --------------------------------------------------------------------------- fn okExit(term: std.process.Child.Term) bool { return switch (term) { .exited => |code| code == 0, else => false, }; } fn runFossil( gpa: std.mem.Allocator, io: std.Io, argv: []const []const u8, cwd_path: []const u8, env: *const std.process.Environ.Map, ) !std.process.RunResult { return std.process.run(gpa, io, .{ .argv = argv, .cwd = .{ .path = cwd_path }, .environ_map = env, }); } /// First whitespace-delimited token of `text` (the hash column of /// `fossil sha1sum`/`sha3sum` output: "<hash> <path>\n"). fn firstToken(text: []const u8) []const u8 { const end = std.mem.indexOfAny(u8, text, " \t\r\n") orelse text.len; return text[0..end]; } // --------------------------------------------------------------------------- // Failure reporting -- never authored from fossil's side, only printed. // --------------------------------------------------------------------------- fn printBytesPreview(out: *std.Io.Writer, label: []const u8, bytes: []const u8) !void { try out.print(" {s} (len {d}): ", .{ label, bytes.len }); const max = @min(bytes.len, 96); for (bytes[0..max]) |b| try out.print("{x:0>2}", .{b}); if (bytes.len > max) try out.writeAll("..."); try out.writeAll("\n"); } fn firstDiffOffset(a: []const u8, b: []const u8) ?usize { const n = @min(a.len, b.len); var i: usize = 0; while (i < n) : (i += 1) { if (a[i] != b[i]) return i; } if (a.len != b.len) return n; return null; } fn printDiffWindow(out: *std.Io.Writer, zit_bytes: []const u8, fossil_bytes: []const u8) !void { const off = firstDiffOffset(zit_bytes, fossil_bytes) orelse return; const start = if (off > 16) off - 16 else 0; const zit_end = @min(zit_bytes.len, off + 16); const fossil_end = @min(fossil_bytes.len, off + 16); try out.print(" first diff at byte {d}\n", .{off}); try out.writeAll(" zit : "); for (zit_bytes[start..zit_end]) |b| try out.print("{x:0>2}", .{b}); try out.writeAll("\n fossil : "); for (fossil_bytes[start..fossil_end]) |b| try out.print("{x:0>2}", .{b}); try out.writeAll("\n"); } fn printMismatchFooter(out: *std.Io.Writer, citation: []const u8) !void { try out.print(" RFC citation: {s}\n", .{citation}); try out.writeAll( \\ Fossil disagrees with zit's RFC-cited expectation. Operator decides \\ RFC-misread vs RFC-wrong; record in ambiguities.md. Never change zit \\ to match fossil. \\ ); } // --------------------------------------------------------------------------- // S1/S2/S3 -- manifest scenarios (parse -> create -> confirm via fossil) // --------------------------------------------------------------------------- fn runManifestScenario( arena: std.mem.Allocator, gpa: std.mem.Allocator, io: std.Io, tmp_root: []const u8, env: *const std.process.Environ.Map, out: *std.Io.Writer, sc: ManifestScenario, ) !bool { // zit's own product: the artifact fed to fossil is `create()`'s output, // never the raw RFC transcription (§6.2 round-trip; the two happen to be // byte-identical for S1/S3 and a strict-lexical reorder for S2, but the // input to fossil is always what zit itself wrote). var m = try artifact.parse(gpa, sc.fixture); defer m.deinit(); const zit_bytes = try artifact.create(&m, gpa); defer gpa.free(zit_bytes); var name_buf: [64]u8 = undefined; const zit_name = artifact.artifactName(sc.algo, zit_bytes, &name_buf); const base = try std.fs.path.join(arena, &.{ tmp_root, sc.name }); const repo_dir = try std.fs.path.join(arena, &.{ base, "repo" }); const checkout_dir = try std.fs.path.join(arena, &.{ base, "checkout" }); try std.Io.Dir.cwd().createDirPath(io, repo_dir); try std.Io.Dir.cwd().createDirPath(io, checkout_dir); const manifest_path = try std.fs.path.join(arena, &.{ repo_dir, "manifest.txt" }); try std.Io.Dir.cwd().writeFile(io, .{ .sub_path = manifest_path, .data = zit_bytes }); // fossil test-parse-manifest: repo-free syntactic accept (§4/§5). Exit is // 0 either way -- the VERDICT TEXT is what confirms zit's parse. { const res = try runFossil(gpa, io, &.{ "fossil", "test-parse-manifest", manifest_path }, repo_dir, env); defer gpa.free(res.stdout); defer gpa.free(res.stderr); if (std.mem.indexOf(u8, res.stdout, "reports the input is ok") == null) { try out.print("FAIL {s} ({s}): fossil test-parse-manifest did not report the input well-formed\n", .{ sc.name, sc.citation }); try printBytesPreview(out, "zit bytes", zit_bytes); try out.print(" fossil verdict: {s}\n", .{res.stdout}); try printMismatchFooter(out, sc.citation); return false; } } // fossil {sha1,sha3}sum: confirms zit's own artifactName computation. { const sum_cmd = if (sc.algo == .sha1) "sha1sum" else "sha3sum"; const res = try runFossil(gpa, io, &.{ "fossil", sum_cmd, manifest_path }, repo_dir, env); defer gpa.free(res.stdout); defer gpa.free(res.stderr); const fossil_name = firstToken(res.stdout); if (!std.mem.eql(u8, fossil_name, zit_name)) { try out.print("FAIL {s} ({s}): fossil {s} disagrees with zit's artifactName\n", .{ sc.name, sc.citation, sum_cmd }); try out.print(" zit name: {s}\n", .{zit_name}); try out.print(" fossil name: {s}\n", .{fossil_name}); try printMismatchFooter(out, sc.citation); return false; } } // init + open + put + retrieve: confirms zit's create() output // round-trips through fossil's own content store under the given name. const repo_fossil = try std.fs.path.join(arena, &.{ repo_dir, "r.fossil" }); const init_argv: []const []const u8 = if (sc.algo == .sha1) &.{ "fossil", "init", "--sha1", repo_fossil } else &.{ "fossil", "init", repo_fossil }; { const res = try runFossil(gpa, io, init_argv, repo_dir, env); defer gpa.free(res.stdout); defer gpa.free(res.stderr); if (!okExit(res.term)) { try out.print("FAIL {s} ({s}): fossil init failed\n stderr: {s}\n", .{ sc.name, sc.citation, res.stderr }); try printMismatchFooter(out, sc.citation); return false; } } { const res = try runFossil(gpa, io, &.{ "fossil", "open", repo_fossil }, checkout_dir, env); defer gpa.free(res.stdout); defer gpa.free(res.stderr); if (!okExit(res.term)) { try out.print("FAIL {s} ({s}): fossil open failed\n stderr: {s}\n", .{ sc.name, sc.citation, res.stderr }); try printMismatchFooter(out, sc.citation); return false; } } { const res = try runFossil(gpa, io, &.{ "fossil", "test-content-put", manifest_path }, checkout_dir, env); defer gpa.free(res.stdout); defer gpa.free(res.stderr); if (!okExit(res.term)) { try out.print("FAIL {s} ({s}): fossil test-content-put failed\n stderr: {s}\n", .{ sc.name, sc.citation, res.stderr }); try printMismatchFooter(out, sc.citation); return false; } } const artifact_out_path = try std.fs.path.join(arena, &.{ checkout_dir, "out.bin" }); { const res = try runFossil(gpa, io, &.{ "fossil", "artifact", zit_name, "out.bin" }, checkout_dir, env); defer gpa.free(res.stdout); defer gpa.free(res.stderr); if (!okExit(res.term)) { try out.print("FAIL {s} ({s}): fossil artifact could not resolve zit's name {s}\n stderr: {s}\n", .{ sc.name, sc.citation, zit_name, res.stderr }); try printMismatchFooter(out, sc.citation); return false; } } const fossil_bytes = try std.Io.Dir.cwd().readFileAlloc(io, artifact_out_path, gpa, .limited(1 << 20)); defer gpa.free(fossil_bytes); if (!std.mem.eql(u8, zit_bytes, fossil_bytes)) { try out.print("FAIL {s} ({s}): fossil artifact returned bytes different from zit's create() output\n", .{ sc.name, sc.citation }); try printBytesPreview(out, "zit bytes", zit_bytes); try printBytesPreview(out, "fossil bytes", fossil_bytes); try printDiffWindow(out, zit_bytes, fossil_bytes); try printMismatchFooter(out, sc.citation); return false; } try out.print("PASS {s} ({s}): zit's create() output confirmed by fossil (name {s})\n", .{ sc.name, sc.citation, zit_name }); return true; } // --------------------------------------------------------------------------- // S4 -- delta round-trip (never compared against fossil test-delta-create; // fossil only applies zit's own delta and confirms the reconstructed target) // --------------------------------------------------------------------------- fn runDeltaScenario( arena: std.mem.Allocator, gpa: std.mem.Allocator, io: std.Io, tmp_root: []const u8, env: *const std.process.Environ.Map, out: *std.Io.Writer, ) !bool { const citation = "§5/§Appendix B.1"; const zit_delta = try delta.create(gpa, delta_source, delta_target); defer gpa.free(zit_delta); // zit's own apply() must already reconstruct the target -- fossil only // ever confirms this, never establishes it. const self_applied = try delta.apply(gpa, delta_source, zit_delta); defer gpa.free(self_applied); std.debug.assert(std.mem.eql(u8, self_applied, delta_target)); const base = try std.fs.path.join(arena, &.{ tmp_root, "S4" }); try std.Io.Dir.cwd().createDirPath(io, base); const src_path = try std.fs.path.join(arena, &.{ base, "src.bin" }); const delta_path = try std.fs.path.join(arena, &.{ base, "zit.delta" }); const out_path = try std.fs.path.join(arena, &.{ base, "out.bin" }); try std.Io.Dir.cwd().writeFile(io, .{ .sub_path = src_path, .data = delta_source }); try std.Io.Dir.cwd().writeFile(io, .{ .sub_path = delta_path, .data = zit_delta }); { const res = try runFossil(gpa, io, &.{ "fossil", "test-delta-apply", src_path, delta_path, out_path }, base, env); defer gpa.free(res.stdout); defer gpa.free(res.stderr); if (!okExit(res.term)) { try out.print("FAIL S4 ({s}): fossil test-delta-apply rejected zit's delta.create() output\n stderr: {s}\n", .{ citation, res.stderr }); try printBytesPreview(out, "zit delta", zit_delta); try printMismatchFooter(out, citation); return false; } } const fossil_out = try std.Io.Dir.cwd().readFileAlloc(io, out_path, gpa, .limited(1 << 20)); defer gpa.free(fossil_out); if (!std.mem.eql(u8, fossil_out, delta_target)) { try out.print("FAIL S4 ({s}): fossil test-delta-apply produced bytes different from zit's target\n", .{citation}); try printBytesPreview(out, "zit target", delta_target); try printBytesPreview(out, "fossil bytes", fossil_out); try printDiffWindow(out, delta_target, fossil_out); try printMismatchFooter(out, citation); return false; } try out.print("PASS S4 ({s}): fossil test-delta-apply reconstructs zit's delta.create() target\n", .{citation}); return true; } // --------------------------------------------------------------------------- // S5 -- clone from a re-packed store (§7.5, covering the §3 // serve-regenerated-delta note). The one scenario that drives the real `zit` // BINARY: build a repo, evolve a file so its blob is stored as a delta, run // `zit pack` so that delta is REGENERATED against our size-minimizing base, then // serve the repacked store and have the real `fossil` client CLONE it. fossil // hash-verifies every artifact it reconstructs on receipt, so a clone that exits // 0 proves the regenerated delta container zit serves reconstructs byte-for-byte // over the wire; the checked-out file is compared to zit's own committed bytes as // the final oracle. Every byte fossil sees is zit's own output — fossil only // confirms, never authors (the file policy at the top of this file). // --------------------------------------------------------------------------- /// Loopback port for S5's `zit serve` (distinct from the e2e ports 47251-47253 /// so a stray concurrent run does not collide). const s5_serve_port: u16 = 47311; /// Build the S5 file blob: 200 similar lines so `zit pack`'s best-of-N base /// selection produces a profitable delta. `changed` mutates one line and appends /// a tail, so the second commit's blob deltifies well against the first. fn buildBlob(arena: std.mem.Allocator, changed: bool) ![]u8 { var aw = std.Io.Writer.Allocating.init(arena); // arena-owned; no deinit var i: usize = 0; while (i < 200) : (i += 1) { if (changed and i == 50) { try aw.writer.print("line {d}: CHANGED CONTENT for the regenerated-delta smoke\n", .{i}); } else { try aw.writer.print("line {d}: the quick brown fox jumps over the lazy dog number {d}\n", .{ i, i }); } } if (changed) try aw.writer.writeAll("line 200: appended tail exercises the delta insert op\n"); return aw.toOwnedSlice(); } /// Absolute path to the compiled `zit` binary. `zit_exe` is relative to the /// build root (this process's cwd); a spawned child chdirs before exec, so it /// must be resolved first. Allocated on `arena`. fn zitAbsPath(arena: std.mem.Allocator, io: std.Io) ![]const u8 { const cwd_abs = try std.process.currentPathAlloc(io, arena); return std.fs.path.resolve(arena, &.{ cwd_abs, zit_exe }); } /// Blocking `zit <tail...>` with cwd `cwd_path` and the shared smoke env /// (FOSSIL_HOME set). Caller owns `result.stdout`/`result.stderr`. fn runZit( gpa: std.mem.Allocator, io: std.Io, abs_exe: []const u8, cwd_path: []const u8, env: *const std.process.Environ.Map, tail: []const []const u8, ) !std.process.RunResult { var argv_buf: [8][]const u8 = undefined; argv_buf[0] = abs_exe; std.debug.assert(tail.len <= argv_buf.len - 1); for (tail, argv_buf[1..][0..tail.len]) |src, *dst| dst.* = src; return std.process.run(gpa, io, .{ .argv = argv_buf[0 .. 1 + tail.len], .cwd = .{ .path = cwd_path }, .environ_map = env, }); } /// Spawn `zit serve --port <port>` detached, cwd `cwd_path` (so `discoverRoot` /// finds `cwd_path/.zit`). stdout/stderr discarded — driven purely over the /// socket. Caller `kill`s the returned child. fn spawnZitServe( io: std.Io, abs_exe: []const u8, cwd_path: []const u8, env: *const std.process.Environ.Map, port_str: []const u8, ) !std.process.Child { return std.process.spawn(io, .{ .argv = &.{ abs_exe, "serve", "--port", port_str }, .cwd = .{ .path = cwd_path }, .environ_map = env, .stdout = .ignore, .stderr = .ignore, }); } /// Bounded wait for a server to bind: retry-connect to the loopback port /// until accepted or a wall-clock deadline elapses (never a fixed sleep). A /// successful TCP connect proves the listener is up. fn waitForListen(io: std.Io, port: u16) !void { const addr = net.IpAddress.parseIp4("127.0.0.1", port) catch return error.BadTestAddress; const deadline_ns: i96 = 15_000_000_000; // 15 s cap — orders of magnitude of slack const start = std.Io.Clock.now(.awake, io).nanoseconds; while (true) { if (addr.connect(io, .{ .mode = .stream })) |stream| { stream.close(io); return; } else |_| {} if (std.Io.Clock.now(.awake, io).nanoseconds - start > deadline_ns) return error.ServerNeverBound; } } /// The count parsed from `zit pack`'s "<N> re-deltaed" clause, or null if the /// clause is absent. Used to assert S5's fixture still forces a REGENERATED /// delta (so the scenario keeps testing serve-of-a-regenerated-delta, not just a /// full-form clone). fn reDeltaCount(text: []const u8) ?usize { const idx = std.mem.indexOf(u8, text, " re-deltaed") orelse return null; var start = idx; while (start > 0 and std.ascii.isDigit(text[start - 1])) start -= 1; if (start == idx) return null; return std.fmt.parseInt(usize, text[start..idx], 10) catch null; } /// Run `zit <tail...>` and fail the scenario (printing stderr) unless it exits 0. /// Returns the captured result on success (caller frees), or null on failure /// (already freed + reported). fn zitStep( gpa: std.mem.Allocator, io: std.Io, out: *std.Io.Writer, abs_exe: []const u8, cwd_path: []const u8, env: *const std.process.Environ.Map, tail: []const []const u8, label: []const u8, ) !?std.process.RunResult { const res = try runZit(gpa, io, abs_exe, cwd_path, env, tail); if (!okExit(res.term)) { try out.print("FAIL S5 (§7.5/§3): {s} failed\n stderr: {s}\n", .{ label, res.stderr }); try printMismatchFooter(out, "§7.5/§3"); gpa.free(res.stdout); gpa.free(res.stderr); return null; } return res; } fn runCloneFromRepackedScenario( arena: std.mem.Allocator, gpa: std.mem.Allocator, io: std.Io, tmp_root: []const u8, env: *const std.process.Environ.Map, out: *std.Io.Writer, ) !bool { const citation = "§7.5/§3"; const abs_exe = try zitAbsPath(arena, io); const base = try std.fs.path.join(arena, &.{ tmp_root, "S5" }); const src_dir = try std.fs.path.join(arena, &.{ base, "src" }); try std.Io.Dir.cwd().createDirPath(io, src_dir); // A source repo with two check-ins of an evolving file: the second blob is // stored as a delta, giving `pack` something to re-base. { const r = (try zitStep(gpa, io, out, abs_exe, src_dir, env, &.{"init"}, "zit init")) orelse return false; gpa.free(r.stdout); gpa.free(r.stderr); } const blob_path = try std.fs.path.join(arena, &.{ src_dir, "big.txt" }); const seed = try buildBlob(arena, false); try std.Io.Dir.cwd().writeFile(io, .{ .sub_path = blob_path, .data = seed }); inline for (.{ .{ &[_][]const u8{ "add", "big.txt" }, "zit add (seed)" }, .{ &[_][]const u8{ "commit", "-m", "seed", "--user", "u" }, "zit commit (seed)" }, }) |step| { const r = (try zitStep(gpa, io, out, abs_exe, src_dir, env, step[0], step[1])) orelse return false; gpa.free(r.stdout); gpa.free(r.stderr); } const edited = try buildBlob(arena, true); try std.Io.Dir.cwd().writeFile(io, .{ .sub_path = blob_path, .data = edited }); inline for (.{ .{ &[_][]const u8{ "add", "big.txt" }, "zit add (grow)" }, .{ &[_][]const u8{ "commit", "-m", "grow", "--user", "u" }, "zit commit (grow)" }, }) |step| { const r = (try zitStep(gpa, io, out, abs_exe, src_dir, env, step[0], step[1])) orelse return false; gpa.free(r.stdout); gpa.free(r.stderr); } // Re-pack: every artifact is re-deltified against our chosen base, so a // served delta container is REGENERATED, not the one the write path stored. { const r = (try zitStep(gpa, io, out, abs_exe, src_dir, env, &.{"pack"}, "zit pack")) orelse return false; defer gpa.free(r.stdout); defer gpa.free(r.stderr); const n = reDeltaCount(r.stdout) orelse { try out.print("FAIL S5 ({s}): could not parse pack's re-delta count\n pack stdout: {s}\n", .{ citation, r.stdout }); return false; }; if (n == 0) { try out.print("FAIL S5 ({s}): pack regenerated no delta — the fixture no longer exercises a served regenerated delta container\n pack stdout: {s}\n", .{ citation, r.stdout }); return false; } } // Serve the repacked store; the real fossil client clones it over the wire. const port_str = try std.fmt.allocPrint(arena, "{d}", .{s5_serve_port}); var server = try spawnZitServe(io, abs_exe, src_dir, env, port_str); defer server.kill(io); waitForListen(io, s5_serve_port) catch { try out.print("FAIL S5 ({s}): zit serve never bound 127.0.0.1:{d}\n", .{ citation, s5_serve_port }); return false; }; // A bare-root URL: a conformant `fossil clone` POSTs to "/" (sync §4). This // is the fossil-client-from-root case e2e explicitly defers to smoke. const url = try std.fmt.allocPrint(arena, "http://127.0.0.1:{d}/", .{s5_serve_port}); const clone_repo = try std.fs.path.join(arena, &.{ base, "verify.fossil" }); { const res = try runFossil(gpa, io, &.{ "fossil", "clone", url, clone_repo }, base, env); defer gpa.free(res.stdout); defer gpa.free(res.stderr); if (!okExit(res.term)) { try out.print("FAIL S5 ({s}): fossil clone from the repacked zit store failed\n stderr: {s}\n", .{ citation, res.stderr }); try printMismatchFooter(out, citation); return false; } } // Check out the clone (autosync off — the server is about to be killed) so // fossil reconstructs the file from the artifacts it received. const co_dir = try std.fs.path.join(arena, &.{ base, "co" }); try std.Io.Dir.cwd().createDirPath(io, co_dir); { const res = try runFossil(gpa, io, &.{ "fossil", "open", "--nosync", clone_repo }, co_dir, env); defer gpa.free(res.stdout); defer gpa.free(res.stderr); if (!okExit(res.term)) { try out.print("FAIL S5 ({s}): fossil open of the clone failed\n stderr: {s}\n", .{ citation, res.stderr }); try printMismatchFooter(out, citation); return false; } } // THE PROOF: fossil's reconstructed file == zit's own committed bytes. A // regenerated delta that dropped or corrupted a byte fails here. const co_blob = try std.fs.path.join(arena, &.{ co_dir, "big.txt" }); const fossil_bytes = try std.Io.Dir.cwd().readFileAlloc(io, co_blob, gpa, .limited(1 << 20)); defer gpa.free(fossil_bytes); if (!std.mem.eql(u8, edited, fossil_bytes)) { try out.print("FAIL S5 ({s}): fossil clone from the repacked store did not reconstruct zit's committed bytes\n", .{citation}); try printBytesPreview(out, "zit committed", edited); try printBytesPreview(out, "fossil clone", fossil_bytes); try printDiffWindow(out, edited, fossil_bytes); try printMismatchFooter(out, citation); return false; } try out.print("PASS S5 ({s}): fossil clone from a repacked zit store reconstructs zit's committed bytes byte-for-byte\n", .{citation}); return true; } // --------------------------------------------------------------------------- // S6 -- clone → open against a real fossil SERVER (sync §3.7/§4, P3-F3). The // mirror of S5: there zit served and fossil cloned; here fossil serves and the // real `zit` binary clones AND opens, so the working tree zit materializes is // the thing under test. This is the half of the clone story digest-set equality // cannot see — a repository can converge perfectly on the wire and still be // unusable if nothing turns it into files on disk. // // The file policy at the top of this file holds: the compared bytes are OUR OWN // (we author `s6_payload`, hand it to `fossil add`/`fossil commit`, and compare // it to what zit materializes). fossil is the container and the transport in // between; it never authors a byte of the oracle. A mismatch means zit's // artifact reconstruction or its working-tree materialization dropped or // corrupted content that fossil carried faithfully. // --------------------------------------------------------------------------- /// Loopback port for S6's `fossil server` (distinct from S5's and from every /// e2e port so a stray concurrent run cannot collide). const s6_serve_port: u16 = 47312; /// The bytes S6 round-trips. Authored here, never by fossil: multi-line with a /// trailing newline, so a materialization that mangled line endings or dropped /// the tail is caught. const s6_payload = \\line one of the fossil-served source tree \\line two, so a truncation is visible \\line three \\ ; /// Run `fossil <argv...>` and fail the scenario (printing stderr) unless it /// exits 0. Returns true on success; the caller aborts the scenario on false. fn fossilStep( gpa: std.mem.Allocator, io: std.Io, out: *std.Io.Writer, argv: []const []const u8, cwd_path: []const u8, env: *const std.process.Environ.Map, label: []const u8, ) !bool { const res = try runFossil(gpa, io, argv, cwd_path, env); defer gpa.free(res.stdout); defer gpa.free(res.stderr); if (!okExit(res.term)) { try out.print("FAIL S6 (§3.7/§4): {s} failed\n stderr: {s}\n", .{ label, res.stderr }); return false; } return true; } fn runOpenFromFossilServerScenario( arena: std.mem.Allocator, gpa: std.mem.Allocator, io: std.Io, tmp_root: []const u8, env: *const std.process.Environ.Map, out: *std.Io.Writer, ) !bool { const citation = "§3.7/§4"; const abs_exe = try zitAbsPath(arena, io); const base = try std.fs.path.join(arena, &.{ tmp_root, "S6" }); const co_dir = try std.fs.path.join(arena, &.{ base, "co" }); try std.Io.Dir.cwd().createDirPath(io, co_dir); const repo_path = try std.fs.path.join(arena, &.{ base, "source.fossil" }); // A real fossil repository holding one check-in of OUR bytes. if (!try fossilStep(gpa, io, out, &.{ "fossil", "init", repo_path }, base, env, "fossil init")) return false; if (!try fossilStep(gpa, io, out, &.{ "fossil", "open", "--nosync", repo_path }, co_dir, env, "fossil open")) return false; const payload_path = try std.fs.path.join(arena, &.{ co_dir, "big.txt" }); try std.Io.Dir.cwd().writeFile(io, .{ .sub_path = payload_path, .data = s6_payload }); if (!try fossilStep(gpa, io, out, &.{ "fossil", "add", "big.txt" }, co_dir, env, "fossil add")) return false; if (!try fossilStep( gpa, io, out, &.{ "fossil", "commit", "-m", "s6 seed", "--user-override", "u", "--no-warnings" }, co_dir, env, "fossil commit", )) return false; // Serve it with the real fossil server and clone it with the real zit // binary over the bare-root URL (sync §4: the client posts the repo URL path // unchanged and MUST NOT append /xfer). const port_str = try std.fmt.allocPrint(arena, "{d}", .{s6_serve_port}); var server = std.process.spawn(io, .{ .argv = &.{ "fossil", "server", "--port", port_str, repo_path }, .cwd = .{ .path = base }, .environ_map = env, .stdout = .ignore, .stderr = .ignore, }) catch |err| { try out.print("FAIL S6 ({s}): could not spawn fossil server: {s}\n", .{ citation, @errorName(err) }); return false; }; defer server.kill(io); // The same retry-connect wait S5 uses: a successful TCP connect proves the // listener is up, whichever binary owns it. waitForListen(io, s6_serve_port) catch { try out.print("FAIL S6 ({s}): fossil server never bound 127.0.0.1:{d}\n", .{ citation, s6_serve_port }); return false; }; const url = try std.fmt.allocPrint(arena, "http://127.0.0.1:{d}/", .{s6_serve_port}); { const r = (try zitStep(gpa, io, out, abs_exe, base, env, &.{ "clone", url, "cloned" }, "zit clone (from fossil server)")) orelse return false; gpa.free(r.stdout); gpa.free(r.stderr); } // OPEN: the clone's checkout is still unborn, so this is what turns the // converged repository into a working tree. const cloned_dir = try std.fs.path.join(arena, &.{ base, "cloned" }); { const r = (try zitStep(gpa, io, out, abs_exe, cloned_dir, env, &.{"open"}, "zit open")) orelse return false; defer gpa.free(r.stdout); defer gpa.free(r.stderr); if (std.mem.indexOf(u8, r.stdout, "trunk") == null) { try out.print("FAIL S6 ({s}): zit open did not report opening trunk\n stdout: {s}\n", .{ citation, r.stdout }); return false; } } // THE PROOF: the file zit materialized == the bytes we committed through // fossil. A dropped artifact, a mis-reconstructed delta, or a materialization // that never wrote the file fails here. const opened_blob = try std.fs.path.join(arena, &.{ cloned_dir, "big.txt" }); const zit_bytes = std.Io.Dir.cwd().readFileAlloc(io, opened_blob, gpa, .limited(1 << 20)) catch |err| { try out.print("FAIL S6 ({s}): zit open left no readable big.txt in the cloned tree: {s}\n", .{ citation, @errorName(err) }); try printMismatchFooter(out, citation); return false; }; defer gpa.free(zit_bytes); if (!std.mem.eql(u8, s6_payload, zit_bytes)) { try out.print("FAIL S6 ({s}): the tree zit opened does not match the bytes committed through fossil\n", .{citation}); try printBytesPreview(out, "committed", s6_payload); try printBytesPreview(out, "zit opened", zit_bytes); try printDiffWindow(out, s6_payload, zit_bytes); try printMismatchFooter(out, citation); return false; } try out.print("PASS S6 ({s}): zit clone+open against a real fossil server materializes the committed bytes byte-for-byte\n", .{citation}); return true; } // --------------------------------------------------------------------------- // S7/S8 -- the PUSH-direction interop pair (sync §3.3/§3.7 push, §3.2/§3.7 // pull). S5/S6 exercise only pull/clone wire (fossil pulls from zit, zit pulls // from fossil); the push-side wire -- zit as the sync CLIENT uploading to a // server, its igot/gimme ordering and capability negotiation -- was invisible. // Both scenarios share the same prelude: zit authors a seed check-in, a real // fossil repo CLONES it (so client and server share a project-code, without // which the sync layer refuses the exchange), zit grows a second check-in the // fossil repo lacks, then zit PUSHES that growth up to a live `fossil server`. // // The file policy at the top holds throughout: every compared byte is OUR OWN // -- we author the pushed file blob and (in S8) the file blob a fossil commit // carries. fossil is the container and the transport; it never authors a byte // of an oracle. A mismatch means zit's push wire, or its pull-and-reconstruct, // dropped or corrupted content fossil relayed faithfully. // --------------------------------------------------------------------------- /// Loopback ports for the push pair -- distinct from S5/S6 (47311/47312) and /// every e2e port so a stray concurrent run cannot collide. Each scenario runs /// a zit-serve phase (fossil clones the seed) then a fossil-serve phase (zit /// pushes / pulls); S8 adds a third fossil-serve phase for the pull. const s7_zit_serve_port: u16 = 47313; const s7_fossil_serve_port: u16 = 47314; const s8_zit_serve_port: u16 = 47315; const s8_fossil_serve_port: u16 = 47316; const s8_fossil_pull_port: u16 = 47317; /// The seed the fossil repo clones and the zit repo shares -- our own bytes. const interop_seed_payload = \\seed line shared by the fossil clone and the zit repo \\ ; /// The file zit authors, commits, and PUSHES to the fossil server. Multi-line /// with a trailing newline so a truncated or line-ending-mangled push is caught. const push_grow_payload = \\zit authored this line and pushed it to a fossil server \\second line so a dropped tail is visible \\ ; /// S8: authored here, committed THROUGH a fossil checkout (fossil is the /// container), that zit must PULL and reconstruct. The library reader confirms /// convergence by reading it back under its own artifact name. const fossil_ext_payload = \\authored here, carried by a fossil commit, pulled by zit \\second line so a truncation is visible \\ ; /// The prelude state both push scenarios build on. const InteropSetup = struct { base: []const u8, zsrc: []const u8, dst_fossil: []const u8, abs_exe: []const u8, }; /// Spawn `fossil server --port <port> <repo>` detached, cwd `cwd_path`. /// stdout/stderr discarded -- driven purely over the socket. Caller `kill`s the /// returned child (kill reaps, releasing the repo's SQLite handle before the /// file is read back). fn spawnFossilServe( io: std.Io, repo_path: []const u8, cwd_path: []const u8, env: *const std.process.Environ.Map, port_str: []const u8, ) !std.process.Child { return std.process.spawn(io, .{ .argv = &.{ "fossil", "server", "--port", port_str, repo_path }, .cwd = .{ .path = cwd_path }, .environ_map = env, .stdout = .ignore, .stderr = .ignore, }); } /// `zit <tail...>` step, tagged with the scenario name/citation for failure /// reporting (the S5-scoped `zitStep` bakes in "S5"). Returns the captured /// result on success (caller frees), or null on a non-zero exit (already freed /// and reported). fn zitStepTagged( gpa: std.mem.Allocator, io: std.Io, out: *std.Io.Writer, abs_exe: []const u8, cwd_path: []const u8, env: *const std.process.Environ.Map, tail: []const []const u8, tag: []const u8, citation: []const u8, label: []const u8, ) !?std.process.RunResult { const res = try runZit(gpa, io, abs_exe, cwd_path, env, tail); if (!okExit(res.term)) { try out.print("FAIL {s} ({s}): {s} failed\n stderr: {s}\n", .{ tag, citation, label, res.stderr }); try printMismatchFooter(out, citation); gpa.free(res.stdout); gpa.free(res.stderr); return null; } return res; } /// `fossil <argv...>` step, tagged with the scenario name/citation (the /// S6-scoped `fossilStep` bakes in "S6"). True on exit 0; the caller aborts the /// scenario on false. fn fossilStepTagged( gpa: std.mem.Allocator, io: std.Io, out: *std.Io.Writer, argv: []const []const u8, cwd_path: []const u8, env: *const std.process.Environ.Map, tag: []const u8, citation: []const u8, label: []const u8, ) !bool { const res = try runFossil(gpa, io, argv, cwd_path, env); defer gpa.free(res.stdout); defer gpa.free(res.stderr); if (!okExit(res.term)) { try out.print("FAIL {s} ({s}): {s} failed\n stderr: {s}\n", .{ tag, citation, label, res.stderr }); return false; } return true; } /// The shared prelude: a zit repo and a fossil repo that share a project-code, /// with a zit-authored `grow` check-in PUSHED from zit up to the fossil server. /// Returns the paths on success, or null on any failure (already reported under /// `tag`). fn establishPushedFossil( arena: std.mem.Allocator, gpa: std.mem.Allocator, io: std.Io, tmp_root: []const u8, env: *const std.process.Environ.Map, out: *std.Io.Writer, tag: []const u8, citation: []const u8, sub: []const u8, seed_port: u16, push_port: u16, ) !?InteropSetup { const abs_exe = try zitAbsPath(arena, io); const base = try std.fs.path.join(arena, &.{ tmp_root, sub }); const zsrc = try std.fs.path.join(arena, &.{ base, "zsrc" }); const dst_fossil = try std.fs.path.join(arena, &.{ base, "dst.fossil" }); try std.Io.Dir.cwd().createDirPath(io, zsrc); // zit authors a seed check-in. { const r = (try zitStepTagged(gpa, io, out, abs_exe, zsrc, env, &.{"init"}, tag, citation, "zit init")) orelse return null; gpa.free(r.stdout); gpa.free(r.stderr); } const seed_path = try std.fs.path.join(arena, &.{ zsrc, "a.txt" }); try std.Io.Dir.cwd().writeFile(io, .{ .sub_path = seed_path, .data = interop_seed_payload }); inline for (.{ .{ &[_][]const u8{ "add", "a.txt" }, "zit add (seed)" }, .{ &[_][]const u8{ "commit", "-m", "seed", "--user", "u" }, "zit commit (seed)" }, }) |step| { const r = (try zitStepTagged(gpa, io, out, abs_exe, zsrc, env, step[0], tag, citation, step[1])) orelse return null; gpa.free(r.stdout); gpa.free(r.stderr); } // Serve the seed; the real fossil binary CLONES it, establishing the shared // project-code the sync layer requires before it will accept a push. const seed_port_str = try std.fmt.allocPrint(arena, "{d}", .{seed_port}); { var server = try spawnZitServe(io, abs_exe, zsrc, env, seed_port_str); waitForListen(io, seed_port) catch { server.kill(io); try out.print("FAIL {s} ({s}): zit serve never bound 127.0.0.1:{d}\n", .{ tag, citation, seed_port }); return null; }; const seed_url = try std.fmt.allocPrint(arena, "http://127.0.0.1:{d}/", .{seed_port}); const ok_clone = try fossilStepTagged(gpa, io, out, &.{ "fossil", "clone", seed_url, dst_fossil }, base, env, tag, citation, "fossil clone of the zit seed"); server.kill(io); if (!ok_clone) return null; } // The clone saved a remote (the now-dead seed server) as the fossil repo's // autosync target; disable autosync so a fossil-side commit (S8) cannot // silently abort trying to reach it. Grant the anonymous loopback sync user // check-in ('i') so the push is authorized (a bare sync request logs in as // "nobody", whose cloned caps are read-only). if (!try fossilStepTagged(gpa, io, out, &.{ "fossil", "settings", "autosync", "off", "-R", dst_fossil }, base, env, tag, citation, "fossil settings autosync off")) return null; if (!try fossilStepTagged(gpa, io, out, &.{ "fossil", "user", "capabilities", "nobody", "gjorzwik", "-R", dst_fossil }, base, env, tag, citation, "fossil user capabilities nobody")) return null; // zit grows a check-in the fossil repo lacks. const grow_path = try std.fs.path.join(arena, &.{ zsrc, "b.txt" }); try std.Io.Dir.cwd().writeFile(io, .{ .sub_path = grow_path, .data = push_grow_payload }); inline for (.{ .{ &[_][]const u8{ "add", "b.txt" }, "zit add (grow)" }, .{ &[_][]const u8{ "commit", "-m", "grow", "--user", "u" }, "zit commit (grow)" }, }) |step| { const r = (try zitStepTagged(gpa, io, out, abs_exe, zsrc, env, step[0], tag, citation, step[1])) orelse return null; gpa.free(r.stdout); gpa.free(r.stderr); } // fossil serves the shared repo; zit PUSHES its growth up over the wire. const push_port_str = try std.fmt.allocPrint(arena, "{d}", .{push_port}); { var server = spawnFossilServe(io, dst_fossil, base, env, push_port_str) catch |err| { try out.print("FAIL {s} ({s}): could not spawn fossil server: {s}\n", .{ tag, citation, @errorName(err) }); return null; }; waitForListen(io, push_port) catch { server.kill(io); try out.print("FAIL {s} ({s}): fossil server never bound 127.0.0.1:{d}\n", .{ tag, citation, push_port }); return null; }; const push_url = try std.fmt.allocPrint(arena, "http://127.0.0.1:{d}/", .{push_port}); const push_res = try zitStepTagged(gpa, io, out, abs_exe, zsrc, env, &.{ "push", push_url }, tag, citation, "zit push to the fossil server"); server.kill(io); const r = push_res orelse return null; gpa.free(r.stdout); gpa.free(r.stderr); } return .{ .base = base, .zsrc = zsrc, .dst_fossil = dst_fossil, .abs_exe = abs_exe }; } /// S7 -- zit push -> fossil server. After the shared prelude has pushed zit's /// grown check-in up to the fossil repo, a fresh fossil checkout of that repo /// must reconstruct the pushed file byte-for-byte: fossil hash-verifies every /// artifact it stores and reconstructs, so a checkout that yields our exact /// bytes proves the push delivered the artifacts intact over the wire. fn runPushToFossilServerScenario( arena: std.mem.Allocator, gpa: std.mem.Allocator, io: std.Io, tmp_root: []const u8, env: *const std.process.Environ.Map, out: *std.Io.Writer, ) !bool { const citation = "§3.3/§3.7"; const setup = (try establishPushedFossil(arena, gpa, io, tmp_root, env, out, "S7", citation, "S7", s7_zit_serve_port, s7_fossil_serve_port)) orelse return false; // THE PROOF: open the pushed-to repo and read back zit's grown file. fossil // materializes it from the artifacts the push delivered; a push that dropped // or corrupted a byte fails here. const co = try std.fs.path.join(arena, &.{ setup.base, "verify-co" }); try std.Io.Dir.cwd().createDirPath(io, co); if (!try fossilStepTagged(gpa, io, out, &.{ "fossil", "open", "--nosync", setup.dst_fossil }, co, env, "S7", citation, "fossil open of the pushed repo")) return false; const co_blob = try std.fs.path.join(arena, &.{ co, "b.txt" }); const fossil_bytes = std.Io.Dir.cwd().readFileAlloc(io, co_blob, gpa, .limited(1 << 20)) catch |err| { try out.print("FAIL S7 ({s}): the pushed repo has no readable b.txt after checkout: {s}\n", .{ citation, @errorName(err) }); try printMismatchFooter(out, citation); return false; }; defer gpa.free(fossil_bytes); if (!std.mem.eql(u8, push_grow_payload, fossil_bytes)) { try out.print("FAIL S7 ({s}): the file fossil reconstructed after zit's push does not match zit's committed bytes\n", .{citation}); try printBytesPreview(out, "zit pushed", push_grow_payload); try printBytesPreview(out, "fossil rebuilt", fossil_bytes); try printDiffWindow(out, push_grow_payload, fossil_bytes); try printMismatchFooter(out, citation); return false; } try out.print("PASS S7 ({s}): fossil reconstructs zit's pushed check-in byte-for-byte from the upload\n", .{citation}); return true; } /// S8 -- fossil commit -> zit pull. On the repo zit pushed to in the prelude, a /// fossil checkout commits one more file (our bytes, carried inside a /// fossil-authored check-in). zit then PULLS that extension. Convergence is /// asserted through the library reader: `zit.Repository` opens the pulled store /// and returns the carried blob under its own artifact name, byte-identical to /// what we authored -- so the pull ingested and reconstructed a check-in it did /// not itself write. fn runPullFossilExtensionScenario( arena: std.mem.Allocator, gpa: std.mem.Allocator, io: std.Io, tmp_root: []const u8, env: *const std.process.Environ.Map, out: *std.Io.Writer, ) !bool { const citation = "§3.2/§3.7"; const setup = (try establishPushedFossil(arena, gpa, io, tmp_root, env, out, "S8", citation, "S8", s8_zit_serve_port, s8_fossil_serve_port)) orelse return false; // A fossil checkout extends the shared repo with one more commit wrapping // OUR bytes. autosync is already off on the repo, so the commit cannot stall // reaching the dead seed remote; --nosync is belt-and-suspenders. const fco = try std.fs.path.join(arena, &.{ setup.base, "fossil-co" }); try std.Io.Dir.cwd().createDirPath(io, fco); if (!try fossilStepTagged(gpa, io, out, &.{ "fossil", "open", "--nosync", setup.dst_fossil }, fco, env, "S8", citation, "fossil open of the shared repo")) return false; if (!try fossilStepTagged(gpa, io, out, &.{ "fossil", "update", "trunk" }, fco, env, "S8", citation, "fossil update trunk")) return false; const ext_path = try std.fs.path.join(arena, &.{ fco, "c.txt" }); try std.Io.Dir.cwd().writeFile(io, .{ .sub_path = ext_path, .data = fossil_ext_payload }); if (!try fossilStepTagged(gpa, io, out, &.{ "fossil", "add", "c.txt" }, fco, env, "S8", citation, "fossil add")) return false; if (!try fossilStepTagged( gpa, io, out, &.{ "fossil", "commit", "-m", "fossil extends", "--user-override", "u", "--no-warnings", "--nosync" }, fco, env, "S8", citation, "fossil commit", )) return false; // fossil serves the extended repo; zit PULLS the check-in it lacks. const pull_port_str = try std.fmt.allocPrint(arena, "{d}", .{s8_fossil_pull_port}); { var server = spawnFossilServe(io, setup.dst_fossil, setup.base, env, pull_port_str) catch |err| { try out.print("FAIL S8 ({s}): could not spawn fossil server: {s}\n", .{ citation, @errorName(err) }); return false; }; waitForListen(io, s8_fossil_pull_port) catch { server.kill(io); try out.print("FAIL S8 ({s}): fossil server never bound 127.0.0.1:{d}\n", .{ citation, s8_fossil_pull_port }); return false; }; const pull_url = try std.fmt.allocPrint(arena, "http://127.0.0.1:{d}/", .{s8_fossil_pull_port}); const pull_res = try zitStepTagged(gpa, io, out, setup.abs_exe, setup.zsrc, env, &.{ "pull", pull_url }, "S8", citation, "zit pull from the fossil server"); server.kill(io); const r = pull_res orelse return false; gpa.free(r.stdout); gpa.free(r.stderr); } // THE PROOF, through the library reader: the pulled store holds the // fossil-carried blob under its own artifact name (sha3-256 over our bytes), // and `Repository.blob` re-verifies content against that name (§7.2) before // returning it. A pull that fetched nothing, or reconstructed the blob // wrong, fails here. var root = std.Io.Dir.cwd().openDir(io, setup.zsrc, .{}) catch |err| { try out.print("FAIL S8 ({s}): could not open the pulled zit checkout root: {s}\n", .{ citation, @errorName(err) }); return false; }; defer root.close(io); var repo: zit.Repository = undefined; repo.open(gpa, io, root, .{}) catch |err| { try out.print("FAIL S8 ({s}): opening the pulled zit repo via the library failed: {s}\n", .{ citation, @errorName(err) }); try printMismatchFooter(out, citation); return false; }; defer repo.deinit(gpa); var name_buf: [64]u8 = undefined; const ext_name = artifact.artifactName(.sha3_256, fossil_ext_payload, &name_buf); const got = (repo.blob(gpa, ext_name) catch |err| { try out.print("FAIL S8 ({s}): reading the pulled blob {s} via the library failed: {s}\n", .{ citation, ext_name, @errorName(err) }); try printMismatchFooter(out, citation); return false; }) orelse { try out.print("FAIL S8 ({s}): the pull did not converge — blob {s} absent from zit's store after pull\n", .{ citation, ext_name }); try printMismatchFooter(out, citation); return false; }; defer gpa.free(got); if (!std.mem.eql(u8, fossil_ext_payload, got)) { try out.print("FAIL S8 ({s}): the pulled blob does not match the bytes committed through fossil\n", .{citation}); try printBytesPreview(out, "committed", fossil_ext_payload); try printBytesPreview(out, "zit pulled", got); try printDiffWindow(out, fossil_ext_payload, got); try printMismatchFooter(out, citation); return false; } try out.print("PASS S8 ({s}): zit pulls a fossil-authored check-in and the library reader confirms convergence\n", .{citation}); return true; }