Files
propresenter-auto-notes/tests/run_tests.py
T

106 lines
4.8 KiB
Python
Raw Normal View History

2026-10-13 15:58:03 -04:00
"""Offline checks (no ProPresenter, no fonts needed). Run: python3 tests/run_tests.py"""
import os
import re
import sys
import traceback
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(HERE, "..", "src"))
import deck # noqa: E402
import notes # noqa: E402
import pbwire # noqa: E402
import plantext # noqa: E402
import rtf # noqa: E402
NOTES = os.path.join(HERE, "notes")
PRO = os.path.join(HERE, "pro")
SERIES = os.path.join(PRO, "_TEMPLATE - DO NOT DELETE 2608 Lower Thirds.pro")
GENERIC = os.path.join(PRO, "_TEMPLATE_GENERIC - DO NOT DELETE 2609.pro")
failures = []
def check(name, cond, detail=""):
print("%-6s %s%s" % ("ok" if cond else "FAIL", name, (" -- " + detail) if (detail and not cond) else ""))
if not cond:
failures.append(name)
def slides_of(data):
p = deck.C.decode(data, "rv.data.Presentation")
cues = {deck.uuid_str(deck.C.get(c, "uuid")): c for c in deck.C.all(p, "cues")}
out = []
for g in deck.C.all(p, "cue_groups"):
for cid in deck.C.all(g, "cue_identifiers"):
a = deck.slide_action(cues[deck.uuid_str(cid)])
texts = []
for el in deck.slide_elements(a):
t = deck.element_text(el)
d = deck.C.get(t, "rtf_data") if t is not None else None
if d:
s = " / ".join(p_.text for p_ in rtf.read(d).paras).strip()
if s:
texts.append((deck.element_name(el), s))
out.append((deck.action_label(a), texts))
return out
def main():
# 1. every .pro file survives decode/encode byte for byte
for f in sorted(os.listdir(PRO)):
if f.endswith(".pro"):
b = open(os.path.join(PRO, f), "rb").read()
check("roundtrip %s" % f, deck.C.encode(deck.C.decode(b, "rv.data.Presentation")) == b)
# 2. every notes file parses; the slide list survives the TextEdit format
for f in sorted(os.listdir(NOTES)):
try:
p = notes.parse(open(os.path.join(NOTES, f), "rb").read())
p2 = plantext.load(plantext.dump(p, f))
same = [(s.kind, s.ref, [(l.text, l.lead) for l in s.lines]) for s in p.slides] == \
[(s.kind, s.ref, [(l.text, l.lead) for l in s.lines]) for s in p2.slides]
check("notes %s (%d slides)" % (f, len(p.slides)), same and p.title_slide is not None)
except Exception:
check("notes %s" % f, False, traceback.format_exc().splitlines()[-1])
# 3. Part 7 from its notes matches the hand-made Part 7 (apart from the known differences)
tpl = deck.Template(open(SERIES, "rb").read())
plan = notes.parse(open(os.path.join(NOTES, "Slides - Where Is Your Faith - Part 7.rtf"), "rb").read())
data = deck.build(tpl, plan, "TEST", ("LOCATION PASTOR", "Mehdi Chniouli"))
gen = slides_of(data)
hand = slides_of(open(os.path.join(PRO, "261011 Where Is Your Faith Part 7.pro"), "rb").read())
check("part 7 slide count", len(gen) == len(hand), "%d vs %d" % (len(gen), len(hand)))
check("part 7 labels", [g[0] for g in gen] == [h[0] for h in hand])
norm = lambda s: re.sub(r"\s+", " ", s.replace(" ", " ")).strip()
diffs = []
for k, (g, h) in enumerate(zip(gen, hand), 1):
if [(n, norm(t)) for n, t in g[1]] != [(n, norm(t)) for n, t in h[1]]:
diffs.append(k)
# 17: the operator added a manual line break ("can't follow / me THROUGH")
check("part 7 texts identical except slide 17", diffs == [17], "differs on %s" % diffs)
# 4. both templates build without text measurement, and the result decodes
for path in (SERIES, GENERIC):
t = deck.Template(open(path, "rb").read())
for f in ("Slides - 7 Letters - Part 2.rtf", "Slides - May 2026 Forge Night.rtf"):
p = notes.parse(open(os.path.join(NOTES, f), "rb").read())
d = deck.build(t, p, "TEST", t.speaker())
out = slides_of(d)
n_build = sum(1 for s in p.slides if s.kind == "build")
want = len(t.head) + sum(1 for s in p.slides if s.kind != "title") + n_build + len(t.tail)
check("build %s + %s" % (os.path.basename(path)[:22], f), len(out) == want, "%d vs %d" % (len(out), want))
# 5. fresh ids everywhere, references to shared things untouched
tu = set(re.findall(rb"[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}", open(SERIES, "rb").read()))
gu = set(re.findall(rb"[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}", data))
shared = tu & gu
check("only shared references reused (%d)" % len(shared), all(
x.decode() in open(SERIES, "rb").read().decode("latin-1") for x in shared) and len(shared) <= 12)
print()
print("FAILED: %s" % failures if failures else "all checks passed")
return 1 if failures else 0
if __name__ == "__main__":
sys.exit(main())