diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 41afacbd..c461bca6 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -21,10 +21,10 @@ jobs: python-version: "3.11" # minimum supported lang version - name: Install dependencies - run: python -m pip install tox + run: python -m pip install hatch - name: Run - run: tox -e linter + run: hatch run lint:check mypy: name: mypy @@ -42,10 +42,10 @@ jobs: python-version: "3.11" # minimum supported lang version - name: Install dependencies - run: python -m pip install tox + run: python -m pip install hatch - name: Check MyPy - run: tox -e mypy + run: hatch run mypy:check pkglint: name: pkglint @@ -63,10 +63,10 @@ jobs: python-version: "3.11" # minimum supported lang version - name: Install dependencies - run: python -m pip install tox + run: python -m pip install hatch - name: Run - run: tox -e pkglint + run: hatch run pkglint:check markdownlint: # https://github.com/marketplace/actions/markdown-lint @@ -100,10 +100,10 @@ jobs: python-version: "3.11" # minimum supported lang version - name: Install dependencies - run: python -m pip install tox + run: python -m pip install hatch - name: Run - run: tox -e docs + run: hatch run docs:build super-linter: name: super-linter diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 069fc8c2..2d87c299 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -47,14 +47,10 @@ jobs: run: unshare -rn echo "unshare works" - name: Install dependencies - run: python -m pip install tox + run: python -m pip install hatch - name: Run tests - run: tox -e py - - - name: Run tests for pyo3_test - working-directory: ./e2e/pyo3_test/ - run: tox -e py + run: hatch run test:test - name: Upload coverage uses: actions/upload-artifact@v4 @@ -143,7 +139,7 @@ jobs: run: unshare -rn echo "unshare works" - name: Install dependencies - run: python -m pip install tox + run: python -m pip install hatch - name: Run tests run: ./e2e/test_${{ matrix.test-script }}.sh @@ -183,12 +179,9 @@ jobs: cache: pip cache-dependency-path: | **/pyproject.toml - **/tox.ini - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install coverage[toml] + run: python -m pip install hatch - name: Download coverage data uses: actions/download-artifact@v4 @@ -198,10 +191,10 @@ jobs: - name: Coverage report run: | - coverage combine - coverage html - coverage report --format=markdown >> $GITHUB_STEP_SUMMARY - coverage report --fail-under=60 + hatch run coverage-report:coverage combine + hatch run coverage-report:coverage html + hatch run coverage-report:coverage report --format=markdown >> $GITHUB_STEP_SUMMARY + hatch run coverage-report:coverage report --fail-under=60 - name: Upload report uses: actions/upload-artifact@v4 diff --git a/.gitignore b/.gitignore index 4b36b777..36a502ff 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,8 @@ __pycache__/ /artifacts /build-logs/ /.pypirc -/.tox/ +.pytest_cache/ +/.hatch/ /node_modules/ /package-lock.json /package.json @@ -29,3 +30,5 @@ __pycache__/ /overrides/ /docs/_build/ /docs/example/bootstrap-output + +/.mypy_cache/ diff --git a/.mergify.yml b/.mergify.yml index 303c6254..f06bd6b5 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -2,10 +2,10 @@ pull_request_rules: - name: Add CI label conditions: - or: - - "title~=^tox:" + - "title~=^hatch:" - "title~=^ci:" - "title~=^e2e:" - - files~=tox.ini + - files~=pyproject.toml - files~=.github/ - files~=.markdownlint-config.yaml - files~=tests/ diff --git a/docs/develop.md b/docs/develop.md index caff9d27..e60d038c 100644 --- a/docs/develop.md +++ b/docs/develop.md @@ -2,12 +2,13 @@ ## Unit tests and linter -The unit tests and linter rely on [tox](https://tox.wiki/) and a -recent version of Python 3 (at least 3.9). +The unit tests and linter now use [Hatch](https://hatch.pypa.io/) and a +recent version of Python (as defined in `pyproject.toml` under `project.requires-python`). -Run `tox` with no arguments to run all of the tests. Use one of the -specific target environments for running fewer or different -tests. Refer to `tox.ini` for the list of possible targets. +To run all default checks and tests (linters, unit tests, etc.), you can typically run `hatch run`. +To execute specific environments or scripts defined in `pyproject.toml`, use the command `hatch run :`. +For instance, linters might be run with `hatch run lint:check`, and unit tests with `hatch run test:test`. +Consult the `[tool.hatch.envs]` and `[tool.hatch.matrix]` sections in `pyproject.toml` for a comprehensive list of available environments, scripts, and their configurations. ## End-to-end tests @@ -28,3 +29,5 @@ writing to INFO level to show what is starting and stopping, respectively. Detailed messages should be logged to DEBUG level so they will not appear on the console by default. + +### Building documentation diff --git a/e2e/common.sh b/e2e/common.sh index 3f397811..a504c9a3 100644 --- a/e2e/common.sh +++ b/e2e/common.sh @@ -25,8 +25,12 @@ mkdir -p "$OUTDIR/build-logs" # Recreate the virtualenv with fromager installed # command_pre hook creates cov.pth -tox -e e2e -r -source .tox/e2e/bin/activate +# tox -e e2e -r +# source .tox/e2e/bin/activate +hatch env remove e2e || true # Remove the e2e env if it exists, ignore error if it doesn't +hatch env create e2e # Ensures the environment exists and dependencies are installed +hatch run e2e:setup-cov # Run the coverage setup script +source "$(hatch env find e2e)/bin/activate" # Set a variable to constrain packages used in the tests export FROMAGER_CONSTRAINTS_FILE="${SCRIPTDIR}/constraints.txt" diff --git a/e2e/pyo3_test/MANIFEST.in b/e2e/pyo3_test/MANIFEST.in index a4efd322..c708d016 100644 --- a/e2e/pyo3_test/MANIFEST.in +++ b/e2e/pyo3_test/MANIFEST.in @@ -1,4 +1,6 @@ graft python graft rust -include Cargo.toml Cargo.lock tests.py tox.ini +include Cargo.toml Cargo.lock tests.py exclude .cargo vendor +graft src +global-exclude *.py[cod] diff --git a/e2e/setup_coverage.py b/e2e/setup_coverage.py new file mode 100644 index 00000000..c3b16730 --- /dev/null +++ b/e2e/setup_coverage.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +"""Simple coverage setup for E2E tests.""" + +import pathlib +import sys +import site + +def setup_coverage(): + """Create coverage.pth file for subprocess coverage collection.""" + # Get the virtual environment root + venv_root = pathlib.Path(sys.prefix) + + # Find site-packages directory + site_packages_dirs = [pathlib.Path(p) for p in site.getsitepackages()] + + # Find the one that's in our virtual environment + site_packages = None + for sp in site_packages_dirs: + if str(sp).startswith(str(venv_root)): + site_packages = sp + break + + if not site_packages: + site_packages = site_packages_dirs[0] # fallback + + # Create coverage.pth file + cov_pth = site_packages / "cov.pth" + cov_pth.parent.mkdir(parents=True, exist_ok=True) + cov_pth.write_text("import coverage; coverage.process_startup()") + + print(f"Coverage setup complete: {cov_pth}") + +if __name__ == "__main__": + setup_coverage() diff --git a/pyproject.toml b/pyproject.toml index 5f372837..556954e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -109,7 +109,7 @@ relative_files = true source = ["fromager", "tests/"] [tool.coverage.paths] -source = ["src/fromager", ".tox/**/site-packages/fromager"] +source = ["src/fromager", ".hatch/**/site-packages/fromager"] tests = ["tests/"] [tool.coverage.report] @@ -194,3 +194,92 @@ reportUnusedParameter = false reportAny = false reportImplicitOverride = false reportAttributeAccessIssue = false # adding our own private property on click commands + +[tool.hatch.envs.default] +# No specific python version here, so it uses the active Python by default. +dependencies = [] + +[tool.hatch.envs.test] +# Corresponds to the default test environment +dependencies = [ + "coverage[toml]!=4.4,>=4.0", + "pytest", + "requests-mock", + "twine>=6.1.0", + "hatchling", + "hatch-vcs", +] +# {args:tests} allows passing arguments to specify which tests to run +scripts.test = "python -m coverage run -m pytest --log-level DEBUG -vv {args:tests}" + +[tool.hatch.envs.coverage-report] +description = "Report coverage over all test runs." +# Depends on test environment to have run and generated coverage data +depends-on = ["test"] # Add "e2e" if its coverage data should be combined. +dependencies = ["coverage[toml]"] +skip-install = true +detached = true # Allows running in parallel +scripts.report = [ + "coverage combine", + "coverage report", +] + +[tool.hatch.envs.lint] +description = "Run linters." +dependencies = [ + "ruff", + "packaging", + "PyYAML", +] +scripts.check = [ + "ruff check src tests", + "ruff format --check src tests", + "python ./e2e/mergify_lint.py", +] +skip-install = true # Corresponds to skipping project installation for linting + +[tool.hatch.envs.fix] +description = "Apply auto-fixes." +dependencies = ["ruff"] +scripts.fix = [ + "ruff format src tests docs", + "ruff check --fix src tests docs", +] +skip-install = true + +[tool.hatch.envs.e2e] +description = "Run end-to-end tests." +dependencies = ["coverage[toml]", "twine"] +env-vars.COVERAGE_PROCESS_START = "{root}/pyproject.toml" +# Script to create cov.pth for coverage in subprocesses +scripts.setup-cov = "python e2e/setup_coverage.py" +# Generic script to run any command in the e2e environment +# e.g. hatch run e2e:run ./e2e/test_bootstrap.sh +scripts.run = "{args}" + +[tool.hatch.envs.mypy] +description = "Python type checking with mypy." +features = ["mypy"] +scripts.check = [ + "mypy -p fromager", + "mypy tests/", +] + +[tool.hatch.envs.pkglint] +description = "Check package and distribution." +features = ["build"] +dependencies = ["check-python-versions"] +scripts.check = [ + "python -m build", + "twine check dist/*.tar.gz dist/*.whl", + "check-python-versions --only pyproject.toml,.github/workflows/test.yml", +] + +[tool.hatch.envs.docs] +description = "Sphinx docs." +features = ["docs"] +scripts.build = "sphinx-build -M html docs docs/_build -j auto --keep-going {args:--fail-on-warning --fresh-env -n}" + +# Default environments to run if `hatch run` is called without arguments +[tool.hatch.matrix] +default.envs = ["test", "lint", "mypy", "docs", "coverage-report"] diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 7186e876..00000000 --- a/tox.ini +++ /dev/null @@ -1,88 +0,0 @@ -[tox] -minversion = 3.2.0 -envlist=py,linter,mypy,docs,coverage-report - -[testenv] -extras = test -deps = - hatchling -commands = - python -m coverage run -m pytest \ - --log-level DEBUG \ - -vv \ - {posargs:tests} - -[testenv:coverage-report] -description = Report coverage over all test runs. -basepython = py312 -depends = py,py3{11,12},e2e -deps = coverage[toml] -skip_install = true -parallel_show_output = true -commands = - coverage combine - coverage report - -[testenv:linter] -base_python=python3.11 -deps= - ruff - packaging - PyYAML -commands = - ruff check src tests - ruff format --check src tests - python ./e2e/mergify_lint.py -skip_install = true -skip_sdist = true - -[testenv:fix] -base_python=python3.11 -deps= - ruff -commands = - ruff format src tests docs - ruff check --fix src tests docs -skip_install = true -skip_sdist = true - -[testenv:cli] -base_python=python3.11 -deps = . -commands = - fromager {posargs} - -[testenv:e2e] -set_env = - COVERAGE_PROCESS_START={toxinidir}/pyproject.toml -commands_pre = - {envpython} -c 'import pathlib; pathlib.Path("{env_site_packages_dir}/cov.pth").write_text("import coverage; coverage.process_startup()")' -deps = - . - coverage[toml] -# empty commands -commands = - -[testenv:mypy] -description = Python type checking with mypy -extras = mypy -commands = - mypy -p fromager - mypy tests/ - -[testenv:pkglint] -base_python=python3.11 -deps= - .[build] - check-python-versions -commands= - python -m build - twine check dist/*.tar.gz dist/*.whl - check-python-versions --only pyproject.toml,.github/workflows/test.yml - -[testenv:docs] -description = sphinx docs -basepython = python3.11 -deps = .[docs] -commands = - sphinx-build -M html docs docs/_build -j auto --keep-going {posargs:--fail-on-warning --fresh-env -n}