merge master into features

This commit is contained in:
Ronny Pfannschmidt 2016-06-22 14:39:33 +02:00
commit 083f64100d
18 changed files with 376 additions and 131 deletions

24
AUTHORS
View File

@ -5,6 +5,7 @@ Contributors include::
Abdeali JK Abdeali JK
Abhijeet Kasurde Abhijeet Kasurde
Alexei Kozlenok
Anatoly Bubenkoff Anatoly Bubenkoff
Andreas Zeidler Andreas Zeidler
Andy Freeland Andy Freeland
@ -12,12 +13,14 @@ Anthon van der Neut
Armin Rigo Armin Rigo
Aron Curzon Aron Curzon
Aviv Palivoda Aviv Palivoda
Ben Webb
Benjamin Peterson Benjamin Peterson
Bob Ippolito Bob Ippolito
Brian Dorsey Brian Dorsey
Brian Okken Brian Okken
Brianna Laugher Brianna Laugher
Bruno Oliveira Bruno Oliveira
Cal Leeming
Carl Friedrich Bolz Carl Friedrich Bolz
Charles Cloud Charles Cloud
Chris Lamb Chris Lamb
@ -28,20 +31,24 @@ Daniel Grana
Daniel Hahler Daniel Hahler
Daniel Nuri Daniel Nuri
Dave Hunt Dave Hunt
David Díaz-Barquero
David Mohr David Mohr
David Vierra David Vierra
Edison Gustavo Muenz Edison Gustavo Muenz
Eduardo Schettino Eduardo Schettino
Endre Galaczi
Elizaveta Shashkova Elizaveta Shashkova
Endre Galaczi
Eric Hunsberger
Eric Hunsberger Eric Hunsberger
Eric Siegerman Eric Siegerman
Erik M. Bray Erik M. Bray
Feng Ma
Florian Bruhin Florian Bruhin
Floris Bruynooghe Floris Bruynooghe
Gabriel Reis Gabriel Reis
Georgy Dyuldin Georgy Dyuldin
Graham Horler Graham Horler
Greg Price
Grig Gheorghiu Grig Gheorghiu
Guido Wesdorp Guido Wesdorp
Harald Armin Massa Harald Armin Massa
@ -65,6 +72,7 @@ Mark Abramowitz
Markus Unterwaditzer Markus Unterwaditzer
Martijn Faassen Martijn Faassen
Martin Prusse Martin Prusse
Martin K. Scherer
Matt Bachmann Matt Bachmann
Matt Williams Matt Williams
Michael Aquilina Michael Aquilina
@ -84,18 +92,16 @@ Raphael Pierzina
Roman Bolshakov Roman Bolshakov
Ronny Pfannschmidt Ronny Pfannschmidt
Ross Lawley Ross Lawley
Russel Winder
Ryan Wooden Ryan Wooden
Samuele Pedroni Samuele Pedroni
Stephan Obermann Stephan Obermann
Tareq Alayan Tareq Alayan
Simon Gomizelj
Stefano Taschini
Stefan Farmbauer
Thomas Grainger
Tom Viner Tom Viner
Trevor Bekolay Trevor Bekolay
Wouter van Ackooy Wouter van Ackooy
David Díaz-Barquero Bernard Pratz
Eric Hunsberger
Simon Gomizelj
Russel Winder
Ben Webb
Alexei Kozlenok
Cal Leeming
Feng Ma

View File

