fix: stop a test from needing an image that only one machine holds #56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| PYTHON_VERSION: "3.12" | |
| jobs: | |
| lint: | |
| name: Format and lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Install ruff | |
| run: pipx install ruff==0.16.3 | |
| - name: Check formatting | |
| run: ruff format --check . | |
| - name: Lint | |
| run: ruff check --output-format=github . | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| - name: Install the formatter | |
| run: pnpm install --frozen-lockfile --ignore-scripts | |
| - name: Check that every JSON file is formatted | |
| run: pnpm run format:check | |
| # The workflows are the one thing here nothing else checks, and a mistake | |
| # in them is only visible once a run has already gone wrong. The archive | |
| # is verified by digest before anything in it runs, for the same reason | |
| # every image this project loads is. | |
| - name: Check the workflows themselves | |
| run: | | |
| curl -fsSL -o actionlint.tar.gz \ | |
| "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" | |
| echo "${ACTIONLINT_SHA256} actionlint.tar.gz" | sha256sum -c - | |
| tar -xzf actionlint.tar.gz actionlint | |
| ./actionlint -color | |
| rm -f actionlint actionlint.tar.gz | |
| shell: bash | |
| env: | |
| ACTIONLINT_VERSION: "1.7.12" | |
| ACTIONLINT_SHA256: "8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8" | |
| types: | |
| name: Types | |
| runs-on: ubuntu-latest | |
| steps: | |
| # The processor is reached through a submodule, and the checker follows the | |
| # import to type it. Without the checkout it would report the whole backend | |
| # as missing rather than as wrong, which is a different and useless answer. | |
| - uses: actions/checkout@v7 | |
| with: | |
| submodules: recursive | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Install the type checker | |
| run: pipx install mypy==1.14.1 | |
| # Strict, with every optional error class the version offers turned on. The | |
| # settings live in pyproject.toml so a local run and this one agree; a gate | |
| # that only holds in CI teaches people to push and find out. | |
| - name: Every annotation has to hold | |
| run: mypy | |
| test: | |
| name: Tests on Python ${{ matrix.python }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python: ["3.12", "3.13", "3.14"] | |
| steps: | |
| # The package reaches the processor through this submodule, and without it | |
| # the microcode backend cannot be built at all. Checking it out here does | |
| # not supply any firmware, so the runner still exercises the path a user | |
| # without an image takes. | |
| - uses: actions/checkout@v7 | |
| with: | |
| submodules: recursive | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: ${{ matrix.python }} | |
| - name: Install coverage | |
| run: python -m pip install --disable-pip-version-check coverage==7.6.10 | |
| - name: Run every test file, under coverage | |
| run: | | |
| python -m coverage erase | |
| status=0 | |
| while IFS= read -r file; do | |
| echo "::group::${file}" | |
| python -m coverage run -a "${file}" || status=1 | |
| echo "::endgroup::" | |
| done < <(find snesdsp conformance -name '*.test.py' | sort) | |
| exit "${status}" | |
| shell: bash | |
| - name: Coverage must be total | |
| run: python -m coverage report | |
| firmware: | |
| name: Against the parts, when any microcode is present | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| submodules: recursive | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| # This runs the program a cartridge carries, which belongs to whoever | |
| # wrote it, so on a machine without one it says so and stops rather than | |
| # reporting a pass. Everything a machine without one can check runs in the | |
| # test job above: this package has no behaviour of its own to get wrong. | |
| # Exit 2 is the runner saying it had nothing to run, which is the honest | |
| # answer to a person who asked it to drive a part and holds no image. It is | |
| # not a failure of this build, so it is caught here rather than by making | |
| # the tool lie about it. | |
| - name: Drive the parts with the exchanges real cartridges make | |
| run: | | |
| set +e | |
| status=0 | |
| for part in dsp1 dsp2 dsp3 dsp4; do | |
| echo "::group::${part}" | |
| python conformance/against_cartridges.py "${part}" | tee -a report.txt | |
| here=${PIPESTATUS[0]} | |
| echo "::endgroup::" | |
| [ "${here}" -eq 0 ] || status="${here}" | |
| done | |
| set -e | |
| if [ "${status}" -eq 2 ]; then | |
| echo "::notice title=No microcode present::The checks that run a part's own program did not run. They are not failures; they had nothing to run." | |
| exit 0 | |
| fi | |
| exit "${status}" | |
| shell: bash | |
| # What each part answers, re-derived and compared against what it answered | |
| # when the corpus was taken. The microcode makes the values right; this | |
| # catches the layers around it changing them, which nothing else would. | |
| - name: Every part still answers what it answered | |
| run: | | |
| set +e | |
| python conformance/answers.py | |
| status=$? | |
| set -e | |
| if [ "${status}" -eq 2 ]; then | |
| echo "::notice title=No microcode present::The recorded answers were not re-derived. They are not failures; there was nothing to run." | |
| exit 0 | |
| fi | |
| exit "${status}" | |
| shell: bash | |
| # The DSP-1B corrected an arithmetic fault in the DSP-1. That is a claim | |
| # with a consequence, and this re-derives the inputs where the two masks | |
| # actually part company rather than taking the claim on trust. | |
| - name: The DSP-1B correction is still where it was | |
| run: | | |
| set +e | |
| python conformance/masks.py | |
| status=$? | |
| set -e | |
| if [ "${status}" -eq 2 ]; then | |
| echo "::notice title=Both DSP-1 masks not present::The mask comparison needs an image for the DSP-1 and the DSP-1B. It did not run." | |
| exit 0 | |
| fi | |
| exit "${status}" | |
| shell: bash | |
| # Every snippet the README prints, run against the parts it names. A | |
| # number in a document is a claim about the code, and this is what keeps | |
| # the two from drifting apart. Same exit 2 rule as above. | |
| - name: Every example in the README still gives what it says | |
| run: | | |
| set +e | |
| python conformance/documented.py | |
| status=$? | |
| set -e | |
| if [ "${status}" -eq 2 ]; then | |
| echo "::notice title=No microcode present::The README examples were not run. They are not failures; they had nothing to run." | |
| exit 0 | |
| fi | |
| exit "${status}" | |
| shell: bash |