Files
tai/taicpp/tools/wa-coverage.sh
T

99 lines
3.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# wa-coverage.sh: gate whatsmeow API coverage against the C++ port (M0 harness;
# see docs/whatsapp.md section 17 and full_transpilation_and_integration.md
# prompt sections 1 and 3).
#
# Usage: tools/wa-coverage.sh [--slice FILE]
#
# tools/wa-api-inventory.sh is re-run first so the inventory is never stale.
# tools/wa-mapping.tsv maps every inventory key to its C++ counterpart or to
# UNMAPPED. The key sets must match exactly: an inventory key missing from the
# mapping, or a mapping key no longer in the inventory, fails loudly (the
# mapping is stale, extend it, do not waive it). There is no allowlist.
#
# A slice file scopes the gate to one milestone's API: lines of
# file:<whatsmeow-relative-path> every entry from that Go file
# pkg:<go-package> every entry from that package
# sym:<exact-key> one entry
# re:<python-regex> every key the regex matches
# ("#" comments and blank lines skipped; an entry matching ANY line counts.)
# Without --slice the whole inventory is gated.
#
# Exit 0 only at 100% mapped; otherwise the unmapped keys are listed.
set -euo pipefail
cd "$(dirname "$0")/.."
SLICE=""
if [ "${1:-}" = "--slice" ]; then SLICE="${2:?need a slice file}"; shift 2; fi
test $# -eq 0 || { echo "usage: tools/wa-coverage.sh [--slice FILE]" >&2; exit 1; }
test -z "$SLICE" || test -f "$SLICE" || { echo "wa-coverage: no slice $SLICE" >&2; exit 1; }
./tools/wa-api-inventory.sh >/dev/null
MAP="tools/wa-mapping.tsv"
test -f "$MAP" || { echo "wa-coverage: missing $MAP" >&2; exit 1; }
SLICE="$SLICE" MAP="$MAP" python3 - <<'EOF'
import os, re, sys
inv = {}
for ln in open("build/wa-inventory.tsv", encoding="utf-8"):
pkg, kind, key, rel = ln.rstrip("\n").split("\t")
inv[key] = (pkg, kind, rel)
mapping = {}
for ln in open(os.environ["MAP"], encoding="utf-8"):
if not ln.strip() or ln.startswith("#"):
continue
key, cpp = ln.rstrip("\n").split("\t", 1)
mapping[key] = cpp.strip()
fail = []
for key in sorted(set(inv) - set(mapping)):
fail.append("missing from mapping: %s (%s %s)" % (key, inv[key][1], inv[key][2]))
for key in sorted(set(mapping) - set(inv)):
fail.append("stale mapping key (not in inventory): %s" % key)
if fail:
print("wa-coverage: mapping out of sync with inventory:")
for f in fail[:40]:
print(" " + f)
if len(fail) > 40:
print(" ... and %d more" % (len(fail) - 40))
sys.exit(1)
slicepath = os.environ["SLICE"]
wanted = None
if slicepath:
rules = []
for ln in open(slicepath, encoding="utf-8"):
ln = ln.strip()
if not ln or ln.startswith("#"):
continue
kind, _, val = ln.partition(":")
rules.append((kind.strip(), val.strip()))
wanted = set()
for key, (pkg, _, rel) in inv.items():
for kind, val in rules:
if (kind == "file" and rel == val) or \
(kind == "pkg" and pkg == val) or \
(kind == "sym" and key == val) or \
(kind == "re" and re.search(val, key)):
wanted.add(key)
break
if not wanted:
print("wa-coverage: slice %s matched nothing" % slicepath)
sys.exit(1)
keys = sorted(wanted if wanted is not None else inv)
unmapped = [k for k in keys if mapping[k] == "UNMAPPED"]
pct = 100.0 * (len(keys) - len(unmapped)) / len(keys)
scope = ("slice %s" % slicepath) if slicepath else "full inventory"
print("wa-coverage [%s]: %d/%d mapped (%.1f%%)" % (scope, len(keys) - len(unmapped), len(keys), pct))
if unmapped:
for k in unmapped[:60]:
print(" UNMAPPED %s (%s %s)" % (k, inv[k][1], inv[k][2]))
if len(unmapped) > 60:
print(" ... and %d more" % (len(unmapped) - 60))
sys.exit(1)
print("wa-coverage: 100%%")
EOF