Files
cellar-cli-c/tests/tar_compare.py
T
Matteo Benedetto 090005f6b9 cellar-cli-c: client CLI di Cellar reimplementato in C (statico, retrocompatibile)
Reimplementazione fedele di cellar-cli.py (enne2/cellar @ f5216b1) in C99
POSIX.1-2008, mono-thread, senza dipendenze esterne: miniz e' vendored per
deflate/inflate raw, mentre contenitore gzip, multipart/form-data in streaming,
tar PAX/ustar, JSON e client HTTP/1.1 sono scritti a mano.

- 7 comandi (list, upload, download, install, scan-local, wizard-upload,
  wizard-install) con stdout/stderr/exit code identici al client Python
- 30/30 test di parita' (tests/parity_test.sh) contro due server mock
  indipendenti: output a confronto, alberi installati, interop tar con
  tarfile di Python e con GNU tar, archivio C equivalente a quello Python
- build statiche x86-64 / i686 / aarch64: nessun simbolo GLIBC richiesto,
  nessuna syscall moderna (statx/openat2/memfd_create/getrandom), LFS 64 bit,
  resolver DNS di riserva (hosts + query UDP) per glibc statica senza NSS
- miglioramento rispetto all'originale: upload multipart in streaming invece
  di leggere l'intero archivio in RAM
- divergenze volute documentate nel README (help piu' sintetico, mtime dei
  symlink non ripristinato come tarfile, EOF nei prompt, niente TLS)

Build: make | make 32 | make arm64 | make test | make verify
2026-09-20 17:29:14 +02:00

67 lines
1.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""Confronta due archivi tar.gz membro per membro (parita' writer C vs Python).
Uso: tar_compare.py a.tar.gz b.tar.gz [--tolerance 1e-6]
"""
from __future__ import annotations
import argparse
import hashlib
import sys
import tarfile
def load(path: str):
out = {}
with tarfile.open(path, "r:gz") as tf:
for m in tf.getmembers():
digest = "-"
if m.isreg():
fh = tf.extractfile(m)
if fh is not None:
digest = hashlib.sha256(fh.read()).hexdigest()
out[m.name] = {
"type": m.type.decode() if isinstance(m.type, bytes) else m.type,
"mode": m.mode,
"uid": m.uid,
"gid": m.gid,
"size": m.size,
"link": m.linkname,
"mtime": m.mtime,
"sha": digest,
}
return out
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("a")
ap.add_argument("b")
ap.add_argument("--tolerance", type=float, default=1e-6)
args = ap.parse_args()
a, b = load(args.a), load(args.b)
ok = True
for name in sorted(set(a) | set(b)):
if name not in a:
print(f"SOLO IN B: {name}")
ok = False
continue
if name not in b:
print(f"SOLO IN A: {name}")
ok = False
continue
x, y = a[name], b[name]
for field in ("type", "mode", "uid", "gid", "size", "link", "sha"):
if x[field] != y[field]:
print(f"DIFF {name}: {field} A={x[field]!r} B={y[field]!r}")
ok = False
if abs(x["mtime"] - y["mtime"]) > args.tolerance:
print(f"DIFF {name}: mtime A={x['mtime']:.7f} B={y['mtime']:.7f}")
ok = False
print("archivi equivalenti" if ok else "archivi DIVERSI")
return 0 if ok else 1
if __name__ == "__main__":
raise SystemExit(main())