molecule-core/workspace/pytest.ini
Hongming Wang 7dba700ac3 feat(preflight): replace SUPPORTED_RUNTIMES static list with adapter discovery
Closes task #123 — last piece of #87 cleanup.

Pre-fix: workspace/preflight.py:11 hardcoded a tuple of "supported"
runtime names (claude-code, codex, ollama, langgraph, etc.). Every
new template repo required a code change in molecule-runtime to be
recognized — direct violation of the universal-runtime principle
(#87) where adapters declare themselves and the runtime stays generic.

Post-fix: discovery-based validation via the same ADAPTER_MODULE env
var that production load paths already consult
(workspace/adapters/__init__.py:get_adapter). Distinguished failure
modes so operator messages are concrete:

  - ADAPTER_MODULE unset → "no adapter installed; set the env var"
  - ADAPTER_MODULE set but module won't import → import error type +
    message
  - module imports but no Adapter class → "convention violation, add
    `Adapter = YourClass`"
  - Adapter.name() raises → caught with operator message
  - Adapter.name() returns non-string → contract violation message
  - Adapter.name() doesn't match config.runtime → drift WARNING (not
    fatal; the adapter wins in production, config.yaml is just
    documentation)

The drift case is the one behavioral change worth calling out: the
prior static-list path would have hard-failed config.runtime values
not in the allowlist. With discovery, an unknown runtime in
config.yaml is just a documentation drift — the adapter that's
actually installed runs regardless. Operator gets a warning naming
both the configured and installed names so they can fix whichever
is stale.

Tests:
  - Replaces the obsolete "static list pass/fail" tests with 6 new
    cases covering each distinguished failure mode, plus a positive
    test for the adapter-matches-config happy path
  - Adds an autouse `_default_langgraph_adapter` fixture that
    pre-installs a fake adapter via sys.modules monkey-patching, so
    existing tests building default WorkspaceConfig (runtime="langgraph")
    inherit a valid adapter without each test setting ADAPTER_MODULE
  - Failure-mode tests opt out of the default fixture via
    @pytest.mark.no_default_adapter (registered in pytest.ini)
  - Sentinel pattern (`_UNSET = object()`) for `name_returns` so None
    is a passable test value (otherwise `is not None` would skip the
    None branch — exact bug the sentinel avoids)

Verification:
  - 22/22 preflight tests pass (was 16; +6 new failure-path tests)
  - 1256/1256 workspace pytest pass (was 1251; +5 net)
  - No production code path other than preflight changed

Source: 2026-04-27 #87 cleanup audit after PR #2154 (wedge extraction).
This change is independent of the cli_executor.py template moves
(task #122) — completes one of the two remaining cleanup items.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 00:44:51 -07:00

29 lines
1.3 KiB
INI

[pytest]
testpaths = tests
python_files = test_*.py
python_functions = test_*
asyncio_mode = auto
# Coverage config moved here from .github/workflows/ci.yml so local
# `pytest` matches CI without operator-typed flags. cov-fail-under
# pins the floor at 86% — 5pp below the 91.11% measured on staging
# (run 24957664272, 2026-04-26). Floor exists so a regression that
# drops coverage doesn't sneak past CI; tightening above 86% should
# follow real measurement, not aspiration. See issue #1817.
#
# Why 86 not 92: the earlier 97% measurement was without the
# .coveragerc omit list. Once `*/__init__.py`, `*/tests/*`, and
# `plugins_registry/*` are excluded (the issue's prescribed omit
# set, more meaningful since those files don't carry behavior),
# the actual measurement of behavior-bearing code is 91.11% — and
# 86% sits at the issue's prescribed `current - 5pp` margin.
addopts =
-q
--cov=.
--cov-report=term-missing
--cov-fail-under=86
markers =
no_default_adapter: opt out of preflight tests' autouse fake-adapter fixture (for tests that exercise the no-adapter / broken-adapter failure paths)
# Coverage omit / report config lives in workspace/.coveragerc — coverage.py
# only reads .coveragerc / setup.cfg / tox.ini / pyproject.toml, NOT
# pytest.ini, so [coverage:*] sections here would be silently ignored.