burn-down round 2: R-593 papra field copy, R-760 vikunja healthcheck reason, R-594 English allow-list, R-605 refusal exit 3, R-781 onboarding decoy clone, R-806 scheme; stale runner test fixed
gates / gates (push) Successful in 4s
gates / gates (push) Successful in 4s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
"""R-760: every catalog service that has NO compose `healthcheck:` says why, in a comment inside its own block.
|
||||
|
||||
A missing healthcheck is sometimes right (the image has no shell; the image brings its own — adventurelog-frontend,
|
||||
R-655), but a silent one cannot be told apart from a forgotten one: vikunja carried none for months with nothing
|
||||
saying whether that was a choice. This test reads the committed composes as TEXT (catalog CI has no PyYAML) and
|
||||
fails for a service block with neither a `healthcheck:` key nor a `# No healthcheck` comment.
|
||||
|
||||
COMPANION RED-PROOF: with the R-760 comment removed from templates/vikunja/docker-compose.yml this test fails and
|
||||
names `vikunja/vikunja`."""
|
||||
import glob
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import unittest
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
EXPLAINED = re.compile(r"^\s*#\s*No healthcheck", re.I)
|
||||
|
||||
|
||||
def service_blocks(text):
|
||||
"""Yield (service name, [lines]) for each service under the top-level `services:` key."""
|
||||
in_services = False
|
||||
name, lines = None, []
|
||||
for line in text.splitlines():
|
||||
if re.match(r"^\S", line): # a top-level key (or a top-level comment) ends the services block
|
||||
if name:
|
||||
yield name, lines
|
||||
name, lines = None, []
|
||||
in_services = line.startswith("services:")
|
||||
continue
|
||||
if not in_services:
|
||||
continue
|
||||
m = re.match(r"^ ([A-Za-z0-9_.-]+):\s*$", line)
|
||||
if m:
|
||||
if name:
|
||||
yield name, lines
|
||||
name, lines = m.group(1), []
|
||||
elif name:
|
||||
lines.append(line)
|
||||
if name:
|
||||
yield name, lines
|
||||
|
||||
|
||||
def unexplained(text):
|
||||
out = []
|
||||
for svc, lines in service_blocks(text):
|
||||
if any(re.match(r"^ healthcheck:", l) for l in lines):
|
||||
continue
|
||||
if any(EXPLAINED.match(l) for l in lines):
|
||||
continue
|
||||
out.append(svc)
|
||||
return out
|
||||
|
||||
|
||||
class HealthcheckExplained(unittest.TestCase):
|
||||
def test_parser_sees_a_missing_and_an_explained_service(self):
|
||||
# decoys for the parser itself: a silent service convicts, an explained one and a checked one pass
|
||||
text = ("services:\n a:\n image: x:1\n b:\n image: y:1\n # No healthcheck: no shell\n"
|
||||
" c:\n image: z:1\n healthcheck:\n test: [\"CMD\", \"true\"]\nvolumes:\n d:\n")
|
||||
self.assertEqual(unexplained(text), ["a"])
|
||||
|
||||
def test_every_catalog_service_has_a_healthcheck_or_says_why(self):
|
||||
paths = sorted(glob.glob(os.path.join(ROOT, "templates", "*", "docker-compose.yml")))
|
||||
self.assertGreater(len(paths), 40, "the template glob found too few composes — the test would pass empty")
|
||||
bad = []
|
||||
for p in paths:
|
||||
app = os.path.basename(os.path.dirname(p))
|
||||
with io.open(p, encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
for svc in unexplained(text):
|
||||
bad.append("%s/%s" % (app, svc))
|
||||
self.assertEqual(bad, [], "service(s) with no compose healthcheck and no '# No healthcheck' comment "
|
||||
"saying why: %s" % bad)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user