99 lines
3.7 KiB
Bash
Executable File
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
|