Compare commits

...

64 Commits
3.3.1 ... 3.3.2

Author SHA1 Message Date
Bruno Oliveira
bd2d0d2c3c Preparing release version 3.3.2 2017-12-25 20:38:27 +00:00
Bruno Oliveira
08997279f4 Merge pull request #3059 from asottile/yesqa
Remove unnecessary `# noqa` comments.
2017-12-22 17:03:02 -02:00
Anthony Sottile
205e29d843 Remove unnecessary # noqa comments.
Commit automated with https://github.com/asottile/yesqa
2017-12-22 08:29:48 -08:00
Ronny Pfannschmidt
672c901c70 Merge pull request #2784 from RonnyPfannschmidt/fix-fdleaks
pytester: ignore files used to obtain current user metadata
2017-12-19 17:08:53 +01:00
Ronny Pfannschmidt
d370e7788d Merge pull request #2068 from flub/commitaccess
[proposal] Add section about how you become a committer
2017-12-18 18:20:49 +01:00
Ronny Pfannschmidt
ed293ec3e9 pytester: no ignore files used to obtain current user metadata in the fd leak detector 2017-12-18 17:52:55 +01:00
Ronny Pfannschmidt
2f8427bb4e Merge pull request #3043 from awbdallas/parameterize_documentation_fix
Parameterize documentation fix
2017-12-18 17:48:41 +01:00
Florian Bruhin
30729b7c3c Merge pull request #3045 from thisch/revert_accidental_push
Revert accidental push
2017-12-17 22:06:38 +01:00
Thomas Hisch
dfc5399cd7 Revert "WIP #3013"
This reverts commit 28a93b9eeb.
2017-12-17 21:50:17 +01:00
Thomas Hisch
76489d30f7 Revert "WIP"
This reverts commit d6f75d2836.
2017-12-17 21:49:57 +01:00
Thomas Hisch
d6f75d2836 WIP 2017-12-17 20:11:24 +01:00
Thomas Hisch
28a93b9eeb WIP #3013 2017-12-17 06:54:57 +01:00
Austin
70461d1ead Update parametrize.rst 2017-12-16 09:31:48 -06:00
Austin
73eccb4c36 Adding news fragment 2017-12-16 09:21:27 -06:00
Austin
d7a76a4d07 Updated for more clarity
- Changed original wording for parameterize. Should help be a little more clear
2017-12-16 09:04:18 -06:00
Bruno Oliveira
924b5e2e3d Merge pull request #3031 from nicoddemus/symlink-privileges
Check if symlink does have privileges on Windows for test_cmdline_python_package_symlink
2017-12-16 12:36:11 -02:00
Austin
c90e76c371 Clarifiyng documentation for parameterize
- Added snippet dealing with parameterize run order of tests
2017-12-16 08:25:02 -06:00
Bruno Oliveira
586ecea6f2 Check if symlink does not privileges on Windows for test_cmdline_python_package_symlink
os.symlink might fail on Windows because users require a special policy
to create symlinks (argh).

This is not a problem on AppVeyor because it is logged in as an admin,
but would be surprising for Windows users running pytest's test
suite on their computer.
2017-12-14 19:23:15 -02:00
Bruno Oliveira
db4df5833a Merge pull request #3030 from nicoddemus/leak
Fix memory leak caused by fixture values never been garbage collected
2017-12-14 19:20:34 -02:00
Bruno Oliveira
b17c6e5f89 Merge pull request #3028 from thisch/removelogger
Remove logger parameter from catching_logs
2017-12-13 20:56:48 -02:00
Bruno Oliveira
c3f63ac143 Fix memory leak caused by fixture values never been garbage collected
The leak was caused by the (unused) `FixtureRequest._fixture_values`
cache.

This was introduced because the `partial` object (created to call
FixtureDef.finish() bound with the Request) is kept alive
through the entire session when a function-scoped fixture depends
on a session-scoped (or higher) fixture because of the nested
`addfinalizer` calls.

FixtureDef.finish() started receiving a request object in order to
obtain the proper hook proxy object (#2127), but this does not seem
useful at all in practice because `pytest_fixture_post_finalizer`
will be called with the `request` object of the moment the fixture value
was *created*, not the request object active when the fixture value
is being destroyed. We should probably deprecate/remove the request
parameter from `pytest_fixture_post_finalizer`.

Fix #2981
2017-12-13 19:49:06 -02:00
Thomas Hisch
3862b0b28d Remove logger parameter from catching_logs
The logger parameter of catching_logs is not used anywhere. The main
motivation for removing the logger parameter is that it removes the

logger = logger or logging.getLogger(logger)

line. IMO there are too many occurences of the string 'logg' ;)
2017-12-13 21:13:59 +01:00
Ronny Pfannschmidt
476d4df1b7 Merge pull request #3010 from cryvate/fix-issue-2985
Improve handling of pyargs
2017-12-13 13:56:42 +01:00
Ronny Pfannschmidt
52449903c3 Merge pull request #3023 from nicoddemus/preparse-deprecated
Add param annotations and types to hookspec
2017-12-13 13:51:20 +01:00
Ronny Pfannschmidt
506c9c91c0 Merge pull request #3025 from anntzer/dont-rewrite-plugin
Respect PYTEST_DONT_REWRITE for plugins too.

closes #2995
2017-12-13 12:34:51 +01:00
Bruno Oliveira
38f34e2fa1 Merge pull request #3012 from thisch/merge_contextmanagers
Integrate logging_using_handler into catching_logs
2017-12-13 06:59:48 -02:00
Bruno Oliveira
ba41015ef6 Merge pull request #3021 from jurko-gospodnetic/cleanup
Code cleanup
2017-12-13 06:58:18 -02:00
Bruno Oliveira
ebfc1c49d1 Fix changelog formatting 2017-12-13 06:58:07 -02:00
Antony Lee
45e7734b1a Change set ops to use methods instead of operators. 2017-12-13 00:54:57 -08:00
Bruno Oliveira
8ce6e39b1c Small formatting to CHANGELOG 2017-12-13 06:52:37 -02:00
Antony Lee
c8e7d1ae34 Respect PYTEST_DONT_REWRITE for plugins too. 2017-12-12 17:43:17 -08:00
Bruno Oliveira
7b5d4d01ed Add param annotations and types to hookspec
Also mention which hook to use instead of the deprecated
pytest_cmdline_preparse

Fix #3022
2017-12-12 18:01:31 -02:00
Jurko Gospodnetić
a4f4579f19 add changelog entry 2017-12-12 13:41:31 +01:00
Jurko Gospodnetić
e4da9bacdf fix actial --> actual typo 2017-12-12 12:55:32 +01:00
Henk-Jaap Wagenaar
1e295535c3 Move _patched_find_module to module namespace. 2017-12-12 09:53:06 +00:00
Henk-Jaap Wagenaar
3ca1e4b7f0 Make patch for issue in pkgutil.ImpImporter local by using contextmanager. 2017-12-12 08:43:01 +00:00
Henk-Jaap Wagenaar
dc19624248 Improve testing comments and code of issue 2985 (symlink in pyargs). 2017-12-12 08:26:40 +00:00
Bruno Oliveira
5c6d7739bc Merge pull request #3015 from jurko-gospodnetic/code-cleanup
Code cleanup
2017-12-11 22:15:50 -02:00
Bruno Oliveira
771b5c8852 Merge pull request #3019 from srinivasreddy/rm_ast
remove '_ast' module; and redirect '_ast' references to 'ast'
2017-12-11 22:12:40 -02:00
Bruno Oliveira
fc5ec5807e Add changelog for 3018 2017-12-11 17:40:35 -02:00
Srinivas Reddy Thatiparthy
fc544dc660 remove redundant checking because
* isinstance([], Sequence) is True