@ -82,15 +82,40 @@
was only available for test modules. Thanks `@flub`_, `@sober7`_ and was only available for test modules. Thanks `@flub`_, `@sober7`_ and
`@nicoddemus`_ for the PR (`#1619`_). `@nicoddemus`_ for the PR (`#1619`_).
* * Text documents without any doctests no longer appear as "skipped".
Thanks `@graingert`_ for reporting and providing a full PR (`#1580`_).
* * Fix internal error issue when ``method`` argument is missing for
``teardown_method()``. Fixes (`#1605`_).
* Fix exception visualization in case the current working directory (CWD) gets
deleted during testing. Fixes (`#1235`). Thanks `@bukzor`_ for reporting. PR by
`@marscher`. Thanks `@nicoddemus`_ for his help.
* Ensure that a module within a namespace package can be found when it
is specified on the command line together with the ``--pyargs``
option. Thanks to `@taschini`_ for the PR (`#1597`_).
* Raise helpful failure message, when requesting parametrized fixture at runtime,
e.g. with ``request.getfuncargvalue``. BACKWARD INCOMPAT: Previously these params
were simply never defined. So a fixture decorated like ``@pytest.fixture(params=[0, 1, 2])``
only ran once. Now a failure is raised. Fixes (`#460`_). Thanks to
`@nikratio`_ for bug report, `@RedBeardCode`_ and `@tomviner`_ for PR.
.. _#1580: https://github.com/pytest-dev/pytest/pull/1580
.. _#1605: https://github.com/pytest-dev/pytest/issues/1605
.. _#1597: https://github.com/pytest-dev/pytest/pull/1597
.. _#460: https://github.com/pytest-dev/pytest/pull/460
.. _@graingert: https://github.com/graingert
.. _@taschini: https://github.com/taschini
.. _@nikratio: https://github.com/nikratio
.. _@RedBeardCode: https://github.com/RedBeardCode
* Fix `#1421`_: Exit tests if a collection error occurs and add * Fix `#1421`_: Exit tests if a collection error occurs and add
``--continue-on-collection-errors`` option to restore previous behaviour. ``--continue-on-collection-errors`` option to restore previous behaviour.
Thanks `@olegpidsadnyi`_ and `@omarkohl`_ for the complete PR (`#1628`_). Thanks `@olegpidsadnyi`_ and `@omarkohl`_ for the complete PR (`#1628`_).
*
* *
@ -294,7 +319,7 @@
Thanks `@biern`_ for the PR. Thanks `@biern`_ for the PR.
* Fix `traceback style docs`_ to describe all of the available options * Fix `traceback style docs`_ to describe all of the available options
(auto/long/short/line/native/no), with `auto` being the default since v2.6. (auto/long/short/line/native/no), with ``auto`` being the default since v2.6.
Thanks `@hackebrot`_ for the PR. Thanks `@hackebrot`_ for the PR.
* Fix (`#1422`_): junit record_xml_property doesn't allow multiple records * Fix (`#1422`_): junit record_xml_property doesn't allow multiple records

View File

