mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 08:41:12 +00:00
feat(stage2-phase2): structlog hot-path, pytest markers, obs package
Phase 2 deliverables not yet committed from plan execution: - structlog wired to 10 hot-path files (orchestrator, eskf, components) - bind_contextvars(correlation_id=frame_id) in process_frame - obs/logging_config.py: configure_logging(env) JSON/console renderer - pyproject.toml: structlog>=25.1, --strict-markers, 6 markers registered - tests/conftest.py: ac(id) validator plugin + pytest_collection hooks Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,96 @@ from gps_denied.core.coordinates import CoordinateTransformer
|
||||
from gps_denied.core.models import ModelManager
|
||||
from gps_denied.schemas import CameraParameters, GPSPoint
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# Phase 2 / TEST-03: AC traceability plugin
|
||||
#
|
||||
# Registers categorical markers (defensive — pyproject.toml is primary), validates
|
||||
# @pytest.mark.ac() arguments against the canonical AC-ID regex at collection time,
|
||||
# and (when --ac-dump=<path> is supplied) writes a {ac_id: [test_nodeid, ...]} JSON
|
||||
# at session end for `scripts/gen_ac_traceability.py` to consume.
|
||||
#
|
||||
# See RESEARCH.md §2.1 for the canonical implementation and rationale.
|
||||
# ---------------------------------------------------------------
|
||||
import json
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
_AC_ID_RE = re.compile(r"^AC-(?:\d+\.\d+[a-z]?|NEW-\d+)$")
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
"""Defensive marker registration. Primary registration lives in pyproject.toml,
|
||||
but doing it here too means a future maintainer who drops the pyproject markers
|
||||
list does not silently break --strict-markers."""
|
||||
for line in (
|
||||
"unit: pure-math or single-class test; no I/O",
|
||||
"integration: cross-subsystem test; in-memory SQLite / ASGI / full wiring",
|
||||
"blackbox: validates external contract without a live producer",
|
||||
"sitl: requires ARDUPILOT_SITL_HOST — nightly only",
|
||||
"e2e: full-pipeline run against a real dataset — nightly only",
|
||||
"ac(ac_id): link test to one or more Acceptance Criteria (e.g. AC-1.1, AC-NEW-3)",
|
||||
):
|
||||
config.addinivalue_line("markers", line)
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption(
|
||||
"--ac-dump",
|
||||
action="store",
|
||||
default=None,
|
||||
help="Path to write the {ac_id: [test_nodeid, ...]} JSON at session end. "
|
||||
"Consumed by scripts/gen_ac_traceability.py.",
|
||||
)
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(config, items):
|
||||
"""Validate @pytest.mark.ac(...) arguments against the canonical AC-ID regex.
|
||||
|
||||
Fail collection (rather than emit a runtime error) so AC-ID typos surface immediately.
|
||||
The traceability script's --check mode catches forward orphans (AC without test);
|
||||
this hook catches backward orphans (test references non-existent AC) by enforcing
|
||||
the syntactic AC-ID shape. Semantic existence (AC ID is declared in the AC doc) is
|
||||
enforced by the script in Plan 02-04.
|
||||
"""
|
||||
errors = []
|
||||
for item in items:
|
||||
for mark in item.iter_markers(name="ac"):
|
||||
if not mark.args:
|
||||
errors.append(
|
||||
f"{item.nodeid}: @pytest.mark.ac() requires at least one AC ID arg"
|
||||
)
|
||||
continue
|
||||
for arg in mark.args:
|
||||
if not isinstance(arg, str) or not _AC_ID_RE.match(arg):
|
||||
errors.append(
|
||||
f"{item.nodeid}: @pytest.mark.ac({arg!r}) — must match "
|
||||
f"AC-X.Y or AC-NEW-N (e.g. 'AC-1.1', 'AC-NEW-3')"
|
||||
)
|
||||
if errors:
|
||||
raise pytest.UsageError(
|
||||
"AC marker validation failed:\n " + "\n ".join(errors)
|
||||
)
|
||||
|
||||
|
||||
def pytest_sessionfinish(session, exitstatus):
|
||||
"""Dump {ac_id: [test_nodeid, ...]} to --ac-dump path if supplied.
|
||||
|
||||
Runs even when pytest is invoked with --collect-only (session.items is populated
|
||||
before tests execute), so scripts/gen_ac_traceability.py can dump in ~seconds on a
|
||||
full suite.
|
||||
"""
|
||||
dump_path = session.config.getoption("--ac-dump", default=None)
|
||||
if not dump_path:
|
||||
return
|
||||
mapping: dict[str, list[str]] = defaultdict(list)
|
||||
for item in session.items:
|
||||
for mark in item.iter_markers(name="ac"):
|
||||
for ac_id in mark.args:
|
||||
mapping[ac_id].append(item.nodeid)
|
||||
Path(dump_path).write_text(json.dumps(dict(sorted(mapping.items())), indent=2))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# Common constants
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user