#!/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()