@ -48,7 +48,7 @@ to fix the bug yet.
Fix bugs Fix bugs
-------- --------
Look through the GitHub issues for bugs. Here is sample filter you can use: Look through the GitHub issues for bugs. Here is a filter you can use:
https://github.com/pytest-dev/pytest/labels/bug https://github.com/pytest-dev/pytest/labels/bug
:ref:`Talk <contact>` to developers to find out how you can fix specific bugs. :ref:`Talk <contact>` to developers to find out how you can fix specific bugs.
@ -60,8 +60,7 @@ Don't forget to check the issue trackers of your favourite plugins, too!
Implement features Implement features
------------------ ------------------
Look through the GitHub issues for enhancements. Here is sample filter you Look through the GitHub issues for enhancements. Here is a filter you can use:
can use:
https://github.com/pytest-dev/pytest/labels/enhancement https://github.com/pytest-dev/pytest/labels/enhancement
:ref:`Talk <contact>` to developers to find out how you can implement specific :ref:`Talk <contact>` to developers to find out how you can implement specific
@ -70,16 +69,15 @@ features.
Write documentation Write documentation
------------------- -------------------
pytest could always use more documentation. What exactly is needed? Pytest could always use more documentation. What exactly is needed?
* More complementary documentation. Have you perhaps found something unclear? * More complementary documentation. Have you perhaps found something unclear?
* Documentation translations. We currently have only English. * Documentation translations. We currently have only English.
* Docstrings. There can never be too many of them. * Docstrings. There can never be too many of them.
* Blog posts, articles and such -- they're all very appreciated. * Blog posts, articles and such -- they're all very appreciated.
You can also edit documentation files directly in the Github web interface You can also edit documentation files directly in the GitHub web interface,
without needing to make a fork and local copy. This can be convenient for without using a local copy. This can be convenient for small fixes.
small fixes.
.. _submitplugin: .. _submitplugin:
@ -95,13 +93,14 @@ in repositories living under the ``pytest-dev`` organisations:
- `pytest-dev on Bitbucket <https://bitbucket.org/pytest-dev>`_ - `pytest-dev on Bitbucket <https://bitbucket.org/pytest-dev>`_
All pytest-dev Contributors team members have write access to all contained All pytest-dev Contributors team members have write access to all contained
repositories. pytest core and plugins are generally developed repositories. Pytest core and plugins are generally developed
using `pull requests`_ to respective repositories. using `pull requests`_ to respective repositories.
The objectives of the ``pytest-dev`` organisation are: The objectives of the ``pytest-dev`` organisation are:
* Having a central location for popular pytest plugins * Having a central location for popular pytest plugins
* Sharing some of the maintenance responsibility (in case a maintainer no longer whishes to maintain a plugin) * Sharing some of the maintenance responsibility (in case a maintainer no
longer wishes to maintain a plugin)
You can submit your plugin by subscribing to the `pytest-dev mail list You can submit your plugin by subscribing to the `pytest-dev mail list
<https://mail.python.org/mailman/listinfo/pytest-dev>`_ and writing a <https://mail.python.org/mailman/listinfo/pytest-dev>`_ and writing a
@ -127,27 +126,18 @@ transferred to the ``pytest-dev`` organisation.
Here's a rundown of how a repository transfer usually proceeds Here's a rundown of how a repository transfer usually proceeds
(using a repository named ``joedoe/pytest-xyz`` as example): (using a repository named ``joedoe/pytest-xyz`` as example):
* One of the ``pytest-dev`` administrators creates: * ``joedoe`` transfers repository ownership to ``pytest-dev`` administrator ``calvin``.
* ``calvin`` creates ``pytest-xyz-admin`` and ``pytest-xyz-developers`` teams, inviting ``joedoe`` to both as **maintainer**.
- ``pytest-xyz-admin`` team, with full administration rights to * ``calvin`` transfers repository to ``pytest-dev`` and configures team access:
``pytest-dev/pytest-xyz``.
- ``pytest-xyz-developers`` team, with write access to - ``pytest-xyz-admin`` **admin** access;
``pytest-dev/pytest-xyz``. - ``pytest-xyz-developers`` **write** access;
* ``joedoe`` is invited to the ``pytest-xyz-admin`` team;
* After accepting the invitation, ``joedoe`` transfers the repository from its
original location to ``pytest-dev/pytest-xyz`` (A nice feature is that GitHub handles URL redirection from
the old to the new location automatically).
* ``joedoe`` is free to add any other collaborators to the
``pytest-xyz-admin`` or ``pytest-xyz-developers`` team as desired.
The ``pytest-dev/Contributors`` team has write access to all projects, and The ``pytest-dev/Contributors`` team has write access to all projects, and
every project administrator is in it. We recommend that each plugin has at least three every project administrator is in it. We recommend that each plugin has at least three
people who have the right to release to PyPI. people who have the right to release to PyPI.
Repository owners can be assured that no ``pytest-dev`` administrator will ever make Repository owners can rest assured that no ``pytest-dev`` administrator will ever make
releases of your repository or take ownership in any way, except in rare cases releases of your repository or take ownership in any way, except in rare cases
where someone becomes unresponsive after months of contact attempts. where someone becomes unresponsive after months of contact attempts.
As stated, the objective is to share maintenance and avoid "plugin-abandon". As stated, the objective is to share maintenance and avoid "plugin-abandon".
@ -159,15 +149,11 @@ As stated, the objective is to share maintenance and avoid "plugin-abandon".
Preparing Pull Requests on GitHub Preparing Pull Requests on GitHub
--------------------------------- ---------------------------------
There's an excellent tutorial on how Pull Requests work in the
`GitHub Help Center <https://help.github.com/articles/using-pull-requests/>`_
.. note:: .. note::
What is a "pull request"? It informs project's core developers about the What is a "pull request"? It informs project's core developers about the
changes you want to review and merge. Pull requests are stored on changes you want to review and merge. Pull requests are stored on
`GitHub servers <https://github.com/pytest-dev/pytest/pulls>`_. `GitHub servers <https://github.com/pytest-dev/pytest/pulls>`_.
Once you send pull request, we can discuss it's potential modifications and Once you send a pull request, we can discuss its potential modifications and
even add more commits to it later on. even add more commits to it later on.
There's an excellent tutorial on how Pull Requests work in the There's an excellent tutorial on how Pull Requests work in the
@ -216,19 +202,19 @@ but here is a simple overview:
This command will run tests via the "tox" tool against Python 2.7 and 3.5 This command will run tests via the "tox" tool against Python 2.7 and 3.5
and also perform "lint" coding-style checks. ``runtox.py`` is and also perform "lint" coding-style checks. ``runtox.py`` is
a thin wrapper around ``tox`` which installs from a development package a thin wrapper around ``tox`` which installs from a development package
index where newer (not yet released to pypi) versions of dependencies index where newer (not yet released to PyPI) versions of dependencies
(especially ``py``) might be present. (especially ``py``) might be present.
#. You can now edit your local working copy. #. You can now edit your local working copy.
You can now make the changes you want and run the tests again as necessary. You can now make the changes you want and run the tests again as necessary.
To run tests on py27 and pass options to pytest (e.g. enter pdb on failure) To run tests on Python 2.7 and pass options to pytest (e.g. enter pdb on
to pytest you can do:: failure) to pytest you can do::
$ python3 runtox.py -e py27 -- --pdb $ python3 runtox.py -e py27 -- --pdb
or to only run tests in a particular test module on py35:: Or to only run tests in a particular test module on Python 3.5::
$ python3 runtox.py -e py35 -- testing/test_config.py $ python3 runtox.py -e py35 -- testing/test_config.py
@ -237,9 +223,9 @@ but here is a simple overview:
$ git commit -a -m "<commit message>" $ git commit -a -m "<commit message>"
$ git push -u $ git push -u
Make sure you add a CHANGELOG message, and add yourself to AUTHORS. If you Make sure you add a message to ``CHANGELOG.rst`` and add yourself to
are unsure about either of these steps, submit your pull request and we'll ``AUTHORS``. If you are unsure about either of these steps, submit your
help you fix it up. pull request and we'll help you fix it up.
#. Finally, submit a pull request through the GitHub website using this data:: #. Finally, submit a pull request through the GitHub website using this data::
@ -248,6 +234,6 @@ but here is a simple overview:
base-fork: pytest-dev/pytest base-fork: pytest-dev/pytest
base: master # if it's a bugfix base: master # if it's a bugfix
base: feature # if it's a feature base: features # if it's a feature

