#!/usr/bin/env python3 """Differential test: this base64 against GNU coreutils base64. Runs both binaries over the same inputs and compares stdout, stderr and exit code. Linux-only — it needs a real GNU base64 to diff against, so it cannot run on the Windows machine the tool is actually built for. cargo build --release && python3 tests/parity.py Note that many distributions now ship uutils coreutils as /usr/bin/base64, which differs from GNU on malformed input. This script looks for a genuine GNU build and refuses to run against anything else. """ import os import random import shutil import subprocess import sys import tempfile ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) MINE = os.path.join(ROOT, "target", "release", "base64") # Differences that are intentional; see README.md. EXPECTED_DIFF = { # We skip '\r' alongside '\n' so CRLF base64 files work on Windows. "decode CRLF wrapped", # Our --help omits GNU's own support URLs. "help", "help after other flags", } def find_gnu(): """Locate a genuine GNU base64, ignoring uutils drop-in replacements.""" for name in ("gnubase64", "base64", "gbase64"): path = shutil.which(name) if not path: continue try: banner = subprocess.run( [path, "--version"], capture_output=True, text=True, timeout=10 ).stdout except OSError: continue if "GNU coreutils" in banner: return path, banner.splitlines()[0] return None, None GNU, GNU_BANNER = find_gnu() def norm(b, gnu_name): """Strip argv[0] and locale noise so the two are comparable.""" b = b.replace(gnu_name.encode(), b"base64") b = b.replace(os.path.basename(gnu_name).encode(), b"base64") # GNU quotes with U+2018/2019 outside the C locale; we always use ASCII. b = b.replace("‘".encode(), b"'").replace("’".encode(), b"'") return b def run(exe, args, data): # C locale: GNU otherwise uses en_GB spelling and Unicode quotes, neither # of which a Windows console would produce. env = dict(os.environ, LC_ALL="C") p = subprocess.run([exe] + args, input=data, capture_output=True, env=env) return p.returncode, norm(p.stdout, GNU), norm(p.stderr, GNU) results = [] def t(name, args, data=b""): mine = run(MINE, args, data) gnu = run(GNU, args, data) ok = mine == gnu if not ok: tag = "DIFF(expected)" if name in EXPECTED_DIFF else "FAIL" print(f"[{tag}] {name} args={args}") if mine[0] != gnu[0]: print(f" exit: mine={mine[0]} gnu={gnu[0]}") if mine[1] != gnu[1]: print(f" out : mine={mine[1][:90]!r}") print(f" gnu ={gnu[1][:90]!r}") if mine[2] != gnu[2]: print(f" err : mine={mine[2][:120]!r}") print(f" gnu ={gnu[2][:120]!r}") results.append((name, ok or name in EXPECTED_DIFF)) def main(): random.seed(7) big = bytes(random.getrandbits(8) for _ in range(200_000)) # ── encoding ────────────────────────────────────────────────────── t("encode empty", [], b"") for n in (1, 2, 3, 4, 5, 56, 57, 58, 76, 1000): t(f"encode {n} bytes", [], (bytes(range(256)) * 10)[:n]) t("encode all byte values", [], bytes(range(256))) t("encode 200KB binary", [], big) sample = b"hello world, this is a longer sample string for wrapping" for w in ("0", "1", "2", "3", "4", "5", "76", "77", "1000"): t(f"encode wrap {w}", ["-w", w], sample) t(f"encode wrap {w} joined", [f"-w{w}"], sample) t(f"encode wrap {w} long", ["--wrap=" + w], big[:5000]) # ── decoding ────────────────────────────────────────────────────── for n in (0, 1, 2, 3, 4, 5, 100, 1000): enc = subprocess.run([GNU], input=big[:n], capture_output=True).stdout t(f"decode roundtrip {n}", ["-d"], enc) t("decode 200KB roundtrip", ["-d"], subprocess.run([GNU], input=big, capture_output=True).stdout) t("decode unwrapped", ["-d"], b"aGVsbG8gd29ybGQ=") t("decode no trailing newline", ["-d"], b"aGVsbG8=") t("decode with newlines", ["-d"], b"aGVs\nbG8=\n") t("decode CRLF wrapped", ["-d"], b"aGVs\r\nbG8=\r\n") t("decode empty", ["-d"], b"") t("decode only newline", ["-d"], b"\n") t("decode spaces garbage", ["-d"], b"aGVs bG8=") t("decode spaces garbage -i", ["-di"], b"aGVs bG8=") t("decode junk", ["-d"], b"aG!!Vs*bG8=") t("decode junk -i", ["-di"], b"aG!!Vs*bG8=") t("decode junk only -i", ["-di"], b"!!!!") t("decode nul bytes -i", ["-di"], b"aGVs\x00bG8=") t("decode high bytes", ["-d"], b"aGVs\xffbG8=") t("decode high bytes -i", ["-di"], b"aGVs\xffbG8=") t("decode urlsafe chars", ["-d"], b"_-_-") # Unpadded tails are legal only when the leftover bits are zero. for s in (b"a", b"aA", b"aQ", b"ag", b"aG", b"aGA", b"aGE", b"aGV", b"aB", b"aC", b"aGB", b"aGC", b"aGVs", b"aGVsb", b"aGVsbG", b"aGVsbG8"): t(f"decode tail {s.decode()}", ["-d"], s) # Padding placement, and streams concatenated after padding. for s in (b"=", b"====", b"=aGVsbG8=", b"aG=sbG8=", b"aGVsbG8===", b"aA==", b"aQ==", b"aG==", b"aGA=", b"aGV=", b"aA=", b"aA=x", b"aG=x", b"aGA=x", b"aA==x", b"aA==aGVs", b"aGVsbG8=x", b"aGVsbG8=aGVs", b"aGVsbG8=\naGVs", b"aGVsbG8=aGVsbG8="): t(f"decode pad {s.decode()}", ["-d"], s) # Every prefix of a known-good encoding, strict and lenient. enc = subprocess.run([GNU], input=b"hello world!", capture_output=True).stdout.strip() for i in range(1, len(enc) + 1): t(f"decode prefix {i}", ["-d"], enc[:i]) t(f"decode prefix {i} -i", ["-di"], enc[:i]) # ── options and usage errors ────────────────────────────────────── t("help", ["--help"]) t("help after other flags", ["--decode", "--help"]) t("abbrev --dec", ["--dec"], b"aGVsbG8=") t("abbrev --ign", ["--ign", "-d"], b"aGV s bG8=") t("abbrev --w", ["--w", "4"], b"hello world") t("bare --", ["--"], b"hello") t("cluster -di", ["-di"], b"aG!Vs bG8=") t("cluster -dw0", ["-dw0"], b"aGVsbG8=") t("wrap missing arg", ["-w"]) t("wrap bad value", ["-w", "abc"]) t("wrap negative", ["-w", "-5"]) t("unknown short", ["-z"]) t("unknown long", ["--nope"]) t("ambiguous long", ["--i"]) t("extra operand", ["a.txt", "b.txt"]) t("missing file", ["/nonexistent/nope.txt"]) t("directory as file", ["/tmp"]) t("dash is stdin", ["-"], b"hello") t("double dash then dash", ["--", "-"], b"hello") # ── file operands ───────────────────────────────────────────────── with tempfile.TemporaryDirectory() as tmp: raw = os.path.join(tmp, "sample.bin") with open(raw, "wb") as fh: fh.write(big[:9999]) t("encode from file", [raw]) t("encode from file wrap 0", ["-w0", raw]) encoded = os.path.join(tmp, "sample.b64") with open(encoded, "wb") as fh: fh.write(subprocess.run([GNU], input=big[:9999], capture_output=True).stdout) t("decode from file", ["-d", encoded]) passed = sum(1 for _, ok in results if ok) print(f"\n{passed}/{len(results)} cases match GNU base64") failed = [n for n, ok in results if not ok] if failed: print("failing:", ", ".join(failed)) return 1 print("all parity checks passed") return 0 if __name__ == "__main__": if not os.path.exists(MINE): sys.exit(f"build it first: cargo build --release (looked for {MINE})") if GNU is None: sys.exit( "no GNU coreutils base64 found. Distributions increasingly ship " "uutils as /usr/bin/base64, which differs on malformed input; " "install GNU coreutils to run this comparison." ) print(f"comparing {MINE}\n against {GNU} ({GNU_BANNER})\n") sys.exit(main())