From d62d16efb95ef66b56496415e88acc40ebc8e049 Mon Sep 17 00:00:00 2001 From: James Bennett Date: Wed, 25 Sep 2024 21:46:03 -0700 Subject: [PATCH] Actions, Hynek-style. --- .github/workflows/ci.yml | 191 ++++++++++++++++++++++++++++++++++----- noxfile.py | 35 +++++-- pyproject.toml | 3 + 3 files changed, 197 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index edeccf1..b508cc0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,50 +3,193 @@ name: CI on: push: - branches: [trunk] - tags: ["*"] + branches: [main] pull_request: - branches: [trunk] workflow_dispatch: env: FORCE_COLOR: "1" - PIP_DISABLE_VERSION_CHECK: "1" + PIP_DISABLE_PIP_VERSION_CHECK: "1" + PIP_NO_PYTHON_VERSION_WARNING: "1" -permissions: - contents: read +permissions: {} jobs: - tests: - name: nox on ${{ matrix.python-version }} + build-package: + name: Build and verify package runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: hynek/build-and-inspect-python-package@v2 + id: baipp + + outputs: + python-versions: ${{ steps.baipp.outputs.supported_python_classifiers_json_array }} + + tests: + name: Tests on ${{ matrix.python-version }} + needs: build-package + runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ${{ fromJson(needs.build-package.outputs.python-versions) }} steps: - - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 - with: - disable-sudo: true - egress-policy: block - allowed-endpoints: > - docs.python.org:443 - files.pythonhosted.org:443 - github.com:443 - pypi.org:443 - - uses: actions/checkout@v4 + - name: Download pre-built packages + uses: actions/download-artifact@v4 + with: + name: Packages + path: dist + - run: tar xf dist/*.tar.gz --strip-components=1 - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - name: "Install dependencies" + allow-prereleases: true + - name: Install test runner run: | python -VV python -Im site - python -Im pip install --upgrade pip setuptools wheel python -Im pip install --upgrade nox python -Im nox --version - - name: "Run CI suite with nox" - run: "python -Im nox --non-interactive --error-on-external-run --python ${{ matrix.python-version }}" + - name: Run tests + run: "python -Im nox --non-interactive --error-on-external-run --tag tests --python ${{ matrix.python-version }}" + - name: Upload coverage data + uses: actions/upload-artifact@v4 + with: + name: coverage-data-${{ matrix.python-version }} + path: .coverage.* + include-hidden-files: true + if-no-files-found: ignore + + + coverage: + name: Combine and check coverage + needs: tests + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - uses: hynek/setup-cached-uv@v2 + - run: python -Im pip install --system --upgrade coverage[toml] + - uses: actions/download-artifact@v4 + with: + pattern: coverage-data-* + merge-multiple: true + + - name: Combine coverage and fail under 100% + run: | + python -Im pip install --upgrade "coverage[toml]" + coverage combine + coverage html --skip-covered --skip-empty + # Report and write to summary. + coverage report --format=markdown >> $GITHUB_STEP_SUMMARY + # Report again and fail if under 100%. + coverage report --fail-under=100 + + - name: Upload HTML report if check failed. + uses: actions/upload-artifact@v4 + with: + name: html-report + path: htmlcov + if: ${{ failure() }} + + + docs: + name: Check documentation + needs: build-package + runs-on: ubuntu-latest + steps: + - name: Download pre-built packages + uses: actions/download-artifact@v4 + with: + name: Packages + path: dist + - run: tar xf dist/*.tar.gz --strip-components=1 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Set up test runner + - run: | + python -VV + python -Im site + python -Im pip install --upgrade nox + python -Im nox --version + - name: Run documentation checks + run: "python -Im nox --non-interactive --error-on-external-run --tag docs" + + + lint-format: + name: Lint code and check formatting + needs: build-package + runs-on: ubuntu-latest + steps: + - name: Download pre-built packages + uses: actions/download-artifact@v4 + with: + name: Packages + path: dist + - run: tar xf dist/*.tar.gz --strip-components=1 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Set up test runner + - run: | + python -VV + python -Im site + python -Im pip install --upgrade nox + python -Im nox --version + - name: Check code formatting + run: "python -Im nox --non-interactive --error-on-external-run --tag formatters --python 3.12" + - name: Lint code + run: "python -Im nox --non-interactive --error-on-external-run --tag linters --python 3.12" + + + check-package: + name: Additional package checks + needs: build-package + runs-on: ubuntu-latest + steps: + - name: Download pre-built packages + uses: actions/download-artifact@v4 + with: + name: Packages + path: dist + - run: tar xf dist/*.tar.gz --strip-components=1 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Set up test runner + - run: | + python -VV + python -Im site + python -Im pip install --upgrade nox + python -Im nox --version + - name: Check package + run: "python -Im nox --non-interactive --error-on-external-run --tag packaging --python 3.12" + + + required-checks-pass: + name: Ensure required checks pass for branch protection + if: always() + + needs: + - check-package + - coverage + - docs + - lint-format + + runs-on: ubuntu-latest + + steps: + - name: Decide whether the jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/noxfile.py b/noxfile.py index 052e9e9..388fd14 100644 --- a/noxfile.py +++ b/noxfile.py @@ -24,6 +24,8 @@ PACKAGE_NAME = "webcolors" +IS_CI = bool(os.getenv("CI", False)) + NOXFILE_PATH = pathlib.Path(__file__).parents[0] ARTIFACT_PATHS = ( NOXFILE_PATH / "src" / f"{PACKAGE_NAME}.egg-info", @@ -41,6 +43,10 @@ def clean(paths: typing.Iterable[os.PathLike] = ARTIFACT_PATHS) -> None: Clean up after a test run. """ + # This cleanup is only useful for the working directory of a local checkout; in CI + # we don't need it because CI environments are ephemeral anyway. + if IS_CI: + return [ shutil.rmtree(path) if path.is_dir() else path.unlink() for path in paths @@ -55,7 +61,7 @@ def clean(paths: typing.Iterable[os.PathLike] = ARTIFACT_PATHS) -> None: @nox.session(python=["3.8", "3.9", "3.10", "3.11", "3.12"], tags=["tests"]) def tests_with_coverage(session: nox.Session) -> None: """ - Run the package's unit tests, with coverage report. + Run the package's unit tests, with coverage instrumentation. """ session.install(".[tests]") @@ -71,14 +77,27 @@ def tests_with_coverage(session: nox.Session) -> None: "unittest", "discover", ) + clean() + + +@nox.session(python=["3.12"], tags=["tests"]) +def coverage_report(session: nox.Session) -> None: + """ + Combine coverage from the various test runs and output the report. + + """ + # In CI this job does not run because we substitute one that integrates with the CI + # system. + if IS_CI: + session.skip( + "Running in CI -- skipping nox coverage job in favor of CI coverage job" + ) + session.install("coverage[toml]") + session.run(f"python{session.python}", "-Im", "coverage", "combine") session.run( - f"python{session.python}", - "-Im", - "coverage", - "report", - "--show-missing", + f"python{session.python}", "-Im", "coverage", "report", "--show-missing" ) - clean() + session.run(f"python{session.python}", "-Im", "coverage", "erase") @nox.session(python=["3.12"], tags=["tests", "release"]) @@ -185,7 +204,7 @@ def docs_spellcheck(session: nox.Session) -> None: clean() -@nox.session(python=["3.11"], tags=["docs", "tests"]) +@nox.session(python=["3.12"], tags=["docs", "tests"]) def docs_test(session: nox.Session) -> None: """ Run the code samples in the documentation with doctest, to ensure they are diff --git a/pyproject.toml b/pyproject.toml index 6fcec77..375d95c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,9 @@ target-version = ["py38", "py39", "py310", "py311", "py312"] [tool.coverage.report] fail_under = 100 +[tool.coverage.run] +parallel = true + [tool.interrogate] ignore-init-module = true