View File

@ -1,3 +1,2 @@
# #
__version__ = '2.10.0.dev1' __version__ = '2.10.0.dev1'

View File

@ -3,7 +3,6 @@ from inspect import CO_VARARGS, CO_VARKEYWORDS
import re import re
import py import py
builtin_repr = repr builtin_repr = repr
reprlib = py.builtin._tryimport('repr', 'reprlib') reprlib = py.builtin._tryimport('repr', 'reprlib')
@ -36,12 +35,16 @@ class Code(object):
def path(self): def path(self):
""" return a path object pointing to source code (note that it """ return a path object pointing to source code (note that it
might not point to an actually existing file). """ might not point to an actually existing file). """
p = py.path.local(self.raw.co_filename) try:
# maybe don't try this checking p = py.path.local(self.raw.co_filename)
if not p.check(): # maybe don't try this checking
if not p.check():
raise OSError("py.path check failed.")
except OSError:
# XXX maybe try harder like the weird logic # XXX maybe try harder like the weird logic
# in the standard lib [linecache.updatecache] does? # in the standard lib [linecache.updatecache] does?
p = self.raw.co_filename p = self.raw.co_filename
return p return p
@property @property

View File

@ -146,23 +146,19 @@ def get_optionflags(parent):
return flag_acc return flag_acc
class DoctestTextfile(DoctestItem, pytest.Module): class DoctestTextfile(pytest.Module):
obj = None
def runtest(self): def collect(self):
import doctest import doctest
fixture_request = _setup_fixtures(self)
# inspired by doctest.testfile; ideally we would use it directly, # inspired by doctest.testfile; ideally we would use it directly,
# but it doesn't support passing a custom checker # but it doesn't support passing a custom checker
text = self.fspath.read() text = self.fspath.read()
filename = str(self.fspath) filename = str(self.fspath)
name = self.fspath.basename name = self.fspath.basename
globs = dict(getfixture=fixture_request.getfuncargvalue) globs = {'__name__': '__main__'}
if '__name__' not in globs:
globs['__name__'] = '__main__'
for name, value in fixture_request.getfuncargvalue('doctest_namespace').items():
globs[name] = value
optionflags = get_optionflags(self) optionflags = get_optionflags(self)
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags, runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
@ -170,8 +166,8 @@ class DoctestTextfile(DoctestItem, pytest.Module):
parser = doctest.DocTestParser() parser = doctest.DocTestParser()
test = parser.get_doctest(text, globs, name, filename, 0) test = parser.get_doctest(text, globs, name, filename, 0)
_check_all_skipped(test) if test.examples:
runner.run(test) yield DoctestItem(test.name, self, runner, test)
def _check_all_skipped(test): def _check_all_skipped(test):

View File

