CanLex / tests /test_docs_drift.py
Beemer
Deploy: the 2026-09-21 code audit and its three verification rounds -- the quantity and relief allowlists, the five Canadian outcome readings, the IRPR s. 17 clock, the retrieval fixes and the performance work (approved_code 82525e1; curated baseline c32bd27 unchanged)
901bf8c
Raw History Blame Contribute Delete
7.68 kB
"""Documentation that must follow the code (the code audit of 2026-09-21,
follow-up of 2026-09-22: 'Remaining doc drift'). Offline; reads the source
tree and RUNBOOK.md.
- RUNBOOK's 'Environment variables' table lists every variable the code
reads (os.environ / env.get / getenv in canlex/ and tools/) and every one
the Dockerfile sets for CanLex or the Hugging Face library; it names no
CANLEX_* variable nothing reads or sets. (28 were undocumented, among them
CANLEX_HTTP and CANLEX_SEE_ALSO_DRAFT.)
- The refresh choreography names every ingester (irb_guidelines and conduct
were missing) and says every ingester takes --allow-shrink.
- The call-log section names every calllog.RESULT_KEYS field (it named five
of twelve).
- No RUNBOOK line cites a scratchpad script that is not in the repo (three
cited the a14/a15 before/after harness).
- canlex_list_acts' 'Bilingual' line counts French LEGISLATION only (it
counted 124 instruments -- the 80 Acts and regulations plus 44 French
guidance documents -- and 26,829 'articles' for 20,680).
"""
import re
import unittest
from pathlib import Path
from canlex import calllog, server
from canlex.index import LegislationIndex
ROOT = Path(__file__).resolve().parent.parent
RUNBOOK = (ROOT / "RUNBOOK.md").read_text(encoding="utf-8")
# os.environ.get("X"), os.environ["X"], env.get("X") (calllog.configure's
# env = os.environ), env["X"] (sweep.py), os.getenv("X"), and a module
# constant naming the variable a read uses, ENV_X = "CANLEX_X"
# (canlex/ratelimit.py reads its two variables through ENV_PER_MIN / ENV_BURST)
_ENV_READ = re.compile(
r"""\b(?:environ|env)(?:\.get\(|\[)\s*["']([A-Z][A-Z0-9_]+)["']"""
r"""|\bgetenv\(\s*["']([A-Z][A-Z0-9_]+)["']"""
r"""|^ENV_[A-Z0-9_]+\s*=\s*["']((?:CANLEX|HF|CANLII)_[A-Z0-9_]+)["']""", re.M)
_DOCKER_ENV = re.compile(r"^\s*(?:ENV\s+)?((?:CANLEX|HF|CANLII)_[A-Z0-9_]+|PORT)=", re.M)
def env_vars_read():
"""{name: [files]} for every environment variable the code reads."""
found = {}
for top in ("canlex", "tools"):
for path in sorted((ROOT / top).rglob("*.py")):
for m in _ENV_READ.finditer(path.read_text(encoding="utf-8")):
name = m.group(1) or m.group(2) or m.group(3)
found.setdefault(name, []).append(path.relative_to(ROOT).as_posix())
return found
def section(heading, level="##"):
"""The RUNBOOK text under `heading` up to the next heading of its level or above."""
start = RUNBOOK.index(heading)
stops = [m.start() for m in re.finditer(r"^#{1,%d} " % len(level), RUNBOOK, re.M)
if m.start() > start]
return RUNBOOK[start:stops[0] if stops else len(RUNBOOK)]
def table_names():
body = section("### Environment variables", "###")
return set(re.findall(r"^\| `([A-Z][A-Z0-9_]+)` \|", body, re.M))
class EnvironmentTableTests(unittest.TestCase):
def test_the_grep_finds_the_known_readers(self):
found = env_vars_read()
for name in ("CANLEX_HTTP", "CANLEX_SEE_ALSO_DRAFT", "CANLEX_LOG_DIR",
"CANLII_API_KEY", "PORT", "CANLEX_MCP_URL"):
self.assertIn(name, found)
self.assertGreaterEqual(len(found), 35)
def test_every_variable_the_code_reads_is_in_the_table(self):
missing = {n: f for n, f in env_vars_read().items() if n not in table_names()}
self.assertEqual(missing, {}, "add these to RUNBOOK 'Environment variables'")
def test_every_variable_the_dockerfile_sets_is_in_the_table(self):
docker = set(_DOCKER_ENV.findall((ROOT / "Dockerfile").read_text(encoding="utf-8")))
self.assertTrue({"HF_HOME", "HF_HUB_OFFLINE", "CANLEX_HTTP"} <= docker)
self.assertEqual(docker - table_names(), set())
def test_the_table_names_nothing_stale(self):
docker = set(_DOCKER_ENV.findall((ROOT / "Dockerfile").read_text(encoding="utf-8")))
stale = {n for n in table_names() if n.startswith("CANLEX_")
and n not in env_vars_read() and n not in docker}
self.assertEqual(stale, set())
def test_the_two_local_only_variables_say_so(self):
body = section("### Environment variables", "###")
http = next(l for l in body.splitlines() if l.startswith("| `CANLEX_HTTP`"))
draft = next(l for l in body.splitlines() if l.startswith("| `CANLEX_SEE_ALSO_DRAFT`"))
self.assertIn("Local use only", http)
self.assertIn("never set it on the Space", draft)
class ChoreographyTests(unittest.TestCase):
def ingesters(self):
"""Every canlex module whose command line takes --allow-shrink."""
return sorted(p.stem for p in (ROOT / "canlex").glob("*.py")
if '"--allow-shrink" in sys.argv' in p.read_text(encoding="utf-8"))
def test_every_ingester_is_in_the_choreography(self):
block = section("## The refresh choreography")
self.assertIn("irb_guidelines", self.ingesters())
missing = [m for m in self.ingesters() if f"py -m canlex.{m}" not in block]
self.assertEqual(missing, [])
def test_allow_shrink_is_documented_there(self):
self.assertIn("--allow-shrink", section("## The refresh choreography"))
class CallLogFieldTests(unittest.TestCase):
def test_every_result_key_is_named(self):
body = section("## Call log (2026-09-16)")
missing = [k for k in calllog.RESULT_KEYS if f"`{k}`" not in body]
self.assertEqual(missing, [])
def test_the_call_log_note_is_current(self):
# the 2026-09-21 audit's 'the live Space predates the call log' is gone
self.assertNotIn("live Space predates the call log", RUNBOOK)
self.assertIn("the live Space carries the call log", RUNBOOK)
class ScratchpadCitationTests(unittest.TestCase):
def test_no_line_cites_a_scratchpad_script(self):
cited = re.findall(r"scratchpad[^\n]{0,40}?\b\w+\.py", RUNBOOK)
self.assertEqual(cited, [])
def _chunk(cid, act_code, lang="en", doc_type="legislation", **extra):
c = {"id": cid, "doc_type": doc_type, "act_code": act_code, "act_short": act_code,
"act_name": act_code, "section": "1", "marginal_note": "t", "part": "",
"division": "", "heading": "", "text": "x", "history": "", "last_amended": "",
"in_force": "", "status": "in force", "current_to": "2026-03-31",
"citation": f"{act_code}, s. 1", "source_url": "https://example.test"}
if lang != "en":
c["lang"] = lang
c.update(extra)
return c
class ListActsBilingualLineTests(unittest.TestCase):
"""canlex_list_acts' coverage line (server.py; HELD: canlex_list_acts is
REVIEWED). Before: every French chunk counted, guidance included."""
def setUp(self):
idx = LegislationIndex.__new__(LegislationIndex)
idx.chunks = [
_chunk("I-2.5-s1", "I-2.5"), _chunk("I-2.5-fr-s1", "I-2.5", "fr"),
_chunk("I-2.5-fr-s2", "I-2.5", "fr"),
_chunk("C-46-s1", "C-46"), _chunk("C-46-fr-s1", "C-46", "fr"),
_chunk("dmemo-fr-1", "D-Memo", "fr", "memorandum", part="Mémorandum"),
_chunk("enf-fr-5-1", "ENF-5", "fr", "memorandum", part="ENF 5"),
_chunk("directive-fr-1", "d1", "fr", "directive"),
]
idx.related = lambda c: {}
server._INDEX = idx
self.addCleanup(setattr, server, "_INDEX", None)
def test_only_french_legislation_is_counted(self):
out = server.canlex_list_acts()
self.assertIn("_Bilingual: 2 instruments also carry their parallel French "
"text (3 articles)", out)
self.assertNotIn("5 instruments", out) # the count before the fix