Testing#

Per plan §8.0, all tests drive the real openswmm.engine.Solver — there are no mocks anywhere in the suite. The unit / integration / regression split is by scope, not by whether the engine is real.

Running the suite#

pip install -e ".[dev,mo,viz]"
pytest tests/unit/ -v

Integration-marked tests (@pytest.mark.integration) are full-episode runs against the real engine. They run by default because the engine is a required dependency. The OPENSWMM_RUN_INTEGRATION env var and --run-integration flag are retained as no-ops for symmetry with openswmm.mcp conventions.

Scope tiers#

Tier

Location

Purpose

Unit

tests/unit/

Per-component tests; tiny in-tree fixtures

Integration

tests/integration/

End-to-end via gymnasium.utils.env_checker.check_env

Regression

tests/regression/

Golden-trajectory JSONL replay

Fixtures#

The root tests/conftest.py provides:

  • minimal_inp — per-test copy of tests/data/minimal.inp (1 junction

    • 1 outfall + 1 conduit; 30-min sim).

  • minimal_rpt / minimal_out — sibling paths.

  • solver_adapter — factory that constructs (and tears down) a SolverAdapter.

Linting#

ruff check src/ tests/
ruff format src/ tests/

Coverage#

pytest tests/unit/ --cov=openswmm_gymnasium --cov-report=term-missing

Target: ≥90% on src/openswmm_gymnasium/ (including the _engine/ adapter; integration tests cover the live-engine surface).

Vector-env tests#

tests/integration/test_vector_env.py runs both gymnasium.vector.SyncVectorEnv (threads) and AsyncVectorEnv (processes) over the same env IDs with identical seeds and asserts the trajectories match. This directly verifies the §2.3 thread-isolation guarantee.