Files
felhom.eu/scripts/script_tests_gate.py
T
admin 970616b72b
gates / gates (push) Failing after 11m41s
New apps 2026-10-10: Grocy and LubeLogger on the website; Monica stopped
The catalogue side is app-catalog-felhom.eu b7f0f7c. Here: the evidence, the
website, the register and the operator's view.

Website
- Two cards in the Otthon & Eletmod / Home & Lifestyle section of BOTH apps
  pages, with assets: grocy-logo.svg is grocy's own icon with its single fill
  made white like the other logos, lubelogger-logo.png is the app's own icon
  with its dark background dropped and the mark made white (the rule
  SparkyFitness's PNG follows), and six screenshots of each app's own UI with a
  household's own data, taken headless on the bench from the published template.
- The app count moved 56 -> 58 in 15 places per language set, both languages,
  and the open-source tile 49 -> 51. Checked by asking the same patterns for the
  new number afterwards. marketing/facebook/COPY.md still says 56 and is NOT
  changed: the post it carries is already scheduled.

Evidence
- documentation/audits/new-apps-2026-10-10/ — FIT.md (checklist group 0 for all
  three, with the Hungarian-UI column), the bench and box transcripts, the
  memory samples, the screenshots and the gate runs.

Register: 137 -> 139 rows, 2 opened, 0 closed.
- R-926 after a remove-keeping-backups and restore, nobody has checked what the
  app page shows for an after_install app's generated password. Measured here:
  LubeLogger is safe (its login is derived from the environment at every start,
  the new password signs in, the data is back); grocy's install password still
  signs in from the restored database but the deployed environment no longer
  carries ADMIN_PASSWORD at all. Seven apps are in the class.
- R-927 Monica, stopped at checklist 0.2 with the measurements, waiting on the
  operator.

Gate scripts: the same console trap in nineteen of them and in repo_gates.py
itself, where it ABORTED THE WHOLE RUNNER at the first gate — printing a
non-ASCII character on this workstation's cp1250 console raised
UnicodeEncodeError before the gate had decided anything, and reuse_refs_check.py
died while printing a NOTE. All now reconfigure their own streams. Red-proof
that the remaining script-test failures are not mine: test_due_checks_gate.py
fails the same 5 of 42 with the change reverted.
2026-10-10 12:34:05 +02:00

86 lines
3.8 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""script_tests_gate.py — every Python test suite under scripts/ runs on every push (R-885).
Usage: python3 scripts/script_tests_gate.py [<repo-root>]
Exit 0 every suite exited 0 · 1 a suite failed, timed out, or NO suite was found.
WHY. `repo_gates.py` ran gates, not the `test_*.py` suites beside them, so a change that broke one — the 15 tests of
the DooPlex hub-DB push/restore scripts, the 73 of the instructions gate — was caught only if someone ran it by hand.
SCOPE IS A FACT (R-421): the suites are FOUND by walking scripts/ (os.walk, every depth), never listed by hand, so a new
suite is covered the day it is added. Finding none is a FAILURE — a gate that checked nothing must not read as green.
A suite's verdict is its EXIT CODE, never its output: a suite that prints "OK" and exits 1 has failed.
NESTING. `test_repo_gates.py` runs `repo_gates.py`, which runs this gate, which would run `test_repo_gates.py` again.
The gate marks its children with FELHOM_SCRIPT_TESTS_GATE=1 and, when it finds the mark, steps aside (exit 0, said
out loud). Pinned by scripts/test_script_tests_gate.py (decoys).
"""
import os
import subprocess
import sys
import time
# A gate's own console must not decide its verdict. These scripts quote back Hungarian copy, file
# paths and arrow characters; a Windows console here is cp1250, and printing one of them raised
# UnicodeEncodeError *before the gate had decided anything* — reuse_refs_check.py died while printing
# a NOTE, which the runner then reported as a failure (2026-10-10). Same trap class as
# poster_facts_gate.py's, which was found by its own red-proof.
for _stream in (sys.stdout, sys.stderr):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
except (AttributeError, ValueError, OSError): # pragma: no cover - old Python, or a pipe
pass
MARK = "FELHOM_SCRIPT_TESTS_GATE"
TIMEOUT_S = 300
SKIP_DIRS = {"__pycache__", ".git", "node_modules"}
def find_suites(scripts_dir):
out = []
for dp, dns, fns in os.walk(scripts_dir):
dns[:] = sorted(d for d in dns if d not in SKIP_DIRS)
for f in sorted(fns):
if f.startswith("test_") and f.endswith(".py"):
out.append(os.path.join(dp, f))
return out
def main(argv):
if os.environ.get(MARK):
print("script-tests: nested run (inside a suite this gate started) — stepping aside")
return 0
root = os.path.abspath(argv[1]) if len(argv) > 1 else os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
suites = find_suites(os.path.join(root, "scripts"))
if not suites:
print("script-tests: FAILED — no test_*.py found under %s/scripts (a gate that checked nothing is not green)" % root)
return 1
env = dict(os.environ, **{MARK: "1"})
failed = []
for s in suites:
rel = os.path.relpath(s, root)
t0 = time.time()
try:
p = subprocess.run([sys.executable, "-W", "ignore", s], cwd=root, env=env,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=TIMEOUT_S)
rc, tail = p.returncode, p.stdout.decode("utf-8", "replace").strip().splitlines()[-3:]
except subprocess.TimeoutExpired:
rc, tail = "timeout", ["(killed after %d s)" % TIMEOUT_S]
verdict = "OK " if rc == 0 else "FAIL"
print(" %s %-55s %5.1fs rc=%s" % (verdict, rel, time.time() - t0, rc))
if rc != 0:
failed.append(rel)
for line in tail:
print(" | " + line[:200])
if failed:
print("script-tests: FAILED — %d of %d suite(s): %s" % (len(failed), len(suites), ", ".join(failed)))
return 1
print("script-tests: OK — %d suite(s), every one exited 0" % len(suites))
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))