Files

157 lines
7.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# Real end-to-end test for packfreeze. Exits 0 only if every assertion holds.
# Run from anywhere: paths are resolved relative to this file.
set -u
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORK="$(mktemp -d "${TMPDIR:-/tmp}/packfreeze-test.XXXXXX")"
trap 'rm -rf "$WORK"' EXIT
PF="python3 $HERE/packfreeze.py"
fails=0
note() { printf ' %s\n' "$1"; }
ok() { printf 'ok - %s\n' "$1"; }
bad() { printf 'FAIL - %s\n' "$1"; fails=$((fails + 1)); }
# ---- fixture -------------------------------------------------------------
mkdir -p "$WORK/src/pkgA" "$WORK/src/pkgB" "$WORK/src/pkgA/__pycache__" "$WORK/src/.git"
printf 'alpha\n' > "$WORK/src/pkgA/a.py"
printf 'beta-beta\n' > "$WORK/src/pkgA/b.py"
printf 'gamma' > "$WORK/src/pkgB/c.py"
printf 'root file\n' > "$WORK/src/README.md"
printf 'stale\n' > "$WORK/src/pkgA/__pycache__/junk.pyc"
printf 'v1\n' > "$WORK/src/.git/config"
ln -s "$WORK/src/pkgA/a.py" "$WORK/src/link.py"
note "fixture built at $WORK/src"
# ---- 1. make -------------------------------------------------------------
$PF make --root "$WORK/src" --out "$WORK/lock1.json" > "$WORK/make1.log" 2>&1
rc=$?
[ $rc -eq 0 ] && ok "make exits 0" || bad "make exit=$rc"
grep -q 'made ' "$WORK/make1.log" && ok "make prints a summary line" || bad "no summary line"
# lock is valid JSON with the fields we rely on
python3 - "$WORK/lock1.json" <<'PY' && ok "lock parses as JSON with required keys" || bad "lock JSON/keys"
import json, sys
d = json.load(open(sys.argv[1]))
need = {"format", "generator", "file_count", "total_bytes", "digest_algorithm",
"packages", "tree_digest", "files"}
assert need <= set(d), need - set(d)
assert d["format"] == 1 and d["digest_algorithm"] == "blake2b"
assert isinstance(d["files"], dict) and d["files"]
PY
# ---- 2. determinism: same tree -> byte-identical lock --------------------
$PF make --root "$WORK/src" --out "$WORK/lock2.json" > /dev/null 2>&1
if cmp -s "$WORK/lock1.json" "$WORK/lock2.json"; then
ok "two runs produce byte-identical lockfiles"
else
bad "lockfiles differ between runs"
fi
# ---- 3. digests are real blake2b of real bytes ---------------------------
python3 - "$WORK/src" "$WORK/lock1.json" <<'PY' && ok "every recorded digest equals a fresh blake2b of the file bytes" || bad "digest mismatch vs blake2b"
import hashlib, json, os, sys
root, lock = sys.argv[1], json.load(open(sys.argv[2]))
bad = 0
for rel, dig in lock["files"].items():
p = os.path.join(root, rel)
h = hashlib.blake2b()
with open(p, "rb") as fh:
for b in iter(lambda: fh.read(1 << 16), b""):
h.update(b)
if h.hexdigest() != dig:
bad += 1
assert bad == 0, f"{bad} mismatches"
PY
# ---- 4. content change is detected --------------------------------------
printf 'alpha-MUTATED\n' > "$WORK/src/pkgA/a.py"
$PF verify --root "$WORK/src" --lock "$WORK/lock1.json" > "$WORK/ver.log" 2>&1
rc=$?
[ $rc -ne 0 ] && ok "verify fails (nonzero) after a content edit" || bad "verify returned 0 on changed file"
grep -q 'CHANGED pkgA/a.py' "$WORK/ver.log" && ok "verify names the changed file" || bad "changed file not reported"
printf 'alpha\n' > "$WORK/src/pkgA/a.py"
# ---- 5. deletion is detected --------------------------------------------
rm "$WORK/src/pkgB/c.py"
$PF verify --root "$WORK/src" --lock "$WORK/lock1.json" > "$WORK/ver2.log" 2>&1
rc=$?
[ $rc -ne 0 ] && ok "verify fails after a deletion" || bad "verify returned 0 on deleted file"
grep -q 'MISSING pkgB/c.py' "$WORK/ver2.log" && ok "verify names the missing file" || bad "missing file not reported"
printf 'gamma' > "$WORK/src/pkgB/c.py"
# ---- 6. added file is detected ------------------------------------------
printf 'new\n' > "$WORK/src/pkgB/d.py"
$PF verify --root "$WORK/src" --lock "$WORK/lock1.json" > "$WORK/ver3.log" 2>&1
rc=$?
[ $rc -ne 0 ] && ok "verify fails after an added file" || bad "verify returned 0 on added file"
grep -q 'ADDED pkgB/d.py' "$WORK/ver3.log" && ok "verify names the added file" || bad "added file not reported"
rm "$WORK/src/pkgB/d.py"
# ---- 7. clean tree verifies OK ------------------------------------------
$PF verify --root "$WORK/src" --lock "$WORK/lock1.json" > "$WORK/ver4.log" 2>&1
rc=$?
[ $rc -eq 0 ] && ok "verify passes on an untouched tree" || { bad "verify failed on clean tree"; cat "$WORK/ver4.log"; }
grep -q 'OK: ' "$WORK/ver4.log" && ok "verify prints an OK line" || bad "no OK line"
# ---- 8. mtime-only change must NOT alter the lock -----------------------
BEFORE=$(sha256sum "$WORK/lock1.json" | cut -d' ' -f1)
sleep 1
touch "$WORK/src/pkgA/a.py" "$WORK/src/README.md"
$PF make --root "$WORK/src" --out "$WORK/lock3.json" > /dev/null 2>&1
AFTER=$(sha256sum "$WORK/lock3.json" | cut -d' ' -f1)
[ "$BEFORE" = "$AFTER" ] && ok "touching files (mtime only) leaves the lock identical" || bad "lock changed on mtime-only touch"
# ---- 9. symlinks and VCS/cache dirs are excluded ------------------------
python3 - "$WORK/lock1.json" <<'PY' && ok "symlinks, .git and __pycache__ are excluded from the lock" || bad "unexpected entries in lock"
import json, sys
files = set(json.load(open(sys.argv[1]))["files"])
assert not any(f.startswith(".git/") for f in files), "git dir leaked in"
assert not any("__pycache__" in f for f in files), "pycache leaked in"
assert "link.py" not in files, "symlink was followed and locked"
assert "pkgA/a.py" in files and "README.md" in files, "real files missing"
PY
# ---- 10. package digests partition the tree ------------------------------
python3 - "$WORK/lock1.json" <<'PY' && ok "package digests cover every locked file exactly once" || bad "package partition wrong"
import json, sys
d = json.load(open(sys.argv[1]))
seen = {}
for pkg, items in []:
pass
# rebuild partition from files list via the documented rule
files = d["files"]
pkgs = {}
for rel in files:
name = rel.split("/", 1)[0] if "/" in rel else "(root)"
pkgs.setdefault(name, []).append(rel)
assert set(pkgs) == set(d["packages"]), (set(pkgs), set(d["packages"]))
assert sum(len(v) for v in pkgs.values()) == len(files), "files double-counted"
PY
# ---- 11. --include filter ------------------------------------------------
$PF make --root "$WORK/src" --out "$WORK/lockf.json" --include 'pkgA/*' > /dev/null 2>&1
python3 - "$WORK/lockf.json" <<'PY' && ok "--include restricts the lock to matching paths" || bad "--include filter broken"
import json, sys
files = json.load(open(sys.argv[1]))["files"]
assert files and all(f.startswith("pkgA/") for f in files), files
assert "pkgA/a.py" in files and "README.md" not in files, files
PY
# ---- 12. check writes a machine-readable report --------------------------
$PF check --root "$WORK/src" --lock "$WORK/lock1.json" --output "$WORK/report.json" > "$WORK/chk.log" 2>&1
rc=$?
[ $rc -eq 0 ] && ok "check exits 0 on a clean tree" || bad "check exit=$rc on clean tree"
python3 - "$WORK/report.json" <<'PY' && ok "check report is valid JSON with ok=true and empty diffs" || bad "report shape wrong"
import json, sys
r = json.load(open(sys.argv[1]))
assert r["ok"] is True, r
assert r["missing"] == [] and r["changed"] == [] and r["added"] == [] and r["stale"] == [], r
assert r["locked_tree_digest"] == r["observed_tree_digest"], r
assert r["file_count"] > 0, r
PY
printf '\n%d failure(s)\n' "$fails"
[ "$fails" -eq 0 ]