@ -1,7 +1,5 @@
""" core implementation of testing process: init, session, runtest loop. """ """ core implementation of testing process: init, session, runtest loop. """
import imp
import os import os
import re
import sys import sys
import _pytest import _pytest
@ -25,8 +23,6 @@ EXIT_INTERNALERROR = 3
EXIT_USAGEERROR = 4 EXIT_USAGEERROR = 4
EXIT_NOTESTSCOLLECTED = 5 EXIT_NOTESTSCOLLECTED = 5
name_re = re.compile("^[a-zA-Z_]\w*$")
def pytest_addoption(parser): def pytest_addoption(parser):
parser.addini("norecursedirs", "directory patterns to avoid for recursion", parser.addini("norecursedirs", "directory patterns to avoid for recursion",
type="args", default=['.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg']) type="args", default=['.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg'])
@ -144,17 +140,8 @@ def pytest_runtestloop(session):
if session.config.option.collectonly: if session.config.option.collectonly:
return True return True
def getnextitem(i):
# this is a function to avoid python2
# keeping sys.exc_info set when calling into a test
# python2 keeps sys.exc_info till the frame is left
try:
return session.items[i+1]
except IndexError:
return None
for i, item in enumerate(session.items): for i, item in enumerate(session.items):
nextitem = getnextitem(i) nextitem = session.items[i+1] if i+1 < len(session.items) else None
item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem) item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
if session.shouldstop: if session.shouldstop:
raise session.Interrupted(session.shouldstop) raise session.Interrupted(session.shouldstop)
@ -400,7 +387,10 @@ class Node(object):
if self.config.option.fulltrace: if self.config.option.fulltrace:
style="long" style="long"
else: else:
tb = _pytest._code.Traceback([excinfo.traceback[-1]])
self._prunetraceback(excinfo) self._prunetraceback(excinfo)
if len(excinfo.traceback) == 0:
excinfo.traceback = tb
tbfilter = False # prunetraceback already does it tbfilter = False # prunetraceback already does it
if style == "auto": if style == "auto":
style = "long" style = "long"
@ -411,7 +401,13 @@ class Node(object):
else: else:
style = "long" style = "long"
return excinfo.getrepr(funcargs=True, try:
os.getcwd()
abspath = False
except OSError:
abspath = True
return excinfo.getrepr(funcargs=True, abspath=abspath,
showlocals=self.config.option.showlocals, showlocals=self.config.option.showlocals,
style=style, tbfilter=tbfilter) style=style, tbfilter=tbfilter)
@ -657,36 +653,32 @@ class Session(FSCollector):
return True return True
def _tryconvertpyarg(self, x): def _tryconvertpyarg(self, x):
mod = None """Convert a dotted module name to path.
path = [os.path.abspath('.')] + sys.path
for name in x.split('.'):
# ignore anything that's not a proper name here
# else something like --pyargs will mess up '.'
# since imp.find_module will actually sometimes work for it
# but it's supposed to be considered a filesystem path
# not a package
if name_re.match(name) is None:
return x
try:
fd, mod, type_ = imp.find_module(name, path)
except ImportError:
return x
else:
if fd is not None:
fd.close()
if type_[2] != imp.PKG_DIRECTORY: """
path = [os.path.dirname(mod)] import pkgutil
else: try:
path = [mod] loader = pkgutil.find_loader(x)
return mod except ImportError:
return x
if loader is None:
return x
# This method is sometimes invoked when AssertionRewritingHook, which
# does not define a get_filename method, is already in place:
try:
path = loader.get_filename()
except AttributeError:
# Retrieve path from AssertionRewritingHook:
path = loader.modules[x][0].co_filename
if loader.is_package(x):
path = os.path.dirname(path)
return path
def _parsearg(self, arg): def _parsearg(self, arg):
""" return (fspath, names) tuple after checking the file exists. """ """ return (fspath, names) tuple after checking the file exists. """
arg = str(arg)
if self.config.option.pyargs:
arg = self._tryconvertpyarg(arg)
parts = str(arg).split("::") parts = str(arg).split("::")
if self.config.option.pyargs:
parts[0] = self._tryconvertpyarg(parts[0])
relpath = parts[0].replace("/", os.sep) relpath = parts[0].replace("/", os.sep)
path = self.config.invocation_dir.join(relpath, abs=True) path = self.config.invocation_dir.join(relpath, abs=True)
if not path.check(): if not path.check():

View File

@ -123,15 +123,18 @@ def getexecutable(name, cache={}):
except KeyError: except KeyError:
executable = py.path.local.sysfind(name) executable = py.path.local.sysfind(name)
if executable: if executable:
import subprocess
popen = subprocess.Popen([str(executable), "--version"],
universal_newlines=True, stderr=subprocess.PIPE)
out, err = popen.communicate()
if name == "jython": if name == "jython":
import subprocess
popen = subprocess.Popen([str(executable), "--version"],
universal_newlines=True, stderr=subprocess.PIPE)
out, err = popen.communicate()
if not err or "2.5" not in err: if not err or "2.5" not in err:
executable = None executable = None
if "2.5.2" in err: if "2.5.2" in err:
executable = None # http://bugs.jython.org/issue1790 executable = None # http://bugs.jython.org/issue1790
elif popen.returncode != 0:
# Handle pyenv's 127.
executable = None
cache[name] = executable cache[name] = executable
return executable return executable

View File

