From 4dfeac63687628e041bef1bbae29e68b4bc3ca39 Mon Sep 17 00:00:00 2001 From: jakkdl Date: Fri, 31 Jan 2025 12:09:11 +0100 Subject: [PATCH 1/4] add initial tox.ini --- .gitignore | 3 ++ tox.ini | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index cad76cb460..e63a1a5556 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# generated by cythonize +tests/cython/test_cython.c + # In case somebody wants to restore the directory for local testing notes-to-self/ diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000000..0a06b3c0ea --- /dev/null +++ b/tox.ini @@ -0,0 +1,102 @@ +[tox] +envlist = py{39,310,311,312,313,314,py310} +labels = + check = typing, gen_exports, type_completeness, pip_compile + cython = py39-cython2,py39-cython,py311-cython2,py313-cython + +# TODO: +# coverage +# ci.sh uses --verbose --durations=10 +# -p _trio_check_attrs_aliases +# mypy cache +# LSP +# apport +# use tox in CI +# switch to nox? +# move to pyproject.toml + +# protip: install uv-tox for faster venv generation + +[testenv] +# use wheels instead of sdist, significantly faster install +package = wheel +wheel_build_env = .pkg +deps = + hypothesis: hypothesis + -r test-requirements.txt +set_env = + slow: TOX_RUN_SLOW = '--run-slow' +commands = + pytest {env:TOX_RUN_SLOW:} {posargs} + +[testenv:no_test_requirements] +commands = + pytest --skip-optional-imports {posargs} + +[testenv:docs] +deps = + -r docs-requirements.txt +# base_python synced with .readthedocs.yml +base_python = 3.11 +commands = + sphinx-build {posargs:--fresh-env} docs/source docs/build + +[testenv:py39-cython2,py39-cython,py311-cython2,py313-cython] +deps = + cython + cython2: cython<3 + setuptools ; python_version >= '3.12' +commands = + cythonize --inplace -X linetrace=True tests/cython/test_cython.pyx + python --version + cython --version + python -m tests.cython.run_test_cython + +# tox -m check should be equivalent to this +[testenv:checksh] +deps = + -r test-requirements.txt + exceptiongroup +base_python = 3.13 +allowlist_externals = ./check.sh +set_env = + PYRIGHT_PYTHON_IGNORE_WARNINGS=1 +commands = + ./check.sh + +[testenv:gen_exports] +deps = + -r test-requirements.txt +base_python = 3.13 +commands = + python ./src/trio/_tools/gen_exports.py --test + +[testenv:pip_compile] +base_python = 3.13 +commands = + pre-commit run pip-compile --all-files + +[testenv:typing] +deps = + -r test-requirements.txt + exceptiongroup +base_python = 3.13 +set_env = + PYRIGHT_PYTHON_IGNORE_WARNINGS=1 +commands = + mypy --platform linux + mypy --platform darwin + mypy --platform win32 + + pyright src/trio/_tests/type_tests + pyright src/trio/_core/_tests/type_tests + +[testenv:type_completeness] +deps = + -r test-requirements.txt + exceptiongroup +base_python = 3.13 +set_env = + PYRIGHT_PYTHON_IGNORE_WARNINGS=1 +commands = + python src/trio/_tests/check_type_completeness.py From a020836286c1a5b433d5d552c6e031a826874ee4 Mon Sep 17 00:00:00 2001 From: jakkdl Date: Fri, 14 Feb 2025 16:56:08 +0100 Subject: [PATCH 2/4] remove check.sh, replace with tox invocation --- .github/workflows/ci.yml | 7 +- check.sh | 81 ----------------- ci.sh | 190 +++++++++++++++++++-------------------- tox.ini | 32 +++---- 4 files changed, 109 insertions(+), 201 deletions(-) delete mode 100755 check.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a4489e609d..1d22dc89a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -276,10 +276,15 @@ jobs: python-version: ${{ fromJSON(format('["{0}", "{1}"]', format('{0}.0-alpha - {0}.X', matrix.python), matrix.python))[startsWith(matrix.python, 'pypy')] }} cache: pip cache-dependency-path: test-requirements.txt + - name: Check Formatting + if: matrix.check_formatting == '1' + run: + python -m pip install tox + tox -m check - name: Run tests + if: matrix.check_formatting == '0' run: ./ci.sh env: - CHECK_FORMATTING: '${{ matrix.check_formatting }}' NO_TEST_REQUIREMENTS: '${{ matrix.no_test_requirements }}' - if: >- always() diff --git a/check.sh b/check.sh deleted file mode 100755 index 701db8ce61..0000000000 --- a/check.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/bin/bash - -set -ex - -ON_GITHUB_CI=true -EXIT_STATUS=0 - -# If not running on Github's CI, discard the summaries -if [ -z "${GITHUB_STEP_SUMMARY+x}" ]; then - GITHUB_STEP_SUMMARY=/dev/null - ON_GITHUB_CI=false -fi - -# Test if the generated code is still up to date -echo "::group::Generate Exports" -python ./src/trio/_tools/gen_exports.py --test \ - || EXIT_STATUS=$? -echo "::endgroup::" - -# Run mypy on all supported platforms -# MYPY is set if any of them fail. -MYPY=0 -echo "::group::Mypy" -# Cleanup previous runs. -rm -f mypy_annotate.dat -# Pipefail makes these pipelines fail if mypy does, even if mypy_annotate.py succeeds. -set -o pipefail -mypy --show-error-end --platform linux | python ./src/trio/_tools/mypy_annotate.py --dumpfile mypy_annotate.dat --platform Linux \ - || { echo "* Mypy (Linux) found type errors." >> "$GITHUB_STEP_SUMMARY"; MYPY=1; } -# Darwin tests FreeBSD too -mypy --show-error-end --platform darwin | python ./src/trio/_tools/mypy_annotate.py --dumpfile mypy_annotate.dat --platform Mac \ - || { echo "* Mypy (Mac) found type errors." >> "$GITHUB_STEP_SUMMARY"; MYPY=1; } -mypy --show-error-end --platform win32 | python ./src/trio/_tools/mypy_annotate.py --dumpfile mypy_annotate.dat --platform Windows \ - || { echo "* Mypy (Windows) found type errors." >> "$GITHUB_STEP_SUMMARY"; MYPY=1; } -set +o pipefail -# Re-display errors using Github's syntax, read out of mypy_annotate.dat -python ./src/trio/_tools/mypy_annotate.py --dumpfile mypy_annotate.dat -# Then discard. -rm -f mypy_annotate.dat -echo "::endgroup::" -# Display a big error if we failed, outside the group so it can't be collapsed. -if [ $MYPY -ne 0 ]; then - echo "::error:: Mypy found type errors." - EXIT_STATUS=1 -fi - -# Check pip compile is consistent -echo "::group::Pip Compile - Tests & Docs" -pre-commit run pip-compile --all-files \ - || EXIT_STATUS=$? -echo "::endgroup::" - -echo "::group::Pyright interface tests" -python src/trio/_tests/check_type_completeness.py || EXIT_STATUS=$? - -pyright src/trio/_tests/type_tests || EXIT_STATUS=$? -pyright src/trio/_core/_tests/type_tests || EXIT_STATUS=$? -echo "::endgroup::" - -# Finally, leave a really clear warning of any issues and exit -if [ $EXIT_STATUS -ne 0 ]; then - cat <> "$GITHUB_STEP_SUMMARY" -exit 0 diff --git a/ci.sh b/ci.sh index 83ec65748b..f414d94a2c 100755 --- a/ci.sh +++ b/ci.sh @@ -47,115 +47,109 @@ python -m build wheel_package=$(ls dist/*.whl) python -m uv pip install "trio @ $wheel_package" -c test-requirements.txt -if [ "$CHECK_FORMATTING" = "1" ]; then - python -m uv pip install -r test-requirements.txt exceptiongroup - echo "::endgroup::" - source check.sh +# Actual tests +# expands to 0 != 1 if NO_TEST_REQUIREMENTS is not set, if set the `-0` has no effect +# https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02 +if [ "${NO_TEST_REQUIREMENTS-0}" == 1 ]; then + python -m uv pip install pytest coverage -c test-requirements.txt + flags="--skip-optional-imports" else - # Actual tests - # expands to 0 != 1 if NO_TEST_REQUIREMENTS is not set, if set the `-0` has no effect - # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02 - if [ "${NO_TEST_REQUIREMENTS-0}" == 1 ]; then - python -m uv pip install pytest coverage -c test-requirements.txt - flags="--skip-optional-imports" - else - python -m uv pip install -r test-requirements.txt - flags="" - fi + python -m uv pip install -r test-requirements.txt + flags="" +fi - # So we can run the test for our apport/excepthook interaction working - if [ -e /etc/lsb-release ] && grep -q Ubuntu /etc/lsb-release; then - sudo apt install -q python3-apport - fi +# So we can run the test for our apport/excepthook interaction working +if [ -e /etc/lsb-release ] && grep -q Ubuntu /etc/lsb-release; then + sudo apt install -q python3-apport +fi - # If we're testing with a LSP installed, then it might break network - # stuff, so wait until after we've finished setting everything else - # up. - if [ "$LSP" != "" ]; then - echo "Installing LSP from ${LSP}" - # We use --insecure because one of the LSP's has been observed to give - # cert verification errors: - # - # https://github.com/python-trio/trio/issues/1478 - # - # *Normally*, you should never ever use --insecure, especially when - # fetching an executable! But *in this case*, we're intentionally - # installing some untrustworthy quasi-malware onto into a sandboxed - # machine for testing. So MITM attacks are really the least of our - # worries. - if [ "$LSP_EXTRACT_FILE" != "" ]; then - # We host the Astrill VPN installer ourselves, and encrypt it - # so as to decrease the chances of becoming an inadvertent - # public redistributor. - curl-harder -o lsp-installer.zip "$LSP" - unzip -P "not very secret trio ci key" lsp-installer.zip "$LSP_EXTRACT_FILE" - mv "$LSP_EXTRACT_FILE" lsp-installer.exe - else - curl-harder --insecure -o lsp-installer.exe "$LSP" - fi - # This is only needed for the Astrill LSP, but there's no harm in - # doing it all the time. The cert was manually extracted by installing - # the package in a VM, clicking "Always trust from this publisher" - # when installing, and then running 'certmgr.msc' and exporting the - # certificate. See: - # http://www.migee.com/2010/09/24/solution-for-unattendedsilent-installs-and-would-you-like-to-install-this-device-software/ - certutil -addstore "TrustedPublisher" src/trio/_tests/astrill-codesigning-cert.cer - # Double-slashes are how you tell windows-bash that you want a single - # slash, and don't treat this as a unix-style filename that needs to - # be replaced by a windows-style filename. - # http://www.mingw.org/wiki/Posix_path_conversion - ./lsp-installer.exe //silent //norestart - echo "Waiting for LSP to appear in Winsock catalog" - while ! netsh winsock show catalog | grep "Layered Chain Entry"; do - sleep 1 - done - netsh winsock show catalog +# If we're testing with a LSP installed, then it might break network +# stuff, so wait until after we've finished setting everything else +# up. +if [ "$LSP" != "" ]; then + echo "Installing LSP from ${LSP}" + # We use --insecure because one of the LSP's has been observed to give + # cert verification errors: + # + # https://github.com/python-trio/trio/issues/1478 + # + # *Normally*, you should never ever use --insecure, especially when + # fetching an executable! But *in this case*, we're intentionally + # installing some untrustworthy quasi-malware onto into a sandboxed + # machine for testing. So MITM attacks are really the least of our + # worries. + if [ "$LSP_EXTRACT_FILE" != "" ]; then + # We host the Astrill VPN installer ourselves, and encrypt it + # so as to decrease the chances of becoming an inadvertent + # public redistributor. + curl-harder -o lsp-installer.zip "$LSP" + unzip -P "not very secret trio ci key" lsp-installer.zip "$LSP_EXTRACT_FILE" + mv "$LSP_EXTRACT_FILE" lsp-installer.exe + else + curl-harder --insecure -o lsp-installer.exe "$LSP" fi - echo "::endgroup::" - - echo "::group::Setup for tests" + # This is only needed for the Astrill LSP, but there's no harm in + # doing it all the time. The cert was manually extracted by installing + # the package in a VM, clicking "Always trust from this publisher" + # when installing, and then running 'certmgr.msc' and exporting the + # certificate. See: + # http://www.migee.com/2010/09/24/solution-for-unattendedsilent-installs-and-would-you-like-to-install-this-device-software/ + certutil -addstore "TrustedPublisher" src/trio/_tests/astrill-codesigning-cert.cer + # Double-slashes are how you tell windows-bash that you want a single + # slash, and don't treat this as a unix-style filename that needs to + # be replaced by a windows-style filename. + # http://www.mingw.org/wiki/Posix_path_conversion + ./lsp-installer.exe //silent //norestart + echo "Waiting for LSP to appear in Winsock catalog" + while ! netsh winsock show catalog | grep "Layered Chain Entry"; do + sleep 1 + done + netsh winsock show catalog +fi +echo "::endgroup::" - # We run the tests from inside an empty directory, to make sure Python - # doesn't pick up any .py files from our working dir. Might have already - # been created by a previous run. - mkdir empty || true - cd empty +echo "::group::Setup for tests" - INSTALLDIR=$(python -c "import os, trio; print(os.path.dirname(trio.__file__))") - cp ../pyproject.toml "$INSTALLDIR" # TODO: remove this +# We run the tests from inside an empty directory, to make sure Python +# doesn't pick up any .py files from our working dir. Might have already +# been created by a previous run. +mkdir empty || true +cd empty - # get mypy tests a nice cache - MYPYPATH=".." mypy --config-file= --cache-dir=./.mypy_cache -c "import trio" >/dev/null 2>/dev/null || true +INSTALLDIR=$(python -c "import os, trio; print(os.path.dirname(trio.__file__))") +cp ../pyproject.toml "$INSTALLDIR" # TODO: remove this - # support subprocess spawning with coverage.py - echo "import coverage; coverage.process_startup()" | tee -a "$INSTALLDIR/../sitecustomize.py" +# get mypy tests a nice cache +MYPYPATH=".." mypy --config-file= --cache-dir=./.mypy_cache -c "import trio" >/dev/null 2>/dev/null || true - perl -i -pe 's/-p trio\._tests\.pytest_plugin//' "$INSTALLDIR/pyproject.toml" +# support subprocess spawning with coverage.py +echo "import coverage; coverage.process_startup()" | tee -a "$INSTALLDIR/../sitecustomize.py" - echo "::endgroup::" - echo "::group:: Run Tests" - if PYTHONPATH=../tests COVERAGE_PROCESS_START=$(pwd)/../pyproject.toml \ - coverage run --rcfile=../pyproject.toml -m \ - pytest -ra --junitxml=../test-results.xml \ - -p _trio_check_attrs_aliases --verbose --durations=10 \ - -p trio._tests.pytest_plugin --run-slow $flags "${INSTALLDIR}"; then - PASSED=true - else - PASSED=false - fi - echo "::endgroup::" - echo "::group::Coverage" +perl -i -pe 's/-p trio\._tests\.pytest_plugin//' "$INSTALLDIR/pyproject.toml" - coverage combine --rcfile ../pyproject.toml - coverage report -m --rcfile ../pyproject.toml - coverage xml --rcfile ../pyproject.toml +echo "::endgroup::" +echo "::group:: Run Tests" +if PYTHONPATH=../tests COVERAGE_PROCESS_START=$(pwd)/../pyproject.toml \ + coverage run --rcfile=../pyproject.toml -m \ + pytest -ra --junitxml=../test-results.xml \ + -p _trio_check_attrs_aliases --verbose --durations=10 \ + -p trio._tests.pytest_plugin --run-slow $flags "${INSTALLDIR}"; then + PASSED=true +else + PASSED=false +fi +echo "::endgroup::" +echo "::group::Coverage" - # Remove the LSP again; again we want to do this ASAP to avoid - # accidentally breaking other stuff. - if [ "$LSP" != "" ]; then - netsh winsock reset - fi +coverage combine --rcfile ../pyproject.toml +coverage report -m --rcfile ../pyproject.toml +coverage xml --rcfile ../pyproject.toml - echo "::endgroup::" - $PASSED +# Remove the LSP again; again we want to do this ASAP to avoid +# accidentally breaking other stuff. +if [ "$LSP" != "" ]; then + netsh winsock reset fi + +echo "::endgroup::" +$PASSED diff --git a/tox.ini b/tox.ini index 0a06b3c0ea..24c5ea99b1 100644 --- a/tox.ini +++ b/tox.ini @@ -5,15 +5,17 @@ labels = cython = py39-cython2,py39-cython,py311-cython2,py313-cython # TODO: -# coverage -# ci.sh uses --verbose --durations=10 -# -p _trio_check_attrs_aliases -# mypy cache -# LSP -# apport -# use tox in CI -# switch to nox? -# move to pyproject.toml +# * environment to check coverage +# * replace ci.sh +# * --verbose --durations=10 +# * -p _trio_check_attrs_aliases +# * mypy cache +# * LSP +# * apport +# * use tox in CI +# * switch to nox? +# * move to pyproject.toml? +# * this means conditional deps need to be replaced # protip: install uv-tox for faster venv generation @@ -52,18 +54,6 @@ commands = cython --version python -m tests.cython.run_test_cython -# tox -m check should be equivalent to this -[testenv:checksh] -deps = - -r test-requirements.txt - exceptiongroup -base_python = 3.13 -allowlist_externals = ./check.sh -set_env = - PYRIGHT_PYTHON_IGNORE_WARNINGS=1 -commands = - ./check.sh - [testenv:gen_exports] deps = -r test-requirements.txt From eeef5bd9766055ae97a241aed0194cd85209d8ae Mon Sep 17 00:00:00 2001 From: jakkdl Date: Fri, 14 Feb 2025 17:24:06 +0100 Subject: [PATCH 3/4] fix ci --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d22dc89a3..a84a676ae1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -279,7 +279,7 @@ jobs: - name: Check Formatting if: matrix.check_formatting == '1' run: - python -m pip install tox + python -m pip install tox && tox -m check - name: Run tests if: matrix.check_formatting == '0' From 6d2a0f569b7d1d41c2d3b91786a10a16611e77ab Mon Sep 17 00:00:00 2001 From: jakkdl Date: Mon, 24 Feb 2025 12:30:45 +0100 Subject: [PATCH 4/4] fixes after review --- tox.ini | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 24c5ea99b1..46b9fae2bc 100644 --- a/tox.ini +++ b/tox.ini @@ -20,6 +20,7 @@ labels = # protip: install uv-tox for faster venv generation [testenv] +description = "Base environment for running tests depending on python version." # use wheels instead of sdist, significantly faster install package = wheel wheel_build_env = .pkg @@ -32,29 +33,37 @@ commands = pytest {env:TOX_RUN_SLOW:} {posargs} [testenv:no_test_requirements] +description = "Run tests without optional test-requirements, to see we don't accidentally depend on a library not specified in depends." +deps = + pytest commands = pytest --skip-optional-imports {posargs} [testenv:docs] +description = "Build documentation into docs/build." deps = -r docs-requirements.txt # base_python synced with .readthedocs.yml +# To avoid syncing we can make RTD call the tox environment base_python = 3.11 commands = sphinx-build {posargs:--fresh-env} docs/source docs/build [testenv:py39-cython2,py39-cython,py311-cython2,py313-cython] +description = "Run cython tests." deps = cython cython2: cython<3 setuptools ; python_version >= '3.12' -commands = - cythonize --inplace -X linetrace=True tests/cython/test_cython.pyx +commands_pre = python --version cython --version + cythonize --inplace -X linetrace=True tests/cython/test_cython.pyx +commands = python -m tests.cython.run_test_cython [testenv:gen_exports] +description = "Run gen_exports.py, regenerating code for public API wrappers." deps = -r test-requirements.txt base_python = 3.13 @@ -62,11 +71,14 @@ commands = python ./src/trio/_tools/gen_exports.py --test [testenv:pip_compile] +description = "Run pre-commit job pip-compile" base_python = 3.13 commands = pre-commit run pip-compile --all-files +# TODO: allow specifying e.g. typing-3.11 to run with --python[-]version=3.11 [testenv:typing] +description = "Run type checks: mypy on all platforms, and pyright on `src/trio[/_core]/_tests/type_tests/`." deps = -r test-requirements.txt exceptiongroup @@ -74,6 +86,7 @@ base_python = 3.13 set_env = PYRIGHT_PYTHON_IGNORE_WARNINGS=1 commands = + # use mypy_annotate if running in CI? if not, should remove it mypy --platform linux mypy --platform darwin mypy --platform win32 @@ -82,6 +95,7 @@ commands = pyright src/trio/_core/_tests/type_tests [testenv:type_completeness] +description = "Check type completeness, using our wrapper around pyright --verifytypes." deps = -r test-requirements.txt exceptiongroup