The suite was reporting 331/331 while at least 82 of those tests asserted
nothing at all.
They raised during setup on APIs removed long ago -- add_layer(name=...),
GridPoint.color, mcrfpy.Animation(), entity.gridstate, mcrfpy.setScene,
assets/kenney_ice.png, GridData.compute_astar -- registered no timers, hit the
engine's auto-exit-when-no-timers path, exited 0, and were scored PASS. Their
assertions had not executed in months. test_metrics.py is the sharpest example:
the existing metrics test died on line 140 with a TypeError, which is precisely
why #341 (get_metrics counters reading 0) went unnoticed.
The engine no longer permits this (#350: a headless --exec script must call
sys.exit()), and run_tests.py no longer passes a test whose output contains a
Traceback. This commit repairs the 82 they exposed, migrating each to the
current API while preserving its original intent -- not deleting assertions to
make the command exit 0. Each repair was adversarially re-verified by a second
pass asking "is this still a test, or was it gutted?"; none were.
Two tests could not be made to pass because they were right and the engine was
wrong. Rather than paper over them they were left failing and the bugs fixed
separately in 48eef0b: DijkstraMap path order (#375) and layer-setter cache
invalidation (#376). Three integration tests had encoded the reversed Dijkstra
order as expected behavior; their assertions now state the real contract
(excludes the origin, ends at the root).
Suite: 334/334, every one of them actually asserting.
Refs #341, #350, #372
164 lines
5.2 KiB
Python
164 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Test that API documentation generator works correctly.
|
|
|
|
The hand-written docs/API_REFERENCE.md is gone; the canonical API reference is now
|
|
docs/API_REFERENCE_DYNAMIC.md, generated from the compiled module by
|
|
tools/generate_dynamic_docs.py (MCRF_* docstring macros -> introspection -> markdown).
|
|
This test verifies that generated artifact exists, is well-formed, and stays in sync
|
|
with the live mcrfpy module.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# tests/unit/test_api_docs.py -> repo root -> docs/
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
DOCS_PATH = REPO_ROOT / "docs" / "API_REFERENCE_DYNAMIC.md"
|
|
|
|
|
|
def test_api_docs_exist():
|
|
"""Test that API documentation was generated."""
|
|
if not DOCS_PATH.exists():
|
|
print(f"ERROR: API documentation not found at {DOCS_PATH}")
|
|
return False
|
|
|
|
print("+ API documentation file exists")
|
|
|
|
# Check file size
|
|
size = DOCS_PATH.stat().st_size
|
|
if size < 1000:
|
|
print(f"ERROR: API documentation seems too small ({size} bytes)")
|
|
return False
|
|
|
|
print(f"+ API documentation has reasonable size ({size} bytes)")
|
|
|
|
# Read content
|
|
with open(DOCS_PATH, 'r') as f:
|
|
content = f.read()
|
|
|
|
# Check for expected sections (current generated layout)
|
|
expected_sections = [
|
|
"# McRogueFace API Reference",
|
|
"## Table of Contents",
|
|
"## Module Attributes",
|
|
"## Functions",
|
|
"## Classes",
|
|
]
|
|
|
|
missing = [s for s in expected_sections if s not in content]
|
|
if missing:
|
|
print(f"ERROR: Missing sections: {missing}")
|
|
return False
|
|
|
|
print("+ All expected sections present")
|
|
|
|
# Check for key classes ("### ClassName" headings)
|
|
key_classes = ["Frame", "Caption", "Sprite", "Grid", "Entity", "Scene"]
|
|
missing_classes = [c for c in key_classes if f"\n### {c}\n" not in content]
|
|
if missing_classes:
|
|
print(f"ERROR: Missing classes: {missing_classes}")
|
|
return False
|
|
|
|
print("+ All key classes documented")
|
|
|
|
# Check for key module-level functions ("### `name(...)`" headings)
|
|
key_functions = ["find", "find_all", "step", "get_metrics", "set_scale", "exit"]
|
|
missing_funcs = [f for f in key_functions if f"\n### `{f}(" not in content]
|
|
if missing_funcs:
|
|
print(f"ERROR: Missing functions: {missing_funcs}")
|
|
return False
|
|
|
|
print("+ All key functions documented")
|
|
|
|
# Scene management moved from functions to the mcrfpy.current_scene attribute
|
|
if "### `mcrfpy.current_scene`" not in content:
|
|
print("ERROR: mcrfpy.current_scene not documented under Module Attributes")
|
|
return False
|
|
|
|
print("+ Module attributes documented")
|
|
|
|
# Count documentation entries
|
|
class_count = sum(1 for line in content.splitlines()
|
|
if line.startswith("### ") and not line.startswith("### `"))
|
|
func_count = sum(1 for line in content.splitlines() if line.startswith("### `"))
|
|
member_count = sum(1 for line in content.splitlines() if line.startswith("#### "))
|
|
|
|
print(f"\nDocumentation Coverage:")
|
|
print(f"- Classes: {class_count}")
|
|
print(f"- Functions/attributes: {func_count}")
|
|
print(f"- Class members: {member_count}")
|
|
|
|
if class_count == 0 or func_count == 0 or member_count == 0:
|
|
print("ERROR: documentation contains no entries")
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def test_doc_accuracy():
|
|
"""Test that documentation matches actual API."""
|
|
import mcrfpy
|
|
|
|
print("\nVerifying documentation accuracy...")
|
|
|
|
with open(DOCS_PATH, 'r') as f:
|
|
content = f.read()
|
|
|
|
passed = True
|
|
|
|
# Check that all public classes are documented
|
|
actual_classes = [name for name in dir(mcrfpy)
|
|
if isinstance(getattr(mcrfpy, name), type) and not name.startswith('_')]
|
|
|
|
undocumented = [c for c in actual_classes if f"\n### {c}\n" not in content]
|
|
if undocumented:
|
|
print(f"ERROR: Undocumented classes: {undocumented}")
|
|
passed = False
|
|
else:
|
|
print(f"+ All {len(actual_classes)} public classes are documented")
|
|
|
|
# Check functions
|
|
actual_funcs = [name for name in dir(mcrfpy)
|
|
if callable(getattr(mcrfpy, name)) and not name.startswith('_')
|
|
and not isinstance(getattr(mcrfpy, name), type)]
|
|
|
|
undoc_funcs = [f for f in actual_funcs if f"\n### `{f}(" not in content]
|
|
if undoc_funcs:
|
|
print(f"ERROR: Undocumented functions: {undoc_funcs}")
|
|
passed = False
|
|
else:
|
|
print(f"+ All {len(actual_funcs)} public functions are documented")
|
|
|
|
return passed
|
|
|
|
|
|
def main():
|
|
"""Run all API documentation tests."""
|
|
print("API Documentation Tests")
|
|
print("======================\n")
|
|
|
|
all_passed = True
|
|
|
|
# Test 1: Documentation exists and is complete
|
|
print("Test 1: Documentation Generation")
|
|
if not test_api_docs_exist():
|
|
all_passed = False
|
|
print()
|
|
|
|
# Test 2: Documentation accuracy
|
|
print("Test 2: Documentation Accuracy")
|
|
if not test_doc_accuracy():
|
|
all_passed = False
|
|
print()
|
|
|
|
if all_passed:
|
|
print("PASS: All API documentation tests passed!")
|
|
sys.exit(0)
|
|
else:
|
|
print("FAIL: Some tests failed.")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|