Adapts the code-review procedure, folder layout, template, and tooling from the sibling mxaccessgw repo to lmxopcua. - REVIEW-PROCESS.md: per-module review workflow — a module is one src/ or tests/ project (ZB.MOM.WW.OtOpcUa. prefix stripped); 10-category checklist; finding IDs/severities/statuses; re-review rules. - code-reviews/_template/findings.md: per-module findings template. - code-reviews/regen-readme.py: generates the cross-module README.md index from the per-module findings.md files; --check gates staleness and consistency. - code-reviews/test_regen_readme.py: dependency-free generator tests. - code-reviews/prompt.md: orchestration prompt for clearing the backlog. - code-reviews/README.md: generated index (no modules reviewed yet). - scripts/check-code-reviews-readme.ps1: CI / pre-commit check wrapper. Adapted to this repo: ZB.MOM.WW.OtOpcUa module naming, OtOpcUa conventions checklist (in-process GalaxyDriver + mxaccessgw, contained-name vs tag-name, ACL at DriverNodeManager), single .NET solution build/test commands, and the lmxopcua design docs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
166 lines
4.6 KiB
Python
166 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Tests for regen-readme.py.
|
|
|
|
Dependency-free: run with `python code-reviews/test_regen_readme.py`.
|
|
Exits 0 if all tests pass, 1 otherwise.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import tempfile
|
|
import traceback
|
|
from pathlib import Path
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
|
|
# regen-readme.py is not an importable module name (hyphen), so load it by path.
|
|
_spec = importlib.util.spec_from_file_location("regen_readme", HERE / "regen-readme.py")
|
|
regen = importlib.util.module_from_spec(_spec)
|
|
_spec.loader.exec_module(regen)
|
|
|
|
FIXTURE = """# Code Review — Demo
|
|
|
|
| Field | Value |
|
|
|---|---|
|
|
| Module | `src/Demo` |
|
|
| Reviewer | Tester |
|
|
| Review date | 2026-05-18 |
|
|
| Commit reviewed | `abc1234` |
|
|
| Status | Reviewed |
|
|
| Open findings | 1 |
|
|
|
|
## Findings
|
|
|
|
### Demo-001
|
|
|
|
| Field | Value |
|
|
|---|---|
|
|
| Severity | High |
|
|
| Category | Security |
|
|
| Location | `src/Demo/File.cs:10` |
|
|
| Status | Open |
|
|
|
|
**Description:** A first problem that matters.
|
|
|
|
**Recommendation:** Fix it.
|
|
|
|
**Resolution:** _(open)_
|
|
|
|
### Demo-002
|
|
|
|
| Field | Value |
|
|
|---|---|
|
|
| Severity | Low |
|
|
| Category | Documentation & comments |
|
|
| Location | `src/Demo/File.cs:20` |
|
|
| Status | Resolved |
|
|
|
|
**Description:** A second, minor problem.
|
|
|
|
**Recommendation:** Tidy it.
|
|
|
|
**Resolution:** Fixed in def5678 on 2026-05-18.
|
|
"""
|
|
|
|
|
|
def _parse_fixture() -> dict:
|
|
"""Write FIXTURE to a temp Demo/findings.md and parse it."""
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "Demo" / "findings.md"
|
|
path.parent.mkdir()
|
|
path.write_text(FIXTURE, encoding="utf-8")
|
|
return regen.parse_module(path)
|
|
|
|
|
|
def test_first_table_skips_separator_and_field_header():
|
|
table = regen.first_table("| Field | Value |\n|---|---|\n| Severity | High |\n")
|
|
assert table == {"Severity": "High"}, table
|
|
|
|
|
|
def test_parse_module_header():
|
|
m = _parse_fixture()
|
|
assert m["module"] == "Demo", m["module"]
|
|
assert m["header"]["Reviewer"] == "Tester"
|
|
assert m["header"]["Status"] == "Reviewed"
|
|
assert m["header"]["Open findings"] == "1"
|
|
|
|
|
|
def test_parse_module_findings():
|
|
m = _parse_fixture()
|
|
assert len(m["findings"]) == 2, len(m["findings"])
|
|
first = m["findings"][0]
|
|
assert first["id"] == "Demo-001"
|
|
assert first["severity"] == "High"
|
|
assert first["category"] == "Security"
|
|
assert first["location"] == "`src/Demo/File.cs:10`"
|
|
assert first["status"] == "Open"
|
|
assert first["description"] == "A first problem that matters."
|
|
assert m["findings"][1]["status"] == "Resolved"
|
|
|
|
|
|
def test_build_readme_splits_pending_and_closed():
|
|
readme = regen.build_readme([_parse_fixture()])
|
|
assert "## Pending findings" in readme
|
|
assert "## Closed findings" in readme
|
|
pending, closed = readme.split("## Closed findings", 1)
|
|
assert "Demo-001" in pending # Open -> pending
|
|
assert "Demo-001" not in closed
|
|
assert "Demo-002" in closed # Resolved -> closed
|
|
assert "_No pending findings._" not in pending
|
|
|
|
|
|
def test_build_readme_handles_no_modules():
|
|
readme = regen.build_readme([])
|
|
assert "no modules reviewed yet" in readme
|
|
assert "_No pending findings._" in readme
|
|
assert "_No closed findings._" in readme
|
|
|
|
|
|
def test_find_inconsistencies_clean_fixture():
|
|
assert regen.find_inconsistencies([_parse_fixture()]) == []
|
|
|
|
|
|
def test_find_inconsistencies_detects_wrong_open_count():
|
|
m = _parse_fixture()
|
|
m["header"]["Open findings"] = "7"
|
|
issues = regen.find_inconsistencies([m])
|
|
assert len(issues) == 1 and "Open findings" in issues[0], issues
|
|
|
|
|
|
def test_find_inconsistencies_detects_unknown_status():
|
|
m = _parse_fixture()
|
|
m["findings"][0]["status"] = "Bogus"
|
|
issues = regen.find_inconsistencies([m])
|
|
# Wrong status also shifts the open count, so expect the status issue present.
|
|
assert any("unrecognised Status" in i for i in issues), issues
|
|
|
|
|
|
def test_summarize_truncates_long_text():
|
|
long = "x" * 500
|
|
out = regen.summarize(long)
|
|
assert len(out) <= 240 and out.endswith("…"), len(out)
|
|
assert regen.summarize("short") == "short"
|
|
|
|
|
|
def main() -> int:
|
|
tests = sorted(
|
|
(name, fn)
|
|
for name, fn in globals().items()
|
|
if name.startswith("test_") and callable(fn)
|
|
)
|
|
failed = 0
|
|
for name, fn in tests:
|
|
try:
|
|
fn()
|
|
print(f"PASS {name}")
|
|
except Exception: # noqa: BLE001 - test runner reports all failures
|
|
failed += 1
|
|
print(f"FAIL {name}")
|
|
traceback.print_exc()
|
|
print(f"\n{len(tests) - failed}/{len(tests)} passed.")
|
|
return 1 if failed else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|