Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd2d0d2c3c | ||
|
|
08997279f4 | ||
|
|
205e29d843 | ||
|
|
672c901c70 | ||
|
|
d370e7788d | ||
|
|
ed293ec3e9 | ||
|
|
2f8427bb4e | ||
|
|
30729b7c3c | ||
|
|
dfc5399cd7 | ||
|
|
76489d30f7 | ||
|
|
d6f75d2836 | ||
|
|
28a93b9eeb | ||
|
|
70461d1ead | ||
|
|
73eccb4c36 | ||
|
|
d7a76a4d07 | ||
|
|
924b5e2e3d | ||
|
|
c90e76c371 | ||
|
|
586ecea6f2 | ||
|
|
db4df5833a | ||
|
|
b17c6e5f89 | ||
|
|
c3f63ac143 | ||
|
|
3862b0b28d | ||
|
|
476d4df1b7 | ||
|
|
52449903c3 | ||
|
|
506c9c91c0 | ||
|
|
38f34e2fa1 | ||
|
|
ba41015ef6 | ||
|
|
ebfc1c49d1 | ||
|
|
45e7734b1a | ||
|
|
8ce6e39b1c | ||
|
|
c8e7d1ae34 | ||
|
|
7b5d4d01ed | ||
|
|
a4f4579f19 | ||
|
|
e4da9bacdf | ||
|
|
1e295535c3 | ||
|
|
3ca1e4b7f0 | ||
|
|
dc19624248 | ||
|
|
5c6d7739bc | ||
|
|
771b5c8852 | ||
|
|
fc5ec5807e | ||
|
|
fc544dc660 | ||
|
|
7792587b3f | ||
|
|
c2cd239d35 | ||
|
|
cb0ba18f53 | ||
|
|
3b85e0c3a9 | ||
|
|
73bc6bacfa | ||
|
|
8e8a953ac6 | ||
|
|
852b96714e | ||
|
|
dd64f1a4a9 | ||
|
|
41a6ec6f31 | ||
|
|
7feab7391d | ||
|
|
f0bfe9de3d | ||
|
|
596937e610 | ||
|
|
57fcd3f57e | ||
|
|
65f5383106 | ||
|
|
964c29cb93 | ||
|
|
38fb6aae78 | ||
|
|
1c5b887dfd | ||
|
|
ba209b5230 | ||
|
|
b62fd79c0c | ||
|
|
88f2cc9b64 | ||
|
|
ed2bb9d723 | ||
|
|
1f0d06641a | ||
|
|
4c62cd451a |
1
AUTHORS
1
AUTHORS
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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__]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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[:]
|
||||
|
||||
@@ -6,6 +6,7 @@ Release announcements
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
release-3.3.2
|
||||
release-3.3.1
|
||||
release-3.3.0
|
||||
release-3.2.5
|
||||
|
||||
28
doc/en/announce/release-3.3.2.rst
Normal file
28
doc/en/announce/release-3.3.2.rst
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`:
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 *'])
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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')")
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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*
|
||||
|
||||
@@ -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*",
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')"
|
||||
|
||||
Reference in New Issue
Block a user