* isinstance((), Sequence) is True
2017-12-12 00:10:59 +05:30
Srinivas Reddy Thatiparthy
7792587b3f remove unnecessary import 2017-12-12 00:03:38 +05:30
Srinivas Reddy Thatiparthy
c2cd239d35 remove _ast reference from test cases as well 2017-12-11 23:24:47 +05:30
Srinivas Reddy Thatiparthy
cb0ba18f53 remove '_ast' module; and redirect '_ast' references to 'ast' 2017-12-11 23:14:17 +05:30
Jurko Gospodnetić
3b85e0c3a9 simplify test_conftest_found_with_double_dash() test code 2017-12-11 15:10:04 +01:00
Jurko Gospodnetić
73bc6bacfa add changelog entry 2017-12-09 13:34:58 +01:00
Jurko Gospodnetić
8e8a953ac6 fix test name typo 2017-12-09 13:34:58 +01:00
Jurko Gospodnetić
852b96714e use spaces consistently instead of some of them being   2017-12-09 13:34:57 +01:00
Jurko Gospodnetić
dd64f1a4a9 fix tox.ini comment typos 2017-12-09 13:34:57 +01:00
Jurko Gospodnetić
41a6ec6f31 touch up test_cmdline_python_namespace_package() test code 2017-12-09 13:34:56 +01:00
Jurko Gospodnetić
7feab7391d simplify test_pdb_collection_failure_is_shown test data setup code 2017-12-09 13:34:56 +01:00
Jurko Gospodnetić
f0bfe9de3d shorten some test code 2017-12-09 13:34:56 +01:00
Jurko Gospodnetić
596937e610 remove extra whitespace 2017-12-09 13:34:55 +01:00
Jurko Gospodnetić
57fcd3f57e remove corpse code comments 2017-12-09 13:34:55 +01:00
Jurko Gospodnetić
65f5383106 fix comment & docstring typos, line wrapping & wording 2017-12-09 13:34:54 +01:00
Ronny Pfannschmidt
964c29cb93 Merge pull request #3014 from nicoddemus/cap-named-tuple
Change capture docs to use namedtuple
2017-12-09 09:38:39 +01:00
Bruno Oliveira
38fb6aae78 Change capture docs to use namedtuple 2017-12-08 18:34:29 -02:00
Bruno Oliveira
1c5b887dfd Fix spelling of pytest in CHANGELOG 2017-12-08 09:04:42 -02:00
Thomas Hisch
ba209b5230 Integrate logging_using_handler into catching_logs
logging_using_handler is only used in catching_logs. Therefore it makes
sense to remove one of the many context managers from the logging
plugin.
2017-12-07 16:34:53 +01:00
Henk-Jaap Wagenaar
b62fd79c0c Fix issue 2985. 2017-12-07 15:12:44 +00:00
Bruno Oliveira
88f2cc9b64 Small formatting fixes in CHANGELOG 2017-12-05 22:28:56 -02:00
Bruno Oliveira
ed2bb9d723 Merge pull request #2997 from nicoddemus/release-3.3.1
Preparing release version 3.3.1
2017-12-05 22:21:41 -02:00
Floris Bruynooghe
1f0d06641a Update text to only give access when wanted
Also clarify merging perms given the PR approval now available.
2017-02-19 15:16:34 -03:00
Floris Bruynooghe
4c62cd451a Add section about how you become a committer 2016-11-17 21:25:22 +00:00
28 changed files with 610 additions and 305 deletions

View File

@@ -74,6 +74,7 @@ Grig Gheorghiu
Grigorii Eremeev (budulianin)
Guido Wesdorp
Harald Armin Massa
Henk-Jaap Wagenaar
Hugo van Kemenade
Hui Wang (coldnight)
Ian Bicking

View File

