From 2a8b0b129737d9c6db01db61c77dc8f85f48f501 Mon Sep 17 00:00:00 2001 From: nstarman Date: Sun, 22 Mar 2026 10:30:59 -0400 Subject: [PATCH] =?UTF-8?q?=E2=9C=85=20test:=20pytest=20gremlins?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 18 ++++++++++- noxfile.py | 19 ++++++++---- pyproject.toml | 11 +++---- uv.lock | 65 +++++++++++++++++++++++++++++++++++----- 4 files changed, 95 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ff7867..771b5df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,10 +89,26 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} + test_mutation: + name: Mutation Testing + runs-on: ubuntu-latest + needs: [format] + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + + - name: Install uv + uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6 + + - name: Install + run: uv sync --no-default-groups --group nox --group test --locked + + - name: Gremlins + run: uv run --frozen nox -s pytest_gremlins + status: name: CI Pass runs-on: ubuntu-latest - needs: [format, tests, test_oldest] + needs: [format, tests, test_oldest, test_mutation] if: always() steps: - name: All required jobs passed diff --git a/noxfile.py b/noxfile.py index dc17fab..5d4a3c7 100644 --- a/noxfile.py +++ b/noxfile.py @@ -22,9 +22,9 @@ @session(uv_groups=["lint"], reuse_venv=True, default=True) def lint(s: nox.Session, /) -> None: """Run the linter.""" - s.notify("precommit") - s.notify("pylint") - s.notify("mypy") + s.run("precommit") + s.run("pylint") + s.run("mypy") @session(uv_groups=["lint"], reuse_venv=True) @@ -49,18 +49,27 @@ def mypy(s: nox.Session, /) -> None: # Testing -@session(uv_groups=["test"], reuse_venv=True, default=True) +@session(python=False, default=True) def test(s: nox.Session, /) -> None: """Run the unit and regular tests.""" s.notify("pytest", posargs=s.posargs) + s.notify("pytest_gremlins", posargs=s.posargs) -@session(uv_groups=["test"], reuse_venv=True) +@session(uv_groups=["test_cov"], reuse_venv=True) def pytest(s: nox.Session, /) -> None: """Run the unit and regular tests.""" s.run("pytest", *s.posargs) +@session(uv_groups=["test_mutation"], reuse_venv=True) +def pytest_gremlins(s: nox.Session, /) -> None: + """Run pytest-gremlins (without coverage, which conflicts with gremlins).""" + # Filter out --cov from posargs since it conflicts with gremlins + filtered_args = [arg for arg in s.posargs if not arg.startswith("--cov")] + s.run("pytest", "--gremlins", "tests", *filtered_args) + + # ============================================================================= # Build diff --git a/pyproject.toml b/pyproject.toml index 757372d..1985a57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,11 +52,12 @@ dev = [ ] lint = ["mypy>=1.19.0", "pre-commit>=4.5.0", "pylint>=4.0.4"] nox = ["nox>=2024.10.9", "nox-uv>=0.6.3"] -test = [ - "attrs>=25.4.0", - "pytest>=8.4.2", - "pytest-cov>=7.0.0", - "sybil[pytest]>=9.2.0", +test = ["pytest>=9.0.0", "sybil[pytest]>=9.2.0"] +test_cov = [{ "include-group" = "test" }, "pytest-cov>=4.0.0", "attrs>=25.4.0"] +test_mutation = [ + { "include-group" = "test" }, + "pytest-gremlins>=1.5.1", # fails if `pytest-cov` is installed + "pytest-xdist>=3.8.0", ] [tool.hatch] diff --git a/uv.lock b/uv.lock index f1be8e3..3bdc023 100644 --- a/uv.lock +++ b/uv.lock @@ -280,7 +280,6 @@ build = [ { name = "build" }, ] dev = [ - { name = "attrs" }, { name = "build" }, { name = "cz-conventional-gitmoji" }, { name = "mypy" }, @@ -289,7 +288,6 @@ dev = [ { name = "pre-commit" }, { name = "pylint" }, { name = "pytest" }, - { name = "pytest-cov" }, { name = "sybil", extra = ["pytest"] }, ] lint = [ @@ -302,11 +300,21 @@ nox = [ { name = "nox-uv" }, ] test = [ + { name = "pytest" }, + { name = "sybil", extra = ["pytest"] }, +] +test-cov = [ { name = "attrs" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "sybil", extra = ["pytest"] }, ] +test-mutation = [ + { name = "pytest" }, + { name = "pytest-gremlins" }, + { name = "pytest-xdist" }, + { name = "sybil", extra = ["pytest"] }, +] [package.metadata] requires-dist = [ @@ -318,7 +326,6 @@ requires-dist = [ [package.metadata.requires-dev] build = [{ name = "build", specifier = ">=1.3.0" }] dev = [ - { name = "attrs", specifier = ">=25.4.0" }, { name = "build", specifier = ">=1.3.0" }, { name = "cz-conventional-gitmoji", specifier = ">=0.6.1" }, { name = "mypy", specifier = ">=1.19.0" }, @@ -326,8 +333,7 @@ dev = [ { name = "nox-uv", specifier = ">=0.6.3" }, { name = "pre-commit", specifier = ">=4.5.0" }, { name = "pylint", specifier = ">=4.0.4" }, - { name = "pytest", specifier = ">=8.4.2" }, - { name = "pytest-cov", specifier = ">=7.0.0" }, + { name = "pytest", specifier = ">=9.0.0" }, { name = "sybil", extras = ["pytest"], specifier = ">=9.2.0" }, ] lint = [ @@ -340,9 +346,19 @@ nox = [ { name = "nox-uv", specifier = ">=0.6.3" }, ] test = [ + { name = "pytest", specifier = ">=9.0.0" }, + { name = "sybil", extras = ["pytest"], specifier = ">=9.2.0" }, +] +test-cov = [ { name = "attrs", specifier = ">=25.4.0" }, - { name = "pytest", specifier = ">=8.4.2" }, - { name = "pytest-cov", specifier = ">=7.0.0" }, + { name = "pytest", specifier = ">=9.0.0" }, + { name = "pytest-cov", specifier = ">=4.0.0" }, + { name = "sybil", extras = ["pytest"], specifier = ">=9.2.0" }, +] +test-mutation = [ + { name = "pytest", specifier = ">=9.0.0" }, + { name = "pytest-gremlins", specifier = ">=1.5.1" }, + { name = "pytest-xdist", specifier = ">=3.8.0" }, { name = "sybil", extras = ["pytest"], specifier = ">=9.2.0" }, ] @@ -385,6 +401,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973, upload-time = "2024-10-09T18:35:44.272Z" }, ] +[[package]] +name = "execnet" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, +] + [[package]] name = "filelock" version = "3.20.3" @@ -826,6 +851,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, ] +[[package]] +name = "pytest-gremlins" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/39/9ea5990445b9423871f85115108cbee1161a754883c32780d05c7f1a1bc4/pytest_gremlins-1.5.1.tar.gz", hash = "sha256:7a936d5e720463fed7e0099ce07e0218cbbb9928451942f8181e8015d494646d", size = 3960939, upload-time = "2026-03-15T20:33:16.383Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/51/db2cddc3570a3e2a3cd244891c980599fd7a344455efc1a52d233062deae/pytest_gremlins-1.5.1-py3-none-any.whl", hash = "sha256:ae860879738a82ac6bc4a95765acb5a53c5bd615919e49f586a57c3398482266", size = 100390, upload-time = "2026-03-15T20:33:14.497Z" }, +] + +[[package]] +name = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, +] + [[package]] name = "pyyaml" version = "6.0.2"