Skip to content
zit Artifact [ec1ed6d6dd]
Login

Artifact [ec1ed6d6dd]

Artifact ec1ed6d6dd008baff21a63c6225aa06768fcf67e0876e23e1813001bb8c329b6:


#!/usr/bin/env python3
"""Compare two unpacked-archive descriptions (#95).

Each input is one member per line, tab separated:

    RELATIVE-PATH <TAB> KIND <TAB> SHA256

where KIND is `file`, `exec`, or `symlink`, and SHA256 is over the file's
contents or, for a symlink, over its target path.

The three categories are reported separately on purpose. "Same bytes, wrong
bit" is a different defect from "wrong bytes": the first ships a release whose
./configure will not run, the second ships a corrupt file, and a comparator
that only said "these differ" would leave the reader to work out which.

Exits 0 when the two describe the same tree, 1 otherwise.
"""

import sys


def load(path):
    members = {}
    with open(path) as handle:
        for lineno, line in enumerate(handle, 1):
            line = line.rstrip("\n")
            if not line:
                continue
            parts = line.split("\t")
            if len(parts) != 3:
                raise SystemExit(f"{path}:{lineno}: expected 3 tab-separated fields")
            rel, kind, digest = parts
            members[rel] = (kind, digest)
    return members


def main(argv):
    if len(argv) != 3:
        raise SystemExit("usage: archive-compare.py FOSSIL.members ZIT.members")
    fossil, zit = load(argv[1]), load(argv[2])
    bad = False

    only_fossil = sorted(set(fossil) - set(zit))
    only_zit = sorted(set(zit) - set(fossil))
    if only_fossil or only_zit:
        bad = True
        print("   member sets DIFFER")
        for path in only_fossil:
            print(f"     only in fossil: {path}")
        for path in only_zit:
            print(f"     only in zit:    {path}")
    else:
        print(f"   member sets identical ({len(fossil)})")

    shared = sorted(set(fossil) & set(zit))

    mode_bad = [p for p in shared if fossil[p][0] != zit[p][0]]
    if mode_bad:
        bad = True
        print("   modes DIFFER (an executable that unpacks 0644 is the archetypal release bug)")
        for path in mode_bad:
            print(f"     {path}: fossil={fossil[path][0]} zit={zit[path][0]}")
    else:
        print("   modes identical")

    hash_bad = [p for p in shared if fossil[p][1] != zit[p][1]]
    if hash_bad:
        bad = True
        print("   contents DIFFER")
        for path in hash_bad:
            print(f"     {path}")
    else:
        print("   contents identical")

    # Say what was actually compared. "modes identical" over a corpus of plain
    # files proves nothing about modes, and reads exactly like a corpus that
    # exercised them -- silence is not success. Reported rather than enforced,
    # because a caller may legitimately point this at a corpus it did not
    # choose; the caller that builds its own fixture should check this line.
    kinds = sorted({fossil[p][0] for p in shared})
    print("   kinds compared: %s" % (", ".join(kinds) if kinds else "none"))
    for expected in ("exec", "symlink"):
        if expected not in kinds:
            print(f"   note: no {expected} member in this corpus, so that mode is untested")

    return 1 if bad else 0


if __name__ == "__main__":
    sys.exit(main(sys.argv))