@@ -8,6 +8,44 @@
.. towncrier release notes start
Pytest 3.3.2 (2017-12-25)
=========================
Bug Fixes
---------
- pytester: ignore files used to obtain current user metadata in the fd leak
detector. (`#2784 <https://github.com/pytest-dev/pytest/issues/2784>`_)
- Fix **memory leak** where objects returned by fixtures were never destructed
by the garbage collector. (`#2981
<https://github.com/pytest-dev/pytest/issues/2981>`_)
- Fix conversion of pyargs to filename to not convert symlinks and not use
deprecated features on Python 3. (`#2985
<https://github.com/pytest-dev/pytest/issues/2985>`_)
- ``PYTEST_DONT_REWRITE`` is now checked for plugins too rather than only for
test modules. (`#2995 <https://github.com/pytest-dev/pytest/issues/2995>`_)
Improved Documentation
----------------------
- Add clarifying note about behavior of multiple parametrized arguments (`#3001
<https://github.com/pytest-dev/pytest/issues/3001>`_)
Trivial/Internal Changes
------------------------
- Code cleanup. (`#3015 <https://github.com/pytest-dev/pytest/issues/3015>`_,
`#3021 <https://github.com/pytest-dev/pytest/issues/3021>`_)
- Clean up code by replacing imports and references of `_ast` to `ast`. (`#3018
<https://github.com/pytest-dev/pytest/issues/3018>`_)
Pytest 3.3.1 (2017-12-05)
=========================
@@ -31,7 +69,7 @@ Bug Fixes
``TerminalReporter._tw``. This alias was removed by accident in the ``3.3.0``
release. (`#2984 <https://github.com/pytest-dev/pytest/issues/2984>`_)
- The pytest-capturelog plugin is now also blacklisted, avoiding errors when
- The ``pytest-capturelog`` plugin is now also blacklisted, avoiding errors when
running pytest with it still installed. (`#3004
<https://github.com/pytest-dev/pytest/issues/3004>`_)
@@ -39,14 +77,14 @@ Bug Fixes
Improved Documentation
----------------------
- Fix broken link to plugin pytest-localserver. (`#2963
- Fix broken link to plugin ``pytest-localserver``. (`#2963
<https://github.com/pytest-dev/pytest/issues/2963>`_)
Trivial/Internal Changes
------------------------
- Update github "bugs" link in CONTRIBUTING.rst (`#2949
- Update github "bugs" link in ``CONTRIBUTING.rst`` (`#2949
<https://github.com/pytest-dev/pytest/issues/2949>`_)
@@ -157,10 +195,10 @@ Bug Fixes
<https://github.com/pytest-dev/pytest/issues/2124>`_)
- If an exception happens while loading a plugin, pytest no longer hides the
original traceback. In python2 it will show the original traceback with a new
message that explains in which plugin. In python3 it will show 2 canonized
original traceback. In Python 2 it will show the original traceback with a new
message that explains in which plugin. In Python 3 it will show 2 canonized
exceptions, the original exception while loading the plugin in addition to an
exception that PyTest throws about loading a plugin. (`#2491
exception that pytest throws about loading a plugin. (`#2491
<https://github.com/pytest-dev/pytest/issues/2491>`_)
- ``capsys`` and ``capfd`` can now be used by other fixtures. (`#2709

View File

@@ -276,3 +276,15 @@ Here is a simple overview, with pytest-specific bits:
base: features # if it's a feature
Joining the Development Team
----------------------------
Anyone who has successfully seen through a pull request which did not
require any extra work from the development team to merge will
themselves gain commit access if they so wish (if we forget to ask please send a friendly
reminder). This does not mean your workflow to contribute changes,
everyone goes through the same pull-request-and-review process and
no-one merges their own pull requests unless already approved. It does however mean you can
participate in the development process more fully since you can merge
pull requests from other contributors yourself after having reviewed
them.

View File

@@ -1,19 +1,15 @@
from __future__ import absolute_import, division, generators, print_function
import ast
from ast import PyCF_ONLY_AST as _AST_FLAG
from bisect import bisect_right
import sys
import six
import inspect
import tokenize
import py
cpy_compile = compile
try:
import _ast
from _ast import PyCF_ONLY_AST as _AST_FLAG
except ImportError:
_AST_FLAG = 0
_ast = None
cpy_compile = compile
class Source(object):
@@ -209,7 +205,7 @@ def compile_(source, filename=None, mode='exec', flags=generators.compiler_flag,
retrieval of the source code for the code object
and any recursively created code objects.
"""
if _ast is not None and isinstance(source, _ast.AST):
if isinstance(source, ast.AST):
# XXX should Source support having AST?
return cpy_compile(source, filename, mode, flags, dont_inherit)
_genframe = sys._getframe(1) # the caller
@@ -322,7 +318,7 @@ def get_statement_startend2(lineno, node):
# AST's line numbers start indexing at 1
values = []
for x in ast.walk(node):
if isinstance(x, _ast.stmt) or isinstance(x, _ast.ExceptHandler):
if isinstance(x, ast.stmt) or isinstance(x, ast.ExceptHandler):
values.append(x.lineno - 1)
for name in "finalbody", "orelse":
val = getattr(x, name, None)

View File

@@ -1,7 +1,6 @@
"""Rewrite assertion AST to produce nice error messages"""
from __future__ import absolute_import, division, print_function
import ast
import _ast
import errno
import itertools
import imp
@@ -180,11 +179,13 @@ class AssertionRewritingHook(object):
The named module or package as well as any nested modules will
be rewritten on import.
"""
already_imported = set(names).intersection(set(sys.modules))
if already_imported:
for name in already_imported:
if name not in self._rewritten_names:
self._warn_already_imported(name)
already_imported = (set(names)
.intersection(sys.modules)
.difference(self._rewritten_names))
for name in already_imported:
if not AssertionRewriter.is_rewrite_disabled(
sys.modules[name].__doc__ or ""):
self._warn_already_imported(name)
self._must_rewrite.update(names)
def _warn_already_imported(self, name):
@@ -636,7 +637,8 @@ class AssertionRewriter(ast.NodeVisitor):
not isinstance(field, ast.expr)):
nodes.append(field)
def is_rewrite_disabled(self, docstring):
@staticmethod
def is_rewrite_disabled(docstring):
return "PYTEST_DONT_REWRITE" in docstring
def variable(self):
@@ -914,7 +916,7 @@ class AssertionRewriter(ast.NodeVisitor):
def visit_Compare(self, comp):
self.push_format_context()
left_res, left_expl = self.visit(comp.left)
if isinstance(comp.left, (_ast.Compare, _ast.BoolOp)):
if isinstance(comp.left, (ast.Compare, ast.BoolOp)):
left_expl = "({0})".format(left_expl)
res_variables = [self.variable() for i in range(len(comp.ops))]
load_names = [ast.Name(v, ast.Load()) for v in res_variables]
@@ -925,7 +927,7 @@ class AssertionRewriter(ast.NodeVisitor):
results = [left_res]
for i, op, next_operand in it:
next_res, next_expl = self.visit(next_operand)
if isinstance(next_operand, (_ast.Compare, _ast.BoolOp)):
if isinstance(next_operand, (ast.Compare, ast.BoolOp)):
next_expl = "({0})".format(next_expl)
results.append(next_res)
sym = binop_map[op.__class__]

View File

@@ -5,11 +5,7 @@ import pprint
import _pytest._code
import py
import six
try:
from collections import Sequence
except ImportError:
Sequence = list
from collections import Sequence
u = six.text_type
@@ -113,7 +109,7 @@ def assertrepr_compare(config, op, left, right):
summary = u('%s %s %s') % (ecu(left_repr), op, ecu(right_repr))
def issequence(x):
return (isinstance(x, (list, tuple, Sequence)) and not isinstance(x, basestring))
return isinstance(x, Sequence) and not isinstance(x, basestring)
def istext(x):
return isinstance(x, basestring)

View File

@@ -267,7 +267,6 @@ class FixtureRequest(FuncargnamesCompatAttr):
self.fixturename = None
#: Scope string, one of "function", "class", "module", "session"
self.scope = "function"
self._fixture_values = {} # argname -> fixture value
self._fixture_defs = {} # argname -> FixtureDef
fixtureinfo = pyfuncitem._fixtureinfo
self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
@@ -450,8 +449,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
raise
# remove indent to prevent the python3 exception
# from leaking into the call
result = self._getfixturevalue(fixturedef)
self._fixture_values[argname] = result
self._compute_fixture_value(fixturedef)
self._fixture_defs[argname] = fixturedef
return fixturedef
@@ -466,7 +464,14 @@ class FixtureRequest(FuncargnamesCompatAttr):
values.append(fixturedef)
current = current._parent_request
def _getfixturevalue(self, fixturedef):
def _compute_fixture_value(self, fixturedef):
"""
Creates a SubRequest based on "self" and calls the execute method of the given fixturedef object. This will
force the FixtureDef object to throw away any previous results and compute a new fixture value, which
will be stored into the FixtureDef object itself.
:param FixtureDef fixturedef:
"""
# prepare a subrequest object before calling fixture function
# (latter managed by fixturedef)
argname = fixturedef.argname
@@ -515,12 +520,11 @@ class FixtureRequest(FuncargnamesCompatAttr):
exc_clear()
try:
# call the fixture function
val = fixturedef.execute(request=subrequest)
fixturedef.execute(request=subrequest)
finally:
# if fixture function failed it might have registered finalizers
self.session._setupstate.addfinalizer(functools.partial(fixturedef.finish, request=subrequest),
subrequest.node)
return val
def _check_scope(self, argname, invoking_scope, requested_scope):
if argname == "request":
@@ -573,7 +577,6 @@ class SubRequest(FixtureRequest):
self.scope = scope
self._fixturedef = fixturedef
self._pyfuncitem = request._pyfuncitem
self._fixture_values = request._fixture_values
self._fixture_defs = request._fixture_defs
self._arg2fixturedefs = request._arg2fixturedefs
self._arg2index = request._arg2index

View File

@@ -12,22 +12,31 @@ hookspec = HookspecMarker("pytest")
@hookspec(historic=True)
def pytest_addhooks(pluginmanager):
"""called at plugin registration time to allow adding new hooks via a call to
pluginmanager.add_hookspecs(module_or_class, prefix)."""
``pluginmanager.add_hookspecs(module_or_class, prefix)``.
:param _pytest.config.PytestPluginManager pluginmanager: pytest plugin manager
"""
@hookspec(historic=True)
def pytest_namespace():
"""
DEPRECATED: this hook causes direct monkeypatching on pytest, its use is strongly discouraged
(**Deprecated**) this hook causes direct monkeypatching on pytest, its use is strongly discouraged
return dict of name->object to be made globally available in
the pytest namespace. This hook is called at plugin registration
time.
the pytest namespace.
This hook is called at plugin registration time.
"""
@hookspec(historic=True)
def pytest_plugin_registered(plugin, manager):
""" a new pytest plugin got registered. """
""" a new pytest plugin got registered.
:param plugin: the plugin module or instance
:param _pytest.config.PytestPluginManager manager: pytest plugin manager
"""
@hookspec(historic=True)
@@ -41,7 +50,7 @@ def pytest_addoption(parser):
files situated at the tests root directory due to how pytest
:ref:`discovers plugins during startup <pluginorder>`.
:arg parser: To add command line options, call
:arg _pytest.config.Parser parser: To add command line options, call
:py:func:`parser.addoption(...) <_pytest.config.Parser.addoption>`.
To add ini-file values call :py:func:`parser.addini(...)
<_pytest.config.Parser.addini>`.
@@ -56,8 +65,7 @@ def pytest_addoption(parser):
a value read from an ini-style file.
The config object is passed around on many internal objects via the ``.config``
attribute or can be retrieved as the ``pytestconfig`` fixture or accessed
via (deprecated) ``pytest.config``.
attribute or can be retrieved as the ``pytestconfig`` fixture.
"""
@@ -72,8 +80,7 @@ def pytest_configure(config):
After that, the hook is called for other conftest files as they are
imported.
:arg config: pytest config object
:type config: _pytest.config.Config
:arg _pytest.config.Config config: pytest config object
"""
# -------------------------------------------------------------------------
@@ -87,11 +94,22 @@ def pytest_configure(config):
def pytest_cmdline_parse(pluginmanager, args):
"""return initialized config object, parsing the specified args.
Stops at first non-None result, see :ref:`firstresult` """
Stops at first non-None result, see :ref:`firstresult`
:param _pytest.config.PytestPluginManager pluginmanager: pytest plugin manager
:param list[str] args: list of arguments passed on the command line
"""
def pytest_cmdline_preparse(config, args):
"""(deprecated) modify command line arguments before option parsing. """
"""(**Deprecated**) modify command line arguments before option parsing.
This hook is considered deprecated and will be removed in a future pytest version. Consider
using :func:`pytest_load_initial_conftests` instead.
:param _pytest.config.Config config: pytest config object
:param list[str] args: list of arguments passed on the command line
"""
@hookspec(firstresult=True)
@@ -99,12 +117,20 @@ def pytest_cmdline_main(config):
""" called for performing the main command line action. The default
implementation will invoke the configure hooks and runtest_mainloop.
Stops at first non-None result, see :ref:`firstresult` """
Stops at first non-None result, see :ref:`firstresult`
:param _pytest.config.Config config: pytest config object
"""
def pytest_load_initial_conftests(early_config, parser, args):
""" implements the loading of initial conftest files ahead
of command line option parsing. """
of command line option parsing.
:param _pytest.config.Config early_config: pytest config object
:param list[str] args: list of arguments passed on the command line
:param _pytest.config.Parser parser: to add command line options
"""
# -------------------------------------------------------------------------
@@ -113,18 +139,29 @@ def pytest_load_initial_conftests(early_config, parser, args):
@hookspec(firstresult=True)
def pytest_collection(session):
""" perform the collection protocol for the given session.
"""Perform the collection protocol for the given session.
Stops at first non-None result, see :ref:`firstresult` """
Stops at first non-None result, see :ref:`firstresult`.
:param _pytest.main.Session session: the pytest session object
"""
def pytest_collection_modifyitems(session, config, items):
""" called after collection has been performed, may filter or re-order
the items in-place."""
the items in-place.
:param _pytest.main.Session session: the pytest session object
:param _pytest.config.Config config: pytest config object
:param List[_pytest.main.Item] items: list of item objects
"""
def pytest_collection_finish(session):
""" called after collection has been performed and modified. """
""" called after collection has been performed and modified.
:param _pytest.main.Session session: the pytest session object
"""
@hookspec(firstresult=True)
@@ -134,6 +171,9 @@ def pytest_ignore_collect(path, config):
more specific hooks.
Stops at first non-None result, see :ref:`firstresult`
:param str path: the path to analyze
:param _pytest.config.Config config: pytest config object
"""
@@ -141,12 +181,18 @@ def pytest_ignore_collect(path, config):
def pytest_collect_directory(path, parent):
""" called before traversing a directory for collection files.
Stops at first non-None result, see :ref:`firstresult` """
Stops at first non-None result, see :ref:`firstresult`
:param str path: the path to analyze
"""
def pytest_collect_file(path, parent):
""" return collection Node or None for the given path. Any new node
needs to have the specified ``parent`` as a parent."""
needs to have the specified ``parent`` as a parent.
:param str path: the path to collect
"""
# logging hooks for collection
@@ -212,7 +258,12 @@ def pytest_make_parametrize_id(config, val, argname):
by @pytest.mark.parametrize calls. Return None if the hook doesn't know about ``val``.
The parameter name is available as ``argname``, if required.
Stops at first non-None result, see :ref:`firstresult` """
Stops at first non-None result, see :ref:`firstresult`
:param _pytest.config.Config config: pytest config object
:param val: the parametrized value
:param str argname: the automatic parameter name produced by pytest
"""
# -------------------------------------------------------------------------
# generic runtest related hooks
@@ -224,11 +275,14 @@ def pytest_runtestloop(session):
""" called for performing the main runtest loop
(after collection finished).
Stops at first non-None result, see :ref:`firstresult` """
Stops at first non-None result, see :ref:`firstresult`
:param _pytest.main.Session session: the pytest session object
"""
def pytest_itemstart(item, node):
""" (deprecated, use pytest_runtest_logstart). """
"""(**Deprecated**) use pytest_runtest_logstart. """
@hookspec(firstresult=True)
@@ -307,15 +361,25 @@ def pytest_fixture_post_finalizer(fixturedef, request):
def pytest_sessionstart(session):
""" before session.main() is called. """
""" before session.main() is called.
:param _pytest.main.Session session: the pytest session object
"""
def pytest_sessionfinish(session, exitstatus):
""" whole test run finishes. """
""" whole test run finishes.
:param _pytest.main.Session session: the pytest session object
:param int exitstatus: the status which pytest will return to the system
"""
def pytest_unconfigure(config):
""" called before test process is exited. """
""" called before test process is exited.
:param _pytest.config.Config config: pytest config object
"""
# -------------------------------------------------------------------------
@@ -329,6 +393,8 @@ def pytest_assertrepr_compare(config, op, left, right):
of strings. The strings will be joined by newlines but any newlines
*in* a string will be escaped. Note that all but the first line will
be indented slightly, the intention is for the first line to be a summary.
:param _pytest.config.Config config: pytest config object
"""
# -------------------------------------------------------------------------
@@ -339,7 +405,7 @@ def pytest_assertrepr_compare(config, op, left, right):
def pytest_report_header(config, startdir):
""" return a string or list of strings to be displayed as header info for terminal reporting.
:param config: the pytest config object.
:param _pytest.config.Config config: pytest config object
:param startdir: py.path object with the starting dir
.. note::
@@ -358,7 +424,7 @@ def pytest_report_collectionfinish(config, startdir, items):
This strings will be displayed after the standard "collected X items" message.
:param config: the pytest config object.
:param _pytest.config.Config config: pytest config object
:param startdir: py.path object with the starting dir
:param items: list of pytest items that are going to be executed; this list should not be modified.
"""
@@ -418,6 +484,5 @@ def pytest_enter_pdb(config):
""" called upon pdb.set_trace(), can be used by plugins to take special
action just before the python debugger enters in interactive mode.
:arg config: pytest config object
:type config: _pytest.config.Config
:param _pytest.config.Config config: pytest config object
"""

View File

@@ -79,39 +79,28 @@ def pytest_addoption(parser):
@contextmanager
def logging_using_handler(handler, logger=None):
"""Context manager that safely registers a given handler."""
logger = logger or logging.getLogger(logger)
if handler in logger.handlers: # reentrancy
# Adding the same handler twice would confuse logging system.
# Just don't do that.
yield
else:
logger.addHandler(handler)
try:
yield
finally:
logger.removeHandler(handler)
@contextmanager
def catching_logs(handler, formatter=None,
level=logging.NOTSET, logger=None):
def catching_logs(handler, formatter=None, level=logging.NOTSET):
"""Context manager that prepares the whole logging machinery properly."""
logger = logger or logging.getLogger(logger)
root_logger = logging.getLogger()
if formatter is not None:
handler.setFormatter(formatter)
handler.setLevel(level)
with logging_using_handler(handler, logger):
orig_level = logger.level
logger.setLevel(min(orig_level, level))
try:
yield handler
finally:
logger.setLevel(orig_level)
# Adding the same handler twice would confuse logging system.
# Just don't do that.
add_new_handler = handler not in root_logger.handlers
if add_new_handler:
root_logger.addHandler(handler)
orig_level = root_logger.level
root_logger.setLevel(min(orig_level, level))
try:
yield handler
finally:
root_logger.setLevel(orig_level)
if add_new_handler:
root_logger.removeHandler(handler)
class LogCaptureHandler(logging.StreamHandler):

View File

@@ -1,8 +1,10 @@
""" core implementation of testing process: init, session, runtest loop. """
from __future__ import absolute_import, division, print_function
import contextlib
import functools
import os
import pkgutil
import six
import sys
@@ -206,6 +208,46 @@ def pytest_ignore_collect(path, config):
return False
@contextlib.contextmanager
def _patched_find_module():
"""Patch bug in pkgutil.ImpImporter.find_module
When using pkgutil.find_loader on python<3.4 it removes symlinks
from the path due to a call to os.path.realpath. This is not consistent
with actually doing the import (in these versions, pkgutil and __import__
did not share the same underlying code). This can break conftest
discovery for pytest where symlinks are involved.
The only supported python<3.4 by pytest is python 2.7.
"""
if six.PY2: # python 3.4+ uses importlib instead
def find_module_patched(self, fullname, path=None):
# Note: we ignore 'path' argument since it is only used via meta_path
subname = fullname.split(".")[-1]
if subname != fullname and self.path is None:
return None
if self.path is None:
path = None
else:
# original: path = [os.path.realpath(self.path)]
path = [self.path]
try:
file, filename, etc = pkgutil.imp.find_module(subname,
path)
except ImportError:
return None
return pkgutil.ImpLoader(fullname, file, filename, etc)
old_find_module = pkgutil.ImpImporter.find_module
pkgutil.ImpImporter.find_module = find_module_patched
try:
yield
finally:
pkgutil.ImpImporter.find_module = old_find_module
else:
yield
class FSHookProxy:
def __init__(self, fspath, pm, remove_mods):
self.fspath = fspath
@@ -728,9 +770,10 @@ class Session(FSCollector):
"""Convert a dotted module name to path.
"""
import pkgutil
try:
loader = pkgutil.find_loader(x)
with _patched_find_module():
loader = pkgutil.find_loader(x)
except ImportError:
return x
if loader is None:
@@ -738,7 +781,8 @@ class Session(FSCollector):
# This method is sometimes invoked when AssertionRewritingHook, which
# does not define a get_filename method, is already in place:
try:
path = loader.get_filename(x)
with _patched_find_module():
path = loader.get_filename(x)
except AttributeError:
# Retrieve path from AssertionRewritingHook:
path = loader.modules[x][0].co_filename

View File

@@ -1,4 +1,4 @@
""" (disabled by default) support for testing pytest and pytest plugins. """
"""(disabled by default) support for testing pytest and pytest plugins."""
from __future__ import absolute_import, division, print_function
import codecs
@@ -26,14 +26,18 @@ from _pytest.assertion.rewrite import AssertionRewritingHook
PYTEST_FULLPATH = os.path.abspath(pytest.__file__.rstrip("oc")).replace("$py.class", ".py")
IGNORE_PAM = [ # filenames added when obtaining details about the current user
u'/var/lib/sss/mc/passwd'
]
def pytest_addoption(parser):
# group = parser.getgroup("pytester", "pytester (self-tests) options")
parser.addoption('--lsof',
action="store_true", dest="lsof", default=False,
help=("run FD checks if lsof is available"))
parser.addoption('--runpytest', default="inprocess", dest="runpytest",
choices=("inprocess", "subprocess", ),
choices=("inprocess", "subprocess"),
help=("run pytest sub runs in tests using an 'inprocess' "
"or 'subprocess' (python -m main) method"))
@@ -67,6 +71,8 @@ class LsofFdLeakChecker(object):
fields = line.split('\0')
fd = fields[0][1:]
filename = fields[1][1:]
if filename in IGNORE_PAM:
continue
if filename.startswith('/'):
open_files.append((fd, filename))
@@ -76,8 +82,8 @@ class LsofFdLeakChecker(object):
try:
py.process.cmdexec("lsof -v")
except (py.process.cmdexec.Error, UnicodeDecodeError):
# cmdexec may raise UnicodeDecodeError on Windows systems
# with locale other than english:
# cmdexec may raise UnicodeDecodeError on Windows systems with
# locale other than English:
# https://bitbucket.org/pytest-dev/py/issues/66
return False
else:
@@ -132,7 +138,7 @@ def getexecutable(name, cache={}):
if "2.5.2" in err:
executable = None # http://bugs.jython.org/issue1790
elif popen.returncode != 0:
# Handle pyenv's 127.
# handle pyenv's 127
executable = None
cache[name] = executable
return executable
@@ -157,9 +163,10 @@ def anypython(request):
@pytest.fixture
def _pytest(request):
""" Return a helper which offers a gethookrecorder(hook)
method which returns a HookRecorder instance which helps
to make assertions about called hooks.
"""Return a helper which offers a gethookrecorder(hook) method which
returns a HookRecorder instance which helps to make assertions about called
hooks.
"""
return PytestArg(request)
@@ -193,8 +200,8 @@ class ParsedCall:
class HookRecorder:
"""Record all hooks called in a plugin manager.
This wraps all the hook calls in the plugin manager, recording
each call before propagating the normal calls.
This wraps all the hook calls in the plugin manager, recording each call
before propagating the normal calls.
"""
@@ -262,7 +269,7 @@ class HookRecorder:
def matchreport(self, inamepart="",
names="pytest_runtest_logreport pytest_collectreport", when=None):
""" return a testreport whose dotted import path matches """
"""return a testreport whose dotted import path matches"""
values = []
for rep in self.getreports(names=names):
try:
@@ -341,14 +348,14 @@ class RunResult:
Attributes:
:ret: The return value.
:outlines: List of lines captured from stdout.
:errlines: List of lines captures from stderr.
:ret: the return value
:outlines: list of lines captured from stdout
:errlines: list of lines captures from stderr
:stdout: :py:class:`LineMatcher` of stdout, use ``stdout.str()`` to
reconstruct stdout or the commonly used
``stdout.fnmatch_lines()`` method.
:stderrr: :py:class:`LineMatcher` of stderr.
:duration: Duration in seconds.
reconstruct stdout or the commonly used ``stdout.fnmatch_lines()``
method
:stderr: :py:class:`LineMatcher` of stderr
:duration: duration in seconds
"""
@@ -361,8 +368,10 @@ class RunResult:
self.duration = duration
def parseoutcomes(self):
""" Return a dictionary of outcomestring->num from parsing
the terminal output that the test process produced."""
"""Return a dictionary of outcomestring->num from parsing the terminal
output that the test process produced.
"""
for line in reversed(self.outlines):
if 'seconds' in line:
outcomes = rex_outcome.findall(line)
@@ -374,8 +383,10 @@ class RunResult:
raise ValueError("Pytest terminal report not found")
def assert_outcomes(self, passed=0, skipped=0, failed=0, error=0):
""" assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run."""
"""Assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run.
"""
d = self.parseoutcomes()
obtained = {
'passed': d.get('passed', 0),
@@ -389,21 +400,18 @@ class RunResult:
class Testdir:
"""Temporary test directory with tools to test/run pytest itself.
This is based on the ``tmpdir`` fixture but provides a number of
methods which aid with testing pytest itself. Unless
:py:meth:`chdir` is used all methods will use :py:attr:`tmpdir` as
current working directory.
This is based on the ``tmpdir`` fixture but provides a number of methods
which aid with testing pytest itself. Unless :py:meth:`chdir` is used all
methods will use :py:attr:`tmpdir` as their current working directory.
Attributes:
:tmpdir: The :py:class:`py.path.local` instance of the temporary
directory.
:tmpdir: The :py:class:`py.path.local` instance of the temporary directory.
:plugins: A list of plugins to use with :py:meth:`parseconfig` and
:py:meth:`runpytest`. Initially this is an empty list but
plugins can be added to the list. The type of items to add to
the list depend on the method which uses them so refer to them
for details.
:py:meth:`runpytest`. Initially this is an empty list but plugins can
be added to the list. The type of items to add to the list depends on
the method using them so refer to them for details.
"""
@@ -429,10 +437,9 @@ class Testdir:
def finalize(self):
"""Clean up global state artifacts.
Some methods modify the global interpreter state and this
tries to clean this up. It does not remove the temporary
directory however so it can be looked at after the test run
has finished.
Some methods modify the global interpreter state and this tries to
clean this up. It does not remove the temporary directory however so
it can be looked at after the test run has finished.
"""
sys.path[:], sys.meta_path[:] = self._savesyspath
@@ -495,17 +502,15 @@ class Testdir:
def makefile(self, ext, *args, **kwargs):
"""Create a new file in the testdir.
ext: The extension the file should use, including the dot.
E.g. ".py".
ext: The extension the file should use, including the dot, e.g. `.py`.
args: All args will be treated as strings and joined using
newlines. The result will be written as contents to the
file. The name of the file will be based on the test
function requesting this fixture.
args: All args will be treated as strings and joined using newlines.
The result will be written as contents to the file. The name of the
file will be based on the test function requesting this fixture.
E.g. "testdir.makefile('.txt', 'line1', 'line2')"
kwargs: Each keyword is the name of a file, while the value of
it will be written as contents of the file.
kwargs: Each keyword is the name of a file, while the value of it will
be written as contents of the file.
E.g. "testdir.makefile('.ini', pytest='[pytest]\naddopts=-rs\n')"
"""
@@ -535,14 +540,16 @@ class Testdir:
def syspathinsert(self, path=None):
"""Prepend a directory to sys.path, defaults to :py:attr:`tmpdir`.
This is undone automatically after the test.
This is undone automatically when this object dies at the end of each
test.
"""
if path is None:
path = self.tmpdir
sys.path.insert(0, str(path))
# a call to syspathinsert() usually means that the caller
# wants to import some dynamically created files.
# with python3 we thus invalidate import caches.
# a call to syspathinsert() usually means that the caller wants to
# import some dynamically created files, thus with python3 we
# invalidate its import caches
self._possibly_invalidate_import_caches()
def _possibly_invalidate_import_caches(self):
@@ -562,8 +569,8 @@ class Testdir:
def mkpydir(self, name):
"""Create a new python package.
This creates a (sub)directory with an empty ``__init__.py``
file so that is recognised as a python package.
This creates a (sub)directory with an empty ``__init__.py`` file so it
gets recognised as a python package.
"""
p = self.mkdir(name)
@@ -576,10 +583,10 @@ class Testdir:
"""Return the collection node of a file.
:param config: :py:class:`_pytest.config.Config` instance, see
:py:meth:`parseconfig` and :py:meth:`parseconfigure` to
create the configuration.
:py:meth:`parseconfig` and :py:meth:`parseconfigure` to create the
configuration
:param arg: A :py:class:`py.path.local` instance of the file.
:param arg: a :py:class:`py.path.local` instance of the file
"""
session = Session(config)
@@ -593,11 +600,10 @@ class Testdir:
def getpathnode(self, path):
"""Return the collection node of a file.
This is like :py:meth:`getnode` but uses
:py:meth:`parseconfigure` to create the (configured) pytest
Config instance.
This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to
create the (configured) pytest Config instance.
:param path: A :py:class:`py.path.local` instance of the file.
:param path: a :py:class:`py.path.local` instance of the file
"""
config = self.parseconfigure(path)
@@ -611,8 +617,8 @@ class Testdir:
def genitems(self, colitems):
"""Generate all test items from a collection node.
This recurses into the collection node and returns a list of
all the test items contained within.
This recurses into the collection node and returns a list of all the
test items contained within.
"""
session = colitems[0].session
@@ -624,10 +630,10 @@ class Testdir:
def runitem(self, source):
"""Run the "test_func" Item.
The calling test instance (the class which contains the test
method) must provide a ``.getrunner()`` method which should
return a runner which can run the test protocol for a single
item, like e.g. :py:func:`_pytest.runner.runtestprotocol`.
The calling test instance (class containing the test method) must
provide a ``.getrunner()`` method which should return a runner which
can run the test protocol for a single item, e.g.
:py:func:`_pytest.runner.runtestprotocol`.
"""
# used from runner functional tests
@@ -641,14 +647,14 @@ class Testdir:
"""Run a test module in process using ``pytest.main()``.
This run writes "source" into a temporary file and runs
``pytest.main()`` on it, returning a :py:class:`HookRecorder`
instance for the result.
``pytest.main()`` on it, returning a :py:class:`HookRecorder` instance
for the result.
:param source: The source code of the test module.
:param source: the source code of the test module
:param cmdlineargs: Any extra command line arguments to use.
:param cmdlineargs: any extra command line arguments to use
:return: :py:class:`HookRecorder` instance of the result.
:return: :py:class:`HookRecorder` instance of the result
"""
p = self.makepyfile(source)
@@ -658,13 +664,9 @@ class Testdir:
def inline_genitems(self, *args):
"""Run ``pytest.main(['--collectonly'])`` in-process.
Returns a tuple of the collected items and a
:py:class:`HookRecorder` instance.
This runs the :py:func:`pytest.main` function to run all of
pytest inside the test process itself like
:py:meth:`inline_run`. However the return value is a tuple of
the collection items and a :py:class:`HookRecorder` instance.
Runs the :py:func:`pytest.main` function to run all of pytest inside
the test process itself like :py:meth:`inline_run`, but returns a
tuple of the collected items and a :py:class:`HookRecorder` instance.
"""
rec = self.inline_run("--collect-only", *args)
@@ -674,24 +676,24 @@ class Testdir:
def inline_run(self, *args, **kwargs):
"""Run ``pytest.main()`` in-process, returning a HookRecorder.
This runs the :py:func:`pytest.main` function to run all of
pytest inside the test process itself. This means it can
return a :py:class:`HookRecorder` instance which gives more
detailed results from then run then can be done by matching
stdout/stderr from :py:meth:`runpytest`.
Runs the :py:func:`pytest.main` function to run all of pytest inside
the test process itself. This means it can return a
:py:class:`HookRecorder` instance which gives more detailed results
from that run than can be done by matching stdout/stderr from
:py:meth:`runpytest`.
:param args: Any command line arguments to pass to
:py:func:`pytest.main`.
:param args: command line arguments to pass to :py:func:`pytest.main`
:param plugin: (keyword-only) Extra plugin instances the
``pytest.main()`` instance should use.
:param plugin: (keyword-only) extra plugin instances the
``pytest.main()`` instance should use
:return: a :py:class:`HookRecorder` instance
:return: A :py:class:`HookRecorder` instance.
"""
# When running py.test inline any plugins active in the main
# test process are already imported. So this disables the
# warning which will trigger to say they can no longer be
# rewritten, which is fine as they are already rewritten.
# When running py.test inline any plugins active in the main test
# process are already imported. So this disables the warning which
# will trigger to say they can no longer be rewritten, which is fine as
# they have already been rewritten.
orig_warn = AssertionRewritingHook._warn_already_imported
def revert():
@@ -717,8 +719,8 @@ class Testdir:
pass
reprec.ret = ret
# typically we reraise keyboard interrupts from the child run
# because it's our user requesting interruption of the testing
# typically we reraise keyboard interrupts from the child run because
# it's our user requesting interruption of the testing
if ret == 2 and not kwargs.get("no_reraise_ctrlc"):
calls = reprec.getcalls("pytest_keyboard_interrupt")
if calls and calls[-1].excinfo.type == KeyboardInterrupt:
@@ -726,8 +728,10 @@ class Testdir:
return reprec
def runpytest_inprocess(self, *args, **kwargs):
""" Return result of running pytest in-process, providing a similar
interface to what self.runpytest() provides. """
"""Return result of running pytest in-process, providing a similar
interface to what self.runpytest() provides.
"""
if kwargs.get("syspathinsert"):
self.syspathinsert()
now = time.time()
@@ -759,7 +763,7 @@ class Testdir:
return res
def runpytest(self, *args, **kwargs):
""" Run pytest inline or in a subprocess, depending on the command line
"""Run pytest inline or in a subprocess, depending on the command line
option "--runpytest" and return a :py:class:`RunResult`.
"""
@@ -780,13 +784,13 @@ class Testdir:
def parseconfig(self, *args):
"""Return a new pytest Config instance from given commandline args.
This invokes the pytest bootstrapping code in _pytest.config
to create a new :py:class:`_pytest.core.PluginManager` and
call the pytest_cmdline_parse hook to create new
This invokes the pytest bootstrapping code in _pytest.config to create
a new :py:class:`_pytest.core.PluginManager` and call the
pytest_cmdline_parse hook to create a new
:py:class:`_pytest.config.Config` instance.
If :py:attr:`plugins` has been populated they should be plugin
modules which will be registered with the PluginManager.
If :py:attr:`plugins` has been populated they should be plugin modules
to be registered with the PluginManager.
"""
args = self._ensure_basetemp(args)
@@ -802,9 +806,8 @@ class Testdir:
def parseconfigure(self, *args):
"""Return a new pytest configured Config instance.
This returns a new :py:class:`_pytest.config.Config` instance
like :py:meth:`parseconfig`, but also calls the
pytest_configure hook.
This returns a new :py:class:`_pytest.config.Config` instance like
:py:meth:`parseconfig`, but also calls the pytest_configure hook.
"""
config = self.parseconfig(*args)
@@ -815,14 +818,14 @@ class Testdir:
def getitem(self, source, funcname="test_func"):
"""Return the test item for a test function.
This writes the source to a python file and runs pytest's
collection on the resulting module, returning the test item
for the requested function name.
This writes the source to a python file and runs pytest's collection on
the resulting module, returning the test item for the requested
function name.
:param source: The module source.
:param source: the module source
:param funcname: The name of the test function for which the
Item must be returned.
:param funcname: the name of the test function for which to return a
test item
"""
items = self.getitems(source)
@@ -835,9 +838,8 @@ class Testdir:
def getitems(self, source):
"""Return all test items collected from the module.
This writes the source to a python file and runs pytest's
collection on the resulting module, returning all test items
contained within.
This writes the source to a python file and runs pytest's collection on
the resulting module, returning all test items contained within.
"""
modcol = self.getmodulecol(source)
@@ -846,17 +848,17 @@ class Testdir:
def getmodulecol(self, source, configargs=(), withinit=False):
"""Return the module collection node for ``source``.
This writes ``source`` to a file using :py:meth:`makepyfile`
and then runs the pytest collection on it, returning the
collection node for the test module.
This writes ``source`` to a file using :py:meth:`makepyfile` and then
runs the pytest collection on it, returning the collection node for the
test module.
:param source: The source code of the module to collect.
:param source: the source code of the module to collect
:param configargs: Any extra arguments to pass to
:py:meth:`parseconfigure`.
:param configargs: any extra arguments to pass to
:py:meth:`parseconfigure`
:param withinit: Whether to also write a ``__init__.py`` file
to the temporary directory to ensure it is a package.
:param withinit: whether to also write an ``__init__.py`` file to the
same directory to ensure it is a package
"""
kw = {self.request.function.__name__: Source(source).strip()}
@@ -871,13 +873,12 @@ class Testdir:
def collect_by_name(self, modcol, name):
"""Return the collection node for name from the module collection.
This will search a module collection node for a collection
node matching the given name.
This will search a module collection node for a collection node
matching the given name.
:param modcol: A module collection node, see
:py:meth:`getmodulecol`.
:param modcol: a module collection node; see :py:meth:`getmodulecol`
:param name: The name of the node to return.
:param name: the name of the node to return
"""
if modcol not in self._mod_collections:
@@ -889,8 +890,8 @@ class Testdir:
def popen(self, cmdargs, stdout, stderr, **kw):
"""Invoke subprocess.Popen.
This calls subprocess.Popen making sure the current working
directory is the PYTHONPATH.
This calls subprocess.Popen making sure the current working directory
is in the PYTHONPATH.
You probably want to use :py:meth:`run` instead.
@@ -908,8 +909,7 @@ class Testdir:
def run(self, *cmdargs):
"""Run a command with arguments.
Run a process using subprocess.Popen saving the stdout and
stderr.
Run a process using subprocess.Popen saving the stdout and stderr.
Returns a :py:class:`RunResult`.
@@ -952,14 +952,15 @@ class Testdir:
print("couldn't print to %s because of encoding" % (fp,))
def _getpytestargs(self):
# we cannot use "(sys.executable,script)"
# because on windows the script is e.g. a pytest.exe
# we cannot use `(sys.executable, script)` because on Windows the
# script is e.g. `pytest.exe`
return (sys.executable, PYTEST_FULLPATH) # noqa
def runpython(self, script):
"""Run a python script using sys.executable as interpreter.
Returns a :py:class:`RunResult`.
"""
return self.run(sys.executable, script)
@@ -970,25 +971,18 @@ class Testdir:
def runpytest_subprocess(self, *args, **kwargs):
"""Run pytest as a subprocess with given arguments.
Any plugins added to the :py:attr:`plugins` list will added
using the ``-p`` command line option. Addtionally
``--basetemp`` is used put any temporary files and directories
in a numbered directory prefixed with "runpytest-" so they do
not conflict with the normal numberd pytest location for
temporary files and directories.
Any plugins added to the :py:attr:`plugins` list will added using the
``-p`` command line option. Additionally ``--basetemp`` is used put
any temporary files and directories in a numbered directory prefixed
with "runpytest-" so they do not conflict with the normal numbered
pytest location for temporary files and directories.
Returns a :py:class:`RunResult`.
"""
p = py.path.local.make_numbered_dir(prefix="runpytest-",
keep=None, rootdir=self.tmpdir)
args = ('--basetemp=%s' % p, ) + args
# for x in args:
# if '--confcutdir' in str(x):
# break
# else:
# pass
# args = ('--confcutdir=.',) + args
args = ('--basetemp=%s' % p,) + args
plugins = [x for x in self.plugins if isinstance(x, str)]
if plugins:
args = ('-p', plugins[0]) + args
@@ -998,8 +992,8 @@ class Testdir:
def spawn_pytest(self, string, expect_timeout=10.0):
"""Run pytest using pexpect.
This makes sure to use the right pytest and sets up the
temporary directory locations.
This makes sure to use the right pytest and sets up the temporary
directory locations.
The pexpect child is returned.
@@ -1013,6 +1007,7 @@ class Testdir:
"""Run a command using pexpect.
The pexpect child is returned.
"""
pexpect = pytest.importorskip("pexpect", "3.0")
if hasattr(sys, 'pypy_version_info') and '64' in platform.machine():
@@ -1039,8 +1034,10 @@ class LineComp:
self.stringio = py.io.TextIO()
def assert_contains_lines(self, lines2):
""" assert that lines2 are contained (linearly) in lines1.
return a list of extralines found.
"""Assert that lines2 are contained (linearly) in lines1.
Return a list of extralines found.
"""
__tracebackhide__ = True
val = self.stringio.getvalue()
@@ -1056,8 +1053,8 @@ class LineMatcher:
This is a convenience class to test large texts like the output of
commands.
The constructor takes a list of lines without their trailing
newlines, i.e. ``text.splitlines()``.
The constructor takes a list of lines without their trailing newlines, i.e.
``text.splitlines()``.
"""
@@ -1077,18 +1074,19 @@ class LineMatcher:
return lines2
def fnmatch_lines_random(self, lines2):
"""Check lines exist in the output using ``fnmatch.fnmatch``, in any order.
"""Check lines exist in the output using in any order.
Lines are checked using ``fnmatch.fnmatch``. The argument is a list of
lines which have to occur in the output, in any order.
The argument is a list of lines which have to occur in the
output, in any order.
"""
self._match_lines_random(lines2, fnmatch)
def re_match_lines_random(self, lines2):
"""Check lines exist in the output using ``re.match``, in any order.
The argument is a list of lines which have to occur in the
output, in any order.
The argument is a list of lines which have to occur in the output, in
any order.
"""
self._match_lines_random(lines2, lambda name, pat: re.match(pat, name))
@@ -1096,8 +1094,8 @@ class LineMatcher:
def _match_lines_random(self, lines2, match_func):
"""Check lines exist in the output.
The argument is a list of lines which have to occur in the
output, in any order. Each line can contain glob whildcards.
The argument is a list of lines which have to occur in the output, in
any order. Each line can contain glob whildcards.
"""
lines2 = self._getlines(lines2)
@@ -1114,6 +1112,7 @@ class LineMatcher:
"""Return all lines following the given line in the text.
The given line can contain glob wildcards.
"""
for i, line in enumerate(self.lines):
if fnline == line or fnmatch(line, fnline):
@@ -1130,10 +1129,9 @@ class LineMatcher:
def fnmatch_lines(self, lines2):
"""Search captured text for matching lines using ``fnmatch.fnmatch``.
The argument is a list of lines which have to match and can
use glob wildcards. If they do not match a pytest.fail() is
called. The matches and non-matches are also printed on
stdout.
The argument is a list of lines which have to match and can use glob
wildcards. If they do not match a pytest.fail() is called. The
matches and non-matches are also printed on stdout.
"""
self._match_lines(lines2, fnmatch, 'fnmatch')
@@ -1144,21 +1142,22 @@ class LineMatcher:
The argument is a list of lines which have to match using ``re.match``.
If they do not match a pytest.fail() is called.
The matches and non-matches are also printed on
stdout.
The matches and non-matches are also printed on stdout.
"""
self._match_lines(lines2, lambda name, pat: re.match(pat, name), 're.match')
def _match_lines(self, lines2, match_func, match_nickname):
"""Underlying implementation of ``fnmatch_lines`` and ``re_match_lines``.
:param list[str] lines2: list of string patterns to match. The actual format depends on
``match_func``.
:param match_func: a callable ``match_func(line, pattern)`` where line is the captured
line from stdout/stderr and pattern is the matching pattern.
:param list[str] lines2: list of string patterns to match. The actual
format depends on ``match_func``
:param match_func: a callable ``match_func(line, pattern)`` where line
is the captured line from stdout/stderr and pattern is the matching
pattern
:param str match_nickname: the nickname for the match function that
will be logged to stdout when a match occurs
:param str match_nickname: the nickname for the match function that will be logged
to stdout when a match occurs.
"""
lines2 = self._getlines(lines2)
lines1 = self.lines[:]

View File

@@ -6,6 +6,7 @@ Release announcements
:maxdepth: 2
release-3.3.2
release-3.3.1
release-3.3.0
release-3.2.5

View File

@@ -0,0 +1,28 @@
pytest-3.3.2
=======================================
pytest 3.3.2 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Anthony Sottile
* Antony Lee
* Austin
* Bruno Oliveira
* Florian Bruhin
* Floris Bruynooghe
* Henk-Jaap Wagenaar
* Jurko Gospodnetić
* Ronny Pfannschmidt
* Srinivas Reddy Thatiparthy
* Thomas Hisch
Happy testing,
The pytest Development Team

View File

@@ -92,14 +92,14 @@ an example test function that performs some output related checks:
.. code-block:: python
def test_myoutput(capsys): # or use "capfd" for fd-level
print ("hello")
print("hello")
sys.stderr.write("world\n")
out, err = capsys.readouterr()
assert out == "hello\n"
assert err == "world\n"
print ("next")
out, err = capsys.readouterr()
assert out == "next\n"
captured = capsys.readouterr()
assert captured.out == "hello\n"
assert captured.err == "world\n"
print("next")
captured = capsys.readouterr()
assert captured.out == "next\n"
The ``readouterr()`` call snapshots the output so far -
and capturing will be continued. After the test
@@ -117,6 +117,10 @@ system level output streams (FD1 and FD2).
.. versionadded:: 3.3
The return value from ``readouterr`` changed to a ``namedtuple`` with two attributes, ``out`` and ``err``.
.. versionadded:: 3.3
If the code under test writes non-textual data, you can capture this using
the ``capsysbinary`` fixture which instead returns ``bytes`` from
the ``readouterr`` method. The ``capfsysbinary`` fixture is currently only

View File

@@ -385,9 +385,9 @@ Now we can profile which test functions execute the slowest::
test_some_are_slow.py ... [100%]
========================= slowest 3 test durations =========================
0.30s call test_some_are_slow.py::test_funcslow2
0.31s call test_some_are_slow.py::test_funcslow2
0.20s call test_some_are_slow.py::test_funcslow1
0.10s call test_some_are_slow.py::test_funcfast
0.17s call test_some_are_slow.py::test_funcfast
========================= 3 passed in 0.12 seconds =========================
incremental testing - test steps

View File

@@ -123,8 +123,8 @@ To get all combinations of multiple parametrized arguments you can stack
def test_foo(x, y):
pass
This will run the test with the arguments set to ``x=0/y=2``, ``x=0/y=3``, ``x=1/y=2`` and
``x=1/y=3``.
This will run the test with the arguments set to ``x=0/y=2``,``x=1/y=2``,
``x=0/y=3``, and ``x=1/y=3`` exhausting parameters in the order of the decorators.
.. _`pytest_generate_tests`:

View File

@@ -616,6 +616,7 @@ Collection hooks
``pytest`` calls the following hooks for collecting files and directories:
.. autofunction:: pytest_collection
.. autofunction:: pytest_ignore_collect
.. autofunction:: pytest_collect_directory
.. autofunction:: pytest_collect_file
@@ -687,6 +688,14 @@ Reference of objects involved in hooks
:members:
:show-inheritance:
.. autoclass:: _pytest.main.FSCollector()
:members:
:show-inheritance:
.. autoclass:: _pytest.main.Session()
:members:
:show-inheritance:
.. autoclass:: _pytest.main.Item()
:members:
:show-inheritance:

View File

@@ -151,7 +151,7 @@ def publish_release(ctx, version, user, pypi_name):
@invoke.task(help={
'version': 'version being released',
'write_out': 'write changes to the actial changelog'
'write_out': 'write changes to the actual changelog'
})
def changelog(ctx, version, write_out=False):
if write_out:

View File

@@ -3,6 +3,8 @@ from __future__ import absolute_import, division, print_function
import os
import sys
import six
import _pytest._code
import py
import pytest
@@ -215,8 +217,8 @@ class TestGeneralUsage(object):
assert not result.ret
def test_issue109_sibling_conftests_not_loaded(self, testdir):
sub1 = testdir.tmpdir.mkdir("sub1")
sub2 = testdir.tmpdir.mkdir("sub2")
sub1 = testdir.mkdir("sub1")
sub2 = testdir.mkdir("sub2")
sub1.join("conftest.py").write("assert 0")
result = testdir.runpytest(sub2)
assert result.ret == EXIT_NOTESTSCOLLECTED
@@ -603,11 +605,11 @@ class TestInvocationVariants(object):
# The structure of the test directory is now:
# .
# ├── hello
# │   └── ns_pkg
# │   ├── __init__.py
# │   └── hello
# │   ├── __init__.py
# │   └── test_hello.py
# │ └── ns_pkg
# │ ├── __init__.py
# │ └── hello
# │ ├── __init__.py
# │ └── test_hello.py
# └── world
# └── ns_pkg
# ├── __init__.py
@@ -624,10 +626,9 @@ class TestInvocationVariants(object):
for p in search_path:
monkeypatch.syspath_prepend(p)
os.chdir('world')
# mixed module and filenames:
os.chdir('world')
result = testdir.runpytest("--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world")
testdir.chdir()
assert result.ret == 0
result.stdout.fnmatch_lines([
"*test_hello.py::test_hello*PASSED*",
@@ -638,6 +639,7 @@ class TestInvocationVariants(object):
])
# specify tests within a module
testdir.chdir()
result = testdir.runpytest("--pyargs", "-v", "ns_pkg.world.test_world::test_other")
assert result.ret == 0
result.stdout.fnmatch_lines([
@@ -645,6 +647,76 @@ class TestInvocationVariants(object):
"*1 passed*"
])
@pytest.mark.skipif(not hasattr(os, "symlink"), reason="requires symlinks")
def test_cmdline_python_package_symlink(self, testdir, monkeypatch):
"""
test --pyargs option with packages with path containing symlink can
have conftest.py in their package (#2985)
"""
# dummy check that we can actually create symlinks: on Windows `os.symlink` is available,
# but normal users require special admin privileges to create symlinks.
if sys.platform == 'win32':
try:
os.symlink(str(testdir.tmpdir.ensure('tmpfile')), str(testdir.tmpdir.join('tmpfile2')))
except OSError as e:
pytest.skip(six.text_type(e.args[0]))
monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', raising=False)
search_path = ["lib", os.path.join("local", "lib")]
dirname = "lib"
d = testdir.mkdir(dirname)
foo = d.mkdir("foo")
foo.ensure("__init__.py")
lib = foo.mkdir('bar')
lib.ensure("__init__.py")
lib.join("test_bar.py"). \
write("def test_bar(): pass\n"
"def test_other(a_fixture):pass")
lib.join("conftest.py"). \
write("import pytest\n"
"@pytest.fixture\n"
"def a_fixture():pass")
d_local = testdir.mkdir("local")
symlink_location = os.path.join(str(d_local), "lib")
if six.PY2:
os.symlink(str(d), symlink_location)
else:
os.symlink(str(d), symlink_location, target_is_directory=True)
# The structure of the test directory is now:
# .
# ├── local
# │ └── lib -> ../lib
# └── lib
# └── foo
# ├── __init__.py
# └── bar
# ├── __init__.py
# ├── conftest.py
# └── test_bar.py
def join_pythonpath(*dirs):
cur = os.getenv('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)
# module picked up in symlink-ed directory:
result = testdir.runpytest("--pyargs", "-v", "foo.bar")
testdir.chdir()
assert result.ret == 0
result.stdout.fnmatch_lines([
"*lib/foo/bar/test_bar.py::test_bar*PASSED*",
"*lib/foo/bar/test_bar.py::test_other*PASSED*",
"*2 passed*"
])
def test_cmdline_python_package_not_exists(self, testdir):
result = testdir.runpytest("--pyargs", "tpkgwhatv")
assert result.ret
@@ -848,3 +920,46 @@ def test_deferred_hook_checking(testdir):
})
result = testdir.runpytest()
result.stdout.fnmatch_lines(['* 1 passed *'])
def test_fixture_values_leak(testdir):
"""Ensure that fixture objects are properly destroyed by the garbage collector at the end of their expected
life-times (#2981).
"""
testdir.makepyfile("""
import attr
import gc
import pytest
import weakref
@attr.s
class SomeObj(object):
name = attr.ib()
fix_of_test1_ref = None
session_ref = None
@pytest.fixture(scope='session')
def session_fix():
global session_ref
obj = SomeObj(name='session-fixture')
session_ref = weakref.ref(obj)
return obj
@pytest.fixture
def fix(session_fix):
global fix_of_test1_ref
obj = SomeObj(name='local-fixture')
fix_of_test1_ref = weakref.ref(obj)
return obj
def test1(fix):
assert fix_of_test1_ref() is fix
def test2():
gc.collect()
# fixture "fix" created during test1 must have been destroyed by now
assert fix_of_test1_ref() is None
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines(['* 2 passed *'])

View File

@@ -542,7 +542,7 @@ raise ValueError()
tb = FakeRawTB()
excinfo.traceback = Traceback(tb)
fail = IOError() # noqa
fail = IOError()
repr = pr.repr_excinfo(excinfo)
assert repr.reprtraceback.reprentries[0].lines[0] == "> ???"
if py.std.sys.version_info[0] >= 3:

View File

@@ -8,13 +8,10 @@ import _pytest._code
import py
import pytest
from _pytest._code import Source
from _pytest._code.source import _ast
from _pytest._code.source import ast
if _ast is not None:
astonly = pytest.mark.nothing
else:
astonly = pytest.mark.xfail("True", reason="only works with AST-compile")
astonly = pytest.mark.nothing
failsonjython = pytest.mark.xfail("sys.platform.startswith('java')")

View File

@@ -128,6 +128,16 @@ class TestAssertionRewrite(object):
assert len(m.body) == 1
assert m.body[0].msg is None
def test_dont_rewrite_plugin(self, testdir):
contents = {
"conftest.py": "pytest_plugins = 'plugin'; import plugin",
"plugin.py": "'PYTEST_DONT_REWRITE'",
"test_foo.py": "def test_foo(): pass",
}
testdir.makepyfile(**contents)
result = testdir.runpytest_subprocess()
assert "warnings" not in "".join(result.outlines)
def test_name(self):
def f():
assert False

View File

@@ -84,7 +84,7 @@ def test_conftest_in_nonpkg_with_init(tmpdir):
def test_doubledash_considered(testdir):
conf = testdir.mkdir("--option")
conf.join("conftest.py").ensure()
conf.ensure("conftest.py")
conftest = PytestPluginManager()
conftest_setinitial(conftest, [conf.basename, conf.basename])
values = conftest._getconftestmodules(conf)
@@ -270,11 +270,7 @@ def test_conftest_found_with_double_dash(testdir):
parser.addoption("--hello-world", action="store_true")
"""))
p = sub.join("test_hello.py")
p.write(py.std.textwrap.dedent("""
import pytest
def test_hello(found):
assert found == 1
"""))
p.write("def test_hello(): pass")
result = testdir.runpytest(str(p) + "::test_hello", "-h")
result.stdout.fnmatch_lines("""
*--hello-world*

View File

@@ -338,7 +338,7 @@ class TestPDB(object):
self.flush(child)
def test_pdb_collection_failure_is_shown(self, testdir):
p1 = testdir.makepyfile("""xxx """)
p1 = testdir.makepyfile("xxx")
result = testdir.runpytest_subprocess("--pdb", p1)
result.stdout.fnmatch_lines([
"*NameError*xxx*",

View File

@@ -141,7 +141,7 @@ def test_inline_run_clean_modules(testdir):
assert result2.ret == EXIT_TESTSFAILED
def test_assert_outcomes_after_pytest_erro(testdir):
def test_assert_outcomes_after_pytest_error(testdir):
testdir.makepyfile("def test_foo(): assert True")
result = testdir.runpytest('--unexpected-argument')

View File

@@ -32,7 +32,7 @@ class TestSetupState(object):
def setup_module(mod):
raise ValueError(42)
def test_func(): pass
""") # noqa
""")
ss = runner.SetupState()
pytest.raises(ValueError, lambda: ss.prepare(item))
pytest.raises(ValueError, lambda: ss.prepare(item))
@@ -564,7 +564,7 @@ def test_importorskip(monkeypatch):
importorskip("asdlkj")
try:
sys = importorskip("sys") # noqa
sys = importorskip("sys")
assert sys == py.std.sys
# path = pytest.importorskip("os.path")
# assert path == py.std.os.path

View File

@@ -589,7 +589,7 @@ class TestSkipif(object):
@pytest.mark.skipif("hasattr(os, 'sep')")
def test_func():
pass
""") # noqa
""")
x = pytest.raises(pytest.skip.Exception, lambda:
pytest_runtest_setup(item))
assert x.value.msg == "condition: hasattr(os, 'sep')"

View File

@@ -138,7 +138,7 @@ commands =
basepython = python
usedevelop = True
skipsdist = True
# ensure the given pyargs cant mean anytrhing else
# ensure the given pyargs can't mean anything else
changedir = doc/
deps =
PyYAML