Files
the-nexus/tests/conftest.py

125 lines
4.3 KiB
Python
Raw Normal View History

feat: Complete Bannerlord MCP Harness implementation (Issue #722) Implements the Hermes observation/control path for local Bannerlord per GamePortal Protocol. ## New Components - nexus/bannerlord_harness.py (874 lines) - MCPClient for JSON-RPC communication with MCP servers - capture_state() → GameState with visual + Steam context - execute_action() → ActionResult for all input types - observe-decide-act loop with telemetry through Hermes WS - Bannerlord-specific actions (inventory, party, save/load) - Mock mode for testing without game running - mcp_servers/desktop_control_server.py (14KB) - 13 desktop automation tools via pyautogui - Screenshot, mouse, keyboard control - Headless environment support - mcp_servers/steam_info_server.py (18KB) - 6 Steam Web API tools - Mock mode without API key, live mode with STEAM_API_KEY - tests/test_bannerlord_harness.py (37 tests, all passing) - GameState/ActionResult validation - Mock mode action tests - ODA loop tests - GamePortal Protocol compliance tests - docs/BANNERLORD_HARNESS_PROOF.md - Architecture documentation - Proof of ODA loop execution - Telemetry flow diagrams - examples/harness_demo.py - Runnable demo showing full ODA loop ## Updates - portals.json: Bannerlord metadata per GAMEPORTAL_PROTOCOL.md - status: active, portal_type: game-world - app_id: 261550, window_title: 'Mount & Blade II: Bannerlord' - telemetry_source: hermes-harness:bannerlord ## Verification pytest tests/test_bannerlord_harness.py -v 37 passed, 2 skipped, 11 warnings Closes #722
2026-03-31 04:53:29 +00:00
"""Pytest configuration for the test suite."""
import re
feat: Complete Bannerlord MCP Harness implementation (Issue #722) Implements the Hermes observation/control path for local Bannerlord per GamePortal Protocol. ## New Components - nexus/bannerlord_harness.py (874 lines) - MCPClient for JSON-RPC communication with MCP servers - capture_state() → GameState with visual + Steam context - execute_action() → ActionResult for all input types - observe-decide-act loop with telemetry through Hermes WS - Bannerlord-specific actions (inventory, party, save/load) - Mock mode for testing without game running - mcp_servers/desktop_control_server.py (14KB) - 13 desktop automation tools via pyautogui - Screenshot, mouse, keyboard control - Headless environment support - mcp_servers/steam_info_server.py (18KB) - 6 Steam Web API tools - Mock mode without API key, live mode with STEAM_API_KEY - tests/test_bannerlord_harness.py (37 tests, all passing) - GameState/ActionResult validation - Mock mode action tests - ODA loop tests - GamePortal Protocol compliance tests - docs/BANNERLORD_HARNESS_PROOF.md - Architecture documentation - Proof of ODA loop execution - Telemetry flow diagrams - examples/harness_demo.py - Runnable demo showing full ODA loop ## Updates - portals.json: Bannerlord metadata per GAMEPORTAL_PROTOCOL.md - status: active, portal_type: game-world - app_id: 261550, window_title: 'Mount & Blade II: Bannerlord' - telemetry_source: hermes-harness:bannerlord ## Verification pytest tests/test_bannerlord_harness.py -v 37 passed, 2 skipped, 11 warnings Closes #722
2026-03-31 04:53:29 +00:00
import pytest
# Configure pytest-asyncio mode
pytest_plugins = ["pytest_asyncio"]
# Pattern that constitutes a valid issue link in a skip reason.
# Accepts: #NNN, https?://..., or JIRA-NNN style keys.
_ISSUE_LINK_RE = re.compile(
r"(#\d+|https?://\S+|[A-Z]+-\d+)",
re.IGNORECASE,
)
def _has_issue_link(reason: str) -> bool:
"""Return True if *reason* contains a recognisable issue reference."""
return bool(_ISSUE_LINK_RE.search(reason or ""))
def _skip_reason(report) -> str:
"""Extract the human-readable skip reason from a pytest report."""
longrepr = getattr(report, "longrepr", None)
if longrepr is None:
return ""
if isinstance(longrepr, tuple) and len(longrepr) >= 3:
# (filename, lineno, "Skipped: <reason>")
return str(longrepr[2])
return str(longrepr)
feat: Complete Bannerlord MCP Harness implementation (Issue #722) Implements the Hermes observation/control path for local Bannerlord per GamePortal Protocol. ## New Components - nexus/bannerlord_harness.py (874 lines) - MCPClient for JSON-RPC communication with MCP servers - capture_state() → GameState with visual + Steam context - execute_action() → ActionResult for all input types - observe-decide-act loop with telemetry through Hermes WS - Bannerlord-specific actions (inventory, party, save/load) - Mock mode for testing without game running - mcp_servers/desktop_control_server.py (14KB) - 13 desktop automation tools via pyautogui - Screenshot, mouse, keyboard control - Headless environment support - mcp_servers/steam_info_server.py (18KB) - 6 Steam Web API tools - Mock mode without API key, live mode with STEAM_API_KEY - tests/test_bannerlord_harness.py (37 tests, all passing) - GameState/ActionResult validation - Mock mode action tests - ODA loop tests - GamePortal Protocol compliance tests - docs/BANNERLORD_HARNESS_PROOF.md - Architecture documentation - Proof of ODA loop execution - Telemetry flow diagrams - examples/harness_demo.py - Runnable demo showing full ODA loop ## Updates - portals.json: Bannerlord metadata per GAMEPORTAL_PROTOCOL.md - status: active, portal_type: game-world - app_id: 261550, window_title: 'Mount & Blade II: Bannerlord' - telemetry_source: hermes-harness:bannerlord ## Verification pytest tests/test_bannerlord_harness.py -v 37 passed, 2 skipped, 11 warnings Closes #722
2026-03-31 04:53:29 +00:00
def pytest_configure(config):
"""Configure pytest."""
config.addinivalue_line(
"markers", "integration: mark test as integration test (requires MCP servers)"
)
config.addinivalue_line(
"markers",
"quarantine: mark test as quarantined (flaky/broken, tracked by issue)",
)
feat: Complete Bannerlord MCP Harness implementation (Issue #722) Implements the Hermes observation/control path for local Bannerlord per GamePortal Protocol. ## New Components - nexus/bannerlord_harness.py (874 lines) - MCPClient for JSON-RPC communication with MCP servers - capture_state() → GameState with visual + Steam context - execute_action() → ActionResult for all input types - observe-decide-act loop with telemetry through Hermes WS - Bannerlord-specific actions (inventory, party, save/load) - Mock mode for testing without game running - mcp_servers/desktop_control_server.py (14KB) - 13 desktop automation tools via pyautogui - Screenshot, mouse, keyboard control - Headless environment support - mcp_servers/steam_info_server.py (18KB) - 6 Steam Web API tools - Mock mode without API key, live mode with STEAM_API_KEY - tests/test_bannerlord_harness.py (37 tests, all passing) - GameState/ActionResult validation - Mock mode action tests - ODA loop tests - GamePortal Protocol compliance tests - docs/BANNERLORD_HARNESS_PROOF.md - Architecture documentation - Proof of ODA loop execution - Telemetry flow diagrams - examples/harness_demo.py - Runnable demo showing full ODA loop ## Updates - portals.json: Bannerlord metadata per GAMEPORTAL_PROTOCOL.md - status: active, portal_type: game-world - app_id: 261550, window_title: 'Mount & Blade II: Bannerlord' - telemetry_source: hermes-harness:bannerlord ## Verification pytest tests/test_bannerlord_harness.py -v 37 passed, 2 skipped, 11 warnings Closes #722
2026-03-31 04:53:29 +00:00
def pytest_addoption(parser):
"""Add custom command-line options."""
parser.addoption(
"--run-integration",
action="store_true",
default=False,
help="Run integration tests that require MCP servers",
)
parser.addoption(
"--no-skip-enforcement",
action="store_true",
default=False,
help="Disable poka-yoke enforcement of issue-linked skip reasons (CI escape hatch)",
)
feat: Complete Bannerlord MCP Harness implementation (Issue #722) Implements the Hermes observation/control path for local Bannerlord per GamePortal Protocol. ## New Components - nexus/bannerlord_harness.py (874 lines) - MCPClient for JSON-RPC communication with MCP servers - capture_state() → GameState with visual + Steam context - execute_action() → ActionResult for all input types - observe-decide-act loop with telemetry through Hermes WS - Bannerlord-specific actions (inventory, party, save/load) - Mock mode for testing without game running - mcp_servers/desktop_control_server.py (14KB) - 13 desktop automation tools via pyautogui - Screenshot, mouse, keyboard control - Headless environment support - mcp_servers/steam_info_server.py (18KB) - 6 Steam Web API tools - Mock mode without API key, live mode with STEAM_API_KEY - tests/test_bannerlord_harness.py (37 tests, all passing) - GameState/ActionResult validation - Mock mode action tests - ODA loop tests - GamePortal Protocol compliance tests - docs/BANNERLORD_HARNESS_PROOF.md - Architecture documentation - Proof of ODA loop execution - Telemetry flow diagrams - examples/harness_demo.py - Runnable demo showing full ODA loop ## Updates - portals.json: Bannerlord metadata per GAMEPORTAL_PROTOCOL.md - status: active, portal_type: game-world - app_id: 261550, window_title: 'Mount & Blade II: Bannerlord' - telemetry_source: hermes-harness:bannerlord ## Verification pytest tests/test_bannerlord_harness.py -v 37 passed, 2 skipped, 11 warnings Closes #722
2026-03-31 04:53:29 +00:00
def pytest_collection_modifyitems(config, items):
"""Modify test collection based on options."""
if not config.getoption("--run-integration"):
skip_integration = pytest.mark.skip(
reason="Integration tests require --run-integration and MCP servers running"
)
for item in items:
if "integration" in item.keywords:
item.add_marker(skip_integration)
# ---------------------------------------------------------------------------
# POKA-YOKE: Treat skipped tests as failures unless they carry an issue link.
# ---------------------------------------------------------------------------
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
"""Intercept skipped reports and fail them if they lack an issue link.
Exceptions:
* Tests in tests/quarantine/ explicitly quarantined, issue link required
on the quarantine marker, not the skip marker.
* Tests using environment-variable-based ``skipif`` conditions these are
legitimate CI gates (RUN_INTEGRATION_TESTS, RUN_LIVE_TESTS, etc.) where
the *condition* is the gate, not a developer opt-out. We allow these
only when the skip reason mentions a recognised env-var pattern.
* --no-skip-enforcement flag set (emergency escape hatch).
"""
outcome = yield
report = outcome.get_result()
if not report.skipped:
return
# Escape hatch for emergency use.
if item.config.getoption("--no-skip-enforcement", default=False):
return
reason = _skip_reason(report)
# Allow quarantined tests — they are tracked by their quarantine marker.
if "quarantine" in item.keywords:
return
# Allow env-var-gated skipif conditions. These come from the
# pytest_collection_modifyitems integration gate above, or from
# explicit @pytest.mark.skipif(..., reason="... requires ENV=1 ...")
_ENV_GATE_RE = re.compile(r"(require|needs|set)\s+\w+=[^\s]+", re.IGNORECASE)
if _ENV_GATE_RE.search(reason):
return
# Allow skips added by the integration gate in this very conftest.
if "require --run-integration" in reason:
return
# Anything else needs an issue link.
if not _has_issue_link(reason):
report.outcome = "failed"
report.longrepr = (
"[POKA-YOKE] Skip without issue link is not allowed.\n"
f" Reason given: {reason!r}\n"
" Fix: add an issue reference to the skip reason, e.g.:\n"
" @pytest.mark.skip(reason='Broken until #NNN is resolved')\n"
" Or quarantine the test: move it to tests/quarantine/ and\n"
" file an issue — see docs/QUARANTINE_PROCESS.md"
)