Files

122 lines
5.5 KiB
YAML

name: Tests
on:
pull_request:
branches: [master]
push:
branches: [master]
jobs:
# Forward-looking Python lint gate (ruff). The Python twin of the ESLint runtime
# guard. Runs the curated [tool.ruff] ruleset (E9 + F + B) but only on lines this
# PR adds/modifies vs the merge-base — so it keeps NEW code clean without demanding
# a reformat of the existing tree's cosmetic backlog (#3273). Fast, fails early.
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# Need history so the gate can diff against the merge-base with master.
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install ruff
run: pip install ruff
- name: Ensure origin/master ref is available for the diff gate
run: git fetch --no-tags --depth=1 origin master || true
# Whole-tree Python syntax gate. The Python analog of the JS `node --check`
# pass. The ruff E9 gate below is diff-scoped (new/changed lines only), so a
# SyntaxError/IndentationError whose parser-reported line sits on an UNTOUCHED
# line (e.g. a dedented `try:` above an unchanged import) can slip the diff
# filter and only surface as a confusing mass-failure of every test shard at
# the conftest server-boot fixture. compileall fails in ~2s with a clear
# file:line, before the matrix suite runs. Whole-tree + unconditional so it
# cannot be bypassed by the diff scope. (#4641)
- name: Python syntax gate (whole tree — fails fast on any unparseable .py)
run: python3 -m compileall -q api server.py bootstrap.py mcp_server.py tests scripts
- name: Ruff forward gate (new/changed lines only)
run: python3 scripts/ruff_lint.py --diff origin/master
- name: Ruff whole-tree report (informational — never blocks)
if: always()
run: python3 scripts/ruff_lint.py --all
# Static-JS runtime-error guards. These catch brick-class bugs that throw only
# when the browser executes the code — node --check, source-presence tests, and
# the mocked pytest suite all miss them. Two complementary ESLint passes:
# * runtime-guard config: no-const-assign / no-import-assign (#3162 class)
# * scope_undef_gate.py: no-undef across the shared classic-<script> global
# scope, catching a function defined nested but called from a sibling
# scope (#3696 — ReferenceError: _sessionAttentionState is not defined).
- name: Set up Node for ESLint runtime guards
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install ESLint
run: npm install --no-save eslint@^10
- name: ESLint runtime-error gate (no-const-assign / no-import-assign, #3162)
run: npx eslint --no-config-lookup -c eslint.runtime-guard.config.mjs "static/**/*.js"
- name: Scope / undefined-reference gate (#3696)
run: python3 scripts/scope_undef_gate.py
test:
runs-on: ubuntu-latest
strategy:
# Don't cancel the other shards/versions when one fails — we want the full
# failure picture across the matrix in a single run.
fail-fast: false
matrix:
python-version: ['3.11', '3.12', '3.13']
# Split the suite across 5 parallel shards per Python version. pytest-shard
# partitions tests deterministically by test-id hash; the suite was made
# shard-safe (no cross-test state leakage) so every shard passes
# independently. See docs/agent-memory note on test-suite shard-safety.
# NOTE: pytest-shard is 0-indexed — shard ids must be 0..num_shards-1.
# Using 1-based ids would crash the out-of-range job AND silently skip
# shard 0's tests.
shard: [0, 1, 2, 3, 4]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: |
**/setup.cfg
**/requirements*.txt
**/pyproject.toml
- name: Install dependencies
run: |
python -m pip install --upgrade pip
# Office parsers stay optional at runtime; CI installs them explicitly for the workspace preview tests.
pip install "pyyaml>=6.0" pytest pytest-timeout pytest-asyncio pytest-shard python-docx openpyxl python-pptx
# ruff is installed so tests/test_ruff_forward_lint.py runs its E9/F821
# tree-clean assertions in-suite (mirrors how eslint is available for
# tests/test_static_js_runtime_lint.py). If install fails the test
# skips cleanly — it never blocks the matrix.
pip install ruff || echo "ruff install failed — test_ruff_forward_lint.py will skip"
# Install the `mcp` package so tests/test_mcp_server.py runs in CI.
# The package is an optional runtime dep of mcp_server.py — users
# who run the MCP integration install it themselves; CI installs
# it so test coverage exists. If mcp install fails (Python 3.13
# wheel not yet available, etc.), tests/test_mcp_server.py uses
# importorskip and the matrix stays green.
pip install mcp || echo "mcp install failed — test_mcp_server.py will importorskip"
- name: Run tests (shard ${{ matrix.shard }} of 5)
run: pytest tests/ -v --timeout=60 --shard-id=${{ matrix.shard }} --num-shards=5