@ -2031,6 +2031,25 @@ class FixtureRequest(FuncargnamesCompatAttr):
except (AttributeError, ValueError): except (AttributeError, ValueError):
param = NOTSET param = NOTSET
param_index = 0 param_index = 0
if fixturedef.params is not None:
frame = inspect.stack()[3]
frameinfo = inspect.getframeinfo(frame[0])
source_path = frameinfo.filename
source_lineno = frameinfo.lineno
source_path = py.path.local(source_path)
if source_path.relto(funcitem.config.rootdir):
source_path = source_path.relto(funcitem.config.rootdir)
msg = (
"The requested fixture has no parameter defined for the "
"current test.\n\nRequested fixture '{0}' defined in:\n{1}"
"\n\nRequested here:\n{2}:{3}".format(
fixturedef.argname,
getlocation(fixturedef.func, funcitem.config.rootdir),
source_path,
source_lineno,
)
)
pytest.fail(msg)
else: else:
# indices might not be set if old-style metafunc.addcall() was used # indices might not be set if old-style metafunc.addcall() was used
param_index = funcitem.callspec.indices.get(argname, 0) param_index = funcitem.callspec.indices.get(argname, 0)
@ -2369,7 +2388,7 @@ class FixtureManager:
else: else:
if marker.name: if marker.name:
name = marker.name name = marker.name
assert not name.startswith(self._argprefix) assert not name.startswith(self._argprefix), name
fixturedef = FixtureDef(self, nodeid, name, obj, fixturedef = FixtureDef(self, nodeid, name, obj,
marker.scope, marker.params, marker.scope, marker.params,
unittest=unittest, ids=marker.ids) unittest=unittest, ids=marker.ids)

View File

@ -7,7 +7,7 @@ Release announcements
sprint2016 sprint2016
release-2.9.1 release-2.9.2
release-2.9.1 release-2.9.1
release-2.9.0 release-2.9.0
release-2.8.7 release-2.8.7

View File

@ -295,13 +295,13 @@ which will add the string to the test header accordingly::
You can also return a list of strings which will be considered as several You can also return a list of strings which will be considered as several
lines of information. You can of course also make the amount of reporting lines of information. You can of course also make the amount of reporting
information on e.g. the value of ``config.option.verbose`` so that information on e.g. the value of ``config.getoption('verbose')`` so that
you present more information appropriately:: you present more information appropriately::
# content of conftest.py # content of conftest.py
def pytest_report_header(config): def pytest_report_header(config):
if config.option.verbose > 0: if config.getoption('verbose') > 0:
return ["info1: did you know that ...", "did you?"] return ["info1: did you know that ...", "did you?"]
which will add info only when run with "--v":: which will add info only when run with "--v"::

View File

