Merge remote-tracking branch 'upstream/master' into merge-master-into-features

# Conflicts:
#	_pytest/capture.py
#	_pytest/compat.py
#	_pytest/python.py
#	testing/python/collect.py
#	testing/test_mark.py
This commit is contained in:
Bruno Oliveira 2017-05-03 19:04:53 -03:00
commit f3b359f5b8
23 changed files with 327 additions and 99 deletions

View File

@ -31,7 +31,7 @@ env:
matrix:
include:
- env: TOXENV=py36
python: '3.6-dev'
python: '3.6'
- env: TOXENV=py37
python: 'nightly'
allow_failures:

View File

@ -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

View File

@ -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)
==================

View File

@ -7,12 +7,19 @@
.. image:: https://img.shields.io/pypi/v/pytest.svg
: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
.. image:: https://img.shields.io/coveralls/pytest-dev/pytest/master.svg
: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

View File

@ -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:

View File

@ -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 = (

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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 <http://pybites.blogspot.com/2011/07/behind-scenes-of-pytests-new-assertion.html>`_.

View File

@ -12,6 +12,7 @@ Full pytest documentation
getting-started
usage
existingtestsuite
assert
builtin
fixture

View File

@ -45,7 +45,7 @@ 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
@ -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
------------------------------------------------

View File

@ -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 <noseintegration>` or
Python's default unittest framework.
Before using this section you will want to :ref:`install pytest <getstarted>`.
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 <repository>
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 <use tox>`.
.. include:: links.inc

View File

@ -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

View File

@ -192,6 +192,7 @@ Here are a few suggestions where to go next:
* :ref:`cmdline` for command line invocation examples
* :ref:`good practices <goodpractices>` 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

View File

@ -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
appmodule.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ș <https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure>`_.
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
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
<https://tox.readthedocs.io/en/latest/example/pytest.html>`_.
@ -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
<https://wiki.jenkins-ci.org/display/JENKINS/xUnit+Plugin>`_).
Integrating with setuptools / ``python setup.py test`` / ``pytest-runner``
--------------------------------------------------------------------------

View File

@ -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 <https://github.com/pytest-dev/pytest/issues/268>`_ for adding some support. Note that
but there is discussion in `#268 <https://github.com/pytest-dev/pytest/issues/268>`_ for adding some support. Note that
`nose2 choose to avoid this sys.path/import hackery <https://nose2.readthedocs.io/en/latest/differences.html#test-discovery-and-loading>`_.
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.

3
doc/en/requirements.txt Normal file
View File

@ -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.*

View File

@ -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
--------------------------------------------------------------

View File

@ -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")

View File

@ -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

View File

@ -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):

View File

@ -109,6 +109,8 @@ commands=
pytest -ra {posargs:testing/test_unittest.py}
[testenv:docs]
skipsdist=True
usedevelop=True
basepython=python
changedir=doc/en
deps=