From bbfa97886188584ffcc7cfb73d084e2206832c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Sun, 25 Aug 2019 15:21:30 +0300 Subject: [PATCH] switch test runner from nose to pytest --- .travis.yml | 6 ++--- README.rst | 11 ++++---- setup.cfg | 11 ++++---- setup.py | 66 ++++++++++++++++++++++++++++++++++++++++++++++-- tests/test_ci.py | 9 +++++++ tox.ini | 2 +- 6 files changed, 87 insertions(+), 18 deletions(-) create mode 100644 tests/test_ci.py diff --git a/.travis.yml b/.travis.yml index af1e2b14..9d2ba8c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -63,8 +63,8 @@ install: - pip install flake8 flake8-import-order - pip install tox # tox 3.11.0 has requirement virtualenv>=14.0.0 - pip install virtualenv # virtualenv>=14.0.0 has dropped Python 3.2 support (and pypy3 is based on py32) - # Install the tox venv. - - tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- -e test + # Install the tox venv (we make pytest avoid running the test by giving a bad pattern) + - tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- -a "-k=test_ci_placeholder" # Install black for Python v3.7 only. - if [[ $TRAVIS_PYTHON_VERSION == '3.7' ]]; then pip install black; fi @@ -76,7 +76,7 @@ before_script: - mongo --eval 'db.version();' # Make sure mongo is awake script: - - tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- --with-coverage + - tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') #-- --with-coverage # For now only submit coveralls for Python v2.7. Python v3.x currently shows # 0% coverage. That's caused by 'use_2to3', which builds the py3-compatible diff --git a/README.rst b/README.rst index 679980f8..853d8fbe 100644 --- a/README.rst +++ b/README.rst @@ -116,7 +116,8 @@ Some simple examples of what MongoEngine code looks like: Tests ===== To run the test suite, ensure you are running a local instance of MongoDB on -the standard port and have ``nose`` installed. Then, run ``python setup.py nosetests``. +the standard port and have ``pytest`` installed. Then, run ``python setup.py test`` +or simply ``pytest``. To run the test suite on every supported Python and PyMongo version, you can use ``tox``. You'll need to make sure you have each supported Python version @@ -129,16 +130,14 @@ installed in your environment and then: # Run the test suites $ tox -If you wish to run a subset of tests, use the nosetests convention: +If you wish to run a subset of tests, use the pytest convention: .. code-block:: shell # Run all the tests in a particular test file - $ python setup.py nosetests --tests tests/fields/fields.py + $ pytest tests/fields/test_fields.py # Run only particular test class in that file - $ python setup.py nosetests --tests tests/fields/fields.py:FieldTest - # Use the -s option if you want to print some debug statements or use pdb - $ python setup.py nosetests --tests tests/fields/fields.py:FieldTest -s + $ pytest tests/fields/test_fields.py::TestField Community ========= diff --git a/setup.cfg b/setup.cfg index 4bded428..ae1b4f7e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,11 +1,10 @@ -[nosetests] -verbosity=2 -detailed-errors=1 -#tests=tests -cover-package=mongoengine - [flake8] ignore=E501,F401,F403,F405,I201,I202,W504, W605, W503 exclude=build,dist,docs,venv,venv3,.tox,.eggs,tests max-complexity=47 application-import-names=mongoengine,tests + +[tool:pytest] +# Limits the discovery to tests directory +# avoids that it runs for instance the benchmark +testpaths = tests diff --git a/setup.py b/setup.py index c73a93ff..81cc9744 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,9 @@ import os import sys + +from pkg_resources import normalize_path from setuptools import find_packages, setup +from setuptools.command.test import test as TestCommand # Hack to silence atexit traceback in newer python versions try: @@ -24,6 +27,65 @@ def get_version(version_tuple): return ".".join(map(str, version_tuple)) +class PyTest(TestCommand): + """Will force pytest to search for tests inside the build directory + for 2to3 converted code (used by tox), instead of the current directory. + Required as long as we need 2to3 + + Known Limitation: https://tox.readthedocs.io/en/latest/example/pytest.html#known-issues-and-limitations + Source: https://www.hackzine.org/python-testing-with-pytest-and-2to3-plus-tox-and-travis-ci.html + """ + + # https://pytest.readthedocs.io/en/2.7.3/goodpractises.html#integration-with-setuptools-test-commands + # Allows to provide pytest command arguments through the test runner command `python setup.py test` + # e.g: `python setup.py test -a "-k=test"` + user_options = [("pytest-args=", "a", "Arguments to pass to py.test")] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = "" + + def finalize_options(self): + TestCommand.finalize_options(self) + self.test_args = ["tests"] + self.test_suite = True + + def run_tests(self): + # import here, cause outside the eggs aren't loaded + from pkg_resources import _namespace_packages + import pytest + + # Purge modules under test from sys.modules. The test loader will + # re-import them from the build location. Required when 2to3 is used + # with namespace packages. + if sys.version_info >= (3,) and getattr(self.distribution, "use_2to3", False): + print("Hack for 2to3", self.test_args) + module = self.test_args[-1].split(".")[0] + if module in _namespace_packages: + del_modules = [] + if module in sys.modules: + del_modules.append(module) + module += "." + for name in sys.modules: + if name.startswith(module): + del_modules.append(name) + map(sys.modules.__delitem__, del_modules) + + # Run on the build directory for 2to3-built code + # This will prevent the old 2.x code from being found + # by py.test discovery mechanism, that apparently + # ignores sys.path.. + ei_cmd = self.get_finalized_command("egg_info") + self.test_args = [normalize_path(ei_cmd.egg_base)] + + print(self.test_args, self.pytest_args) + cmd_args = self.test_args + ([self.pytest_args] if self.pytest_args else []) + print(cmd_args) + errno = pytest.main(cmd_args) + + sys.exit(errno) + + # Dirty hack to get version number from monogengine/__init__.py - we can't # import it as it depends on PyMongo and PyMongo isn't installed until this # file is read @@ -51,7 +113,7 @@ CLASSIFIERS = [ extra_opts = { "packages": find_packages(exclude=["tests", "tests.*"]), - "tests_require": ["nose", "coverage==4.2", "blinker", "Pillow>=2.0.0"], + "tests_require": ["pytest<5.0", "coverage==4.2", "blinker", "Pillow>=2.0.0"], } if sys.version_info[0] == 3: extra_opts["use_2to3"] = True @@ -79,6 +141,6 @@ setup( platforms=["any"], classifiers=CLASSIFIERS, install_requires=["pymongo>=3.4", "six"], - test_suite="nose.collector", + cmdclass={"test": PyTest}, **extra_opts ) diff --git a/tests/test_ci.py b/tests/test_ci.py new file mode 100644 index 00000000..04a800eb --- /dev/null +++ b/tests/test_ci.py @@ -0,0 +1,9 @@ +def test_ci_placeholder(): + # This empty test is used within the CI to + # setup the tox venv without running the test suite + # if we simply skip all test with pytest -k=wrong_pattern + # pytest command would return with exit_code=5 (i.e "no tests run") + # making travis fail + # this empty test is the recommended way to handle this + # as described in https://github.com/pytest-dev/pytest/issues/2393 + pass diff --git a/tox.ini b/tox.ini index b4a57818..94ccc9cf 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ envlist = {py27,py35,pypy,pypy3}-{mg34,mg36} [testenv] commands = - python setup.py nosetests {posargs} + python setup.py test {posargs} deps = nose mg34: pymongo>=3.4,<3.5