diff --git a/.travis.yml b/.travis.yml index af1e2b14..cbf34cde 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 + # tox dryrun to setup the tox venv (we run a mock test). + - 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,13 +76,13 @@ 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/') -- -a "--cov=mongoengine" # 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 # code in a separate dir and runs tests on that. after_success: -- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coveralls --verbose; fi +- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coveralls --verbose; else echo "coveralls only sent for py27"; fi notifications: irc: irc.freenode.org#mongoengine 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/docs/changelog.rst b/docs/changelog.rst index 58d7f272..249d99b1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -19,6 +19,7 @@ Development - From now on keyword arguments (e.g. ``Doc(field_name=value)``) are required. - Fix updating/modifying/deleting/reloading a document that's sharded by a field with ``db_field`` specified. #2125 - ``ListField`` now accepts an optional ``max_length`` parameter. #2110 +- Switch from nosetest to pytest as test runner #2114 - The codebase is now formatted using ``black``. #2109 - In bulk write insert, the detailed error message would raise in exception. diff --git a/docs/conf.py b/docs/conf.py index 0d642e0c..48c8e859 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,7 +11,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import os +import sys import sphinx_rtd_theme diff --git a/mongoengine/context_managers.py b/mongoengine/context_managers.py index 3424a5d5..d8dfeaac 100644 --- a/mongoengine/context_managers.py +++ b/mongoengine/context_managers.py @@ -247,8 +247,8 @@ class query_counter(object): - self._ctx_query_counter ) self._ctx_query_counter += ( - 1 - ) # Account for the query we just issued to gather the information + 1 # Account for the query we just issued to gather the information + ) return count diff --git a/mongoengine/queryset/base.py b/mongoengine/queryset/base.py index a09cbf99..a648391e 100644 --- a/mongoengine/queryset/base.py +++ b/mongoengine/queryset/base.py @@ -1193,9 +1193,7 @@ class BaseQuerySet(object): validate_read_preference("read_preference", read_preference) queryset = self.clone() queryset._read_preference = read_preference - queryset._cursor_obj = ( - None - ) # we need to re-create the cursor object whenever we apply read_preference + queryset._cursor_obj = None # we need to re-create the cursor object whenever we apply read_preference return queryset def scalar(self, *fields): 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..2bc1ae1c 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,62 @@ 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 argument through the test runner command `python setup.py test` + # e.g: `python setup.py test -a "-k=test"` + # This only works for 1 argument though + 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): + 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)] + + cmd_args = self.test_args + ([self.pytest_args] if self.pytest_args else []) + 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 +110,13 @@ 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", + "pytest-cov", + "coverage<5.0", # recent coverage switched to sqlite format for the .coverage file which isn't handled properly by coveralls + "blinker", + "Pillow>=2.0.0", + ], } if sys.version_info[0] == 3: extra_opts["use_2to3"] = True @@ -79,6 +144,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/document/test_instance.py b/tests/document/test_instance.py index 9b4a16e5..203e2cce 100644 --- a/tests/document/test_instance.py +++ b/tests/document/test_instance.py @@ -1615,7 +1615,7 @@ class TestInstance(MongoDBTestCase): self.assertEqual(person.active, False) def test__get_changed_fields_same_ids_reference_field_does_not_enters_infinite_loop_embedded_doc( - self + self, ): # Refers to Issue #1685 class EmbeddedChildModel(EmbeddedDocument): @@ -1629,7 +1629,7 @@ class TestInstance(MongoDBTestCase): self.assertEqual(changed_fields, []) def test__get_changed_fields_same_ids_reference_field_does_not_enters_infinite_loop_different_doc( - self + self, ): # Refers to Issue #1685 class User(Document): diff --git a/tests/fields/test_complex_datetime_field.py b/tests/fields/test_complex_datetime_field.py index 4eea5bdc..611c0ff8 100644 --- a/tests/fields/test_complex_datetime_field.py +++ b/tests/fields/test_complex_datetime_field.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import datetime -import math import itertools +import math import re from mongoengine import * diff --git a/tests/fields/test_embedded_document_field.py b/tests/fields/test_embedded_document_field.py index 6b420781..8db8c180 100644 --- a/tests/fields/test_embedded_document_field.py +++ b/tests/fields/test_embedded_document_field.py @@ -1,17 +1,15 @@ # -*- coding: utf-8 -*- from mongoengine import ( Document, - StringField, - ValidationError, EmbeddedDocument, EmbeddedDocumentField, - InvalidQueryError, - LookUpError, - IntField, GenericEmbeddedDocumentField, + IntField, + InvalidQueryError, ListField, - EmbeddedDocumentListField, - ReferenceField, + LookUpError, + StringField, + ValidationError, ) from tests.utils import MongoDBTestCase diff --git a/tests/fields/test_fields.py b/tests/fields/test_fields.py index d9279c22..bd2149e6 100644 --- a/tests/fields/test_fields.py +++ b/tests/fields/test_fields.py @@ -79,7 +79,7 @@ class TestField(MongoDBTestCase): self.assertEqual(data_to_be_saved, ["age", "created", "day", "name", "userid"]) def test_custom_field_validation_raise_deprecated_error_when_validation_return_something( - self + self, ): # Covers introduction of a breaking change in the validation parameter (0.18) def _not_empty(z): @@ -202,7 +202,7 @@ class TestField(MongoDBTestCase): self.assertEqual(data_to_be_saved, ["age", "created", "userid"]) def test_default_value_is_not_used_when_changing_value_to_empty_list_for_strict_doc( - self + self, ): """List field with default can be set to the empty list (strict)""" # Issue #1733 @@ -216,7 +216,7 @@ class TestField(MongoDBTestCase): self.assertEqual(reloaded.x, []) def test_default_value_is_not_used_when_changing_value_to_empty_list_for_dyn_doc( - self + self, ): """List field with default can be set to the empty list (dynamic)""" # Issue #1733 @@ -1245,7 +1245,7 @@ class TestField(MongoDBTestCase): self.assertEqual(a.b.c.txt, "hi") def test_embedded_document_field_cant_reference_using_a_str_if_it_does_not_exist_yet( - self + self, ): raise SkipTest( "Using a string reference in an EmbeddedDocumentField does not work if the class isnt registerd yet" diff --git a/tests/fields/test_reference_field.py b/tests/fields/test_reference_field.py index 5fd053fe..783a46da 100644 --- a/tests/fields/test_reference_field.py +++ b/tests/fields/test_reference_field.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from bson import SON, DBRef +from bson import DBRef, SON from mongoengine import * 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/tests/test_common.py b/tests/test_common.py index 5d702668..28f0b992 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -1,7 +1,7 @@ import unittest -from mongoengine.common import _import_class from mongoengine import Document +from mongoengine.common import _import_class class TestCommon(unittest.TestCase): diff --git a/tests/test_connection.py b/tests/test_connection.py index 071f4207..1519a835 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -168,7 +168,7 @@ class ConnectionTest(unittest.TestCase): ) def test_connect_fails_if_similar_connection_settings_arent_defined_the_same_way( - self + self, ): """Intended to keep the detecton function simple but robust""" db_name = "mongoenginetest" diff --git a/tests/test_context_managers.py b/tests/test_context_managers.py index dc9b9bf3..32e48a70 100644 --- a/tests/test_context_managers.py +++ b/tests/test_context_managers.py @@ -3,11 +3,11 @@ import unittest from mongoengine import * from mongoengine.connection import get_db from mongoengine.context_managers import ( - switch_db, - switch_collection, - no_sub_classes, no_dereference, + no_sub_classes, query_counter, + switch_collection, + switch_db, ) from mongoengine.pymongo_support import count_documents diff --git a/tests/test_datastructures.py b/tests/test_datastructures.py index 7def2ac7..ff7598be 100644 --- a/tests/test_datastructures.py +++ b/tests/test_datastructures.py @@ -2,7 +2,7 @@ import unittest from six import iterkeys from mongoengine import Document -from mongoengine.base.datastructures import StrictDict, BaseList, BaseDict +from mongoengine.base.datastructures import BaseDict, BaseList, StrictDict class DocumentStub(object): @@ -20,8 +20,8 @@ class TestBaseDict(unittest.TestCase): fake_doc = DocumentStub() base_list = BaseDict(dict_items, instance=None, name="my_name") base_list._instance = ( - fake_doc - ) # hack to inject the mock, it does not work in the constructor + fake_doc # hack to inject the mock, it does not work in the constructor + ) return base_list def test___init___(self): @@ -156,8 +156,8 @@ class TestBaseList(unittest.TestCase): fake_doc = DocumentStub() base_list = BaseList(list_items, instance=None, name="my_name") base_list._instance = ( - fake_doc - ) # hack to inject the mock, it does not work in the constructor + fake_doc # hack to inject the mock, it does not work in the constructor + ) return base_list def test___init___(self): diff --git a/tests/test_replicaset_connection.py b/tests/test_replicaset_connection.py index 6dfab407..e92f3d09 100644 --- a/tests/test_replicaset_connection.py +++ b/tests/test_replicaset_connection.py @@ -1,7 +1,7 @@ import unittest -from pymongo import ReadPreference from pymongo import MongoClient +from pymongo import ReadPreference import mongoengine from mongoengine.connection import ConnectionFailure diff --git a/tests/test_utils.py b/tests/test_utils.py index 2d1e8b00..897c19b2 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,5 +1,5 @@ -import unittest import re +import unittest from mongoengine.base.utils import LazyRegexCompiler diff --git a/tests/utils.py b/tests/utils.py index eb3f016f..0719d6ef 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,7 +4,7 @@ import unittest from nose.plugins.skip import SkipTest from mongoengine import connect -from mongoengine.connection import get_db, disconnect_all +from mongoengine.connection import disconnect_all, get_db from mongoengine.mongodb_support import get_mongodb_version 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