Artifact f2565465a36a6c187d49d116f4e671ed843ea6c1a291fcc9c063af29a9baa3c8:
- File
src/smoke.zig
— part of check-in
[efaf2d3ae1]
at
2026-07-18 08:21:09
on branch storage
— feat(§6): pack re-deltification with chronological+wire-delta rebasing and monotonic-shrink guard
fossil-scm-clone: zit=110083984B fossil=72245248B ratio=1.5238 (packed store, exact accounting header64+zlib+delta+config==110083984: zlib 8377 frames / 84,763,872 B 77.0% avg 10118 B; delta 59122 frames / 25,319,960 B 23.0% avg 428 B; config 1 frame / 88 B; raw 0. Artifact-type: checkin/manifest 20848 / 23,816,728 B 21.6%, other 6929 / 4,706,432 B 4.3%, file-blob 39722 / 81,560,672 B 74.1%. As-cloned baseline was 117,528,088 B zlib 9647/92,073,728; delta 57852/25,454,120; ratio 1.6268.)
sqlite: zit=175246544B fossil=147709952B ratio=1.1864 (packed store, exact accounting header64+zlib+delta+config==175246544: zlib 9260 frames / 113,048,584 B 64.5% avg 12208 B; delta 121734 frames / 62,197,808 B 35.5% avg 510 B; config 1 frame / 88 B; raw 0. Artifact-type: checkin/manifest 36515 / 65,427,440 B 37.3%, other 4775 / 5,462,856 B 3.1%, file-blob 89704 / 104,356,096 B 59.5%. As-cloned baseline was 207,344,544 B zlib 10847/140,422,768; delta 120147/66,921,536; ratio 1.4037.)
vs baseline:
fossil-scm-clone: zit=117528088B fossil=72245248B ratio=1.627 (raw=0B/0f; zlib=92073728B/9647f 78.3%; delta=25454120B/57852f 21.7%; config=176B/2f. Header+padding overhead 3711845B 3.2%. Artifact-type: checkin/manifest 26721968B/20848, other 4835264B/6929, file-blob 85970616B/39722. Accounting exact.)
sqlite: zit=207344544B fossil=147709952B ratio=1.404 (raw=0B/0f; zlib=140422768B/10847f 67.7%; delta=66921536B/120147f 32.3%; config=176B/2f. Header+padding overhead 7212808B 3.5%. Artifact-type: checkin/manifest 81006048B/36515, other 5531960B/4775, file-blob 120806296B/89704. Accounting exact.) (user: dmestas, size: 28842) [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; if (!fossilAvailable(gpa, io)) { 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; try out.flush(); if (!all_pass) std.process.exit(1); } // --------------------------------------------------------------------------- // Preflight // --------------------------------------------------------------------------- fn fossilAvailable(gpa: std.mem.Allocator, io: std.Io) bool { const res = std.process.run(gpa, io, .{ .argv = &.{ "fossil", "version" } }) catch return false; defer gpa.free(res.stdout); defer gpa.free(res.stderr); return okExit(res.term); } 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 `zit serve` 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; }