@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
import os
import sys import sys
import _pytest._code import _pytest._code
@ -513,12 +515,11 @@ class TestInvocationVariants:
path = testdir.mkpydir("tpkg") path = testdir.mkpydir("tpkg")
path.join("test_hello.py").write('raise ImportError') path.join("test_hello.py").write('raise ImportError')
result = testdir.runpytest("--pyargs", "tpkg.test_hello") result = testdir.runpytest_subprocess("--pyargs", "tpkg.test_hello")
assert result.ret != 0 assert result.ret != 0
# FIXME: It would be more natural to match NOT
# "ERROR*file*or*package*not*found*".
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*collected 0 items*" "collected*0*items*/*1*errors"
]) ])
def test_cmdline_python_package(self, testdir, monkeypatch): def test_cmdline_python_package(self, testdir, monkeypatch):
@ -540,7 +541,7 @@ class TestInvocationVariants:
def join_pythonpath(what): def join_pythonpath(what):
cur = py.std.os.environ.get('PYTHONPATH') cur = py.std.os.environ.get('PYTHONPATH')
if cur: if cur:
return str(what) + ':' + cur return str(what) + os.pathsep + cur
return what return what
empty_package = testdir.mkpydir("empty_package") empty_package = testdir.mkpydir("empty_package")
monkeypatch.setenv('PYTHONPATH', join_pythonpath(empty_package)) monkeypatch.setenv('PYTHONPATH', join_pythonpath(empty_package))
@ -551,11 +552,72 @@ class TestInvocationVariants:
]) ])
monkeypatch.setenv('PYTHONPATH', join_pythonpath(testdir)) monkeypatch.setenv('PYTHONPATH', join_pythonpath(testdir))
path.join('test_hello.py').remove() result = testdir.runpytest("--pyargs", "tpkg.test_missing")
result = testdir.runpytest("--pyargs", "tpkg.test_hello")
assert result.ret != 0 assert result.ret != 0
result.stderr.fnmatch_lines([ result.stderr.fnmatch_lines([
"*not*found*test_hello*", "*not*found*test_missing*",
])
def test_cmdline_python_namespace_package(self, testdir, monkeypatch):
"""
test --pyargs option with namespace packages (#1567)
"""
monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', raising=False)
search_path = []
for dirname in "hello", "world":
d = testdir.mkdir(dirname)
search_path.append(d)
ns = d.mkdir("ns_pkg")
ns.join("__init__.py").write(
"__import__('pkg_resources').declare_namespace(__name__)")
lib = ns.mkdir(dirname)
lib.ensure("__init__.py")
lib.join("test_{0}.py".format(dirname)). \
write("def test_{0}(): pass\n"
"def test_other():pass".format(dirname))
# The structure of the test directory is now:
# .
# ├── hello
# │   └── ns_pkg
# │   ├── __init__.py
# │   └── hello
# │   ├── __init__.py
# │   └── test_hello.py
# └── world
# └── ns_pkg
# ├── __init__.py
# └── world
# ├── __init__.py
# └── test_world.py
def join_pythonpath(*dirs):
cur = py.std.os.environ.get('PYTHONPATH')
if cur:
dirs += (cur,)
return os.pathsep.join(str(p) for p in dirs)
monkeypatch.setenv('PYTHONPATH', join_pythonpath(*search_path))
for p in search_path:
monkeypatch.syspath_prepend(p)
# mixed module and filenames:
result = testdir.runpytest("--pyargs", "-v", "ns_pkg.hello", "world/ns_pkg")
assert result.ret == 0
result.stdout.fnmatch_lines([
"*test_hello.py::test_hello*PASSED",
"*test_hello.py::test_other*PASSED",
"*test_world.py::test_world*PASSED",
"*test_world.py::test_other*PASSED",
"*4 passed*"
])
# specify tests within a module
result = testdir.runpytest("--pyargs", "-v", "ns_pkg.world.test_world::test_other")
assert result.ret == 0
result.stdout.fnmatch_lines([
"*test_world.py::test_other*PASSED",
"*1 passed*"
]) ])
def test_cmdline_python_package_not_exists(self, testdir): def test_cmdline_python_package_not_exists(self, testdir):
@ -700,4 +762,3 @@ class TestDurationWithFixture:
* setup *test_1* * setup *test_1*
* call *test_1* * call *test_1*
""") """)

View File

@ -1066,3 +1066,15 @@ def test_repr_traceback_with_unicode(style, encoding):
formatter = FormattedExcinfo(style=style) formatter = FormattedExcinfo(style=style)
repr_traceback = formatter.repr_traceback(e_info) repr_traceback = formatter.repr_traceback(e_info)
assert repr_traceback is not None assert repr_traceback is not None
def test_cwd_deleted(testdir):
testdir.makepyfile("""
def test(tmpdir):
tmpdir.chdir()
tmpdir.remove()
assert False
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines(['* 1 failed in *'])
assert 'INTERNALERROR' not in result.stdout.str() + result.stderr.str()

View File

@ -2704,3 +2704,108 @@ class TestContextManagerFixtureFuncs:
""".format(flavor=flavor)) """.format(flavor=flavor))
result = testdir.runpytest("-s") result = testdir.runpytest("-s")
result.stdout.fnmatch_lines("*mew*") result.stdout.fnmatch_lines("*mew*")
class TestParameterizedSubRequest:
def test_call_from_fixture(self, testdir):
testfile = testdir.makepyfile("""
import pytest
@pytest.fixture(params=[0, 1, 2])
def fix_with_param(request):
return request.param
@pytest.fixture
def get_named_fixture(request):
return request.getfuncargvalue('fix_with_param')
def test_foo(request, get_named_fixture):
pass
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines("""
E*Failed: The requested fixture has no parameter defined for the current test.
E*
E*Requested fixture 'fix_with_param' defined in:
E*{0}:4
E*Requested here:
E*{1}:9
*1 error*
""".format(testfile.basename, testfile.basename))
def test_call_from_test(self, testdir):
testfile = testdir.makepyfile("""
import pytest
@pytest.fixture(params=[0, 1, 2])
def fix_with_param(request):
return request.param
def test_foo(request):
request.getfuncargvalue('fix_with_param')
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines("""
E*Failed: The requested fixture has no parameter defined for the current test.
E*
E*Requested fixture 'fix_with_param' defined in:
E*{0}:4
E*Requested here:
E*{1}:8
*1 failed*
""".format(testfile.basename, testfile.basename))
def test_external_fixture(self, testdir):
conffile = testdir.makeconftest("""
import pytest
@pytest.fixture(params=[0, 1, 2])
def fix_with_param(request):
return request.param
""")
testfile = testdir.makepyfile("""
def test_foo(request):
request.getfuncargvalue('fix_with_param')
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines("""
E*Failed: The requested fixture has no parameter defined for the current test.
E*
E*Requested fixture 'fix_with_param' defined in:
E*{0}:4
E*Requested here:
E*{1}:2
*1 failed*
""".format(conffile.basename, testfile.basename))
def test_non_relative_path(self, testdir):
tests_dir = testdir.mkdir('tests')
fixdir = testdir.mkdir('fixtures')
fixfile = fixdir.join("fix.py")
fixfile.write(_pytest._code.Source("""
import pytest
@pytest.fixture(params=[0, 1, 2])
def fix_with_param(request):
return request.param
"""))
testfile = tests_dir.join("test_foos.py")
testfile.write(_pytest._code.Source("""
from fix import fix_with_param
def test_foo(request):
request.getfuncargvalue('fix_with_param')
"""))
tests_dir.chdir()
testdir.syspathinsert(fixdir)
result = testdir.runpytest()
result.stdout.fnmatch_lines("""
E*Failed: The requested fixture has no parameter defined for the current test.
E*
E*Requested fixture 'fix_with_param' defined in:
E*{0}:5
E*Requested here:
E*{1}:5
*1 failed*
""".format(fixfile.strpath, testfile.basename))

View File

@ -152,7 +152,9 @@ class TestCollectPluginHookRelay:
wascalled = [] wascalled = []
class Plugin: class Plugin:
def pytest_collect_file(self, path, parent): def pytest_collect_file(self, path, parent):
wascalled.append(path) if not path.basename.startswith("."):
# Ignore hidden files, e.g. .testmondata.
wascalled.append(path)
testdir.makefile(".abc", "xyz") testdir.makefile(".abc", "xyz")
pytest.main([testdir.tmpdir], plugins=[Plugin()]) pytest.main([testdir.tmpdir], plugins=[Plugin()])
assert len(wascalled) == 1 assert len(wascalled) == 1

View File

@ -14,13 +14,16 @@ class TestDoctests:
>>> i-1 >>> i-1
4 4
""") """)
for x in (testdir.tmpdir, checkfile): for x in (testdir.tmpdir, checkfile):
#print "checking that %s returns custom items" % (x,) #print "checking that %s returns custom items" % (x,)
items, reprec = testdir.inline_genitems(x) items, reprec = testdir.inline_genitems(x)
assert len(items) == 1 assert len(items) == 1
assert isinstance(items[0], DoctestTextfile) assert isinstance(items[0], DoctestItem)
assert isinstance(items[0].parent, DoctestTextfile)
# Empty file has no items.
items, reprec = testdir.inline_genitems(w) items, reprec = testdir.inline_genitems(w)
assert len(items) == 1 assert len(items) == 0
def test_collect_module_empty(self, testdir): def test_collect_module_empty(self, testdir):
path = testdir.makepyfile(whatever="#") path = testdir.makepyfile(whatever="#")
@ -608,6 +611,11 @@ class TestDoctestSkips:
reprec = testdir.inline_run("--doctest-modules") reprec = testdir.inline_run("--doctest-modules")
reprec.assertoutcome(skipped=1) reprec.assertoutcome(skipped=1)
def test_vacuous_all_skipped(self, testdir, makedoctest):
makedoctest('')
reprec = testdir.inline_run("--doctest-modules")
reprec.assertoutcome(passed=0, skipped=0)
class TestDoctestAutoUseFixtures: class TestDoctestAutoUseFixtures:

View File

@ -228,6 +228,39 @@ class BaseFunctionalTests:
assert reps[5].nodeid.endswith("test_func") assert reps[5].nodeid.endswith("test_func")
assert reps[5].failed assert reps[5].failed
def test_exact_teardown_issue1206(self, testdir):
rec = testdir.inline_runsource("""
import pytest
class TestClass:
def teardown_method(self):
pass
def test_method(self):
assert True
""")
reps = rec.getreports("pytest_runtest_logreport")
print (reps)
assert len(reps) == 3
#
assert reps[0].nodeid.endswith("test_method")
assert reps[0].passed
assert reps[0].when == 'setup'
#
assert reps[1].nodeid.endswith("test_method")
assert reps[1].passed
assert reps[1].when == 'call'
#
assert reps[2].nodeid.endswith("test_method")
assert reps[2].failed
assert reps[2].when == "teardown"
assert reps[2].longrepr.reprcrash.message in (
# python3 error
'TypeError: teardown_method() takes 1 positional argument but 2 were given',
# python2 error
'TypeError: teardown_method() takes exactly 1 argument (2 given)'
)
def test_failure_in_setup_function_ignores_custom_repr(self, testdir): def test_failure_in_setup_function_ignores_custom_repr(self, testdir):
testdir.makepyfile(conftest=""" testdir.makepyfile(conftest="""
import pytest import pytest

View File

@ -8,16 +8,11 @@ import _pytest._pluggy as pluggy
import _pytest._code import _pytest._code
import py import py
import pytest import pytest
from _pytest import runner
from _pytest.main import EXIT_NOTESTSCOLLECTED from _pytest.main import EXIT_NOTESTSCOLLECTED
from _pytest.terminal import TerminalReporter, repr_pythonversion, getreportopt from _pytest.terminal import TerminalReporter, repr_pythonversion, getreportopt
from _pytest.terminal import build_summary_stats_line, _plugin_nameversions from _pytest.terminal import build_summary_stats_line, _plugin_nameversions
def basic_run_report(item):
runner.call_and_report(item, "setup", log=False)
return runner.call_and_report(item, "call", log=False)
DistInfo = collections.namedtuple('DistInfo', ['project_name', 'version']) DistInfo = collections.namedtuple('DistInfo', ['project_name', 'version'])