name: Tests on: pull_request: push: branches: [main] # Manual runs against any branch — useful when a PR's automatic run # needs a re-run or didn't get created. workflow_dispatch: # The jobs only check out the repo and run the tests. permissions: contents: read jobs: plugin-safety: name: Plugin safety harness + unit tests (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest # The two Pythons the installer supports: Raspberry Pi OS Bookworm ships # 3.11 and Trixie 3.13. strategy: fail-fast: false matrix: python-version: ["3.11", "3.13"] env: # The bundled fixture plugin gives the harness at least one real plugin # to render, and REQUIRE_PLUGINS turns "discovered zero plugins" into a # hard failure instead of a silent all-skip green run. LEDMATRIX_PLUGINS_DIR: test/fixtures/plugins LEDMATRIX_REQUIRE_PLUGINS: "1" steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: ${{ matrix.python-version }} cache: pip - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt -r web_interface/requirements.txt -r requirements-test.txt pip install RGBMatrixEmulator - name: Run plugin safety harness run: | pytest --no-cov test/plugins/ unit-tests: name: Core unit tests (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest # Bookworm's Python (3.11) and Trixie's (3.13); see plugin-safety. strategy: fail-fast: false matrix: python-version: ["3.11", "3.13"] steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: ${{ matrix.python-version }} cache: pip - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt -r web_interface/requirements.txt -r requirements-test.txt pip install RGBMatrixEmulator # Run the ENTIRE test tree (except test/plugins, which the # plugin-safety job owns). New test files are enrolled automatically; # excluding anything requires a visible, commented --ignore here. # Coverage is measured and enforced only in this step — pytest.ini # deliberately carries no coverage flags so local runs stay fast. - name: Run core unit suites run: | pytest -m "not hardware" test/ \ --ignore=test/plugins \ --cov=src --cov=web_interface \ --cov-report=term \ --cov-fail-under=52 js-tests: name: Web UI JS tests runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: "3.13" cache: pip - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: "22" - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt -r web_interface/requirements.txt npm install --no-audit --no-fund --prefix test/js # The DOM suites test the real server-rendered pages and API, so they # need the web interface running. REQUIRE_DOM turns "couldn't reach it" # into a failure instead of a silent skip. - name: Start the web interface run: | EMULATOR=true python -c "from web_interface.app import app; app.run(host='127.0.0.1', port=5000, threaded=True)" > web.log 2>&1 & for i in $(seq 60); do curl -sf -o /dev/null http://127.0.0.1:5000/ && exit 0; sleep 1; done cat web.log exit 1 - name: Run JS suites env: BASE: http://127.0.0.1:5000 REQUIRE_DOM: "1" run: node test/js/run_all.js css-build: name: Tailwind CSS is up to date runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: "3.13" # Downloads the pinned standalone Tailwind CLI (SHA-256 checked; no # Node), rebuilds static/v3/tailwind.css and plugin-frame.css from the # templates and JS, and fails if the committed files differ. Fix a # failure by running `python3 scripts/build_css.py` and committing. - name: Check the committed CSS matches a fresh build run: python scripts/build_css.py --check type-check: name: Type check (mypy ratchet) runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: "3.13" cache: pip # The runtime requirements are installed so mypy sees the real types of # PIL, requests, psutil and friends -- missing, they'd be Any and the # result would differ from a developer's machine. mypy and the stubs are # pinned so a new release can't turn this red without a code change. - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt -r web_interface/requirements.txt pip install mypy==1.20.2 types-requests==2.33.0.20260906 types-pytz==2026.4.0.20260926 # mypy on exactly the modules in mypy-clean.txt; fails on any error in # them, or if a listed file is missing. See CONTRIBUTING.md. - name: Run mypy on the ratchet list run: python scripts/check_types.py sports-drift-report: name: Sports drift report (report only) runs-on: ubuntu-latest # A progress measure for docs/SPORTS_UNIFICATION.md, never a gate: the # monorepo's own check_sports_drift.py is the gate. The step summary shows # how many bodies each scoreboard method family still has. continue-on-error: true steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: Check out ledmatrix-plugins (main) uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: ChuckBuilds/ledmatrix-plugins path: ledmatrix-plugins persist-credentials: false - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: "3.13" # Stdlib only; exits 0 whatever it finds. - name: Report method-family drift across the nine scoreboards run: | python scripts/sports_drift_report.py --plugins ledmatrix-plugins \ --markdown --json sports-drift.json >> "$GITHUB_STEP_SUMMARY" python scripts/sports_drift_report.py --plugins ledmatrix-plugins - name: Upload the full report uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: sports-drift-report path: sports-drift.json