From 82ec40de14a4ce9b2329d06588d45e54e9bf15b9 Mon Sep 17 00:00:00 2001 From: 2253781369 <2253781369@qq.com> Date: Fri, 22 May 2026 14:50:04 +0800 Subject: [PATCH 1/2] Enable coverage reporting for test code Remove test files from the coverage omit list so that test code is included in coverage tracking. This helps detect tests that are always skipped. The CI coverage job now reports full coverage to the step summary (including test code) while still enforcing 100% coverage for production code only. Fixes #711 --- noxfile.py | 197 +++++++-------- pyproject.toml | 349 +++++++++++++-------------- src/towncrier/newsfragments/711.misc | 0 3 files changed, 273 insertions(+), 273 deletions(-) create mode 100644 src/towncrier/newsfragments/711.misc diff --git a/noxfile.py b/noxfile.py index 494085c7..0ca7582e 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,98 +1,99 @@ -from __future__ import annotations - -import os - -import nox - - -nox.options.sessions = ["pre_commit", "docs", "typecheck", "tests"] -nox.options.reuse_existing_virtualenvs = True -nox.options.error_on_external_run = True - - -@nox.session -def pre_commit(session: nox.Session) -> None: - session.install("pre-commit") - - session.run("pre-commit", "run", "--all-files", "--show-diff-on-failure") - - -# Keep list in-sync with ci.yml/test-linux & pyproject.toml -@nox.session(python=["pypy3.10", "3.9", "3.10", "3.11", "3.12", "3.13"]) -def tests(session: nox.Session) -> None: - session.env["PYTHONWARNDEFAULTENCODING"] = "1" - session.install("Twisted", "coverage[toml]") - posargs = list(session.posargs) - - try: - # Allow `--use-wheel path/to/wheel.whl` to be passed. - i = session.posargs.index("--use-wheel") - session.install(session.posargs[i + 1]) - del posargs[i : i + 2] - except ValueError: - session.install(".") - - if not posargs: - posargs = ["towncrier"] - - session.run("coverage", "run", "--module", "twisted.trial", *posargs) - - if os.environ.get("CI") != "true": - session.notify("coverage_report") - - -@nox.session -def coverage_report(session: nox.Session) -> None: - session.install("coverage[toml]") - - session.run("coverage", "combine") - session.run("coverage", "report") - - -@nox.session -def check_newsfragment(session: nox.Session) -> None: - session.install(".") - session.run("python", "-m", "towncrier.check", "--compare-with", "origin/trunk") - - -@nox.session -def draft_newsfragment(session: nox.Session) -> None: - session.install(".") - session.run("python", "-m", "towncrier.build", "--draft") - - -@nox.session -def typecheck(session: nox.Session) -> None: - # Click 8.1.4 is bad type hints -- lets not complicate packaging and only - # pin here. - session.install(".", "mypy", "click!=8.1.4") - session.run("mypy", "src") - - -@nox.session -def docs(session: nox.Session) -> None: - session.install(".[dev]") - - session.run( - # fmt: off - "python", "-m", "sphinx", - "-T", "-E", - "-W", "--keep-going", - "-b", "html", - "-d", "docs/_build/doctrees", - "-D", "language=en", - "docs", - "docs/_build/html", - # fmt: on - ) - - -@nox.session -def build(session: nox.Session) -> None: - session.install("build", "twine", "pkginfo>=1.12.0") - - # If no argument is passed, build builds an sdist and then a wheel from - # that sdist. - session.run("python", "-m", "build") - - session.run("twine", "check", "--strict", "dist/*") +from __future__ import annotations + +import os + +import nox + + +nox.options.sessions = ["pre_commit", "docs", "typecheck", "tests"] +nox.options.reuse_existing_virtualenvs = True +nox.options.error_on_external_run = True + + +@nox.session +def pre_commit(session: nox.Session) -> None: + session.install("pre-commit") + + session.run("pre-commit", "run", "--all-files", "--show-diff-on-failure") + + +# Keep list in-sync with ci.yml/test-linux & pyproject.toml +@nox.session(python=["pypy3.10", "3.9", "3.10", "3.11", "3.12", "3.13"]) +def tests(session: nox.Session) -> None: + session.env["PYTHONWARNDEFAULTENCODING"] = "1" + session.install("Twisted", "coverage[toml]") + posargs = list(session.posargs) + + try: + # Allow `--use-wheel path/to/wheel.whl` to be passed. + i = session.posargs.index("--use-wheel") + session.install(session.posargs[i + 1]) + del posargs[i : i + 2] + except ValueError: + session.install(".") + + if not posargs: + posargs = ["towncrier"] + + session.run("coverage", "run", "--module", "twisted.trial", *posargs) + + if os.environ.get("CI") != "true": + session.notify("coverage_report") + + +@nox.session +def coverage_report(session: nox.Session) -> None: + session.install("coverage[toml]") + + session.run("coverage", "combine") + session.run("coverage", "report", "--omit=src/towncrier/test/*") + session.run("coverage", "report", "--include=src/towncrier/test/*") + + +@nox.session +def check_newsfragment(session: nox.Session) -> None: + session.install(".") + session.run("python", "-m", "towncrier.check", "--compare-with", "origin/trunk") + + +@nox.session +def draft_newsfragment(session: nox.Session) -> None: + session.install(".") + session.run("python", "-m", "towncrier.build", "--draft") + + +@nox.session +def typecheck(session: nox.Session) -> None: + # Click 8.1.4 is bad type hints -- lets not complicate packaging and only + # pin here. + session.install(".", "mypy", "click!=8.1.4") + session.run("mypy", "src") + + +@nox.session +def docs(session: nox.Session) -> None: + session.install(".[dev]") + + session.run( + # fmt: off + "python", "-m", "sphinx", + "-T", "-E", + "-W", "--keep-going", + "-b", "html", + "-d", "docs/_build/doctrees", + "-D", "language=en", + "docs", + "docs/_build/html", + # fmt: on + ) + + +@nox.session +def build(session: nox.Session) -> None: + session.install("build", "twine", "pkginfo>=1.12.0") + + # If no argument is passed, build builds an sdist and then a wheel from + # that sdist. + session.run("python", "-m", "build") + + session.run("twine", "check", "--strict", "dist/*") diff --git a/pyproject.toml b/pyproject.toml index f39dd740..08c2f5f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,175 +1,174 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - - -[project] -name = "towncrier" -# For dev - 23.11.0.dev0 -# For RC - 23.11.0rc1 (release candidate starts at 1) -# For final - 23.11.0 -# make sure to follow PEP440 -version = "25.8.0.dev0" -description = "Building newsfiles for your project." -readme = "README.rst" -license = "MIT" -# Keep version list in-sync with noxfile/tests & ci.yml/test-linux. -classifiers = [ - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Operating System :: POSIX :: Linux", - "Operating System :: MacOS :: MacOS X", - "Operating System :: Microsoft :: Windows", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", -] -requires-python = ">=3.9" -dependencies = [ - "click", - "importlib-resources>=5; python_version<'3.10'", - "importlib-metadata>=4.6; python_version<'3.10'", - "jinja2", - "tomli; python_version<'3.11'", -] - -[project.optional-dependencies] -dev = [ - "packaging", - "sphinx >= 5", - "furo >= 2024.05.06", - "twisted", - "nox", -] - -[project.scripts] -towncrier = "towncrier._shell:cli" - -[project.urls] -Documentation = "https://towncrier.readthedocs.io/" -Chat = "https://web.libera.chat/?channels=%23twisted" -"Mailing list" = "https://mail.python.org/mailman3/lists/twisted.python.org/" -Issues = "https://github.com/twisted/towncrier/issues" -Repository = "https://github.com/twisted/towncrier" -Tests = "https://github.com/twisted/towncrier/actions?query=branch%3Atrunk" -Coverage = "https://codecov.io/gh/twisted/towncrier" -Distribution = "https://pypi.org/project/towncrier" - -[tool.hatch.build] -exclude = [ - "admin", - "bin", - ".github", - ".git-blame-ignore-revs", - ".pre-commit-config.yaml", - ".pre-commit-hooks.yaml", - ".readthedocs.yaml", - "src/towncrier/newsfragments", -] - - -[tool.towncrier] - package = "towncrier" - package_dir = "src" - filename = "NEWS.rst" - issue_format = "`#{issue} `_" - - [[tool.towncrier.section]] - path = "" - - [[tool.towncrier.type]] - directory = "feature" - name = "Features" - showcontent = true - - [[tool.towncrier.type]] - directory = "bugfix" - name = "Bugfixes" - showcontent = true - - [[tool.towncrier.type]] - directory = "doc" - name = "Improved Documentation" - showcontent = true - - [[tool.towncrier.type]] - directory = "removal" - name = "Deprecations and Removals" - showcontent = true - - [[tool.towncrier.type]] - directory = "misc" - name = "Misc" - showcontent = false - - -[tool.black] -exclude = ''' - -( - /( - \.eggs # exclude a few common directories in the - | \.git # root of the project - | \.nox - | \.venv - | \.env - | env - | _build - | _trial_temp.* - | build - | dist - | debian - )/ -) -''' - - -[tool.isort] -profile = "attrs" -line_length = 88 - - -[tool.ruff.isort] -# Match isort's "attrs" profile -lines-after-imports = 2 -lines-between-types = 1 - - -[tool.mypy] -strict = true -# 2022-09-04: Trial's API isn't annotated yet, which limits the usefulness of type-checking -# the unit tests. Therefore they have not been annotated yet. -exclude = '^src/towncrier/test/test_.*\.py$' - -[[tool.mypy.overrides]] -module = 'towncrier.click_default_group' -# Vendored module without type annotations. -ignore_errors = true - -[tool.coverage.run] -parallel = true -branch = true -source = ["towncrier"] - -[tool.coverage.paths] -source = ["src", ".nox/tests-*/**/site-packages"] - -[tool.coverage.report] -show_missing = true -skip_covered = false -exclude_lines = [ - "pragma: no cover", - "if TYPE_CHECKING:", - # Empty functions of a Protocol definition (used in _vcs.py) can never be - # executed. Ignoring them. - ": \\.\\.\\.$", -] -omit = [ - "src/towncrier/__main__.py", - "src/towncrier/test/*", - "src/towncrier/click_default_group.py", -] +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + + +[project] +name = "towncrier" +# For dev - 23.11.0.dev0 +# For RC - 23.11.0rc1 (release candidate starts at 1) +# For final - 23.11.0 +# make sure to follow PEP440 +version = "25.8.0.dev0" +description = "Building newsfiles for your project." +readme = "README.rst" +license = "MIT" +# Keep version list in-sync with noxfile/tests & ci.yml/test-linux. +classifiers = [ + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +requires-python = ">=3.9" +dependencies = [ + "click", + "importlib-resources>=5; python_version<'3.10'", + "importlib-metadata>=4.6; python_version<'3.10'", + "jinja2", + "tomli; python_version<'3.11'", +] + +[project.optional-dependencies] +dev = [ + "packaging", + "sphinx >= 5", + "furo >= 2024.05.06", + "twisted", + "nox", +] + +[project.scripts] +towncrier = "towncrier._shell:cli" + +[project.urls] +Documentation = "https://towncrier.readthedocs.io/" +Chat = "https://web.libera.chat/?channels=%23twisted" +"Mailing list" = "https://mail.python.org/mailman3/lists/twisted.python.org/" +Issues = "https://github.com/twisted/towncrier/issues" +Repository = "https://github.com/twisted/towncrier" +Tests = "https://github.com/twisted/towncrier/actions?query=branch%3Atrunk" +Coverage = "https://codecov.io/gh/twisted/towncrier" +Distribution = "https://pypi.org/project/towncrier" + +[tool.hatch.build] +exclude = [ + "admin", + "bin", + ".github", + ".git-blame-ignore-revs", + ".pre-commit-config.yaml", + ".pre-commit-hooks.yaml", + ".readthedocs.yaml", + "src/towncrier/newsfragments", +] + + +[tool.towncrier] + package = "towncrier" + package_dir = "src" + filename = "NEWS.rst" + issue_format = "`#{issue} `_" + + [[tool.towncrier.section]] + path = "" + + [[tool.towncrier.type]] + directory = "feature" + name = "Features" + showcontent = true + + [[tool.towncrier.type]] + directory = "bugfix" + name = "Bugfixes" + showcontent = true + + [[tool.towncrier.type]] + directory = "doc" + name = "Improved Documentation" + showcontent = true + + [[tool.towncrier.type]] + directory = "removal" + name = "Deprecations and Removals" + showcontent = true + + [[tool.towncrier.type]] + directory = "misc" + name = "Misc" + showcontent = false + + +[tool.black] +exclude = ''' + +( + /( + \.eggs # exclude a few common directories in the + | \.git # root of the project + | \.nox + | \.venv + | \.env + | env + | _build + | _trial_temp.* + | build + | dist + | debian + )/ +) +''' + + +[tool.isort] +profile = "attrs" +line_length = 88 + + +[tool.ruff.isort] +# Match isort's "attrs" profile +lines-after-imports = 2 +lines-between-types = 1 + + +[tool.mypy] +strict = true +# 2022-09-04: Trial's API isn't annotated yet, which limits the usefulness of type-checking +# the unit tests. Therefore they have not been annotated yet. +exclude = '^src/towncrier/test/test_.*\.py$' + +[[tool.mypy.overrides]] +module = 'towncrier.click_default_group' +# Vendored module without type annotations. +ignore_errors = true + +[tool.coverage.run] +parallel = true +branch = true +source = ["towncrier"] + +[tool.coverage.paths] +source = ["src", ".nox/tests-*/**/site-packages"] + +[tool.coverage.report] +show_missing = true +skip_covered = false +exclude_lines = [ + "pragma: no cover", + "if TYPE_CHECKING:", + # Empty functions of a Protocol definition (used in _vcs.py) can never be + # executed. Ignoring them. + ": \\.\\.\\.$", +] +omit = [ + "src/towncrier/__main__.py", + "src/towncrier/click_default_group.py", +] diff --git a/src/towncrier/newsfragments/711.misc b/src/towncrier/newsfragments/711.misc new file mode 100644 index 00000000..e69de29b From b1bacf81e296bbb3a260b446a2c886e679583aca Mon Sep 17 00:00:00 2001 From: 2253781369 <2253781369@qq.com> Date: Fri, 22 May 2026 14:54:23 +0800 Subject: [PATCH 2/2] Update CI coverage step to report test coverage separately --- .github/workflows/ci.yml | 668 +++++++++++++++++++-------------------- 1 file changed, 334 insertions(+), 334 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d7cebc0..18488da0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,334 +1,334 @@ -name: CI - -on: - push: - branches: [ trunk ] - tags: [ "**" ] - pull_request: - workflow_dispatch: - -defaults: - run: - shell: bash - -env: - PIP_DISABLE_PIP_VERSION_CHECK: "1" - -jobs: - build: - name: ${{ matrix.task.name}} - ${{ matrix.os.name }} ${{ matrix.python.name }} - runs-on: ${{ matrix.os.runs-on }} - strategy: - fail-fast: false - matrix: - os: - - name: Linux - runs-on: ubuntu-latest - python: - - name: CPython 3.9 - action: 3.9 - task: - - name: Build - nox: build - - steps: - - uses: actions/checkout@v3 - - - uses: hynek/build-and-inspect-python-package@efb823f52190ad02594531168b7a2d5790e66516 # v2.14.0 - - - name: Set up ${{ matrix.python.name }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python.action }} - - - name: Install dependencies - run: python -m pip install --upgrade pip nox - - - uses: twisted/python-info-action@v1 - - - run: nox -e ${{ matrix.task.nox }} - - - name: Publish - uses: actions/upload-artifact@v4 - with: - name: dist - path: dist/ - - test-linux: - name: ${{ matrix.task.name}} - Linux ${{ matrix.python.name }} - runs-on: ubuntu-latest - needs: - - build - strategy: - fail-fast: false - matrix: - # Keep list in-sync with noxfile/tests & pyproject.toml. - python: - - name: CPython 3.9 - action: 3.9 - - name: CPython 3.10 - action: '3.10' - - name: CPython 3.11 - action: '3.11' - - name: CPython 3.12 - action: '3.12' - - name: CPython 3.13 - action: '3.13' - - name: PyPy 3.10 - action: pypy3.10 - task: - - name: Test - nox: tests - - steps: - - uses: actions/checkout@v3 - - - name: Download package files - uses: actions/download-artifact@v4 - with: - name: dist - path: dist/ - - - name: Set up ${{ matrix.python.name }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python.action }} - allow-prereleases: true - cache: pip - - - name: Install dependencies - run: python -m pip install --upgrade pip nox - - - uses: twisted/python-info-action@v1 - - - run: nox --python ${{ matrix.python.action }} -e ${{ matrix.task.nox }} -- --use-wheel dist/*.whl - - - name: Upload coverage data - uses: actions/upload-artifact@v4 - with: - name: coverage-data-${{ matrix.python.action }} - path: .coverage.* - include-hidden-files: true - if-no-files-found: ignore - - - test-windows: - name: ${{ matrix.task.name}} - Windows ${{ matrix.python.name }} - runs-on: windows-latest - needs: - - build - strategy: - fail-fast: false - matrix: - python: - - name: CPython 3.9 - action: '3.9' - task: - - name: Test - nox: tests - - steps: - - uses: actions/checkout@v3 - - - name: Download package files - uses: actions/download-artifact@v4 - with: - name: dist - path: dist/ - - - name: Set up ${{ matrix.python.name }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python.action }} - - - name: Install dependencies - run: python -m pip install --upgrade pip nox - - - uses: twisted/python-info-action@v1 - - - run: nox --python ${{ matrix.python.action }} -e ${{ matrix.task.nox }} -- --use-wheel dist/*.whl - - - check: - name: ${{ matrix.task.name}} - ${{ matrix.python.name }} - runs-on: ubuntu-latest - needs: - - build - strategy: - fail-fast: false - matrix: - python: - # Use a recent version to avoid having common disconnects between - # local development setups and CI. - - name: CPython 3.12 - python-version: '3.12' - task: - - name: Check Newsfragment - run: | - nox -e check_newsfragment - nox -e draft_newsfragment >> $GITHUB_STEP_SUMMARY - run-if: ${{ github.head_ref != 'pre-commit-ci-update-config' }} - - name: Check mypy - run: nox -e typecheck - run-if: true - - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Download package files - uses: actions/download-artifact@v4 - with: - name: dist - path: dist/ - - - name: Set up ${{ matrix.python.name }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python.python-version }} - - - name: Install dependencies - run: python -m pip install --upgrade pip nox - - - uses: twisted/python-info-action@v1 - - - name: Check - run: | - ${{ matrix.task.run }} - if: ${{ matrix.task.run-if }} - - - pre-commit: - name: Check pre-commit integration - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up python - uses: actions/setup-python@v5 - with: - python-version: 3.12 - - - name: Install dependencies - run: python -m pip install pre-commit - - - name: Install pre-commit - run: | - pre-commit install - - - name: Update pre-commit - run: | - pre-commit autoupdate - - - name: Run pre-commit - run: | - pre-commit run -a - - - pypi-publish: - name: Check tag and publish - # Only trigger this for tag changes. - if: startsWith(github.ref, 'refs/tags/') - runs-on: ubuntu-latest - permissions: - # IMPORTANT: this permission is mandatory for trusted publishing - id-token: write - - needs: - - build - - test-linux - - test-windows - steps: - - uses: actions/checkout@v3 - - - name: Download package files - uses: actions/download-artifact@v4 - with: - name: dist - path: dist/ - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: 3.12 - - - name: Display structure of files to be pushed - run: ls --recursive dist/ - - - name: Ensure tag and package versions match. - run: | - python -Im pip install dist/*.whl - python -I admin/check_tag_version_match.py "${{ github.ref }}" - - - name: Publish to PyPI - on tag - uses: pypa/gh-action-pypi-publish@release/v1 - - - coverage: - name: Combine & check coverage. - needs: test-linux - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - # Use latest Python, so it understands all syntax. - python-version: 3.12 - - - run: python -Im pip install --upgrade coverage[toml] - - - uses: actions/download-artifact@v4 - with: - pattern: coverage-data-* - merge-multiple: true - path: . - - - name: Combine coverage & fail if it's <100%. - run: | - python -Im coverage combine - python -Im coverage html --skip-covered --skip-empty - - # Report and write to summary. - python -Im coverage report --format=markdown >> $GITHUB_STEP_SUMMARY - - # Report again and fail if under 100%. - python -Im coverage report --fail-under=100 - - - name: Upload HTML report if check failed. - uses: actions/upload-artifact@v4 - with: - name: html-report - path: htmlcov - include-hidden-files: true - if: ${{ failure() }} - - # This is a meta-job to simplify PR CI enforcement configuration in GitHub. - # Inside the GitHub config UI you only configure this job as required. - # All the extra requirements are defined "as code" as part of the `needs` - # list for this job. - all: - name: All success - runs-on: ubuntu-latest - # The always() part is very important. - # If not set, the job will be skipped on failing dependencies. - if: always() - needs: - # This is the list of CI job that we are interested to be green before - # a merge. - # pypi-publish is skipped since this is only executed for a tag. - - build - - test-linux - - test-windows - - coverage - - check - - pre-commit - steps: - - name: Require all successes - uses: re-actors/alls-green@3a2de129f0713010a71314c74e33c0e3ef90e696 - with: - jobs: ${{ toJSON(needs) }} +name: CI + +on: + push: + branches: [ trunk ] + tags: [ "**" ] + pull_request: + workflow_dispatch: + +defaults: + run: + shell: bash + +env: + PIP_DISABLE_PIP_VERSION_CHECK: "1" + +jobs: + build: + name: ${{ matrix.task.name}} - ${{ matrix.os.name }} ${{ matrix.python.name }} + runs-on: ${{ matrix.os.runs-on }} + strategy: + fail-fast: false + matrix: + os: + - name: Linux + runs-on: ubuntu-latest + python: + - name: CPython 3.9 + action: 3.9 + task: + - name: Build + nox: build + + steps: + - uses: actions/checkout@v3 + + - uses: hynek/build-and-inspect-python-package@efb823f52190ad02594531168b7a2d5790e66516 # v2.14.0 + + - name: Set up ${{ matrix.python.name }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python.action }} + + - name: Install dependencies + run: python -m pip install --upgrade pip nox + + - uses: twisted/python-info-action@v1 + + - run: nox -e ${{ matrix.task.nox }} + + - name: Publish + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + + test-linux: + name: ${{ matrix.task.name}} - Linux ${{ matrix.python.name }} + runs-on: ubuntu-latest + needs: + - build + strategy: + fail-fast: false + matrix: + # Keep list in-sync with noxfile/tests & pyproject.toml. + python: + - name: CPython 3.9 + action: 3.9 + - name: CPython 3.10 + action: '3.10' + - name: CPython 3.11 + action: '3.11' + - name: CPython 3.12 + action: '3.12' + - name: CPython 3.13 + action: '3.13' + - name: PyPy 3.10 + action: pypy3.10 + task: + - name: Test + nox: tests + + steps: + - uses: actions/checkout@v3 + + - name: Download package files + uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + + - name: Set up ${{ matrix.python.name }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python.action }} + allow-prereleases: true + cache: pip + + - name: Install dependencies + run: python -m pip install --upgrade pip nox + + - uses: twisted/python-info-action@v1 + + - run: nox --python ${{ matrix.python.action }} -e ${{ matrix.task.nox }} -- --use-wheel dist/*.whl + + - name: Upload coverage data + uses: actions/upload-artifact@v4 + with: + name: coverage-data-${{ matrix.python.action }} + path: .coverage.* + include-hidden-files: true + if-no-files-found: ignore + + + test-windows: + name: ${{ matrix.task.name}} - Windows ${{ matrix.python.name }} + runs-on: windows-latest + needs: + - build + strategy: + fail-fast: false + matrix: + python: + - name: CPython 3.9 + action: '3.9' + task: + - name: Test + nox: tests + + steps: + - uses: actions/checkout@v3 + + - name: Download package files + uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + + - name: Set up ${{ matrix.python.name }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python.action }} + + - name: Install dependencies + run: python -m pip install --upgrade pip nox + + - uses: twisted/python-info-action@v1 + + - run: nox --python ${{ matrix.python.action }} -e ${{ matrix.task.nox }} -- --use-wheel dist/*.whl + + + check: + name: ${{ matrix.task.name}} - ${{ matrix.python.name }} + runs-on: ubuntu-latest + needs: + - build + strategy: + fail-fast: false + matrix: + python: + # Use a recent version to avoid having common disconnects between + # local development setups and CI. + - name: CPython 3.12 + python-version: '3.12' + task: + - name: Check Newsfragment + run: | + nox -e check_newsfragment + nox -e draft_newsfragment >> $GITHUB_STEP_SUMMARY + run-if: ${{ github.head_ref != 'pre-commit-ci-update-config' }} + - name: Check mypy + run: nox -e typecheck + run-if: true + + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Download package files + uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + + - name: Set up ${{ matrix.python.name }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python.python-version }} + + - name: Install dependencies + run: python -m pip install --upgrade pip nox + + - uses: twisted/python-info-action@v1 + + - name: Check + run: | + ${{ matrix.task.run }} + if: ${{ matrix.task.run-if }} + + + pre-commit: + name: Check pre-commit integration + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up python + uses: actions/setup-python@v5 + with: + python-version: 3.12 + + - name: Install dependencies + run: python -m pip install pre-commit + + - name: Install pre-commit + run: | + pre-commit install + + - name: Update pre-commit + run: | + pre-commit autoupdate + + - name: Run pre-commit + run: | + pre-commit run -a + + + pypi-publish: + name: Check tag and publish + # Only trigger this for tag changes. + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + permissions: + # IMPORTANT: this permission is mandatory for trusted publishing + id-token: write + + needs: + - build + - test-linux + - test-windows + steps: + - uses: actions/checkout@v3 + + - name: Download package files + uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.12 + + - name: Display structure of files to be pushed + run: ls --recursive dist/ + + - name: Ensure tag and package versions match. + run: | + python -Im pip install dist/*.whl + python -I admin/check_tag_version_match.py "${{ github.ref }}" + + - name: Publish to PyPI - on tag + uses: pypa/gh-action-pypi-publish@release/v1 + + + coverage: + name: Combine & check coverage. + needs: test-linux + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + # Use latest Python, so it understands all syntax. + python-version: 3.12 + + - run: python -Im pip install --upgrade coverage[toml] + + - uses: actions/download-artifact@v4 + with: + pattern: coverage-data-* + merge-multiple: true + path: . + + - name: Combine coverage & check thresholds. + run: | + python -Im coverage combine + python -Im coverage html --skip-covered --skip-empty + + # Report full coverage (including test code) to summary. + python -Im coverage report --format=markdown >> $GITHUB_STEP_SUMMARY + + # Report production code coverage and fail if under 100%. + python -Im coverage report --omit="src/towncrier/test/*" --fail-under=100 + + - name: Upload HTML report if check failed. + uses: actions/upload-artifact@v4 + with: + name: html-report + path: htmlcov + include-hidden-files: true + if: ${{ failure() }} + + # This is a meta-job to simplify PR CI enforcement configuration in GitHub. + # Inside the GitHub config UI you only configure this job as required. + # All the extra requirements are defined "as code" as part of the `needs` + # list for this job. + all: + name: All success + runs-on: ubuntu-latest + # The always() part is very important. + # If not set, the job will be skipped on failing dependencies. + if: always() + needs: + # This is the list of CI job that we are interested to be green before + # a merge. + # pypi-publish is skipped since this is only executed for a tag. + - build + - test-linux + - test-windows + - coverage + - check + - pre-commit + steps: + - name: Require all successes + uses: re-actors/alls-green@3a2de129f0713010a71314c74e33c0e3ef90e696 + with: + jobs: ${{ toJSON(needs) }}