From 8c35f2fa13f2a35b625148390e93d36cd477f47f Mon Sep 17 00:00:00 2001 From: Preocts Date: Fri, 30 May 2025 00:18:53 -0400 Subject: [PATCH 1/3] Use nox.options instead of global variable Move the declaration of the backend to a `nox.options` declaration instead of passing an extra variable to each `@session`. --- noxfile.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/noxfile.py b/noxfile.py index d5ff087..96ac120 100644 --- a/noxfile.py +++ b/noxfile.py @@ -13,7 +13,6 @@ COVERAGE_FAIL_UNDER = 50 DEFAULT_PYTHON = "3.12" PYTHON_MATRIX = ["3.9", "3.10", "3.11", "3.12", "3.13"] -VENV_BACKEND = "venv" VENV_PATH = ".venv" REQUIREMENTS_PATH = "./requirements" @@ -32,8 +31,8 @@ "./**/*.pyo", ] - # Define the default sessions run when `nox` is called on the CLI +nox.options.default_venv_backend = "uv|virtualenv" nox.options.sessions = [ "version_coverage", "coverage_combine", @@ -73,7 +72,7 @@ def dev(session: nox.Session) -> None: session.log(f"\n\nRun '{activate_command}' to enter the virtual environment.\n") -@nox.session(python=PYTHON_MATRIX, venv_backend=VENV_BACKEND) +@nox.session(python=PYTHON_MATRIX) def version_coverage(session: nox.Session) -> None: """Run unit tests with coverage saved to partial file.""" print_standard_logs(session) @@ -84,7 +83,7 @@ def version_coverage(session: nox.Session) -> None: session.run("coverage", "run", "-p", "-m", "pytest", TESTS_PATH) -@nox.session(python=DEFAULT_PYTHON, venv_backend=VENV_BACKEND) +@nox.session(python=DEFAULT_PYTHON) def coverage_combine(session: nox.Session) -> None: """Combine all coverage partial files and generate JSON report.""" print_standard_logs(session) @@ -99,7 +98,7 @@ def coverage_combine(session: nox.Session) -> None: session.run("python", "-m", "coverage", "json") -@nox.session(python=DEFAULT_PYTHON, venv_backend=VENV_BACKEND) +@nox.session(python=DEFAULT_PYTHON) def mypy(session: nox.Session) -> None: """Run mypy against package and all required dependencies.""" print_standard_logs(session) @@ -110,7 +109,7 @@ def mypy(session: nox.Session) -> None: session.run("mypy", "-p", MODULE_NAME, "--no-incremental") -@nox.session(python=False, venv_backend=VENV_BACKEND) +@nox.session(python=False) def coverage(session: nox.Session) -> None: """Generate a coverage report. Does not use a venv.""" session.run("coverage", "erase") @@ -118,7 +117,7 @@ def coverage(session: nox.Session) -> None: session.run("coverage", "report", "-m") -@nox.session(python=DEFAULT_PYTHON, venv_backend=VENV_BACKEND) +@nox.session(python=DEFAULT_PYTHON) def build(session: nox.Session) -> None: """Build distribution files.""" print_standard_logs(session) @@ -127,7 +126,7 @@ def build(session: nox.Session) -> None: session.run("python", "-m", "build") -@nox.session(python=DEFAULT_PYTHON, venv_backend=VENV_BACKEND, name="update-deps") +@nox.session(python=DEFAULT_PYTHON, name="update-deps") def update_deps(session: nox.Session) -> None: """Process requirement*.txt files, updating only additions/removals.""" print_standard_logs(session) @@ -145,7 +144,7 @@ def update_deps(session: nox.Session) -> None: ) -@nox.session(python=DEFAULT_PYTHON, venv_backend=VENV_BACKEND, name="upgrade-deps") +@nox.session(python=DEFAULT_PYTHON, name="upgrade-deps") def upgrade_deps(session: nox.Session) -> None: """Process requirement*.txt files and upgrade all libraries as possible.""" print_standard_logs(session) @@ -164,7 +163,7 @@ def upgrade_deps(session: nox.Session) -> None: ) -@nox.session(python=False, venv_backend=VENV_BACKEND) +@nox.session(python=False) def clean(_: nox.Session) -> None: """Clean cache, .pyc, .pyo, and test/build artifact files from project.""" count = 0 From e5ecc467a694cfea3aa0eaf76285978e69e1b498 Mon Sep 17 00:00:00 2001 From: Preocts Date: Fri, 30 May 2025 00:49:49 -0400 Subject: [PATCH 2/3] Refactor running tests with nox Instead of having a session that handles a large matrix of python versions, use a session that leverages the default python. This reduces the overhead of maintaining the `noxfile.py`. The replacement session has an optional posarg `partial-coverage` which will produce parallel-mode coverage files. This creates drop-in replacement behavior for the GitHub action which runs the tests on its own matrix of python versions and operating systems. `coverage_combine` moves to a CI only session. --- .github/workflows/python-tests.yml | 12 ++++--- noxfile.py | 52 ++++++++++++++---------------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 890e754..a2b2569 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -15,7 +15,7 @@ on: jobs: run-tests-and-coverage: - name: "Run nox for tests and coverage" + name: "Run pytest with coverage." runs-on: "${{ matrix.os }}" strategy: fail-fast: false @@ -40,14 +40,15 @@ jobs: with: python-version: "${{ matrix.python-version }}" allow-prereleases: true + cache: "pip" - name: "Install nox" run: | - python -m pip install --upgrade pip nox + python -m pip install --upgrade nox - name: "Run tests and coverage via nox" run: | - nox --session version_coverage-${{ matrix.python-version }} + nox --session test -- partial-coverage - name: "Save coverage artifact" uses: "actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b" @@ -58,7 +59,7 @@ jobs: include-hidden-files: true coverage-compile: - name: "coverage compile" + name: "Compile coverage reports." needs: "run-tests-and-coverage" runs-on: "ubuntu-latest" steps: @@ -69,10 +70,11 @@ jobs: uses: "actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b" with: python-version: "3.12" + cache: "pip" - name: "Install nox" run: | - python -m pip install --upgrade pip nox + python -m pip install --upgrade nox - name: "Download coverage artifacts" uses: "actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16" diff --git a/noxfile.py b/noxfile.py index 96ac120..658df4c 100644 --- a/noxfile.py +++ b/noxfile.py @@ -4,6 +4,7 @@ import pathlib import shutil import sys +from functools import partial import nox @@ -12,7 +13,6 @@ TESTS_PATH = "tests" COVERAGE_FAIL_UNDER = 50 DEFAULT_PYTHON = "3.12" -PYTHON_MATRIX = ["3.9", "3.10", "3.11", "3.12", "3.13"] VENV_PATH = ".venv" REQUIREMENTS_PATH = "./requirements" @@ -32,10 +32,9 @@ ] # Define the default sessions run when `nox` is called on the CLI -nox.options.default_venv_backend = "uv|virtualenv" +nox.options.default_venv_backend = "virtualenv" nox.options.sessions = [ - "version_coverage", - "coverage_combine", + "test", "mypy", ] @@ -72,30 +71,37 @@ def dev(session: nox.Session) -> None: session.log(f"\n\nRun '{activate_command}' to enter the virtual environment.\n") -@nox.session(python=PYTHON_MATRIX) -def version_coverage(session: nox.Session) -> None: - """Run unit tests with coverage saved to partial file.""" +@nox.session(name="test") +def run_tests_with_coverage(session: nox.Session) -> None: + """Run pytest with coverage, outputs console report and json.""" print_standard_logs(session) session.install(".") - session.install("-r", "requirements/requirements.txt") - session.install("-r", "requirements/requirements-test.txt") - session.run("coverage", "run", "-p", "-m", "pytest", TESTS_PATH) + session.install("-r", f"{REQUIREMENTS_PATH}/requirements-test.txt") + coverage = partial(session.run, "python", "-m", "coverage") -@nox.session(python=DEFAULT_PYTHON) + coverage("erase") + + if "partial-coverage" in session.posargs: + coverage("run", "--parallel-mode", "--module", "pytest", TESTS_PATH) + else: + coverage("run", "--module", "pytest", TESTS_PATH) + coverage("report", "--show-missing", f"--fail-under={COVERAGE_FAIL_UNDER}") + coverage("json") + + +@nox.session() def coverage_combine(session: nox.Session) -> None: - """Combine all coverage partial files and generate JSON report.""" + """CI: Combine parallel-mode coverage files and produce reports.""" print_standard_logs(session) - fail_under = f"--fail-under={COVERAGE_FAIL_UNDER}" + session.install("-r", f"{REQUIREMENTS_PATH}/requirements-test.txt") - session.install(".") - session.install("-r", "requirements/requirements.txt") - session.install("-r", "requirements/requirements-test.txt") - session.run("python", "-m", "coverage", "combine") - session.run("python", "-m", "coverage", "report", "-m", fail_under) - session.run("python", "-m", "coverage", "json") + coverage = partial(session.run, "python", "-m", "coverage") + coverage("combine") + coverage("report", "--show-missing", f"--fail-under={COVERAGE_FAIL_UNDER}") + coverage("json") @nox.session(python=DEFAULT_PYTHON) @@ -109,14 +115,6 @@ def mypy(session: nox.Session) -> None: session.run("mypy", "-p", MODULE_NAME, "--no-incremental") -@nox.session(python=False) -def coverage(session: nox.Session) -> None: - """Generate a coverage report. Does not use a venv.""" - session.run("coverage", "erase") - session.run("coverage", "run", "-m", "pytest", TESTS_PATH) - session.run("coverage", "report", "-m") - - @nox.session(python=DEFAULT_PYTHON) def build(session: nox.Session) -> None: """Build distribution files.""" From 864de18e3f467ab5645a06916c7d503ef7dea272 Mon Sep 17 00:00:00 2001 From: Preocts Date: Fri, 30 May 2025 01:14:31 -0400 Subject: [PATCH 3/3] Remove cache: pip option This is having a negative impact to the CI. Will investigate later --- .github/workflows/python-tests.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index a2b2569..4aeed51 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -40,7 +40,6 @@ jobs: with: python-version: "${{ matrix.python-version }}" allow-prereleases: true - cache: "pip" - name: "Install nox" run: | @@ -70,7 +69,6 @@ jobs: uses: "actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b" with: python-version: "3.12" - cache: "pip" - name: "Install nox" run: |