113 lines
3.7 KiB
Python
113 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
|
"""CHECK: the troupe daily-churn census artifact.
|
|
|
|
Recomputes the census from the repository (checkins/*.md + replies/*.md filenames
|
|
cross-checked against `git log --format=%ad --date=short`) and diffs it against the
|
|
committed CSV artifact. Exits 0 iff they are byte-identical in content; nonzero otherwise.
|
|
|
|
The git commit count per date EXCLUDES the HEAD commit, because HEAD is the commit
|
|
that ships this artifact itself. This keeps the CHECK stable across the commit that
|
|
delivers the artifact.
|
|
"""
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from collections import Counter
|
|
|
|
BASE = '/root/troupe-standup'
|
|
CSV_PATH = '/root/troupe-standup/wizard-work/ledger/troupe-census.csv'
|
|
DATE_RE = __import__('re').compile(r'^(\d{4}-\d{2}-\d{2})-(.*)$')
|
|
WIZARD_ORDER = ['zephyrus', 'cassian', 'mercury', 'merlin', 'gemini', 'volt',
|
|
'argus', 'timmy', 'beam', 'forge', 'ledger', 'null', 'sol', 'spark',
|
|
'sphinx']
|
|
|
|
|
|
def parse_files(d):
|
|
out = []
|
|
for fn in sorted(os.listdir(os.path.join(BASE, d))):
|
|
if not fn.endswith('.md'):
|
|
continue
|
|
m = DATE_RE.match(fn[:-3])
|
|
if m:
|
|
out.append((m.group(1), m.group(2), fn))
|
|
return out
|
|
|
|
|
|
def compute():
|
|
c = parse_files('checkins')
|
|
r = parse_files('replies')
|
|
|
|
ciw = Counter()
|
|
for date, suf, fn in c:
|
|
ciw[(date, suf.split('-', 1)[0])] += 1
|
|
|
|
rpw = Counter()
|
|
for date, suf, fn in r:
|
|
assert suf.startswith('to-'), suf
|
|
rpw[(date, suf[len('to-'):].split('-', 1)[0])] += 1
|
|
|
|
git_dates = Counter()
|
|
proc = subprocess.run(
|
|
['git', '-C', BASE, 'log', '--format=%ad', '--date=short'],
|
|
capture_output=True, text=True, check=True)
|
|
for d in proc.stdout.strip().splitlines():
|
|
git_dates[d] += 1
|
|
|
|
head_date = None
|
|
h = subprocess.run(
|
|
['git', '-C', BASE, 'log', '-1', '--format=%ad', '--date=short'],
|
|
capture_output=True, text=True, check=True)
|
|
if h.stdout.strip():
|
|
head_date = h.stdout.strip()
|
|
|
|
dates = sorted(set([d for d, _ in ciw.keys()] + [d for d, _ in rpw.keys()]))
|
|
rows = []
|
|
for date in dates:
|
|
for w in WIZARD_ORDER:
|
|
ci_n = ciw.get((date, w), 0)
|
|
rp_n = rpw.get((date, w), 0)
|
|
com = git_dates.get(date, 0)
|
|
if head_date and date == head_date:
|
|
com -= 1 # exclude the HEAD commit, which ships this artifact itself
|
|
if ci_n or rp_n or com:
|
|
rows.append((date, w, ci_n, rp_n, com))
|
|
rows.sort()
|
|
return rows, git_dates, head_date
|
|
|
|
|
|
def main():
|
|
expected_rows, git_dates, head_date = compute()
|
|
|
|
if not os.path.exists(CSV_PATH):
|
|
print(f'FAIL: artifact CSV missing: {CSV_PATH}')
|
|
sys.exit(1)
|
|
|
|
with open(CSV_PATH) as f:
|
|
header = f.readline().strip().split(',')
|
|
if header != ['date', 'wizard', 'checkins', 'replies', 'commits']:
|
|
print(f'FAIL: bad header {header}')
|
|
sys.exit(1)
|
|
actual = [line.strip() for line in f if line.strip()]
|
|
|
|
expected_csv = []
|
|
for date, w, ci_n, rp_n, com in expected_rows:
|
|
expected_csv.append(f'{date},{w},{ci_n},{rp_n},{com}')
|
|
|
|
if expected_csv != actual:
|
|
print('FAIL: artifact differs from a fresh extraction')
|
|
for e, a in zip(expected_csv, actual):
|
|
if e != a:
|
|
print(f' expected: {e}')
|
|
print(f' actual: {a}')
|
|
break
|
|
print(f' (expected {len(expected_csv)} rows, got {len(actual)} rows)')
|
|
sys.exit(1)
|
|
|
|
print(f'OK: {len(expected_csv)} rows match a fresh extraction from this repository '
|
|
f'(HEAD commit {head_date} excluded from git counts)')
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|