This document is the single source of truth for how copilot-omni is tested, how tests are organized, how to run them, and what the per-module coverage bar is.
Tests live under tests/ and are categorized by pytest markers registered in pytest.ini:
| Marker | Purpose | Default |
|---|---|---|
unit |
Single-module behavior under mocked I/O | Run by default |
integration |
Cross-module invariants (hooks + subagent + MCP) | Run by default |
e2e |
End-to-end pipeline recipes parsed from SKILL.md |
Run by default under FAKE |
tmux |
Requires tmux binary on PATH |
Skipped if unavailable |
slow |
Exceeds ~2 seconds | Run by default; -m "not slow" excludes |
integration_local |
Local-only Copilot CLI installer smoke (npm i -g @github/copilot + auth) |
Excluded from default run; opt-in with pytest -m integration_local |
# Full suite (what CI runs — integration_local auto-excluded via pytest.ini)
python3 -m pytest -q
# Local Copilot CLI installer smoke (requires npm + auth on this machine)
python3 -m pytest -m integration_local
# Skip slow + tmux tests for rapid iteration
python3 -m pytest -q -m "not slow and not tmux"
# Just integration smokes
python3 -m pytest tests/test_integration_phase_b.py -v
# One module at a time
python3 -m pytest tests/test_mcp_state_session.py tests/test_mcp_server.py -vTests that exercise pipeline recipes (autopilot/ralph/ultrawork/ultraqa/ralplan/team) do NOT invoke real copilot. They set OMNI_SUBAGENT_FAKE=1 which:
- Bypasses the real
copilot -pcall inscripts/subagent.py. - Runs a synthetic inline Python one-liner that sleeps
OMNI_SUBAGENT_FAKE_SLEEP_SECS(default 0.05 in tests) and exitsOMNI_SUBAGENT_FAKE_EXIT_CODE(default 0). - Optionally reads scripted per-agent responses from
OMNI_SUBAGENT_FAKE_RESPONSE_FILE(seetests/fixtures/ralplan-*.json). - Requires
PYTEST_CURRENT_TESTorOMNI_TEST_MODE=1to activate — production invocations refuse the hatch (ADR-0010 + WS7 T4 guard).
A real-Copilot nightly job is tracked in docs/PHASE-C-BACKLOG.md; it is NOT in the default matrix.
Per-module line coverage gates, enforced by python3 scripts/measure_coverage.py --check:
| Module | Target | Rationale |
|---|---|---|
mcp/ |
≥ 80% | MCP server is the state API backbone; schema violations → silent data loss |
hooks/ |
≥ 70% | Kill switches, audit logging, routing — must be reliable |
scripts/ |
≥ 60% | Broad surface; some CLI-heavy paths are harder to cover cleanly |
The coverage package is a dev-only dependency (requirements-dev.txt). CI installs it on the coverage job (Linux py3.11 only).
# Install dev-deps once
python3 -m pip install --user -r requirements-dev.txt
# or in CI / sandboxed system Python
python3 -m pip install --break-system-packages -r requirements-dev.txt
# Run the coverage gate
python3 scripts/measure_coverage.py --all
python3 scripts/measure_coverage.py --check # exits 1 if any module under target- Place it under
tests/test_<topic>.py. - Declare markers at the top:
pytestmark = [pytest.mark.integration]for anything cross-module. - For tests that exercise a
SKILL.mdrecipe: usetests/_pipeline_runner.py— never re-implement skill logic inside the test. - For tests that need a fresh run-dir: use a
tmp_pathfixture plus a UUID-based session id to avoid cross-test contamination. - For scripted subagent responses: check in a JSON fixture under
tests/fixtures/and point to it viaOMNI_SUBAGENT_FAKE_RESPONSE_FILE. - Integration smokes belong in
tests/test_integration_phase_b.py— keep them FAST (< 200 ms each) and cross-wave.
.github/workflows/ci.yml runs:
lint & contract validatoracross Python 3.9/3.10/3.11/3.12 — fast (~15 s per cell).unit tests— ubuntu/macOS/Windows × py3.9–3.12. Linux cells are blocking; macOS/Windows arecontinue-on-error: true(C11/Wave-3.x). Windows skips tmux tests (-m "not tmux").mcp-smoke,discovery-smoke— quick JSON-RPC and layout checks.coverage— the per-module gate (Linux py3.11 only); fails the pipeline if any module drops below its target.copilot-smoke— best-effortnpm install -g @github/copilot+ offline discovery (continue-on-error).
| Platform | Status | Notes |
|---|---|---|
| Linux (ubuntu-latest) | Blocking | Full test suite |
| macOS (macos-latest) | Best-effort | continue-on-error=true; Phase-C for blocking |
| Windows (windows-latest) | Best-effort | continue-on-error=true; tmux tests skipped; Phase-C for blocking |
Full blocking cross-OS CI is tracked in docs/PHASE-C-BACKLOG.md.
- If a test passes locally and fails once on CI: open an issue, tag
flake. Do NOT re-run until investigated. - If the root cause is a timing race (cancel cascade, pool backpressure): widen the accepted states or use
wait_for_terminalwith a real budget. Never sleep + assert. - If the root cause is a CI-resource constraint (hook latency budget): tighten the production code, not the test.
pytest.ini— marker registry and default optionsscripts/measure_coverage.py— coverage harnessrequirements-dev.txt— dev dependencies (coverage)docs/ADR/ADR-0006-mode-composition.md— cancel cascade protocol (guides e2e test shape)docs/ADR/ADR-0010-subagent-back-pressure.md— pool semantics (guides stress-test shape).omni/plans/wave-3-WS10-report.md— WS10 completion report with starting/ending coverage numbers