From f905df8553efcfc5d4be05bebcdaf9a0de584420 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 23 Aug 2023 15:52:07 -0500 Subject: [PATCH] MOTOR-1139 Switch to Pytest (#222) --- .evergreen/config.yml | 162 +++---- .evergreen/run-enterprise-auth-tests.sh | 2 +- .evergreen/run-tox.sh | 66 ++- .flake8 | 31 ++ .github/workflows/test-python.yml | 118 +++-- CONTRIBUTING.rst | 10 +- MANIFEST.in | 5 +- RELEASE.rst | 6 +- ez_setup.py | 447 ------------------- motor/core.py | 4 +- pyproject.toml | 82 ++++ pytest.ini | 15 + release.sh | 15 +- setup.py | 192 +------- synchro/__init__.py | 4 - synchro/synchrotest.py | 2 + test/__init__.py | 43 +- test/asyncio_tests/test_asyncio_ssl.py | 2 + test/test_environment.py | 4 + test/tornado_tests/__init__.py | 3 +- test/tornado_tests/test_motor_basic.py | 2 + test/tornado_tests/test_motor_transaction.py | 2 +- test/utils.py | 2 + tox.ini | 215 ++++----- 24 files changed, 418 insertions(+), 1016 deletions(-) create mode 100644 .flake8 delete mode 100644 ez_setup.py create mode 100644 pyproject.toml create mode 100644 pytest.ini diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 428bdcd4..3191141f 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -403,9 +403,6 @@ functions: set -x export LIBMONGOCRYPT_URL="${libmongocrypt_url}" export TEST_ENCRYPTION=1 - if [ -n "${EXTRA_PATH}" ]; then - export PATH="${EXTRA_PATH}:$PATH" - fi PYTHON_BINARY="${PYTHON_BINARY}" \ TOX_BINARY="${TOX_BINARY}" \ TOX_ENV="${TOX_ENV}" \ @@ -424,7 +421,6 @@ functions: working_dir: "src" script: | # DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does) - CLIENT_PEM=${DRIVERS_TOOLS}/.evergreen/x509gen/client.pem \ CA_PEM=${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem \ PYTHON_BINARY=${PYTHON_BINARY} \ @@ -799,7 +795,7 @@ tasks: commands: - func: "run tox" vars: - TOX_ENV: py3-sphinx-docs + TOX_ENV: docs - name: "doctest" commands: @@ -809,7 +805,7 @@ tasks: TOPOLOGY: "server" - func: "run tox" vars: - TOX_ENV: py3-sphinx-doctest + TOX_ENV: doctest # }}} @@ -831,127 +827,95 @@ axes: - id: tox-env display_name: "Tox Env RHEL8" values: - - id: "tornado5-pypy37" + - id: "test-pypy38" variables: - TOX_ENV: "tornado5-pypy37" - PYTHON_BINARY: "/opt/python/pypy3.7/bin/pypy3" - - id: "tornado5-py37" + TOX_ENV: "test" + PYTHON_BINARY: "/opt/python/pypy3.8/bin/python3" + - id: "test-py37" variables: - TOX_ENV: "tornado5-py37" + TOX_ENV: "test" PYTHON_BINARY: "/opt/python/3.7/bin/python3" - - id: "tornado6-pypy37" + - id: "test-py37" variables: - TOX_ENV: "tornado6-pypy37" - PYTHON_BINARY: "/opt/python/pypy3.7/bin/pypy3" - - id: "tornado6-py37" - variables: - TOX_ENV: "tornado6-py37" + TOX_ENV: "test" PYTHON_BINARY: "/opt/python/3.7/bin/python3" - - id: "tornado6-py38" + - id: "test-py38" variables: - TOX_ENV: "tornado6-py38" + TOX_ENV: "test" PYTHON_BINARY: "/opt/python/3.8/bin/python3" - - id: "tornado6-py39" + - id: "test-py39" variables: - TOX_ENV: "tornado6-py39" + TOX_ENV: "test" PYTHON_BINARY: "/opt/python/3.9/bin/python3" - - id: "tornado6-py310" + - id: "test-py310" variables: - TOX_ENV: "tornado6-py310" + TOX_ENV: "test" PYTHON_BINARY: "/opt/python/3.10/bin/python3" - - id: "tornado6-py311" + - id: "test-py311" variables: - TOX_ENV: "tornado6-py311" + TOX_ENV: "test" PYTHON_BINARY: "/opt/python/3.11/bin/python3" - - id: "tornado6-py312" + - id: "test-py312" variables: - TOX_ENV: "tornado6-py312" + TOX_ENV: "test" PYTHON_BINARY: "/opt/python/3.12/bin/python3" - - id: "tornado_git-py38" + - id: "test-pymongo-latest" variables: - TOX_ENV: "tornado_git-py38" - PYTHON_BINARY: "/opt/python/3.8/bin/python3" - - id: "asyncio-pypy37" - variables: - TOX_ENV: "asyncio-pypy37" - PYTHON_BINARY: "/opt/python/pypy3.7/bin/pypy3" - - id: "asyncio-py37" - variables: - TOX_ENV: "asyncio-py37" + TOX_ENV: "test-pymongo-latest" PYTHON_BINARY: "/opt/python/3.7/bin/python3" - - id: "asyncio-py38" + - id: "synchro-py37" variables: - TOX_ENV: "asyncio-py38" - PYTHON_BINARY: "/opt/python/3.8/bin/python3" - - id: "asyncio-py39" - variables: - TOX_ENV: "asyncio-py39" - PYTHON_BINARY: "/opt/python/3.9/bin/python3" - - id: "asyncio-py310" - variables: - TOX_ENV: "asyncio-py310" - PYTHON_BINARY: "/opt/python/3.10/bin/python3" - - id: "asyncio-py311" - variables: - TOX_ENV: "asyncio-py311" - PYTHON_BINARY: "/opt/python/3.11/bin/python3" - - id: "asyncio-py312" - variables: - TOX_ENV: "asyncio-py312" - PYTHON_BINARY: "/opt/python/3.12/bin/python3" - - id: "py3-pymongo-latest" - variables: - TOX_ENV: "py3-pymongo-latest" - PYTHON_BINARY: "/opt/python/3.7/bin/python3" - - id: "synchro37" - variables: - TOX_ENV: "synchro37" + TOX_ENV: "synchro" PYTHON_BINARY: "/opt/python/3.7/bin/python3" - - id: "synchro312" + - id: "synchro-py312" variables: - TOX_ENV: "synchro312" + TOX_ENV: "synchro" PYTHON_BINARY: "/opt/python/3.12/bin/python3" - id: tox-env-rhel7 display_name: "Tox Env RHEL7" values: - - id: "tornado6-py39" + - id: "test" variables: - TOX_ENV: "tornado6-py39" - PYTHON_BINARY: "/opt/python/3.9/bin/python3" - - id: "asyncio-py39" - variables: - TOX_ENV: "asyncio-py39" + TOX_ENV: "test" PYTHON_BINARY: "/opt/python/3.9/bin/python3" # Test Python 3.8 only on Mac. - id: tox-env-osx display_name: "Tox Env OSX" values: - - id: "tornado5" + - id: "test" variables: - TOX_ENV: "tornado5" - EXTRA_PATH: "/Library/Frameworks/Python.framework/Versions/3.8/bin/" - - id: "tornado_git" - variables: - TOX_ENV: "tornado_git" - EXTRA_PATH: "/Library/Frameworks/Python.framework/Versions/3.8/bin/" + TOX_ENV: "test" + PYTHON_BINARY: "/Library/Frameworks/Python.framework/Versions/3.8/bin/python3" - id: tox-env-win display_name: "Tox Env Windows" values: - - id: "asyncio" + - id: "test-py37" variables: - TOX_ENV: "asyncio" - EXTRA_PATH: "c:\\python\\Python37" - - id: "tornado6" + TOX_ENV: "test" + PYTHON_BINARY: "c:/python/Python37/python.exe" + - id: "test-py38" variables: - TOX_ENV: "tornado6" - EXTRA_PATH: "c:\\python\\Python37" - - id: "tornado6" + TOX_ENV: "test" + PYTHON_BINARY: "c:/python/Python39/python.exe" + - id: "test-py39" variables: - TOX_ENV: "tornado6" - EXTRA_PATH: "c:\\python\\Python38" + TOX_ENV: "test" + PYTHON_BINARY: "c:/python/Python39/python.exe" + - id: "test-py310" + variables: + TOX_ENV: "test" + PYTHON_BINARY: "c:/python/Python310/python.exe" + - id: "test-py311" + variables: + TOX_ENV: "test" + PYTHON_BINARY: "c:/python/Python311/python.exe" + - id: "test-py312" + variables: + TOX_ENV: "test" + PYTHON_BINARY: "c:/python/Python312/python.exe" - id: os display_name: "Operating System" @@ -959,27 +923,15 @@ axes: - id: "rhel84" display_name: "RHEL 8.4" run_on: "rhel84-small" - variables: - INSTALL_TOX: true - VIRTUALENV: "/opt/python/3.7/bin/python3 -m virtualenv" - id: "rhel76" display_name: "RHEL 7.6" run_on: "rhel76-small" - variables: - INSTALL_TOX: true - VIRTUALENV: "/opt/python/3.7/bin/python3 -m virtualenv" - id: "win" display_name: "Windows" run_on: "windows-64-vsMulti-small" - variables: - INSTALL_TOX: true - VIRTUALENV: "/cygdrive/c/python/Python37/python.exe -m virtualenv" - id: "macos-1100" display_name: "macOS 11.00" run_on: "macos-1100" - variables: - INSTALL_TOX: true - VIRTUALENV: "/Library/Frameworks/Python.framework/Versions/3.8/bin/python3 -m venv" buildvariants: @@ -994,7 +946,7 @@ buildvariants: # TODO: synchro needs PyMongo's updated SSL test certs, # which may require Motor test suite changes. - os: "*" - tox-env: ["synchro37", "synchro312"] + tox-env: ["synchro-py37", "synchro-py312"] ssl: "ssl" tasks: - ".rapid" @@ -1052,7 +1004,7 @@ buildvariants: - matrix_name: "enterprise-auth" display_name: "Enterprise Auth-${tox-env}" - matrix_spec: {"tox-env": ["synchro37", "synchro312"], ssl: "ssl"} + matrix_spec: {"tox-env": ["synchro-py37", "synchro-py312"], ssl: "ssl"} run_on: - "rhel84-small" tasks: @@ -1063,9 +1015,7 @@ buildvariants: run_on: - "rhel84-small" expansions: - TOX_ENV: "py3-sphinx-docs" - INSTALL_TOX: true - VIRTUALENV: "/opt/python/3.7/bin/python3 -m virtualenv" + TOX_ENV: "docs" PYTHON_BINARY: "/opt/python/3.7/bin/python3" tasks: - name: "docs" @@ -1075,9 +1025,7 @@ buildvariants: run_on: - "rhel84-small" expansions: - TOX_ENV: "py3-sphinx-doctest" - INSTALL_TOX: true - VIRTUALENV: "/opt/python/3.7/bin/python3 -m virtualenv" + TOX_ENV: "doctest" PYTHON_BINARY: "/opt/python/3.7/bin/python3" tasks: - name: "doctest" diff --git a/.evergreen/run-enterprise-auth-tests.sh b/.evergreen/run-enterprise-auth-tests.sh index 91f92238..29300dee 100644 --- a/.evergreen/run-enterprise-auth-tests.sh +++ b/.evergreen/run-enterprise-auth-tests.sh @@ -26,4 +26,4 @@ export GSSAPI_PRINCIPAL=${PRINCIPAL} export TOX_TESTENV_PASSENV="*" # --sitepackages allows use of pykerberos without a test dep. -/opt/python/3.7/bin/python3 -m tox -e "$TOX_ENV" --sitepackages -- -x test.test_auth +/opt/python/3.7/bin/python3 -m tox -m "$TOX_ENV" --sitepackages -- -x test.test_auth diff --git a/.evergreen/run-tox.sh b/.evergreen/run-tox.sh index 06e22c92..5bd2137d 100755 --- a/.evergreen/run-tox.sh +++ b/.evergreen/run-tox.sh @@ -6,14 +6,16 @@ set -o errexit # Exit the script with error if any of the commands fail # AUTH Set to enable authentication. Defaults to "noauth" # SSL Set to enable SSL. Defaults to "nossl" # TOX_ENV Tox environment name, e.g. "tornado5-py37" -# TOX_BINARY Path to tox executable -# INSTALL_TOX Whether to install tox in a virtualenv # PYTHON_BINARY Path to python -# VIRTUALENV Path to virtualenv script AUTH=${AUTH:-noauth} SSL=${SSL:-nossl} +if [ -z $PYTHON_BINARY ]; then + echo "PYTHON_BINARY is undefined!" + exit 1 +fi + if [ "$AUTH" != "noauth" ]; then export DB_USER="bob" export DB_PASSWORD="pwd123" @@ -24,24 +26,50 @@ if [ "$SSL" != "nossl" ]; then export CA_PEM="$DRIVERS_TOOLS/.evergreen/x509gen/ca.pem" fi -if [ "$TOX_ENV" = "synchro37" ]; then - SETUP_ARGS="-- --check-exclude-patterns" -fi -if [ "${INSTALL_TOX}" = "true" ]; then - $VIRTUALENV motorenv - set +o xtrace - if [ -f motorenv/bin/activate ]; then - source motorenv/bin/activate +# Usage: +# createvirtualenv /path/to/python /output/path/for/venv +# * param1: Python binary to use for the virtualenv +# * param2: Path to the virtualenv to create +createvirtualenv () { + PYTHON=$1 + VENVPATH=$2 + if $PYTHON -m virtualenv --version; then + VIRTUALENV="$PYTHON -m virtualenv" + elif $PYTHON -m venv -h > /dev/null; then + # System virtualenv might not be compatible with the python3 on our path + VIRTUALENV="$PYTHON -m venv" else - # Windows. - ls -l motorenv - source motorenv/Scripts/activate + echo "Cannot test without virtualenv" + exit 1 fi - set -o xtrace - pip install "tox>=3.18,<4" - TOX_BINARY=tox + # Workaround for bug in older versions of virtualenv. + $VIRTUALENV $VENVPATH || $PYTHON -m venv $VENVPATH + if [ "Windows_NT" = "$OS" ]; then + # Workaround https://bugs.python.org/issue32451: + # mongovenv/Scripts/activate: line 3: $'\r': command not found + dos2unix $VENVPATH/Scripts/activate || true + . $VENVPATH/Scripts/activate + else + . $VENVPATH/bin/activate + fi + + python -m pip install --upgrade pip + python -m pip install --upgrade setuptools wheel tox +} + + +if $PYTHON_BINARY -m tox --version; then + run_tox() { + $PYTHON_BINARY -m tox -m $TOX_ENV "$@" + } +else # No toolchain present, set up virtualenv before installing tox + createvirtualenv "$PYTHON_BINARY" toxenv + trap "deactivate; rm -rf toxenv" EXIT HUP + python -m pip install tox + run_tox() { + python -m tox -m $TOX_ENV "$@" + } fi -# Run the tests, and store the results in Evergreen compatible XUnit XML -${TOX_BINARY} -e ${TOX_ENV} ${SETUP_ARGS} "$@" +run_tox "${@:1}" diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..1ad38100 --- /dev/null +++ b/.flake8 @@ -0,0 +1,31 @@ +[flake8] +max-line-length = 100 +enable-extensions = G +extend-ignore = + G200, G202, G001 + # black adds spaces around ':' + E203, + # E501 line too long (let black handle line length) + E501 + # B305 `.next()` is not a thing on Python 3. Use the `next()` builtin. For Python 2 compatibility, use `six.next()`. + B305 +per-file-ignores = + # F841 local variable 'foo' is assigned to but never used + # E731 do not assign a lambda expression, use a def + # F811 redefinition of unused 'foo' from line XXX + test/*/test_examples.py: F841,E731,F811 + + # F811 redefinition of unused 'foo' from line XXX + # B011 Do not call assert False since python -O removes these calls. Instead callers should raise AssertionError(). + + test/*: F811,B011 + + # E402 module level import not at top of file + doc/examples/monitoring_example.py: E402 + + # F403 'from foo import *' used; unable to detect undefined names + # F401 'foo' imported but unused + synchro/__init__.py: F403,F401 + + # F401 'foo' imported but unused + motor/__init__.py: F401 diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 127749bd..a7903bf2 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -4,55 +4,34 @@ on: push: pull_request: +concurrency: + group: tests-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash -eux {0} + jobs: - pre-commit: - name: pre-commit - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - - uses: pre-commit/action@v2.0.0 - with: - extra_args: --all-files --hook-stage=manual - build: runs-on: ${{ matrix.os }} + timeout-minutes: 10 strategy: matrix: os: [ubuntu-20.04] - python-version: ["3.7", "3.11"] + python-version: ["3.7", "3.12"] fail-fast: false name: CPython ${{ matrix.python-version }}-${{ matrix.os }} steps: - uses: actions/checkout@v2 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} cache: 'pip' - cache-dependency-path: 'setup.py' - - name: Start MongoDB with Custom Options - run: | - mkdir data - mongod --fork --dbpath=$(pwd)/data --logpath=$PWD/mongo.log --setParameter enableTestCommands=1 - - name: Install Python dependencies - run: | - python -m pip install -U pip tox tox-gh-actions setuptools - - name: Run tests - run: | - tox - - docs: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: 3.7 - cache: 'pip' - cache-dependency-path: 'setup.py' + cache-dependency-path: 'pyproject.toml' + allow-prereleases: true - name: Start MongoDB with Custom Options run: | mkdir data @@ -60,30 +39,85 @@ jobs: - name: Install Python dependencies run: | python -m pip install -U pip tox - - name: Run docs + - name: Run tests run: | - tox -e py3-sphinx-docs - tox -e py3-sphinx-doctest - tox -e py3-sphinx-linkcheck + tox -m test + + lint: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v3 + with: + python-version: 3.8 + cache: 'pip' + cache-dependency-path: 'pyproject.toml' + - name: Install Python dependencies + run: | + python -m pip install -U pip tox + - name: Run linters + run: | + tox -m lint-manual + tox -m manifest + + docs: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v3 + with: + python-version: 3.7 + cache: 'pip' + cache-dependency-path: 'pyproject.toml' + - name: Install Python dependencies + run: | + python -m pip install -U pip tox + - name: Run docs + run: tox -m docs + - name: Run linkcheck + run: tox -m linkcheck + - name: Start MongoDB with Custom Options + run: | + mkdir data + mongod --fork --dbpath=$(pwd)/data --logpath=$PWD/mongo.log --setParameter enableTestCommands=1 + - name: Run doctest + run: tox -m doctest + + release: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v3 + with: + python-version: 3.7 + cache: 'pip' + cache-dependency-path: 'pyproject.toml' + - name: Install Python dependencies + run: | + python -m pip install -U pip + - name: Run the release script + run: | + bash release.sh typing: name: Typing Tests runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.7', '3.11'] + python-version: ['3.7', '3.12'] fail-fast: false steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} cache: 'pip' - cache-dependency-path: 'setup.py' + cache-dependency-path: 'pyproject.toml' + allow-prereleases: true - name: Install dependencies run: | python -m pip install -U pip tox - name: Run mypy run: | - tox -e typecheck-mypy + tox -m typecheck-mypy diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index bee5c252..10c7b675 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -29,14 +29,14 @@ Control how the tests connect to MongoDB with these environment variables: Install `tox`_ and run it from the command line in the repository directory. You will need a variety of Python interpreters installed. For a minimal test, -ensure you have Python 3.9, and run:: +ensure you have your desired Python version on your path, and run:: - > tox -e asyncio-py39 + > tox -m test -The doctests pass with Python 3.7 and a MongoDB 5.0 instance running on +The doctests pass with Python 3.7+ and a MongoDB 5.0 instance running on port 27017: - > tox -e py3-sphinx-doctest + > tox -m doctest .. _tox: https://testrun.org/tox/ @@ -55,7 +55,7 @@ To set up ``pre-commit`` locally, run:: To run ``pre-commit`` manually, run:: - > tox -e lint + > tox -m lint General Guidelines ------------------ diff --git a/MANIFEST.in b/MANIFEST.in index 96a39faf..2a49b9a8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,8 @@ include README.rst include LICENSE include tox.ini +include pytest.ini +include pyproject.toml include mypy.ini include doc/Makefile include doc/examples/tornado_change_stream_templates/index.html @@ -17,12 +19,13 @@ recursive-include doc *.bat recursive-include synchro *.py recursive-include motor *.pyi recursive-include motor *.typed +recursive-include motor *.py +exclude .flake8 exclude .readthedocs.yaml exclude .git-blame-ignore-revs exclude .pre-commit-config.yaml exclude release.sh -exclude ez_setup.py exclude RELEASE.rst exclude CONTRIBUTING.rst exclude .evergreen/* diff --git a/RELEASE.rst b/RELEASE.rst index f3419988..2cb046cb 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -32,9 +32,9 @@ and a `source distribution .dev0 in setup.py/__init__.py, +#. Bump the version number to .dev0 in motor/_version.py, commit, push. #. Publish the release version in Jira. diff --git a/ez_setup.py b/ez_setup.py deleted file mode 100644 index 57de963f..00000000 --- a/ez_setup.py +++ /dev/null @@ -1,447 +0,0 @@ -#!/usr/bin/env python - -""" -Setuptools bootstrapping installer. - -Maintained at https://github.com/pypa/setuptools/tree/bootstrap. - -Run this script to install or upgrade setuptools. -""" - -import codecs -import contextlib -import json -import optparse -import os -import platform -import shutil -import subprocess -import sys -import tempfile -import textwrap -import zipfile -from distutils import log - -try: - from urllib.parse import urljoin - from urllib.request import urlopen -except ImportError: - from urllib2 import urlopen - from urlparse import urljoin - -try: - from site import USER_SITE -except ImportError: - USER_SITE = None - -LATEST = object() -DEFAULT_VERSION = LATEST -DEFAULT_URL = "https://pypi.io/packages/source/s/setuptools/" -DEFAULT_SAVE_DIR = os.curdir - - -def _python_cmd(*args): - """ - Execute a command. - - Return True if the command succeeded. - """ - args = (sys.executable,) + args - return subprocess.call(args) == 0 - - -def _install(archive_filename, install_args=()): - """Install Setuptools.""" - with archive_context(archive_filename): - # installing - log.warning("Installing Setuptools") - if not _python_cmd("setup.py", "install", *install_args): - log.warning("Something went wrong during the installation.") - log.warning("See the error message above.") - # exitcode will be 2 - return 2 - - -def _build_egg(egg, archive_filename, to_dir): - """Build Setuptools egg.""" - with archive_context(archive_filename): - # building an egg - log.warning("Building a Setuptools egg in %s", to_dir) - _python_cmd("setup.py", "-q", "bdist_egg", "--dist-dir", to_dir) - # returning the result - log.warning(egg) - if not os.path.exists(egg): - raise IOError("Could not build the egg.") - - -class ContextualZipFile(zipfile.ZipFile): - - """Supplement ZipFile class to support context manager for Python 2.6.""" - - def __enter__(self): - return self - - def __exit__(self, type, value, traceback): - self.close() - - def __new__(cls, *args, **kwargs): - """Construct a ZipFile or ContextualZipFile as appropriate.""" - if hasattr(zipfile.ZipFile, "__exit__"): - return zipfile.ZipFile(*args, **kwargs) - return super().__new__(cls) - - -@contextlib.contextmanager -def archive_context(filename): - """ - Unzip filename to a temporary directory, set to the cwd. - - The unzipped target is cleaned up after. - """ - tmpdir = tempfile.mkdtemp() - log.warning("Extracting in %s", tmpdir) - old_wd = os.getcwd() - try: - os.chdir(tmpdir) - with ContextualZipFile(filename) as archive: - archive.extractall() - - # going in the directory - subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) - os.chdir(subdir) - log.warning("Now working in %s", subdir) - yield - - finally: - os.chdir(old_wd) - shutil.rmtree(tmpdir) - - -def _do_download(version, download_base, to_dir, download_delay): - """Download Setuptools.""" - py_desig = "py{sys.version_info[0]}.{sys.version_info[1]}".format(sys=sys) - tp = "setuptools-{version}-{py_desig}.egg" - egg = os.path.join(to_dir, tp.format(**locals())) - if not os.path.exists(egg): - archive = download_setuptools(version, download_base, to_dir, download_delay) - _build_egg(egg, archive, to_dir) - sys.path.insert(0, egg) - - # Remove previously-imported pkg_resources if present (see - # https://bitbucket.org/pypa/setuptools/pull-request/7/ for details). - if "pkg_resources" in sys.modules: - _unload_pkg_resources() - - import setuptools - - setuptools.bootstrap_install_from = egg - - -def use_setuptools( - version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=DEFAULT_SAVE_DIR, download_delay=15 -): - """ - Ensure that a setuptools version is installed. - - Return None. Raise SystemExit if the requested version - or later cannot be installed. - """ - version = _resolve_version(version) - to_dir = os.path.abspath(to_dir) - - # prior to importing, capture the module state for - # representative modules. - rep_modules = "pkg_resources", "setuptools" - imported = set(sys.modules).intersection(rep_modules) - - try: - import pkg_resources - - pkg_resources.require("setuptools>=" + version) - # a suitable version is already installed - return - except ImportError: - # pkg_resources not available; setuptools is not installed; download - pass - except pkg_resources.DistributionNotFound: - # no version of setuptools was found; allow download - pass - except pkg_resources.VersionConflict as VC_err: - if imported: - _conflict_bail(VC_err, version) - - # otherwise, unload pkg_resources to allow the downloaded version to - # take precedence. - del pkg_resources - _unload_pkg_resources() - - return _do_download(version, download_base, to_dir, download_delay) - - -def _conflict_bail(VC_err, version): - """ - Setuptools was imported prior to invocation, so it is - unsafe to unload it. Bail out. - """ - conflict_tmpl = textwrap.dedent( - """ - The required version of setuptools (>={version}) is not available, - and can't be installed while this script is running. Please - install a more recent version first, using - 'easy_install -U setuptools'. - - (Currently using {VC_err.args[0]!r}) - """ - ) - msg = conflict_tmpl.format(**locals()) - sys.stderr.write(msg) - sys.exit(2) - - -def _unload_pkg_resources(): - sys.meta_path = [ - importer - for importer in sys.meta_path - if importer.__class__.__module__ != "pkg_resources.extern" - ] - del_modules = [name for name in sys.modules if name.startswith("pkg_resources")] - for mod_name in del_modules: - del sys.modules[mod_name] - - -def _clean_check(cmd, target): - """ - Run the command to download target. - - If the command fails, clean up before re-raising the error. - """ - try: - subprocess.check_call(cmd) - except subprocess.CalledProcessError: - if os.access(target, os.F_OK): - os.unlink(target) - raise - - -def download_file_powershell(url, target): - """ - Download the file at url to target using Powershell. - - Powershell will validate trust. - Raise an exception if the command cannot complete. - """ - target = os.path.abspath(target) - ps_cmd = ( - "[System.Net.WebRequest]::DefaultWebProxy.Credentials = " - "[System.Net.CredentialCache]::DefaultCredentials; " - '(new-object System.Net.WebClient).DownloadFile("%(url)s", "%(target)s")' % locals() - ) - cmd = [ - "powershell", - "-Command", - ps_cmd, - ] - _clean_check(cmd, target) - - -def has_powershell(): - """Determine if Powershell is available.""" - if platform.system() != "Windows": - return False - cmd = ["powershell", "-Command", "echo test"] - with open(os.path.devnull, "wb") as devnull: - try: - subprocess.check_call(cmd, stdout=devnull, stderr=devnull) - except Exception: - return False - return True - - -download_file_powershell.viable = has_powershell - - -def download_file_curl(url, target): - cmd = ["curl", url, "--location", "--silent", "--output", target] - _clean_check(cmd, target) - - -def has_curl(): - cmd = ["curl", "--version"] - with open(os.path.devnull, "wb") as devnull: - try: - subprocess.check_call(cmd, stdout=devnull, stderr=devnull) - except Exception: - return False - return True - - -download_file_curl.viable = has_curl - - -def download_file_wget(url, target): - cmd = ["wget", url, "--quiet", "--output-document", target] - _clean_check(cmd, target) - - -def has_wget(): - cmd = ["wget", "--version"] - with open(os.path.devnull, "wb") as devnull: - try: - subprocess.check_call(cmd, stdout=devnull, stderr=devnull) - except Exception: - return False - return True - - -download_file_wget.viable = has_wget - - -def download_file_insecure(url, target): - """Use Python to download the file, without connection authentication.""" - src = urlopen(url) - try: - # Read all the data in one block. - data = src.read() - finally: - src.close() - - # Write all the data in one block to avoid creating a partial file. - with open(target, "wb") as dst: - dst.write(data) - - -download_file_insecure.viable = lambda: True - - -def get_best_downloader(): - downloaders = ( - download_file_powershell, - download_file_curl, - download_file_wget, - download_file_insecure, - ) - viable_downloaders = (dl for dl in downloaders if dl.viable()) - return next(viable_downloaders, None) - - -def download_setuptools( - version=DEFAULT_VERSION, - download_base=DEFAULT_URL, - to_dir=DEFAULT_SAVE_DIR, - delay=15, - downloader_factory=get_best_downloader, -): - """ - Download setuptools from a specified location and return its filename. - - `version` should be a valid setuptools version number that is available - as an sdist for download under the `download_base` URL (which should end - with a '/'). `to_dir` is the directory where the egg will be downloaded. - `delay` is the number of seconds to pause before an actual download - attempt. - - ``downloader_factory`` should be a function taking no arguments and - returning a function for downloading a URL to a target. - """ - version = _resolve_version(version) - # making sure we use the absolute path - to_dir = os.path.abspath(to_dir) - zip_name = "setuptools-%s.zip" % version - url = download_base + zip_name - saveto = os.path.join(to_dir, zip_name) - if not os.path.exists(saveto): # Avoid repeated downloads - log.warning("Downloading %s", url) - downloader = downloader_factory() - downloader(url, saveto) - return os.path.realpath(saveto) - - -def _resolve_version(version): - """ - Resolve LATEST version - """ - if version is not LATEST: - return version - - meta_url = urljoin(DEFAULT_URL, "/pypi/setuptools/json") - resp = urlopen(meta_url) - with contextlib.closing(resp): - try: - charset = resp.info().get_content_charset() - except Exception: - # Python 2 compat; assume UTF-8 - charset = "UTF-8" - reader = codecs.getreader(charset) - doc = json.load(reader(resp)) - - return str(doc["info"]["version"]) - - -def _build_install_args(options): - """ - Build the arguments to 'python setup.py install' on the setuptools package. - - Returns list of command line arguments. - """ - return ["--user"] if options.user_install else [] - - -def _parse_args(): - """Parse the command line for options.""" - parser = optparse.OptionParser() - parser.add_option( - "--user", - dest="user_install", - action="store_true", - default=False, - help="install in user site package", - ) - parser.add_option( - "--download-base", - dest="download_base", - metavar="URL", - default=DEFAULT_URL, - help="alternative URL from where to download the setuptools package", - ) - parser.add_option( - "--insecure", - dest="downloader_factory", - action="store_const", - const=lambda: download_file_insecure, - default=get_best_downloader, - help="Use internal, non-validating downloader", - ) - parser.add_option( - "--version", - help="Specify which version to download", - default=DEFAULT_VERSION, - ) - parser.add_option( - "--to-dir", - help="Directory to save (and re-use) package", - default=DEFAULT_SAVE_DIR, - ) - options, args = parser.parse_args() - # positional arguments are ignored - return options - - -def _download_args(options): - """Return args for download_setuptools function from cmdline args.""" - return dict( - version=options.version, - download_base=options.download_base, - downloader_factory=options.downloader_factory, - to_dir=options.to_dir, - ) - - -def main(): - """Install or upgrade setuptools and EasyInstall.""" - options = _parse_args() - archive = download_setuptools(**_download_args(options)) - return _install(archive, _build_install_args(options)) - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/motor/core.py b/motor/core.py index 5ca96014..4f7e4067 100644 --- a/motor/core.py +++ b/motor/core.py @@ -1325,14 +1325,14 @@ class AgnosticCollection(AgnosticBaseProperties): # Latent cursor that will send initial command on first "async for". return cursor_class(self, self._async_list_indexes, session=session, **kwargs) - def _list_search_indexes(self, session=None, **kwargs): + def _list_search_indexes(self, *args, **kwargs): """Return a cursor over search indexes for the current collection.""" cursor_class = create_class_with_framework( AgnosticLatentCommandCursor, self._framework, self.__module__ ) # Latent cursor that will send initial command on first "async for". - return cursor_class(self, self._async_list_search_indexes, session=session, **kwargs) + return cursor_class(self, self._async_list_search_indexes, *args, **kwargs) # TODO: MOTOR-1169 if hasattr(Collection, "list_search_indexes"): diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..1263513e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,82 @@ +[build-system] +requires = ["setuptools>61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "motor" +dynamic = ["version"] +description = "Non-blocking MongoDB driver for Tornado or asyncio" +readme = "README.rst" +license = { file = "LICENSE" } +requires-python = ">=3.7" +authors = [ + { name = "A. Jesse Jiryu Davis", email = "jesse@mongodb.com" }, +] +keywords = [ + "asyncio", + "bson", + "gridfs", + "mongo", + "mongodb", + "motor", + "pymongo", + "tornado", +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Natural Language :: English", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows", + "Operating System :: Unix", + "Typing :: Typed", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +dependencies = [ + "pymongo>=4.4,<5", +] + +[project.optional-dependencies] +aws = [ + "pymongo[aws]>=4.4,<5", +] +encryption = [ + "pymongo[encryption]>=4.4,<5", +] +gssapi = [ + "pymongo[gssapi]>=4.4,<5", +] +ocsp = [ + "pymongo[ocsp]>=4.4,<5", +] +snappy = [ + "pymongo[snappy]>=4.4,<5", +] +srv = [ + "pymongo[srv]>=4.4,<5", +] +test = [ + "pytest>=7", "mockupdb", "tornado>=5", "aiohttp", "motor[encryption]" +] +zstd = [ + "pymongo[zstd]>=4.4,<5", +] + +[project.urls] +Homepage = "https://github.com/mongodb/motor/" + +[tool.setuptools.dynamic] +version = {attr = "motor._version.version"} + +[tool.setuptools.packages.find] +include = ["motor"] diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..0a211eca --- /dev/null +++ b/pytest.ini @@ -0,0 +1,15 @@ +[pytest] +testpaths = + test +addopts = -ra --junitxml=xunit-results/TEST-results.xml +filterwarnings = + error + ignore:Bare functions are deprecated, use async ones:DeprecationWarning + ignore:Application.make_handler:DeprecationWarning + ignore:unclosed =3.7") - -classifiers = """\ -Intended Audience :: Developers -License :: OSI Approved :: Apache Software License -Development Status :: 5 - Production/Stable -Natural Language :: English -Programming Language :: Python :: 3 -Programming Language :: Python :: 3.7 -Programming Language :: Python :: 3.8 -Programming Language :: Python :: 3.9 -Programming Language :: Python :: 3.10 -Programming Language :: Python :: 3.11 -Operating System :: MacOS :: MacOS X -Operating System :: Unix -Operating System :: Microsoft :: Windows -Programming Language :: Python -Programming Language :: Python :: Implementation :: CPython -Programming Language :: Python :: Implementation :: PyPy -Typing :: Typed -""" - -description = "Non-blocking MongoDB driver for Tornado or asyncio" - -with open("README.rst") as readme: - long_description = readme.read() - -pymongo_ver = ">=4.4,<5" - -install_requires = ["pymongo" + pymongo_ver] - -extras_require = { - "encryption": ["pymongo[encryption]" + pymongo_ver], - "ocsp": ["pymongo[ocsp]" + pymongo_ver], - "snappy": ["pymongo[snappy]" + pymongo_ver], - "zstd": ["pymongo[zstd]" + pymongo_ver], - "aws": ["pymongo[aws]" + pymongo_ver], - "srv": ["pymongo[srv]" + pymongo_ver], - "gssapi": ["pymongo[gssapi]" + pymongo_ver], -} - -tests_require = ["mockupdb>=1.4.0"] - - -class test(Command): - description = "run the tests" - - user_options = [ - ("test-module=", "m", "Discover tests in specified module"), - ("test-suite=", "s", "Test suite to run (e.g. 'some_module.test_suite')"), - ("failfast", "f", "Stop running tests on first failure or error"), - ("tornado-warnings", "w", "Let Tornado log warnings"), - ("xunit-output=", "x", "Generate a results directory with XUnit XML format"), - ] - - def initialize_options(self): - self.test_module = None - self.test_suite = None - self.failfast = False - self.tornado_warnings = False - self.xunit_output = None - - def finalize_options(self): - if self.test_suite is None and self.test_module is None: - self.test_module = "test" - elif self.test_module is not None and self.test_suite is not None: - raise OptionError("You may specify a module or suite, but not both") - - def run(self): - # Installing required packages, running egg_info and build_ext are - # part of normal operation for setuptools.command.test.test. Motor - # has no extensions so build_ext is a no-op. - if self.distribution.install_requires: - self.distribution.fetch_build_eggs(self.distribution.install_requires) - if self.distribution.tests_require: - self.distribution.fetch_build_eggs(self.distribution.tests_require) - if self.xunit_output: - self.distribution.fetch_build_eggs(["unittest-xml-reporting>=1.14.0,<2.0.0a0"]) - self.run_command("egg_info") - build_ext_cmd = self.reinitialize_command("build_ext") - build_ext_cmd.inplace = 1 - self.run_command("build_ext") - - from test import MotorTestLoader, env, suppress_tornado_warnings - from test import test_environment as testenv - - loader = MotorTestLoader() - loader.avoid("high_availability", reason="Runs separately") - - if not (testenv.HAVE_ASYNCIO or testenv.HAVE_TORNADO): - raise ImportError("No tornado nor asyncio") - elif not testenv.HAVE_TORNADO: - loader.avoid("tornado_tests", reason="no tornado") - elif not testenv.HAVE_ASYNCIO: - loader.avoid("asyncio_tests", reason="no asyncio") - - if not testenv.HAVE_AIOHTTP: - loader.avoid("asyncio_tests.test_aiohttp_gridfs", reason="no aiohttp") - - # Decide if we can run async / await tests with Tornado. - if not testenv.HAVE_TORNADO: - test_motor_await = "tornado_tests.test_motor_await" - loader.avoid(test_motor_await, reason="no tornado") - - if self.test_suite is None: - suite = loader.discover(self.test_module) - else: - suite = loader.loadTestsFromName(self.test_suite) - - runner_kwargs = dict(verbosity=2, failfast=self.failfast) - - if self.xunit_output: - try: - from xmlrunner import XMLTestRunner - except ImportError: - self.xunit_output = False - else: - runner_kwargs["output"] = self.xunit_output - runner_class = XMLTestRunner - - if not self.xunit_output: - import unittest - - runner_class = unittest.TextTestRunner - - runner = runner_class(**runner_kwargs) - if "SKIP_ENV_SETUP" not in os.environ: - env.setup() - if not self.tornado_warnings: - suppress_tornado_warnings() - - result = runner.run(suite) - sys.exit(not result.wasSuccessful()) - - -packages = [ - "motor", - "motor.frameworks", - "motor.frameworks.tornado", - "motor.frameworks.asyncio", - "motor.aiohttp", -] - - -version_ns = {} -with open("motor/_version.py") as fp: - exec(fp.read(), version_ns) -version = version_ns["version"] - - -setup( - name="motor", - version=version, - packages=packages, - description=description, - long_description=long_description, - author="A. Jesse Jiryu Davis", - author_email="jesse@mongodb.com", - url="https://github.com/mongodb/motor/", - python_requires=">=3.7", - install_requires=install_requires, - extras_require=extras_require, - license="http://www.apache.org/licenses/LICENSE-2.0", - classifiers=[c for c in classifiers.split("\n") if c], - keywords=[ - "mongo", - "mongodb", - "pymongo", - "gridfs", - "bson", - "motor", - "tornado", - "asyncio", - ], - tests_require=tests_require, - test_suite="test", - zip_safe=False, - cmdclass={"test": test}, -) +setup() diff --git a/synchro/__init__.py b/synchro/__init__.py index 96f1d791..ceaa97d4 100644 --- a/synchro/__init__.py +++ b/synchro/__init__.py @@ -490,11 +490,7 @@ class Collection(Synchro): aggregate = WrapOutgoing() aggregate_raw_batches = WrapOutgoing() list_indexes = WrapOutgoing() - create_search_index = WrapOutgoing() - create_search_indexes = WrapOutgoing() - drop_search_index = WrapOutgoing() list_search_indexes = WrapOutgoing() - update_search_index = WrapOutgoing() watch = WrapOutgoing() __bool__ = WrapOutgoing() diff --git a/synchro/synchrotest.py b/synchro/synchrotest.py index 03db25de..6ae8cd07 100644 --- a/synchro/synchrotest.py +++ b/synchro/synchrotest.py @@ -188,6 +188,8 @@ excluded_tests = [ "TestUnifiedPoolClearedError.test_PoolClearedError_does_not_mark_server_unknown", # These tests have hard-coded values that differ from motor. "TestClient.test_handshake.*", + # This test is not a valid unittest target. + "TestRangeQueryProse.run_test_cases", ] diff --git a/test/__init__.py b/test/__init__.py index 235e34d3..e8602eef 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -14,10 +14,8 @@ """Test Motor, an asynchronous driver for MongoDB and Tornado.""" -import logging -import unittest from test.test_environment import CLIENT_PEM, db_user, env # noqa: F401 -from unittest import SkipTest +from unittest import SkipTest # noqa: F401 try: # Enable the fault handler to dump the traceback of each running @@ -33,45 +31,6 @@ except ImportError: pass -def suppress_tornado_warnings(): - for name in ["tornado.general", "tornado.access"]: - logger = logging.getLogger(name) - logger.setLevel(logging.ERROR) - - -class SkippedModule(object): - def __init__(self, name, reason): - def runTest(self): - raise SkipTest(str(reason)) - - self.test_case = type(str(name), (unittest.TestCase,), {"runTest": runTest}) - - -class MotorTestLoader(unittest.TestLoader): - def __init__(self, avoid=None, reason=None): - super().__init__() - self._avoid = [] - - def avoid(self, *prefixes, **kwargs): - """Skip a module. - - The usual "raise SkipTest" from a module doesn't work if the module - won't even parse in Python 2, so prevent TestLoader from importing - modules with the given prefix. - - "prefix" is a path prefix like "asyncio_tests". - """ - for prefix in prefixes: - self._avoid.append((prefix, kwargs["reason"])) - - def _get_module_from_name(self, name): - for prefix, reason in self._avoid: - if name.startswith(prefix): - return SkippedModule(name, reason) - - return super()._get_module_from_name(name) - - class MockRequestHandler(object): """For testing MotorGridOut.stream_to_handler.""" diff --git a/test/asyncio_tests/test_asyncio_ssl.py b/test/asyncio_tests/test_asyncio_ssl.py index 809df47b..321e4a76 100644 --- a/test/asyncio_tests/test_asyncio_ssl.py +++ b/test/asyncio_tests/test_asyncio_ssl.py @@ -47,6 +47,8 @@ class TestAsyncIOSSL(unittest.TestCase): self.loop = asyncio.new_event_loop() def tearDown(self): + if not hasattr(self, "loop"): + return self.loop.stop() self.loop.run_forever() self.loop.close() diff --git a/test/test_environment.py b/test/test_environment.py index ab9e4430..a6dc16a6 100644 --- a/test/test_environment.py +++ b/test/test_environment.py @@ -104,6 +104,8 @@ def is_server_resolvable(): class TestEnvironment(object): + __test__ = False + def __init__(self): self.initialized = False self.host = None @@ -369,3 +371,5 @@ class TestEnvironment(object): env = TestEnvironment() +if "SKIP_ENV_SETUP" not in os.environ: + env.setup() diff --git a/test/tornado_tests/__init__.py b/test/tornado_tests/__init__.py index 2b0ffb94..ac09397b 100644 --- a/test/tornado_tests/__init__.py +++ b/test/tornado_tests/__init__.py @@ -101,7 +101,8 @@ class MotorTest(testing.AsyncTestCase): def tearDown(self): env.sync_cx.motor_test.test_collection.delete_many({}) - self.cx.close() + if hasattr(self, "cx"): + self.cx.close() super().tearDown() diff --git a/test/tornado_tests/test_motor_basic.py b/test/tornado_tests/test_motor_basic.py index c87a321e..2406a2b0 100644 --- a/test/tornado_tests/test_motor_basic.py +++ b/test/tornado_tests/test_motor_basic.py @@ -13,6 +13,7 @@ # limitations under the License. """Test Motor, an asynchronous driver for MongoDB and Tornado.""" +import asyncio import os import test import unittest @@ -158,6 +159,7 @@ class ExecutorForkTest(MotorTest): parent_conn, child_conn = Pipe() lock_pid = os.fork() if lock_pid == 0: # Child + asyncio.set_event_loop(asyncio.new_event_loop()) self.loop = IOLoop.current() client = self.motor_client() try: diff --git a/test/tornado_tests/test_motor_transaction.py b/test/tornado_tests/test_motor_transaction.py index 93887974..a44f3393 100644 --- a/test/tornado_tests/test_motor_transaction.py +++ b/test/tornado_tests/test_motor_transaction.py @@ -191,7 +191,7 @@ class TestTransactionsConvenientAPI(MotorTest): self.assertEqual( listener.started_command_names(), ["insert", "commitTransaction", "commitTransaction"] ) - self.set_fail_point(client, {"configureFailPoint": "failCommand", "mode": "off"}) + await self.set_fail_point(client, {"configureFailPoint": "failCommand", "mode": "off"}) if __name__ == "__main__": diff --git a/test/utils.py b/test/utils.py index c0aa0e63..b17df736 100644 --- a/test/utils.py +++ b/test/utils.py @@ -72,6 +72,8 @@ def get_primary_pool(client): # Ignore auth commands like saslStart, so we can assert lsid is in all commands. class TestListener(monitoring.CommandListener): + __test__ = False + def __init__(self): self.results = defaultdict(list) diff --git a/tox.ini b/tox.ini index a899734e..d7061b14 100644 --- a/tox.ini +++ b/tox.ini @@ -4,41 +4,45 @@ # Adapted from Tornado's tox.ini. [tox] +requires = + tox>=4 + envlist = - # Tornado 5 supports Python 3.4+. - tornado5-{py37}, - - # Tornado 6 supports Python 3.5+. - tornado6-{pypy37,py37,py38,py39,py310,py311,py312}, - - # Test Tornado's dev version in a few configurations. - tornado_git-{py38}, - + # Run the unit test suite + test # Ensure the sphinx build has no errors or warnings. - py3-sphinx-docs, - + docs, # Run the doctests, include examples and tutorial, via Sphinx. - py3-sphinx-doctest, - - # asyncio without Tornado. - asyncio-{py37,py38,py39,py310,py311,py312}, - + doctest, + # Check links of sphinx docs + linkcheck, # Test with the latest PyMongo. - py3-pymongo-latest, - + test-pymongo-latest, # Apply PyMongo's test suite to Motor via Synchro. - synchro37 - synchro312 - + synchro # Run pre-commit on all files. lint - + # Run pre-commit on all files with manual checks. + lint-manual # Check the sdist integrity. manifest - # Typecheck with mypy typecheck-mypy + +labels = # Use labels and -m instead of -e so that tox -m