diff --git a/.travis.yml b/.travis.yml index 41537a466..0a71e7dc1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,7 @@ env: matrix: include: - env: TOXENV=py36 - python: '3.6-dev' + python: '3.6' - env: TOXENV=py37 python: 'nightly' allow_failures: diff --git a/AUTHORS b/AUTHORS index 3d7f10d5e..f3dafb999 100644 --- a/AUTHORS +++ b/AUTHORS @@ -6,6 +6,7 @@ Contributors include:: Abdeali JK Abhijeet Kasurde Ahn Ki-Wook +Alexander Johnson Alexei Kozlenok Anatoly Bubenkoff Andreas Zeidler @@ -86,6 +87,7 @@ Justyna Janczyszyn Kale Kundert Katarzyna Jachim Kevin Cox +Kodi B. Arfer Lee Kamentsky Lev Maximov Loic Esteve @@ -122,6 +124,7 @@ Oliver Bestwalter Omar Kohl Omer Hadari Patrick Hayes +Paweł Adamczak Pieter Mulder Piotr Banaszkiewicz Punyashloka Biswal @@ -140,6 +143,7 @@ Russel Winder Ryan Wooden Samuele Pedroni Simon Gomizelj +Skylar Downes Stefan Farmbauer Stefan Zimmermann Stefano Taschini @@ -155,5 +159,6 @@ Vasily Kuznetsov Victor Uriarte Vlad Dragos Vidar T. Fauske +Vitaly Lashmanov Wouter van Ackooy Xuecong Liao diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 749bfab3a..b71152cd8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -121,7 +121,22 @@ Bug Fixes 3.0.8 (unreleased) ================== -* +* Change capture.py's ``DontReadFromInput`` class to throw ``io.UnsupportedOperation`` errors rather + than ValueErrors in the ``fileno`` method (`#2276`_). + Thanks `@metasyn`_ for the PR. + +* Fix exception formatting while importing modules when the exception message + contains non-ascii characters (`#2336`_). + Thanks `@fabioz`_ for the report and `@nicoddemus`_ for the PR. + +* Added documentation related to issue (`#1937`_) + Thanks `@skylarjhdownes`_ for the PR. + +* Allow collecting files with any file extension as Python modules (`#2369`_). + Thanks `@Kodiologist`_ for the PR. + +* Show the correct error message when collect "parametrize" func with wrong args (`#2383`_). + Thanks `@The-Compiler`_ for the report and `@robin0371`_ for the PR. * @@ -129,7 +144,20 @@ Bug Fixes * -* + + +.. _@skylarjhdownes: https://github.com/skylarjhdownes +.. _@fabioz: https://github.com/fabioz +.. _@metasyn: https://github.com/metasyn +.. _@Kodiologist: https://github.com/Kodiologist +.. _@robin0371: https://github.com/robin0371 + + +.. _#1937: https://github.com/pytest-dev/pytest/issues/1937 +.. _#2276: https://github.com/pytest-dev/pytest/issues/2276 +.. _#2336: https://github.com/pytest-dev/pytest/issues/2336 +.. _#2369: https://github.com/pytest-dev/pytest/issues/2369 +.. _#2383: https://github.com/pytest-dev/pytest/issues/2383 3.0.7 (2017-03-14) @@ -138,7 +166,7 @@ Bug Fixes * Fix issue in assertion rewriting breaking due to modules silently discarding other modules when importing fails - Notably, importing the `anydbm` module is fixed. (`#2248`_). + Notably, importing the ``anydbm`` module is fixed. (`#2248`_). Thanks `@pfhayes`_ for the PR. * junitxml: Fix problematic case where system-out tag occured twice per testcase @@ -401,6 +429,7 @@ Bug Fixes + 3.0.2 (2016-09-01) ================== diff --git a/README.rst b/README.rst index d5650af65..3a0abc5c1 100644 --- a/README.rst +++ b/README.rst @@ -6,13 +6,20 @@ ------ .. image:: https://img.shields.io/pypi/v/pytest.svg - :target: https://pypi.python.org/pypi/pytest + :target: https://pypi.python.org/pypi/pytest + +.. image:: https://anaconda.org/conda-forge/pytest/badges/version.svg + :target: https://anaconda.org/conda-forge/pytest + .. image:: https://img.shields.io/pypi/pyversions/pytest.svg - :target: https://pypi.python.org/pypi/pytest + :target: https://pypi.python.org/pypi/pytest + .. image:: https://img.shields.io/coveralls/pytest-dev/pytest/master.svg - :target: https://coveralls.io/r/pytest-dev/pytest + :target: https://coveralls.io/r/pytest-dev/pytest + .. image:: https://travis-ci.org/pytest-dev/pytest.svg?branch=master :target: https://travis-ci.org/pytest-dev/pytest + .. image:: https://ci.appveyor.com/api/projects/status/mrgbjaua7t33pg6b?svg=true :target: https://ci.appveyor.com/project/pytestbot/pytest @@ -34,7 +41,7 @@ An example of a simple test: To execute it:: $ pytest - ============================= test session starts ============================= + ============================= test session starts ============================= collected 1 items test_sample.py F diff --git a/_pytest/_code/code.py b/_pytest/_code/code.py index 2f1ac7fb0..f872dba0b 100644 --- a/_pytest/_code/code.py +++ b/_pytest/_code/code.py @@ -3,13 +3,14 @@ import sys from inspect import CO_VARARGS, CO_VARKEYWORDS import re from weakref import ref +from _pytest.compat import _PY2, _PY3, PY35 import py builtin_repr = repr reprlib = py.builtin._tryimport('repr', 'reprlib') -if sys.version_info[0] >= 3: +if _PY3: from traceback import format_exception_only else: from ._py2traceback import format_exception_only @@ -353,7 +354,7 @@ class ExceptionInfo(object): help for navigating the traceback. """ _striptext = '' - _assert_start_repr = "AssertionError(u\'assert " if sys.version_info[0] < 3 else "AssertionError(\'assert " + _assert_start_repr = "AssertionError(u\'assert " if _PY2 else "AssertionError(\'assert " def __init__(self, tup=None, exprinfo=None): import _pytest._code @@ -618,7 +619,7 @@ class FormattedExcinfo(object): def repr_excinfo(self, excinfo): - if sys.version_info[0] < 3: + if _PY2: reprtraceback = self.repr_traceback(excinfo) reprcrash = excinfo._getreprcrash() @@ -655,7 +656,7 @@ class FormattedExcinfo(object): class TerminalRepr(object): def __str__(self): s = self.__unicode__() - if sys.version_info[0] < 3: + if _PY2: s = s.encode('utf-8') return s @@ -851,7 +852,7 @@ def getrawcode(obj, trycall=True): return obj -if sys.version_info[:2] >= (3, 5): # RecursionError introduced in 3.5 +if PY35: # RecursionError introduced in 3.5 def is_recursion_error(excinfo): return excinfo.errisinstance(RecursionError) # noqa else: diff --git a/_pytest/compat.py b/_pytest/compat.py index c06e3f4ca..25610b645 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -28,6 +28,7 @@ _PY2 = not _PY3 NoneType = type(None) NOTSET = object() +PY35 = sys.version_info[:2] >= (3, 5) PY36 = sys.version_info[:2] >= (3, 6) MODULE_NOT_FOUND_ERROR = 'ModuleNotFoundError' if PY36 else 'ImportError' @@ -250,8 +251,10 @@ else: try: return str(v) except UnicodeError: + if not isinstance(v, unicode): + v = unicode(v) errors = 'replace' - return v.encode('ascii', errors) + return v.encode('utf-8', errors) COLLECT_FAKEMODULE_ATTRIBUTES = ( diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 2c7c9e771..066d7e86a 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -826,8 +826,8 @@ class FixtureFunctionMarker(object): def fixture(scope="function", params=None, autouse=False, ids=None, name=None): """ (return a) decorator to mark a fixture factory function. - This decorator can be used (with or or without parameters) to define - a fixture function. The name of the fixture function can later be + This decorator can be used (with or without parameters) to define a + fixture function. The name of the fixture function can later be referenced to cause its invocation ahead of running tests: test modules or classes can use the pytest.mark.usefixtures(fixturename) marker. Test functions can directly use fixture names as input @@ -846,16 +846,16 @@ def fixture(scope="function", params=None, autouse=False, ids=None, name=None): reference is needed to activate the fixture. :arg ids: list of string ids each corresponding to the params - so that they are part of the test id. If no ids are provided - they will be generated automatically from the params. + so that they are part of the test id. If no ids are provided + they will be generated automatically from the params. :arg name: the name of the fixture. This defaults to the name of the - decorated function. If a fixture is used in the same module in - which it is defined, the function name of the fixture will be - shadowed by the function arg that requests the fixture; one way - to resolve this is to name the decorated function - ``fixture_`` and then use - ``@pytest.fixture(name='')``. + decorated function. If a fixture is used in the same module in + which it is defined, the function name of the fixture will be + shadowed by the function arg that requests the fixture; one way + to resolve this is to name the decorated function + ``fixture_`` and then use + ``@pytest.fixture(name='')``. Fixtures can optionally provide their values to test functions using a ``yield`` statement, instead of ``return``. In this case, the code block after the ``yield`` statement is executed diff --git a/_pytest/pytester.py b/_pytest/pytester.py index 0ee0047e0..cc2e5a2ad 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -452,9 +452,10 @@ class Testdir(object): the module is re-imported. """ for name in set(sys.modules).difference(self._savemodulekeys): - # zope.interface (used by twisted-related tests) keeps internal - # state and can't be deleted - if not name.startswith("zope.interface"): + # some zope modules used by twisted-related tests keeps internal + # state and can't be deleted; we had some trouble in the past + # with zope.interface for example + if not name.startswith("zope"): del sys.modules[name] def make_hook_recorder(self, pluginmanager): diff --git a/_pytest/python.py b/_pytest/python.py index 1f540c84f..06f74ce4b 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -4,6 +4,7 @@ from __future__ import absolute_import, division, print_function import fnmatch import inspect import sys +import os import collections import math from itertools import count @@ -20,7 +21,7 @@ from _pytest.compat import ( isclass, isfunction, is_generator, _escape_strings, REGEX_TYPE, STRING_TYPES, NoneType, NOTSET, get_real_func, getfslineno, safe_getattr, - getlocation, enum, + safe_str, getlocation, enum, ) from _pytest.runner import fail @@ -223,8 +224,7 @@ class PyobjMixin(PyobjContext): continue name = node.name if isinstance(node, Module): - assert name.endswith(".py") - name = name[:-3] + name = os.path.splitext(name)[0] if stopatmodule: if includemodule: parts.append(name) @@ -427,7 +427,7 @@ class Module(main.File, PyCollector): if self.config.getoption('verbose') < 2: exc_info.traceback = exc_info.traceback.filter(filter_traceback) exc_repr = exc_info.getrepr(style='short') if exc_info.traceback else exc_info.exconly() - formatted_tb = py._builtin._totext(exc_repr) + formatted_tb = safe_str(exc_repr) raise self.CollectError( "ImportError while importing test module '{fspath}'.\n" "Hint: make sure your test modules/packages have valid Python names.\n" @@ -843,7 +843,11 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): for callspec in self._calls or [CallSpec2(self)]: elements = zip(ids, parameters, count()) for a_id, param, param_index in elements: - assert len(param.values) == len(argnames) + if len(param.values) != len(argnames): + raise ValueError( + 'In "parametrize" the number of values ({0}) must be ' + 'equal to the number of names ({1})'.format( + param.values, argnames)) newcallspec = callspec.copy(self) newcallspec.setmulti(valtypes, argnames, param.values, a_id, param.deprecated_arg_dict, scopenum, param_index) diff --git a/doc/en/assert.rst b/doc/en/assert.rst index ba078c4c4..26090247c 100644 --- a/doc/en/assert.rst +++ b/doc/en/assert.rst @@ -270,12 +270,21 @@ supporting modules which are not themselves test modules will not be rewritten. .. note:: - ``pytest`` rewrites test modules on import. It does this by using an import - hook to write new pyc files. Most of the time this works transparently. + ``pytest`` rewrites test modules on import by using an import + hook to write new ``pyc`` files. Most of the time this works transparently. However, if you are messing with import yourself, the import hook may - interfere. If this is the case, use ``--assert=plain``. Additionally, - rewriting will fail silently if it cannot write new pycs, i.e. in a read-only - filesystem or a zipfile. + interfere. + + If this is the case you have two options: + + * Disable rewriting for a specific module by adding the string + ``PYTEST_DONT_REWRITE`` to its docstring. + + * Disable rewriting for all modules by using ``--assert=plain``. + + Additionally, rewriting will fail silently if it cannot write new ``.pyc`` files, + i.e. in a read-only filesystem or a zipfile. + For further information, Benjamin Peterson wrote up `Behind the scenes of pytest's new assertion rewriting `_. diff --git a/doc/en/contents.rst b/doc/en/contents.rst index f6a561839..9f4a9a1be 100644 --- a/doc/en/contents.rst +++ b/doc/en/contents.rst @@ -12,6 +12,7 @@ Full pytest documentation getting-started usage + existingtestsuite assert builtin fixture diff --git a/doc/en/customize.rst b/doc/en/customize.rst index 4421889db..ce0a36c11 100644 --- a/doc/en/customize.rst +++ b/doc/en/customize.rst @@ -45,11 +45,11 @@ Here is the algorithm which finds the rootdir from ``args``: matched, it becomes the ini-file and its directory becomes the rootdir. - if no ini-file was found, use the already determined common ancestor as root - directory. This allows to work with pytest in structures that are not part of + directory. This allows the use of pytest in structures that are not part of a package and don't have any particular ini-file configuration. If no ``args`` are given, pytest collects test below the current working -directory and also starts determining the rootdir from there. +directory and also starts determining the rootdir from there. :warning: custom pytest plugin commandline arguments may include a path, as in ``pytest --log-output ../../test.log args``. Then ``args`` is mandatory, @@ -97,6 +97,8 @@ check for ini-files as follows:: .. _`how to change command line options defaults`: .. _`adding default options`: + + How to change command line options defaults ------------------------------------------------ diff --git a/doc/en/existingtestsuite.rst b/doc/en/existingtestsuite.rst new file mode 100644 index 000000000..d304b30c9 --- /dev/null +++ b/doc/en/existingtestsuite.rst @@ -0,0 +1,34 @@ +.. _existingtestsuite: + +Using pytest with an existing test suite +=========================================== + +Pytest can be used with most existing test suites, but its +behavior differs from other test runners such as :ref:`nose ` or +Python's default unittest framework. + +Before using this section you will want to :ref:`install pytest `. + +Running an existing test suite with pytest +--------------------------------------------- + +Say you want to contribute to an existing repository somewhere. +After pulling the code into your development space using some +flavor of version control and (optionally) setting up a virtualenv +you will want to run:: + + cd + pip install -e . # Environment dependent alternatives include + # 'python setup.py develop' and 'conda develop' + +in your project root. This will set up a symlink to your code in +site-packages, allowing you to edit your code while your tests +run against it as if it were installed. + +Setting up your project in development mode lets you avoid having to +reinstall every time you want to run your tests, and is less brittle than +mucking about with sys.path to point your tests at local code. + +Also consider using :ref:`tox `. + +.. include:: links.inc diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 98a0604f2..04b908522 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -253,7 +253,7 @@ the code after the *yield* statement serves as the teardown code: import pytest @pytest.fixture(scope="module") - def smtp(request): + def smtp(): smtp = smtplib.SMTP("smtp.gmail.com") yield smtp # provide the fixture value print("teardown smtp") @@ -287,7 +287,7 @@ Note that we can also seamlessly use the ``yield`` syntax with ``with`` statemen import pytest @pytest.fixture(scope="module") - def smtp(request): + def smtp(): with smtplib.SMTP("smtp.gmail.com") as smtp: yield smtp # provide the fixture value diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 21ee0fee0..153d9b2e1 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -49,17 +49,17 @@ That's it. You can execute the test function now:: platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items - + test_sample.py F - + ======= FAILURES ======== _______ test_answer ________ - + def test_answer(): > assert func(3) == 5 E assert 4 == 5 E + where 4 = func(3) - + test_sample.py:5: AssertionError ======= 1 failed in 0.12 seconds ======== @@ -128,15 +128,15 @@ run the module by passing its filename:: .F ======= FAILURES ======== _______ TestClass.test_two ________ - + self = - + def test_two(self): x = "hello" > assert hasattr(x, 'check') E AssertionError: assert False E + where False = hasattr('hello', 'check') - + test_class.py:8: AssertionError 1 failed, 1 passed in 0.12 seconds @@ -165,14 +165,14 @@ before performing the test function call. Let's just run it:: F ======= FAILURES ======== _______ test_needsfiles ________ - + tmpdir = local('PYTEST_TMPDIR/test_needsfiles0') - + def test_needsfiles(tmpdir): print (tmpdir) > assert 0 E assert 0 - + test_tmpdir.py:3: AssertionError --------------------------- Captured stdout call --------------------------- PYTEST_TMPDIR/test_needsfiles0 @@ -192,6 +192,7 @@ Here are a few suggestions where to go next: * :ref:`cmdline` for command line invocation examples * :ref:`good practices ` for virtualenv, test layout +* :ref:`existingtestsuite` for working with pre-existing tests * :ref:`fixtures` for providing a functional baseline to your tests * :ref:`plugins` managing and writing plugins diff --git a/doc/en/goodpractices.rst b/doc/en/goodpractices.rst index 48f80a9e2..92cd9ed81 100644 --- a/doc/en/goodpractices.rst +++ b/doc/en/goodpractices.rst @@ -30,68 +30,106 @@ Within Python modules, ``pytest`` also discovers tests using the standard Choosing a test layout / import rules ------------------------------------------- +------------------------------------- ``pytest`` supports two common test layouts: -* putting tests into an extra directory outside your actual application - code, useful if you have many functional tests or for other reasons - want to keep tests separate from actual application code (often a good - idea):: +Tests outside application code +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - setup.py # your setuptools Python package metadata +Putting tests into an extra directory outside your actual application code +might be useful if you have many functional tests or for other reasons want +to keep tests separate from actual application code (often a good idea):: + + setup.py mypkg/ __init__.py - appmodule.py + app.py + view.py tests/ test_app.py + test_view.py ... +This way your tests can run easily against an installed version +of ``mypkg``. -* inlining test directories into your application package, useful if you - have direct relation between (unit-)test and application modules and - want to distribute your tests along with your application:: +Note that using this scheme your test files must have **unique names**, because +``pytest`` will import them as *top-level* modules since there are no packages +to derive a full package name from. In other words, the test files in the example above will +be imported as ``test_app`` and ``test_view`` top-level modules by adding ``tests/`` to +``sys.path``. - setup.py # your setuptools Python package metadata +If you need to have test modules with the same name, you might add ``__init__.py`` files to your +``tests`` folder and subfolders, changing them to packages:: + + setup.py + mypkg/ + ... + tests/ + __init__.py + foo/ + __init__.py + test_view.py + bar/ + __init__.py + test_view.py + +Now pytest will load the modules as ``tests.foo.test_view`` and ``tests.bar.test_view``, allowing +you to have modules with the same name. But now this introduces a subtle problem: in order to load +the test modules from the ``tests`` directory, pytest prepends the root of the repository to +``sys.path``, which adds the side-effect that now ``mypkg`` is also importable. +This is problematic if you are using a tool like `tox`_ to test your package in a virtual environment, +because you want to test the *installed* version of your package, not the local code from the repository. + +In this situation, it is **strongly** suggested to use a ``src`` layout where application root package resides in a +sub-directory of your root:: + + setup.py + src/ + mypkg/ + __init__.py + app.py + view.py + tests/ + __init__.py + foo/ + __init__.py + test_view.py + bar/ + __init__.py + test_view.py + + +This layout prevents a lot of common pitfalls and has many benefits, which are better explained in this excellent +`blog post by Ionel Cristian Mărieș `_. + +Tests as part of application code +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Inlining test directories into your application package +is useful if you have direct relation between tests and application modules and +want to distribute them along with your application:: + + setup.py mypkg/ __init__.py - appmodule.py - ... + app.py + view.py test/ + __init__.py test_app.py + test_view.py ... -Important notes relating to both schemes: +In this scheme, it is easy to your run tests using the ``--pyargs`` option:: -- **make sure that "mypkg" is importable**, for example by typing once:: + pytest --pyargs mypkg - pip install -e . # install package using setup.py in editable mode +``pytest`` will discover where ``mypkg`` is installed and collect tests from there. -- **avoid "__init__.py" files in your test directories**. - This way your tests can run easily against an installed version - of ``mypkg``, independently from the installed package if it contains - the tests or not. +Note that this layout also works in conjunction with the ``src`` layout mentioned in the previous section. -- With inlined tests you might put ``__init__.py`` into test - directories and make them installable as part of your application. - Using the ``pytest --pyargs mypkg`` invocation pytest will - discover where mypkg is installed and collect tests from there. - With the "external" test you can still distribute tests but they - will not be installed or become importable. - -Typically you can run tests by pointing to test directories or modules:: - - pytest tests/test_app.py # for external test dirs - pytest mypkg/test/test_app.py # for inlined test dirs - pytest mypkg # run tests in all below test directories - pytest # run all tests below current dir - ... - -Because of the above ``editable install`` mode you can change your -source code (both tests and the app) and rerun tests at will. -Once you are done with your work, you can `use tox`_ to make sure -that the package is really correct and tests pass in all -required configurations. .. note:: @@ -144,7 +182,15 @@ for installing your application and any dependencies as well as the ``pytest`` package itself. This ensures your code and dependencies are isolated from the system Python installation. -If you frequently release code and want to make sure that your actual +You can then install your package in "editable" mode:: + + pip install -e . + +which lets you change your source code (both tests and application) and rerun tests at will. +This is similar to running `python setup.py develop` or `conda develop` in that it installs +your package using a symlink to your development code. + +Once you are done with your work and want to make sure that your actual package passes all tests you may want to look into `tox`_, the virtualenv test automation tool and its `pytest support `_. @@ -154,11 +200,6 @@ options. It will run tests against the installed package and not against your source code checkout, helping to detect packaging glitches. -Continuous integration services such as Jenkins_ can make use of the -``--junitxml=PATH`` option to create a JUnitXML file and generate reports (e.g. -by publishing the results in a nice format with the `Jenkins xUnit Plugin -`_). - Integrating with setuptools / ``python setup.py test`` / ``pytest-runner`` -------------------------------------------------------------------------- diff --git a/doc/en/nose.rst b/doc/en/nose.rst index a785ecfaa..5effd0d7b 100644 --- a/doc/en/nose.rst +++ b/doc/en/nose.rst @@ -47,9 +47,19 @@ Unsupported idioms / known issues ``tests.test_mod``) but different file system paths (e.g. ``tests/test_mode.py`` and ``other/tests/test_mode.py``) by extending sys.path/import semantics. pytest does not do that - but there is discussion in `issue268 `_ for adding some support. Note that + but there is discussion in `#268 `_ for adding some support. Note that `nose2 choose to avoid this sys.path/import hackery `_. + If you place a conftest.py file in the root directory of your project + (as determined by pytest) pytest will run tests "nose style" against + the code below that directory by adding it to your ``sys.path`` instead of + running against your installed code. + + You may find yourself wanting to do this if you ran ``python setup.py install`` + to set up your project, as opposed to ``python setup.py develop`` or any of + the package manager equivalents. Installing with develop in a + virtual environment like Tox is recommended over this pattern. + - nose-style doctests are not collected and executed correctly, also doctest fixtures don't work. @@ -62,3 +72,4 @@ Unsupported idioms / known issues being the recommended alternative. + diff --git a/doc/en/requirements.txt b/doc/en/requirements.txt new file mode 100644 index 000000000..72bb60a81 --- /dev/null +++ b/doc/en/requirements.txt @@ -0,0 +1,3 @@ +# pinning sphinx to 1.4.* due to search issues with rtd: +# https://github.com/rtfd/readthedocs-sphinx-ext/issues/25 +sphinx ==1.4.* diff --git a/doc/en/usage.rst b/doc/en/usage.rst index e87c56ac3..f6c20a978 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -19,6 +19,18 @@ You can invoke testing through the Python interpreter from the command line:: This is almost equivalent to invoking the command line script ``pytest [...]`` directly, except that python will also add the current directory to ``sys.path``. +Possible exit codes +-------------------------------------------------------------- + +Running ``pytest`` can result in six different exit codes: + +:Exit code 0: All tests were collected and passed successfully +:Exit code 1: Tests were collected and run but some of the tests failed +:Exit code 2: Test execution was interrupted by the user +:Exit code 3: Internal error happened while executing tests +:Exit code 4: pytest command line usage error +:Exit code 5: No tests were collected + Getting help on version, option names, environment variables -------------------------------------------------------------- diff --git a/testing/python/collect.py b/testing/python/collect.py index 30beb5715..236421f1c 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -104,6 +104,22 @@ class TestModule(object): else: assert name not in stdout + def test_show_traceback_import_error_unicode(self, testdir): + """Check test modules collected which raise ImportError with unicode messages + are handled properly (#2336). + """ + testdir.makepyfile(u""" + # -*- coding: utf-8 -*- + raise ImportError(u'Something bad happened ☺') + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + "ImportError while importing test module*", + "Traceback:", + "*raise ImportError*Something bad happened*", + ]) + assert result.ret == 2 + class TestClass(object): def test_class_with_init_warning(self, testdir): @@ -826,6 +842,34 @@ class TestConftestCustomization(object): l = modcol.collect() assert '_hello' not in l + def test_issue2369_collect_module_fileext(self, testdir): + """Ensure we can collect files with weird file extensions as Python + modules (#2369)""" + # We'll implement a little finder and loader to import files containing + # Python source code whose file extension is ".narf". + testdir.makeconftest(""" + import sys, os, imp + from _pytest.python import Module + + class Loader: + def load_module(self, name): + return imp.load_source(name, name + ".narf") + class Finder: + def find_module(self, name, path=None): + if os.path.exists(name + ".narf"): + return Loader() + sys.meta_path.append(Finder()) + + def pytest_collect_file(path, parent): + if path.ext == ".narf": + return Module(path, parent)""") + testdir.makefile(".narf", """ + def test_something(): + assert 1 + 1 == 2""") + # Use runpytest_subprocess, since we're futzing with sys.meta_path. + result = testdir.runpytest_subprocess() + result.stdout.fnmatch_lines('*1 passed*') + def test_setup_only_available_in_subdir(testdir): sub1 = testdir.mkpydir("sub1") sub2 = testdir.mkpydir("sub2") diff --git a/testing/test_capture.py b/testing/test_capture.py index aa2a3bae5..8f6f2ccb2 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -5,6 +5,7 @@ from __future__ import with_statement import pickle import os import sys +from io import UnsupportedOperation import _pytest._code import py @@ -671,7 +672,7 @@ def test_dontreadfrominput(): pytest.raises(IOError, f.read) pytest.raises(IOError, f.readlines) pytest.raises(IOError, iter, f) - pytest.raises(ValueError, f.fileno) + pytest.raises(UnsupportedOperation, f.fileno) f.close() # just for completeness diff --git a/testing/test_mark.py b/testing/test_mark.py index 9a8c35bf3..0792b04fd 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -305,6 +305,23 @@ def test_parametrized_collected_from_command_line(testdir): rec.assertoutcome(passed=3) +def test_parametrized_collect_with_wrong_args(testdir): + """Test collect parametrized func with wrong number of args.""" + py_file = testdir.makepyfile(""" + import pytest + + @pytest.mark.parametrize('foo, bar', [(1, 2, 3)]) + def test_func(foo, bar): + pass + """) + + result = testdir.runpytest(py_file) + result.stdout.fnmatch_lines([ + 'E ValueError: In "parametrize" the number of values ((1, 2, 3)) ' + 'must be equal to the number of names ([\'foo\', \'bar\'])' + ]) + + class TestFunctional(object): def test_mark_per_function(self, testdir): diff --git a/tox.ini b/tox.ini index d46e468c8..52bd471c4 100644 --- a/tox.ini +++ b/tox.ini @@ -109,6 +109,8 @@ commands= pytest -ra {posargs:testing/test_unittest.py} [testenv:docs] +skipsdist=True +usedevelop=True basepython=python changedir=doc/en deps=