aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/pytest/py2/_pytest
diff options
context:
space:
mode:
authordeshevoy <deshevoy@yandex-team.ru>2022-02-10 16:46:57 +0300
committerDaniil Cherednik <dcherednik@yandex-team.ru>2022-02-10 16:46:57 +0300
commit28148f76dbfcc644d96427d41c92f36cbf2fdc6e (patch)
treeb83306b6e37edeea782e9eed673d89286c4fef35 /contrib/python/pytest/py2/_pytest
parente988f30484abe5fdeedcc7a5d3c226c01a21800c (diff)
downloadydb-28148f76dbfcc644d96427d41c92f36cbf2fdc6e.tar.gz
Restoring authorship annotation for <deshevoy@yandex-team.ru>. Commit 2 of 2.
Diffstat (limited to 'contrib/python/pytest/py2/_pytest')
-rw-r--r--contrib/python/pytest/py2/_pytest/__init__.py16
-rw-r--r--contrib/python/pytest/py2/_pytest/_argcomplete.py218
-rw-r--r--contrib/python/pytest/py2/_pytest/_code/__init__.py28
-rw-r--r--contrib/python/pytest/py2/_pytest/_code/_py2traceback.py188
-rw-r--r--contrib/python/pytest/py2/_pytest/_code/code.py1988
-rw-r--r--contrib/python/pytest/py2/_pytest/_code/source.py634
-rw-r--r--contrib/python/pytest/py2/_pytest/_version.py6
-rw-r--r--contrib/python/pytest/py2/_pytest/assertion/__init__.py310
-rw-r--r--contrib/python/pytest/py2/_pytest/assertion/rewrite.py1982
-rw-r--r--contrib/python/pytest/py2/_pytest/assertion/truncate.py202
-rw-r--r--contrib/python/pytest/py2/_pytest/assertion/util.py560
-rw-r--r--contrib/python/pytest/py2/_pytest/cacheprovider.py660
-rw-r--r--contrib/python/pytest/py2/_pytest/capture.py1448
-rw-r--r--contrib/python/pytest/py2/_pytest/compat.py856
-rw-r--r--contrib/python/pytest/py2/_pytest/config/__init__.py1848
-rw-r--r--contrib/python/pytest/py2/_pytest/config/argparsing.py794
-rw-r--r--contrib/python/pytest/py2/_pytest/config/exceptions.py18
-rw-r--r--contrib/python/pytest/py2/_pytest/config/findpaths.py242
-rw-r--r--contrib/python/pytest/py2/_pytest/debugging.py368
-rw-r--r--contrib/python/pytest/py2/_pytest/deprecated.py98
-rw-r--r--contrib/python/pytest/py2/_pytest/doctest.py1028
-rw-r--r--contrib/python/pytest/py2/_pytest/fixtures.py2536
-rw-r--r--contrib/python/pytest/py2/_pytest/freeze_support.py94
-rw-r--r--contrib/python/pytest/py2/_pytest/helpconfig.py408
-rw-r--r--contrib/python/pytest/py2/_pytest/hookspec.py1172
-rw-r--r--contrib/python/pytest/py2/_pytest/junitxml.py1048
-rw-r--r--contrib/python/pytest/py2/_pytest/logging.py1110
-rw-r--r--contrib/python/pytest/py2/_pytest/main.py1378
-rw-r--r--contrib/python/pytest/py2/_pytest/mark/__init__.py318
-rw-r--r--contrib/python/pytest/py2/_pytest/mark/evaluate.py250
-rw-r--r--contrib/python/pytest/py2/_pytest/mark/legacy.py194
-rw-r--r--contrib/python/pytest/py2/_pytest/mark/structures.py628
-rw-r--r--contrib/python/pytest/py2/_pytest/monkeypatch.py622
-rw-r--r--contrib/python/pytest/py2/_pytest/nodes.py820
-rw-r--r--contrib/python/pytest/py2/_pytest/nose.py126
-rw-r--r--contrib/python/pytest/py2/_pytest/outcomes.py318
-rw-r--r--contrib/python/pytest/py2/_pytest/pastebin.py216
-rw-r--r--contrib/python/pytest/py2/_pytest/pathlib.py620
-rw-r--r--contrib/python/pytest/py2/_pytest/pytester.py2566
-rw-r--r--contrib/python/pytest/py2/_pytest/python.py2432
-rw-r--r--contrib/python/pytest/py2/_pytest/python_api.py1334
-rw-r--r--contrib/python/pytest/py2/_pytest/recwarn.py470
-rw-r--r--contrib/python/pytest/py2/_pytest/reports.py374
-rw-r--r--contrib/python/pytest/py2/_pytest/resultlog.py192
-rw-r--r--contrib/python/pytest/py2/_pytest/runner.py658
-rw-r--r--contrib/python/pytest/py2/_pytest/setuponly.py176
-rw-r--r--contrib/python/pytest/py2/_pytest/setupplan.py62
-rw-r--r--contrib/python/pytest/py2/_pytest/skipping.py364
-rw-r--r--contrib/python/pytest/py2/_pytest/stepwise.py184
-rw-r--r--contrib/python/pytest/py2/_pytest/terminal.py1550
-rw-r--r--contrib/python/pytest/py2/_pytest/tmpdir.py346
-rw-r--r--contrib/python/pytest/py2/_pytest/unittest.py474
-rw-r--r--contrib/python/pytest/py2/_pytest/warning_types.py108
-rw-r--r--contrib/python/pytest/py2/_pytest/warnings.py348
54 files changed, 18494 insertions, 18494 deletions
diff --git a/contrib/python/pytest/py2/_pytest/__init__.py b/contrib/python/pytest/py2/_pytest/__init__.py
index 42eb1578e2..17cc20b615 100644
--- a/contrib/python/pytest/py2/_pytest/__init__.py
+++ b/contrib/python/pytest/py2/_pytest/__init__.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-__all__ = ["__version__"]
-
-try:
- from ._version import version as __version__
-except ImportError:
- # broken installation, we don't even try
- # unknown only works because we do poor mans version compare
- __version__ = "unknown"
+__all__ = ["__version__"]
+
+try:
+ from ._version import version as __version__
+except ImportError:
+ # broken installation, we don't even try
+ # unknown only works because we do poor mans version compare
+ __version__ = "unknown"
diff --git a/contrib/python/pytest/py2/_pytest/_argcomplete.py b/contrib/python/pytest/py2/_pytest/_argcomplete.py
index ba7bc89b70..c6cf1d8fdd 100644
--- a/contrib/python/pytest/py2/_pytest/_argcomplete.py
+++ b/contrib/python/pytest/py2/_pytest/_argcomplete.py
@@ -1,110 +1,110 @@
# -*- coding: utf-8 -*-
-"""allow bash-completion for argparse with argcomplete if installed
-needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail
-to find the magic string, so _ARGCOMPLETE env. var is never set, and
-this does not need special code.
-
-Function try_argcomplete(parser) should be called directly before
-the call to ArgumentParser.parse_args().
-
-The filescompleter is what you normally would use on the positional
-arguments specification, in order to get "dirname/" after "dirn<TAB>"
-instead of the default "dirname ":
-
- optparser.add_argument(Config._file_or_dir, nargs='*'
- ).completer=filescompleter
-
-Other, application specific, completers should go in the file
-doing the add_argument calls as they need to be specified as .completer
-attributes as well. (If argcomplete is not installed, the function the
-attribute points to will not be used).
-
-SPEEDUP
-=======
-The generic argcomplete script for bash-completion
-(/etc/bash_completion.d/python-argcomplete.sh )
-uses a python program to determine startup script generated by pip.
-You can speed up completion somewhat by changing this script to include
- # PYTHON_ARGCOMPLETE_OK
-so the the python-argcomplete-check-easy-install-script does not
-need to be called to find the entry point of the code and see if that is
-marked with PYTHON_ARGCOMPLETE_OK
-
-INSTALL/DEBUGGING
-=================
-To include this support in another application that has setup.py generated
-scripts:
-- add the line:
- # PYTHON_ARGCOMPLETE_OK
- near the top of the main python entry point
-- include in the file calling parse_args():
- from _argcomplete import try_argcomplete, filescompleter
- , call try_argcomplete just before parse_args(), and optionally add
- filescompleter to the positional arguments' add_argument()
-If things do not work right away:
-- switch on argcomplete debugging with (also helpful when doing custom
- completers):
- export _ARC_DEBUG=1
-- run:
- python-argcomplete-check-easy-install-script $(which appname)
- echo $?
- will echo 0 if the magic line has been found, 1 if not
-- sometimes it helps to find early on errors using:
- _ARGCOMPLETE=1 _ARC_DEBUG=1 appname
- which should throw a KeyError: 'COMPLINE' (which is properly set by the
- global argcomplete script).
-"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import os
-import sys
-from glob import glob
-
-
-class FastFilesCompleter(object):
- "Fast file completer class"
-
- def __init__(self, directories=True):
- self.directories = directories
-
- def __call__(self, prefix, **kwargs):
- """only called on non option completions"""
- if os.path.sep in prefix[1:]:
- prefix_dir = len(os.path.dirname(prefix) + os.path.sep)
- else:
- prefix_dir = 0
- completion = []
- globbed = []
- if "*" not in prefix and "?" not in prefix:
- # we are on unix, otherwise no bash
- if not prefix or prefix[-1] == os.path.sep:
- globbed.extend(glob(prefix + ".*"))
- prefix += "*"
- globbed.extend(glob(prefix))
- for x in sorted(globbed):
- if os.path.isdir(x):
- x += "/"
- # append stripping the prefix (like bash, not like compgen)
- completion.append(x[prefix_dir:])
- return completion
-
-
-if os.environ.get("_ARGCOMPLETE"):
- try:
- import argcomplete.completers
- except ImportError:
- sys.exit(-1)
- filescompleter = FastFilesCompleter()
-
- def try_argcomplete(parser):
- argcomplete.autocomplete(parser, always_complete_options=False)
-
-
-else:
-
- def try_argcomplete(parser):
- pass
-
- filescompleter = None
+"""allow bash-completion for argparse with argcomplete if installed
+needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail
+to find the magic string, so _ARGCOMPLETE env. var is never set, and
+this does not need special code.
+
+Function try_argcomplete(parser) should be called directly before
+the call to ArgumentParser.parse_args().
+
+The filescompleter is what you normally would use on the positional
+arguments specification, in order to get "dirname/" after "dirn<TAB>"
+instead of the default "dirname ":
+
+ optparser.add_argument(Config._file_or_dir, nargs='*'
+ ).completer=filescompleter
+
+Other, application specific, completers should go in the file
+doing the add_argument calls as they need to be specified as .completer
+attributes as well. (If argcomplete is not installed, the function the
+attribute points to will not be used).
+
+SPEEDUP
+=======
+The generic argcomplete script for bash-completion
+(/etc/bash_completion.d/python-argcomplete.sh )
+uses a python program to determine startup script generated by pip.
+You can speed up completion somewhat by changing this script to include
+ # PYTHON_ARGCOMPLETE_OK
+so the the python-argcomplete-check-easy-install-script does not
+need to be called to find the entry point of the code and see if that is
+marked with PYTHON_ARGCOMPLETE_OK
+
+INSTALL/DEBUGGING
+=================
+To include this support in another application that has setup.py generated
+scripts:
+- add the line:
+ # PYTHON_ARGCOMPLETE_OK
+ near the top of the main python entry point
+- include in the file calling parse_args():
+ from _argcomplete import try_argcomplete, filescompleter
+ , call try_argcomplete just before parse_args(), and optionally add
+ filescompleter to the positional arguments' add_argument()
+If things do not work right away:
+- switch on argcomplete debugging with (also helpful when doing custom
+ completers):
+ export _ARC_DEBUG=1
+- run:
+ python-argcomplete-check-easy-install-script $(which appname)
+ echo $?
+ will echo 0 if the magic line has been found, 1 if not
+- sometimes it helps to find early on errors using:
+ _ARGCOMPLETE=1 _ARC_DEBUG=1 appname
+ which should throw a KeyError: 'COMPLINE' (which is properly set by the
+ global argcomplete script).
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import os
+import sys
+from glob import glob
+
+
+class FastFilesCompleter(object):
+ "Fast file completer class"
+
+ def __init__(self, directories=True):
+ self.directories = directories
+
+ def __call__(self, prefix, **kwargs):
+ """only called on non option completions"""
+ if os.path.sep in prefix[1:]:
+ prefix_dir = len(os.path.dirname(prefix) + os.path.sep)
+ else:
+ prefix_dir = 0
+ completion = []
+ globbed = []
+ if "*" not in prefix and "?" not in prefix:
+ # we are on unix, otherwise no bash
+ if not prefix or prefix[-1] == os.path.sep:
+ globbed.extend(glob(prefix + ".*"))
+ prefix += "*"
+ globbed.extend(glob(prefix))
+ for x in sorted(globbed):
+ if os.path.isdir(x):
+ x += "/"
+ # append stripping the prefix (like bash, not like compgen)
+ completion.append(x[prefix_dir:])
+ return completion
+
+
+if os.environ.get("_ARGCOMPLETE"):
+ try:
+ import argcomplete.completers
+ except ImportError:
+ sys.exit(-1)
+ filescompleter = FastFilesCompleter()
+
+ def try_argcomplete(parser):
+ argcomplete.autocomplete(parser, always_complete_options=False)
+
+
+else:
+
+ def try_argcomplete(parser):
+ pass
+
+ filescompleter = None
diff --git a/contrib/python/pytest/py2/_pytest/_code/__init__.py b/contrib/python/pytest/py2/_pytest/_code/__init__.py
index 654aaac4b0..1394b2b10e 100644
--- a/contrib/python/pytest/py2/_pytest/_code/__init__.py
+++ b/contrib/python/pytest/py2/_pytest/_code/__init__.py
@@ -1,15 +1,15 @@
# -*- coding: utf-8 -*-
-""" python inspection/code generation API """
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-from .code import Code # noqa
-from .code import ExceptionInfo # noqa
-from .code import filter_traceback # noqa
-from .code import Frame # noqa
-from .code import getrawcode # noqa
-from .code import Traceback # noqa
-from .source import compile_ as compile # noqa
-from .source import getfslineno # noqa
-from .source import Source # noqa
+""" python inspection/code generation API """
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+from .code import Code # noqa
+from .code import ExceptionInfo # noqa
+from .code import filter_traceback # noqa
+from .code import Frame # noqa
+from .code import getrawcode # noqa
+from .code import Traceback # noqa
+from .source import compile_ as compile # noqa
+from .source import getfslineno # noqa
+from .source import Source # noqa
diff --git a/contrib/python/pytest/py2/_pytest/_code/_py2traceback.py b/contrib/python/pytest/py2/_pytest/_code/_py2traceback.py
index 7697d9c502..faacc02166 100644
--- a/contrib/python/pytest/py2/_pytest/_code/_py2traceback.py
+++ b/contrib/python/pytest/py2/_pytest/_code/_py2traceback.py
@@ -1,95 +1,95 @@
# -*- coding: utf-8 -*-
-# copied from python-2.7.3's traceback.py
-# CHANGES:
-# - some_str is replaced, trying to create unicode strings
-#
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-from __future__ import unicode_literals
-
-import types
-
-from six import text_type
-
-
-def format_exception_only(etype, value):
- """Format the exception part of a traceback.
-
- The arguments are the exception type and value such as given by
- sys.last_type and sys.last_value. The return value is a list of
- strings, each ending in a newline.
-
- Normally, the list contains a single string; however, for
- SyntaxError exceptions, it contains several lines that (when
- printed) display detailed information about where the syntax
- error occurred.
-
- The message indicating which exception occurred is always the last
- string in the list.
-
- """
-
- # An instance should not have a meaningful value parameter, but
- # sometimes does, particularly for string exceptions, such as
- # >>> raise string1, string2 # deprecated
- #
- # Clear these out first because issubtype(string1, SyntaxError)
- # would throw another exception and mask the original problem.
- if (
- isinstance(etype, BaseException)
- or isinstance(etype, types.InstanceType)
- or etype is None
- or type(etype) is str
- ):
- return [_format_final_exc_line(etype, value)]
-
- stype = etype.__name__
-
- if not issubclass(etype, SyntaxError):
- return [_format_final_exc_line(stype, value)]
-
- # It was a syntax error; show exactly where the problem was found.
- lines = []
- try:
- msg, (filename, lineno, offset, badline) = value.args
- except Exception:
- pass
- else:
- filename = filename or "<string>"
- lines.append(' File "{}", line {}\n'.format(filename, lineno))
- if badline is not None:
- if isinstance(badline, bytes): # python 2 only
- badline = badline.decode("utf-8", "replace")
- lines.append(" {}\n".format(badline.strip()))
- if offset is not None:
- caretspace = badline.rstrip("\n")[:offset].lstrip()
- # non-space whitespace (likes tabs) must be kept for alignment
- caretspace = ((c.isspace() and c or " ") for c in caretspace)
- # only three spaces to account for offset1 == pos 0
- lines.append(" {}^\n".format("".join(caretspace)))
- value = msg
-
- lines.append(_format_final_exc_line(stype, value))
- return lines
-
-
-def _format_final_exc_line(etype, value):
- """Return a list of a single line -- normal case for format_exception_only"""
- valuestr = _some_str(value)
- if value is None or not valuestr:
- line = "{}\n".format(etype)
- else:
- line = "{}: {}\n".format(etype, valuestr)
- return line
-
-
-def _some_str(value):
- try:
- return text_type(value)
- except Exception:
- try:
- return bytes(value).decode("UTF-8", "replace")
- except Exception:
- pass
- return "<unprintable {} object>".format(type(value).__name__)
+# copied from python-2.7.3's traceback.py
+# CHANGES:
+# - some_str is replaced, trying to create unicode strings
+#
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import types
+
+from six import text_type
+
+
+def format_exception_only(etype, value):
+ """Format the exception part of a traceback.
+
+ The arguments are the exception type and value such as given by
+ sys.last_type and sys.last_value. The return value is a list of
+ strings, each ending in a newline.
+
+ Normally, the list contains a single string; however, for
+ SyntaxError exceptions, it contains several lines that (when
+ printed) display detailed information about where the syntax
+ error occurred.
+
+ The message indicating which exception occurred is always the last
+ string in the list.
+
+ """
+
+ # An instance should not have a meaningful value parameter, but
+ # sometimes does, particularly for string exceptions, such as
+ # >>> raise string1, string2 # deprecated
+ #
+ # Clear these out first because issubtype(string1, SyntaxError)
+ # would throw another exception and mask the original problem.
+ if (
+ isinstance(etype, BaseException)
+ or isinstance(etype, types.InstanceType)
+ or etype is None
+ or type(etype) is str
+ ):
+ return [_format_final_exc_line(etype, value)]
+
+ stype = etype.__name__
+
+ if not issubclass(etype, SyntaxError):
+ return [_format_final_exc_line(stype, value)]
+
+ # It was a syntax error; show exactly where the problem was found.
+ lines = []
+ try:
+ msg, (filename, lineno, offset, badline) = value.args
+ except Exception:
+ pass
+ else:
+ filename = filename or "<string>"
+ lines.append(' File "{}", line {}\n'.format(filename, lineno))
+ if badline is not None:
+ if isinstance(badline, bytes): # python 2 only
+ badline = badline.decode("utf-8", "replace")
+ lines.append(" {}\n".format(badline.strip()))
+ if offset is not None:
+ caretspace = badline.rstrip("\n")[:offset].lstrip()
+ # non-space whitespace (likes tabs) must be kept for alignment
+ caretspace = ((c.isspace() and c or " ") for c in caretspace)
+ # only three spaces to account for offset1 == pos 0
+ lines.append(" {}^\n".format("".join(caretspace)))
+ value = msg
+
+ lines.append(_format_final_exc_line(stype, value))
+ return lines
+
+
+def _format_final_exc_line(etype, value):
+ """Return a list of a single line -- normal case for format_exception_only"""
+ valuestr = _some_str(value)
+ if value is None or not valuestr:
+ line = "{}\n".format(etype)
+ else:
+ line = "{}: {}\n".format(etype, valuestr)
+ return line
+
+
+def _some_str(value):
+ try:
+ return text_type(value)
+ except Exception:
+ try:
+ return bytes(value).decode("UTF-8", "replace")
+ except Exception:
+ pass
+ return "<unprintable {} object>".format(type(value).__name__)
diff --git a/contrib/python/pytest/py2/_pytest/_code/code.py b/contrib/python/pytest/py2/_pytest/_code/code.py
index b8ebe7a1d3..175d6fda01 100644
--- a/contrib/python/pytest/py2/_pytest/_code/code.py
+++ b/contrib/python/pytest/py2/_pytest/_code/code.py
@@ -1,408 +1,408 @@
# -*- coding: utf-8 -*-
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import inspect
-import re
-import sys
-import traceback
-from inspect import CO_VARARGS
-from inspect import CO_VARKEYWORDS
-from weakref import ref
-
-import attr
-import pluggy
-import py
-from six import text_type
-
-import _pytest
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import inspect
+import re
+import sys
+import traceback
+from inspect import CO_VARARGS
+from inspect import CO_VARKEYWORDS
+from weakref import ref
+
+import attr
+import pluggy
+import py
+from six import text_type
+
+import _pytest
from _pytest._io.saferepr import safeformat
from _pytest._io.saferepr import saferepr
-from _pytest.compat import _PY2
-from _pytest.compat import _PY3
-from _pytest.compat import PY35
-from _pytest.compat import safe_str
-
-if _PY3:
- from traceback import format_exception_only
-else:
- from ._py2traceback import format_exception_only
-
-
-class Code(object):
- """ wrapper around Python code objects """
-
- def __init__(self, rawcode):
- if not hasattr(rawcode, "co_filename"):
- rawcode = getrawcode(rawcode)
- try:
- self.filename = rawcode.co_filename
- self.firstlineno = rawcode.co_firstlineno - 1
- self.name = rawcode.co_name
- except AttributeError:
- raise TypeError("not a code object: %r" % (rawcode,))
- self.raw = rawcode
-
- def __eq__(self, other):
- return self.raw == other.raw
-
- __hash__ = None
-
- def __ne__(self, other):
- return not self == other
-
- @property
- def path(self):
- """ return a path object pointing to source code (note that it
- might not point to an actually existing file). """
- try:
- p = py.path.local(self.raw.co_filename)
- # maybe don't try this checking
- if not p.check():
- raise OSError("py.path check failed.")
- except OSError:
- # XXX maybe try harder like the weird logic
- # in the standard lib [linecache.updatecache] does?
- p = self.raw.co_filename
-
- return p
-
- @property
- def fullsource(self):
- """ return a _pytest._code.Source object for the full source file of the code
- """
- from _pytest._code import source
-
- full, _ = source.findsource(self.raw)
- return full
-
- def source(self):
- """ return a _pytest._code.Source object for the code object's source only
- """
- # return source only for that part of code
- import _pytest._code
-
- return _pytest._code.Source(self.raw)
-
- def getargs(self, var=False):
- """ return a tuple with the argument names for the code object
-
- if 'var' is set True also return the names of the variable and
- keyword arguments when present
- """
- # handfull shortcut for getting args
- raw = self.raw
- argcount = raw.co_argcount
- if var:
- argcount += raw.co_flags & CO_VARARGS
- argcount += raw.co_flags & CO_VARKEYWORDS
- return raw.co_varnames[:argcount]
-
-
-class Frame(object):
- """Wrapper around a Python frame holding f_locals and f_globals
- in which expressions can be evaluated."""
-
- def __init__(self, frame):
- self.lineno = frame.f_lineno - 1
- self.f_globals = frame.f_globals
- self.f_locals = frame.f_locals
- self.raw = frame
- self.code = Code(frame.f_code)
-
- @property
- def statement(self):
- """ statement this frame is at """
- import _pytest._code
-
- if self.code.fullsource is None:
- return _pytest._code.Source("")
- return self.code.fullsource.getstatement(self.lineno)
-
- def eval(self, code, **vars):
- """ evaluate 'code' in the frame
-
- 'vars' are optional additional local variables
-
- returns the result of the evaluation
- """
- f_locals = self.f_locals.copy()
- f_locals.update(vars)
- return eval(code, self.f_globals, f_locals)
-
- def exec_(self, code, **vars):
- """ exec 'code' in the frame
-
- 'vars' are optiona; additional local variables
- """
- f_locals = self.f_locals.copy()
- f_locals.update(vars)
+from _pytest.compat import _PY2
+from _pytest.compat import _PY3
+from _pytest.compat import PY35
+from _pytest.compat import safe_str
+
+if _PY3:
+ from traceback import format_exception_only
+else:
+ from ._py2traceback import format_exception_only
+
+
+class Code(object):
+ """ wrapper around Python code objects """
+
+ def __init__(self, rawcode):
+ if not hasattr(rawcode, "co_filename"):
+ rawcode = getrawcode(rawcode)
+ try:
+ self.filename = rawcode.co_filename
+ self.firstlineno = rawcode.co_firstlineno - 1
+ self.name = rawcode.co_name
+ except AttributeError:
+ raise TypeError("not a code object: %r" % (rawcode,))
+ self.raw = rawcode
+
+ def __eq__(self, other):
+ return self.raw == other.raw
+
+ __hash__ = None
+
+ def __ne__(self, other):
+ return not self == other
+
+ @property
+ def path(self):
+ """ return a path object pointing to source code (note that it
+ might not point to an actually existing file). """
+ try:
+ p = py.path.local(self.raw.co_filename)
+ # maybe don't try this checking
+ if not p.check():
+ raise OSError("py.path check failed.")
+ except OSError:
+ # XXX maybe try harder like the weird logic
+ # in the standard lib [linecache.updatecache] does?
+ p = self.raw.co_filename
+
+ return p
+
+ @property
+ def fullsource(self):
+ """ return a _pytest._code.Source object for the full source file of the code
+ """
+ from _pytest._code import source
+
+ full, _ = source.findsource(self.raw)
+ return full
+
+ def source(self):
+ """ return a _pytest._code.Source object for the code object's source only
+ """
+ # return source only for that part of code
+ import _pytest._code
+
+ return _pytest._code.Source(self.raw)
+
+ def getargs(self, var=False):
+ """ return a tuple with the argument names for the code object
+
+ if 'var' is set True also return the names of the variable and
+ keyword arguments when present
+ """
+ # handfull shortcut for getting args
+ raw = self.raw
+ argcount = raw.co_argcount
+ if var:
+ argcount += raw.co_flags & CO_VARARGS
+ argcount += raw.co_flags & CO_VARKEYWORDS
+ return raw.co_varnames[:argcount]
+
+
+class Frame(object):
+ """Wrapper around a Python frame holding f_locals and f_globals
+ in which expressions can be evaluated."""
+
+ def __init__(self, frame):
+ self.lineno = frame.f_lineno - 1
+ self.f_globals = frame.f_globals
+ self.f_locals = frame.f_locals
+ self.raw = frame
+ self.code = Code(frame.f_code)
+
+ @property
+ def statement(self):
+ """ statement this frame is at """
+ import _pytest._code
+
+ if self.code.fullsource is None:
+ return _pytest._code.Source("")
+ return self.code.fullsource.getstatement(self.lineno)
+
+ def eval(self, code, **vars):
+ """ evaluate 'code' in the frame
+
+ 'vars' are optional additional local variables
+
+ returns the result of the evaluation
+ """
+ f_locals = self.f_locals.copy()
+ f_locals.update(vars)
+ return eval(code, self.f_globals, f_locals)
+
+ def exec_(self, code, **vars):
+ """ exec 'code' in the frame
+
+ 'vars' are optiona; additional local variables
+ """
+ f_locals = self.f_locals.copy()
+ f_locals.update(vars)
exec(code, self.f_globals, f_locals)
-
- def repr(self, object):
- """ return a 'safe' (non-recursive, one-line) string repr for 'object'
- """
+
+ def repr(self, object):
+ """ return a 'safe' (non-recursive, one-line) string repr for 'object'
+ """
return saferepr(object)
-
- def is_true(self, object):
- return object
-
- def getargs(self, var=False):
- """ return a list of tuples (name, value) for all arguments
-
- if 'var' is set True also include the variable and keyword
- arguments when present
- """
- retval = []
- for arg in self.code.getargs(var):
- try:
- retval.append((arg, self.f_locals[arg]))
- except KeyError:
- pass # this can occur when using Psyco
- return retval
-
-
-class TracebackEntry(object):
- """ a single entry in a traceback """
-
- _repr_style = None
- exprinfo = None
-
- def __init__(self, rawentry, excinfo=None):
- self._excinfo = excinfo
- self._rawentry = rawentry
- self.lineno = rawentry.tb_lineno - 1
-
- def set_repr_style(self, mode):
- assert mode in ("short", "long")
- self._repr_style = mode
-
- @property
- def frame(self):
- import _pytest._code
-
- return _pytest._code.Frame(self._rawentry.tb_frame)
-
- @property
- def relline(self):
- return self.lineno - self.frame.code.firstlineno
-
- def __repr__(self):
- return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1)
-
- @property
- def statement(self):
- """ _pytest._code.Source object for the current statement """
- source = self.frame.code.fullsource
- return source.getstatement(self.lineno)
-
- @property
- def path(self):
- """ path to the source code """
- return self.frame.code.path
-
- def getlocals(self):
- return self.frame.f_locals
-
- locals = property(getlocals, None, None, "locals of underlaying frame")
-
- def getfirstlinesource(self):
- # on Jython this firstlineno can be -1 apparently
- return max(self.frame.code.firstlineno, 0)
-
- def getsource(self, astcache=None):
- """ return failing source code. """
- # we use the passed in astcache to not reparse asttrees
- # within exception info printing
- from _pytest._code.source import getstatementrange_ast
-
- source = self.frame.code.fullsource
- if source is None:
- return None
- key = astnode = None
- if astcache is not None:
- key = self.frame.code.path
- if key is not None:
- astnode = astcache.get(key, None)
- start = self.getfirstlinesource()
- try:
- astnode, _, end = getstatementrange_ast(
- self.lineno, source, astnode=astnode
- )
- except SyntaxError:
- end = self.lineno + 1
- else:
- if key is not None:
- astcache[key] = astnode
- return source[start:end]
-
- source = property(getsource)
-
- def ishidden(self):
- """ return True if the current frame has a var __tracebackhide__
+
+ def is_true(self, object):
+ return object
+
+ def getargs(self, var=False):
+ """ return a list of tuples (name, value) for all arguments
+
+ if 'var' is set True also include the variable and keyword
+ arguments when present
+ """
+ retval = []
+ for arg in self.code.getargs(var):
+ try:
+ retval.append((arg, self.f_locals[arg]))
+ except KeyError:
+ pass # this can occur when using Psyco
+ return retval
+
+
+class TracebackEntry(object):
+ """ a single entry in a traceback """
+
+ _repr_style = None
+ exprinfo = None
+
+ def __init__(self, rawentry, excinfo=None):
+ self._excinfo = excinfo
+ self._rawentry = rawentry
+ self.lineno = rawentry.tb_lineno - 1
+
+ def set_repr_style(self, mode):
+ assert mode in ("short", "long")
+ self._repr_style = mode
+
+ @property
+ def frame(self):
+ import _pytest._code
+
+ return _pytest._code.Frame(self._rawentry.tb_frame)
+
+ @property
+ def relline(self):
+ return self.lineno - self.frame.code.firstlineno
+
+ def __repr__(self):
+ return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1)
+
+ @property
+ def statement(self):
+ """ _pytest._code.Source object for the current statement """
+ source = self.frame.code.fullsource
+ return source.getstatement(self.lineno)
+
+ @property
+ def path(self):
+ """ path to the source code """
+ return self.frame.code.path
+
+ def getlocals(self):
+ return self.frame.f_locals
+
+ locals = property(getlocals, None, None, "locals of underlaying frame")
+
+ def getfirstlinesource(self):
+ # on Jython this firstlineno can be -1 apparently
+ return max(self.frame.code.firstlineno, 0)
+
+ def getsource(self, astcache=None):
+ """ return failing source code. """
+ # we use the passed in astcache to not reparse asttrees
+ # within exception info printing
+ from _pytest._code.source import getstatementrange_ast
+
+ source = self.frame.code.fullsource
+ if source is None:
+ return None
+ key = astnode = None
+ if astcache is not None:
+ key = self.frame.code.path
+ if key is not None:
+ astnode = astcache.get(key, None)
+ start = self.getfirstlinesource()
+ try:
+ astnode, _, end = getstatementrange_ast(
+ self.lineno, source, astnode=astnode
+ )
+ except SyntaxError:
+ end = self.lineno + 1
+ else:
+ if key is not None:
+ astcache[key] = astnode
+ return source[start:end]
+
+ source = property(getsource)
+
+ def ishidden(self):
+ """ return True if the current frame has a var __tracebackhide__
resolving to True.
-
- If __tracebackhide__ is a callable, it gets called with the
- ExceptionInfo instance and can decide whether to hide the traceback.
-
- mostly for internal use
- """
+
+ If __tracebackhide__ is a callable, it gets called with the
+ ExceptionInfo instance and can decide whether to hide the traceback.
+
+ mostly for internal use
+ """
f = self.frame
tbh = f.f_locals.get(
"__tracebackhide__", f.f_globals.get("__tracebackhide__", False)
)
if tbh and callable(tbh):
- return tbh(None if self._excinfo is None else self._excinfo())
+ return tbh(None if self._excinfo is None else self._excinfo())
return tbh
-
- def __str__(self):
- try:
- fn = str(self.path)
- except py.error.Error:
- fn = "???"
- name = self.frame.code.name
- try:
- line = str(self.statement).lstrip()
- except KeyboardInterrupt:
- raise
- except: # noqa
- line = "???"
- return " File %r:%d in %s\n %s\n" % (fn, self.lineno + 1, name, line)
-
- def name(self):
- return self.frame.code.raw.co_name
-
- name = property(name, None, None, "co_name of underlaying code")
-
-
-class Traceback(list):
- """ Traceback objects encapsulate and offer higher level
- access to Traceback entries.
- """
-
- Entry = TracebackEntry
-
- def __init__(self, tb, excinfo=None):
- """ initialize from given python traceback object and ExceptionInfo """
- self._excinfo = excinfo
- if hasattr(tb, "tb_next"):
-
- def f(cur):
- while cur is not None:
- yield self.Entry(cur, excinfo=excinfo)
- cur = cur.tb_next
-
- list.__init__(self, f(tb))
- else:
- list.__init__(self, tb)
-
- def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
- """ return a Traceback instance wrapping part of this Traceback
-
- by provding any combination of path, lineno and firstlineno, the
- first frame to start the to-be-returned traceback is determined
-
- this allows cutting the first part of a Traceback instance e.g.
- for formatting reasons (removing some uninteresting bits that deal
- with handling of the exception/traceback)
- """
- for x in self:
- code = x.frame.code
- codepath = code.path
- if (
- (path is None or codepath == path)
- and (
- excludepath is None
- or not hasattr(codepath, "relto")
- or not codepath.relto(excludepath)
- )
- and (lineno is None or x.lineno == lineno)
- and (firstlineno is None or x.frame.code.firstlineno == firstlineno)
- ):
- return Traceback(x._rawentry, self._excinfo)
- return self
-
- def __getitem__(self, key):
- val = super(Traceback, self).__getitem__(key)
- if isinstance(key, type(slice(0))):
- val = self.__class__(val)
- return val
-
- def filter(self, fn=lambda x: not x.ishidden()):
- """ return a Traceback instance with certain items removed
-
- fn is a function that gets a single argument, a TracebackEntry
- instance, and should return True when the item should be added
- to the Traceback, False when not
-
- by default this removes all the TracebackEntries which are hidden
- (see ishidden() above)
- """
- return Traceback(filter(fn, self), self._excinfo)
-
- def getcrashentry(self):
- """ return last non-hidden traceback entry that lead
- to the exception of a traceback.
- """
- for i in range(-1, -len(self) - 1, -1):
- entry = self[i]
- if not entry.ishidden():
- return entry
- return self[-1]
-
- def recursionindex(self):
- """ return the index of the frame/TracebackEntry where recursion
- originates if appropriate, None if no recursion occurred
- """
- cache = {}
- for i, entry in enumerate(self):
- # id for the code.raw is needed to work around
- # the strange metaprogramming in the decorator lib from pypi
- # which generates code objects that have hash/value equality
- # XXX needs a test
- key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
- # print "checking for recursion at", key
- values = cache.setdefault(key, [])
- if values:
- f = entry.frame
- loc = f.f_locals
- for otherloc in values:
- if f.is_true(
- f.eval(
- co_equal,
- __recursioncache_locals_1=loc,
- __recursioncache_locals_2=otherloc,
- )
- ):
- return i
- values.append(entry.frame.f_locals)
- return None
-
-
-co_equal = compile(
- "__recursioncache_locals_1 == __recursioncache_locals_2", "?", "eval"
-)
-
-
+
+ def __str__(self):
+ try:
+ fn = str(self.path)
+ except py.error.Error:
+ fn = "???"
+ name = self.frame.code.name
+ try:
+ line = str(self.statement).lstrip()
+ except KeyboardInterrupt:
+ raise
+ except: # noqa
+ line = "???"
+ return " File %r:%d in %s\n %s\n" % (fn, self.lineno + 1, name, line)
+
+ def name(self):
+ return self.frame.code.raw.co_name
+
+ name = property(name, None, None, "co_name of underlaying code")
+
+
+class Traceback(list):
+ """ Traceback objects encapsulate and offer higher level
+ access to Traceback entries.
+ """
+
+ Entry = TracebackEntry
+
+ def __init__(self, tb, excinfo=None):
+ """ initialize from given python traceback object and ExceptionInfo """
+ self._excinfo = excinfo
+ if hasattr(tb, "tb_next"):
+
+ def f(cur):
+ while cur is not None:
+ yield self.Entry(cur, excinfo=excinfo)
+ cur = cur.tb_next
+
+ list.__init__(self, f(tb))
+ else:
+ list.__init__(self, tb)
+
+ def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
+ """ return a Traceback instance wrapping part of this Traceback
+
+ by provding any combination of path, lineno and firstlineno, the
+ first frame to start the to-be-returned traceback is determined
+
+ this allows cutting the first part of a Traceback instance e.g.
+ for formatting reasons (removing some uninteresting bits that deal
+ with handling of the exception/traceback)
+ """
+ for x in self:
+ code = x.frame.code
+ codepath = code.path
+ if (
+ (path is None or codepath == path)
+ and (
+ excludepath is None
+ or not hasattr(codepath, "relto")
+ or not codepath.relto(excludepath)
+ )
+ and (lineno is None or x.lineno == lineno)
+ and (firstlineno is None or x.frame.code.firstlineno == firstlineno)
+ ):
+ return Traceback(x._rawentry, self._excinfo)
+ return self
+
+ def __getitem__(self, key):
+ val = super(Traceback, self).__getitem__(key)
+ if isinstance(key, type(slice(0))):
+ val = self.__class__(val)
+ return val
+
+ def filter(self, fn=lambda x: not x.ishidden()):
+ """ return a Traceback instance with certain items removed
+
+ fn is a function that gets a single argument, a TracebackEntry
+ instance, and should return True when the item should be added
+ to the Traceback, False when not
+
+ by default this removes all the TracebackEntries which are hidden
+ (see ishidden() above)
+ """
+ return Traceback(filter(fn, self), self._excinfo)
+
+ def getcrashentry(self):
+ """ return last non-hidden traceback entry that lead
+ to the exception of a traceback.
+ """
+ for i in range(-1, -len(self) - 1, -1):
+ entry = self[i]
+ if not entry.ishidden():
+ return entry
+ return self[-1]
+
+ def recursionindex(self):
+ """ return the index of the frame/TracebackEntry where recursion
+ originates if appropriate, None if no recursion occurred
+ """
+ cache = {}
+ for i, entry in enumerate(self):
+ # id for the code.raw is needed to work around
+ # the strange metaprogramming in the decorator lib from pypi
+ # which generates code objects that have hash/value equality
+ # XXX needs a test
+ key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
+ # print "checking for recursion at", key
+ values = cache.setdefault(key, [])
+ if values:
+ f = entry.frame
+ loc = f.f_locals
+ for otherloc in values:
+ if f.is_true(
+ f.eval(
+ co_equal,
+ __recursioncache_locals_1=loc,
+ __recursioncache_locals_2=otherloc,
+ )
+ ):
+ return i
+ values.append(entry.frame.f_locals)
+ return None
+
+
+co_equal = compile(
+ "__recursioncache_locals_1 == __recursioncache_locals_2", "?", "eval"
+)
+
+
@attr.s(repr=False)
-class ExceptionInfo(object):
- """ wraps sys.exc_info() objects and offers
- help for navigating the traceback.
- """
-
- _assert_start_repr = (
- "AssertionError(u'assert " if _PY2 else "AssertionError('assert "
- )
-
+class ExceptionInfo(object):
+ """ wraps sys.exc_info() objects and offers
+ help for navigating the traceback.
+ """
+
+ _assert_start_repr = (
+ "AssertionError(u'assert " if _PY2 else "AssertionError('assert "
+ )
+
_excinfo = attr.ib()
_striptext = attr.ib(default="")
_traceback = attr.ib(default=None)
-
+
@classmethod
def from_current(cls, exprinfo=None):
"""returns an ExceptionInfo matching the current traceback
-
+
.. warning::
Experimental API
@@ -461,117 +461,117 @@ class ExceptionInfo(object):
def traceback(self, value):
self._traceback = value
- def __repr__(self):
+ def __repr__(self):
if self._excinfo is None:
return "<ExceptionInfo for raises contextmanager>"
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
-
- def exconly(self, tryshort=False):
- """ return the exception as a string
-
- when 'tryshort' resolves to True, and the exception is a
- _pytest._code._AssertionError, only the actual exception part of
- the exception representation is returned (so 'AssertionError: ' is
- removed from the beginning)
- """
- lines = format_exception_only(self.type, self.value)
- text = "".join(lines)
- text = text.rstrip()
- if tryshort:
- if text.startswith(self._striptext):
- text = text[len(self._striptext) :]
- return text
-
- def errisinstance(self, exc):
- """ return True if the exception is an instance of exc """
- return isinstance(self.value, exc)
-
- def _getreprcrash(self):
- exconly = self.exconly(tryshort=True)
- entry = self.traceback.getcrashentry()
- path, lineno = entry.frame.code.raw.co_filename, entry.lineno
- return ReprFileLocation(path, lineno + 1, exconly)
-
- def getrepr(
- self,
- showlocals=False,
- style="long",
- abspath=False,
- tbfilter=True,
- funcargs=False,
- truncate_locals=True,
- chain=True,
- ):
- """
- Return str()able representation of this exception info.
-
- :param bool showlocals:
- Show locals per traceback entry.
- Ignored if ``style=="native"``.
-
- :param str style: long|short|no|native traceback style
-
- :param bool abspath:
- If paths should be changed to absolute or left unchanged.
-
- :param bool tbfilter:
- Hide entries that contain a local variable ``__tracebackhide__==True``.
- Ignored if ``style=="native"``.
-
- :param bool funcargs:
- Show fixtures ("funcargs" for legacy purposes) per traceback entry.
-
- :param bool truncate_locals:
- With ``showlocals==True``, make sure locals can be safely represented as strings.
-
- :param bool chain: if chained exceptions in Python 3 should be shown.
-
- .. versionchanged:: 3.9
-
- Added the ``chain`` parameter.
- """
- if style == "native":
- return ReprExceptionInfo(
- ReprTracebackNative(
- traceback.format_exception(
- self.type, self.value, self.traceback[0]._rawentry
- )
- ),
- self._getreprcrash(),
- )
-
- fmt = FormattedExcinfo(
- showlocals=showlocals,
- style=style,
- abspath=abspath,
- tbfilter=tbfilter,
- funcargs=funcargs,
- truncate_locals=truncate_locals,
- chain=chain,
- )
- return fmt.repr_excinfo(self)
-
- def __str__(self):
+
+ def exconly(self, tryshort=False):
+ """ return the exception as a string
+
+ when 'tryshort' resolves to True, and the exception is a
+ _pytest._code._AssertionError, only the actual exception part of
+ the exception representation is returned (so 'AssertionError: ' is
+ removed from the beginning)
+ """
+ lines = format_exception_only(self.type, self.value)
+ text = "".join(lines)
+ text = text.rstrip()
+ if tryshort:
+ if text.startswith(self._striptext):
+ text = text[len(self._striptext) :]
+ return text
+
+ def errisinstance(self, exc):
+ """ return True if the exception is an instance of exc """
+ return isinstance(self.value, exc)
+
+ def _getreprcrash(self):
+ exconly = self.exconly(tryshort=True)
+ entry = self.traceback.getcrashentry()
+ path, lineno = entry.frame.code.raw.co_filename, entry.lineno
+ return ReprFileLocation(path, lineno + 1, exconly)
+
+ def getrepr(
+ self,
+ showlocals=False,
+ style="long",
+ abspath=False,
+ tbfilter=True,
+ funcargs=False,
+ truncate_locals=True,
+ chain=True,
+ ):
+ """
+ Return str()able representation of this exception info.
+
+ :param bool showlocals:
+ Show locals per traceback entry.
+ Ignored if ``style=="native"``.
+
+ :param str style: long|short|no|native traceback style
+
+ :param bool abspath:
+ If paths should be changed to absolute or left unchanged.
+
+ :param bool tbfilter:
+ Hide entries that contain a local variable ``__tracebackhide__==True``.
+ Ignored if ``style=="native"``.
+
+ :param bool funcargs:
+ Show fixtures ("funcargs" for legacy purposes) per traceback entry.
+
+ :param bool truncate_locals:
+ With ``showlocals==True``, make sure locals can be safely represented as strings.
+
+ :param bool chain: if chained exceptions in Python 3 should be shown.
+
+ .. versionchanged:: 3.9
+
+ Added the ``chain`` parameter.
+ """
+ if style == "native":
+ return ReprExceptionInfo(
+ ReprTracebackNative(
+ traceback.format_exception(
+ self.type, self.value, self.traceback[0]._rawentry
+ )
+ ),
+ self._getreprcrash(),
+ )
+
+ fmt = FormattedExcinfo(
+ showlocals=showlocals,
+ style=style,
+ abspath=abspath,
+ tbfilter=tbfilter,
+ funcargs=funcargs,
+ truncate_locals=truncate_locals,
+ chain=chain,
+ )
+ return fmt.repr_excinfo(self)
+
+ def __str__(self):
if self._excinfo is None:
- return repr(self)
+ return repr(self)
entry = self.traceback[-1]
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
return str(loc)
-
- def __unicode__(self):
- entry = self.traceback[-1]
- loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
- return text_type(loc)
-
- def match(self, regexp):
- """
+
+ def __unicode__(self):
+ entry = self.traceback[-1]
+ loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
+ return text_type(loc)
+
+ def match(self, regexp):
+ """
Check whether the regular expression 'regexp' is found in the string
representation of the exception using ``re.search``. If it matches
then True is returned (so that it is possible to write
``assert excinfo.match()``). If it doesn't match an AssertionError is
raised.
- """
- __tracebackhide__ = True
+ """
+ __tracebackhide__ = True
value = (
text_type(self.value) if isinstance(regexp, text_type) else str(self.value)
)
@@ -579,515 +579,515 @@ class ExceptionInfo(object):
raise AssertionError(
u"Pattern {!r} not found in {!r}".format(regexp, value)
)
- return True
-
-
-@attr.s
-class FormattedExcinfo(object):
- """ presenting information about failing Functions and Generators. """
-
- # for traceback entries
- flow_marker = ">"
- fail_marker = "E"
-
- showlocals = attr.ib(default=False)
- style = attr.ib(default="long")
- abspath = attr.ib(default=True)
- tbfilter = attr.ib(default=True)
- funcargs = attr.ib(default=False)
- truncate_locals = attr.ib(default=True)
- chain = attr.ib(default=True)
- astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False)
-
- def _getindent(self, source):
- # figure out indent for given source
- try:
- s = str(source.getstatement(len(source) - 1))
- except KeyboardInterrupt:
- raise
- except: # noqa
- try:
- s = str(source[-1])
- except KeyboardInterrupt:
- raise
- except: # noqa
- return 0
- return 4 + (len(s) - len(s.lstrip()))
-
- def _getentrysource(self, entry):
- source = entry.getsource(self.astcache)
- if source is not None:
- source = source.deindent()
- return source
-
- def repr_args(self, entry):
- if self.funcargs:
- args = []
- for argname, argvalue in entry.frame.getargs(var=True):
+ return True
+
+
+@attr.s
+class FormattedExcinfo(object):
+ """ presenting information about failing Functions and Generators. """
+
+ # for traceback entries
+ flow_marker = ">"
+ fail_marker = "E"
+
+ showlocals = attr.ib(default=False)
+ style = attr.ib(default="long")
+ abspath = attr.ib(default=True)
+ tbfilter = attr.ib(default=True)
+ funcargs = attr.ib(default=False)
+ truncate_locals = attr.ib(default=True)
+ chain = attr.ib(default=True)
+ astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False)
+
+ def _getindent(self, source):
+ # figure out indent for given source
+ try:
+ s = str(source.getstatement(len(source) - 1))
+ except KeyboardInterrupt:
+ raise
+ except: # noqa
+ try:
+ s = str(source[-1])
+ except KeyboardInterrupt:
+ raise
+ except: # noqa
+ return 0
+ return 4 + (len(s) - len(s.lstrip()))
+
+ def _getentrysource(self, entry):
+ source = entry.getsource(self.astcache)
+ if source is not None:
+ source = source.deindent()
+ return source
+
+ def repr_args(self, entry):
+ if self.funcargs:
+ args = []
+ for argname, argvalue in entry.frame.getargs(var=True):
args.append((argname, saferepr(argvalue)))
- return ReprFuncArgs(args)
-
- def get_source(self, source, line_index=-1, excinfo=None, short=False):
- """ return formatted and marked up source lines. """
- import _pytest._code
-
- lines = []
- if source is None or line_index >= len(source.lines):
- source = _pytest._code.Source("???")
- line_index = 0
- if line_index < 0:
- line_index += len(source)
- space_prefix = " "
- if short:
- lines.append(space_prefix + source.lines[line_index].strip())
- else:
- for line in source.lines[:line_index]:
- lines.append(space_prefix + line)
- lines.append(self.flow_marker + " " + source.lines[line_index])
- for line in source.lines[line_index + 1 :]:
- lines.append(space_prefix + line)
- if excinfo is not None:
- indent = 4 if short else self._getindent(source)
- lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
- return lines
-
- def get_exconly(self, excinfo, indent=4, markall=False):
- lines = []
- indent = " " * indent
- # get the real exception information out
- exlines = excinfo.exconly(tryshort=True).split("\n")
- failindent = self.fail_marker + indent[1:]
- for line in exlines:
- lines.append(failindent + line)
- if not markall:
- failindent = indent
- return lines
-
- def repr_locals(self, locals):
- if self.showlocals:
- lines = []
- keys = [loc for loc in locals if loc[0] != "@"]
- keys.sort()
- for name in keys:
- value = locals[name]
- if name == "__builtins__":
- lines.append("__builtins__ = <builtins>")
- else:
- # This formatting could all be handled by the
- # _repr() function, which is only reprlib.Repr in
- # disguise, so is very configurable.
- if self.truncate_locals:
+ return ReprFuncArgs(args)
+
+ def get_source(self, source, line_index=-1, excinfo=None, short=False):
+ """ return formatted and marked up source lines. """
+ import _pytest._code
+
+ lines = []
+ if source is None or line_index >= len(source.lines):
+ source = _pytest._code.Source("???")
+ line_index = 0
+ if line_index < 0:
+ line_index += len(source)
+ space_prefix = " "
+ if short:
+ lines.append(space_prefix + source.lines[line_index].strip())
+ else:
+ for line in source.lines[:line_index]:
+ lines.append(space_prefix + line)
+ lines.append(self.flow_marker + " " + source.lines[line_index])
+ for line in source.lines[line_index + 1 :]:
+ lines.append(space_prefix + line)
+ if excinfo is not None:
+ indent = 4 if short else self._getindent(source)
+ lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
+ return lines
+
+ def get_exconly(self, excinfo, indent=4, markall=False):
+ lines = []
+ indent = " " * indent
+ # get the real exception information out
+ exlines = excinfo.exconly(tryshort=True).split("\n")
+ failindent = self.fail_marker + indent[1:]
+ for line in exlines:
+ lines.append(failindent + line)
+ if not markall:
+ failindent = indent
+ return lines
+
+ def repr_locals(self, locals):
+ if self.showlocals:
+ lines = []
+ keys = [loc for loc in locals if loc[0] != "@"]
+ keys.sort()
+ for name in keys:
+ value = locals[name]
+ if name == "__builtins__":
+ lines.append("__builtins__ = <builtins>")
+ else:
+ # This formatting could all be handled by the
+ # _repr() function, which is only reprlib.Repr in
+ # disguise, so is very configurable.
+ if self.truncate_locals:
str_repr = saferepr(value)
- else:
+ else:
str_repr = safeformat(value)
- # if len(str_repr) < 70 or not isinstance(value,
- # (list, tuple, dict)):
- lines.append("%-10s = %s" % (name, str_repr))
- # else:
- # self._line("%-10s =\\" % (name,))
- # # XXX
- # pprint.pprint(value, stream=self.excinfowriter)
- return ReprLocals(lines)
-
- def repr_traceback_entry(self, entry, excinfo=None):
- import _pytest._code
-
- source = self._getentrysource(entry)
- if source is None:
- source = _pytest._code.Source("???")
- line_index = 0
- else:
- # entry.getfirstlinesource() can be -1, should be 0 on jython
- line_index = entry.lineno - max(entry.getfirstlinesource(), 0)
-
- lines = []
- style = entry._repr_style
- if style is None:
- style = self.style
- if style in ("short", "long"):
- short = style == "short"
- reprargs = self.repr_args(entry) if not short else None
- s = self.get_source(source, line_index, excinfo, short=short)
- lines.extend(s)
- if short:
- message = "in %s" % (entry.name)
- else:
- message = excinfo and excinfo.typename or ""
- path = self._makepath(entry.path)
- filelocrepr = ReprFileLocation(path, entry.lineno + 1, message)
- localsrepr = None
- if not short:
- localsrepr = self.repr_locals(entry.locals)
- return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style)
- if excinfo:
- lines.extend(self.get_exconly(excinfo, indent=4))
- return ReprEntry(lines, None, None, None, style)
-
- def _makepath(self, path):
- if not self.abspath:
- try:
- np = py.path.local().bestrelpath(path)
- except OSError:
- return path
- if len(np) < len(str(path)):
- path = np
- return path
-
- def repr_traceback(self, excinfo):
- traceback = excinfo.traceback
- if self.tbfilter:
- traceback = traceback.filter()
-
- if is_recursion_error(excinfo):
- traceback, extraline = self._truncate_recursive_traceback(traceback)
- else:
- extraline = None
-
- last = traceback[-1]
- entries = []
- for index, entry in enumerate(traceback):
- einfo = (last == entry) and excinfo or None
- reprentry = self.repr_traceback_entry(entry, einfo)
- entries.append(reprentry)
- return ReprTraceback(entries, extraline, style=self.style)
-
- def _truncate_recursive_traceback(self, traceback):
- """
- Truncate the given recursive traceback trying to find the starting point
- of the recursion.
-
- The detection is done by going through each traceback entry and finding the
- point in which the locals of the frame are equal to the locals of a previous frame (see ``recursionindex()``.
-
- Handle the situation where the recursion process might raise an exception (for example
- comparing numpy arrays using equality raises a TypeError), in which case we do our best to
- warn the user of the error and show a limited traceback.
- """
- try:
- recursionindex = traceback.recursionindex()
- except Exception as e:
- max_frames = 10
- extraline = (
- "!!! Recursion error detected, but an error occurred locating the origin of recursion.\n"
- " The following exception happened when comparing locals in the stack frame:\n"
- " {exc_type}: {exc_msg}\n"
- " Displaying first and last {max_frames} stack frames out of {total}."
- ).format(
- exc_type=type(e).__name__,
- exc_msg=safe_str(e),
- max_frames=max_frames,
- total=len(traceback),
- )
- traceback = traceback[:max_frames] + traceback[-max_frames:]
- else:
- if recursionindex is not None:
- extraline = "!!! Recursion detected (same locals & position)"
- traceback = traceback[: recursionindex + 1]
- else:
- extraline = None
-
- return traceback, extraline
-
- def repr_excinfo(self, excinfo):
- if _PY2:
- reprtraceback = self.repr_traceback(excinfo)
- reprcrash = excinfo._getreprcrash()
-
- return ReprExceptionInfo(reprtraceback, reprcrash)
- else:
- repr_chain = []
- e = excinfo.value
- descr = None
- seen = set()
- while e is not None and id(e) not in seen:
- seen.add(id(e))
- if excinfo:
- reprtraceback = self.repr_traceback(excinfo)
- reprcrash = excinfo._getreprcrash()
- else:
- # fallback to native repr if the exception doesn't have a traceback:
- # ExceptionInfo objects require a full traceback to work
- reprtraceback = ReprTracebackNative(
- traceback.format_exception(type(e), e, None)
- )
- reprcrash = None
-
- repr_chain += [(reprtraceback, reprcrash, descr)]
- if e.__cause__ is not None and self.chain:
- e = e.__cause__
- excinfo = (
- ExceptionInfo((type(e), e, e.__traceback__))
- if e.__traceback__
- else None
- )
- descr = "The above exception was the direct cause of the following exception:"
- elif (
- e.__context__ is not None
- and not e.__suppress_context__
- and self.chain
- ):
- e = e.__context__
- excinfo = (
- ExceptionInfo((type(e), e, e.__traceback__))
- if e.__traceback__
- else None
- )
- descr = "During handling of the above exception, another exception occurred:"
- else:
- e = None
- repr_chain.reverse()
- return ExceptionChainRepr(repr_chain)
-
-
-class TerminalRepr(object):
- def __str__(self):
- s = self.__unicode__()
- if _PY2:
- s = s.encode("utf-8")
- return s
-
- def __unicode__(self):
- # FYI this is called from pytest-xdist's serialization of exception
- # information.
- io = py.io.TextIO()
- tw = py.io.TerminalWriter(file=io)
- self.toterminal(tw)
- return io.getvalue().strip()
-
- def __repr__(self):
- return "<%s instance at %0x>" % (self.__class__, id(self))
-
-
-class ExceptionRepr(TerminalRepr):
- def __init__(self):
- self.sections = []
-
- def addsection(self, name, content, sep="-"):
- self.sections.append((name, content, sep))
-
- def toterminal(self, tw):
- for name, content, sep in self.sections:
- tw.sep(sep, name)
- tw.line(content)
-
-
-class ExceptionChainRepr(ExceptionRepr):
- def __init__(self, chain):
- super(ExceptionChainRepr, self).__init__()
- self.chain = chain
- # reprcrash and reprtraceback of the outermost (the newest) exception
- # in the chain
- self.reprtraceback = chain[-1][0]
- self.reprcrash = chain[-1][1]
-
- def toterminal(self, tw):
- for element in self.chain:
- element[0].toterminal(tw)
- if element[2] is not None:
- tw.line("")
- tw.line(element[2], yellow=True)
- super(ExceptionChainRepr, self).toterminal(tw)
-
-
-class ReprExceptionInfo(ExceptionRepr):
- def __init__(self, reprtraceback, reprcrash):
- super(ReprExceptionInfo, self).__init__()
- self.reprtraceback = reprtraceback
- self.reprcrash = reprcrash
-
- def toterminal(self, tw):
- self.reprtraceback.toterminal(tw)
- super(ReprExceptionInfo, self).toterminal(tw)
-
-
-class ReprTraceback(TerminalRepr):
- entrysep = "_ "
-
- def __init__(self, reprentries, extraline, style):
- self.reprentries = reprentries
- self.extraline = extraline
- self.style = style
-
- def toterminal(self, tw):
- # the entries might have different styles
- for i, entry in enumerate(self.reprentries):
- if entry.style == "long":
- tw.line("")
- entry.toterminal(tw)
- if i < len(self.reprentries) - 1:
- next_entry = self.reprentries[i + 1]
- if (
- entry.style == "long"
- or entry.style == "short"
- and next_entry.style == "long"
- ):
- tw.sep(self.entrysep)
-
- if self.extraline:
- tw.line(self.extraline)
-
-
-class ReprTracebackNative(ReprTraceback):
- def __init__(self, tblines):
- self.style = "native"
- self.reprentries = [ReprEntryNative(tblines)]
- self.extraline = None
-
-
-class ReprEntryNative(TerminalRepr):
- style = "native"
-
- def __init__(self, tblines):
- self.lines = tblines
-
- def toterminal(self, tw):
- tw.write("".join(self.lines))
-
-
-class ReprEntry(TerminalRepr):
- def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
- self.lines = lines
- self.reprfuncargs = reprfuncargs
- self.reprlocals = reprlocals
- self.reprfileloc = filelocrepr
- self.style = style
-
- def toterminal(self, tw):
- if self.style == "short":
- self.reprfileloc.toterminal(tw)
- for line in self.lines:
- red = line.startswith("E ")
- tw.line(line, bold=True, red=red)
- # tw.line("")
- return
- if self.reprfuncargs:
- self.reprfuncargs.toterminal(tw)
- for line in self.lines:
- red = line.startswith("E ")
- tw.line(line, bold=True, red=red)
- if self.reprlocals:
- tw.line("")
- self.reprlocals.toterminal(tw)
- if self.reprfileloc:
- if self.lines:
- tw.line("")
- self.reprfileloc.toterminal(tw)
-
- def __str__(self):
- return "%s\n%s\n%s" % ("\n".join(self.lines), self.reprlocals, self.reprfileloc)
-
-
-class ReprFileLocation(TerminalRepr):
- def __init__(self, path, lineno, message):
- self.path = str(path)
- self.lineno = lineno
- self.message = message
-
- def toterminal(self, tw):
- # filename and lineno output for each entry,
- # using an output format that most editors unterstand
- msg = self.message
- i = msg.find("\n")
- if i != -1:
- msg = msg[:i]
- tw.write(self.path, bold=True, red=True)
- tw.line(":%s: %s" % (self.lineno, msg))
-
-
-class ReprLocals(TerminalRepr):
- def __init__(self, lines):
- self.lines = lines
-
- def toterminal(self, tw):
- for line in self.lines:
- tw.line(line)
-
-
-class ReprFuncArgs(TerminalRepr):
- def __init__(self, args):
- self.args = args
-
- def toterminal(self, tw):
- if self.args:
- linesofar = ""
- for name, value in self.args:
- ns = "%s = %s" % (safe_str(name), safe_str(value))
- if len(ns) + len(linesofar) + 2 > tw.fullwidth:
- if linesofar:
- tw.line(linesofar)
- linesofar = ns
- else:
- if linesofar:
- linesofar += ", " + ns
- else:
- linesofar = ns
- if linesofar:
- tw.line(linesofar)
- tw.line("")
-
-
-def getrawcode(obj, trycall=True):
- """ return code object for given function. """
- try:
- return obj.__code__
- except AttributeError:
- obj = getattr(obj, "im_func", obj)
- obj = getattr(obj, "func_code", obj)
- obj = getattr(obj, "f_code", obj)
- obj = getattr(obj, "__code__", obj)
- if trycall and not hasattr(obj, "co_firstlineno"):
- if hasattr(obj, "__call__") and not inspect.isclass(obj):
- x = getrawcode(obj.__call__, trycall=False)
- if hasattr(x, "co_firstlineno"):
- return x
- return obj
-
-
-if PY35: # RecursionError introduced in 3.5
-
- def is_recursion_error(excinfo):
- return excinfo.errisinstance(RecursionError) # noqa
-
-
-else:
-
- def is_recursion_error(excinfo):
- if not excinfo.errisinstance(RuntimeError):
- return False
- try:
- return "maximum recursion depth exceeded" in str(excinfo.value)
- except UnicodeError:
- return False
-
-
-# relative paths that we use to filter traceback entries from appearing to the user;
-# see filter_traceback
-# note: if we need to add more paths than what we have now we should probably use a list
-# for better maintenance
-
-_PLUGGY_DIR = py.path.local(pluggy.__file__.rstrip("oc"))
-# pluggy is either a package or a single module depending on the version
-if _PLUGGY_DIR.basename == "__init__.py":
- _PLUGGY_DIR = _PLUGGY_DIR.dirpath()
-_PYTEST_DIR = py.path.local(_pytest.__file__).dirpath()
-_PY_DIR = py.path.local(py.__file__).dirpath()
-
-
-def filter_traceback(entry):
- """Return True if a TracebackEntry instance should be removed from tracebacks:
- * dynamically generated code (no code to show up for it);
- * internal traceback from pytest or its internal libraries, py and pluggy.
- """
- # entry.path might sometimes return a str object when the entry
- # points to dynamically generated code
- # see https://bitbucket.org/pytest-dev/py/issues/71
- raw_filename = entry.frame.code.raw.co_filename
- is_generated = "<" in raw_filename and ">" in raw_filename
- if is_generated:
- return False
- # entry.path might point to a non-existing file, in which case it will
- # also return a str object. see #1133
- p = py.path.local(entry.path)
- return (
- not p.relto(_PLUGGY_DIR) and not p.relto(_PYTEST_DIR) and not p.relto(_PY_DIR)
- )
+ # if len(str_repr) < 70 or not isinstance(value,
+ # (list, tuple, dict)):
+ lines.append("%-10s = %s" % (name, str_repr))
+ # else:
+ # self._line("%-10s =\\" % (name,))
+ # # XXX
+ # pprint.pprint(value, stream=self.excinfowriter)
+ return ReprLocals(lines)
+
+ def repr_traceback_entry(self, entry, excinfo=None):
+ import _pytest._code
+
+ source = self._getentrysource(entry)
+ if source is None:
+ source = _pytest._code.Source("???")
+ line_index = 0
+ else:
+ # entry.getfirstlinesource() can be -1, should be 0 on jython
+ line_index = entry.lineno - max(entry.getfirstlinesource(), 0)
+
+ lines = []
+ style = entry._repr_style
+ if style is None:
+ style = self.style
+ if style in ("short", "long"):
+ short = style == "short"
+ reprargs = self.repr_args(entry) if not short else None
+ s = self.get_source(source, line_index, excinfo, short=short)
+ lines.extend(s)
+ if short:
+ message = "in %s" % (entry.name)
+ else:
+ message = excinfo and excinfo.typename or ""
+ path = self._makepath(entry.path)
+ filelocrepr = ReprFileLocation(path, entry.lineno + 1, message)
+ localsrepr = None
+ if not short:
+ localsrepr = self.repr_locals(entry.locals)
+ return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style)
+ if excinfo:
+ lines.extend(self.get_exconly(excinfo, indent=4))
+ return ReprEntry(lines, None, None, None, style)
+
+ def _makepath(self, path):
+ if not self.abspath:
+ try:
+ np = py.path.local().bestrelpath(path)
+ except OSError:
+ return path
+ if len(np) < len(str(path)):
+ path = np
+ return path
+
+ def repr_traceback(self, excinfo):
+ traceback = excinfo.traceback
+ if self.tbfilter:
+ traceback = traceback.filter()
+
+ if is_recursion_error(excinfo):
+ traceback, extraline = self._truncate_recursive_traceback(traceback)
+ else:
+ extraline = None
+
+ last = traceback[-1]
+ entries = []
+ for index, entry in enumerate(traceback):
+ einfo = (last == entry) and excinfo or None
+ reprentry = self.repr_traceback_entry(entry, einfo)
+ entries.append(reprentry)
+ return ReprTraceback(entries, extraline, style=self.style)
+
+ def _truncate_recursive_traceback(self, traceback):
+ """
+ Truncate the given recursive traceback trying to find the starting point
+ of the recursion.
+
+ The detection is done by going through each traceback entry and finding the
+ point in which the locals of the frame are equal to the locals of a previous frame (see ``recursionindex()``.
+
+ Handle the situation where the recursion process might raise an exception (for example
+ comparing numpy arrays using equality raises a TypeError), in which case we do our best to
+ warn the user of the error and show a limited traceback.
+ """
+ try:
+ recursionindex = traceback.recursionindex()
+ except Exception as e:
+ max_frames = 10
+ extraline = (
+ "!!! Recursion error detected, but an error occurred locating the origin of recursion.\n"
+ " The following exception happened when comparing locals in the stack frame:\n"
+ " {exc_type}: {exc_msg}\n"
+ " Displaying first and last {max_frames} stack frames out of {total}."
+ ).format(
+ exc_type=type(e).__name__,
+ exc_msg=safe_str(e),
+ max_frames=max_frames,
+ total=len(traceback),
+ )
+ traceback = traceback[:max_frames] + traceback[-max_frames:]
+ else:
+ if recursionindex is not None:
+ extraline = "!!! Recursion detected (same locals & position)"
+ traceback = traceback[: recursionindex + 1]
+ else:
+ extraline = None
+
+ return traceback, extraline
+
+ def repr_excinfo(self, excinfo):
+ if _PY2:
+ reprtraceback = self.repr_traceback(excinfo)
+ reprcrash = excinfo._getreprcrash()
+
+ return ReprExceptionInfo(reprtraceback, reprcrash)
+ else:
+ repr_chain = []
+ e = excinfo.value
+ descr = None
+ seen = set()
+ while e is not None and id(e) not in seen:
+ seen.add(id(e))
+ if excinfo:
+ reprtraceback = self.repr_traceback(excinfo)
+ reprcrash = excinfo._getreprcrash()
+ else:
+ # fallback to native repr if the exception doesn't have a traceback:
+ # ExceptionInfo objects require a full traceback to work
+ reprtraceback = ReprTracebackNative(
+ traceback.format_exception(type(e), e, None)
+ )
+ reprcrash = None
+
+ repr_chain += [(reprtraceback, reprcrash, descr)]
+ if e.__cause__ is not None and self.chain:
+ e = e.__cause__
+ excinfo = (
+ ExceptionInfo((type(e), e, e.__traceback__))
+ if e.__traceback__
+ else None
+ )
+ descr = "The above exception was the direct cause of the following exception:"
+ elif (
+ e.__context__ is not None
+ and not e.__suppress_context__
+ and self.chain
+ ):
+ e = e.__context__
+ excinfo = (
+ ExceptionInfo((type(e), e, e.__traceback__))
+ if e.__traceback__
+ else None
+ )
+ descr = "During handling of the above exception, another exception occurred:"
+ else:
+ e = None
+ repr_chain.reverse()
+ return ExceptionChainRepr(repr_chain)
+
+
+class TerminalRepr(object):
+ def __str__(self):
+ s = self.__unicode__()
+ if _PY2:
+ s = s.encode("utf-8")
+ return s
+
+ def __unicode__(self):
+ # FYI this is called from pytest-xdist's serialization of exception
+ # information.
+ io = py.io.TextIO()
+ tw = py.io.TerminalWriter(file=io)
+ self.toterminal(tw)
+ return io.getvalue().strip()
+
+ def __repr__(self):
+ return "<%s instance at %0x>" % (self.__class__, id(self))
+
+
+class ExceptionRepr(TerminalRepr):
+ def __init__(self):
+ self.sections = []
+
+ def addsection(self, name, content, sep="-"):
+ self.sections.append((name, content, sep))
+
+ def toterminal(self, tw):
+ for name, content, sep in self.sections:
+ tw.sep(sep, name)
+ tw.line(content)
+
+
+class ExceptionChainRepr(ExceptionRepr):
+ def __init__(self, chain):
+ super(ExceptionChainRepr, self).__init__()
+ self.chain = chain
+ # reprcrash and reprtraceback of the outermost (the newest) exception
+ # in the chain
+ self.reprtraceback = chain[-1][0]
+ self.reprcrash = chain[-1][1]
+
+ def toterminal(self, tw):
+ for element in self.chain:
+ element[0].toterminal(tw)
+ if element[2] is not None:
+ tw.line("")
+ tw.line(element[2], yellow=True)
+ super(ExceptionChainRepr, self).toterminal(tw)
+
+
+class ReprExceptionInfo(ExceptionRepr):
+ def __init__(self, reprtraceback, reprcrash):
+ super(ReprExceptionInfo, self).__init__()
+ self.reprtraceback = reprtraceback
+ self.reprcrash = reprcrash
+
+ def toterminal(self, tw):
+ self.reprtraceback.toterminal(tw)
+ super(ReprExceptionInfo, self).toterminal(tw)
+
+
+class ReprTraceback(TerminalRepr):
+ entrysep = "_ "
+
+ def __init__(self, reprentries, extraline, style):
+ self.reprentries = reprentries
+ self.extraline = extraline
+ self.style = style
+
+ def toterminal(self, tw):
+ # the entries might have different styles
+ for i, entry in enumerate(self.reprentries):
+ if entry.style == "long":
+ tw.line("")
+ entry.toterminal(tw)
+ if i < len(self.reprentries) - 1:
+ next_entry = self.reprentries[i + 1]
+ if (
+ entry.style == "long"
+ or entry.style == "short"
+ and next_entry.style == "long"
+ ):
+ tw.sep(self.entrysep)
+
+ if self.extraline:
+ tw.line(self.extraline)
+
+
+class ReprTracebackNative(ReprTraceback):
+ def __init__(self, tblines):
+ self.style = "native"
+ self.reprentries = [ReprEntryNative(tblines)]
+ self.extraline = None
+
+
+class ReprEntryNative(TerminalRepr):
+ style = "native"
+
+ def __init__(self, tblines):
+ self.lines = tblines
+
+ def toterminal(self, tw):
+ tw.write("".join(self.lines))
+
+
+class ReprEntry(TerminalRepr):
+ def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
+ self.lines = lines
+ self.reprfuncargs = reprfuncargs
+ self.reprlocals = reprlocals
+ self.reprfileloc = filelocrepr
+ self.style = style
+
+ def toterminal(self, tw):
+ if self.style == "short":
+ self.reprfileloc.toterminal(tw)
+ for line in self.lines:
+ red = line.startswith("E ")
+ tw.line(line, bold=True, red=red)
+ # tw.line("")
+ return
+ if self.reprfuncargs:
+ self.reprfuncargs.toterminal(tw)
+ for line in self.lines:
+ red = line.startswith("E ")
+ tw.line(line, bold=True, red=red)
+ if self.reprlocals:
+ tw.line("")
+ self.reprlocals.toterminal(tw)
+ if self.reprfileloc:
+ if self.lines:
+ tw.line("")
+ self.reprfileloc.toterminal(tw)
+
+ def __str__(self):
+ return "%s\n%s\n%s" % ("\n".join(self.lines), self.reprlocals, self.reprfileloc)
+
+
+class ReprFileLocation(TerminalRepr):
+ def __init__(self, path, lineno, message):
+ self.path = str(path)
+ self.lineno = lineno
+ self.message = message
+
+ def toterminal(self, tw):
+ # filename and lineno output for each entry,
+ # using an output format that most editors unterstand
+ msg = self.message
+ i = msg.find("\n")
+ if i != -1:
+ msg = msg[:i]
+ tw.write(self.path, bold=True, red=True)
+ tw.line(":%s: %s" % (self.lineno, msg))
+
+
+class ReprLocals(TerminalRepr):
+ def __init__(self, lines):
+ self.lines = lines
+
+ def toterminal(self, tw):
+ for line in self.lines:
+ tw.line(line)
+
+
+class ReprFuncArgs(TerminalRepr):
+ def __init__(self, args):
+ self.args = args
+
+ def toterminal(self, tw):
+ if self.args:
+ linesofar = ""
+ for name, value in self.args:
+ ns = "%s = %s" % (safe_str(name), safe_str(value))
+ if len(ns) + len(linesofar) + 2 > tw.fullwidth:
+ if linesofar:
+ tw.line(linesofar)
+ linesofar = ns
+ else:
+ if linesofar:
+ linesofar += ", " + ns
+ else:
+ linesofar = ns
+ if linesofar:
+ tw.line(linesofar)
+ tw.line("")
+
+
+def getrawcode(obj, trycall=True):
+ """ return code object for given function. """
+ try:
+ return obj.__code__
+ except AttributeError:
+ obj = getattr(obj, "im_func", obj)
+ obj = getattr(obj, "func_code", obj)
+ obj = getattr(obj, "f_code", obj)
+ obj = getattr(obj, "__code__", obj)
+ if trycall and not hasattr(obj, "co_firstlineno"):
+ if hasattr(obj, "__call__") and not inspect.isclass(obj):
+ x = getrawcode(obj.__call__, trycall=False)
+ if hasattr(x, "co_firstlineno"):
+ return x
+ return obj
+
+
+if PY35: # RecursionError introduced in 3.5
+
+ def is_recursion_error(excinfo):
+ return excinfo.errisinstance(RecursionError) # noqa
+
+
+else:
+
+ def is_recursion_error(excinfo):
+ if not excinfo.errisinstance(RuntimeError):
+ return False
+ try:
+ return "maximum recursion depth exceeded" in str(excinfo.value)
+ except UnicodeError:
+ return False
+
+
+# relative paths that we use to filter traceback entries from appearing to the user;
+# see filter_traceback
+# note: if we need to add more paths than what we have now we should probably use a list
+# for better maintenance
+
+_PLUGGY_DIR = py.path.local(pluggy.__file__.rstrip("oc"))
+# pluggy is either a package or a single module depending on the version
+if _PLUGGY_DIR.basename == "__init__.py":
+ _PLUGGY_DIR = _PLUGGY_DIR.dirpath()
+_PYTEST_DIR = py.path.local(_pytest.__file__).dirpath()
+_PY_DIR = py.path.local(py.__file__).dirpath()
+
+
+def filter_traceback(entry):
+ """Return True if a TracebackEntry instance should be removed from tracebacks:
+ * dynamically generated code (no code to show up for it);
+ * internal traceback from pytest or its internal libraries, py and pluggy.
+ """
+ # entry.path might sometimes return a str object when the entry
+ # points to dynamically generated code
+ # see https://bitbucket.org/pytest-dev/py/issues/71
+ raw_filename = entry.frame.code.raw.co_filename
+ is_generated = "<" in raw_filename and ">" in raw_filename
+ if is_generated:
+ return False
+ # entry.path might point to a non-existing file, in which case it will
+ # also return a str object. see #1133
+ p = py.path.local(entry.path)
+ return (
+ not p.relto(_PLUGGY_DIR) and not p.relto(_PYTEST_DIR) and not p.relto(_PY_DIR)
+ )
diff --git a/contrib/python/pytest/py2/_pytest/_code/source.py b/contrib/python/pytest/py2/_pytest/_code/source.py
index c329c7732a..b35e97b9ce 100644
--- a/contrib/python/pytest/py2/_pytest/_code/source.py
+++ b/contrib/python/pytest/py2/_pytest/_code/source.py
@@ -1,324 +1,324 @@
# -*- coding: utf-8 -*-
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import ast
-import inspect
-import linecache
-import sys
-import textwrap
-import tokenize
-import warnings
-from ast import PyCF_ONLY_AST as _AST_FLAG
-from bisect import bisect_right
-
-import py
-import six
-
-
-class Source(object):
- """ an immutable object holding a source code fragment,
- possibly deindenting it.
- """
-
- _compilecounter = 0
-
- def __init__(self, *parts, **kwargs):
- self.lines = lines = []
- de = kwargs.get("deindent", True)
- for part in parts:
- if not part:
- partlines = []
- elif isinstance(part, Source):
- partlines = part.lines
- elif isinstance(part, (tuple, list)):
- partlines = [x.rstrip("\n") for x in part]
- elif isinstance(part, six.string_types):
- partlines = part.split("\n")
- else:
- partlines = getsource(part, deindent=de).lines
- if de:
- partlines = deindent(partlines)
- lines.extend(partlines)
-
- def __eq__(self, other):
- try:
- return self.lines == other.lines
- except AttributeError:
- if isinstance(other, str):
- return str(self) == other
- return False
-
- __hash__ = None
-
- def __getitem__(self, key):
- if isinstance(key, int):
- return self.lines[key]
- else:
- if key.step not in (None, 1):
- raise IndexError("cannot slice a Source with a step")
- newsource = Source()
- newsource.lines = self.lines[key.start : key.stop]
- return newsource
-
- def __len__(self):
- return len(self.lines)
-
- def strip(self):
- """ return new source object with trailing
- and leading blank lines removed.
- """
- start, end = 0, len(self)
- while start < end and not self.lines[start].strip():
- start += 1
- while end > start and not self.lines[end - 1].strip():
- end -= 1
- source = Source()
- source.lines[:] = self.lines[start:end]
- return source
-
- def putaround(self, before="", after="", indent=" " * 4):
- """ return a copy of the source object with
- 'before' and 'after' wrapped around it.
- """
- before = Source(before)
- after = Source(after)
- newsource = Source()
- lines = [(indent + line) for line in self.lines]
- newsource.lines = before.lines + lines + after.lines
- return newsource
-
- def indent(self, indent=" " * 4):
- """ return a copy of the source object with
- all lines indented by the given indent-string.
- """
- newsource = Source()
- newsource.lines = [(indent + line) for line in self.lines]
- return newsource
-
- def getstatement(self, lineno):
- """ return Source statement which contains the
- given linenumber (counted from 0).
- """
- start, end = self.getstatementrange(lineno)
- return self[start:end]
-
- def getstatementrange(self, lineno):
- """ return (start, end) tuple which spans the minimal
- statement region which containing the given lineno.
- """
- if not (0 <= lineno < len(self)):
- raise IndexError("lineno out of range")
- ast, start, end = getstatementrange_ast(lineno, self)
- return start, end
-
- def deindent(self):
- """return a new source object deindented."""
- newsource = Source()
- newsource.lines[:] = deindent(self.lines)
- return newsource
-
- def isparseable(self, deindent=True):
- """ return True if source is parseable, heuristically
- deindenting it by default.
- """
- if deindent:
- source = str(self.deindent())
- else:
- source = str(self)
- try:
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import ast
+import inspect
+import linecache
+import sys
+import textwrap
+import tokenize
+import warnings
+from ast import PyCF_ONLY_AST as _AST_FLAG
+from bisect import bisect_right
+
+import py
+import six
+
+
+class Source(object):
+ """ an immutable object holding a source code fragment,
+ possibly deindenting it.
+ """
+
+ _compilecounter = 0
+
+ def __init__(self, *parts, **kwargs):
+ self.lines = lines = []
+ de = kwargs.get("deindent", True)
+ for part in parts:
+ if not part:
+ partlines = []
+ elif isinstance(part, Source):
+ partlines = part.lines
+ elif isinstance(part, (tuple, list)):
+ partlines = [x.rstrip("\n") for x in part]
+ elif isinstance(part, six.string_types):
+ partlines = part.split("\n")
+ else:
+ partlines = getsource(part, deindent=de).lines
+ if de:
+ partlines = deindent(partlines)
+ lines.extend(partlines)
+
+ def __eq__(self, other):
+ try:
+ return self.lines == other.lines
+ except AttributeError:
+ if isinstance(other, str):
+ return str(self) == other
+ return False
+
+ __hash__ = None
+
+ def __getitem__(self, key):
+ if isinstance(key, int):
+ return self.lines[key]
+ else:
+ if key.step not in (None, 1):
+ raise IndexError("cannot slice a Source with a step")
+ newsource = Source()
+ newsource.lines = self.lines[key.start : key.stop]
+ return newsource
+
+ def __len__(self):
+ return len(self.lines)
+
+ def strip(self):
+ """ return new source object with trailing
+ and leading blank lines removed.
+ """
+ start, end = 0, len(self)
+ while start < end and not self.lines[start].strip():
+ start += 1
+ while end > start and not self.lines[end - 1].strip():
+ end -= 1
+ source = Source()
+ source.lines[:] = self.lines[start:end]
+ return source
+
+ def putaround(self, before="", after="", indent=" " * 4):
+ """ return a copy of the source object with
+ 'before' and 'after' wrapped around it.
+ """
+ before = Source(before)
+ after = Source(after)
+ newsource = Source()
+ lines = [(indent + line) for line in self.lines]
+ newsource.lines = before.lines + lines + after.lines
+ return newsource
+
+ def indent(self, indent=" " * 4):
+ """ return a copy of the source object with
+ all lines indented by the given indent-string.
+ """
+ newsource = Source()
+ newsource.lines = [(indent + line) for line in self.lines]
+ return newsource
+
+ def getstatement(self, lineno):
+ """ return Source statement which contains the
+ given linenumber (counted from 0).
+ """
+ start, end = self.getstatementrange(lineno)
+ return self[start:end]
+
+ def getstatementrange(self, lineno):
+ """ return (start, end) tuple which spans the minimal
+ statement region which containing the given lineno.
+ """
+ if not (0 <= lineno < len(self)):
+ raise IndexError("lineno out of range")
+ ast, start, end = getstatementrange_ast(lineno, self)
+ return start, end
+
+ def deindent(self):
+ """return a new source object deindented."""
+ newsource = Source()
+ newsource.lines[:] = deindent(self.lines)
+ return newsource
+
+ def isparseable(self, deindent=True):
+ """ return True if source is parseable, heuristically
+ deindenting it by default.
+ """
+ if deindent:
+ source = str(self.deindent())
+ else:
+ source = str(self)
+ try:
ast.parse(source)
except (SyntaxError, ValueError, TypeError):
- return False
- else:
- return True
-
- def __str__(self):
- return "\n".join(self.lines)
-
- def compile(
- self, filename=None, mode="exec", flag=0, dont_inherit=0, _genframe=None
- ):
- """ return compiled code object. if filename is None
- invent an artificial filename which displays
- the source/line position of the caller frame.
- """
- if not filename or py.path.local(filename).check(file=0):
- if _genframe is None:
- _genframe = sys._getframe(1) # the caller
- fn, lineno = _genframe.f_code.co_filename, _genframe.f_lineno
- base = "<%d-codegen " % self._compilecounter
- self.__class__._compilecounter += 1
- if not filename:
- filename = base + "%s:%d>" % (fn, lineno)
- else:
- filename = base + "%r %s:%d>" % (filename, fn, lineno)
- source = "\n".join(self.lines) + "\n"
- try:
- co = compile(source, filename, mode, flag)
- except SyntaxError:
- ex = sys.exc_info()[1]
- # re-represent syntax errors from parsing python strings
- msglines = self.lines[: ex.lineno]
- if ex.offset:
- msglines.append(" " * ex.offset + "^")
- msglines.append("(code was compiled probably from here: %s)" % filename)
- newex = SyntaxError("\n".join(msglines))
- newex.offset = ex.offset
- newex.lineno = ex.lineno
- newex.text = ex.text
- raise newex
- else:
- if flag & _AST_FLAG:
- return co
- lines = [(x + "\n") for x in self.lines]
- linecache.cache[filename] = (1, None, lines, filename)
- return co
-
-
-#
-# public API shortcut functions
-#
-
-
-def compile_(source, filename=None, mode="exec", flags=0, dont_inherit=0):
- """ compile the given source to a raw code object,
- and maintain an internal cache which allows later
- retrieval of the source code for the code object
- and any recursively created code objects.
- """
- if isinstance(source, ast.AST):
- # XXX should Source support having AST?
- return compile(source, filename, mode, flags, dont_inherit)
- _genframe = sys._getframe(1) # the caller
- s = Source(source)
- co = s.compile(filename, mode, flags, _genframe=_genframe)
- return co
-
-
-def getfslineno(obj):
- """ Return source location (path, lineno) for the given object.
+ return False
+ else:
+ return True
+
+ def __str__(self):
+ return "\n".join(self.lines)
+
+ def compile(
+ self, filename=None, mode="exec", flag=0, dont_inherit=0, _genframe=None
+ ):
+ """ return compiled code object. if filename is None
+ invent an artificial filename which displays
+ the source/line position of the caller frame.
+ """
+ if not filename or py.path.local(filename).check(file=0):
+ if _genframe is None:
+ _genframe = sys._getframe(1) # the caller
+ fn, lineno = _genframe.f_code.co_filename, _genframe.f_lineno
+ base = "<%d-codegen " % self._compilecounter
+ self.__class__._compilecounter += 1
+ if not filename:
+ filename = base + "%s:%d>" % (fn, lineno)
+ else:
+ filename = base + "%r %s:%d>" % (filename, fn, lineno)
+ source = "\n".join(self.lines) + "\n"
+ try:
+ co = compile(source, filename, mode, flag)
+ except SyntaxError:
+ ex = sys.exc_info()[1]
+ # re-represent syntax errors from parsing python strings
+ msglines = self.lines[: ex.lineno]
+ if ex.offset:
+ msglines.append(" " * ex.offset + "^")
+ msglines.append("(code was compiled probably from here: %s)" % filename)
+ newex = SyntaxError("\n".join(msglines))
+ newex.offset = ex.offset
+ newex.lineno = ex.lineno
+ newex.text = ex.text
+ raise newex
+ else:
+ if flag & _AST_FLAG:
+ return co
+ lines = [(x + "\n") for x in self.lines]
+ linecache.cache[filename] = (1, None, lines, filename)
+ return co
+
+
+#
+# public API shortcut functions
+#
+
+
+def compile_(source, filename=None, mode="exec", flags=0, dont_inherit=0):
+ """ compile the given source to a raw code object,
+ and maintain an internal cache which allows later
+ retrieval of the source code for the code object
+ and any recursively created code objects.
+ """
+ if isinstance(source, ast.AST):
+ # XXX should Source support having AST?
+ return compile(source, filename, mode, flags, dont_inherit)
+ _genframe = sys._getframe(1) # the caller
+ s = Source(source)
+ co = s.compile(filename, mode, flags, _genframe=_genframe)
+ return co
+
+
+def getfslineno(obj):
+ """ Return source location (path, lineno) for the given object.
If the source cannot be determined return ("", -1).
The line number is 0-based.
- """
- from .code import Code
-
- try:
- code = Code(obj)
- except TypeError:
- try:
- fn = inspect.getsourcefile(obj) or inspect.getfile(obj)
- except TypeError:
- return "", -1
-
- fspath = fn and py.path.local(fn) or None
- lineno = -1
- if fspath:
- try:
- _, lineno = findsource(obj)
- except IOError:
- pass
- else:
- fspath = code.path
- lineno = code.firstlineno
- assert isinstance(lineno, int)
- return fspath, lineno
-
-
-#
-# helper functions
-#
-
-
-def findsource(obj):
- try:
- sourcelines, lineno = inspect.findsource(obj)
+ """
+ from .code import Code
+
+ try:
+ code = Code(obj)
+ except TypeError:
+ try:
+ fn = inspect.getsourcefile(obj) or inspect.getfile(obj)
+ except TypeError:
+ return "", -1
+
+ fspath = fn and py.path.local(fn) or None
+ lineno = -1
+ if fspath:
+ try:
+ _, lineno = findsource(obj)
+ except IOError:
+ pass
+ else:
+ fspath = code.path
+ lineno = code.firstlineno
+ assert isinstance(lineno, int)
+ return fspath, lineno
+
+
+#
+# helper functions
+#
+
+
+def findsource(obj):
+ try:
+ sourcelines, lineno = inspect.findsource(obj)
except Exception:
- return None, -1
- source = Source()
- source.lines = [line.rstrip() for line in sourcelines]
- return source, lineno
-
-
-def getsource(obj, **kwargs):
- from .code import getrawcode
-
- obj = getrawcode(obj)
- try:
- strsrc = inspect.getsource(obj)
- except IndentationError:
- strsrc = '"Buggy python version consider upgrading, cannot get source"'
- assert isinstance(strsrc, str)
- return Source(strsrc, **kwargs)
-
-
-def deindent(lines):
- return textwrap.dedent("\n".join(lines)).splitlines()
-
-
-def get_statement_startend2(lineno, node):
- import ast
-
- # flatten all statements and except handlers into one lineno-list
- # AST's line numbers start indexing at 1
- values = []
- for x in ast.walk(node):
- if isinstance(x, (ast.stmt, ast.ExceptHandler)):
- values.append(x.lineno - 1)
- for name in ("finalbody", "orelse"):
- val = getattr(x, name, None)
- if val:
- # treat the finally/orelse part as its own statement
- values.append(val[0].lineno - 1 - 1)
- values.sort()
- insert_index = bisect_right(values, lineno)
- start = values[insert_index - 1]
- if insert_index >= len(values):
- end = None
- else:
- end = values[insert_index]
- return start, end
-
-
-def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
- if astnode is None:
- content = str(source)
- # See #4260:
- # don't produce duplicate warnings when compiling source to find ast
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- astnode = compile(content, "source", "exec", _AST_FLAG)
-
- start, end = get_statement_startend2(lineno, astnode)
- # we need to correct the end:
- # - ast-parsing strips comments
- # - there might be empty lines
- # - we might have lesser indented code blocks at the end
- if end is None:
- end = len(source.lines)
-
- if end > start + 1:
- # make sure we don't span differently indented code blocks
- # by using the BlockFinder helper used which inspect.getsource() uses itself
- block_finder = inspect.BlockFinder()
- # if we start with an indented line, put blockfinder to "started" mode
- block_finder.started = source.lines[start][0].isspace()
- it = ((x + "\n") for x in source.lines[start:end])
- try:
- for tok in tokenize.generate_tokens(lambda: next(it)):
- block_finder.tokeneater(*tok)
- except (inspect.EndOfBlock, IndentationError):
- end = block_finder.last + start
- except Exception:
- pass
-
- # the end might still point to a comment or empty line, correct it
- while end:
- line = source.lines[end - 1].lstrip()
- if line.startswith("#") or not line:
- end -= 1
- else:
- break
- return astnode, start, end
+ return None, -1
+ source = Source()
+ source.lines = [line.rstrip() for line in sourcelines]
+ return source, lineno
+
+
+def getsource(obj, **kwargs):
+ from .code import getrawcode
+
+ obj = getrawcode(obj)
+ try:
+ strsrc = inspect.getsource(obj)
+ except IndentationError:
+ strsrc = '"Buggy python version consider upgrading, cannot get source"'
+ assert isinstance(strsrc, str)
+ return Source(strsrc, **kwargs)
+
+
+def deindent(lines):
+ return textwrap.dedent("\n".join(lines)).splitlines()
+
+
+def get_statement_startend2(lineno, node):
+ import ast
+
+ # flatten all statements and except handlers into one lineno-list
+ # AST's line numbers start indexing at 1
+ values = []
+ for x in ast.walk(node):
+ if isinstance(x, (ast.stmt, ast.ExceptHandler)):
+ values.append(x.lineno - 1)
+ for name in ("finalbody", "orelse"):
+ val = getattr(x, name, None)
+ if val:
+ # treat the finally/orelse part as its own statement
+ values.append(val[0].lineno - 1 - 1)
+ values.sort()
+ insert_index = bisect_right(values, lineno)
+ start = values[insert_index - 1]
+ if insert_index >= len(values):
+ end = None
+ else:
+ end = values[insert_index]
+ return start, end
+
+
+def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
+ if astnode is None:
+ content = str(source)
+ # See #4260:
+ # don't produce duplicate warnings when compiling source to find ast
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ astnode = compile(content, "source", "exec", _AST_FLAG)
+
+ start, end = get_statement_startend2(lineno, astnode)
+ # we need to correct the end:
+ # - ast-parsing strips comments
+ # - there might be empty lines
+ # - we might have lesser indented code blocks at the end
+ if end is None:
+ end = len(source.lines)
+
+ if end > start + 1:
+ # make sure we don't span differently indented code blocks
+ # by using the BlockFinder helper used which inspect.getsource() uses itself
+ block_finder = inspect.BlockFinder()
+ # if we start with an indented line, put blockfinder to "started" mode
+ block_finder.started = source.lines[start][0].isspace()
+ it = ((x + "\n") for x in source.lines[start:end])
+ try:
+ for tok in tokenize.generate_tokens(lambda: next(it)):
+ block_finder.tokeneater(*tok)
+ except (inspect.EndOfBlock, IndentationError):
+ end = block_finder.last + start
+ except Exception:
+ pass
+
+ # the end might still point to a comment or empty line, correct it
+ while end:
+ line = source.lines[end - 1].lstrip()
+ if line.startswith("#") or not line:
+ end -= 1
+ else:
+ break
+ return astnode, start, end
diff --git a/contrib/python/pytest/py2/_pytest/_version.py b/contrib/python/pytest/py2/_pytest/_version.py
index c9d35307e4..3d587586fc 100644
--- a/contrib/python/pytest/py2/_pytest/_version.py
+++ b/contrib/python/pytest/py2/_pytest/_version.py
@@ -1,4 +1,4 @@
-# coding: utf-8
-# file generated by setuptools_scm
-# don't change, don't track in version control
+# coding: utf-8
+# file generated by setuptools_scm
+# don't change, don't track in version control
version = '4.6.11'
diff --git a/contrib/python/pytest/py2/_pytest/assertion/__init__.py b/contrib/python/pytest/py2/_pytest/assertion/__init__.py
index f5e0972876..6b6abb863a 100644
--- a/contrib/python/pytest/py2/_pytest/assertion/__init__.py
+++ b/contrib/python/pytest/py2/_pytest/assertion/__init__.py
@@ -1,156 +1,156 @@
# -*- coding: utf-8 -*-
-"""
-support for presenting detailed information in failing assertions.
-"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import sys
-
-import six
-
-from _pytest.assertion import rewrite
-from _pytest.assertion import truncate
-from _pytest.assertion import util
-
-
-def pytest_addoption(parser):
- group = parser.getgroup("debugconfig")
- group.addoption(
- "--assert",
- action="store",
- dest="assertmode",
- choices=("rewrite", "plain"),
- default="rewrite",
- metavar="MODE",
- help="""Control assertion debugging tools. 'plain'
- performs no assertion debugging. 'rewrite'
- (the default) rewrites assert statements in
- test modules on import to provide assert
- expression information.""",
- )
-
-
-def register_assert_rewrite(*names):
- """Register one or more module names to be rewritten on import.
-
- This function will make sure that this module or all modules inside
- the package will get their assert statements rewritten.
- Thus you should make sure to call this before the module is
- actually imported, usually in your __init__.py if you are a plugin
- using a package.
-
- :raise TypeError: if the given module names are not strings.
- """
- for name in names:
- if not isinstance(name, str):
- msg = "expected module names as *args, got {0} instead"
- raise TypeError(msg.format(repr(names)))
- for hook in sys.meta_path:
- if isinstance(hook, rewrite.AssertionRewritingHook):
- importhook = hook
- break
- else:
- importhook = DummyRewriteHook()
- importhook.mark_rewrite(*names)
-
-
-class DummyRewriteHook(object):
- """A no-op import hook for when rewriting is disabled."""
-
- def mark_rewrite(self, *names):
- pass
-
-
-class AssertionState(object):
- """State for the assertion plugin."""
-
- def __init__(self, config, mode):
- self.mode = mode
- self.trace = config.trace.root.get("assertion")
- self.hook = None
-
-
-def install_importhook(config):
- """Try to install the rewrite hook, raise SystemError if it fails."""
- # Jython has an AST bug that make the assertion rewriting hook malfunction.
- if sys.platform.startswith("java"):
- raise SystemError("rewrite not supported")
-
- config._assertstate = AssertionState(config, "rewrite")
- config._assertstate.hook = hook = rewrite.AssertionRewritingHook(config)
- sys.meta_path.insert(0, hook)
- config._assertstate.trace("installed rewrite import hook")
-
- def undo():
- hook = config._assertstate.hook
- if hook is not None and hook in sys.meta_path:
- sys.meta_path.remove(hook)
-
- config.add_cleanup(undo)
- return hook
-
-
-def pytest_collection(session):
- # this hook is only called when test modules are collected
- # so for example not in the master process of pytest-xdist
- # (which does not collect test modules)
- assertstate = getattr(session.config, "_assertstate", None)
- if assertstate:
- if assertstate.hook is not None:
- assertstate.hook.set_session(session)
-
-
-def pytest_runtest_setup(item):
- """Setup the pytest_assertrepr_compare hook
-
- The newinterpret and rewrite modules will use util._reprcompare if
- it exists to use custom reporting via the
- pytest_assertrepr_compare hook. This sets up this custom
- comparison for the test.
- """
-
- def callbinrepr(op, left, right):
- """Call the pytest_assertrepr_compare hook and prepare the result
-
- This uses the first result from the hook and then ensures the
- following:
- * Overly verbose explanations are truncated unless configured otherwise
- (eg. if running in verbose mode).
- * Embedded newlines are escaped to help util.format_explanation()
- later.
- * If the rewrite mode is used embedded %-characters are replaced
- to protect later % formatting.
-
- The result can be formatted by util.format_explanation() for
- pretty printing.
- """
- hook_result = item.ihook.pytest_assertrepr_compare(
- config=item.config, op=op, left=left, right=right
- )
- for new_expl in hook_result:
- if new_expl:
- new_expl = truncate.truncate_if_required(new_expl, item)
- new_expl = [line.replace("\n", "\\n") for line in new_expl]
- res = six.text_type("\n~").join(new_expl)
- if item.config.getvalue("assertmode") == "rewrite":
- res = res.replace("%", "%%")
- return res
-
- util._reprcompare = callbinrepr
-
-
-def pytest_runtest_teardown(item):
- util._reprcompare = None
-
-
-def pytest_sessionfinish(session):
- assertstate = getattr(session.config, "_assertstate", None)
- if assertstate:
- if assertstate.hook is not None:
- assertstate.hook.set_session(None)
-
-
-# Expose this plugin's implementation for the pytest_assertrepr_compare hook
-pytest_assertrepr_compare = util.assertrepr_compare
+"""
+support for presenting detailed information in failing assertions.
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import sys
+
+import six
+
+from _pytest.assertion import rewrite
+from _pytest.assertion import truncate
+from _pytest.assertion import util
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("debugconfig")
+ group.addoption(
+ "--assert",
+ action="store",
+ dest="assertmode",
+ choices=("rewrite", "plain"),
+ default="rewrite",
+ metavar="MODE",
+ help="""Control assertion debugging tools. 'plain'
+ performs no assertion debugging. 'rewrite'
+ (the default) rewrites assert statements in
+ test modules on import to provide assert
+ expression information.""",
+ )
+
+
+def register_assert_rewrite(*names):
+ """Register one or more module names to be rewritten on import.
+
+ This function will make sure that this module or all modules inside
+ the package will get their assert statements rewritten.
+ Thus you should make sure to call this before the module is
+ actually imported, usually in your __init__.py if you are a plugin
+ using a package.
+
+ :raise TypeError: if the given module names are not strings.
+ """
+ for name in names:
+ if not isinstance(name, str):
+ msg = "expected module names as *args, got {0} instead"
+ raise TypeError(msg.format(repr(names)))
+ for hook in sys.meta_path:
+ if isinstance(hook, rewrite.AssertionRewritingHook):
+ importhook = hook
+ break
+ else:
+ importhook = DummyRewriteHook()
+ importhook.mark_rewrite(*names)
+
+
+class DummyRewriteHook(object):
+ """A no-op import hook for when rewriting is disabled."""
+
+ def mark_rewrite(self, *names):
+ pass
+
+
+class AssertionState(object):
+ """State for the assertion plugin."""
+
+ def __init__(self, config, mode):
+ self.mode = mode
+ self.trace = config.trace.root.get("assertion")
+ self.hook = None
+
+
+def install_importhook(config):
+ """Try to install the rewrite hook, raise SystemError if it fails."""
+ # Jython has an AST bug that make the assertion rewriting hook malfunction.
+ if sys.platform.startswith("java"):
+ raise SystemError("rewrite not supported")
+
+ config._assertstate = AssertionState(config, "rewrite")
+ config._assertstate.hook = hook = rewrite.AssertionRewritingHook(config)
+ sys.meta_path.insert(0, hook)
+ config._assertstate.trace("installed rewrite import hook")
+
+ def undo():
+ hook = config._assertstate.hook
+ if hook is not None and hook in sys.meta_path:
+ sys.meta_path.remove(hook)
+
+ config.add_cleanup(undo)
+ return hook
+
+
+def pytest_collection(session):
+ # this hook is only called when test modules are collected
+ # so for example not in the master process of pytest-xdist
+ # (which does not collect test modules)
+ assertstate = getattr(session.config, "_assertstate", None)
+ if assertstate:
+ if assertstate.hook is not None:
+ assertstate.hook.set_session(session)
+
+
+def pytest_runtest_setup(item):
+ """Setup the pytest_assertrepr_compare hook
+
+ The newinterpret and rewrite modules will use util._reprcompare if
+ it exists to use custom reporting via the
+ pytest_assertrepr_compare hook. This sets up this custom
+ comparison for the test.
+ """
+
+ def callbinrepr(op, left, right):
+ """Call the pytest_assertrepr_compare hook and prepare the result
+
+ This uses the first result from the hook and then ensures the
+ following:
+ * Overly verbose explanations are truncated unless configured otherwise
+ (eg. if running in verbose mode).
+ * Embedded newlines are escaped to help util.format_explanation()
+ later.
+ * If the rewrite mode is used embedded %-characters are replaced
+ to protect later % formatting.
+
+ The result can be formatted by util.format_explanation() for
+ pretty printing.
+ """
+ hook_result = item.ihook.pytest_assertrepr_compare(
+ config=item.config, op=op, left=left, right=right
+ )
+ for new_expl in hook_result:
+ if new_expl:
+ new_expl = truncate.truncate_if_required(new_expl, item)
+ new_expl = [line.replace("\n", "\\n") for line in new_expl]
+ res = six.text_type("\n~").join(new_expl)
+ if item.config.getvalue("assertmode") == "rewrite":
+ res = res.replace("%", "%%")
+ return res
+
+ util._reprcompare = callbinrepr
+
+
+def pytest_runtest_teardown(item):
+ util._reprcompare = None
+
+
+def pytest_sessionfinish(session):
+ assertstate = getattr(session.config, "_assertstate", None)
+ if assertstate:
+ if assertstate.hook is not None:
+ assertstate.hook.set_session(None)
+
+
+# Expose this plugin's implementation for the pytest_assertrepr_compare hook
+pytest_assertrepr_compare = util.assertrepr_compare
diff --git a/contrib/python/pytest/py2/_pytest/assertion/rewrite.py b/contrib/python/pytest/py2/_pytest/assertion/rewrite.py
index 6f619ed6a1..6cfd81a32f 100644
--- a/contrib/python/pytest/py2/_pytest/assertion/rewrite.py
+++ b/contrib/python/pytest/py2/_pytest/assertion/rewrite.py
@@ -1,834 +1,834 @@
# -*- coding: utf-8 -*-
-"""Rewrite assertion AST to produce nice error messages"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
+"""Rewrite assertion AST to produce nice error messages"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning, module="_pytest.assertion.rewrite")
-import ast
-import errno
-import imp
-import itertools
-import marshal
-import os
-import re
-import string
-import struct
-import sys
-import types
-
-import atomicwrites
-import py
-import six
-
+import ast
+import errno
+import imp
+import itertools
+import marshal
+import os
+import re
+import string
+import struct
+import sys
+import types
+
+import atomicwrites
+import py
+import six
+
from _pytest._io.saferepr import saferepr
-from _pytest.assertion import util
+from _pytest.assertion import util
from _pytest.assertion.util import ( # noqa: F401
format_explanation as _format_explanation,
)
-from _pytest.compat import spec_from_file_location
-from _pytest.pathlib import fnmatch_ex
-from _pytest.pathlib import PurePath
-
-# pytest caches rewritten pycs in __pycache__.
-if hasattr(imp, "get_tag"):
- PYTEST_TAG = imp.get_tag() + "-PYTEST"
-else:
- if hasattr(sys, "pypy_version_info"):
- impl = "pypy"
- elif sys.platform == "java":
- impl = "jython"
- else:
- impl = "cpython"
- ver = sys.version_info
- PYTEST_TAG = "%s-%s%s-PYTEST" % (impl, ver[0], ver[1])
- del ver, impl
-
-PYC_EXT = ".py" + (__debug__ and "c" or "o")
-PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
-
-ASCII_IS_DEFAULT_ENCODING = sys.version_info[0] < 3
-
-if sys.version_info >= (3, 5):
- ast_Call = ast.Call
-else:
-
- def ast_Call(a, b, c):
- return ast.Call(a, b, c, None, None)
-
-
-class AssertionRewritingHook(object):
- """PEP302 Import hook which rewrites asserts."""
-
- def __init__(self, config):
- self.config = config
+from _pytest.compat import spec_from_file_location
+from _pytest.pathlib import fnmatch_ex
+from _pytest.pathlib import PurePath
+
+# pytest caches rewritten pycs in __pycache__.
+if hasattr(imp, "get_tag"):
+ PYTEST_TAG = imp.get_tag() + "-PYTEST"
+else:
+ if hasattr(sys, "pypy_version_info"):
+ impl = "pypy"
+ elif sys.platform == "java":
+ impl = "jython"
+ else:
+ impl = "cpython"
+ ver = sys.version_info
+ PYTEST_TAG = "%s-%s%s-PYTEST" % (impl, ver[0], ver[1])
+ del ver, impl
+
+PYC_EXT = ".py" + (__debug__ and "c" or "o")
+PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
+
+ASCII_IS_DEFAULT_ENCODING = sys.version_info[0] < 3
+
+if sys.version_info >= (3, 5):
+ ast_Call = ast.Call
+else:
+
+ def ast_Call(a, b, c):
+ return ast.Call(a, b, c, None, None)
+
+
+class AssertionRewritingHook(object):
+ """PEP302 Import hook which rewrites asserts."""
+
+ def __init__(self, config):
+ self.config = config
try:
self.fnpats = config.getini("python_files")
except ValueError:
self.fnpats = ["test_*.py", "*_test.py"]
- self.session = None
- self.modules = {}
- self._rewritten_names = set()
- self._must_rewrite = set()
- # flag to guard against trying to rewrite a pyc file while we are already writing another pyc file,
- # which might result in infinite recursion (#3506)
- self._writing_pyc = False
- self._basenames_to_check_rewrite = {"conftest"}
- self._marked_for_rewrite_cache = {}
- self._session_paths_checked = False
-
- def set_session(self, session):
- self.session = session
- self._session_paths_checked = False
-
- def _imp_find_module(self, name, path=None):
- """Indirection so we can mock calls to find_module originated from the hook during testing"""
- return imp.find_module(name, path)
-
- def find_module(self, name, path=None):
- if self._writing_pyc:
- return None
- state = self.config._assertstate
- if self._early_rewrite_bailout(name, state):
- return None
- state.trace("find_module called for: %s" % name)
- names = name.rsplit(".", 1)
- lastname = names[-1]
- pth = None
- if path is not None:
- # Starting with Python 3.3, path is a _NamespacePath(), which
- # causes problems if not converted to list.
- path = list(path)
- if len(path) == 1:
- pth = path[0]
- if pth is None:
- try:
- fd, fn, desc = self._imp_find_module(lastname, path)
- except ImportError:
- return None
- if fd is not None:
- fd.close()
- tp = desc[2]
- if tp == imp.PY_COMPILED:
- if hasattr(imp, "source_from_cache"):
- try:
- fn = imp.source_from_cache(fn)
- except ValueError:
- # Python 3 doesn't like orphaned but still-importable
- # .pyc files.
- fn = fn[:-1]
- else:
- fn = fn[:-1]
- elif tp != imp.PY_SOURCE:
- # Don't know what this is.
- return None
- else:
- fn = os.path.join(pth, name.rpartition(".")[2] + ".py")
-
- fn_pypath = py.path.local(fn)
- if not self._should_rewrite(name, fn_pypath, state):
- return None
-
- self._rewritten_names.add(name)
-
- # The requested module looks like a test file, so rewrite it. This is
- # the most magical part of the process: load the source, rewrite the
- # asserts, and load the rewritten source. We also cache the rewritten
- # module code in a special pyc. We must be aware of the possibility of
- # concurrent pytest processes rewriting and loading pycs. To avoid
- # tricky race conditions, we maintain the following invariant: The
- # cached pyc is always a complete, valid pyc. Operations on it must be
- # atomic. POSIX's atomic rename comes in handy.
- write = not sys.dont_write_bytecode
- cache_dir = os.path.join(fn_pypath.dirname, "__pycache__")
- if write:
- try:
- os.mkdir(cache_dir)
- except OSError:
- e = sys.exc_info()[1].errno
- if e == errno.EEXIST:
- # Either the __pycache__ directory already exists (the
- # common case) or it's blocked by a non-dir node. In the
- # latter case, we'll ignore it in _write_pyc.
- pass
- elif e in [errno.ENOENT, errno.ENOTDIR]:
- # One of the path components was not a directory, likely
- # because we're in a zip file.
- write = False
- elif e in [errno.EACCES, errno.EROFS, errno.EPERM]:
- state.trace("read only directory: %r" % fn_pypath.dirname)
- write = False
- else:
- raise
- cache_name = fn_pypath.basename[:-3] + PYC_TAIL
- pyc = os.path.join(cache_dir, cache_name)
- # Notice that even if we're in a read-only directory, I'm going
- # to check for a cached pyc. This may not be optimal...
- co = _read_pyc(fn_pypath, pyc, state.trace)
- if co is None:
- state.trace("rewriting %r" % (fn,))
- source_stat, co = _rewrite_test(self.config, fn_pypath)
- if co is None:
- # Probably a SyntaxError in the test.
- return None
- if write:
- self._writing_pyc = True
- try:
- _write_pyc(state, co, source_stat, pyc)
- finally:
- self._writing_pyc = False
- else:
- state.trace("found cached rewritten pyc for %r" % (fn,))
- self.modules[name] = co, pyc
- return self
-
- def _early_rewrite_bailout(self, name, state):
- """
- This is a fast way to get out of rewriting modules. Profiling has
- shown that the call to imp.find_module (inside of the find_module
- from this class) is a major slowdown, so, this method tries to
- filter what we're sure won't be rewritten before getting to it.
- """
- if self.session is not None and not self._session_paths_checked:
- self._session_paths_checked = True
- for path in self.session._initialpaths:
- # Make something as c:/projects/my_project/path.py ->
- # ['c:', 'projects', 'my_project', 'path.py']
- parts = str(path).split(os.path.sep)
- # add 'path' to basenames to be checked.
- self._basenames_to_check_rewrite.add(os.path.splitext(parts[-1])[0])
-
- # Note: conftest already by default in _basenames_to_check_rewrite.
- parts = name.split(".")
- if parts[-1] in self._basenames_to_check_rewrite:
- return False
-
- # For matching the name it must be as if it was a filename.
- path = PurePath(os.path.sep.join(parts) + ".py")
-
- for pat in self.fnpats:
- # if the pattern contains subdirectories ("tests/**.py" for example) we can't bail out based
- # on the name alone because we need to match against the full path
- if os.path.dirname(pat):
- return False
- if fnmatch_ex(pat, path):
- return False
-
- if self._is_marked_for_rewrite(name, state):
- return False
-
- state.trace("early skip of rewriting module: %s" % (name,))
- return True
-
- def _should_rewrite(self, name, fn_pypath, state):
- # always rewrite conftest files
- fn = str(fn_pypath)
- if fn_pypath.basename == "conftest.py":
- state.trace("rewriting conftest file: %r" % (fn,))
- return True
-
- if self.session is not None:
- if self.session.isinitpath(fn):
- state.trace("matched test file (was specified on cmdline): %r" % (fn,))
- return True
-
- # modules not passed explicitly on the command line are only
- # rewritten if they match the naming convention for test files
- for pat in self.fnpats:
- if fn_pypath.fnmatch(pat):
- state.trace("matched test file %r" % (fn,))
- return True
-
- return self._is_marked_for_rewrite(name, state)
-
- def _is_marked_for_rewrite(self, name, state):
- try:
- return self._marked_for_rewrite_cache[name]
- except KeyError:
- for marked in self._must_rewrite:
- if name == marked or name.startswith(marked + "."):
- state.trace("matched marked file %r (from %r)" % (name, marked))
- self._marked_for_rewrite_cache[name] = True
- return True
-
- self._marked_for_rewrite_cache[name] = False
- return False
-
- def mark_rewrite(self, *names):
- """Mark import names as needing to be rewritten.
-
- The named module or package as well as any nested modules will
- be rewritten on import.
- """
- 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)
- self._marked_for_rewrite_cache.clear()
-
- def _warn_already_imported(self, name):
+ self.session = None
+ self.modules = {}
+ self._rewritten_names = set()
+ self._must_rewrite = set()
+ # flag to guard against trying to rewrite a pyc file while we are already writing another pyc file,
+ # which might result in infinite recursion (#3506)
+ self._writing_pyc = False
+ self._basenames_to_check_rewrite = {"conftest"}
+ self._marked_for_rewrite_cache = {}
+ self._session_paths_checked = False
+
+ def set_session(self, session):
+ self.session = session
+ self._session_paths_checked = False
+
+ def _imp_find_module(self, name, path=None):
+ """Indirection so we can mock calls to find_module originated from the hook during testing"""
+ return imp.find_module(name, path)
+
+ def find_module(self, name, path=None):
+ if self._writing_pyc:
+ return None
+ state = self.config._assertstate
+ if self._early_rewrite_bailout(name, state):
+ return None
+ state.trace("find_module called for: %s" % name)
+ names = name.rsplit(".", 1)
+ lastname = names[-1]
+ pth = None
+ if path is not None:
+ # Starting with Python 3.3, path is a _NamespacePath(), which
+ # causes problems if not converted to list.
+ path = list(path)
+ if len(path) == 1:
+ pth = path[0]
+ if pth is None:
+ try:
+ fd, fn, desc = self._imp_find_module(lastname, path)
+ except ImportError:
+ return None
+ if fd is not None:
+ fd.close()
+ tp = desc[2]
+ if tp == imp.PY_COMPILED:
+ if hasattr(imp, "source_from_cache"):
+ try:
+ fn = imp.source_from_cache(fn)
+ except ValueError:
+ # Python 3 doesn't like orphaned but still-importable
+ # .pyc files.
+ fn = fn[:-1]
+ else:
+ fn = fn[:-1]
+ elif tp != imp.PY_SOURCE:
+ # Don't know what this is.
+ return None
+ else:
+ fn = os.path.join(pth, name.rpartition(".")[2] + ".py")
+
+ fn_pypath = py.path.local(fn)
+ if not self._should_rewrite(name, fn_pypath, state):
+ return None
+
+ self._rewritten_names.add(name)
+
+ # The requested module looks like a test file, so rewrite it. This is
+ # the most magical part of the process: load the source, rewrite the
+ # asserts, and load the rewritten source. We also cache the rewritten
+ # module code in a special pyc. We must be aware of the possibility of
+ # concurrent pytest processes rewriting and loading pycs. To avoid
+ # tricky race conditions, we maintain the following invariant: The
+ # cached pyc is always a complete, valid pyc. Operations on it must be
+ # atomic. POSIX's atomic rename comes in handy.
+ write = not sys.dont_write_bytecode
+ cache_dir = os.path.join(fn_pypath.dirname, "__pycache__")
+ if write:
+ try:
+ os.mkdir(cache_dir)
+ except OSError:
+ e = sys.exc_info()[1].errno
+ if e == errno.EEXIST:
+ # Either the __pycache__ directory already exists (the
+ # common case) or it's blocked by a non-dir node. In the
+ # latter case, we'll ignore it in _write_pyc.
+ pass
+ elif e in [errno.ENOENT, errno.ENOTDIR]:
+ # One of the path components was not a directory, likely
+ # because we're in a zip file.
+ write = False
+ elif e in [errno.EACCES, errno.EROFS, errno.EPERM]:
+ state.trace("read only directory: %r" % fn_pypath.dirname)
+ write = False
+ else:
+ raise
+ cache_name = fn_pypath.basename[:-3] + PYC_TAIL
+ pyc = os.path.join(cache_dir, cache_name)
+ # Notice that even if we're in a read-only directory, I'm going
+ # to check for a cached pyc. This may not be optimal...
+ co = _read_pyc(fn_pypath, pyc, state.trace)
+ if co is None:
+ state.trace("rewriting %r" % (fn,))
+ source_stat, co = _rewrite_test(self.config, fn_pypath)
+ if co is None:
+ # Probably a SyntaxError in the test.
+ return None
+ if write:
+ self._writing_pyc = True
+ try:
+ _write_pyc(state, co, source_stat, pyc)
+ finally:
+ self._writing_pyc = False
+ else:
+ state.trace("found cached rewritten pyc for %r" % (fn,))
+ self.modules[name] = co, pyc
+ return self
+
+ def _early_rewrite_bailout(self, name, state):
+ """
+ This is a fast way to get out of rewriting modules. Profiling has
+ shown that the call to imp.find_module (inside of the find_module
+ from this class) is a major slowdown, so, this method tries to
+ filter what we're sure won't be rewritten before getting to it.
+ """
+ if self.session is not None and not self._session_paths_checked:
+ self._session_paths_checked = True
+ for path in self.session._initialpaths:
+ # Make something as c:/projects/my_project/path.py ->
+ # ['c:', 'projects', 'my_project', 'path.py']
+ parts = str(path).split(os.path.sep)
+ # add 'path' to basenames to be checked.
+ self._basenames_to_check_rewrite.add(os.path.splitext(parts[-1])[0])
+
+ # Note: conftest already by default in _basenames_to_check_rewrite.
+ parts = name.split(".")
+ if parts[-1] in self._basenames_to_check_rewrite:
+ return False
+
+ # For matching the name it must be as if it was a filename.
+ path = PurePath(os.path.sep.join(parts) + ".py")
+
+ for pat in self.fnpats:
+ # if the pattern contains subdirectories ("tests/**.py" for example) we can't bail out based
+ # on the name alone because we need to match against the full path
+ if os.path.dirname(pat):
+ return False
+ if fnmatch_ex(pat, path):
+ return False
+
+ if self._is_marked_for_rewrite(name, state):
+ return False
+
+ state.trace("early skip of rewriting module: %s" % (name,))
+ return True
+
+ def _should_rewrite(self, name, fn_pypath, state):
+ # always rewrite conftest files
+ fn = str(fn_pypath)
+ if fn_pypath.basename == "conftest.py":
+ state.trace("rewriting conftest file: %r" % (fn,))
+ return True
+
+ if self.session is not None:
+ if self.session.isinitpath(fn):
+ state.trace("matched test file (was specified on cmdline): %r" % (fn,))
+ return True
+
+ # modules not passed explicitly on the command line are only
+ # rewritten if they match the naming convention for test files
+ for pat in self.fnpats:
+ if fn_pypath.fnmatch(pat):
+ state.trace("matched test file %r" % (fn,))
+ return True
+
+ return self._is_marked_for_rewrite(name, state)
+
+ def _is_marked_for_rewrite(self, name, state):
+ try:
+ return self._marked_for_rewrite_cache[name]
+ except KeyError:
+ for marked in self._must_rewrite:
+ if name == marked or name.startswith(marked + "."):
+ state.trace("matched marked file %r (from %r)" % (name, marked))
+ self._marked_for_rewrite_cache[name] = True
+ return True
+
+ self._marked_for_rewrite_cache[name] = False
+ return False
+
+ def mark_rewrite(self, *names):
+ """Mark import names as needing to be rewritten.
+
+ The named module or package as well as any nested modules will
+ be rewritten on import.
+ """
+ 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)
+ self._marked_for_rewrite_cache.clear()
+
+ def _warn_already_imported(self, name):
from _pytest.warning_types import PytestAssertRewriteWarning
from _pytest.warnings import _issue_warning_captured
-
+
_issue_warning_captured(
PytestAssertRewriteWarning(
"Module already imported so cannot be rewritten: %s" % name
),
self.config.hook,
- stacklevel=5,
- )
-
- def load_module(self, name):
- co, pyc = self.modules.pop(name)
- if name in sys.modules:
- # If there is an existing module object named 'fullname' in
- # sys.modules, the loader must use that existing module. (Otherwise,
- # the reload() builtin will not work correctly.)
- mod = sys.modules[name]
- else:
- # I wish I could just call imp.load_compiled here, but __file__ has to
- # be set properly. In Python 3.2+, this all would be handled correctly
- # by load_compiled.
- mod = sys.modules[name] = imp.new_module(name)
- try:
- mod.__file__ = co.co_filename
- # Normally, this attribute is 3.2+.
- mod.__cached__ = pyc
- mod.__loader__ = self
- # Normally, this attribute is 3.4+
- mod.__spec__ = spec_from_file_location(name, co.co_filename, loader=self)
+ stacklevel=5,
+ )
+
+ def load_module(self, name):
+ co, pyc = self.modules.pop(name)
+ if name in sys.modules:
+ # If there is an existing module object named 'fullname' in
+ # sys.modules, the loader must use that existing module. (Otherwise,
+ # the reload() builtin will not work correctly.)
+ mod = sys.modules[name]
+ else:
+ # I wish I could just call imp.load_compiled here, but __file__ has to
+ # be set properly. In Python 3.2+, this all would be handled correctly
+ # by load_compiled.
+ mod = sys.modules[name] = imp.new_module(name)
+ try:
+ mod.__file__ = co.co_filename
+ # Normally, this attribute is 3.2+.
+ mod.__cached__ = pyc
+ mod.__loader__ = self
+ # Normally, this attribute is 3.4+
+ mod.__spec__ = spec_from_file_location(name, co.co_filename, loader=self)
exec(co, mod.__dict__)
- except: # noqa
- if name in sys.modules:
- del sys.modules[name]
- raise
- return sys.modules[name]
-
- def is_package(self, name):
- try:
- fd, fn, desc = self._imp_find_module(name)
- except ImportError:
- return False
- if fd is not None:
- fd.close()
- tp = desc[2]
- return tp == imp.PKG_DIRECTORY
-
- def get_data(self, pathname):
- """Optional PEP302 get_data API.
- """
- with open(pathname, "rb") as f:
- return f.read()
-
-
-def _write_pyc(state, co, source_stat, pyc):
- # Technically, we don't have to have the same pyc format as
- # (C)Python, since these "pycs" should never be seen by builtin
- # import. However, there's little reason deviate, and I hope
- # sometime to be able to use imp.load_compiled to load them. (See
- # the comment in load_module above.)
- try:
- with atomicwrites.atomic_write(pyc, mode="wb", overwrite=True) as fp:
- fp.write(imp.get_magic())
+ except: # noqa
+ if name in sys.modules:
+ del sys.modules[name]
+ raise
+ return sys.modules[name]
+
+ def is_package(self, name):
+ try:
+ fd, fn, desc = self._imp_find_module(name)
+ except ImportError:
+ return False
+ if fd is not None:
+ fd.close()
+ tp = desc[2]
+ return tp == imp.PKG_DIRECTORY
+
+ def get_data(self, pathname):
+ """Optional PEP302 get_data API.
+ """
+ with open(pathname, "rb") as f:
+ return f.read()
+
+
+def _write_pyc(state, co, source_stat, pyc):
+ # Technically, we don't have to have the same pyc format as
+ # (C)Python, since these "pycs" should never be seen by builtin
+ # import. However, there's little reason deviate, and I hope
+ # sometime to be able to use imp.load_compiled to load them. (See
+ # the comment in load_module above.)
+ try:
+ with atomicwrites.atomic_write(pyc, mode="wb", overwrite=True) as fp:
+ fp.write(imp.get_magic())
# as of now, bytecode header expects 32-bit numbers for size and mtime (#4903)
mtime = int(source_stat.mtime) & 0xFFFFFFFF
- size = source_stat.size & 0xFFFFFFFF
+ size = source_stat.size & 0xFFFFFFFF
# "<LL" stands for 2 unsigned longs, little-ending
fp.write(struct.pack("<LL", mtime, size))
- fp.write(marshal.dumps(co))
- except EnvironmentError as e:
- state.trace("error writing pyc file at %s: errno=%s" % (pyc, e.errno))
- # we ignore any failure to write the cache file
- # there are many reasons, permission-denied, __pycache__ being a
- # file etc.
- return False
- return True
-
-
-RN = "\r\n".encode("utf-8")
-N = "\n".encode("utf-8")
-
-cookie_re = re.compile(r"^[ \t\f]*#.*coding[:=][ \t]*[-\w.]+")
-BOM_UTF8 = "\xef\xbb\xbf"
-
-
-def _rewrite_test(config, fn):
- """Try to read and rewrite *fn* and return the code object."""
- state = config._assertstate
- try:
- stat = fn.stat()
- source = fn.read("rb")
- except EnvironmentError:
- return None, None
- if ASCII_IS_DEFAULT_ENCODING:
- # ASCII is the default encoding in Python 2. Without a coding
- # declaration, Python 2 will complain about any bytes in the file
- # outside the ASCII range. Sadly, this behavior does not extend to
- # compile() or ast.parse(), which prefer to interpret the bytes as
- # latin-1. (At least they properly handle explicit coding cookies.) To
- # preserve this error behavior, we could force ast.parse() to use ASCII
- # as the encoding by inserting a coding cookie. Unfortunately, that
- # messes up line numbers. Thus, we have to check ourselves if anything
- # is outside the ASCII range in the case no encoding is explicitly
- # declared. For more context, see issue #269. Yay for Python 3 which
- # gets this right.
- end1 = source.find("\n")
- end2 = source.find("\n", end1 + 1)
- if (
- not source.startswith(BOM_UTF8)
- and cookie_re.match(source[0:end1]) is None
- and cookie_re.match(source[end1 + 1 : end2]) is None
- ):
- if hasattr(state, "_indecode"):
- # encodings imported us again, so don't rewrite.
- return None, None
- state._indecode = True
- try:
- try:
- source.decode("ascii")
- except UnicodeDecodeError:
- # Let it fail in real import.
- return None, None
- finally:
- del state._indecode
- try:
- tree = ast.parse(source, filename=fn.strpath)
- except SyntaxError:
- # Let this pop up again in the real import.
- state.trace("failed to parse: %r" % (fn,))
- return None, None
- rewrite_asserts(tree, fn, config)
- try:
- co = compile(tree, fn.strpath, "exec", dont_inherit=True)
- except SyntaxError:
- # It's possible that this error is from some bug in the
- # assertion rewriting, but I don't know of a fast way to tell.
- state.trace("failed to compile: %r" % (fn,))
- return None, None
- return stat, co
-
-
-def _read_pyc(source, pyc, trace=lambda x: None):
- """Possibly read a pytest pyc containing rewritten code.
-
- Return rewritten code if successful or None if not.
- """
- try:
- fp = open(pyc, "rb")
- except IOError:
- return None
- with fp:
- try:
- mtime = int(source.mtime())
- size = source.size()
- data = fp.read(12)
- except EnvironmentError as e:
- trace("_read_pyc(%s): EnvironmentError %s" % (source, e))
- return None
- # Check for invalid or out of date pyc file.
- if (
- len(data) != 12
- or data[:4] != imp.get_magic()
+ fp.write(marshal.dumps(co))
+ except EnvironmentError as e:
+ state.trace("error writing pyc file at %s: errno=%s" % (pyc, e.errno))
+ # we ignore any failure to write the cache file
+ # there are many reasons, permission-denied, __pycache__ being a
+ # file etc.
+ return False
+ return True
+
+
+RN = "\r\n".encode("utf-8")
+N = "\n".encode("utf-8")
+
+cookie_re = re.compile(r"^[ \t\f]*#.*coding[:=][ \t]*[-\w.]+")
+BOM_UTF8 = "\xef\xbb\xbf"
+
+
+def _rewrite_test(config, fn):
+ """Try to read and rewrite *fn* and return the code object."""
+ state = config._assertstate
+ try:
+ stat = fn.stat()
+ source = fn.read("rb")
+ except EnvironmentError:
+ return None, None
+ if ASCII_IS_DEFAULT_ENCODING:
+ # ASCII is the default encoding in Python 2. Without a coding
+ # declaration, Python 2 will complain about any bytes in the file
+ # outside the ASCII range. Sadly, this behavior does not extend to
+ # compile() or ast.parse(), which prefer to interpret the bytes as
+ # latin-1. (At least they properly handle explicit coding cookies.) To
+ # preserve this error behavior, we could force ast.parse() to use ASCII
+ # as the encoding by inserting a coding cookie. Unfortunately, that
+ # messes up line numbers. Thus, we have to check ourselves if anything
+ # is outside the ASCII range in the case no encoding is explicitly
+ # declared. For more context, see issue #269. Yay for Python 3 which
+ # gets this right.
+ end1 = source.find("\n")
+ end2 = source.find("\n", end1 + 1)
+ if (
+ not source.startswith(BOM_UTF8)
+ and cookie_re.match(source[0:end1]) is None
+ and cookie_re.match(source[end1 + 1 : end2]) is None
+ ):
+ if hasattr(state, "_indecode"):
+ # encodings imported us again, so don't rewrite.
+ return None, None
+ state._indecode = True
+ try:
+ try:
+ source.decode("ascii")
+ except UnicodeDecodeError:
+ # Let it fail in real import.
+ return None, None
+ finally:
+ del state._indecode
+ try:
+ tree = ast.parse(source, filename=fn.strpath)
+ except SyntaxError:
+ # Let this pop up again in the real import.
+ state.trace("failed to parse: %r" % (fn,))
+ return None, None
+ rewrite_asserts(tree, fn, config)
+ try:
+ co = compile(tree, fn.strpath, "exec", dont_inherit=True)
+ except SyntaxError:
+ # It's possible that this error is from some bug in the
+ # assertion rewriting, but I don't know of a fast way to tell.
+ state.trace("failed to compile: %r" % (fn,))
+ return None, None
+ return stat, co
+
+
+def _read_pyc(source, pyc, trace=lambda x: None):
+ """Possibly read a pytest pyc containing rewritten code.
+
+ Return rewritten code if successful or None if not.
+ """
+ try:
+ fp = open(pyc, "rb")
+ except IOError:
+ return None
+ with fp:
+ try:
+ mtime = int(source.mtime())
+ size = source.size()
+ data = fp.read(12)
+ except EnvironmentError as e:
+ trace("_read_pyc(%s): EnvironmentError %s" % (source, e))
+ return None
+ # Check for invalid or out of date pyc file.
+ if (
+ len(data) != 12
+ or data[:4] != imp.get_magic()
or struct.unpack("<LL", data[4:]) != (mtime & 0xFFFFFFFF, size & 0xFFFFFFFF)
- ):
- trace("_read_pyc(%s): invalid or out of date pyc" % source)
- return None
- try:
- co = marshal.load(fp)
- except Exception as e:
- trace("_read_pyc(%s): marshal.load error %s" % (source, e))
- return None
- if not isinstance(co, types.CodeType):
- trace("_read_pyc(%s): not a code object" % source)
- return None
- return co
-
-
-def rewrite_asserts(mod, module_path=None, config=None):
- """Rewrite the assert statements in mod."""
- AssertionRewriter(module_path, config).run(mod)
-
-
-def _saferepr(obj):
- """Get a safe repr of an object for assertion error messages.
-
- The assertion formatting (util.format_explanation()) requires
- newlines to be escaped since they are a special character for it.
- Normally assertion.util.format_explanation() does this but for a
- custom repr it is possible to contain one of the special escape
- sequences, especially '\n{' and '\n}' are likely to be present in
- JSON reprs.
-
- """
+ ):
+ trace("_read_pyc(%s): invalid or out of date pyc" % source)
+ return None
+ try:
+ co = marshal.load(fp)
+ except Exception as e:
+ trace("_read_pyc(%s): marshal.load error %s" % (source, e))
+ return None
+ if not isinstance(co, types.CodeType):
+ trace("_read_pyc(%s): not a code object" % source)
+ return None
+ return co
+
+
+def rewrite_asserts(mod, module_path=None, config=None):
+ """Rewrite the assert statements in mod."""
+ AssertionRewriter(module_path, config).run(mod)
+
+
+def _saferepr(obj):
+ """Get a safe repr of an object for assertion error messages.
+
+ The assertion formatting (util.format_explanation()) requires
+ newlines to be escaped since they are a special character for it.
+ Normally assertion.util.format_explanation() does this but for a
+ custom repr it is possible to contain one of the special escape
+ sequences, especially '\n{' and '\n}' are likely to be present in
+ JSON reprs.
+
+ """
r = saferepr(obj)
- # only occurs in python2.x, repr must return text in python3+
- if isinstance(r, bytes):
- # Represent unprintable bytes as `\x##`
- r = u"".join(
- u"\\x{:x}".format(ord(c)) if c not in string.printable else c.decode()
- for c in r
- )
- return r.replace(u"\n", u"\\n")
-
-
-def _format_assertmsg(obj):
- """Format the custom assertion message given.
-
- For strings this simply replaces newlines with '\n~' so that
- util.format_explanation() will preserve them instead of escaping
+ # only occurs in python2.x, repr must return text in python3+
+ if isinstance(r, bytes):
+ # Represent unprintable bytes as `\x##`
+ r = u"".join(
+ u"\\x{:x}".format(ord(c)) if c not in string.printable else c.decode()
+ for c in r
+ )
+ return r.replace(u"\n", u"\\n")
+
+
+def _format_assertmsg(obj):
+ """Format the custom assertion message given.
+
+ For strings this simply replaces newlines with '\n~' so that
+ util.format_explanation() will preserve them instead of escaping
newlines. For other objects saferepr() is used first.
-
- """
- # reprlib appears to have a bug which means that if a string
- # contains a newline it gets escaped, however if an object has a
- # .__repr__() which contains newlines it does not get escaped.
- # However in either case we want to preserve the newline.
- replaces = [(u"\n", u"\n~"), (u"%", u"%%")]
- if not isinstance(obj, six.string_types):
+
+ """
+ # reprlib appears to have a bug which means that if a string
+ # contains a newline it gets escaped, however if an object has a
+ # .__repr__() which contains newlines it does not get escaped.
+ # However in either case we want to preserve the newline.
+ replaces = [(u"\n", u"\n~"), (u"%", u"%%")]
+ if not isinstance(obj, six.string_types):
obj = saferepr(obj)
- replaces.append((u"\\n", u"\n~"))
-
- if isinstance(obj, bytes):
- replaces = [(r1.encode(), r2.encode()) for r1, r2 in replaces]
-
- for r1, r2 in replaces:
- obj = obj.replace(r1, r2)
-
- return obj
-
-
-def _should_repr_global_name(obj):
+ replaces.append((u"\\n", u"\n~"))
+
+ if isinstance(obj, bytes):
+ replaces = [(r1.encode(), r2.encode()) for r1, r2 in replaces]
+
+ for r1, r2 in replaces:
+ obj = obj.replace(r1, r2)
+
+ return obj
+
+
+def _should_repr_global_name(obj):
if callable(obj):
return False
-
+
try:
return not hasattr(obj, "__name__")
except Exception:
return True
-
-
-def _format_boolop(explanations, is_or):
- explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")"
- if isinstance(explanation, six.text_type):
- return explanation.replace(u"%", u"%%")
- else:
- return explanation.replace(b"%", b"%%")
-
-
-def _call_reprcompare(ops, results, expls, each_obj):
- for i, res, expl in zip(range(len(ops)), results, expls):
- try:
- done = not res
- except Exception:
- done = True
- if done:
- break
- if util._reprcompare is not None:
- custom = util._reprcompare(ops[i], each_obj[i], each_obj[i + 1])
- if custom is not None:
- return custom
- return expl
-
-
-unary_map = {ast.Not: "not %s", ast.Invert: "~%s", ast.USub: "-%s", ast.UAdd: "+%s"}
-
-binop_map = {
- ast.BitOr: "|",
- ast.BitXor: "^",
- ast.BitAnd: "&",
- ast.LShift: "<<",
- ast.RShift: ">>",
- ast.Add: "+",
- ast.Sub: "-",
- ast.Mult: "*",
- ast.Div: "/",
- ast.FloorDiv: "//",
- ast.Mod: "%%", # escaped for string formatting
- ast.Eq: "==",
- ast.NotEq: "!=",
- ast.Lt: "<",
- ast.LtE: "<=",
- ast.Gt: ">",
- ast.GtE: ">=",
- ast.Pow: "**",
- ast.Is: "is",
- ast.IsNot: "is not",
- ast.In: "in",
- ast.NotIn: "not in",
-}
-# Python 3.5+ compatibility
-try:
- binop_map[ast.MatMult] = "@"
-except AttributeError:
- pass
-
-# Python 3.4+ compatibility
-if hasattr(ast, "NameConstant"):
- _NameConstant = ast.NameConstant
-else:
-
- def _NameConstant(c):
- return ast.Name(str(c), ast.Load())
-
-
-def set_location(node, lineno, col_offset):
- """Set node location information recursively."""
-
- def _fix(node, lineno, col_offset):
- if "lineno" in node._attributes:
- node.lineno = lineno
- if "col_offset" in node._attributes:
- node.col_offset = col_offset
- for child in ast.iter_child_nodes(node):
- _fix(child, lineno, col_offset)
-
- _fix(node, lineno, col_offset)
- return node
-
-
-class AssertionRewriter(ast.NodeVisitor):
- """Assertion rewriting implementation.
-
- The main entrypoint is to call .run() with an ast.Module instance,
- this will then find all the assert statements and rewrite them to
- provide intermediate values and a detailed assertion error. See
- http://pybites.blogspot.be/2011/07/behind-scenes-of-pytests-new-assertion.html
- for an overview of how this works.
-
- The entry point here is .run() which will iterate over all the
- statements in an ast.Module and for each ast.Assert statement it
- finds call .visit() with it. Then .visit_Assert() takes over and
- is responsible for creating new ast statements to replace the
- original assert statement: it rewrites the test of an assertion
- to provide intermediate values and replace it with an if statement
- which raises an assertion error with a detailed explanation in
- case the expression is false.
-
- For this .visit_Assert() uses the visitor pattern to visit all the
- AST nodes of the ast.Assert.test field, each visit call returning
- an AST node and the corresponding explanation string. During this
- state is kept in several instance attributes:
-
- :statements: All the AST statements which will replace the assert
- statement.
-
- :variables: This is populated by .variable() with each variable
- used by the statements so that they can all be set to None at
- the end of the statements.
-
- :variable_counter: Counter to create new unique variables needed
- by statements. Variables are created using .variable() and
- have the form of "@py_assert0".
-
- :on_failure: The AST statements which will be executed if the
- assertion test fails. This is the code which will construct
- the failure message and raises the AssertionError.
-
- :explanation_specifiers: A dict filled by .explanation_param()
- with %-formatting placeholders and their corresponding
- expressions to use in the building of an assertion message.
- This is used by .pop_format_context() to build a message.
-
- :stack: A stack of the explanation_specifiers dicts maintained by
- .push_format_context() and .pop_format_context() which allows
- to build another %-formatted string while already building one.
-
- This state is reset on every new assert statement visited and used
- by the other visitors.
-
- """
-
- def __init__(self, module_path, config):
- super(AssertionRewriter, self).__init__()
- self.module_path = module_path
- self.config = config
-
- def run(self, mod):
- """Find all assert statements in *mod* and rewrite them."""
- if not mod.body:
- # Nothing to do.
- return
- # Insert some special imports at the top of the module but after any
- # docstrings and __future__ imports.
- aliases = [
+
+
+def _format_boolop(explanations, is_or):
+ explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")"
+ if isinstance(explanation, six.text_type):
+ return explanation.replace(u"%", u"%%")
+ else:
+ return explanation.replace(b"%", b"%%")
+
+
+def _call_reprcompare(ops, results, expls, each_obj):
+ for i, res, expl in zip(range(len(ops)), results, expls):
+ try:
+ done = not res
+ except Exception:
+ done = True
+ if done:
+ break
+ if util._reprcompare is not None:
+ custom = util._reprcompare(ops[i], each_obj[i], each_obj[i + 1])
+ if custom is not None:
+ return custom
+ return expl
+
+
+unary_map = {ast.Not: "not %s", ast.Invert: "~%s", ast.USub: "-%s", ast.UAdd: "+%s"}
+
+binop_map = {
+ ast.BitOr: "|",
+ ast.BitXor: "^",
+ ast.BitAnd: "&",
+ ast.LShift: "<<",
+ ast.RShift: ">>",
+ ast.Add: "+",
+ ast.Sub: "-",
+ ast.Mult: "*",
+ ast.Div: "/",
+ ast.FloorDiv: "//",
+ ast.Mod: "%%", # escaped for string formatting
+ ast.Eq: "==",
+ ast.NotEq: "!=",
+ ast.Lt: "<",
+ ast.LtE: "<=",
+ ast.Gt: ">",
+ ast.GtE: ">=",
+ ast.Pow: "**",
+ ast.Is: "is",
+ ast.IsNot: "is not",
+ ast.In: "in",
+ ast.NotIn: "not in",
+}
+# Python 3.5+ compatibility
+try:
+ binop_map[ast.MatMult] = "@"
+except AttributeError:
+ pass
+
+# Python 3.4+ compatibility
+if hasattr(ast, "NameConstant"):
+ _NameConstant = ast.NameConstant
+else:
+
+ def _NameConstant(c):
+ return ast.Name(str(c), ast.Load())
+
+
+def set_location(node, lineno, col_offset):
+ """Set node location information recursively."""
+
+ def _fix(node, lineno, col_offset):
+ if "lineno" in node._attributes:
+ node.lineno = lineno
+ if "col_offset" in node._attributes:
+ node.col_offset = col_offset
+ for child in ast.iter_child_nodes(node):
+ _fix(child, lineno, col_offset)
+
+ _fix(node, lineno, col_offset)
+ return node
+
+
+class AssertionRewriter(ast.NodeVisitor):
+ """Assertion rewriting implementation.
+
+ The main entrypoint is to call .run() with an ast.Module instance,
+ this will then find all the assert statements and rewrite them to
+ provide intermediate values and a detailed assertion error. See
+ http://pybites.blogspot.be/2011/07/behind-scenes-of-pytests-new-assertion.html
+ for an overview of how this works.
+
+ The entry point here is .run() which will iterate over all the
+ statements in an ast.Module and for each ast.Assert statement it
+ finds call .visit() with it. Then .visit_Assert() takes over and
+ is responsible for creating new ast statements to replace the
+ original assert statement: it rewrites the test of an assertion
+ to provide intermediate values and replace it with an if statement
+ which raises an assertion error with a detailed explanation in
+ case the expression is false.
+
+ For this .visit_Assert() uses the visitor pattern to visit all the
+ AST nodes of the ast.Assert.test field, each visit call returning
+ an AST node and the corresponding explanation string. During this
+ state is kept in several instance attributes:
+
+ :statements: All the AST statements which will replace the assert
+ statement.
+
+ :variables: This is populated by .variable() with each variable
+ used by the statements so that they can all be set to None at
+ the end of the statements.
+
+ :variable_counter: Counter to create new unique variables needed
+ by statements. Variables are created using .variable() and
+ have the form of "@py_assert0".
+
+ :on_failure: The AST statements which will be executed if the
+ assertion test fails. This is the code which will construct
+ the failure message and raises the AssertionError.
+
+ :explanation_specifiers: A dict filled by .explanation_param()
+ with %-formatting placeholders and their corresponding
+ expressions to use in the building of an assertion message.
+ This is used by .pop_format_context() to build a message.
+
+ :stack: A stack of the explanation_specifiers dicts maintained by
+ .push_format_context() and .pop_format_context() which allows
+ to build another %-formatted string while already building one.
+
+ This state is reset on every new assert statement visited and used
+ by the other visitors.
+
+ """
+
+ def __init__(self, module_path, config):
+ super(AssertionRewriter, self).__init__()
+ self.module_path = module_path
+ self.config = config
+
+ def run(self, mod):
+ """Find all assert statements in *mod* and rewrite them."""
+ if not mod.body:
+ # Nothing to do.
+ return
+ # Insert some special imports at the top of the module but after any
+ # docstrings and __future__ imports.
+ aliases = [
ast.alias(six.moves.builtins.__name__, "@py_builtins"),
- ast.alias("_pytest.assertion.rewrite", "@pytest_ar"),
- ]
- doc = getattr(mod, "docstring", None)
- expect_docstring = doc is None
- if doc is not None and self.is_rewrite_disabled(doc):
- return
- pos = 0
- lineno = 1
- for item in mod.body:
- if (
- expect_docstring
- and isinstance(item, ast.Expr)
- and isinstance(item.value, ast.Str)
- ):
- doc = item.value.s
- if self.is_rewrite_disabled(doc):
- return
- expect_docstring = False
- elif (
- not isinstance(item, ast.ImportFrom)
- or item.level > 0
- or item.module != "__future__"
- ):
- lineno = item.lineno
- break
- pos += 1
- else:
- lineno = item.lineno
- imports = [
- ast.Import([alias], lineno=lineno, col_offset=0) for alias in aliases
- ]
- mod.body[pos:pos] = imports
- # Collect asserts.
- nodes = [mod]
- while nodes:
- node = nodes.pop()
- for name, field in ast.iter_fields(node):
- if isinstance(field, list):
- new = []
- for i, child in enumerate(field):
- if isinstance(child, ast.Assert):
- # Transform assert.
- new.extend(self.visit(child))
- else:
- new.append(child)
- if isinstance(child, ast.AST):
- nodes.append(child)
- setattr(node, name, new)
- elif (
- isinstance(field, ast.AST)
- # Don't recurse into expressions as they can't contain
- # asserts.
- and not isinstance(field, ast.expr)
- ):
- nodes.append(field)
-
- @staticmethod
- def is_rewrite_disabled(docstring):
- return "PYTEST_DONT_REWRITE" in docstring
-
- def variable(self):
- """Get a new variable."""
- # Use a character invalid in python identifiers to avoid clashing.
- name = "@py_assert" + str(next(self.variable_counter))
- self.variables.append(name)
- return name
-
- def assign(self, expr):
- """Give *expr* a name."""
- name = self.variable()
- self.statements.append(ast.Assign([ast.Name(name, ast.Store())], expr))
- return ast.Name(name, ast.Load())
-
- def display(self, expr):
+ ast.alias("_pytest.assertion.rewrite", "@pytest_ar"),
+ ]
+ doc = getattr(mod, "docstring", None)
+ expect_docstring = doc is None
+ if doc is not None and self.is_rewrite_disabled(doc):
+ return
+ pos = 0
+ lineno = 1
+ for item in mod.body:
+ if (
+ expect_docstring
+ and isinstance(item, ast.Expr)
+ and isinstance(item.value, ast.Str)
+ ):
+ doc = item.value.s
+ if self.is_rewrite_disabled(doc):
+ return
+ expect_docstring = False
+ elif (
+ not isinstance(item, ast.ImportFrom)
+ or item.level > 0
+ or item.module != "__future__"
+ ):
+ lineno = item.lineno
+ break
+ pos += 1
+ else:
+ lineno = item.lineno
+ imports = [
+ ast.Import([alias], lineno=lineno, col_offset=0) for alias in aliases
+ ]
+ mod.body[pos:pos] = imports
+ # Collect asserts.
+ nodes = [mod]
+ while nodes:
+ node = nodes.pop()
+ for name, field in ast.iter_fields(node):
+ if isinstance(field, list):
+ new = []
+ for i, child in enumerate(field):
+ if isinstance(child, ast.Assert):
+ # Transform assert.
+ new.extend(self.visit(child))
+ else:
+ new.append(child)
+ if isinstance(child, ast.AST):
+ nodes.append(child)
+ setattr(node, name, new)
+ elif (
+ isinstance(field, ast.AST)
+ # Don't recurse into expressions as they can't contain
+ # asserts.
+ and not isinstance(field, ast.expr)
+ ):
+ nodes.append(field)
+
+ @staticmethod
+ def is_rewrite_disabled(docstring):
+ return "PYTEST_DONT_REWRITE" in docstring
+
+ def variable(self):
+ """Get a new variable."""
+ # Use a character invalid in python identifiers to avoid clashing.
+ name = "@py_assert" + str(next(self.variable_counter))
+ self.variables.append(name)
+ return name
+
+ def assign(self, expr):
+ """Give *expr* a name."""
+ name = self.variable()
+ self.statements.append(ast.Assign([ast.Name(name, ast.Store())], expr))
+ return ast.Name(name, ast.Load())
+
+ def display(self, expr):
"""Call saferepr on the expression."""
return self.helper("_saferepr", expr)
-
- def helper(self, name, *args):
- """Call a helper in this module."""
- py_name = ast.Name("@pytest_ar", ast.Load())
+
+ def helper(self, name, *args):
+ """Call a helper in this module."""
+ py_name = ast.Name("@pytest_ar", ast.Load())
attr = ast.Attribute(py_name, name, ast.Load())
- return ast_Call(attr, list(args), [])
-
- def builtin(self, name):
- """Return the builtin called *name*."""
- builtin_name = ast.Name("@py_builtins", ast.Load())
- return ast.Attribute(builtin_name, name, ast.Load())
-
- def explanation_param(self, expr):
- """Return a new named %-formatting placeholder for expr.
-
- This creates a %-formatting placeholder for expr in the
- current formatting context, e.g. ``%(py0)s``. The placeholder
- and expr are placed in the current format context so that it
- can be used on the next call to .pop_format_context().
-
- """
- specifier = "py" + str(next(self.variable_counter))
- self.explanation_specifiers[specifier] = expr
- return "%(" + specifier + ")s"
-
- def push_format_context(self):
- """Create a new formatting context.
-
- The format context is used for when an explanation wants to
- have a variable value formatted in the assertion message. In
- this case the value required can be added using
- .explanation_param(). Finally .pop_format_context() is used
- to format a string of %-formatted values as added by
- .explanation_param().
-
- """
- self.explanation_specifiers = {}
- self.stack.append(self.explanation_specifiers)
-
- def pop_format_context(self, expl_expr):
- """Format the %-formatted string with current format context.
-
- The expl_expr should be an ast.Str instance constructed from
- the %-placeholders created by .explanation_param(). This will
- add the required code to format said string to .on_failure and
- return the ast.Name instance of the formatted string.
-
- """
- current = self.stack.pop()
- if self.stack:
- self.explanation_specifiers = self.stack[-1]
- keys = [ast.Str(key) for key in current.keys()]
- format_dict = ast.Dict(keys, list(current.values()))
- form = ast.BinOp(expl_expr, ast.Mod(), format_dict)
- name = "@py_format" + str(next(self.variable_counter))
- self.on_failure.append(ast.Assign([ast.Name(name, ast.Store())], form))
- return ast.Name(name, ast.Load())
-
- def generic_visit(self, node):
- """Handle expressions we don't have custom code for."""
- assert isinstance(node, ast.expr)
- res = self.assign(node)
- return res, self.explanation_param(self.display(res))
-
- def visit_Assert(self, assert_):
- """Return the AST statements to replace the ast.Assert instance.
-
- This rewrites the test of an assertion to provide
- intermediate values and replace it with an if statement which
- raises an assertion error with a detailed explanation in case
- the expression is false.
-
- """
- if isinstance(assert_.test, ast.Tuple) and len(assert_.test.elts) >= 1:
+ return ast_Call(attr, list(args), [])
+
+ def builtin(self, name):
+ """Return the builtin called *name*."""
+ builtin_name = ast.Name("@py_builtins", ast.Load())
+ return ast.Attribute(builtin_name, name, ast.Load())
+
+ def explanation_param(self, expr):
+ """Return a new named %-formatting placeholder for expr.
+
+ This creates a %-formatting placeholder for expr in the
+ current formatting context, e.g. ``%(py0)s``. The placeholder
+ and expr are placed in the current format context so that it
+ can be used on the next call to .pop_format_context().
+
+ """
+ specifier = "py" + str(next(self.variable_counter))
+ self.explanation_specifiers[specifier] = expr
+ return "%(" + specifier + ")s"
+
+ def push_format_context(self):
+ """Create a new formatting context.
+
+ The format context is used for when an explanation wants to
+ have a variable value formatted in the assertion message. In
+ this case the value required can be added using
+ .explanation_param(). Finally .pop_format_context() is used
+ to format a string of %-formatted values as added by
+ .explanation_param().
+
+ """
+ self.explanation_specifiers = {}
+ self.stack.append(self.explanation_specifiers)
+
+ def pop_format_context(self, expl_expr):
+ """Format the %-formatted string with current format context.
+
+ The expl_expr should be an ast.Str instance constructed from
+ the %-placeholders created by .explanation_param(). This will
+ add the required code to format said string to .on_failure and
+ return the ast.Name instance of the formatted string.
+
+ """
+ current = self.stack.pop()
+ if self.stack:
+ self.explanation_specifiers = self.stack[-1]
+ keys = [ast.Str(key) for key in current.keys()]
+ format_dict = ast.Dict(keys, list(current.values()))
+ form = ast.BinOp(expl_expr, ast.Mod(), format_dict)
+ name = "@py_format" + str(next(self.variable_counter))
+ self.on_failure.append(ast.Assign([ast.Name(name, ast.Store())], form))
+ return ast.Name(name, ast.Load())
+
+ def generic_visit(self, node):
+ """Handle expressions we don't have custom code for."""
+ assert isinstance(node, ast.expr)
+ res = self.assign(node)
+ return res, self.explanation_param(self.display(res))
+
+ def visit_Assert(self, assert_):
+ """Return the AST statements to replace the ast.Assert instance.
+
+ This rewrites the test of an assertion to provide
+ intermediate values and replace it with an if statement which
+ raises an assertion error with a detailed explanation in case
+ the expression is false.
+
+ """
+ if isinstance(assert_.test, ast.Tuple) and len(assert_.test.elts) >= 1:
from _pytest.warning_types import PytestAssertRewriteWarning
- import warnings
-
- warnings.warn_explicit(
+ import warnings
+
+ warnings.warn_explicit(
PytestAssertRewriteWarning(
"assertion is always true, perhaps remove parentheses?"
),
- category=None,
- filename=str(self.module_path),
- lineno=assert_.lineno,
- )
-
- self.statements = []
- self.variables = []
- self.variable_counter = itertools.count()
- self.stack = []
- self.on_failure = []
- self.push_format_context()
- # Rewrite assert into a bunch of statements.
- top_condition, explanation = self.visit(assert_.test)
+ category=None,
+ filename=str(self.module_path),
+ lineno=assert_.lineno,
+ )
+
+ self.statements = []
+ self.variables = []
+ self.variable_counter = itertools.count()
+ self.stack = []
+ self.on_failure = []
+ self.push_format_context()
+ # Rewrite assert into a bunch of statements.
+ top_condition, explanation = self.visit(assert_.test)
# If in a test module, check if directly asserting None, in order to warn [Issue #3191]
if self.module_path is not None:
self.statements.append(
@@ -836,36 +836,36 @@ class AssertionRewriter(ast.NodeVisitor):
top_condition, module_path=self.module_path, lineno=assert_.lineno
)
)
- # Create failure message.
- body = self.on_failure
- negation = ast.UnaryOp(ast.Not(), top_condition)
- self.statements.append(ast.If(negation, body, []))
- if assert_.msg:
+ # Create failure message.
+ body = self.on_failure
+ negation = ast.UnaryOp(ast.Not(), top_condition)
+ self.statements.append(ast.If(negation, body, []))
+ if assert_.msg:
assertmsg = self.helper("_format_assertmsg", assert_.msg)
- explanation = "\n>assert " + explanation
- else:
- assertmsg = ast.Str("")
- explanation = "assert " + explanation
- template = ast.BinOp(assertmsg, ast.Add(), ast.Str(explanation))
- msg = self.pop_format_context(template)
+ explanation = "\n>assert " + explanation
+ else:
+ assertmsg = ast.Str("")
+ explanation = "assert " + explanation
+ template = ast.BinOp(assertmsg, ast.Add(), ast.Str(explanation))
+ msg = self.pop_format_context(template)
fmt = self.helper("_format_explanation", msg)
- err_name = ast.Name("AssertionError", ast.Load())
- exc = ast_Call(err_name, [fmt], [])
- if sys.version_info[0] >= 3:
- raise_ = ast.Raise(exc, None)
- else:
- raise_ = ast.Raise(exc, None, None)
- body.append(raise_)
- # Clear temporary variables by setting them to None.
- if self.variables:
- variables = [ast.Name(name, ast.Store()) for name in self.variables]
- clear = ast.Assign(variables, _NameConstant(None))
- self.statements.append(clear)
- # Fix line numbers.
- for stmt in self.statements:
- set_location(stmt, assert_.lineno, assert_.col_offset)
- return self.statements
-
+ err_name = ast.Name("AssertionError", ast.Load())
+ exc = ast_Call(err_name, [fmt], [])
+ if sys.version_info[0] >= 3:
+ raise_ = ast.Raise(exc, None)
+ else:
+ raise_ = ast.Raise(exc, None, None)
+ body.append(raise_)
+ # Clear temporary variables by setting them to None.
+ if self.variables:
+ variables = [ast.Name(name, ast.Store()) for name in self.variables]
+ clear = ast.Assign(variables, _NameConstant(None))
+ self.statements.append(clear)
+ # Fix line numbers.
+ for stmt in self.statements:
+ set_location(stmt, assert_.lineno, assert_.col_offset)
+ return self.statements
+
def warn_about_none_ast(self, node, module_path, lineno):
"""
Returns an AST issuing a warning if the value of node is `None`.
@@ -893,180 +893,180 @@ warn_explicit(
).body
return ast.If(val_is_none, send_warning, [])
- def visit_Name(self, name):
- # Display the repr of the name if it's a local variable or
- # _should_repr_global_name() thinks it's acceptable.
- locs = ast_Call(self.builtin("locals"), [], [])
- inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs])
+ def visit_Name(self, name):
+ # Display the repr of the name if it's a local variable or
+ # _should_repr_global_name() thinks it's acceptable.
+ locs = ast_Call(self.builtin("locals"), [], [])
+ inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs])
dorepr = self.helper("_should_repr_global_name", name)
- test = ast.BoolOp(ast.Or(), [inlocs, dorepr])
- expr = ast.IfExp(test, self.display(name), ast.Str(name.id))
- return name, self.explanation_param(expr)
-
- def visit_BoolOp(self, boolop):
- res_var = self.variable()
- expl_list = self.assign(ast.List([], ast.Load()))
- app = ast.Attribute(expl_list, "append", ast.Load())
- is_or = int(isinstance(boolop.op, ast.Or))
- body = save = self.statements
- fail_save = self.on_failure
- levels = len(boolop.values) - 1
- self.push_format_context()
- # Process each operand, short-circuting if needed.
- for i, v in enumerate(boolop.values):
- if i:
- fail_inner = []
- # cond is set in a prior loop iteration below
- self.on_failure.append(ast.If(cond, fail_inner, [])) # noqa
- self.on_failure = fail_inner
- self.push_format_context()
- res, expl = self.visit(v)
- body.append(ast.Assign([ast.Name(res_var, ast.Store())], res))
- expl_format = self.pop_format_context(ast.Str(expl))
- call = ast_Call(app, [expl_format], [])
- self.on_failure.append(ast.Expr(call))
- if i < levels:
- cond = res
- if is_or:
- cond = ast.UnaryOp(ast.Not(), cond)
- inner = []
- self.statements.append(ast.If(cond, inner, []))
- self.statements = body = inner
- self.statements = save
- self.on_failure = fail_save
+ test = ast.BoolOp(ast.Or(), [inlocs, dorepr])
+ expr = ast.IfExp(test, self.display(name), ast.Str(name.id))
+ return name, self.explanation_param(expr)
+
+ def visit_BoolOp(self, boolop):
+ res_var = self.variable()
+ expl_list = self.assign(ast.List([], ast.Load()))
+ app = ast.Attribute(expl_list, "append", ast.Load())
+ is_or = int(isinstance(boolop.op, ast.Or))
+ body = save = self.statements
+ fail_save = self.on_failure
+ levels = len(boolop.values) - 1
+ self.push_format_context()
+ # Process each operand, short-circuting if needed.
+ for i, v in enumerate(boolop.values):
+ if i:
+ fail_inner = []
+ # cond is set in a prior loop iteration below
+ self.on_failure.append(ast.If(cond, fail_inner, [])) # noqa
+ self.on_failure = fail_inner
+ self.push_format_context()
+ res, expl = self.visit(v)
+ body.append(ast.Assign([ast.Name(res_var, ast.Store())], res))
+ expl_format = self.pop_format_context(ast.Str(expl))
+ call = ast_Call(app, [expl_format], [])
+ self.on_failure.append(ast.Expr(call))
+ if i < levels:
+ cond = res
+ if is_or:
+ cond = ast.UnaryOp(ast.Not(), cond)
+ inner = []
+ self.statements.append(ast.If(cond, inner, []))
+ self.statements = body = inner
+ self.statements = save
+ self.on_failure = fail_save
expl_template = self.helper("_format_boolop", expl_list, ast.Num(is_or))
- expl = self.pop_format_context(expl_template)
- return ast.Name(res_var, ast.Load()), self.explanation_param(expl)
-
- def visit_UnaryOp(self, unary):
- pattern = unary_map[unary.op.__class__]
- operand_res, operand_expl = self.visit(unary.operand)
- res = self.assign(ast.UnaryOp(unary.op, operand_res))
- return res, pattern % (operand_expl,)
-
- def visit_BinOp(self, binop):
- symbol = binop_map[binop.op.__class__]
- left_expr, left_expl = self.visit(binop.left)
- right_expr, right_expl = self.visit(binop.right)
- explanation = "(%s %s %s)" % (left_expl, symbol, right_expl)
- res = self.assign(ast.BinOp(left_expr, binop.op, right_expr))
- return res, explanation
-
- def visit_Call_35(self, call):
- """
- visit `ast.Call` nodes on Python3.5 and after
- """
- new_func, func_expl = self.visit(call.func)
- arg_expls = []
- new_args = []
- new_kwargs = []
- for arg in call.args:
- res, expl = self.visit(arg)
- arg_expls.append(expl)
- new_args.append(res)
- for keyword in call.keywords:
- res, expl = self.visit(keyword.value)
- new_kwargs.append(ast.keyword(keyword.arg, res))
- if keyword.arg:
- arg_expls.append(keyword.arg + "=" + expl)
- else: # **args have `arg` keywords with an .arg of None
- arg_expls.append("**" + expl)
-
- expl = "%s(%s)" % (func_expl, ", ".join(arg_expls))
- new_call = ast.Call(new_func, new_args, new_kwargs)
- res = self.assign(new_call)
- res_expl = self.explanation_param(self.display(res))
- outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
- return res, outer_expl
-
- def visit_Starred(self, starred):
- # From Python 3.5, a Starred node can appear in a function call
- res, expl = self.visit(starred.value)
- new_starred = ast.Starred(res, starred.ctx)
- return new_starred, "*" + expl
-
- def visit_Call_legacy(self, call):
- """
- visit `ast.Call nodes on 3.4 and below`
- """
- new_func, func_expl = self.visit(call.func)
- arg_expls = []
- new_args = []
- new_kwargs = []
- new_star = new_kwarg = None
- for arg in call.args:
- res, expl = self.visit(arg)
- new_args.append(res)
- arg_expls.append(expl)
- for keyword in call.keywords:
- res, expl = self.visit(keyword.value)
- new_kwargs.append(ast.keyword(keyword.arg, res))
- arg_expls.append(keyword.arg + "=" + expl)
- if call.starargs:
- new_star, expl = self.visit(call.starargs)
- arg_expls.append("*" + expl)
- if call.kwargs:
- new_kwarg, expl = self.visit(call.kwargs)
- arg_expls.append("**" + expl)
- expl = "%s(%s)" % (func_expl, ", ".join(arg_expls))
- new_call = ast.Call(new_func, new_args, new_kwargs, new_star, new_kwarg)
- res = self.assign(new_call)
- res_expl = self.explanation_param(self.display(res))
- outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
- return res, outer_expl
-
- # ast.Call signature changed on 3.5,
- # conditionally change which methods is named
- # visit_Call depending on Python version
- if sys.version_info >= (3, 5):
- visit_Call = visit_Call_35
- else:
- visit_Call = visit_Call_legacy
-
- def visit_Attribute(self, attr):
- if not isinstance(attr.ctx, ast.Load):
- return self.generic_visit(attr)
- value, value_expl = self.visit(attr.value)
- res = self.assign(ast.Attribute(value, attr.attr, ast.Load()))
- res_expl = self.explanation_param(self.display(res))
- pat = "%s\n{%s = %s.%s\n}"
- expl = pat % (res_expl, res_expl, value_expl, attr.attr)
- return res, expl
-
- 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)):
- left_expl = "({})".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]
- store_names = [ast.Name(v, ast.Store()) for v in res_variables]
- it = zip(range(len(comp.ops)), comp.ops, comp.comparators)
- expls = []
- syms = []
- 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)):
- next_expl = "({})".format(next_expl)
- results.append(next_res)
- sym = binop_map[op.__class__]
- syms.append(ast.Str(sym))
- expl = "%s %s %s" % (left_expl, sym, next_expl)
- expls.append(ast.Str(expl))
- res_expr = ast.Compare(left_res, [op], [next_res])
- self.statements.append(ast.Assign([store_names[i]], res_expr))
- left_res, left_expl = next_res, next_expl
- # Use pytest.assertion.util._reprcompare if that's available.
- expl_call = self.helper(
+ expl = self.pop_format_context(expl_template)
+ return ast.Name(res_var, ast.Load()), self.explanation_param(expl)
+
+ def visit_UnaryOp(self, unary):
+ pattern = unary_map[unary.op.__class__]
+ operand_res, operand_expl = self.visit(unary.operand)
+ res = self.assign(ast.UnaryOp(unary.op, operand_res))
+ return res, pattern % (operand_expl,)
+
+ def visit_BinOp(self, binop):
+ symbol = binop_map[binop.op.__class__]
+ left_expr, left_expl = self.visit(binop.left)
+ right_expr, right_expl = self.visit(binop.right)
+ explanation = "(%s %s %s)" % (left_expl, symbol, right_expl)
+ res = self.assign(ast.BinOp(left_expr, binop.op, right_expr))
+ return res, explanation
+
+ def visit_Call_35(self, call):
+ """
+ visit `ast.Call` nodes on Python3.5 and after
+ """
+ new_func, func_expl = self.visit(call.func)
+ arg_expls = []
+ new_args = []
+ new_kwargs = []
+ for arg in call.args:
+ res, expl = self.visit(arg)
+ arg_expls.append(expl)
+ new_args.append(res)
+ for keyword in call.keywords:
+ res, expl = self.visit(keyword.value)
+ new_kwargs.append(ast.keyword(keyword.arg, res))
+ if keyword.arg:
+ arg_expls.append(keyword.arg + "=" + expl)
+ else: # **args have `arg` keywords with an .arg of None
+ arg_expls.append("**" + expl)
+
+ expl = "%s(%s)" % (func_expl, ", ".join(arg_expls))
+ new_call = ast.Call(new_func, new_args, new_kwargs)
+ res = self.assign(new_call)
+ res_expl = self.explanation_param(self.display(res))
+ outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
+ return res, outer_expl
+
+ def visit_Starred(self, starred):
+ # From Python 3.5, a Starred node can appear in a function call
+ res, expl = self.visit(starred.value)
+ new_starred = ast.Starred(res, starred.ctx)
+ return new_starred, "*" + expl
+
+ def visit_Call_legacy(self, call):
+ """
+ visit `ast.Call nodes on 3.4 and below`
+ """
+ new_func, func_expl = self.visit(call.func)
+ arg_expls = []
+ new_args = []
+ new_kwargs = []
+ new_star = new_kwarg = None
+ for arg in call.args:
+ res, expl = self.visit(arg)
+ new_args.append(res)
+ arg_expls.append(expl)
+ for keyword in call.keywords:
+ res, expl = self.visit(keyword.value)
+ new_kwargs.append(ast.keyword(keyword.arg, res))
+ arg_expls.append(keyword.arg + "=" + expl)
+ if call.starargs:
+ new_star, expl = self.visit(call.starargs)
+ arg_expls.append("*" + expl)
+ if call.kwargs:
+ new_kwarg, expl = self.visit(call.kwargs)
+ arg_expls.append("**" + expl)
+ expl = "%s(%s)" % (func_expl, ", ".join(arg_expls))
+ new_call = ast.Call(new_func, new_args, new_kwargs, new_star, new_kwarg)
+ res = self.assign(new_call)
+ res_expl = self.explanation_param(self.display(res))
+ outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
+ return res, outer_expl
+
+ # ast.Call signature changed on 3.5,
+ # conditionally change which methods is named
+ # visit_Call depending on Python version
+ if sys.version_info >= (3, 5):
+ visit_Call = visit_Call_35
+ else:
+ visit_Call = visit_Call_legacy
+
+ def visit_Attribute(self, attr):
+ if not isinstance(attr.ctx, ast.Load):
+ return self.generic_visit(attr)
+ value, value_expl = self.visit(attr.value)
+ res = self.assign(ast.Attribute(value, attr.attr, ast.Load()))
+ res_expl = self.explanation_param(self.display(res))
+ pat = "%s\n{%s = %s.%s\n}"
+ expl = pat % (res_expl, res_expl, value_expl, attr.attr)
+ return res, expl
+
+ 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)):
+ left_expl = "({})".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]
+ store_names = [ast.Name(v, ast.Store()) for v in res_variables]
+ it = zip(range(len(comp.ops)), comp.ops, comp.comparators)
+ expls = []
+ syms = []
+ 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)):
+ next_expl = "({})".format(next_expl)
+ results.append(next_res)
+ sym = binop_map[op.__class__]
+ syms.append(ast.Str(sym))
+ expl = "%s %s %s" % (left_expl, sym, next_expl)
+ expls.append(ast.Str(expl))
+ res_expr = ast.Compare(left_res, [op], [next_res])
+ self.statements.append(ast.Assign([store_names[i]], res_expr))
+ left_res, left_expl = next_res, next_expl
+ # Use pytest.assertion.util._reprcompare if that's available.
+ expl_call = self.helper(
"_call_reprcompare",
- ast.Tuple(syms, ast.Load()),
- ast.Tuple(load_names, ast.Load()),
- ast.Tuple(expls, ast.Load()),
- ast.Tuple(results, ast.Load()),
- )
- if len(comp.ops) > 1:
- res = ast.BoolOp(ast.And(), load_names)
- else:
- res = load_names[0]
- return res, self.explanation_param(self.pop_format_context(expl_call))
+ ast.Tuple(syms, ast.Load()),
+ ast.Tuple(load_names, ast.Load()),
+ ast.Tuple(expls, ast.Load()),
+ ast.Tuple(results, ast.Load()),
+ )
+ if len(comp.ops) > 1:
+ res = ast.BoolOp(ast.And(), load_names)
+ else:
+ res = load_names[0]
+ return res, self.explanation_param(self.pop_format_context(expl_call))
diff --git a/contrib/python/pytest/py2/_pytest/assertion/truncate.py b/contrib/python/pytest/py2/_pytest/assertion/truncate.py
index 9412b168d2..525896ea9a 100644
--- a/contrib/python/pytest/py2/_pytest/assertion/truncate.py
+++ b/contrib/python/pytest/py2/_pytest/assertion/truncate.py
@@ -1,102 +1,102 @@
# -*- coding: utf-8 -*-
-"""
-Utilities for truncating assertion output.
-
-Current default behaviour is to truncate assertion explanations at
-~8 terminal lines, unless running in "-vv" mode or running on CI.
-"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import os
-
-import six
-
-DEFAULT_MAX_LINES = 8
-DEFAULT_MAX_CHARS = 8 * 80
-USAGE_MSG = "use '-vv' to show"
-
-
-def truncate_if_required(explanation, item, max_length=None):
- """
- Truncate this assertion explanation if the given test item is eligible.
- """
- if _should_truncate_item(item):
- return _truncate_explanation(explanation)
- return explanation
-
-
-def _should_truncate_item(item):
- """
- Whether or not this test item is eligible for truncation.
- """
- verbose = item.config.option.verbose
- return verbose < 2 and not _running_on_ci()
-
-
-def _running_on_ci():
- """Check if we're currently running on a CI system."""
- env_vars = ["CI", "BUILD_NUMBER"]
- return any(var in os.environ for var in env_vars)
-
-
-def _truncate_explanation(input_lines, max_lines=None, max_chars=None):
- """
- Truncate given list of strings that makes up the assertion explanation.
-
- Truncates to either 8 lines, or 640 characters - whichever the input reaches
- first. The remaining lines will be replaced by a usage message.
- """
-
- if max_lines is None:
- max_lines = DEFAULT_MAX_LINES
- if max_chars is None:
- max_chars = DEFAULT_MAX_CHARS
-
- # Check if truncation required
- input_char_count = len("".join(input_lines))
- if len(input_lines) <= max_lines and input_char_count <= max_chars:
- return input_lines
-
- # Truncate first to max_lines, and then truncate to max_chars if max_chars
- # is exceeded.
- truncated_explanation = input_lines[:max_lines]
- truncated_explanation = _truncate_by_char_count(truncated_explanation, max_chars)
-
- # Add ellipsis to final line
- truncated_explanation[-1] = truncated_explanation[-1] + "..."
-
- # Append useful message to explanation
- truncated_line_count = len(input_lines) - len(truncated_explanation)
- truncated_line_count += 1 # Account for the part-truncated final line
- msg = "...Full output truncated"
- if truncated_line_count == 1:
- msg += " ({} line hidden)".format(truncated_line_count)
- else:
- msg += " ({} lines hidden)".format(truncated_line_count)
- msg += ", {}".format(USAGE_MSG)
- truncated_explanation.extend([six.text_type(""), six.text_type(msg)])
- return truncated_explanation
-
-
-def _truncate_by_char_count(input_lines, max_chars):
- # Check if truncation required
- if len("".join(input_lines)) <= max_chars:
- return input_lines
-
- # Find point at which input length exceeds total allowed length
- iterated_char_count = 0
- for iterated_index, input_line in enumerate(input_lines):
- if iterated_char_count + len(input_line) > max_chars:
- break
- iterated_char_count += len(input_line)
-
- # Create truncated explanation with modified final line
- truncated_result = input_lines[:iterated_index]
- final_line = input_lines[iterated_index]
- if final_line:
- final_line_truncate_point = max_chars - iterated_char_count
- final_line = final_line[:final_line_truncate_point]
- truncated_result.append(final_line)
- return truncated_result
+"""
+Utilities for truncating assertion output.
+
+Current default behaviour is to truncate assertion explanations at
+~8 terminal lines, unless running in "-vv" mode or running on CI.
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import os
+
+import six
+
+DEFAULT_MAX_LINES = 8
+DEFAULT_MAX_CHARS = 8 * 80
+USAGE_MSG = "use '-vv' to show"
+
+
+def truncate_if_required(explanation, item, max_length=None):
+ """
+ Truncate this assertion explanation if the given test item is eligible.
+ """
+ if _should_truncate_item(item):
+ return _truncate_explanation(explanation)
+ return explanation
+
+
+def _should_truncate_item(item):
+ """
+ Whether or not this test item is eligible for truncation.
+ """
+ verbose = item.config.option.verbose
+ return verbose < 2 and not _running_on_ci()
+
+
+def _running_on_ci():
+ """Check if we're currently running on a CI system."""
+ env_vars = ["CI", "BUILD_NUMBER"]
+ return any(var in os.environ for var in env_vars)
+
+
+def _truncate_explanation(input_lines, max_lines=None, max_chars=None):
+ """
+ Truncate given list of strings that makes up the assertion explanation.
+
+ Truncates to either 8 lines, or 640 characters - whichever the input reaches
+ first. The remaining lines will be replaced by a usage message.
+ """
+
+ if max_lines is None:
+ max_lines = DEFAULT_MAX_LINES
+ if max_chars is None:
+ max_chars = DEFAULT_MAX_CHARS
+
+ # Check if truncation required
+ input_char_count = len("".join(input_lines))
+ if len(input_lines) <= max_lines and input_char_count <= max_chars:
+ return input_lines
+
+ # Truncate first to max_lines, and then truncate to max_chars if max_chars
+ # is exceeded.
+ truncated_explanation = input_lines[:max_lines]
+ truncated_explanation = _truncate_by_char_count(truncated_explanation, max_chars)
+
+ # Add ellipsis to final line
+ truncated_explanation[-1] = truncated_explanation[-1] + "..."
+
+ # Append useful message to explanation
+ truncated_line_count = len(input_lines) - len(truncated_explanation)
+ truncated_line_count += 1 # Account for the part-truncated final line
+ msg = "...Full output truncated"
+ if truncated_line_count == 1:
+ msg += " ({} line hidden)".format(truncated_line_count)
+ else:
+ msg += " ({} lines hidden)".format(truncated_line_count)
+ msg += ", {}".format(USAGE_MSG)
+ truncated_explanation.extend([six.text_type(""), six.text_type(msg)])
+ return truncated_explanation
+
+
+def _truncate_by_char_count(input_lines, max_chars):
+ # Check if truncation required
+ if len("".join(input_lines)) <= max_chars:
+ return input_lines
+
+ # Find point at which input length exceeds total allowed length
+ iterated_char_count = 0
+ for iterated_index, input_line in enumerate(input_lines):
+ if iterated_char_count + len(input_line) > max_chars:
+ break
+ iterated_char_count += len(input_line)
+
+ # Create truncated explanation with modified final line
+ truncated_result = input_lines[:iterated_index]
+ final_line = input_lines[iterated_index]
+ if final_line:
+ final_line_truncate_point = max_chars - iterated_char_count
+ final_line = final_line[:final_line_truncate_point]
+ truncated_result.append(final_line)
+ return truncated_result
diff --git a/contrib/python/pytest/py2/_pytest/assertion/util.py b/contrib/python/pytest/py2/_pytest/assertion/util.py
index 6d63b0f9d4..c382f1c609 100644
--- a/contrib/python/pytest/py2/_pytest/assertion/util.py
+++ b/contrib/python/pytest/py2/_pytest/assertion/util.py
@@ -1,125 +1,125 @@
# -*- coding: utf-8 -*-
-"""Utilities for assertion debugging"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import pprint
-
-import six
-
-import _pytest._code
-from ..compat import Sequence
+"""Utilities for assertion debugging"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import pprint
+
+import six
+
+import _pytest._code
+from ..compat import Sequence
from _pytest import outcomes
from _pytest._io.saferepr import saferepr
from _pytest.compat import ATTRS_EQ_FIELD
-
-# The _reprcompare attribute on the util module is used by the new assertion
-# interpretation code and assertion rewriter to detect this plugin was
-# loaded and in turn call the hooks defined here as part of the
-# DebugInterpreter.
-_reprcompare = None
-
-
-# the re-encoding is needed for python2 repr
-# with non-ascii characters (see issue 877 and 1379)
-def ecu(s):
- if isinstance(s, bytes):
- return s.decode("UTF-8", "replace")
- else:
- return s
-
-
-def format_explanation(explanation):
- """This formats an explanation
-
- Normally all embedded newlines are escaped, however there are
- three exceptions: \n{, \n} and \n~. The first two are intended
- cover nested explanations, see function and attribute explanations
- for examples (.visit_Call(), visit_Attribute()). The last one is
- for when one explanation needs to span multiple lines, e.g. when
- displaying diffs.
- """
- explanation = ecu(explanation)
- lines = _split_explanation(explanation)
- result = _format_lines(lines)
- return u"\n".join(result)
-
-
-def _split_explanation(explanation):
- """Return a list of individual lines in the explanation
-
- This will return a list of lines split on '\n{', '\n}' and '\n~'.
- Any other newlines will be escaped and appear in the line as the
- literal '\n' characters.
- """
- raw_lines = (explanation or u"").split("\n")
- lines = [raw_lines[0]]
- for values in raw_lines[1:]:
- if values and values[0] in ["{", "}", "~", ">"]:
- lines.append(values)
- else:
- lines[-1] += "\\n" + values
- return lines
-
-
-def _format_lines(lines):
- """Format the individual lines
-
- This will replace the '{', '}' and '~' characters of our mini
- formatting language with the proper 'where ...', 'and ...' and ' +
- ...' text, taking care of indentation along the way.
-
- Return a list of formatted lines.
- """
- result = lines[:1]
- stack = [0]
- stackcnt = [0]
- for line in lines[1:]:
- if line.startswith("{"):
- if stackcnt[-1]:
- s = u"and "
- else:
- s = u"where "
- stack.append(len(result))
- stackcnt[-1] += 1
- stackcnt.append(0)
- result.append(u" +" + u" " * (len(stack) - 1) + s + line[1:])
- elif line.startswith("}"):
- stack.pop()
- stackcnt.pop()
- result[stack[-1]] += line[1:]
- else:
- assert line[0] in ["~", ">"]
- stack[-1] += 1
- indent = len(stack) if line.startswith("~") else len(stack) - 1
- result.append(u" " * indent + line[1:])
- assert len(stack) == 1
- return result
-
-
-# Provide basestring in python3
-try:
- basestring = basestring
-except NameError:
- basestring = str
-
-
+
+# The _reprcompare attribute on the util module is used by the new assertion
+# interpretation code and assertion rewriter to detect this plugin was
+# loaded and in turn call the hooks defined here as part of the
+# DebugInterpreter.
+_reprcompare = None
+
+
+# the re-encoding is needed for python2 repr
+# with non-ascii characters (see issue 877 and 1379)
+def ecu(s):
+ if isinstance(s, bytes):
+ return s.decode("UTF-8", "replace")
+ else:
+ return s
+
+
+def format_explanation(explanation):
+ """This formats an explanation
+
+ Normally all embedded newlines are escaped, however there are
+ three exceptions: \n{, \n} and \n~. The first two are intended
+ cover nested explanations, see function and attribute explanations
+ for examples (.visit_Call(), visit_Attribute()). The last one is
+ for when one explanation needs to span multiple lines, e.g. when
+ displaying diffs.
+ """
+ explanation = ecu(explanation)
+ lines = _split_explanation(explanation)
+ result = _format_lines(lines)
+ return u"\n".join(result)
+
+
+def _split_explanation(explanation):
+ """Return a list of individual lines in the explanation
+
+ This will return a list of lines split on '\n{', '\n}' and '\n~'.
+ Any other newlines will be escaped and appear in the line as the
+ literal '\n' characters.
+ """
+ raw_lines = (explanation or u"").split("\n")
+ lines = [raw_lines[0]]
+ for values in raw_lines[1:]:
+ if values and values[0] in ["{", "}", "~", ">"]:
+ lines.append(values)
+ else:
+ lines[-1] += "\\n" + values
+ return lines
+
+
+def _format_lines(lines):
+ """Format the individual lines
+
+ This will replace the '{', '}' and '~' characters of our mini
+ formatting language with the proper 'where ...', 'and ...' and ' +
+ ...' text, taking care of indentation along the way.
+
+ Return a list of formatted lines.
+ """
+ result = lines[:1]
+ stack = [0]
+ stackcnt = [0]
+ for line in lines[1:]:
+ if line.startswith("{"):
+ if stackcnt[-1]:
+ s = u"and "
+ else:
+ s = u"where "
+ stack.append(len(result))
+ stackcnt[-1] += 1
+ stackcnt.append(0)
+ result.append(u" +" + u" " * (len(stack) - 1) + s + line[1:])
+ elif line.startswith("}"):
+ stack.pop()
+ stackcnt.pop()
+ result[stack[-1]] += line[1:]
+ else:
+ assert line[0] in ["~", ">"]
+ stack[-1] += 1
+ indent = len(stack) if line.startswith("~") else len(stack) - 1
+ result.append(u" " * indent + line[1:])
+ assert len(stack) == 1
+ return result
+
+
+# Provide basestring in python3
+try:
+ basestring = basestring
+except NameError:
+ basestring = str
+
+
def issequence(x):
return isinstance(x, Sequence) and not isinstance(x, basestring)
-
-
+
+
def istext(x):
return isinstance(x, basestring)
-
-
+
+
def isdict(x):
return isinstance(x, dict)
-
-
+
+
def isset(x):
return isinstance(x, (set, frozenset))
-
+
def isdatacls(obj):
return getattr(obj, "__dataclass_fields__", None) is not None
@@ -145,111 +145,111 @@ def assertrepr_compare(config, op, left, right):
summary = u"%s %s %s" % (ecu(left_repr), op, ecu(right_repr))
- verbose = config.getoption("verbose")
- explanation = None
- try:
- if op == "==":
- if istext(left) and istext(right):
- explanation = _diff_text(left, right, verbose)
- else:
- if issequence(left) and issequence(right):
- explanation = _compare_eq_sequence(left, right, verbose)
- elif isset(left) and isset(right):
- explanation = _compare_eq_set(left, right, verbose)
- elif isdict(left) and isdict(right):
- explanation = _compare_eq_dict(left, right, verbose)
+ verbose = config.getoption("verbose")
+ explanation = None
+ try:
+ if op == "==":
+ if istext(left) and istext(right):
+ explanation = _diff_text(left, right, verbose)
+ else:
+ if issequence(left) and issequence(right):
+ explanation = _compare_eq_sequence(left, right, verbose)
+ elif isset(left) and isset(right):
+ explanation = _compare_eq_set(left, right, verbose)
+ elif isdict(left) and isdict(right):
+ explanation = _compare_eq_dict(left, right, verbose)
elif type(left) == type(right) and (isdatacls(left) or isattrs(left)):
type_fn = (isdatacls, isattrs)
explanation = _compare_eq_cls(left, right, verbose, type_fn)
elif verbose > 0:
explanation = _compare_eq_verbose(left, right)
- if isiterable(left) and isiterable(right):
- expl = _compare_eq_iterable(left, right, verbose)
- if explanation is not None:
- explanation.extend(expl)
- else:
- explanation = expl
- elif op == "not in":
- if istext(left) and istext(right):
- explanation = _notin_text(left, right, verbose)
+ if isiterable(left) and isiterable(right):
+ expl = _compare_eq_iterable(left, right, verbose)
+ if explanation is not None:
+ explanation.extend(expl)
+ else:
+ explanation = expl
+ elif op == "not in":
+ if istext(left) and istext(right):
+ explanation = _notin_text(left, right, verbose)
except outcomes.Exit:
raise
- except Exception:
- explanation = [
- u"(pytest_assertion plugin: representation of details failed. "
- u"Probably an object has a faulty __repr__.)",
+ except Exception:
+ explanation = [
+ u"(pytest_assertion plugin: representation of details failed. "
+ u"Probably an object has a faulty __repr__.)",
six.text_type(_pytest._code.ExceptionInfo.from_current()),
- ]
-
- if not explanation:
- return None
-
- return [summary] + explanation
-
-
+ ]
+
+ if not explanation:
+ return None
+
+ return [summary] + explanation
+
+
def _diff_text(left, right, verbose=0):
"""Return the explanation for the diff between text or bytes.
-
- Unless --verbose is used this will skip leading and trailing
- characters which are identical to keep the diff minimal.
-
- If the input are bytes they will be safely converted to text.
- """
- from difflib import ndiff
-
- explanation = []
-
- def escape_for_readable_diff(binary_text):
- """
- Ensures that the internal string is always valid unicode, converting any bytes safely to valid unicode.
- This is done using repr() which then needs post-processing to fix the encompassing quotes and un-escape
- newlines and carriage returns (#429).
- """
- r = six.text_type(repr(binary_text)[1:-1])
- r = r.replace(r"\n", "\n")
- r = r.replace(r"\r", "\r")
- return r
-
- if isinstance(left, bytes):
- left = escape_for_readable_diff(left)
- if isinstance(right, bytes):
- right = escape_for_readable_diff(right)
+
+ Unless --verbose is used this will skip leading and trailing
+ characters which are identical to keep the diff minimal.
+
+ If the input are bytes they will be safely converted to text.
+ """
+ from difflib import ndiff
+
+ explanation = []
+
+ def escape_for_readable_diff(binary_text):
+ """
+ Ensures that the internal string is always valid unicode, converting any bytes safely to valid unicode.
+ This is done using repr() which then needs post-processing to fix the encompassing quotes and un-escape
+ newlines and carriage returns (#429).
+ """
+ r = six.text_type(repr(binary_text)[1:-1])
+ r = r.replace(r"\n", "\n")
+ r = r.replace(r"\r", "\r")
+ return r
+
+ if isinstance(left, bytes):
+ left = escape_for_readable_diff(left)
+ if isinstance(right, bytes):
+ right = escape_for_readable_diff(right)
if verbose < 1:
- i = 0 # just in case left or right has zero length
- for i in range(min(len(left), len(right))):
- if left[i] != right[i]:
- break
- if i > 42:
- i -= 10 # Provide some context
- explanation = [
- u"Skipping %s identical leading characters in diff, use -v to show" % i
- ]
- left = left[i:]
- right = right[i:]
- if len(left) == len(right):
- for i in range(len(left)):
- if left[-i] != right[-i]:
- break
- if i > 42:
- i -= 10 # Provide some context
- explanation += [
- u"Skipping {} identical trailing "
- u"characters in diff, use -v to show".format(i)
- ]
- left = left[:-i]
- right = right[:-i]
- keepends = True
- if left.isspace() or right.isspace():
- left = repr(str(left))
- right = repr(str(right))
- explanation += [u"Strings contain only whitespace, escaping them using repr()"]
- explanation += [
- line.strip("\n")
- for line in ndiff(left.splitlines(keepends), right.splitlines(keepends))
- ]
- return explanation
-
-
+ i = 0 # just in case left or right has zero length
+ for i in range(min(len(left), len(right))):
+ if left[i] != right[i]:
+ break
+ if i > 42:
+ i -= 10 # Provide some context
+ explanation = [
+ u"Skipping %s identical leading characters in diff, use -v to show" % i
+ ]
+ left = left[i:]
+ right = right[i:]
+ if len(left) == len(right):
+ for i in range(len(left)):
+ if left[-i] != right[-i]:
+ break
+ if i > 42:
+ i -= 10 # Provide some context
+ explanation += [
+ u"Skipping {} identical trailing "
+ u"characters in diff, use -v to show".format(i)
+ ]
+ left = left[:-i]
+ right = right[:-i]
+ keepends = True
+ if left.isspace() or right.isspace():
+ left = repr(str(left))
+ right = repr(str(right))
+ explanation += [u"Strings contain only whitespace, escaping them using repr()"]
+ explanation += [
+ line.strip("\n")
+ for line in ndiff(left.splitlines(keepends), right.splitlines(keepends))
+ ]
+ return explanation
+
+
def _compare_eq_verbose(left, right):
keepends = True
left_lines = repr(left).splitlines(keepends)
@@ -263,36 +263,36 @@ def _compare_eq_verbose(left, right):
def _compare_eq_iterable(left, right, verbose=0):
- if not verbose:
- return [u"Use -v to get the full diff"]
- # dynamic import to speedup pytest
- import difflib
-
- try:
- left_formatting = pprint.pformat(left).splitlines()
- right_formatting = pprint.pformat(right).splitlines()
- explanation = [u"Full diff:"]
- except Exception:
- # hack: PrettyPrinter.pformat() in python 2 fails when formatting items that can't be sorted(), ie, calling
- # sorted() on a list would raise. See issue #718.
- # As a workaround, the full diff is generated by using the repr() string of each item of each container.
- left_formatting = sorted(repr(x) for x in left)
- right_formatting = sorted(repr(x) for x in right)
- explanation = [u"Full diff (fallback to calling repr on each item):"]
- explanation.extend(
- line.strip() for line in difflib.ndiff(left_formatting, right_formatting)
- )
- return explanation
-
-
+ if not verbose:
+ return [u"Use -v to get the full diff"]
+ # dynamic import to speedup pytest
+ import difflib
+
+ try:
+ left_formatting = pprint.pformat(left).splitlines()
+ right_formatting = pprint.pformat(right).splitlines()
+ explanation = [u"Full diff:"]
+ except Exception:
+ # hack: PrettyPrinter.pformat() in python 2 fails when formatting items that can't be sorted(), ie, calling
+ # sorted() on a list would raise. See issue #718.
+ # As a workaround, the full diff is generated by using the repr() string of each item of each container.
+ left_formatting = sorted(repr(x) for x in left)
+ right_formatting = sorted(repr(x) for x in right)
+ explanation = [u"Full diff (fallback to calling repr on each item):"]
+ explanation.extend(
+ line.strip() for line in difflib.ndiff(left_formatting, right_formatting)
+ )
+ return explanation
+
+
def _compare_eq_sequence(left, right, verbose=0):
- explanation = []
+ explanation = []
len_left = len(left)
len_right = len(right)
for i in range(min(len_left, len_right)):
- if left[i] != right[i]:
- explanation += [u"At index %s diff: %r != %r" % (i, left[i], right[i])]
- break
+ if left[i] != right[i]:
+ explanation += [u"At index %s diff: %r != %r" % (i, left[i], right[i])]
+ break
len_diff = len_left - len_right
if len_diff:
@@ -311,39 +311,39 @@ def _compare_eq_sequence(left, right, verbose=0):
u"%s contains %d more items, first extra item: %s"
% (dir_with_more, len_diff, extra)
]
- return explanation
-
-
+ return explanation
+
+
def _compare_eq_set(left, right, verbose=0):
- explanation = []
- diff_left = left - right
- diff_right = right - left
- if diff_left:
- explanation.append(u"Extra items in the left set:")
- for item in diff_left:
+ explanation = []
+ diff_left = left - right
+ diff_right = right - left
+ if diff_left:
+ explanation.append(u"Extra items in the left set:")
+ for item in diff_left:
explanation.append(saferepr(item))
- if diff_right:
- explanation.append(u"Extra items in the right set:")
- for item in diff_right:
+ if diff_right:
+ explanation.append(u"Extra items in the right set:")
+ for item in diff_right:
explanation.append(saferepr(item))
- return explanation
-
-
+ return explanation
+
+
def _compare_eq_dict(left, right, verbose=0):
- explanation = []
+ explanation = []
set_left = set(left)
set_right = set(right)
common = set_left.intersection(set_right)
- same = {k: left[k] for k in common if left[k] == right[k]}
- if same and verbose < 2:
- explanation += [u"Omitting %s identical items, use -vv to show" % len(same)]
- elif same:
- explanation += [u"Common items:"]
- explanation += pprint.pformat(same).splitlines()
- diff = {k for k in common if left[k] != right[k]}
- if diff:
- explanation += [u"Differing items:"]
- for k in diff:
+ same = {k: left[k] for k in common if left[k] == right[k]}
+ if same and verbose < 2:
+ explanation += [u"Omitting %s identical items, use -vv to show" % len(same)]
+ elif same:
+ explanation += [u"Common items:"]
+ explanation += pprint.pformat(same).splitlines()
+ diff = {k for k in common if left[k] != right[k]}
+ if diff:
+ explanation += [u"Differing items:"]
+ for k in diff:
explanation += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})]
extra_left = set_left - set_right
len_extra_left = len(extra_left)
@@ -352,9 +352,9 @@ def _compare_eq_dict(left, right, verbose=0):
u"Left contains %d more item%s:"
% (len_extra_left, "" if len_extra_left == 1 else "s")
)
- explanation.extend(
- pprint.pformat({k: left[k] for k in extra_left}).splitlines()
- )
+ explanation.extend(
+ pprint.pformat({k: left[k] for k in extra_left}).splitlines()
+ )
extra_right = set_right - set_left
len_extra_right = len(extra_right)
if len_extra_right:
@@ -362,12 +362,12 @@ def _compare_eq_dict(left, right, verbose=0):
u"Right contains %d more item%s:"
% (len_extra_right, "" if len_extra_right == 1 else "s")
)
- explanation.extend(
- pprint.pformat({k: right[k] for k in extra_right}).splitlines()
- )
- return explanation
-
-
+ explanation.extend(
+ pprint.pformat({k: right[k] for k in extra_right}).splitlines()
+ )
+ return explanation
+
+
def _compare_eq_cls(left, right, verbose, type_fns):
isdatacls, isattrs = type_fns
if isdatacls(left):
@@ -403,19 +403,19 @@ def _compare_eq_cls(left, right, verbose, type_fns):
def _notin_text(term, text, verbose=0):
- index = text.find(term)
- head = text[:index]
- tail = text[index + len(term) :]
- correct_text = head + tail
- diff = _diff_text(correct_text, text, verbose)
+ index = text.find(term)
+ head = text[:index]
+ tail = text[index + len(term) :]
+ correct_text = head + tail
+ diff = _diff_text(correct_text, text, verbose)
newdiff = [u"%s is contained here:" % saferepr(term, maxsize=42)]
- for line in diff:
- if line.startswith(u"Skipping"):
- continue
- if line.startswith(u"- "):
- continue
- if line.startswith(u"+ "):
- newdiff.append(u" " + line[2:])
- else:
- newdiff.append(line)
- return newdiff
+ for line in diff:
+ if line.startswith(u"Skipping"):
+ continue
+ if line.startswith(u"- "):
+ continue
+ if line.startswith(u"+ "):
+ newdiff.append(u" " + line[2:])
+ else:
+ newdiff.append(line)
+ return newdiff
diff --git a/contrib/python/pytest/py2/_pytest/cacheprovider.py b/contrib/python/pytest/py2/_pytest/cacheprovider.py
index 611653f8ba..f5c5545484 100644
--- a/contrib/python/pytest/py2/_pytest/cacheprovider.py
+++ b/contrib/python/pytest/py2/_pytest/cacheprovider.py
@@ -1,166 +1,166 @@
# -*- coding: utf-8 -*-
-"""
-merged implementation of the cache provider
-
-the name cache was not chosen to ensure pluggy automatically
-ignores the external pytest-cache
-"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import json
-import os
-from collections import OrderedDict
-
-import attr
-import py
-import six
-
-import pytest
-from .compat import _PY2 as PY2
-from .pathlib import Path
-from .pathlib import resolve_from_str
+"""
+merged implementation of the cache provider
+
+the name cache was not chosen to ensure pluggy automatically
+ignores the external pytest-cache
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import json
+import os
+from collections import OrderedDict
+
+import attr
+import py
+import six
+
+import pytest
+from .compat import _PY2 as PY2
+from .pathlib import Path
+from .pathlib import resolve_from_str
from .pathlib import rm_rf
-
-README_CONTENT = u"""\
-# pytest cache directory #
-
-This directory contains data from the pytest's cache plugin,
-which provides the `--lf` and `--ff` options, as well as the `cache` fixture.
-
-**Do not** commit this to version control.
-
-See [the docs](https://docs.pytest.org/en/latest/cache.html) for more information.
-"""
-
+
+README_CONTENT = u"""\
+# pytest cache directory #
+
+This directory contains data from the pytest's cache plugin,
+which provides the `--lf` and `--ff` options, as well as the `cache` fixture.
+
+**Do not** commit this to version control.
+
+See [the docs](https://docs.pytest.org/en/latest/cache.html) for more information.
+"""
+
CACHEDIR_TAG_CONTENT = b"""\
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by pytest.
# For information about cache directory tags, see:
# http://www.bford.info/cachedir/spec.html
"""
-
-
-@attr.s
-class Cache(object):
- _cachedir = attr.ib(repr=False)
- _config = attr.ib(repr=False)
-
- @classmethod
- def for_config(cls, config):
- cachedir = cls.cache_dir_from_config(config)
- if config.getoption("cacheclear") and cachedir.exists():
+
+
+@attr.s
+class Cache(object):
+ _cachedir = attr.ib(repr=False)
+ _config = attr.ib(repr=False)
+
+ @classmethod
+ def for_config(cls, config):
+ cachedir = cls.cache_dir_from_config(config)
+ if config.getoption("cacheclear") and cachedir.exists():
rm_rf(cachedir)
- cachedir.mkdir()
- return cls(cachedir, config)
-
- @staticmethod
- def cache_dir_from_config(config):
- return resolve_from_str(config.getini("cache_dir"), config.rootdir)
-
- def warn(self, fmt, **args):
+ cachedir.mkdir()
+ return cls(cachedir, config)
+
+ @staticmethod
+ def cache_dir_from_config(config):
+ return resolve_from_str(config.getini("cache_dir"), config.rootdir)
+
+ def warn(self, fmt, **args):
from _pytest.warnings import _issue_warning_captured
from _pytest.warning_types import PytestCacheWarning
-
+
_issue_warning_captured(
PytestCacheWarning(fmt.format(**args) if args else fmt),
self._config.hook,
- stacklevel=3,
- )
-
- def makedir(self, name):
- """ return a directory path object with the given name. If the
- directory does not yet exist, it will be created. You can use it
- to manage files likes e. g. store/retrieve database
- dumps across test sessions.
-
- :param name: must be a string not containing a ``/`` separator.
- Make sure the name contains your plugin or application
- identifiers to prevent clashes with other cache users.
- """
- name = Path(name)
- if len(name.parts) > 1:
- raise ValueError("name is not allowed to contain path separators")
- res = self._cachedir.joinpath("d", name)
- res.mkdir(exist_ok=True, parents=True)
- return py.path.local(res)
-
- def _getvaluepath(self, key):
- return self._cachedir.joinpath("v", Path(key))
-
- def get(self, key, default):
- """ return cached value for the given key. If no value
- was yet cached or the value cannot be read, the specified
- default is returned.
-
- :param key: must be a ``/`` separated value. Usually the first
- name is the name of your plugin or your application.
- :param default: must be provided in case of a cache-miss or
- invalid cache values.
-
- """
- path = self._getvaluepath(key)
- try:
- with path.open("r") as f:
- return json.load(f)
- except (ValueError, IOError, OSError):
- return default
-
- def set(self, key, value):
- """ save value for the given key.
-
- :param key: must be a ``/`` separated value. Usually the first
- name is the name of your plugin or your application.
- :param value: must be of any combination of basic
- python types, including nested types
- like e. g. lists of dictionaries.
- """
- path = self._getvaluepath(key)
- try:
- if path.parent.is_dir():
- cache_dir_exists_already = True
- else:
- cache_dir_exists_already = self._cachedir.exists()
+ stacklevel=3,
+ )
+
+ def makedir(self, name):
+ """ return a directory path object with the given name. If the
+ directory does not yet exist, it will be created. You can use it
+ to manage files likes e. g. store/retrieve database
+ dumps across test sessions.
+
+ :param name: must be a string not containing a ``/`` separator.
+ Make sure the name contains your plugin or application
+ identifiers to prevent clashes with other cache users.
+ """
+ name = Path(name)
+ if len(name.parts) > 1:
+ raise ValueError("name is not allowed to contain path separators")
+ res = self._cachedir.joinpath("d", name)
+ res.mkdir(exist_ok=True, parents=True)
+ return py.path.local(res)
+
+ def _getvaluepath(self, key):
+ return self._cachedir.joinpath("v", Path(key))
+
+ def get(self, key, default):
+ """ return cached value for the given key. If no value
+ was yet cached or the value cannot be read, the specified
+ default is returned.
+
+ :param key: must be a ``/`` separated value. Usually the first
+ name is the name of your plugin or your application.
+ :param default: must be provided in case of a cache-miss or
+ invalid cache values.
+
+ """
+ path = self._getvaluepath(key)
+ try:
+ with path.open("r") as f:
+ return json.load(f)
+ except (ValueError, IOError, OSError):
+ return default
+
+ def set(self, key, value):
+ """ save value for the given key.
+
+ :param key: must be a ``/`` separated value. Usually the first
+ name is the name of your plugin or your application.
+ :param value: must be of any combination of basic
+ python types, including nested types
+ like e. g. lists of dictionaries.
+ """
+ path = self._getvaluepath(key)
+ try:
+ if path.parent.is_dir():
+ cache_dir_exists_already = True
+ else:
+ cache_dir_exists_already = self._cachedir.exists()
path.parent.mkdir(exist_ok=True, parents=True)
- except (IOError, OSError):
- self.warn("could not create cache path {path}", path=path)
- return
+ except (IOError, OSError):
+ self.warn("could not create cache path {path}", path=path)
+ return
if not cache_dir_exists_already:
self._ensure_supporting_files()
- try:
- f = path.open("wb" if PY2 else "w")
- except (IOError, OSError):
- self.warn("cache could not write path {path}", path=path)
- else:
- with f:
- json.dump(value, f, indent=2, sort_keys=True)
-
- def _ensure_supporting_files(self):
- """Create supporting files in the cache dir that are not really part of the cache."""
+ try:
+ f = path.open("wb" if PY2 else "w")
+ except (IOError, OSError):
+ self.warn("cache could not write path {path}", path=path)
+ else:
+ with f:
+ json.dump(value, f, indent=2, sort_keys=True)
+
+ def _ensure_supporting_files(self):
+ """Create supporting files in the cache dir that are not really part of the cache."""
readme_path = self._cachedir / "README.md"
readme_path.write_text(README_CONTENT)
-
+
gitignore_path = self._cachedir.joinpath(".gitignore")
msg = u"# Created by pytest automatically.\n*"
gitignore_path.write_text(msg, encoding="UTF-8")
-
+
cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG")
cachedir_tag_path.write_bytes(CACHEDIR_TAG_CONTENT)
-
-
-class LFPlugin(object):
- """ Plugin which implements the --lf (run last-failing) option """
-
- def __init__(self, config):
- self.config = config
- active_keys = "lf", "failedfirst"
- self.active = any(config.getoption(key) for key in active_keys)
- self.lastfailed = config.cache.get("cache/lastfailed", {})
- self._previously_failed_count = None
+
+
+class LFPlugin(object):
+ """ Plugin which implements the --lf (run last-failing) option """
+
+ def __init__(self, config):
+ self.config = config
+ active_keys = "lf", "failedfirst"
+ self.active = any(config.getoption(key) for key in active_keys)
+ self.lastfailed = config.cache.get("cache/lastfailed", {})
+ self._previously_failed_count = None
self._report_status = None
self._skipped_files = 0 # count skipped files during collection due to --lf
-
+
def last_failed_paths(self):
"""Returns a set with all Paths()s of the previously failed nodeids (cached).
"""
@@ -186,26 +186,26 @@ class LFPlugin(object):
self._skipped_files += 1
return skip_it
- def pytest_report_collectionfinish(self):
- if self.active and self.config.getoption("verbose") >= 0:
+ def pytest_report_collectionfinish(self):
+ if self.active and self.config.getoption("verbose") >= 0:
return "run-last-failure: %s" % self._report_status
-
- def pytest_runtest_logreport(self, report):
- if (report.when == "call" and report.passed) or report.skipped:
- self.lastfailed.pop(report.nodeid, None)
- elif report.failed:
- self.lastfailed[report.nodeid] = True
-
- def pytest_collectreport(self, report):
- passed = report.outcome in ("passed", "skipped")
- if passed:
- if report.nodeid in self.lastfailed:
- self.lastfailed.pop(report.nodeid)
- self.lastfailed.update((item.nodeid, True) for item in report.result)
- else:
- self.lastfailed[report.nodeid] = True
-
- def pytest_collection_modifyitems(self, session, config, items):
+
+ def pytest_runtest_logreport(self, report):
+ if (report.when == "call" and report.passed) or report.skipped:
+ self.lastfailed.pop(report.nodeid, None)
+ elif report.failed:
+ self.lastfailed[report.nodeid] = True
+
+ def pytest_collectreport(self, report):
+ passed = report.outcome in ("passed", "skipped")
+ if passed:
+ if report.nodeid in self.lastfailed:
+ self.lastfailed.pop(report.nodeid)
+ self.lastfailed.update((item.nodeid, True) for item in report.result)
+ else:
+ self.lastfailed[report.nodeid] = True
+
+ def pytest_collection_modifyitems(self, session, config, items):
if not self.active:
return
@@ -226,11 +226,11 @@ class LFPlugin(object):
len(self.lastfailed),
)
else:
- if self.config.getoption("lf"):
- items[:] = previously_failed
- config.hook.pytest_deselected(items=previously_passed)
+ if self.config.getoption("lf"):
+ items[:] = previously_failed
+ config.hook.pytest_deselected(items=previously_passed)
else: # --failedfirst
- items[:] = previously_failed + previously_passed
+ items[:] = previously_failed + previously_passed
noun = "failure" if self._previously_failed_count == 1 else "failures"
suffix = " first" if self.config.getoption("failedfirst") else ""
@@ -247,192 +247,192 @@ class LFPlugin(object):
self._report_status = "no previously failed tests, "
if self.config.getoption("last_failed_no_failures") == "none":
self._report_status += "deselecting all items."
- config.hook.pytest_deselected(items=items)
- items[:] = []
+ config.hook.pytest_deselected(items=items)
+ items[:] = []
else:
self._report_status += "not deselecting items."
-
- def pytest_sessionfinish(self, session):
- config = self.config
- if config.getoption("cacheshow") or hasattr(config, "slaveinput"):
- return
-
- saved_lastfailed = config.cache.get("cache/lastfailed", {})
- if saved_lastfailed != self.lastfailed:
- config.cache.set("cache/lastfailed", self.lastfailed)
-
-
-class NFPlugin(object):
- """ Plugin which implements the --nf (run new-first) option """
-
- def __init__(self, config):
- self.config = config
- self.active = config.option.newfirst
- self.cached_nodeids = config.cache.get("cache/nodeids", [])
-
- def pytest_collection_modifyitems(self, session, config, items):
- if self.active:
- new_items = OrderedDict()
- other_items = OrderedDict()
- for item in items:
- if item.nodeid not in self.cached_nodeids:
- new_items[item.nodeid] = item
- else:
- other_items[item.nodeid] = item
-
- items[:] = self._get_increasing_order(
- six.itervalues(new_items)
- ) + self._get_increasing_order(six.itervalues(other_items))
- self.cached_nodeids = [x.nodeid for x in items if isinstance(x, pytest.Item)]
-
- def _get_increasing_order(self, items):
- return sorted(items, key=lambda item: item.fspath.mtime(), reverse=True)
-
- def pytest_sessionfinish(self, session):
- config = self.config
- if config.getoption("cacheshow") or hasattr(config, "slaveinput"):
- return
-
- config.cache.set("cache/nodeids", self.cached_nodeids)
-
-
-def pytest_addoption(parser):
- group = parser.getgroup("general")
- group.addoption(
- "--lf",
- "--last-failed",
- action="store_true",
- dest="lf",
- help="rerun only the tests that failed "
- "at the last run (or all if none failed)",
- )
- group.addoption(
- "--ff",
- "--failed-first",
- action="store_true",
- dest="failedfirst",
- help="run all tests but run the last failures first. "
- "This may re-order tests and thus lead to "
- "repeated fixture setup/teardown",
- )
- group.addoption(
- "--nf",
- "--new-first",
- action="store_true",
- dest="newfirst",
- help="run tests from new files first, then the rest of the tests "
- "sorted by file mtime",
- )
- group.addoption(
- "--cache-show",
+
+ def pytest_sessionfinish(self, session):
+ config = self.config
+ if config.getoption("cacheshow") or hasattr(config, "slaveinput"):
+ return
+
+ saved_lastfailed = config.cache.get("cache/lastfailed", {})
+ if saved_lastfailed != self.lastfailed:
+ config.cache.set("cache/lastfailed", self.lastfailed)
+
+
+class NFPlugin(object):
+ """ Plugin which implements the --nf (run new-first) option """
+
+ def __init__(self, config):
+ self.config = config
+ self.active = config.option.newfirst
+ self.cached_nodeids = config.cache.get("cache/nodeids", [])
+
+ def pytest_collection_modifyitems(self, session, config, items):
+ if self.active:
+ new_items = OrderedDict()
+ other_items = OrderedDict()
+ for item in items:
+ if item.nodeid not in self.cached_nodeids:
+ new_items[item.nodeid] = item
+ else:
+ other_items[item.nodeid] = item
+
+ items[:] = self._get_increasing_order(
+ six.itervalues(new_items)
+ ) + self._get_increasing_order(six.itervalues(other_items))
+ self.cached_nodeids = [x.nodeid for x in items if isinstance(x, pytest.Item)]
+
+ def _get_increasing_order(self, items):
+ return sorted(items, key=lambda item: item.fspath.mtime(), reverse=True)
+
+ def pytest_sessionfinish(self, session):
+ config = self.config
+ if config.getoption("cacheshow") or hasattr(config, "slaveinput"):
+ return
+
+ config.cache.set("cache/nodeids", self.cached_nodeids)
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("general")
+ group.addoption(
+ "--lf",
+ "--last-failed",
+ action="store_true",
+ dest="lf",
+ help="rerun only the tests that failed "
+ "at the last run (or all if none failed)",
+ )
+ group.addoption(
+ "--ff",
+ "--failed-first",
+ action="store_true",
+ dest="failedfirst",
+ help="run all tests but run the last failures first. "
+ "This may re-order tests and thus lead to "
+ "repeated fixture setup/teardown",
+ )
+ group.addoption(
+ "--nf",
+ "--new-first",
+ action="store_true",
+ dest="newfirst",
+ help="run tests from new files first, then the rest of the tests "
+ "sorted by file mtime",
+ )
+ group.addoption(
+ "--cache-show",
action="append",
nargs="?",
- dest="cacheshow",
+ dest="cacheshow",
help=(
"show cache contents, don't perform collection or tests. "
"Optional argument: glob (default: '*')."
),
- )
- group.addoption(
- "--cache-clear",
- action="store_true",
- dest="cacheclear",
- help="remove all cache contents at start of test run.",
- )
- cache_dir_default = ".pytest_cache"
- if "TOX_ENV_DIR" in os.environ:
- cache_dir_default = os.path.join(os.environ["TOX_ENV_DIR"], cache_dir_default)
- parser.addini("cache_dir", default=cache_dir_default, help="cache directory path.")
- group.addoption(
- "--lfnf",
- "--last-failed-no-failures",
- action="store",
- dest="last_failed_no_failures",
- choices=("all", "none"),
- default="all",
+ )
+ group.addoption(
+ "--cache-clear",
+ action="store_true",
+ dest="cacheclear",
+ help="remove all cache contents at start of test run.",
+ )
+ cache_dir_default = ".pytest_cache"
+ if "TOX_ENV_DIR" in os.environ:
+ cache_dir_default = os.path.join(os.environ["TOX_ENV_DIR"], cache_dir_default)
+ parser.addini("cache_dir", default=cache_dir_default, help="cache directory path.")
+ group.addoption(
+ "--lfnf",
+ "--last-failed-no-failures",
+ action="store",
+ dest="last_failed_no_failures",
+ choices=("all", "none"),
+ default="all",
help="which tests to run with no previously (known) failures.",
- )
-
-
-def pytest_cmdline_main(config):
- if config.option.cacheshow:
- from _pytest.main import wrap_session
-
- return wrap_session(config, cacheshow)
-
-
-@pytest.hookimpl(tryfirst=True)
-def pytest_configure(config):
- config.cache = Cache.for_config(config)
- config.pluginmanager.register(LFPlugin(config), "lfplugin")
- config.pluginmanager.register(NFPlugin(config), "nfplugin")
-
-
-@pytest.fixture
-def cache(request):
- """
- Return a cache object that can persist state between testing sessions.
-
- cache.get(key, default)
- cache.set(key, value)
-
- Keys must be a ``/`` separated value, where the first part is usually the
- name of your plugin or application to avoid clashes with other cache users.
-
- Values can be any object handled by the json stdlib module.
- """
- return request.config.cache
-
-
-def pytest_report_header(config):
- """Display cachedir with --cache-show and if non-default."""
+ )
+
+
+def pytest_cmdline_main(config):
+ if config.option.cacheshow:
+ from _pytest.main import wrap_session
+
+ return wrap_session(config, cacheshow)
+
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_configure(config):
+ config.cache = Cache.for_config(config)
+ config.pluginmanager.register(LFPlugin(config), "lfplugin")
+ config.pluginmanager.register(NFPlugin(config), "nfplugin")
+
+
+@pytest.fixture
+def cache(request):
+ """
+ Return a cache object that can persist state between testing sessions.
+
+ cache.get(key, default)
+ cache.set(key, value)
+
+ Keys must be a ``/`` separated value, where the first part is usually the
+ name of your plugin or application to avoid clashes with other cache users.
+
+ Values can be any object handled by the json stdlib module.
+ """
+ return request.config.cache
+
+
+def pytest_report_header(config):
+ """Display cachedir with --cache-show and if non-default."""
if config.option.verbose > 0 or config.getini("cache_dir") != ".pytest_cache":
- cachedir = config.cache._cachedir
- # TODO: evaluate generating upward relative paths
- # starting with .., ../.. if sensible
-
- try:
- displaypath = cachedir.relative_to(config.rootdir)
- except ValueError:
- displaypath = cachedir
- return "cachedir: {}".format(displaypath)
-
-
-def cacheshow(config, session):
- from pprint import pformat
-
- tw = py.io.TerminalWriter()
- tw.line("cachedir: " + str(config.cache._cachedir))
- if not config.cache._cachedir.is_dir():
- tw.line("cache is empty")
- return 0
+ cachedir = config.cache._cachedir
+ # TODO: evaluate generating upward relative paths
+ # starting with .., ../.. if sensible
+
+ try:
+ displaypath = cachedir.relative_to(config.rootdir)
+ except ValueError:
+ displaypath = cachedir
+ return "cachedir: {}".format(displaypath)
+
+
+def cacheshow(config, session):
+ from pprint import pformat
+
+ tw = py.io.TerminalWriter()
+ tw.line("cachedir: " + str(config.cache._cachedir))
+ if not config.cache._cachedir.is_dir():
+ tw.line("cache is empty")
+ return 0
glob = config.option.cacheshow[0]
if glob is None:
glob = "*"
- dummy = object()
- basedir = config.cache._cachedir
- vdir = basedir / "v"
+ dummy = object()
+ basedir = config.cache._cachedir
+ vdir = basedir / "v"
tw.sep("-", "cache values for %r" % glob)
for valpath in sorted(x for x in vdir.rglob(glob) if x.is_file()):
- key = valpath.relative_to(vdir)
- val = config.cache.get(key, dummy)
- if val is dummy:
- tw.line("%s contains unreadable content, will be ignored" % key)
- else:
- tw.line("%s contains:" % key)
- for line in pformat(val).splitlines():
- tw.line(" " + line)
-
- ddir = basedir / "d"
- if ddir.is_dir():
+ key = valpath.relative_to(vdir)
+ val = config.cache.get(key, dummy)
+ if val is dummy:
+ tw.line("%s contains unreadable content, will be ignored" % key)
+ else:
+ tw.line("%s contains:" % key)
+ for line in pformat(val).splitlines():
+ tw.line(" " + line)
+
+ ddir = basedir / "d"
+ if ddir.is_dir():
contents = sorted(ddir.rglob(glob))
tw.sep("-", "cache directories for %r" % glob)
- for p in contents:
- # if p.check(dir=1):
- # print("%s/" % p.relto(basedir))
- if p.is_file():
- key = p.relative_to(basedir)
- tw.line("{} is a file of length {:d}".format(key, p.stat().st_size))
- return 0
+ for p in contents:
+ # if p.check(dir=1):
+ # print("%s/" % p.relto(basedir))
+ if p.is_file():
+ key = p.relative_to(basedir)
+ tw.line("{} is a file of length {:d}".format(key, p.stat().st_size))
+ return 0
diff --git a/contrib/python/pytest/py2/_pytest/capture.py b/contrib/python/pytest/py2/_pytest/capture.py
index 676c1d9eb5..68c17772f3 100644
--- a/contrib/python/pytest/py2/_pytest/capture.py
+++ b/contrib/python/pytest/py2/_pytest/capture.py
@@ -1,90 +1,90 @@
# -*- coding: utf-8 -*-
-"""
-per-test stdout/stderr capturing mechanism.
-
-"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import collections
-import contextlib
-import io
-import os
-import sys
-from io import UnsupportedOperation
-from tempfile import TemporaryFile
-
-import six
-
-import pytest
+"""
+per-test stdout/stderr capturing mechanism.
+
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import collections
+import contextlib
+import io
+import os
+import sys
+from io import UnsupportedOperation
+from tempfile import TemporaryFile
+
+import six
+
+import pytest
from _pytest.compat import _PY3
-from _pytest.compat import CaptureIO
-
-patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"}
-
-
-def pytest_addoption(parser):
- group = parser.getgroup("general")
- group._addoption(
- "--capture",
- action="store",
- default="fd" if hasattr(os, "dup") else "sys",
- metavar="method",
- choices=["fd", "sys", "no"],
- help="per-test capturing method: one of fd|sys|no.",
- )
- group._addoption(
- "-s",
- action="store_const",
- const="no",
- dest="capture",
- help="shortcut for --capture=no.",
- )
-
-
-@pytest.hookimpl(hookwrapper=True)
-def pytest_load_initial_conftests(early_config, parser, args):
- ns = early_config.known_args_namespace
- if ns.capture == "fd":
- _py36_windowsconsoleio_workaround(sys.stdout)
- _colorama_workaround()
- _readline_workaround()
- pluginmanager = early_config.pluginmanager
- capman = CaptureManager(ns.capture)
- pluginmanager.register(capman, "capturemanager")
-
- # make sure that capturemanager is properly reset at final shutdown
- early_config.add_cleanup(capman.stop_global_capturing)
-
- # finally trigger conftest loading but while capturing (issue93)
- capman.start_global_capturing()
- outcome = yield
- capman.suspend_global_capture()
- if outcome.excinfo is not None:
- out, err = capman.read_global_capture()
- sys.stdout.write(out)
- sys.stderr.write(err)
-
-
-class CaptureManager(object):
- """
- Capture plugin, manages that the appropriate capture method is enabled/disabled during collection and each
- test phase (setup, call, teardown). After each of those points, the captured output is obtained and
- attached to the collection/runtest report.
-
- There are two levels of capture:
- * global: which is enabled by default and can be suppressed by the ``-s`` option. This is always enabled/disabled
- during collection and each test phase.
- * fixture: when a test function or one of its fixture depend on the ``capsys`` or ``capfd`` fixtures. In this
- case special handling is needed to ensure the fixtures take precedence over the global capture.
- """
-
- def __init__(self, method):
- self._method = method
- self._global_capturing = None
- self._current_item = None
-
+from _pytest.compat import CaptureIO
+
+patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"}
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("general")
+ group._addoption(
+ "--capture",
+ action="store",
+ default="fd" if hasattr(os, "dup") else "sys",
+ metavar="method",
+ choices=["fd", "sys", "no"],
+ help="per-test capturing method: one of fd|sys|no.",
+ )
+ group._addoption(
+ "-s",
+ action="store_const",
+ const="no",
+ dest="capture",
+ help="shortcut for --capture=no.",
+ )
+
+
+@pytest.hookimpl(hookwrapper=True)
+def pytest_load_initial_conftests(early_config, parser, args):
+ ns = early_config.known_args_namespace
+ if ns.capture == "fd":
+ _py36_windowsconsoleio_workaround(sys.stdout)
+ _colorama_workaround()
+ _readline_workaround()
+ pluginmanager = early_config.pluginmanager
+ capman = CaptureManager(ns.capture)
+ pluginmanager.register(capman, "capturemanager")
+
+ # make sure that capturemanager is properly reset at final shutdown
+ early_config.add_cleanup(capman.stop_global_capturing)
+
+ # finally trigger conftest loading but while capturing (issue93)
+ capman.start_global_capturing()
+ outcome = yield
+ capman.suspend_global_capture()
+ if outcome.excinfo is not None:
+ out, err = capman.read_global_capture()
+ sys.stdout.write(out)
+ sys.stderr.write(err)
+
+
+class CaptureManager(object):
+ """
+ Capture plugin, manages that the appropriate capture method is enabled/disabled during collection and each
+ test phase (setup, call, teardown). After each of those points, the captured output is obtained and
+ attached to the collection/runtest report.
+
+ There are two levels of capture:
+ * global: which is enabled by default and can be suppressed by the ``-s`` option. This is always enabled/disabled
+ during collection and each test phase.
+ * fixture: when a test function or one of its fixture depend on the ``capsys`` or ``capfd`` fixtures. In this
+ case special handling is needed to ensure the fixtures take precedence over the global capture.
+ """
+
+ def __init__(self, method):
+ self._method = method
+ self._global_capturing = None
+ self._current_item = None
+
def __repr__(self):
return "<CaptureManager _method=%r _global_capturing=%r _current_item=%r>" % (
self._method,
@@ -92,15 +92,15 @@ class CaptureManager(object):
self._current_item,
)
- def _getcapture(self, method):
- if method == "fd":
- return MultiCapture(out=True, err=True, Capture=FDCapture)
- elif method == "sys":
- return MultiCapture(out=True, err=True, Capture=SysCapture)
- elif method == "no":
- return MultiCapture(out=False, err=False, in_=False)
+ def _getcapture(self, method):
+ if method == "fd":
+ return MultiCapture(out=True, err=True, Capture=FDCapture)
+ elif method == "sys":
+ return MultiCapture(out=True, err=True, Capture=SysCapture)
+ elif method == "no":
+ return MultiCapture(out=False, err=False, in_=False)
raise ValueError("unknown capturing method: %r" % method) # pragma: no cover
-
+
def is_capturing(self):
if self.is_globally_capturing():
return "global"
@@ -111,33 +111,33 @@ class CaptureManager(object):
)
return False
- # Global capturing control
-
- def is_globally_capturing(self):
- return self._method != "no"
-
- def start_global_capturing(self):
- assert self._global_capturing is None
- self._global_capturing = self._getcapture(self._method)
- self._global_capturing.start_capturing()
-
- def stop_global_capturing(self):
- if self._global_capturing is not None:
- self._global_capturing.pop_outerr_to_orig()
- self._global_capturing.stop_capturing()
- self._global_capturing = None
-
- def resume_global_capture(self):
+ # Global capturing control
+
+ def is_globally_capturing(self):
+ return self._method != "no"
+
+ def start_global_capturing(self):
+ assert self._global_capturing is None
+ self._global_capturing = self._getcapture(self._method)
+ self._global_capturing.start_capturing()
+
+ def stop_global_capturing(self):
+ if self._global_capturing is not None:
+ self._global_capturing.pop_outerr_to_orig()
+ self._global_capturing.stop_capturing()
+ self._global_capturing = None
+
+ def resume_global_capture(self):
# During teardown of the python process, and on rare occasions, capture
# attributes can be `None` while trying to resume global capture.
if self._global_capturing is not None:
self._global_capturing.resume_capturing()
-
- def suspend_global_capture(self, in_=False):
- cap = getattr(self, "_global_capturing", None)
- if cap is not None:
- cap.suspend_capturing(in_=in_)
-
+
+ def suspend_global_capture(self, in_=False):
+ cap = getattr(self, "_global_capturing", None)
+ if cap is not None:
+ cap.suspend_capturing(in_=in_)
+
def suspend(self, in_=False):
# Need to undo local capsys-et-al if it exists before disabling global capture.
self.suspend_fixture(self._current_item)
@@ -147,331 +147,331 @@ class CaptureManager(object):
self.resume_global_capture()
self.resume_fixture(self._current_item)
- def read_global_capture(self):
- return self._global_capturing.readouterr()
-
- # Fixture Control (it's just forwarding, think about removing this later)
-
- def activate_fixture(self, item):
- """If the current item is using ``capsys`` or ``capfd``, activate them so they take precedence over
- the global capture.
- """
- fixture = getattr(item, "_capture_fixture", None)
- if fixture is not None:
- fixture._start()
-
- def deactivate_fixture(self, item):
- """Deactivates the ``capsys`` or ``capfd`` fixture of this item, if any."""
- fixture = getattr(item, "_capture_fixture", None)
- if fixture is not None:
- fixture.close()
-
- def suspend_fixture(self, item):
- fixture = getattr(item, "_capture_fixture", None)
- if fixture is not None:
- fixture._suspend()
-
- def resume_fixture(self, item):
- fixture = getattr(item, "_capture_fixture", None)
- if fixture is not None:
- fixture._resume()
-
- # Helper context managers
-
- @contextlib.contextmanager
- def global_and_fixture_disabled(self):
+ def read_global_capture(self):
+ return self._global_capturing.readouterr()
+
+ # Fixture Control (it's just forwarding, think about removing this later)
+
+ def activate_fixture(self, item):
+ """If the current item is using ``capsys`` or ``capfd``, activate them so they take precedence over
+ the global capture.
+ """
+ fixture = getattr(item, "_capture_fixture", None)
+ if fixture is not None:
+ fixture._start()
+
+ def deactivate_fixture(self, item):
+ """Deactivates the ``capsys`` or ``capfd`` fixture of this item, if any."""
+ fixture = getattr(item, "_capture_fixture", None)
+ if fixture is not None:
+ fixture.close()
+
+ def suspend_fixture(self, item):
+ fixture = getattr(item, "_capture_fixture", None)
+ if fixture is not None:
+ fixture._suspend()
+
+ def resume_fixture(self, item):
+ fixture = getattr(item, "_capture_fixture", None)
+ if fixture is not None:
+ fixture._resume()
+
+ # Helper context managers
+
+ @contextlib.contextmanager
+ def global_and_fixture_disabled(self):
"""Context manager to temporarily disable global and current fixture capturing."""
self.suspend()
- try:
- yield
- finally:
+ try:
+ yield
+ finally:
self.resume()
-
- @contextlib.contextmanager
- def item_capture(self, when, item):
- self.resume_global_capture()
- self.activate_fixture(item)
- try:
- yield
- finally:
- self.deactivate_fixture(item)
- self.suspend_global_capture(in_=False)
-
- out, err = self.read_global_capture()
- item.add_report_section(when, "stdout", out)
- item.add_report_section(when, "stderr", err)
-
- # Hooks
-
- @pytest.hookimpl(hookwrapper=True)
- def pytest_make_collect_report(self, collector):
- if isinstance(collector, pytest.File):
- self.resume_global_capture()
- outcome = yield
- self.suspend_global_capture()
- out, err = self.read_global_capture()
- rep = outcome.get_result()
- if out:
- rep.sections.append(("Captured stdout", out))
- if err:
- rep.sections.append(("Captured stderr", err))
- else:
- yield
-
- @pytest.hookimpl(hookwrapper=True)
- def pytest_runtest_protocol(self, item):
- self._current_item = item
- yield
- self._current_item = None
-
- @pytest.hookimpl(hookwrapper=True)
- def pytest_runtest_setup(self, item):
- with self.item_capture("setup", item):
- yield
-
- @pytest.hookimpl(hookwrapper=True)
- def pytest_runtest_call(self, item):
- with self.item_capture("call", item):
- yield
-
- @pytest.hookimpl(hookwrapper=True)
- def pytest_runtest_teardown(self, item):
- with self.item_capture("teardown", item):
- yield
-
- @pytest.hookimpl(tryfirst=True)
- def pytest_keyboard_interrupt(self, excinfo):
- self.stop_global_capturing()
-
- @pytest.hookimpl(tryfirst=True)
- def pytest_internalerror(self, excinfo):
- self.stop_global_capturing()
-
-
-capture_fixtures = {"capfd", "capfdbinary", "capsys", "capsysbinary"}
-
-
-def _ensure_only_one_capture_fixture(request, name):
- fixtures = set(request.fixturenames) & capture_fixtures - {name}
- if fixtures:
- fixtures = sorted(fixtures)
- fixtures = fixtures[0] if len(fixtures) == 1 else fixtures
- raise request.raiseerror(
- "cannot use {} and {} at the same time".format(fixtures, name)
- )
-
-
-@pytest.fixture
-def capsys(request):
+
+ @contextlib.contextmanager
+ def item_capture(self, when, item):
+ self.resume_global_capture()
+ self.activate_fixture(item)
+ try:
+ yield
+ finally:
+ self.deactivate_fixture(item)
+ self.suspend_global_capture(in_=False)
+
+ out, err = self.read_global_capture()
+ item.add_report_section(when, "stdout", out)
+ item.add_report_section(when, "stderr", err)
+
+ # Hooks
+
+ @pytest.hookimpl(hookwrapper=True)
+ def pytest_make_collect_report(self, collector):
+ if isinstance(collector, pytest.File):
+ self.resume_global_capture()
+ outcome = yield
+ self.suspend_global_capture()
+ out, err = self.read_global_capture()
+ rep = outcome.get_result()
+ if out:
+ rep.sections.append(("Captured stdout", out))
+ if err:
+ rep.sections.append(("Captured stderr", err))
+ else:
+ yield
+
+ @pytest.hookimpl(hookwrapper=True)
+ def pytest_runtest_protocol(self, item):
+ self._current_item = item
+ yield
+ self._current_item = None
+
+ @pytest.hookimpl(hookwrapper=True)
+ def pytest_runtest_setup(self, item):
+ with self.item_capture("setup", item):
+ yield
+
+ @pytest.hookimpl(hookwrapper=True)
+ def pytest_runtest_call(self, item):
+ with self.item_capture("call", item):
+ yield
+
+ @pytest.hookimpl(hookwrapper=True)
+ def pytest_runtest_teardown(self, item):
+ with self.item_capture("teardown", item):
+ yield
+
+ @pytest.hookimpl(tryfirst=True)
+ def pytest_keyboard_interrupt(self, excinfo):
+ self.stop_global_capturing()
+
+ @pytest.hookimpl(tryfirst=True)
+ def pytest_internalerror(self, excinfo):
+ self.stop_global_capturing()
+
+
+capture_fixtures = {"capfd", "capfdbinary", "capsys", "capsysbinary"}
+
+
+def _ensure_only_one_capture_fixture(request, name):
+ fixtures = set(request.fixturenames) & capture_fixtures - {name}
+ if fixtures:
+ fixtures = sorted(fixtures)
+ fixtures = fixtures[0] if len(fixtures) == 1 else fixtures
+ raise request.raiseerror(
+ "cannot use {} and {} at the same time".format(fixtures, name)
+ )
+
+
+@pytest.fixture
+def capsys(request):
"""Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
The captured output is made available via ``capsys.readouterr()`` method
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``text`` objects.
- """
- _ensure_only_one_capture_fixture(request, "capsys")
- with _install_capture_fixture_on_item(request, SysCapture) as fixture:
- yield fixture
-
-
-@pytest.fixture
-def capsysbinary(request):
+ """
+ _ensure_only_one_capture_fixture(request, "capsys")
+ with _install_capture_fixture_on_item(request, SysCapture) as fixture:
+ yield fixture
+
+
+@pytest.fixture
+def capsysbinary(request):
"""Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.
The captured output is made available via ``capsysbinary.readouterr()``
method calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``bytes`` objects.
- """
- _ensure_only_one_capture_fixture(request, "capsysbinary")
- # Currently, the implementation uses the python3 specific `.buffer`
- # property of CaptureIO.
- if sys.version_info < (3,):
+ """
+ _ensure_only_one_capture_fixture(request, "capsysbinary")
+ # Currently, the implementation uses the python3 specific `.buffer`
+ # property of CaptureIO.
+ if sys.version_info < (3,):
raise request.raiseerror("capsysbinary is only supported on Python 3")
- with _install_capture_fixture_on_item(request, SysCaptureBinary) as fixture:
- yield fixture
-
-
-@pytest.fixture
-def capfd(request):
+ with _install_capture_fixture_on_item(request, SysCaptureBinary) as fixture:
+ yield fixture
+
+
+@pytest.fixture
+def capfd(request):
"""Enable text capturing of writes to file descriptors ``1`` and ``2``.
The captured output is made available via ``capfd.readouterr()`` method
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``text`` objects.
- """
- _ensure_only_one_capture_fixture(request, "capfd")
- if not hasattr(os, "dup"):
- pytest.skip(
- "capfd fixture needs os.dup function which is not available in this system"
- )
- with _install_capture_fixture_on_item(request, FDCapture) as fixture:
- yield fixture
-
-
-@pytest.fixture
-def capfdbinary(request):
+ """
+ _ensure_only_one_capture_fixture(request, "capfd")
+ if not hasattr(os, "dup"):
+ pytest.skip(
+ "capfd fixture needs os.dup function which is not available in this system"
+ )
+ with _install_capture_fixture_on_item(request, FDCapture) as fixture:
+ yield fixture
+
+
+@pytest.fixture
+def capfdbinary(request):
"""Enable bytes capturing of writes to file descriptors ``1`` and ``2``.
The captured output is made available via ``capfd.readouterr()`` method
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``byte`` objects.
- """
- _ensure_only_one_capture_fixture(request, "capfdbinary")
- if not hasattr(os, "dup"):
- pytest.skip(
- "capfdbinary fixture needs os.dup function which is not available in this system"
- )
- with _install_capture_fixture_on_item(request, FDCaptureBinary) as fixture:
- yield fixture
-
-
-@contextlib.contextmanager
-def _install_capture_fixture_on_item(request, capture_class):
- """
- Context manager which creates a ``CaptureFixture`` instance and "installs" it on
- the item/node of the given request. Used by ``capsys`` and ``capfd``.
-
- The CaptureFixture is added as attribute of the item because it needs to accessed
- by ``CaptureManager`` during its ``pytest_runtest_*`` hooks.
- """
- request.node._capture_fixture = fixture = CaptureFixture(capture_class, request)
- capmanager = request.config.pluginmanager.getplugin("capturemanager")
+ """
+ _ensure_only_one_capture_fixture(request, "capfdbinary")
+ if not hasattr(os, "dup"):
+ pytest.skip(
+ "capfdbinary fixture needs os.dup function which is not available in this system"
+ )
+ with _install_capture_fixture_on_item(request, FDCaptureBinary) as fixture:
+ yield fixture
+
+
+@contextlib.contextmanager
+def _install_capture_fixture_on_item(request, capture_class):
+ """
+ Context manager which creates a ``CaptureFixture`` instance and "installs" it on
+ the item/node of the given request. Used by ``capsys`` and ``capfd``.
+
+ The CaptureFixture is added as attribute of the item because it needs to accessed
+ by ``CaptureManager`` during its ``pytest_runtest_*`` hooks.
+ """
+ request.node._capture_fixture = fixture = CaptureFixture(capture_class, request)
+ capmanager = request.config.pluginmanager.getplugin("capturemanager")
# Need to active this fixture right away in case it is being used by another fixture (setup phase).
# If this fixture is being used only by a test function (call phase), then we wouldn't need this
# activation, but it doesn't hurt.
- capmanager.activate_fixture(request.node)
- yield fixture
- fixture.close()
- del request.node._capture_fixture
-
-
-class CaptureFixture(object):
- """
- Object returned by :py:func:`capsys`, :py:func:`capsysbinary`, :py:func:`capfd` and :py:func:`capfdbinary`
- fixtures.
- """
-
- def __init__(self, captureclass, request):
- self.captureclass = captureclass
- self.request = request
- self._capture = None
- self._captured_out = self.captureclass.EMPTY_BUFFER
- self._captured_err = self.captureclass.EMPTY_BUFFER
-
- def _start(self):
+ capmanager.activate_fixture(request.node)
+ yield fixture
+ fixture.close()
+ del request.node._capture_fixture
+
+
+class CaptureFixture(object):
+ """
+ Object returned by :py:func:`capsys`, :py:func:`capsysbinary`, :py:func:`capfd` and :py:func:`capfdbinary`
+ fixtures.
+ """
+
+ def __init__(self, captureclass, request):
+ self.captureclass = captureclass
+ self.request = request
+ self._capture = None
+ self._captured_out = self.captureclass.EMPTY_BUFFER
+ self._captured_err = self.captureclass.EMPTY_BUFFER
+
+ def _start(self):
if self._capture is None:
- self._capture = MultiCapture(
- out=True, err=True, in_=False, Capture=self.captureclass
- )
- self._capture.start_capturing()
-
- def close(self):
- if self._capture is not None:
- out, err = self._capture.pop_outerr_to_orig()
- self._captured_out += out
- self._captured_err += err
- self._capture.stop_capturing()
- self._capture = None
-
- def readouterr(self):
- """Read and return the captured output so far, resetting the internal buffer.
-
+ self._capture = MultiCapture(
+ out=True, err=True, in_=False, Capture=self.captureclass
+ )
+ self._capture.start_capturing()
+
+ def close(self):
+ if self._capture is not None:
+ out, err = self._capture.pop_outerr_to_orig()
+ self._captured_out += out
+ self._captured_err += err
+ self._capture.stop_capturing()
+ self._capture = None
+
+ def readouterr(self):
+ """Read and return the captured output so far, resetting the internal buffer.
+
:return: captured content as a namedtuple with ``out`` and ``err`` string attributes
- """
- captured_out, captured_err = self._captured_out, self._captured_err
- if self._capture is not None:
- out, err = self._capture.readouterr()
- captured_out += out
- captured_err += err
- self._captured_out = self.captureclass.EMPTY_BUFFER
- self._captured_err = self.captureclass.EMPTY_BUFFER
- return CaptureResult(captured_out, captured_err)
-
- def _suspend(self):
- """Suspends this fixture's own capturing temporarily."""
+ """
+ captured_out, captured_err = self._captured_out, self._captured_err
+ if self._capture is not None:
+ out, err = self._capture.readouterr()
+ captured_out += out
+ captured_err += err
+ self._captured_out = self.captureclass.EMPTY_BUFFER
+ self._captured_err = self.captureclass.EMPTY_BUFFER
+ return CaptureResult(captured_out, captured_err)
+
+ def _suspend(self):
+ """Suspends this fixture's own capturing temporarily."""
if self._capture is not None:
self._capture.suspend_capturing()
-
- def _resume(self):
- """Resumes this fixture's own capturing temporarily."""
+
+ def _resume(self):
+ """Resumes this fixture's own capturing temporarily."""
if self._capture is not None:
self._capture.resume_capturing()
-
- @contextlib.contextmanager
- def disabled(self):
- """Temporarily disables capture while inside the 'with' block."""
- capmanager = self.request.config.pluginmanager.getplugin("capturemanager")
- with capmanager.global_and_fixture_disabled():
- yield
-
-
-def safe_text_dupfile(f, mode, default_encoding="UTF8"):
- """ return an open text file object that's a duplicate of f on the
- FD-level if possible.
- """
- encoding = getattr(f, "encoding", None)
- try:
- fd = f.fileno()
- except Exception:
- if "b" not in getattr(f, "mode", "") and hasattr(f, "encoding"):
- # we seem to have a text stream, let's just use it
- return f
- else:
- newfd = os.dup(fd)
- if "b" not in mode:
- mode += "b"
- f = os.fdopen(newfd, mode, 0) # no buffering
- return EncodedFile(f, encoding or default_encoding)
-
-
-class EncodedFile(object):
- errors = "strict" # possibly needed by py3 code (issue555)
-
- def __init__(self, buffer, encoding):
- self.buffer = buffer
- self.encoding = encoding
-
- def write(self, obj):
- if isinstance(obj, six.text_type):
- obj = obj.encode(self.encoding, "replace")
+
+ @contextlib.contextmanager
+ def disabled(self):
+ """Temporarily disables capture while inside the 'with' block."""
+ capmanager = self.request.config.pluginmanager.getplugin("capturemanager")
+ with capmanager.global_and_fixture_disabled():
+ yield
+
+
+def safe_text_dupfile(f, mode, default_encoding="UTF8"):
+ """ return an open text file object that's a duplicate of f on the
+ FD-level if possible.
+ """
+ encoding = getattr(f, "encoding", None)
+ try:
+ fd = f.fileno()
+ except Exception:
+ if "b" not in getattr(f, "mode", "") and hasattr(f, "encoding"):
+ # we seem to have a text stream, let's just use it
+ return f
+ else:
+ newfd = os.dup(fd)
+ if "b" not in mode:
+ mode += "b"
+ f = os.fdopen(newfd, mode, 0) # no buffering
+ return EncodedFile(f, encoding or default_encoding)
+
+
+class EncodedFile(object):
+ errors = "strict" # possibly needed by py3 code (issue555)
+
+ def __init__(self, buffer, encoding):
+ self.buffer = buffer
+ self.encoding = encoding
+
+ def write(self, obj):
+ if isinstance(obj, six.text_type):
+ obj = obj.encode(self.encoding, "replace")
elif _PY3:
raise TypeError(
"write() argument must be str, not {}".format(type(obj).__name__)
)
- self.buffer.write(obj)
-
- def writelines(self, linelist):
- data = "".join(linelist)
- self.write(data)
-
- @property
- def name(self):
- """Ensure that file.name is a string."""
- return repr(self.buffer)
-
+ self.buffer.write(obj)
+
+ def writelines(self, linelist):
+ data = "".join(linelist)
+ self.write(data)
+
+ @property
+ def name(self):
+ """Ensure that file.name is a string."""
+ return repr(self.buffer)
+
@property
def mode(self):
return self.buffer.mode.replace("b", "")
- def __getattr__(self, name):
- return getattr(object.__getattribute__(self, "buffer"), name)
-
-
-CaptureResult = collections.namedtuple("CaptureResult", ["out", "err"])
-
-
-class MultiCapture(object):
- out = err = in_ = None
+ def __getattr__(self, name):
+ return getattr(object.__getattribute__(self, "buffer"), name)
+
+
+CaptureResult = collections.namedtuple("CaptureResult", ["out", "err"])
+
+
+class MultiCapture(object):
+ out = err = in_ = None
_state = None
-
- def __init__(self, out=True, err=True, in_=True, Capture=None):
- if in_:
- self.in_ = Capture(0)
- if out:
- self.out = Capture(1)
- if err:
- self.err = Capture(2)
-
+
+ def __init__(self, out=True, err=True, in_=True, Capture=None):
+ if in_:
+ self.in_ = Capture(0)
+ if out:
+ self.out = Capture(1)
+ if err:
+ self.err = Capture(2)
+
def __repr__(self):
return "<MultiCapture out=%r err=%r in_=%r _state=%r _in_suspended=%r>" % (
self.out,
@@ -481,185 +481,185 @@ class MultiCapture(object):
getattr(self, "_in_suspended", "<UNSET>"),
)
- def start_capturing(self):
+ def start_capturing(self):
self._state = "started"
- if self.in_:
- self.in_.start()
- if self.out:
- self.out.start()
- if self.err:
- self.err.start()
-
- def pop_outerr_to_orig(self):
- """ pop current snapshot out/err capture and flush to orig streams. """
- out, err = self.readouterr()
- if out:
- self.out.writeorg(out)
- if err:
- self.err.writeorg(err)
- return out, err
-
- def suspend_capturing(self, in_=False):
+ if self.in_:
+ self.in_.start()
+ if self.out:
+ self.out.start()
+ if self.err:
+ self.err.start()
+
+ def pop_outerr_to_orig(self):
+ """ pop current snapshot out/err capture and flush to orig streams. """
+ out, err = self.readouterr()
+ if out:
+ self.out.writeorg(out)
+ if err:
+ self.err.writeorg(err)
+ return out, err
+
+ def suspend_capturing(self, in_=False):
self._state = "suspended"
- if self.out:
- self.out.suspend()
- if self.err:
- self.err.suspend()
- if in_ and self.in_:
- self.in_.suspend()
- self._in_suspended = True
-
- def resume_capturing(self):
+ if self.out:
+ self.out.suspend()
+ if self.err:
+ self.err.suspend()
+ if in_ and self.in_:
+ self.in_.suspend()
+ self._in_suspended = True
+
+ def resume_capturing(self):
self._state = "resumed"
- if self.out:
- self.out.resume()
- if self.err:
- self.err.resume()
- if hasattr(self, "_in_suspended"):
- self.in_.resume()
- del self._in_suspended
-
- def stop_capturing(self):
- """ stop capturing and reset capturing streams """
+ if self.out:
+ self.out.resume()
+ if self.err:
+ self.err.resume()
+ if hasattr(self, "_in_suspended"):
+ self.in_.resume()
+ del self._in_suspended
+
+ def stop_capturing(self):
+ """ stop capturing and reset capturing streams """
if self._state == "stopped":
- raise ValueError("was already stopped")
+ raise ValueError("was already stopped")
self._state = "stopped"
- if self.out:
- self.out.done()
- if self.err:
- self.err.done()
- if self.in_:
- self.in_.done()
-
- def readouterr(self):
- """ return snapshot unicode value of stdout/stderr capturings. """
- return CaptureResult(
- self.out.snap() if self.out is not None else "",
- self.err.snap() if self.err is not None else "",
- )
-
-
-class NoCapture(object):
- EMPTY_BUFFER = None
- __init__ = start = done = suspend = resume = lambda *args: None
-
-
-class FDCaptureBinary(object):
- """Capture IO to/from a given os-level filedescriptor.
-
- snap() produces `bytes`
- """
-
- EMPTY_BUFFER = b""
+ if self.out:
+ self.out.done()
+ if self.err:
+ self.err.done()
+ if self.in_:
+ self.in_.done()
+
+ def readouterr(self):
+ """ return snapshot unicode value of stdout/stderr capturings. """
+ return CaptureResult(
+ self.out.snap() if self.out is not None else "",
+ self.err.snap() if self.err is not None else "",
+ )
+
+
+class NoCapture(object):
+ EMPTY_BUFFER = None
+ __init__ = start = done = suspend = resume = lambda *args: None
+
+
+class FDCaptureBinary(object):
+ """Capture IO to/from a given os-level filedescriptor.
+
+ snap() produces `bytes`
+ """
+
+ EMPTY_BUFFER = b""
_state = None
-
- def __init__(self, targetfd, tmpfile=None):
- self.targetfd = targetfd
- try:
- self.targetfd_save = os.dup(self.targetfd)
- except OSError:
- self.start = lambda: None
- self.done = lambda: None
- else:
- if targetfd == 0:
- assert not tmpfile, "cannot set tmpfile with stdin"
- tmpfile = open(os.devnull, "r")
- self.syscapture = SysCapture(targetfd)
- else:
- if tmpfile is None:
- f = TemporaryFile()
- with f:
- tmpfile = safe_text_dupfile(f, mode="wb+")
- if targetfd in patchsysdict:
- self.syscapture = SysCapture(targetfd, tmpfile)
- else:
- self.syscapture = NoCapture()
- self.tmpfile = tmpfile
- self.tmpfile_fd = tmpfile.fileno()
-
- def __repr__(self):
+
+ def __init__(self, targetfd, tmpfile=None):
+ self.targetfd = targetfd
+ try:
+ self.targetfd_save = os.dup(self.targetfd)
+ except OSError:
+ self.start = lambda: None
+ self.done = lambda: None
+ else:
+ if targetfd == 0:
+ assert not tmpfile, "cannot set tmpfile with stdin"
+ tmpfile = open(os.devnull, "r")
+ self.syscapture = SysCapture(targetfd)
+ else:
+ if tmpfile is None:
+ f = TemporaryFile()
+ with f:
+ tmpfile = safe_text_dupfile(f, mode="wb+")
+ if targetfd in patchsysdict:
+ self.syscapture = SysCapture(targetfd, tmpfile)
+ else:
+ self.syscapture = NoCapture()
+ self.tmpfile = tmpfile
+ self.tmpfile_fd = tmpfile.fileno()
+
+ def __repr__(self):
return "<FDCapture %s oldfd=%s _state=%r>" % (
self.targetfd,
getattr(self, "targetfd_save", None),
self._state,
)
-
- def start(self):
- """ Start capturing on targetfd using memorized tmpfile. """
- try:
- os.fstat(self.targetfd_save)
- except (AttributeError, OSError):
- raise ValueError("saved filedescriptor not valid anymore")
- os.dup2(self.tmpfile_fd, self.targetfd)
- self.syscapture.start()
+
+ def start(self):
+ """ Start capturing on targetfd using memorized tmpfile. """
+ try:
+ os.fstat(self.targetfd_save)
+ except (AttributeError, OSError):
+ raise ValueError("saved filedescriptor not valid anymore")
+ os.dup2(self.tmpfile_fd, self.targetfd)
+ self.syscapture.start()
self._state = "started"
-
- def snap(self):
- self.tmpfile.seek(0)
- res = self.tmpfile.read()
- self.tmpfile.seek(0)
- self.tmpfile.truncate()
- return res
-
- def done(self):
- """ stop capturing, restore streams, return original capture file,
- seeked to position zero. """
- targetfd_save = self.__dict__.pop("targetfd_save")
- os.dup2(targetfd_save, self.targetfd)
- os.close(targetfd_save)
- self.syscapture.done()
- _attempt_to_close_capture_file(self.tmpfile)
+
+ def snap(self):
+ self.tmpfile.seek(0)
+ res = self.tmpfile.read()
+ self.tmpfile.seek(0)
+ self.tmpfile.truncate()
+ return res
+
+ def done(self):
+ """ stop capturing, restore streams, return original capture file,
+ seeked to position zero. """
+ targetfd_save = self.__dict__.pop("targetfd_save")
+ os.dup2(targetfd_save, self.targetfd)
+ os.close(targetfd_save)
+ self.syscapture.done()
+ _attempt_to_close_capture_file(self.tmpfile)
self._state = "done"
-
- def suspend(self):
- self.syscapture.suspend()
- os.dup2(self.targetfd_save, self.targetfd)
+
+ def suspend(self):
+ self.syscapture.suspend()
+ os.dup2(self.targetfd_save, self.targetfd)
self._state = "suspended"
-
- def resume(self):
- self.syscapture.resume()
- os.dup2(self.tmpfile_fd, self.targetfd)
+
+ def resume(self):
+ self.syscapture.resume()
+ os.dup2(self.tmpfile_fd, self.targetfd)
self._state = "resumed"
-
- def writeorg(self, data):
- """ write to original file descriptor. """
- if isinstance(data, six.text_type):
- data = data.encode("utf8") # XXX use encoding of original stream
- os.write(self.targetfd_save, data)
-
-
-class FDCapture(FDCaptureBinary):
- """Capture IO to/from a given os-level filedescriptor.
-
- snap() produces text
- """
-
- EMPTY_BUFFER = str()
-
- def snap(self):
+
+ def writeorg(self, data):
+ """ write to original file descriptor. """
+ if isinstance(data, six.text_type):
+ data = data.encode("utf8") # XXX use encoding of original stream
+ os.write(self.targetfd_save, data)
+
+
+class FDCapture(FDCaptureBinary):
+ """Capture IO to/from a given os-level filedescriptor.
+
+ snap() produces text
+ """
+
+ EMPTY_BUFFER = str()
+
+ def snap(self):
res = super(FDCapture, self).snap()
- enc = getattr(self.tmpfile, "encoding", None)
- if enc and isinstance(res, bytes):
- res = six.text_type(res, enc, "replace")
- return res
-
-
-class SysCapture(object):
-
- EMPTY_BUFFER = str()
+ enc = getattr(self.tmpfile, "encoding", None)
+ if enc and isinstance(res, bytes):
+ res = six.text_type(res, enc, "replace")
+ return res
+
+
+class SysCapture(object):
+
+ EMPTY_BUFFER = str()
_state = None
-
- def __init__(self, fd, tmpfile=None):
- name = patchsysdict[fd]
- self._old = getattr(sys, name)
- self.name = name
- if tmpfile is None:
- if name == "stdin":
- tmpfile = DontReadFromInput()
- else:
- tmpfile = CaptureIO()
- self.tmpfile = tmpfile
-
+
+ def __init__(self, fd, tmpfile=None):
+ name = patchsysdict[fd]
+ self._old = getattr(sys, name)
+ self.name = name
+ if tmpfile is None:
+ if name == "stdin":
+ tmpfile = DontReadFromInput()
+ else:
+ tmpfile = CaptureIO()
+ self.tmpfile = tmpfile
+
def __repr__(self):
return "<SysCapture %s _old=%r, tmpfile=%r _state=%r>" % (
self.name,
@@ -668,183 +668,183 @@ class SysCapture(object):
self._state,
)
- def start(self):
- setattr(sys, self.name, self.tmpfile)
+ def start(self):
+ setattr(sys, self.name, self.tmpfile)
self._state = "started"
-
- def snap(self):
- res = self.tmpfile.getvalue()
- self.tmpfile.seek(0)
- self.tmpfile.truncate()
- return res
-
- def done(self):
- setattr(sys, self.name, self._old)
- del self._old
- _attempt_to_close_capture_file(self.tmpfile)
+
+ def snap(self):
+ res = self.tmpfile.getvalue()
+ self.tmpfile.seek(0)
+ self.tmpfile.truncate()
+ return res
+
+ def done(self):
+ setattr(sys, self.name, self._old)
+ del self._old
+ _attempt_to_close_capture_file(self.tmpfile)
self._state = "done"
-
- def suspend(self):
- setattr(sys, self.name, self._old)
+
+ def suspend(self):
+ setattr(sys, self.name, self._old)
self._state = "suspended"
-
- def resume(self):
- setattr(sys, self.name, self.tmpfile)
+
+ def resume(self):
+ setattr(sys, self.name, self.tmpfile)
self._state = "resumed"
-
- def writeorg(self, data):
- self._old.write(data)
- self._old.flush()
-
-
-class SysCaptureBinary(SysCapture):
- EMPTY_BUFFER = b""
-
- def snap(self):
- res = self.tmpfile.buffer.getvalue()
- self.tmpfile.seek(0)
- self.tmpfile.truncate()
- return res
-
-
-class DontReadFromInput(six.Iterator):
- """Temporary stub class. Ideally when stdin is accessed, the
- capturing should be turned off, with possibly all data captured
- so far sent to the screen. This should be configurable, though,
- because in automated test runs it is better to crash than
- hang indefinitely.
- """
-
- encoding = None
-
- def read(self, *args):
- raise IOError("reading from stdin while output is captured")
-
- readline = read
- readlines = read
- __next__ = read
-
- def __iter__(self):
- return self
-
- def fileno(self):
- raise UnsupportedOperation("redirected stdin is pseudofile, has no fileno()")
-
- def isatty(self):
- return False
-
- def close(self):
- pass
-
- @property
- def buffer(self):
- if sys.version_info >= (3, 0):
- return self
- else:
- raise AttributeError("redirected stdin has no attribute buffer")
-
-
-def _colorama_workaround():
- """
- Ensure colorama is imported so that it attaches to the correct stdio
- handles on Windows.
-
- colorama uses the terminal on import time. So if something does the
- first import of colorama while I/O capture is active, colorama will
- fail in various ways.
- """
+
+ def writeorg(self, data):
+ self._old.write(data)
+ self._old.flush()
+
+
+class SysCaptureBinary(SysCapture):
+ EMPTY_BUFFER = b""
+
+ def snap(self):
+ res = self.tmpfile.buffer.getvalue()
+ self.tmpfile.seek(0)
+ self.tmpfile.truncate()
+ return res
+
+
+class DontReadFromInput(six.Iterator):
+ """Temporary stub class. Ideally when stdin is accessed, the
+ capturing should be turned off, with possibly all data captured
+ so far sent to the screen. This should be configurable, though,
+ because in automated test runs it is better to crash than
+ hang indefinitely.
+ """
+
+ encoding = None
+
+ def read(self, *args):
+ raise IOError("reading from stdin while output is captured")
+
+ readline = read
+ readlines = read
+ __next__ = read
+
+ def __iter__(self):
+ return self
+
+ def fileno(self):
+ raise UnsupportedOperation("redirected stdin is pseudofile, has no fileno()")
+
+ def isatty(self):
+ return False
+
+ def close(self):
+ pass
+
+ @property
+ def buffer(self):
+ if sys.version_info >= (3, 0):
+ return self
+ else:
+ raise AttributeError("redirected stdin has no attribute buffer")
+
+
+def _colorama_workaround():
+ """
+ Ensure colorama is imported so that it attaches to the correct stdio
+ handles on Windows.
+
+ colorama uses the terminal on import time. So if something does the
+ first import of colorama while I/O capture is active, colorama will
+ fail in various ways.
+ """
if sys.platform.startswith("win32"):
try:
import colorama # noqa: F401
except ImportError:
pass
-
-
-def _readline_workaround():
- """
- Ensure readline is imported so that it attaches to the correct stdio
- handles on Windows.
-
- Pdb uses readline support where available--when not running from the Python
- prompt, the readline module is not imported until running the pdb REPL. If
- running pytest with the --pdb option this means the readline module is not
- imported until after I/O capture has been started.
-
- This is a problem for pyreadline, which is often used to implement readline
- support on Windows, as it does not attach to the correct handles for stdout
- and/or stdin if they have been redirected by the FDCapture mechanism. This
- workaround ensures that readline is imported before I/O capture is setup so
- that it can attach to the actual stdin/out for the console.
-
- See https://github.com/pytest-dev/pytest/pull/1281
- """
+
+
+def _readline_workaround():
+ """
+ Ensure readline is imported so that it attaches to the correct stdio
+ handles on Windows.
+
+ Pdb uses readline support where available--when not running from the Python
+ prompt, the readline module is not imported until running the pdb REPL. If
+ running pytest with the --pdb option this means the readline module is not
+ imported until after I/O capture has been started.
+
+ This is a problem for pyreadline, which is often used to implement readline
+ support on Windows, as it does not attach to the correct handles for stdout
+ and/or stdin if they have been redirected by the FDCapture mechanism. This
+ workaround ensures that readline is imported before I/O capture is setup so
+ that it can attach to the actual stdin/out for the console.
+
+ See https://github.com/pytest-dev/pytest/pull/1281
+ """
if sys.platform.startswith("win32"):
try:
import readline # noqa: F401
except ImportError:
pass
-
-
-def _py36_windowsconsoleio_workaround(stream):
- """
- Python 3.6 implemented unicode console handling for Windows. This works
- by reading/writing to the raw console handle using
- ``{Read,Write}ConsoleW``.
-
- The problem is that we are going to ``dup2`` over the stdio file
- descriptors when doing ``FDCapture`` and this will ``CloseHandle`` the
- handles used by Python to write to the console. Though there is still some
- weirdness and the console handle seems to only be closed randomly and not
- on the first call to ``CloseHandle``, or maybe it gets reopened with the
- same handle value when we suspend capturing.
-
- The workaround in this case will reopen stdio with a different fd which
- also means a different handle by replicating the logic in
- "Py_lifecycle.c:initstdio/create_stdio".
-
- :param stream: in practice ``sys.stdout`` or ``sys.stderr``, but given
- here as parameter for unittesting purposes.
-
- See https://github.com/pytest-dev/py/issues/103
- """
- if not sys.platform.startswith("win32") or sys.version_info[:2] < (3, 6):
- return
-
- # bail out if ``stream`` doesn't seem like a proper ``io`` stream (#2666)
- if not hasattr(stream, "buffer"):
- return
-
- buffered = hasattr(stream.buffer, "raw")
- raw_stdout = stream.buffer.raw if buffered else stream.buffer
-
- if not isinstance(raw_stdout, io._WindowsConsoleIO):
- return
-
- def _reopen_stdio(f, mode):
- if not buffered and mode[0] == "w":
- buffering = 0
- else:
- buffering = -1
-
- return io.TextIOWrapper(
- open(os.dup(f.fileno()), mode, buffering),
- f.encoding,
- f.errors,
- f.newlines,
- f.line_buffering,
- )
-
+
+
+def _py36_windowsconsoleio_workaround(stream):
+ """
+ Python 3.6 implemented unicode console handling for Windows. This works
+ by reading/writing to the raw console handle using
+ ``{Read,Write}ConsoleW``.
+
+ The problem is that we are going to ``dup2`` over the stdio file
+ descriptors when doing ``FDCapture`` and this will ``CloseHandle`` the
+ handles used by Python to write to the console. Though there is still some
+ weirdness and the console handle seems to only be closed randomly and not
+ on the first call to ``CloseHandle``, or maybe it gets reopened with the
+ same handle value when we suspend capturing.
+
+ The workaround in this case will reopen stdio with a different fd which
+ also means a different handle by replicating the logic in
+ "Py_lifecycle.c:initstdio/create_stdio".
+
+ :param stream: in practice ``sys.stdout`` or ``sys.stderr``, but given
+ here as parameter for unittesting purposes.
+
+ See https://github.com/pytest-dev/py/issues/103
+ """
+ if not sys.platform.startswith("win32") or sys.version_info[:2] < (3, 6):
+ return
+
+ # bail out if ``stream`` doesn't seem like a proper ``io`` stream (#2666)
+ if not hasattr(stream, "buffer"):
+ return
+
+ buffered = hasattr(stream.buffer, "raw")
+ raw_stdout = stream.buffer.raw if buffered else stream.buffer
+
+ if not isinstance(raw_stdout, io._WindowsConsoleIO):
+ return
+
+ def _reopen_stdio(f, mode):
+ if not buffered and mode[0] == "w":
+ buffering = 0
+ else:
+ buffering = -1
+
+ return io.TextIOWrapper(
+ open(os.dup(f.fileno()), mode, buffering),
+ f.encoding,
+ f.errors,
+ f.newlines,
+ f.line_buffering,
+ )
+
sys.stdin = _reopen_stdio(sys.stdin, "rb")
sys.stdout = _reopen_stdio(sys.stdout, "wb")
sys.stderr = _reopen_stdio(sys.stderr, "wb")
-
-
-def _attempt_to_close_capture_file(f):
- """Suppress IOError when closing the temporary file used for capturing streams in py27 (#2370)"""
- if six.PY2:
- try:
- f.close()
- except IOError:
- pass
- else:
- f.close()
+
+
+def _attempt_to_close_capture_file(f):
+ """Suppress IOError when closing the temporary file used for capturing streams in py27 (#2370)"""
+ if six.PY2:
+ try:
+ f.close()
+ except IOError:
+ pass
+ else:
+ f.close()
diff --git a/contrib/python/pytest/py2/_pytest/compat.py b/contrib/python/pytest/py2/_pytest/compat.py
index a4b30ad550..e5c3b84667 100644
--- a/contrib/python/pytest/py2/_pytest/compat.py
+++ b/contrib/python/pytest/py2/_pytest/compat.py
@@ -1,195 +1,195 @@
# -*- coding: utf-8 -*-
-"""
-python version compatibility code
-"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import codecs
-import functools
-import inspect
-import re
-import sys
-from contextlib import contextmanager
-
+"""
+python version compatibility code
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import codecs
+import functools
+import inspect
+import re
+import sys
+from contextlib import contextmanager
+
import attr
-import py
-import six
-from six import text_type
-
-import _pytest
+import py
+import six
+from six import text_type
+
+import _pytest
from _pytest._io.saferepr import saferepr
-from _pytest.outcomes import fail
-from _pytest.outcomes import TEST_OUTCOME
-
-try:
- import enum
-except ImportError: # pragma: no cover
- # Only available in Python 3.4+ or as a backport
- enum = None
-
-_PY3 = sys.version_info > (3, 0)
-_PY2 = not _PY3
-
-
-if _PY3:
- from inspect import signature, Parameter as Parameter
-else:
- from funcsigs import signature, Parameter as Parameter
-
-NOTSET = object()
-
-PY35 = sys.version_info[:2] >= (3, 5)
-PY36 = sys.version_info[:2] >= (3, 6)
-MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError"
-
-
-if _PY3:
- from collections.abc import MutableMapping as MappingMixin
+from _pytest.outcomes import fail
+from _pytest.outcomes import TEST_OUTCOME
+
+try:
+ import enum
+except ImportError: # pragma: no cover
+ # Only available in Python 3.4+ or as a backport
+ enum = None
+
+_PY3 = sys.version_info > (3, 0)
+_PY2 = not _PY3
+
+
+if _PY3:
+ from inspect import signature, Parameter as Parameter
+else:
+ from funcsigs import signature, Parameter as Parameter
+
+NOTSET = object()
+
+PY35 = sys.version_info[:2] >= (3, 5)
+PY36 = sys.version_info[:2] >= (3, 6)
+MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError"
+
+
+if _PY3:
+ from collections.abc import MutableMapping as MappingMixin
from collections.abc import Iterable, Mapping, Sequence, Sized
-else:
- # those raise DeprecationWarnings in Python >=3.7
- from collections import MutableMapping as MappingMixin # noqa
+else:
+ # those raise DeprecationWarnings in Python >=3.7
+ from collections import MutableMapping as MappingMixin # noqa
from collections import Iterable, Mapping, Sequence, Sized # noqa
-
-
-if sys.version_info >= (3, 4):
- from importlib.util import spec_from_file_location
-else:
-
- def spec_from_file_location(*_, **__):
- return None
-
-
+
+
+if sys.version_info >= (3, 4):
+ from importlib.util import spec_from_file_location
+else:
+
+ def spec_from_file_location(*_, **__):
+ return None
+
+
if sys.version_info >= (3, 8):
from importlib import metadata as importlib_metadata # noqa
else:
import importlib_metadata # noqa
-def _format_args(func):
- return str(signature(func))
-
-
-isfunction = inspect.isfunction
-isclass = inspect.isclass
-# used to work around a python2 exception info leak
-exc_clear = getattr(sys, "exc_clear", lambda: None)
-# The type of re.compile objects is not exposed in Python.
-REGEX_TYPE = type(re.compile(""))
-
-
-def is_generator(func):
- genfunc = inspect.isgeneratorfunction(func)
- return genfunc and not iscoroutinefunction(func)
-
-
-def iscoroutinefunction(func):
- """Return True if func is a decorated coroutine function.
-
- Note: copied and modified from Python 3.5's builtin couroutines.py to avoid import asyncio directly,
- which in turns also initializes the "logging" module as side-effect (see issue #8).
- """
- return getattr(func, "_is_coroutine", False) or (
- hasattr(inspect, "iscoroutinefunction") and inspect.iscoroutinefunction(func)
- )
-
-
-def getlocation(function, curdir):
- function = get_real_func(function)
- fn = py.path.local(inspect.getfile(function))
- lineno = function.__code__.co_firstlineno
- if fn.relto(curdir):
- fn = fn.relto(curdir)
- return "%s:%d" % (fn, lineno + 1)
-
-
-def num_mock_patch_args(function):
- """ return number of arguments used up by mock arguments (if any) """
- patchings = getattr(function, "patchings", None)
- if not patchings:
- return 0
- mock_modules = [sys.modules.get("mock"), sys.modules.get("unittest.mock")]
- if any(mock_modules):
- sentinels = [m.DEFAULT for m in mock_modules if m is not None]
- return len(
- [p for p in patchings if not p.attribute_name and p.new in sentinels]
- )
- return len(patchings)
-
-
-def getfuncargnames(function, is_method=False, cls=None):
- """Returns the names of a function's mandatory arguments.
-
- This should return the names of all function arguments that:
- * Aren't bound to an instance or type as in instance or class methods.
- * Don't have default values.
- * Aren't bound with functools.partial.
- * Aren't replaced with mocks.
-
- The is_method and cls arguments indicate that the function should
- be treated as a bound method even though it's not unless, only in
- the case of cls, the function is a static method.
-
- @RonnyPfannschmidt: This function should be refactored when we
- revisit fixtures. The fixture mechanism should ask the node for
- the fixture names, and not try to obtain directly from the
- function object well after collection has occurred.
-
- """
- # The parameters attribute of a Signature object contains an
- # ordered mapping of parameter names to Parameter instances. This
- # creates a tuple of the names of the parameters that don't have
- # defaults.
- try:
- parameters = signature(function).parameters
- except (ValueError, TypeError) as e:
- fail(
- "Could not determine arguments of {!r}: {}".format(function, e),
- pytrace=False,
- )
-
- arg_names = tuple(
- p.name
- for p in parameters.values()
- if (
- p.kind is Parameter.POSITIONAL_OR_KEYWORD
- or p.kind is Parameter.KEYWORD_ONLY
- )
- and p.default is Parameter.empty
- )
- # If this function should be treated as a bound method even though
- # it's passed as an unbound method or function, remove the first
- # parameter name.
- if is_method or (
- cls and not isinstance(cls.__dict__.get(function.__name__, None), staticmethod)
- ):
- arg_names = arg_names[1:]
- # Remove any names that will be replaced with mocks.
- if hasattr(function, "__wrapped__"):
- arg_names = arg_names[num_mock_patch_args(function) :]
- return arg_names
-
-
-@contextmanager
-def dummy_context_manager():
- """Context manager that does nothing, useful in situations where you might need an actual context manager or not
- depending on some condition. Using this allow to keep the same code"""
- yield
-
-
-def get_default_arg_names(function):
- # Note: this code intentionally mirrors the code at the beginning of getfuncargnames,
- # to get the arguments which were excluded from its result because they had default values
- return tuple(
- p.name
- for p in signature(function).parameters.values()
- if p.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY)
- and p.default is not Parameter.empty
- )
-
-
+def _format_args(func):
+ return str(signature(func))
+
+
+isfunction = inspect.isfunction
+isclass = inspect.isclass
+# used to work around a python2 exception info leak
+exc_clear = getattr(sys, "exc_clear", lambda: None)
+# The type of re.compile objects is not exposed in Python.
+REGEX_TYPE = type(re.compile(""))
+
+
+def is_generator(func):
+ genfunc = inspect.isgeneratorfunction(func)
+ return genfunc and not iscoroutinefunction(func)
+
+
+def iscoroutinefunction(func):
+ """Return True if func is a decorated coroutine function.
+
+ Note: copied and modified from Python 3.5's builtin couroutines.py to avoid import asyncio directly,
+ which in turns also initializes the "logging" module as side-effect (see issue #8).
+ """
+ return getattr(func, "_is_coroutine", False) or (
+ hasattr(inspect, "iscoroutinefunction") and inspect.iscoroutinefunction(func)
+ )
+
+
+def getlocation(function, curdir):
+ function = get_real_func(function)
+ fn = py.path.local(inspect.getfile(function))
+ lineno = function.__code__.co_firstlineno
+ if fn.relto(curdir):
+ fn = fn.relto(curdir)
+ return "%s:%d" % (fn, lineno + 1)
+
+
+def num_mock_patch_args(function):
+ """ return number of arguments used up by mock arguments (if any) """
+ patchings = getattr(function, "patchings", None)
+ if not patchings:
+ return 0
+ mock_modules = [sys.modules.get("mock"), sys.modules.get("unittest.mock")]
+ if any(mock_modules):
+ sentinels = [m.DEFAULT for m in mock_modules if m is not None]
+ return len(
+ [p for p in patchings if not p.attribute_name and p.new in sentinels]
+ )
+ return len(patchings)
+
+
+def getfuncargnames(function, is_method=False, cls=None):
+ """Returns the names of a function's mandatory arguments.
+
+ This should return the names of all function arguments that:
+ * Aren't bound to an instance or type as in instance or class methods.
+ * Don't have default values.
+ * Aren't bound with functools.partial.
+ * Aren't replaced with mocks.
+
+ The is_method and cls arguments indicate that the function should
+ be treated as a bound method even though it's not unless, only in
+ the case of cls, the function is a static method.
+
+ @RonnyPfannschmidt: This function should be refactored when we
+ revisit fixtures. The fixture mechanism should ask the node for
+ the fixture names, and not try to obtain directly from the
+ function object well after collection has occurred.
+
+ """
+ # The parameters attribute of a Signature object contains an
+ # ordered mapping of parameter names to Parameter instances. This
+ # creates a tuple of the names of the parameters that don't have
+ # defaults.
+ try:
+ parameters = signature(function).parameters
+ except (ValueError, TypeError) as e:
+ fail(
+ "Could not determine arguments of {!r}: {}".format(function, e),
+ pytrace=False,
+ )
+
+ arg_names = tuple(
+ p.name
+ for p in parameters.values()
+ if (
+ p.kind is Parameter.POSITIONAL_OR_KEYWORD
+ or p.kind is Parameter.KEYWORD_ONLY
+ )
+ and p.default is Parameter.empty
+ )
+ # If this function should be treated as a bound method even though
+ # it's passed as an unbound method or function, remove the first
+ # parameter name.
+ if is_method or (
+ cls and not isinstance(cls.__dict__.get(function.__name__, None), staticmethod)
+ ):
+ arg_names = arg_names[1:]
+ # Remove any names that will be replaced with mocks.
+ if hasattr(function, "__wrapped__"):
+ arg_names = arg_names[num_mock_patch_args(function) :]
+ return arg_names
+
+
+@contextmanager
+def dummy_context_manager():
+ """Context manager that does nothing, useful in situations where you might need an actual context manager or not
+ depending on some condition. Using this allow to keep the same code"""
+ yield
+
+
+def get_default_arg_names(function):
+ # Note: this code intentionally mirrors the code at the beginning of getfuncargnames,
+ # to get the arguments which were excluded from its result because they had default values
+ return tuple(
+ p.name
+ for p in signature(function).parameters.values()
+ if p.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY)
+ and p.default is not Parameter.empty
+ )
+
+
_non_printable_ascii_translate_table = {
i: u"\\x{:02x}".format(i) for i in range(128) if i not in range(32, 127)
}
@@ -202,269 +202,269 @@ def _translate_non_printable(s):
return s.translate(_non_printable_ascii_translate_table)
-if _PY3:
- STRING_TYPES = bytes, str
- UNICODE_TYPES = six.text_type
-
- if PY35:
-
- def _bytes_to_ascii(val):
- return val.decode("ascii", "backslashreplace")
-
- else:
-
- def _bytes_to_ascii(val):
- if val:
- # source: http://goo.gl/bGsnwC
- encoded_bytes, _ = codecs.escape_encode(val)
- return encoded_bytes.decode("ascii")
- else:
- # empty bytes crashes codecs.escape_encode (#1087)
- return ""
-
- def ascii_escaped(val):
- """If val is pure ascii, returns it as a str(). Otherwise, escapes
- bytes objects into a sequence of escaped bytes:
-
- b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6'
-
- and escapes unicode objects into a sequence of escaped unicode
- ids, e.g.:
-
- '4\\nV\\U00043efa\\x0eMXWB\\x1e\\u3028\\u15fd\\xcd\\U0007d944'
-
- note:
- the obvious "v.decode('unicode-escape')" will return
- valid utf-8 unicode if it finds them in bytes, but we
- want to return escaped bytes for any byte, even if they match
- a utf-8 string.
-
- """
- if isinstance(val, bytes):
+if _PY3:
+ STRING_TYPES = bytes, str
+ UNICODE_TYPES = six.text_type
+
+ if PY35:
+
+ def _bytes_to_ascii(val):
+ return val.decode("ascii", "backslashreplace")
+
+ else:
+
+ def _bytes_to_ascii(val):
+ if val:
+ # source: http://goo.gl/bGsnwC
+ encoded_bytes, _ = codecs.escape_encode(val)
+ return encoded_bytes.decode("ascii")
+ else:
+ # empty bytes crashes codecs.escape_encode (#1087)
+ return ""
+
+ def ascii_escaped(val):
+ """If val is pure ascii, returns it as a str(). Otherwise, escapes
+ bytes objects into a sequence of escaped bytes:
+
+ b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6'
+
+ and escapes unicode objects into a sequence of escaped unicode
+ ids, e.g.:
+
+ '4\\nV\\U00043efa\\x0eMXWB\\x1e\\u3028\\u15fd\\xcd\\U0007d944'
+
+ note:
+ the obvious "v.decode('unicode-escape')" will return
+ valid utf-8 unicode if it finds them in bytes, but we
+ want to return escaped bytes for any byte, even if they match
+ a utf-8 string.
+
+ """
+ if isinstance(val, bytes):
ret = _bytes_to_ascii(val)
- else:
+ else:
ret = val
return ret
-
-
-else:
- STRING_TYPES = six.string_types
- UNICODE_TYPES = six.text_type
-
- def ascii_escaped(val):
- """In py2 bytes and str are the same type, so return if it's a bytes
- object, return it unchanged if it is a full ascii string,
- otherwise escape it into its binary form.
-
- If it's a unicode string, change the unicode characters into
- unicode escapes.
-
- """
- if isinstance(val, bytes):
- try:
+
+
+else:
+ STRING_TYPES = six.string_types
+ UNICODE_TYPES = six.text_type
+
+ def ascii_escaped(val):
+ """In py2 bytes and str are the same type, so return if it's a bytes
+ object, return it unchanged if it is a full ascii string,
+ otherwise escape it into its binary form.
+
+ If it's a unicode string, change the unicode characters into
+ unicode escapes.
+
+ """
+ if isinstance(val, bytes):
+ try:
ret = val.decode("utf-8")
- except UnicodeDecodeError:
+ except UnicodeDecodeError:
ret = val.decode("utf-8", "ignore")
- else:
+ else:
ret = val.encode("utf-8", "replace").decode("utf-8")
return ret
-
-
-class _PytestWrapper(object):
- """Dummy wrapper around a function object for internal use only.
-
- Used to correctly unwrap the underlying function object
- when we are creating fixtures, because we wrap the function object ourselves with a decorator
- to issue warnings when the fixture function is called directly.
- """
-
- def __init__(self, obj):
- self.obj = obj
-
-
-def get_real_func(obj):
- """ gets the real function object of the (possibly) wrapped object by
- functools.wraps or functools.partial.
- """
- start_obj = obj
- for i in range(100):
- # __pytest_wrapped__ is set by @pytest.fixture when wrapping the fixture function
- # to trigger a warning if it gets called directly instead of by pytest: we don't
- # want to unwrap further than this otherwise we lose useful wrappings like @mock.patch (#3774)
- new_obj = getattr(obj, "__pytest_wrapped__", None)
- if isinstance(new_obj, _PytestWrapper):
- obj = new_obj.obj
- break
- new_obj = getattr(obj, "__wrapped__", None)
- if new_obj is None:
- break
- obj = new_obj
- else:
- raise ValueError(
- ("could not find real function of {start}\nstopped at {current}").format(
+
+
+class _PytestWrapper(object):
+ """Dummy wrapper around a function object for internal use only.
+
+ Used to correctly unwrap the underlying function object
+ when we are creating fixtures, because we wrap the function object ourselves with a decorator
+ to issue warnings when the fixture function is called directly.
+ """
+
+ def __init__(self, obj):
+ self.obj = obj
+
+
+def get_real_func(obj):
+ """ gets the real function object of the (possibly) wrapped object by
+ functools.wraps or functools.partial.
+ """
+ start_obj = obj
+ for i in range(100):
+ # __pytest_wrapped__ is set by @pytest.fixture when wrapping the fixture function
+ # to trigger a warning if it gets called directly instead of by pytest: we don't
+ # want to unwrap further than this otherwise we lose useful wrappings like @mock.patch (#3774)
+ new_obj = getattr(obj, "__pytest_wrapped__", None)
+ if isinstance(new_obj, _PytestWrapper):
+ obj = new_obj.obj
+ break
+ new_obj = getattr(obj, "__wrapped__", None)
+ if new_obj is None:
+ break
+ obj = new_obj
+ else:
+ raise ValueError(
+ ("could not find real function of {start}\nstopped at {current}").format(
start=saferepr(start_obj), current=saferepr(obj)
- )
- )
- if isinstance(obj, functools.partial):
- obj = obj.func
- return obj
-
-
-def get_real_method(obj, holder):
- """
- Attempts to obtain the real function object that might be wrapping ``obj``, while at the same time
- returning a bound method to ``holder`` if the original object was a bound method.
- """
- try:
- is_method = hasattr(obj, "__func__")
- obj = get_real_func(obj)
- except Exception:
- return obj
- if is_method and hasattr(obj, "__get__") and callable(obj.__get__):
- obj = obj.__get__(holder)
- return obj
-
-
-def getfslineno(obj):
- # xxx let decorators etc specify a sane ordering
- obj = get_real_func(obj)
- if hasattr(obj, "place_as"):
- obj = obj.place_as
- fslineno = _pytest._code.getfslineno(obj)
- assert isinstance(fslineno[1], int), obj
- return fslineno
-
-
-def getimfunc(func):
- try:
- return func.__func__
- except AttributeError:
- return func
-
-
-def safe_getattr(object, name, default):
- """ Like getattr but return default upon any Exception or any OutcomeException.
-
- Attribute access can potentially fail for 'evil' Python objects.
- See issue #214.
- It catches OutcomeException because of #2490 (issue #580), new outcomes are derived from BaseException
- instead of Exception (for more details check #2707)
- """
- try:
- return getattr(object, name, default)
- except TEST_OUTCOME:
- return default
-
-
-def safe_isclass(obj):
- """Ignore any exception via isinstance on Python 3."""
- try:
- return isclass(obj)
- except Exception:
- return False
-
-
-def _is_unittest_unexpected_success_a_failure():
- """Return if the test suite should fail if an @expectedFailure unittest test PASSES.
-
+ )
+ )
+ if isinstance(obj, functools.partial):
+ obj = obj.func
+ return obj
+
+
+def get_real_method(obj, holder):
+ """
+ Attempts to obtain the real function object that might be wrapping ``obj``, while at the same time
+ returning a bound method to ``holder`` if the original object was a bound method.
+ """
+ try:
+ is_method = hasattr(obj, "__func__")
+ obj = get_real_func(obj)
+ except Exception:
+ return obj
+ if is_method and hasattr(obj, "__get__") and callable(obj.__get__):
+ obj = obj.__get__(holder)
+ return obj
+
+
+def getfslineno(obj):
+ # xxx let decorators etc specify a sane ordering
+ obj = get_real_func(obj)
+ if hasattr(obj, "place_as"):
+ obj = obj.place_as
+ fslineno = _pytest._code.getfslineno(obj)
+ assert isinstance(fslineno[1], int), obj
+ return fslineno
+
+
+def getimfunc(func):
+ try:
+ return func.__func__
+ except AttributeError:
+ return func
+
+
+def safe_getattr(object, name, default):
+ """ Like getattr but return default upon any Exception or any OutcomeException.
+
+ Attribute access can potentially fail for 'evil' Python objects.
+ See issue #214.
+ It catches OutcomeException because of #2490 (issue #580), new outcomes are derived from BaseException
+ instead of Exception (for more details check #2707)
+ """
+ try:
+ return getattr(object, name, default)
+ except TEST_OUTCOME:
+ return default
+
+
+def safe_isclass(obj):
+ """Ignore any exception via isinstance on Python 3."""
+ try:
+ return isclass(obj)
+ except Exception:
+ return False
+
+
+def _is_unittest_unexpected_success_a_failure():
+ """Return if the test suite should fail if an @expectedFailure unittest test PASSES.
+
From https://docs.python.org/3/library/unittest.html?highlight=unittest#unittest.TestResult.wasSuccessful:
- Changed in version 3.4: Returns False if there were any
- unexpectedSuccesses from tests marked with the expectedFailure() decorator.
- """
- return sys.version_info >= (3, 4)
-
-
-if _PY3:
-
- def safe_str(v):
- """returns v as string"""
+ Changed in version 3.4: Returns False if there were any
+ unexpectedSuccesses from tests marked with the expectedFailure() decorator.
+ """
+ return sys.version_info >= (3, 4)
+
+
+if _PY3:
+
+ def safe_str(v):
+ """returns v as string"""
try:
return str(v)
except UnicodeEncodeError:
return str(v, encoding="utf-8")
-
-
-else:
-
- def safe_str(v):
+
+
+else:
+
+ def safe_str(v):
"""returns v as string, converting to utf-8 if necessary"""
- try:
- return str(v)
- except UnicodeError:
- if not isinstance(v, text_type):
- v = text_type(v)
- errors = "replace"
- return v.encode("utf-8", errors)
-
-
-COLLECT_FAKEMODULE_ATTRIBUTES = (
- "Collector",
- "Module",
- "Function",
- "Instance",
- "Session",
- "Item",
- "Class",
- "File",
- "_fillfuncargs",
-)
-
-
-def _setup_collect_fakemodule():
- from types import ModuleType
- import pytest
-
- pytest.collect = ModuleType("pytest.collect")
- pytest.collect.__all__ = [] # used for setns
+ try:
+ return str(v)
+ except UnicodeError:
+ if not isinstance(v, text_type):
+ v = text_type(v)
+ errors = "replace"
+ return v.encode("utf-8", errors)
+
+
+COLLECT_FAKEMODULE_ATTRIBUTES = (
+ "Collector",
+ "Module",
+ "Function",
+ "Instance",
+ "Session",
+ "Item",
+ "Class",
+ "File",
+ "_fillfuncargs",
+)
+
+
+def _setup_collect_fakemodule():
+ from types import ModuleType
+ import pytest
+
+ pytest.collect = ModuleType("pytest.collect")
+ pytest.collect.__all__ = [] # used for setns
for attribute in COLLECT_FAKEMODULE_ATTRIBUTES:
setattr(pytest.collect, attribute, getattr(pytest, attribute))
-
-
-if _PY2:
- # Without this the test_dupfile_on_textio will fail, otherwise CaptureIO could directly inherit from StringIO.
- from py.io import TextIO
-
- class CaptureIO(TextIO):
- @property
- def encoding(self):
- return getattr(self, "_encoding", "UTF-8")
-
-
-else:
- import io
-
- class CaptureIO(io.TextIOWrapper):
- def __init__(self):
- super(CaptureIO, self).__init__(
- io.BytesIO(), encoding="UTF-8", newline="", write_through=True
- )
-
- def getvalue(self):
- return self.buffer.getvalue().decode("UTF-8")
-
-
-class FuncargnamesCompatAttr(object):
- """ helper class so that Metafunc, Function and FixtureRequest
- don't need to each define the "funcargnames" compatibility attribute.
- """
-
- @property
- def funcargnames(self):
- """ alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
- return self.fixturenames
-
-
-if six.PY2:
-
- def lru_cache(*_, **__):
- def dec(fn):
- return fn
-
- return dec
-
-
-else:
- from functools import lru_cache # noqa: F401
+
+
+if _PY2:
+ # Without this the test_dupfile_on_textio will fail, otherwise CaptureIO could directly inherit from StringIO.
+ from py.io import TextIO
+
+ class CaptureIO(TextIO):
+ @property
+ def encoding(self):
+ return getattr(self, "_encoding", "UTF-8")
+
+
+else:
+ import io
+
+ class CaptureIO(io.TextIOWrapper):
+ def __init__(self):
+ super(CaptureIO, self).__init__(
+ io.BytesIO(), encoding="UTF-8", newline="", write_through=True
+ )
+
+ def getvalue(self):
+ return self.buffer.getvalue().decode("UTF-8")
+
+
+class FuncargnamesCompatAttr(object):
+ """ helper class so that Metafunc, Function and FixtureRequest
+ don't need to each define the "funcargnames" compatibility attribute.
+ """
+
+ @property
+ def funcargnames(self):
+ """ alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
+ return self.fixturenames
+
+
+if six.PY2:
+
+ def lru_cache(*_, **__):
+ def dec(fn):
+ return fn
+
+ return dec
+
+
+else:
+ from functools import lru_cache # noqa: F401
if getattr(attr, "__version_info__", ()) >= (19, 2):
diff --git a/contrib/python/pytest/py2/_pytest/config/__init__.py b/contrib/python/pytest/py2/_pytest/config/__init__.py
index 52acbccf7b..0737ff9d51 100644
--- a/contrib/python/pytest/py2/_pytest/config/__init__.py
+++ b/contrib/python/pytest/py2/_pytest/config/__init__.py
@@ -1,164 +1,164 @@
# -*- coding: utf-8 -*-
-""" command line options, ini-file and conftest.py processing. """
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import argparse
-import copy
-import inspect
-import os
-import shlex
-import sys
-import types
-import warnings
-
+""" command line options, ini-file and conftest.py processing. """
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import argparse
+import copy
+import inspect
+import os
+import shlex
+import sys
+import types
+import warnings
+
import attr
-import py
-import six
+import py
+import six
from packaging.version import Version
-from pluggy import HookimplMarker
-from pluggy import HookspecMarker
-from pluggy import PluginManager
-
-import _pytest._code
-import _pytest.assertion
-import _pytest.hookspec # the extension point definitions
-from .exceptions import PrintHelp
-from .exceptions import UsageError
-from .findpaths import determine_setup
-from .findpaths import exists
+from pluggy import HookimplMarker
+from pluggy import HookspecMarker
+from pluggy import PluginManager
+
+import _pytest._code
+import _pytest.assertion
+import _pytest.hookspec # the extension point definitions
+from .exceptions import PrintHelp
+from .exceptions import UsageError
+from .findpaths import determine_setup
+from .findpaths import exists
from _pytest import deprecated
-from _pytest._code import ExceptionInfo
-from _pytest._code import filter_traceback
+from _pytest._code import ExceptionInfo
+from _pytest._code import filter_traceback
from _pytest.compat import importlib_metadata
-from _pytest.compat import lru_cache
-from _pytest.compat import safe_str
+from _pytest.compat import lru_cache
+from _pytest.compat import safe_str
from _pytest.outcomes import fail
-from _pytest.outcomes import Skipped
+from _pytest.outcomes import Skipped
from _pytest.pathlib import Path
from _pytest.warning_types import PytestConfigWarning
-
-hookimpl = HookimplMarker("pytest")
-hookspec = HookspecMarker("pytest")
-
-
-class ConftestImportFailure(Exception):
- def __init__(self, path, excinfo):
- Exception.__init__(self, path, excinfo)
- self.path = path
- self.excinfo = excinfo
-
-
-def main(args=None, plugins=None):
- """ return exit code, after performing an in-process test run.
-
- :arg args: list of command line arguments.
-
- :arg plugins: list of plugin objects to be auto-registered during
- initialization.
- """
- from _pytest.main import EXIT_USAGEERROR
-
- try:
- try:
- config = _prepareconfig(args, plugins)
- except ConftestImportFailure as e:
- exc_info = ExceptionInfo(e.excinfo)
- tw = py.io.TerminalWriter(sys.stderr)
- tw.line(
- "ImportError while loading conftest '{e.path}'.".format(e=e), red=True
- )
- exc_info.traceback = exc_info.traceback.filter(filter_traceback)
- exc_repr = (
- exc_info.getrepr(style="short", chain=False)
- if exc_info.traceback
- else exc_info.exconly()
- )
- formatted_tb = safe_str(exc_repr)
- for line in formatted_tb.splitlines():
- tw.line(line.rstrip(), red=True)
- return 4
- else:
- try:
- return config.hook.pytest_cmdline_main(config=config)
- finally:
- config._ensure_unconfigure()
- except UsageError as e:
- tw = py.io.TerminalWriter(sys.stderr)
- for msg in e.args:
- tw.line("ERROR: {}\n".format(msg), red=True)
- return EXIT_USAGEERROR
-
-
-class cmdline(object): # compatibility namespace
- main = staticmethod(main)
-
-
-def filename_arg(path, optname):
- """ Argparse type validator for filename arguments.
-
- :path: path of filename
- :optname: name of the option
- """
- if os.path.isdir(path):
- raise UsageError("{} must be a filename, given: {}".format(optname, path))
- return path
-
-
-def directory_arg(path, optname):
- """Argparse type validator for directory arguments.
-
- :path: path of directory
- :optname: name of the option
- """
- if not os.path.isdir(path):
- raise UsageError("{} must be a directory, given: {}".format(optname, path))
- return path
-
-
+
+hookimpl = HookimplMarker("pytest")
+hookspec = HookspecMarker("pytest")
+
+
+class ConftestImportFailure(Exception):
+ def __init__(self, path, excinfo):
+ Exception.__init__(self, path, excinfo)
+ self.path = path
+ self.excinfo = excinfo
+
+
+def main(args=None, plugins=None):
+ """ return exit code, after performing an in-process test run.
+
+ :arg args: list of command line arguments.
+
+ :arg plugins: list of plugin objects to be auto-registered during
+ initialization.
+ """
+ from _pytest.main import EXIT_USAGEERROR
+
+ try:
+ try:
+ config = _prepareconfig(args, plugins)
+ except ConftestImportFailure as e:
+ exc_info = ExceptionInfo(e.excinfo)
+ tw = py.io.TerminalWriter(sys.stderr)
+ tw.line(
+ "ImportError while loading conftest '{e.path}'.".format(e=e), red=True
+ )
+ exc_info.traceback = exc_info.traceback.filter(filter_traceback)
+ exc_repr = (
+ exc_info.getrepr(style="short", chain=False)
+ if exc_info.traceback
+ else exc_info.exconly()
+ )
+ formatted_tb = safe_str(exc_repr)
+ for line in formatted_tb.splitlines():
+ tw.line(line.rstrip(), red=True)
+ return 4
+ else:
+ try:
+ return config.hook.pytest_cmdline_main(config=config)
+ finally:
+ config._ensure_unconfigure()
+ except UsageError as e:
+ tw = py.io.TerminalWriter(sys.stderr)
+ for msg in e.args:
+ tw.line("ERROR: {}\n".format(msg), red=True)
+ return EXIT_USAGEERROR
+
+
+class cmdline(object): # compatibility namespace
+ main = staticmethod(main)
+
+
+def filename_arg(path, optname):
+ """ Argparse type validator for filename arguments.
+
+ :path: path of filename
+ :optname: name of the option
+ """
+ if os.path.isdir(path):
+ raise UsageError("{} must be a filename, given: {}".format(optname, path))
+ return path
+
+
+def directory_arg(path, optname):
+ """Argparse type validator for directory arguments.
+
+ :path: path of directory
+ :optname: name of the option
+ """
+ if not os.path.isdir(path):
+ raise UsageError("{} must be a directory, given: {}".format(optname, path))
+ return path
+
+
# Plugins that cannot be disabled via "-p no:X" currently.
essential_plugins = (
- "mark",
- "main",
- "runner",
+ "mark",
+ "main",
+ "runner",
"fixtures",
"helpconfig", # Provides -p.
)
default_plugins = essential_plugins + (
- "python",
+ "python",
"terminal",
- "debugging",
- "unittest",
- "capture",
- "skipping",
- "tmpdir",
- "monkeypatch",
- "recwarn",
- "pastebin",
- "nose",
- "assertion",
- "junitxml",
- "resultlog",
- "doctest",
- "cacheprovider",
- "freeze_support",
- "setuponly",
- "setupplan",
- "stepwise",
- "warnings",
- "logging",
+ "debugging",
+ "unittest",
+ "capture",
+ "skipping",
+ "tmpdir",
+ "monkeypatch",
+ "recwarn",
+ "pastebin",
+ "nose",
+ "assertion",
+ "junitxml",
+ "resultlog",
+ "doctest",
+ "cacheprovider",
+ "freeze_support",
+ "setuponly",
+ "setupplan",
+ "stepwise",
+ "warnings",
+ "logging",
"reports",
-)
-
-builtin_plugins = set(default_plugins)
-builtin_plugins.add("pytester")
-
-
+)
+
+builtin_plugins = set(default_plugins)
+builtin_plugins.add("pytester")
+
+
def get_config(args=None, plugins=None):
- # subsequent calls to main will create a fresh instance
- pluginmanager = PytestPluginManager()
+ # subsequent calls to main will create a fresh instance
+ pluginmanager = PytestPluginManager()
config = Config(
pluginmanager,
invocation_params=Config.InvocationParams(
@@ -170,323 +170,323 @@ def get_config(args=None, plugins=None):
# Handle any "-p no:plugin" args.
pluginmanager.consider_preparse(args)
- for spec in default_plugins:
- pluginmanager.import_plugin(spec)
- return config
-
-
-def get_plugin_manager():
- """
- Obtain a new instance of the
- :py:class:`_pytest.config.PytestPluginManager`, with default plugins
- already loaded.
-
- This function can be used by integration with other tools, like hooking
- into pytest to run tests into an IDE.
- """
- return get_config().pluginmanager
-
-
-def _prepareconfig(args=None, plugins=None):
- warning = None
- if args is None:
- args = sys.argv[1:]
- elif isinstance(args, py.path.local):
- args = [str(args)]
- elif not isinstance(args, (tuple, list)):
+ for spec in default_plugins:
+ pluginmanager.import_plugin(spec)
+ return config
+
+
+def get_plugin_manager():
+ """
+ Obtain a new instance of the
+ :py:class:`_pytest.config.PytestPluginManager`, with default plugins
+ already loaded.
+
+ This function can be used by integration with other tools, like hooking
+ into pytest to run tests into an IDE.
+ """
+ return get_config().pluginmanager
+
+
+def _prepareconfig(args=None, plugins=None):
+ warning = None
+ if args is None:
+ args = sys.argv[1:]
+ elif isinstance(args, py.path.local):
+ args = [str(args)]
+ elif not isinstance(args, (tuple, list)):
msg = "`args` parameter expected to be a list or tuple of strings, got: {!r} (type: {})"
raise TypeError(msg.format(args, type(args)))
-
+
config = get_config(args, plugins)
- pluginmanager = config.pluginmanager
- try:
- if plugins:
- for plugin in plugins:
- if isinstance(plugin, six.string_types):
- pluginmanager.consider_pluginarg(plugin)
- else:
- pluginmanager.register(plugin)
- if warning:
+ pluginmanager = config.pluginmanager
+ try:
+ if plugins:
+ for plugin in plugins:
+ if isinstance(plugin, six.string_types):
+ pluginmanager.consider_pluginarg(plugin)
+ else:
+ pluginmanager.register(plugin)
+ if warning:
from _pytest.warnings import _issue_warning_captured
-
+
_issue_warning_captured(warning, hook=config.hook, stacklevel=4)
- return pluginmanager.hook.pytest_cmdline_parse(
- pluginmanager=pluginmanager, args=args
- )
- except BaseException:
- config._ensure_unconfigure()
- raise
-
-
-class PytestPluginManager(PluginManager):
- """
- Overwrites :py:class:`pluggy.PluginManager <pluggy.PluginManager>` to add pytest-specific
- functionality:
-
- * loading plugins from the command line, ``PYTEST_PLUGINS`` env variable and
- ``pytest_plugins`` global variables found in plugins being loaded;
- * ``conftest.py`` loading during start-up;
- """
-
- def __init__(self):
- super(PytestPluginManager, self).__init__("pytest")
- self._conftest_plugins = set()
-
- # state related to local conftest plugins
- self._dirpath2confmods = {}
- self._conftestpath2mod = {}
- self._confcutdir = None
- self._noconftest = False
- self._duplicatepaths = set()
-
- self.add_hookspecs(_pytest.hookspec)
- self.register(self)
- if os.environ.get("PYTEST_DEBUG"):
- err = sys.stderr
- encoding = getattr(err, "encoding", "utf8")
- try:
- err = py.io.dupfile(err, encoding=encoding)
- except Exception:
- pass
- self.trace.root.setwriter(err.write)
- self.enable_tracing()
-
- # Config._consider_importhook will set a real object if required.
- self.rewrite_hook = _pytest.assertion.DummyRewriteHook()
- # Used to know when we are importing conftests after the pytest_configure stage
- self._configured = False
-
- def addhooks(self, module_or_class):
- """
- .. deprecated:: 2.8
-
- Use :py:meth:`pluggy.PluginManager.add_hookspecs <PluginManager.add_hookspecs>`
- instead.
- """
+ return pluginmanager.hook.pytest_cmdline_parse(
+ pluginmanager=pluginmanager, args=args
+ )
+ except BaseException:
+ config._ensure_unconfigure()
+ raise
+
+
+class PytestPluginManager(PluginManager):
+ """
+ Overwrites :py:class:`pluggy.PluginManager <pluggy.PluginManager>` to add pytest-specific
+ functionality:
+
+ * loading plugins from the command line, ``PYTEST_PLUGINS`` env variable and
+ ``pytest_plugins`` global variables found in plugins being loaded;
+ * ``conftest.py`` loading during start-up;
+ """
+
+ def __init__(self):
+ super(PytestPluginManager, self).__init__("pytest")
+ self._conftest_plugins = set()
+
+ # state related to local conftest plugins
+ self._dirpath2confmods = {}
+ self._conftestpath2mod = {}
+ self._confcutdir = None
+ self._noconftest = False
+ self._duplicatepaths = set()
+
+ self.add_hookspecs(_pytest.hookspec)
+ self.register(self)
+ if os.environ.get("PYTEST_DEBUG"):
+ err = sys.stderr
+ encoding = getattr(err, "encoding", "utf8")
+ try:
+ err = py.io.dupfile(err, encoding=encoding)
+ except Exception:
+ pass
+ self.trace.root.setwriter(err.write)
+ self.enable_tracing()
+
+ # Config._consider_importhook will set a real object if required.
+ self.rewrite_hook = _pytest.assertion.DummyRewriteHook()
+ # Used to know when we are importing conftests after the pytest_configure stage
+ self._configured = False
+
+ def addhooks(self, module_or_class):
+ """
+ .. deprecated:: 2.8
+
+ Use :py:meth:`pluggy.PluginManager.add_hookspecs <PluginManager.add_hookspecs>`
+ instead.
+ """
warnings.warn(deprecated.PLUGIN_MANAGER_ADDHOOKS, stacklevel=2)
- return self.add_hookspecs(module_or_class)
-
- def parse_hookimpl_opts(self, plugin, name):
- # pytest hooks are always prefixed with pytest_
- # so we avoid accessing possibly non-readable attributes
- # (see issue #1073)
- if not name.startswith("pytest_"):
- return
+ return self.add_hookspecs(module_or_class)
+
+ def parse_hookimpl_opts(self, plugin, name):
+ # pytest hooks are always prefixed with pytest_
+ # so we avoid accessing possibly non-readable attributes
+ # (see issue #1073)
+ if not name.startswith("pytest_"):
+ return
# ignore names which can not be hooks
if name == "pytest_plugins":
- return
-
- method = getattr(plugin, name)
- opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name)
-
- # consider only actual functions for hooks (#3775)
- if not inspect.isroutine(method):
- return
-
- # collect unmarked hooks as long as they have the `pytest_' prefix
- if opts is None and name.startswith("pytest_"):
- opts = {}
+ return
+
+ method = getattr(plugin, name)
+ opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name)
+
+ # consider only actual functions for hooks (#3775)
+ if not inspect.isroutine(method):
+ return
+
+ # collect unmarked hooks as long as they have the `pytest_' prefix
+ if opts is None and name.startswith("pytest_"):
+ opts = {}
if opts is not None:
# TODO: DeprecationWarning, people should use hookimpl
# https://github.com/pytest-dev/pytest/issues/4562
known_marks = {m.name for m in getattr(method, "pytestmark", [])}
-
- for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"):
+
+ for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"):
opts.setdefault(name, hasattr(method, name) or name in known_marks)
- return opts
-
- def parse_hookspec_opts(self, module_or_class, name):
- opts = super(PytestPluginManager, self).parse_hookspec_opts(
- module_or_class, name
- )
- if opts is None:
- method = getattr(module_or_class, name)
-
- if name.startswith("pytest_"):
+ return opts
+
+ def parse_hookspec_opts(self, module_or_class, name):
+ opts = super(PytestPluginManager, self).parse_hookspec_opts(
+ module_or_class, name
+ )
+ if opts is None:
+ method = getattr(module_or_class, name)
+
+ if name.startswith("pytest_"):
# todo: deprecate hookspec hacks
# https://github.com/pytest-dev/pytest/issues/4562
known_marks = {m.name for m in getattr(method, "pytestmark", [])}
- opts = {
+ opts = {
"firstresult": hasattr(method, "firstresult")
or "firstresult" in known_marks,
"historic": hasattr(method, "historic")
or "historic" in known_marks,
- }
- return opts
-
- def register(self, plugin, name=None):
- if name in ["pytest_catchlog", "pytest_capturelog"]:
+ }
+ return opts
+
+ def register(self, plugin, name=None):
+ if name in ["pytest_catchlog", "pytest_capturelog"]:
warnings.warn(
PytestConfigWarning(
"{} plugin has been merged into the core, "
"please remove it from your requirements.".format(
name.replace("_", "-")
)
- )
- )
- return
- ret = super(PytestPluginManager, self).register(plugin, name)
- if ret:
- self.hook.pytest_plugin_registered.call_historic(
- kwargs=dict(plugin=plugin, manager=self)
- )
-
- if isinstance(plugin, types.ModuleType):
- self.consider_module(plugin)
- return ret
-
- def getplugin(self, name):
- # support deprecated naming because plugins (xdist e.g.) use it
- return self.get_plugin(name)
-
- def hasplugin(self, name):
- """Return True if the plugin with the given name is registered."""
- return bool(self.get_plugin(name))
-
- def pytest_configure(self, config):
- # XXX now that the pluginmanager exposes hookimpl(tryfirst...)
- # we should remove tryfirst/trylast as markers
- config.addinivalue_line(
- "markers",
- "tryfirst: mark a hook implementation function such that the "
- "plugin machinery will try to call it first/as early as possible.",
- )
- config.addinivalue_line(
- "markers",
- "trylast: mark a hook implementation function such that the "
- "plugin machinery will try to call it last/as late as possible.",
- )
- self._configured = True
-
- #
- # internal API for local conftest plugin handling
- #
- def _set_initial_conftests(self, namespace):
- """ load initial conftest files given a preparsed "namespace".
- As conftest files may add their own command line options
- which have arguments ('--my-opt somepath') we might get some
- false positives. All builtin and 3rd party plugins will have
- been loaded, however, so common options will not confuse our logic
- here.
- """
- current = py.path.local()
- self._confcutdir = (
- current.join(namespace.confcutdir, abs=True)
- if namespace.confcutdir
- else None
- )
- self._noconftest = namespace.noconftest
- self._using_pyargs = namespace.pyargs
- testpaths = namespace.file_or_dir
- foundanchor = False
- for path in testpaths:
- path = str(path)
- # remove node-id syntax
- i = path.find("::")
- if i != -1:
- path = path[:i]
- anchor = current.join(path, abs=1)
- if exists(anchor): # we found some file object
- self._try_load_conftest(anchor)
- foundanchor = True
- if not foundanchor:
- self._try_load_conftest(current)
-
- def _try_load_conftest(self, anchor):
- self._getconftestmodules(anchor)
- # let's also consider test* subdirs
- if anchor.check(dir=1):
- for x in anchor.listdir("test*"):
- if x.check(dir=1):
- self._getconftestmodules(x)
-
- @lru_cache(maxsize=128)
- def _getconftestmodules(self, path):
- if self._noconftest:
- return []
-
- if path.isfile():
- directory = path.dirpath()
- else:
- directory = path
-
- if six.PY2: # py2 is not using lru_cache.
- try:
- return self._dirpath2confmods[directory]
- except KeyError:
- pass
-
- # XXX these days we may rather want to use config.rootdir
- # and allow users to opt into looking into the rootdir parent
- # directories instead of requiring to specify confcutdir
- clist = []
- for parent in directory.realpath().parts():
- if self._confcutdir and self._confcutdir.relto(parent):
- continue
- conftestpath = parent.join("conftest.py")
- if conftestpath.isfile():
+ )
+ )
+ return
+ ret = super(PytestPluginManager, self).register(plugin, name)
+ if ret:
+ self.hook.pytest_plugin_registered.call_historic(
+ kwargs=dict(plugin=plugin, manager=self)
+ )
+
+ if isinstance(plugin, types.ModuleType):
+ self.consider_module(plugin)
+ return ret
+
+ def getplugin(self, name):
+ # support deprecated naming because plugins (xdist e.g.) use it
+ return self.get_plugin(name)
+
+ def hasplugin(self, name):
+ """Return True if the plugin with the given name is registered."""
+ return bool(self.get_plugin(name))
+
+ def pytest_configure(self, config):
+ # XXX now that the pluginmanager exposes hookimpl(tryfirst...)
+ # we should remove tryfirst/trylast as markers
+ config.addinivalue_line(
+ "markers",
+ "tryfirst: mark a hook implementation function such that the "
+ "plugin machinery will try to call it first/as early as possible.",
+ )
+ config.addinivalue_line(
+ "markers",
+ "trylast: mark a hook implementation function such that the "
+ "plugin machinery will try to call it last/as late as possible.",
+ )
+ self._configured = True
+
+ #
+ # internal API for local conftest plugin handling
+ #
+ def _set_initial_conftests(self, namespace):
+ """ load initial conftest files given a preparsed "namespace".
+ As conftest files may add their own command line options
+ which have arguments ('--my-opt somepath') we might get some
+ false positives. All builtin and 3rd party plugins will have
+ been loaded, however, so common options will not confuse our logic
+ here.
+ """
+ current = py.path.local()
+ self._confcutdir = (
+ current.join(namespace.confcutdir, abs=True)
+ if namespace.confcutdir
+ else None
+ )
+ self._noconftest = namespace.noconftest
+ self._using_pyargs = namespace.pyargs
+ testpaths = namespace.file_or_dir
+ foundanchor = False
+ for path in testpaths:
+ path = str(path)
+ # remove node-id syntax
+ i = path.find("::")
+ if i != -1:
+ path = path[:i]
+ anchor = current.join(path, abs=1)
+ if exists(anchor): # we found some file object
+ self._try_load_conftest(anchor)
+ foundanchor = True
+ if not foundanchor:
+ self._try_load_conftest(current)
+
+ def _try_load_conftest(self, anchor):
+ self._getconftestmodules(anchor)
+ # let's also consider test* subdirs
+ if anchor.check(dir=1):
+ for x in anchor.listdir("test*"):
+ if x.check(dir=1):
+ self._getconftestmodules(x)
+
+ @lru_cache(maxsize=128)
+ def _getconftestmodules(self, path):
+ if self._noconftest:
+ return []
+
+ if path.isfile():
+ directory = path.dirpath()
+ else:
+ directory = path
+
+ if six.PY2: # py2 is not using lru_cache.
+ try:
+ return self._dirpath2confmods[directory]
+ except KeyError:
+ pass
+
+ # XXX these days we may rather want to use config.rootdir
+ # and allow users to opt into looking into the rootdir parent
+ # directories instead of requiring to specify confcutdir
+ clist = []
+ for parent in directory.realpath().parts():
+ if self._confcutdir and self._confcutdir.relto(parent):
+ continue
+ conftestpath = parent.join("conftest.py")
+ if conftestpath.isfile():
# Use realpath to avoid loading the same conftest twice
# with build systems that create build directories containing
# symlinks to actual files.
mod = self._importconftest(conftestpath.realpath())
- clist.append(mod)
- self._dirpath2confmods[directory] = clist
- return clist
-
- def _rget_with_confmod(self, name, path):
- modules = self._getconftestmodules(path)
- for mod in reversed(modules):
- try:
- return mod, getattr(mod, name)
- except AttributeError:
- continue
- raise KeyError(name)
-
- def _importconftest(self, conftestpath):
- try:
- return self._conftestpath2mod[conftestpath]
- except KeyError:
- pkgpath = conftestpath.pypkgpath()
- if pkgpath is None:
- _ensure_removed_sysmodule(conftestpath.purebasename)
- try:
- mod = conftestpath.pyimport()
- if (
- hasattr(mod, "pytest_plugins")
- and self._configured
- and not self._using_pyargs
- ):
- from _pytest.deprecated import (
+ clist.append(mod)
+ self._dirpath2confmods[directory] = clist
+ return clist
+
+ def _rget_with_confmod(self, name, path):
+ modules = self._getconftestmodules(path)
+ for mod in reversed(modules):
+ try:
+ return mod, getattr(mod, name)
+ except AttributeError:
+ continue
+ raise KeyError(name)
+
+ def _importconftest(self, conftestpath):
+ try:
+ return self._conftestpath2mod[conftestpath]
+ except KeyError:
+ pkgpath = conftestpath.pypkgpath()
+ if pkgpath is None:
+ _ensure_removed_sysmodule(conftestpath.purebasename)
+ try:
+ mod = conftestpath.pyimport()
+ if (
+ hasattr(mod, "pytest_plugins")
+ and self._configured
+ and not self._using_pyargs
+ ):
+ from _pytest.deprecated import (
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST,
- )
-
+ )
+
fail(
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST.format(
conftestpath, self._confcutdir
),
pytrace=False,
- )
- except Exception:
- raise ConftestImportFailure(conftestpath, sys.exc_info())
-
- self._conftest_plugins.add(mod)
- self._conftestpath2mod[conftestpath] = mod
- dirpath = conftestpath.dirpath()
- if dirpath in self._dirpath2confmods:
- for path, mods in self._dirpath2confmods.items():
- if path and path.relto(dirpath) or path == dirpath:
- assert mod not in mods
- mods.append(mod)
- self.trace("loaded conftestmodule %r" % (mod))
- self.consider_conftest(mod)
- return mod
-
- #
- # API for bootstrapping plugin loading
- #
- #
-
- def consider_preparse(self, args):
+ )
+ except Exception:
+ raise ConftestImportFailure(conftestpath, sys.exc_info())
+
+ self._conftest_plugins.add(mod)
+ self._conftestpath2mod[conftestpath] = mod
+ dirpath = conftestpath.dirpath()
+ if dirpath in self._dirpath2confmods:
+ for path, mods in self._dirpath2confmods.items():
+ if path and path.relto(dirpath) or path == dirpath:
+ assert mod not in mods
+ mods.append(mod)
+ self.trace("loaded conftestmodule %r" % (mod))
+ self.consider_conftest(mod)
+ return mod
+
+ #
+ # API for bootstrapping plugin loading
+ #
+ #
+
+ def consider_preparse(self, args):
i = 0
n = len(args)
while i < n:
@@ -504,22 +504,22 @@ class PytestPluginManager(PluginManager):
else:
continue
self.consider_pluginarg(parg)
-
- def consider_pluginarg(self, arg):
- if arg.startswith("no:"):
- name = arg[3:]
+
+ def consider_pluginarg(self, arg):
+ if arg.startswith("no:"):
+ name = arg[3:]
if name in essential_plugins:
raise UsageError("plugin %s cannot be disabled" % name)
- # PR #4304 : remove stepwise if cacheprovider is blocked
- if name == "cacheprovider":
- self.set_blocked("stepwise")
- self.set_blocked("pytest_stepwise")
-
- self.set_blocked(name)
- if not name.startswith("pytest_"):
- self.set_blocked("pytest_" + name)
- else:
+ # PR #4304 : remove stepwise if cacheprovider is blocked
+ if name == "cacheprovider":
+ self.set_blocked("stepwise")
+ self.set_blocked("pytest_stepwise")
+
+ self.set_blocked(name)
+ if not name.startswith("pytest_"):
+ self.set_blocked("pytest_" + name)
+ else:
name = arg
# Unblock the plugin. None indicates that it has been blocked.
# There is no interface with pluggy for this.
@@ -529,58 +529,58 @@ class PytestPluginManager(PluginManager):
if self._name2plugin.get("pytest_" + name, -1) is None:
del self._name2plugin["pytest_" + name]
self.import_plugin(arg, consider_entry_points=True)
-
- def consider_conftest(self, conftestmodule):
- self.register(conftestmodule, name=conftestmodule.__file__)
-
- def consider_env(self):
- self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
-
- def consider_module(self, mod):
- self._import_plugin_specs(getattr(mod, "pytest_plugins", []))
-
- def _import_plugin_specs(self, spec):
- plugins = _get_plugin_specs_as_list(spec)
- for import_spec in plugins:
- self.import_plugin(import_spec)
-
+
+ def consider_conftest(self, conftestmodule):
+ self.register(conftestmodule, name=conftestmodule.__file__)
+
+ def consider_env(self):
+ self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
+
+ def consider_module(self, mod):
+ self._import_plugin_specs(getattr(mod, "pytest_plugins", []))
+
+ def _import_plugin_specs(self, spec):
+ plugins = _get_plugin_specs_as_list(spec)
+ for import_spec in plugins:
+ self.import_plugin(import_spec)
+
def import_plugin(self, modname, consider_entry_points=False):
"""
Imports a plugin with ``modname``. If ``consider_entry_points`` is True, entry point
names are also considered to find a plugin.
"""
- # most often modname refers to builtin modules, e.g. "pytester",
- # "terminal" or "capture". Those plugins are registered under their
- # basename for historic purposes but must be imported with the
- # _pytest prefix.
+ # most often modname refers to builtin modules, e.g. "pytester",
+ # "terminal" or "capture". Those plugins are registered under their
+ # basename for historic purposes but must be imported with the
+ # _pytest prefix.
assert isinstance(modname, six.string_types), (
- "module name as text required, got %r" % modname
- )
- modname = str(modname)
- if self.is_blocked(modname) or self.get_plugin(modname) is not None:
- return
+ "module name as text required, got %r" % modname
+ )
+ modname = str(modname)
+ if self.is_blocked(modname) or self.get_plugin(modname) is not None:
+ return
importspec = "_pytest." + modname if modname in builtin_plugins else modname
- self.rewrite_hook.mark_rewrite(importspec)
+ self.rewrite_hook.mark_rewrite(importspec)
if consider_entry_points:
loaded = self.load_setuptools_entrypoints("pytest11", name=modname)
if loaded:
return
- try:
- __import__(importspec)
- except ImportError as e:
- new_exc_message = 'Error importing plugin "%s": %s' % (
- modname,
- safe_str(e.args[0]),
- )
+ try:
+ __import__(importspec)
+ except ImportError as e:
+ new_exc_message = 'Error importing plugin "%s": %s' % (
+ modname,
+ safe_str(e.args[0]),
+ )
new_exc = ImportError(new_exc_message)
tb = sys.exc_info()[2]
-
+
six.reraise(ImportError, new_exc, tb)
-
- except Skipped as e:
+
+ except Skipped as e:
from _pytest.warnings import _issue_warning_captured
_issue_warning_captured(
@@ -588,47 +588,47 @@ class PytestPluginManager(PluginManager):
self.hook,
stacklevel=1,
)
- else:
- mod = sys.modules[importspec]
- self.register(mod, modname)
-
-
-def _get_plugin_specs_as_list(specs):
- """
- Parses a list of "plugin specs" and returns a list of plugin names.
-
- Plugin specs can be given as a list of strings separated by "," or already as a list/tuple in
- which case it is returned as a list. Specs can also be `None` in which case an
- empty list is returned.
- """
+ else:
+ mod = sys.modules[importspec]
+ self.register(mod, modname)
+
+
+def _get_plugin_specs_as_list(specs):
+ """
+ Parses a list of "plugin specs" and returns a list of plugin names.
+
+ Plugin specs can be given as a list of strings separated by "," or already as a list/tuple in
+ which case it is returned as a list. Specs can also be `None` in which case an
+ empty list is returned.
+ """
if specs is not None and not isinstance(specs, types.ModuleType):
if isinstance(specs, six.string_types):
- specs = specs.split(",") if specs else []
- if not isinstance(specs, (list, tuple)):
- raise UsageError(
- "Plugin specs must be a ','-separated string or a "
- "list/tuple of strings for plugin names. Given: %r" % specs
- )
- return list(specs)
- return []
-
-
-def _ensure_removed_sysmodule(modname):
- try:
- del sys.modules[modname]
- except KeyError:
- pass
-
-
-class Notset(object):
- def __repr__(self):
- return "<NOTSET>"
-
-
-notset = Notset()
-
-
-def _iter_rewritable_modules(package_files):
+ specs = specs.split(",") if specs else []
+ if not isinstance(specs, (list, tuple)):
+ raise UsageError(
+ "Plugin specs must be a ','-separated string or a "
+ "list/tuple of strings for plugin names. Given: %r" % specs
+ )
+ return list(specs)
+ return []
+
+
+def _ensure_removed_sysmodule(modname):
+ try:
+ del sys.modules[modname]
+ except KeyError:
+ pass
+
+
+class Notset(object):
+ def __repr__(self):
+ return "<NOTSET>"
+
+
+notset = Notset()
+
+
+def _iter_rewritable_modules(package_files):
"""
Given an iterable of file names in a source distribution, return the "names" that should
be marked for assertion rewrite (for example the package "pytest_mock/__init__.py" should
@@ -661,20 +661,20 @@ def _iter_rewritable_modules(package_files):
"""
package_files = list(package_files)
seen_some = False
- for fn in package_files:
- is_simple_module = "/" not in fn and fn.endswith(".py")
- is_package = fn.count("/") == 1 and fn.endswith("__init__.py")
- if is_simple_module:
- module_name, _ = os.path.splitext(fn)
+ for fn in package_files:
+ is_simple_module = "/" not in fn and fn.endswith(".py")
+ is_package = fn.count("/") == 1 and fn.endswith("__init__.py")
+ if is_simple_module:
+ module_name, _ = os.path.splitext(fn)
# we ignore "setup.py" at the root of the distribution
if module_name != "setup":
seen_some = True
yield module_name
- elif is_package:
- package_name = os.path.dirname(fn)
+ elif is_package:
+ package_name = os.path.dirname(fn)
seen_some = True
- yield package_name
-
+ yield package_name
+
if not seen_some:
# at this point we did not find any packages or modules suitable for assertion
# rewriting, so we try again by stripping the first path component (to account for
@@ -690,12 +690,12 @@ def _iter_rewritable_modules(package_files):
if new_package_files:
for _module in _iter_rewritable_modules(new_package_files):
yield _module
-
-class Config(object):
+
+class Config(object):
"""
Access to configuration values, pluginmanager and plugin hooks.
-
+
:ivar PytestPluginManager pluginmanager: the plugin manager handles plugin registration and hook invocation.
:ivar argparse.Namespace option: access to command line option as attributes.
@@ -734,67 +734,67 @@ class Config(object):
args=(), plugins=None, dir=Path().resolve()
)
- #: access to command line option as attributes.
- #: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead
- self.option = argparse.Namespace()
-
+ #: access to command line option as attributes.
+ #: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead
+ self.option = argparse.Namespace()
+
self.invocation_params = invocation_params
- _a = FILE_OR_DIR
- self._parser = Parser(
- usage="%%(prog)s [options] [%s] [%s] [...]" % (_a, _a),
- processopt=self._processopt,
- )
- #: a pluginmanager instance
- self.pluginmanager = pluginmanager
- self.trace = self.pluginmanager.trace.root.get("config")
- self.hook = self.pluginmanager.hook
- self._inicache = {}
- self._override_ini = ()
- self._opt2dest = {}
- self._cleanup = []
- self.pluginmanager.register(self, "pytestconfig")
- self._configured = False
+ _a = FILE_OR_DIR
+ self._parser = Parser(
+ usage="%%(prog)s [options] [%s] [%s] [...]" % (_a, _a),
+ processopt=self._processopt,
+ )
+ #: a pluginmanager instance
+ self.pluginmanager = pluginmanager
+ self.trace = self.pluginmanager.trace.root.get("config")
+ self.hook = self.pluginmanager.hook
+ self._inicache = {}
+ self._override_ini = ()
+ self._opt2dest = {}
+ self._cleanup = []
+ self.pluginmanager.register(self, "pytestconfig")
+ self._configured = False
self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser))
-
+
@property
def invocation_dir(self):
"""Backward compatibility"""
return py.path.local(str(self.invocation_params.dir))
-
- def add_cleanup(self, func):
- """ Add a function to be called when the config object gets out of
- use (usually coninciding with pytest_unconfigure)."""
- self._cleanup.append(func)
-
- def _do_configure(self):
- assert not self._configured
- self._configured = True
- self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
-
- def _ensure_unconfigure(self):
- if self._configured:
- self._configured = False
- self.hook.pytest_unconfigure(config=self)
- self.hook.pytest_configure._call_history = []
- while self._cleanup:
- fin = self._cleanup.pop()
- fin()
-
+
+ def add_cleanup(self, func):
+ """ Add a function to be called when the config object gets out of
+ use (usually coninciding with pytest_unconfigure)."""
+ self._cleanup.append(func)
+
+ def _do_configure(self):
+ assert not self._configured
+ self._configured = True
+ self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
+
+ def _ensure_unconfigure(self):
+ if self._configured:
+ self._configured = False
+ self.hook.pytest_unconfigure(config=self)
+ self.hook.pytest_configure._call_history = []
+ while self._cleanup:
+ fin = self._cleanup.pop()
+ fin()
+
def get_terminal_writer(self):
return self.pluginmanager.get_plugin("terminalreporter")._tw
-
+
def pytest_cmdline_parse(self, pluginmanager, args):
try:
self.parse(args)
except UsageError:
-
+
# Handle --version and --help here in a minimal fashion.
# This gets done via helpconfig normally, but its
# pytest_cmdline_main is not called in case of errors.
if getattr(self.option, "version", False) or "--version" in args:
from _pytest.helpconfig import showversion
-
+
showversion(self)
elif (
getattr(self.option, "help", False) or "--help" in args or "-h" in args
@@ -803,111 +803,111 @@ class Config(object):
sys.stdout.write(
"\nNOTE: displaying only minimal help due to UsageError.\n\n"
)
-
+
raise
-
- return self
-
- def notify_exception(self, excinfo, option=None):
+
+ return self
+
+ def notify_exception(self, excinfo, option=None):
if option and getattr(option, "fulltrace", False):
- style = "long"
- else:
- style = "native"
- excrepr = excinfo.getrepr(
- funcargs=True, showlocals=getattr(option, "showlocals", False), style=style
- )
- res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo)
- if not any(res):
- for line in str(excrepr).split("\n"):
- sys.stderr.write("INTERNALERROR> %s\n" % line)
- sys.stderr.flush()
-
- def cwd_relative_nodeid(self, nodeid):
- # nodeid's are relative to the rootpath, compute relative to cwd
- if self.invocation_dir != self.rootdir:
- fullpath = self.rootdir.join(nodeid)
- nodeid = self.invocation_dir.bestrelpath(fullpath)
- return nodeid
-
- @classmethod
- def fromdictargs(cls, option_dict, args):
- """ constructor useable for subprocesses. """
+ style = "long"
+ else:
+ style = "native"
+ excrepr = excinfo.getrepr(
+ funcargs=True, showlocals=getattr(option, "showlocals", False), style=style
+ )
+ res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo)
+ if not any(res):
+ for line in str(excrepr).split("\n"):
+ sys.stderr.write("INTERNALERROR> %s\n" % line)
+ sys.stderr.flush()
+
+ def cwd_relative_nodeid(self, nodeid):
+ # nodeid's are relative to the rootpath, compute relative to cwd
+ if self.invocation_dir != self.rootdir:
+ fullpath = self.rootdir.join(nodeid)
+ nodeid = self.invocation_dir.bestrelpath(fullpath)
+ return nodeid
+
+ @classmethod
+ def fromdictargs(cls, option_dict, args):
+ """ constructor useable for subprocesses. """
config = get_config(args)
- config.option.__dict__.update(option_dict)
- config.parse(args, addopts=False)
- for x in config.option.plugins:
- config.pluginmanager.consider_pluginarg(x)
- return config
-
- def _processopt(self, opt):
- for name in opt._short_opts + opt._long_opts:
- self._opt2dest[name] = opt.dest
-
- if hasattr(opt, "default") and opt.dest:
- if not hasattr(self.option, opt.dest):
- setattr(self.option, opt.dest, opt.default)
-
- @hookimpl(trylast=True)
- def pytest_load_initial_conftests(self, early_config):
- self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
-
- def _initini(self, args):
- ns, unknown_args = self._parser.parse_known_and_unknown_args(
- args, namespace=copy.copy(self.option)
- )
- r = determine_setup(
- ns.inifilename,
- ns.file_or_dir + unknown_args,
- rootdir_cmd_arg=ns.rootdir or None,
- config=self,
- )
- self.rootdir, self.inifile, self.inicfg = r
- self._parser.extra_info["rootdir"] = self.rootdir
- self._parser.extra_info["inifile"] = self.inifile
- self._parser.addini("addopts", "extra command line options", "args")
- self._parser.addini("minversion", "minimally required pytest version")
- self._override_ini = ns.override_ini or ()
-
- def _consider_importhook(self, args):
- """Install the PEP 302 import hook if using assertion rewriting.
-
- Needs to parse the --assert=<mode> option from the commandline
- and find all the installed plugins to mark them for rewriting
- by the importhook.
- """
- ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
+ config.option.__dict__.update(option_dict)
+ config.parse(args, addopts=False)
+ for x in config.option.plugins:
+ config.pluginmanager.consider_pluginarg(x)
+ return config
+
+ def _processopt(self, opt):
+ for name in opt._short_opts + opt._long_opts:
+ self._opt2dest[name] = opt.dest
+
+ if hasattr(opt, "default") and opt.dest:
+ if not hasattr(self.option, opt.dest):
+ setattr(self.option, opt.dest, opt.default)
+
+ @hookimpl(trylast=True)
+ def pytest_load_initial_conftests(self, early_config):
+ self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
+
+ def _initini(self, args):
+ ns, unknown_args = self._parser.parse_known_and_unknown_args(
+ args, namespace=copy.copy(self.option)
+ )
+ r = determine_setup(
+ ns.inifilename,
+ ns.file_or_dir + unknown_args,
+ rootdir_cmd_arg=ns.rootdir or None,
+ config=self,
+ )
+ self.rootdir, self.inifile, self.inicfg = r
+ self._parser.extra_info["rootdir"] = self.rootdir
+ self._parser.extra_info["inifile"] = self.inifile
+ self._parser.addini("addopts", "extra command line options", "args")
+ self._parser.addini("minversion", "minimally required pytest version")
+ self._override_ini = ns.override_ini or ()
+
+ def _consider_importhook(self, args):
+ """Install the PEP 302 import hook if using assertion rewriting.
+
+ Needs to parse the --assert=<mode> option from the commandline
+ and find all the installed plugins to mark them for rewriting
+ by the importhook.
+ """
+ ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
mode = getattr(ns, "assertmode", "plain")
- if mode == "rewrite":
- try:
- hook = _pytest.assertion.install_importhook(self)
- except SystemError:
- mode = "plain"
- else:
- self._mark_plugins_for_rewrite(hook)
- _warn_about_missing_assertion(mode)
-
- def _mark_plugins_for_rewrite(self, hook):
- """
- Given an importhook, mark for rewrite any top-level
- modules or packages in the distribution package for
- all pytest plugins.
- """
- self.pluginmanager.rewrite_hook = hook
-
- if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
- # We don't autoload from setuptools entry points, no need to continue.
- return
-
- package_files = (
+ if mode == "rewrite":
+ try:
+ hook = _pytest.assertion.install_importhook(self)
+ except SystemError:
+ mode = "plain"
+ else:
+ self._mark_plugins_for_rewrite(hook)
+ _warn_about_missing_assertion(mode)
+
+ def _mark_plugins_for_rewrite(self, hook):
+ """
+ Given an importhook, mark for rewrite any top-level
+ modules or packages in the distribution package for
+ all pytest plugins.
+ """
+ self.pluginmanager.rewrite_hook = hook
+
+ if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
+ # We don't autoload from setuptools entry points, no need to continue.
+ return
+
+ package_files = (
str(file)
for dist in importlib_metadata.distributions()
if any(ep.group == "pytest11" for ep in dist.entry_points)
for file in dist.files or []
- )
-
- for name in _iter_rewritable_modules(package_files):
- hook.mark_rewrite(name)
-
+ )
+
+ for name in _iter_rewritable_modules(package_files):
+ hook.mark_rewrite(name)
+
def _validate_args(self, args, via):
"""Validate known args."""
self._parser._config_source_hint = via
@@ -920,43 +920,43 @@ class Config(object):
return args
- def _preparse(self, args, addopts=True):
- if addopts:
+ def _preparse(self, args, addopts=True):
+ if addopts:
env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
if len(env_addopts):
args[:] = (
self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS")
+ args
)
- self._initini(args)
- if addopts:
+ self._initini(args)
+ if addopts:
args[:] = (
self._validate_args(self.getini("addopts"), "via addopts config") + args
)
- self._checkversion()
- self._consider_importhook(args)
- self.pluginmanager.consider_preparse(args)
- if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
- # Don't autoload from setuptools entry point. Only explicitly specified
- # plugins are going to be loaded.
- self.pluginmanager.load_setuptools_entrypoints("pytest11")
- self.pluginmanager.consider_env()
- self.known_args_namespace = ns = self._parser.parse_known_args(
- args, namespace=copy.copy(self.option)
- )
- if self.known_args_namespace.confcutdir is None and self.inifile:
- confcutdir = py.path.local(self.inifile).dirname
- self.known_args_namespace.confcutdir = confcutdir
- try:
- self.hook.pytest_load_initial_conftests(
- early_config=self, args=args, parser=self._parser
- )
- except ConftestImportFailure:
- e = sys.exc_info()[1]
- if ns.help or ns.version:
- # we don't want to prevent --help/--version to work
- # so just let is pass and print a warning at the end
+ self._checkversion()
+ self._consider_importhook(args)
+ self.pluginmanager.consider_preparse(args)
+ if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
+ # Don't autoload from setuptools entry point. Only explicitly specified
+ # plugins are going to be loaded.
+ self.pluginmanager.load_setuptools_entrypoints("pytest11")
+ self.pluginmanager.consider_env()
+ self.known_args_namespace = ns = self._parser.parse_known_args(
+ args, namespace=copy.copy(self.option)
+ )
+ if self.known_args_namespace.confcutdir is None and self.inifile:
+ confcutdir = py.path.local(self.inifile).dirname
+ self.known_args_namespace.confcutdir = confcutdir
+ try:
+ self.hook.pytest_load_initial_conftests(
+ early_config=self, args=args, parser=self._parser
+ )
+ except ConftestImportFailure:
+ e = sys.exc_info()[1]
+ if ns.help or ns.version:
+ # we don't want to prevent --help/--version to work
+ # so just let is pass and print a warning at the end
from _pytest.warnings import _issue_warning_captured
_issue_warning_captured(
@@ -966,238 +966,238 @@ class Config(object):
self.hook,
stacklevel=2,
)
- else:
- raise
-
- def _checkversion(self):
- import pytest
-
- minver = self.inicfg.get("minversion", None)
- if minver:
+ else:
+ raise
+
+ def _checkversion(self):
+ import pytest
+
+ minver = self.inicfg.get("minversion", None)
+ if minver:
if Version(minver) > Version(pytest.__version__):
- raise pytest.UsageError(
- "%s:%d: requires pytest-%s, actual pytest-%s'"
- % (
- self.inicfg.config.path,
- self.inicfg.lineof("minversion"),
- minver,
- pytest.__version__,
- )
- )
-
- def parse(self, args, addopts=True):
- # parse given cmdline arguments into this config object.
- assert not hasattr(
- self, "args"
- ), "can only parse cmdline args at most once per Config object"
- self._origargs = args
- self.hook.pytest_addhooks.call_historic(
- kwargs=dict(pluginmanager=self.pluginmanager)
- )
- self._preparse(args, addopts=addopts)
- # XXX deprecated hook:
- self.hook.pytest_cmdline_preparse(config=self, args=args)
- self._parser.after_preparse = True
- try:
- args = self._parser.parse_setoption(
- args, self.option, namespace=self.option
- )
- if not args:
- if self.invocation_dir == self.rootdir:
- args = self.getini("testpaths")
- if not args:
- args = [str(self.invocation_dir)]
- self.args = args
- except PrintHelp:
- pass
-
- def addinivalue_line(self, name, line):
- """ add a line to an ini-file option. The option must have been
- declared but might not yet be set in which case the line becomes the
- the first line in its value. """
- x = self.getini(name)
- assert isinstance(x, list)
- x.append(line) # modifies the cached list inline
-
- def getini(self, name):
- """ return configuration value from an :ref:`ini file <inifiles>`. If the
- specified name hasn't been registered through a prior
- :py:func:`parser.addini <_pytest.config.Parser.addini>`
- call (usually from a plugin), a ValueError is raised. """
- try:
- return self._inicache[name]
- except KeyError:
- self._inicache[name] = val = self._getini(name)
- return val
-
- def _getini(self, name):
- try:
- description, type, default = self._parser._inidict[name]
- except KeyError:
- raise ValueError("unknown configuration value: %r" % (name,))
- value = self._get_override_ini_value(name)
- if value is None:
- try:
- value = self.inicfg[name]
- except KeyError:
- if default is not None:
- return default
- if type is None:
- return ""
- return []
- if type == "pathlist":
- dp = py.path.local(self.inicfg.config.path).dirpath()
- values = []
- for relpath in shlex.split(value):
- values.append(dp.join(relpath, abs=True))
- return values
- elif type == "args":
- return shlex.split(value)
- elif type == "linelist":
- return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
- elif type == "bool":
- return bool(_strtobool(value.strip()))
- else:
- assert type is None
- return value
-
- def _getconftest_pathlist(self, name, path):
- try:
- mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
- except KeyError:
- return None
- modpath = py.path.local(mod.__file__).dirpath()
- values = []
- for relroot in relroots:
- if not isinstance(relroot, py.path.local):
- relroot = relroot.replace("/", py.path.local.sep)
- relroot = modpath.join(relroot, abs=True)
- values.append(relroot)
- return values
-
- def _get_override_ini_value(self, name):
- value = None
- # override_ini is a list of "ini=value" options
- # always use the last item if multiple values are set for same ini-name,
- # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2
- for ini_config in self._override_ini:
- try:
- key, user_ini_value = ini_config.split("=", 1)
- except ValueError:
- raise UsageError("-o/--override-ini expects option=value style.")
- else:
- if key == name:
- value = user_ini_value
- return value
-
- def getoption(self, name, default=notset, skip=False):
- """ return command line option value.
-
- :arg name: name of the option. You may also specify
- the literal ``--OPT`` option instead of the "dest" option name.
- :arg default: default value if no option of that name exists.
- :arg skip: if True raise pytest.skip if option does not exists
- or has a None value.
- """
- name = self._opt2dest.get(name, name)
- try:
- val = getattr(self.option, name)
- if val is None and skip:
- raise AttributeError(name)
- return val
- except AttributeError:
- if default is not notset:
- return default
- if skip:
- import pytest
-
- pytest.skip("no %r option found" % (name,))
- raise ValueError("no option named %r" % (name,))
-
- def getvalue(self, name, path=None):
- """ (deprecated, use getoption()) """
- return self.getoption(name)
-
- def getvalueorskip(self, name, path=None):
- """ (deprecated, use getoption(skip=True)) """
- return self.getoption(name, skip=True)
-
-
-def _assertion_supported():
- try:
- assert False
- except AssertionError:
- return True
- else:
- return False
-
-
-def _warn_about_missing_assertion(mode):
- if not _assertion_supported():
- if mode == "plain":
- sys.stderr.write(
- "WARNING: ASSERTIONS ARE NOT EXECUTED"
- " and FAILING TESTS WILL PASS. Are you"
- " using python -O?"
- )
- else:
- sys.stderr.write(
- "WARNING: assertions not in test modules or"
- " plugins will be ignored"
- " because assert statements are not executed "
- "by the underlying Python interpreter "
- "(are you using python -O?)\n"
- )
-
-
-def setns(obj, dic):
- import pytest
-
- for name, value in dic.items():
- if isinstance(value, dict):
- mod = getattr(obj, name, None)
- if mod is None:
- modname = "pytest.%s" % name
- mod = types.ModuleType(modname)
- sys.modules[modname] = mod
- mod.__all__ = []
- setattr(obj, name, mod)
- obj.__all__.append(name)
- setns(mod, value)
- else:
- setattr(obj, name, value)
- obj.__all__.append(name)
- # if obj != pytest:
- # pytest.__all__.append(name)
- setattr(pytest, name, value)
-
-
-def create_terminal_writer(config, *args, **kwargs):
- """Create a TerminalWriter instance configured according to the options
- in the config object. Every code which requires a TerminalWriter object
- and has access to a config object should use this function.
- """
- tw = py.io.TerminalWriter(*args, **kwargs)
- if config.option.color == "yes":
- tw.hasmarkup = True
- if config.option.color == "no":
- tw.hasmarkup = False
- return tw
-
-
-def _strtobool(val):
- """Convert a string representation of truth to true (1) or false (0).
-
- True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
- are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
- 'val' is anything else.
-
- .. note:: copied from distutils.util
- """
- val = val.lower()
- if val in ("y", "yes", "t", "true", "on", "1"):
- return 1
- elif val in ("n", "no", "f", "false", "off", "0"):
- return 0
- else:
- raise ValueError("invalid truth value %r" % (val,))
+ raise pytest.UsageError(
+ "%s:%d: requires pytest-%s, actual pytest-%s'"
+ % (
+ self.inicfg.config.path,
+ self.inicfg.lineof("minversion"),
+ minver,
+ pytest.__version__,
+ )
+ )
+
+ def parse(self, args, addopts=True):
+ # parse given cmdline arguments into this config object.
+ assert not hasattr(
+ self, "args"
+ ), "can only parse cmdline args at most once per Config object"
+ self._origargs = args
+ self.hook.pytest_addhooks.call_historic(
+ kwargs=dict(pluginmanager=self.pluginmanager)
+ )
+ self._preparse(args, addopts=addopts)
+ # XXX deprecated hook:
+ self.hook.pytest_cmdline_preparse(config=self, args=args)
+ self._parser.after_preparse = True
+ try:
+ args = self._parser.parse_setoption(
+ args, self.option, namespace=self.option
+ )
+ if not args:
+ if self.invocation_dir == self.rootdir:
+ args = self.getini("testpaths")
+ if not args:
+ args = [str(self.invocation_dir)]
+ self.args = args
+ except PrintHelp:
+ pass
+
+ def addinivalue_line(self, name, line):
+ """ add a line to an ini-file option. The option must have been
+ declared but might not yet be set in which case the line becomes the
+ the first line in its value. """
+ x = self.getini(name)
+ assert isinstance(x, list)
+ x.append(line) # modifies the cached list inline
+
+ def getini(self, name):
+ """ return configuration value from an :ref:`ini file <inifiles>`. If the
+ specified name hasn't been registered through a prior
+ :py:func:`parser.addini <_pytest.config.Parser.addini>`
+ call (usually from a plugin), a ValueError is raised. """
+ try:
+ return self._inicache[name]
+ except KeyError:
+ self._inicache[name] = val = self._getini(name)
+ return val
+
+ def _getini(self, name):
+ try:
+ description, type, default = self._parser._inidict[name]
+ except KeyError:
+ raise ValueError("unknown configuration value: %r" % (name,))
+ value = self._get_override_ini_value(name)
+ if value is None:
+ try:
+ value = self.inicfg[name]
+ except KeyError:
+ if default is not None:
+ return default
+ if type is None:
+ return ""
+ return []
+ if type == "pathlist":
+ dp = py.path.local(self.inicfg.config.path).dirpath()
+ values = []
+ for relpath in shlex.split(value):
+ values.append(dp.join(relpath, abs=True))
+ return values
+ elif type == "args":
+ return shlex.split(value)
+ elif type == "linelist":
+ return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
+ elif type == "bool":
+ return bool(_strtobool(value.strip()))
+ else:
+ assert type is None
+ return value
+
+ def _getconftest_pathlist(self, name, path):
+ try:
+ mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
+ except KeyError:
+ return None
+ modpath = py.path.local(mod.__file__).dirpath()
+ values = []
+ for relroot in relroots:
+ if not isinstance(relroot, py.path.local):
+ relroot = relroot.replace("/", py.path.local.sep)
+ relroot = modpath.join(relroot, abs=True)
+ values.append(relroot)
+ return values
+
+ def _get_override_ini_value(self, name):
+ value = None
+ # override_ini is a list of "ini=value" options
+ # always use the last item if multiple values are set for same ini-name,
+ # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2
+ for ini_config in self._override_ini:
+ try:
+ key, user_ini_value = ini_config.split("=", 1)
+ except ValueError:
+ raise UsageError("-o/--override-ini expects option=value style.")
+ else:
+ if key == name:
+ value = user_ini_value
+ return value
+
+ def getoption(self, name, default=notset, skip=False):
+ """ return command line option value.
+
+ :arg name: name of the option. You may also specify
+ the literal ``--OPT`` option instead of the "dest" option name.
+ :arg default: default value if no option of that name exists.
+ :arg skip: if True raise pytest.skip if option does not exists
+ or has a None value.
+ """
+ name = self._opt2dest.get(name, name)
+ try:
+ val = getattr(self.option, name)
+ if val is None and skip:
+ raise AttributeError(name)
+ return val
+ except AttributeError:
+ if default is not notset:
+ return default
+ if skip:
+ import pytest
+
+ pytest.skip("no %r option found" % (name,))
+ raise ValueError("no option named %r" % (name,))
+
+ def getvalue(self, name, path=None):
+ """ (deprecated, use getoption()) """
+ return self.getoption(name)
+
+ def getvalueorskip(self, name, path=None):
+ """ (deprecated, use getoption(skip=True)) """
+ return self.getoption(name, skip=True)
+
+
+def _assertion_supported():
+ try:
+ assert False
+ except AssertionError:
+ return True
+ else:
+ return False
+
+
+def _warn_about_missing_assertion(mode):
+ if not _assertion_supported():
+ if mode == "plain":
+ sys.stderr.write(
+ "WARNING: ASSERTIONS ARE NOT EXECUTED"
+ " and FAILING TESTS WILL PASS. Are you"
+ " using python -O?"
+ )
+ else:
+ sys.stderr.write(
+ "WARNING: assertions not in test modules or"
+ " plugins will be ignored"
+ " because assert statements are not executed "
+ "by the underlying Python interpreter "
+ "(are you using python -O?)\n"
+ )
+
+
+def setns(obj, dic):
+ import pytest
+
+ for name, value in dic.items():
+ if isinstance(value, dict):
+ mod = getattr(obj, name, None)
+ if mod is None:
+ modname = "pytest.%s" % name
+ mod = types.ModuleType(modname)
+ sys.modules[modname] = mod
+ mod.__all__ = []
+ setattr(obj, name, mod)
+ obj.__all__.append(name)
+ setns(mod, value)
+ else:
+ setattr(obj, name, value)
+ obj.__all__.append(name)
+ # if obj != pytest:
+ # pytest.__all__.append(name)
+ setattr(pytest, name, value)
+
+
+def create_terminal_writer(config, *args, **kwargs):
+ """Create a TerminalWriter instance configured according to the options
+ in the config object. Every code which requires a TerminalWriter object
+ and has access to a config object should use this function.
+ """
+ tw = py.io.TerminalWriter(*args, **kwargs)
+ if config.option.color == "yes":
+ tw.hasmarkup = True
+ if config.option.color == "no":
+ tw.hasmarkup = False
+ return tw
+
+
+def _strtobool(val):
+ """Convert a string representation of truth to true (1) or false (0).
+
+ True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
+ are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
+ 'val' is anything else.
+
+ .. note:: copied from distutils.util
+ """
+ val = val.lower()
+ if val in ("y", "yes", "t", "true", "on", "1"):
+ return 1
+ elif val in ("n", "no", "f", "false", "off", "0"):
+ return 0
+ else:
+ raise ValueError("invalid truth value %r" % (val,))
diff --git a/contrib/python/pytest/py2/_pytest/config/argparsing.py b/contrib/python/pytest/py2/_pytest/config/argparsing.py
index e7d9b224ed..37fb772db9 100644
--- a/contrib/python/pytest/py2/_pytest/config/argparsing.py
+++ b/contrib/python/pytest/py2/_pytest/config/argparsing.py
@@ -1,410 +1,410 @@
# -*- coding: utf-8 -*-
-import argparse
-import warnings
-
-import py
-import six
-
+import argparse
+import warnings
+
+import py
+import six
+
from _pytest.config.exceptions import UsageError
-
-FILE_OR_DIR = "file_or_dir"
-
-
-class Parser(object):
- """ Parser for command line arguments and ini-file values.
-
- :ivar extra_info: dict of generic param -> value to display in case
- there's an error processing the command line arguments.
- """
-
+
+FILE_OR_DIR = "file_or_dir"
+
+
+class Parser(object):
+ """ Parser for command line arguments and ini-file values.
+
+ :ivar extra_info: dict of generic param -> value to display in case
+ there's an error processing the command line arguments.
+ """
+
prog = None
- def __init__(self, usage=None, processopt=None):
- self._anonymous = OptionGroup("custom options", parser=self)
- self._groups = []
- self._processopt = processopt
- self._usage = usage
- self._inidict = {}
- self._ininames = []
- self.extra_info = {}
-
- def processoption(self, option):
- if self._processopt:
- if option.dest:
- self._processopt(option)
-
- def getgroup(self, name, description="", after=None):
- """ get (or create) a named option Group.
-
- :name: name of the option group.
- :description: long description for --help output.
- :after: name of other group, used for ordering --help output.
-
- The returned group object has an ``addoption`` method with the same
- signature as :py:func:`parser.addoption
- <_pytest.config.Parser.addoption>` but will be shown in the
- respective group in the output of ``pytest. --help``.
- """
- for group in self._groups:
- if group.name == name:
- return group
- group = OptionGroup(name, description, parser=self)
- i = 0
- for i, grp in enumerate(self._groups):
- if grp.name == after:
- break
- self._groups.insert(i + 1, group)
- return group
-
- def addoption(self, *opts, **attrs):
- """ register a command line option.
-
- :opts: option names, can be short or long options.
- :attrs: same attributes which the ``add_option()`` function of the
- `argparse library
- <http://docs.python.org/2/library/argparse.html>`_
- accepts.
-
- After command line parsing options are available on the pytest config
- object via ``config.option.NAME`` where ``NAME`` is usually set
- by passing a ``dest`` attribute, for example
- ``addoption("--long", dest="NAME", ...)``.
- """
- self._anonymous.addoption(*opts, **attrs)
-
- def parse(self, args, namespace=None):
- from _pytest._argcomplete import try_argcomplete
-
- self.optparser = self._getparser()
- try_argcomplete(self.optparser)
- args = [str(x) if isinstance(x, py.path.local) else x for x in args]
- return self.optparser.parse_args(args, namespace=namespace)
-
- def _getparser(self):
- from _pytest._argcomplete import filescompleter
-
+ def __init__(self, usage=None, processopt=None):
+ self._anonymous = OptionGroup("custom options", parser=self)
+ self._groups = []
+ self._processopt = processopt
+ self._usage = usage
+ self._inidict = {}
+ self._ininames = []
+ self.extra_info = {}
+
+ def processoption(self, option):
+ if self._processopt:
+ if option.dest:
+ self._processopt(option)
+
+ def getgroup(self, name, description="", after=None):
+ """ get (or create) a named option Group.
+
+ :name: name of the option group.
+ :description: long description for --help output.
+ :after: name of other group, used for ordering --help output.
+
+ The returned group object has an ``addoption`` method with the same
+ signature as :py:func:`parser.addoption
+ <_pytest.config.Parser.addoption>` but will be shown in the
+ respective group in the output of ``pytest. --help``.
+ """
+ for group in self._groups:
+ if group.name == name:
+ return group
+ group = OptionGroup(name, description, parser=self)
+ i = 0
+ for i, grp in enumerate(self._groups):
+ if grp.name == after:
+ break
+ self._groups.insert(i + 1, group)
+ return group
+
+ def addoption(self, *opts, **attrs):
+ """ register a command line option.
+
+ :opts: option names, can be short or long options.
+ :attrs: same attributes which the ``add_option()`` function of the
+ `argparse library
+ <http://docs.python.org/2/library/argparse.html>`_
+ accepts.
+
+ After command line parsing options are available on the pytest config
+ object via ``config.option.NAME`` where ``NAME`` is usually set
+ by passing a ``dest`` attribute, for example
+ ``addoption("--long", dest="NAME", ...)``.
+ """
+ self._anonymous.addoption(*opts, **attrs)
+
+ def parse(self, args, namespace=None):
+ from _pytest._argcomplete import try_argcomplete
+
+ self.optparser = self._getparser()
+ try_argcomplete(self.optparser)
+ args = [str(x) if isinstance(x, py.path.local) else x for x in args]
+ return self.optparser.parse_args(args, namespace=namespace)
+
+ def _getparser(self):
+ from _pytest._argcomplete import filescompleter
+
optparser = MyOptionParser(self, self.extra_info, prog=self.prog)
- groups = self._groups + [self._anonymous]
- for group in groups:
- if group.options:
- desc = group.description or group.name
- arggroup = optparser.add_argument_group(desc)
- for option in group.options:
- n = option.names()
- a = option.attrs()
- arggroup.add_argument(*n, **a)
- # bash like autocompletion for dirs (appending '/')
- optparser.add_argument(FILE_OR_DIR, nargs="*").completer = filescompleter
- return optparser
-
- def parse_setoption(self, args, option, namespace=None):
- parsedoption = self.parse(args, namespace=namespace)
- for name, value in parsedoption.__dict__.items():
- setattr(option, name, value)
- return getattr(parsedoption, FILE_OR_DIR)
-
- def parse_known_args(self, args, namespace=None):
- """parses and returns a namespace object with known arguments at this
- point.
- """
- return self.parse_known_and_unknown_args(args, namespace=namespace)[0]
-
- def parse_known_and_unknown_args(self, args, namespace=None):
- """parses and returns a namespace object with known arguments, and
- the remaining arguments unknown at this point.
- """
- optparser = self._getparser()
- args = [str(x) if isinstance(x, py.path.local) else x for x in args]
- return optparser.parse_known_args(args, namespace=namespace)
-
- def addini(self, name, help, type=None, default=None):
- """ register an ini-file option.
-
- :name: name of the ini-variable
- :type: type of the variable, can be ``pathlist``, ``args``, ``linelist``
- or ``bool``.
- :default: default value if no ini-file option exists but is queried.
-
- The value of ini-variables can be retrieved via a call to
- :py:func:`config.getini(name) <_pytest.config.Config.getini>`.
- """
- assert type in (None, "pathlist", "args", "linelist", "bool")
- self._inidict[name] = (help, type, default)
- self._ininames.append(name)
-
-
-class ArgumentError(Exception):
- """
- Raised if an Argument instance is created with invalid or
- inconsistent arguments.
- """
-
- def __init__(self, msg, option):
- self.msg = msg
- self.option_id = str(option)
-
- def __str__(self):
- if self.option_id:
- return "option %s: %s" % (self.option_id, self.msg)
- else:
- return self.msg
-
-
-class Argument(object):
- """class that mimics the necessary behaviour of optparse.Option
-
- it's currently a least effort implementation
- and ignoring choices and integer prefixes
- https://docs.python.org/3/library/optparse.html#optparse-standard-option-types
- """
-
- _typ_map = {"int": int, "string": str, "float": float, "complex": complex}
-
- def __init__(self, *names, **attrs):
- """store parms in private vars for use in add_argument"""
- self._attrs = attrs
- self._short_opts = []
- self._long_opts = []
- self.dest = attrs.get("dest")
- if "%default" in (attrs.get("help") or ""):
- warnings.warn(
- 'pytest now uses argparse. "%default" should be'
- ' changed to "%(default)s" ',
- DeprecationWarning,
- stacklevel=3,
- )
- try:
- typ = attrs["type"]
- except KeyError:
- pass
- else:
- # this might raise a keyerror as well, don't want to catch that
- if isinstance(typ, six.string_types):
- if typ == "choice":
- warnings.warn(
- "`type` argument to addoption() is the string %r."
- " For choices this is optional and can be omitted, "
- " but when supplied should be a type (for example `str` or `int`)."
- " (options: %s)" % (typ, names),
- DeprecationWarning,
- stacklevel=4,
- )
- # argparse expects a type here take it from
- # the type of the first element
- attrs["type"] = type(attrs["choices"][0])
- else:
- warnings.warn(
- "`type` argument to addoption() is the string %r, "
- " but when supplied should be a type (for example `str` or `int`)."
- " (options: %s)" % (typ, names),
- DeprecationWarning,
- stacklevel=4,
- )
- attrs["type"] = Argument._typ_map[typ]
- # used in test_parseopt -> test_parse_defaultgetter
- self.type = attrs["type"]
- else:
- self.type = typ
- try:
- # attribute existence is tested in Config._processopt
- self.default = attrs["default"]
- except KeyError:
- pass
- self._set_opt_strings(names)
- if not self.dest:
- if self._long_opts:
- self.dest = self._long_opts[0][2:].replace("-", "_")
- else:
- try:
- self.dest = self._short_opts[0][1:]
- except IndexError:
- raise ArgumentError("need a long or short option", self)
-
- def names(self):
- return self._short_opts + self._long_opts
-
- def attrs(self):
- # update any attributes set by processopt
- attrs = "default dest help".split()
- if self.dest:
- attrs.append(self.dest)
- for attr in attrs:
- try:
- self._attrs[attr] = getattr(self, attr)
- except AttributeError:
- pass
- if self._attrs.get("help"):
- a = self._attrs["help"]
- a = a.replace("%default", "%(default)s")
- # a = a.replace('%prog', '%(prog)s')
- self._attrs["help"] = a
- return self._attrs
-
- def _set_opt_strings(self, opts):
- """directly from optparse
-
- might not be necessary as this is passed to argparse later on"""
- for opt in opts:
- if len(opt) < 2:
- raise ArgumentError(
- "invalid option string %r: "
- "must be at least two characters long" % opt,
- self,
- )
- elif len(opt) == 2:
- if not (opt[0] == "-" and opt[1] != "-"):
- raise ArgumentError(
- "invalid short option string %r: "
- "must be of the form -x, (x any non-dash char)" % opt,
- self,
- )
- self._short_opts.append(opt)
- else:
- if not (opt[0:2] == "--" and opt[2] != "-"):
- raise ArgumentError(
- "invalid long option string %r: "
- "must start with --, followed by non-dash" % opt,
- self,
- )
- self._long_opts.append(opt)
-
- def __repr__(self):
- args = []
- if self._short_opts:
- args += ["_short_opts: " + repr(self._short_opts)]
- if self._long_opts:
- args += ["_long_opts: " + repr(self._long_opts)]
- args += ["dest: " + repr(self.dest)]
- if hasattr(self, "type"):
- args += ["type: " + repr(self.type)]
- if hasattr(self, "default"):
- args += ["default: " + repr(self.default)]
- return "Argument({})".format(", ".join(args))
-
-
-class OptionGroup(object):
- def __init__(self, name, description="", parser=None):
- self.name = name
- self.description = description
- self.options = []
- self.parser = parser
-
- def addoption(self, *optnames, **attrs):
- """ add an option to this group.
-
- if a shortened version of a long option is specified it will
- be suppressed in the help. addoption('--twowords', '--two-words')
- results in help showing '--two-words' only, but --twowords gets
- accepted **and** the automatic destination is in args.twowords
- """
- conflict = set(optnames).intersection(
- name for opt in self.options for name in opt.names()
- )
- if conflict:
- raise ValueError("option names %s already added" % conflict)
- option = Argument(*optnames, **attrs)
- self._addoption_instance(option, shortupper=False)
-
- def _addoption(self, *optnames, **attrs):
- option = Argument(*optnames, **attrs)
- self._addoption_instance(option, shortupper=True)
-
- def _addoption_instance(self, option, shortupper=False):
- if not shortupper:
- for opt in option._short_opts:
- if opt[0] == "-" and opt[1].islower():
- raise ValueError("lowercase shortoptions reserved")
- if self.parser:
- self.parser.processoption(option)
- self.options.append(option)
-
-
-class MyOptionParser(argparse.ArgumentParser):
+ groups = self._groups + [self._anonymous]
+ for group in groups:
+ if group.options:
+ desc = group.description or group.name
+ arggroup = optparser.add_argument_group(desc)
+ for option in group.options:
+ n = option.names()
+ a = option.attrs()
+ arggroup.add_argument(*n, **a)
+ # bash like autocompletion for dirs (appending '/')
+ optparser.add_argument(FILE_OR_DIR, nargs="*").completer = filescompleter
+ return optparser
+
+ def parse_setoption(self, args, option, namespace=None):
+ parsedoption = self.parse(args, namespace=namespace)
+ for name, value in parsedoption.__dict__.items():
+ setattr(option, name, value)
+ return getattr(parsedoption, FILE_OR_DIR)
+
+ def parse_known_args(self, args, namespace=None):
+ """parses and returns a namespace object with known arguments at this
+ point.
+ """
+ return self.parse_known_and_unknown_args(args, namespace=namespace)[0]
+
+ def parse_known_and_unknown_args(self, args, namespace=None):
+ """parses and returns a namespace object with known arguments, and
+ the remaining arguments unknown at this point.
+ """
+ optparser = self._getparser()
+ args = [str(x) if isinstance(x, py.path.local) else x for x in args]
+ return optparser.parse_known_args(args, namespace=namespace)
+
+ def addini(self, name, help, type=None, default=None):
+ """ register an ini-file option.
+
+ :name: name of the ini-variable
+ :type: type of the variable, can be ``pathlist``, ``args``, ``linelist``
+ or ``bool``.
+ :default: default value if no ini-file option exists but is queried.
+
+ The value of ini-variables can be retrieved via a call to
+ :py:func:`config.getini(name) <_pytest.config.Config.getini>`.
+ """
+ assert type in (None, "pathlist", "args", "linelist", "bool")
+ self._inidict[name] = (help, type, default)
+ self._ininames.append(name)
+
+
+class ArgumentError(Exception):
+ """
+ Raised if an Argument instance is created with invalid or
+ inconsistent arguments.
+ """
+
+ def __init__(self, msg, option):
+ self.msg = msg
+ self.option_id = str(option)
+
+ def __str__(self):
+ if self.option_id:
+ return "option %s: %s" % (self.option_id, self.msg)
+ else:
+ return self.msg
+
+
+class Argument(object):
+ """class that mimics the necessary behaviour of optparse.Option
+
+ it's currently a least effort implementation
+ and ignoring choices and integer prefixes
+ https://docs.python.org/3/library/optparse.html#optparse-standard-option-types
+ """
+
+ _typ_map = {"int": int, "string": str, "float": float, "complex": complex}
+
+ def __init__(self, *names, **attrs):
+ """store parms in private vars for use in add_argument"""
+ self._attrs = attrs
+ self._short_opts = []
+ self._long_opts = []
+ self.dest = attrs.get("dest")
+ if "%default" in (attrs.get("help") or ""):
+ warnings.warn(
+ 'pytest now uses argparse. "%default" should be'
+ ' changed to "%(default)s" ',
+ DeprecationWarning,
+ stacklevel=3,
+ )
+ try:
+ typ = attrs["type"]
+ except KeyError:
+ pass
+ else:
+ # this might raise a keyerror as well, don't want to catch that
+ if isinstance(typ, six.string_types):
+ if typ == "choice":
+ warnings.warn(
+ "`type` argument to addoption() is the string %r."
+ " For choices this is optional and can be omitted, "
+ " but when supplied should be a type (for example `str` or `int`)."
+ " (options: %s)" % (typ, names),
+ DeprecationWarning,
+ stacklevel=4,
+ )
+ # argparse expects a type here take it from
+ # the type of the first element
+ attrs["type"] = type(attrs["choices"][0])
+ else:
+ warnings.warn(
+ "`type` argument to addoption() is the string %r, "
+ " but when supplied should be a type (for example `str` or `int`)."
+ " (options: %s)" % (typ, names),
+ DeprecationWarning,
+ stacklevel=4,
+ )
+ attrs["type"] = Argument._typ_map[typ]
+ # used in test_parseopt -> test_parse_defaultgetter
+ self.type = attrs["type"]
+ else:
+ self.type = typ
+ try:
+ # attribute existence is tested in Config._processopt
+ self.default = attrs["default"]
+ except KeyError:
+ pass
+ self._set_opt_strings(names)
+ if not self.dest:
+ if self._long_opts:
+ self.dest = self._long_opts[0][2:].replace("-", "_")
+ else:
+ try:
+ self.dest = self._short_opts[0][1:]
+ except IndexError:
+ raise ArgumentError("need a long or short option", self)
+
+ def names(self):
+ return self._short_opts + self._long_opts
+
+ def attrs(self):
+ # update any attributes set by processopt
+ attrs = "default dest help".split()
+ if self.dest:
+ attrs.append(self.dest)
+ for attr in attrs:
+ try:
+ self._attrs[attr] = getattr(self, attr)
+ except AttributeError:
+ pass
+ if self._attrs.get("help"):
+ a = self._attrs["help"]
+ a = a.replace("%default", "%(default)s")
+ # a = a.replace('%prog', '%(prog)s')
+ self._attrs["help"] = a
+ return self._attrs
+
+ def _set_opt_strings(self, opts):
+ """directly from optparse
+
+ might not be necessary as this is passed to argparse later on"""
+ for opt in opts:
+ if len(opt) < 2:
+ raise ArgumentError(
+ "invalid option string %r: "
+ "must be at least two characters long" % opt,
+ self,
+ )
+ elif len(opt) == 2:
+ if not (opt[0] == "-" and opt[1] != "-"):
+ raise ArgumentError(
+ "invalid short option string %r: "
+ "must be of the form -x, (x any non-dash char)" % opt,
+ self,
+ )
+ self._short_opts.append(opt)
+ else:
+ if not (opt[0:2] == "--" and opt[2] != "-"):
+ raise ArgumentError(
+ "invalid long option string %r: "
+ "must start with --, followed by non-dash" % opt,
+ self,
+ )
+ self._long_opts.append(opt)
+
+ def __repr__(self):
+ args = []
+ if self._short_opts:
+ args += ["_short_opts: " + repr(self._short_opts)]
+ if self._long_opts:
+ args += ["_long_opts: " + repr(self._long_opts)]
+ args += ["dest: " + repr(self.dest)]
+ if hasattr(self, "type"):
+ args += ["type: " + repr(self.type)]
+ if hasattr(self, "default"):
+ args += ["default: " + repr(self.default)]
+ return "Argument({})".format(", ".join(args))
+
+
+class OptionGroup(object):
+ def __init__(self, name, description="", parser=None):
+ self.name = name
+ self.description = description
+ self.options = []
+ self.parser = parser
+
+ def addoption(self, *optnames, **attrs):
+ """ add an option to this group.
+
+ if a shortened version of a long option is specified it will
+ be suppressed in the help. addoption('--twowords', '--two-words')
+ results in help showing '--two-words' only, but --twowords gets
+ accepted **and** the automatic destination is in args.twowords
+ """
+ conflict = set(optnames).intersection(
+ name for opt in self.options for name in opt.names()
+ )
+ if conflict:
+ raise ValueError("option names %s already added" % conflict)
+ option = Argument(*optnames, **attrs)
+ self._addoption_instance(option, shortupper=False)
+
+ def _addoption(self, *optnames, **attrs):
+ option = Argument(*optnames, **attrs)
+ self._addoption_instance(option, shortupper=True)
+
+ def _addoption_instance(self, option, shortupper=False):
+ if not shortupper:
+ for opt in option._short_opts:
+ if opt[0] == "-" and opt[1].islower():
+ raise ValueError("lowercase shortoptions reserved")
+ if self.parser:
+ self.parser.processoption(option)
+ self.options.append(option)
+
+
+class MyOptionParser(argparse.ArgumentParser):
def __init__(self, parser, extra_info=None, prog=None):
- if not extra_info:
- extra_info = {}
- self._parser = parser
- argparse.ArgumentParser.__init__(
- self,
+ if not extra_info:
+ extra_info = {}
+ self._parser = parser
+ argparse.ArgumentParser.__init__(
+ self,
prog=prog,
- usage=parser._usage,
- add_help=False,
- formatter_class=DropShorterLongHelpFormatter,
- )
- # extra_info is a dict of (param -> value) to display if there's
- # an usage error to provide more contextual information to the user
- self.extra_info = extra_info
-
- def error(self, message):
+ usage=parser._usage,
+ add_help=False,
+ formatter_class=DropShorterLongHelpFormatter,
+ )
+ # extra_info is a dict of (param -> value) to display if there's
+ # an usage error to provide more contextual information to the user
+ self.extra_info = extra_info
+
+ def error(self, message):
"""Transform argparse error message into UsageError."""
msg = "%s: error: %s" % (self.prog, message)
-
+
if hasattr(self._parser, "_config_source_hint"):
msg = "%s (%s)" % (msg, self._parser._config_source_hint)
-
+
raise UsageError(self.format_usage() + msg)
- def parse_args(self, args=None, namespace=None):
- """allow splitting of positional arguments"""
- args, argv = self.parse_known_args(args, namespace)
- if argv:
- for arg in argv:
- if arg and arg[0] == "-":
- lines = ["unrecognized arguments: %s" % (" ".join(argv))]
- for k, v in sorted(self.extra_info.items()):
- lines.append(" %s: %s" % (k, v))
- self.error("\n".join(lines))
- getattr(args, FILE_OR_DIR).extend(argv)
- return args
-
-
-class DropShorterLongHelpFormatter(argparse.HelpFormatter):
- """shorten help for long options that differ only in extra hyphens
-
- - collapse **long** options that are the same except for extra hyphens
- - special action attribute map_long_option allows surpressing additional
- long options
- - shortcut if there are only two options and one of them is a short one
- - cache result on action object as this is called at least 2 times
- """
-
- def _format_action_invocation(self, action):
- orgstr = argparse.HelpFormatter._format_action_invocation(self, action)
- if orgstr and orgstr[0] != "-": # only optional arguments
- return orgstr
- res = getattr(action, "_formatted_action_invocation", None)
- if res:
- return res
- options = orgstr.split(", ")
- if len(options) == 2 and (len(options[0]) == 2 or len(options[1]) == 2):
- # a shortcut for '-h, --help' or '--abc', '-a'
- action._formatted_action_invocation = orgstr
- return orgstr
- return_list = []
- option_map = getattr(action, "map_long_option", {})
- if option_map is None:
- option_map = {}
- short_long = {}
- for option in options:
- if len(option) == 2 or option[2] == " ":
- continue
- if not option.startswith("--"):
- raise ArgumentError(
- 'long optional argument without "--": [%s]' % (option), self
- )
- xxoption = option[2:]
- if xxoption.split()[0] not in option_map:
- shortened = xxoption.replace("-", "")
- if shortened not in short_long or len(short_long[shortened]) < len(
- xxoption
- ):
- short_long[shortened] = xxoption
- # now short_long has been filled out to the longest with dashes
- # **and** we keep the right option ordering from add_argument
- for option in options:
- if len(option) == 2 or option[2] == " ":
- return_list.append(option)
- if option[2:] == short_long.get(option.replace("-", "")):
- return_list.append(option.replace(" ", "=", 1))
- action._formatted_action_invocation = ", ".join(return_list)
- return action._formatted_action_invocation
+ def parse_args(self, args=None, namespace=None):
+ """allow splitting of positional arguments"""
+ args, argv = self.parse_known_args(args, namespace)
+ if argv:
+ for arg in argv:
+ if arg and arg[0] == "-":
+ lines = ["unrecognized arguments: %s" % (" ".join(argv))]
+ for k, v in sorted(self.extra_info.items()):
+ lines.append(" %s: %s" % (k, v))
+ self.error("\n".join(lines))
+ getattr(args, FILE_OR_DIR).extend(argv)
+ return args
+
+
+class DropShorterLongHelpFormatter(argparse.HelpFormatter):
+ """shorten help for long options that differ only in extra hyphens
+
+ - collapse **long** options that are the same except for extra hyphens
+ - special action attribute map_long_option allows surpressing additional
+ long options
+ - shortcut if there are only two options and one of them is a short one
+ - cache result on action object as this is called at least 2 times
+ """
+
+ def _format_action_invocation(self, action):
+ orgstr = argparse.HelpFormatter._format_action_invocation(self, action)
+ if orgstr and orgstr[0] != "-": # only optional arguments
+ return orgstr
+ res = getattr(action, "_formatted_action_invocation", None)
+ if res:
+ return res
+ options = orgstr.split(", ")
+ if len(options) == 2 and (len(options[0]) == 2 or len(options[1]) == 2):
+ # a shortcut for '-h, --help' or '--abc', '-a'
+ action._formatted_action_invocation = orgstr
+ return orgstr
+ return_list = []
+ option_map = getattr(action, "map_long_option", {})
+ if option_map is None:
+ option_map = {}
+ short_long = {}
+ for option in options:
+ if len(option) == 2 or option[2] == " ":
+ continue
+ if not option.startswith("--"):
+ raise ArgumentError(
+ 'long optional argument without "--": [%s]' % (option), self
+ )
+ xxoption = option[2:]
+ if xxoption.split()[0] not in option_map:
+ shortened = xxoption.replace("-", "")
+ if shortened not in short_long or len(short_long[shortened]) < len(
+ xxoption
+ ):
+ short_long[shortened] = xxoption
+ # now short_long has been filled out to the longest with dashes
+ # **and** we keep the right option ordering from add_argument
+ for option in options:
+ if len(option) == 2 or option[2] == " ":
+ return_list.append(option)
+ if option[2:] == short_long.get(option.replace("-", "")):
+ return_list.append(option.replace(" ", "=", 1))
+ action._formatted_action_invocation = ", ".join(return_list)
+ return action._formatted_action_invocation
diff --git a/contrib/python/pytest/py2/_pytest/config/exceptions.py b/contrib/python/pytest/py2/_pytest/config/exceptions.py
index 3463aa2bd1..bf58fde5db 100644
--- a/contrib/python/pytest/py2/_pytest/config/exceptions.py
+++ b/contrib/python/pytest/py2/_pytest/config/exceptions.py
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
-class UsageError(Exception):
- """ error in pytest usage or invocation"""
-
-
-class PrintHelp(Exception):
- """Raised when pytest should print it's help to skip the rest of the
- argument parsing and validation."""
-
- pass
+class UsageError(Exception):
+ """ error in pytest usage or invocation"""
+
+
+class PrintHelp(Exception):
+ """Raised when pytest should print it's help to skip the rest of the
+ argument parsing and validation."""
+
+ pass
diff --git a/contrib/python/pytest/py2/_pytest/config/findpaths.py b/contrib/python/pytest/py2/_pytest/config/findpaths.py
index f3776f1e75..e6779b289b 100644
--- a/contrib/python/pytest/py2/_pytest/config/findpaths.py
+++ b/contrib/python/pytest/py2/_pytest/config/findpaths.py
@@ -1,48 +1,48 @@
# -*- coding: utf-8 -*-
-import os
-
-import py
-
-from .exceptions import UsageError
+import os
+
+import py
+
+from .exceptions import UsageError
from _pytest.outcomes import fail
-
-
-def exists(path, ignore=EnvironmentError):
- try:
- return path.check()
- except ignore:
- return False
-
-
-def getcfg(args, config=None):
- """
- Search the list of arguments for a valid ini-file for pytest,
- and return a tuple of (rootdir, inifile, cfg-dict).
-
- note: config is optional and used only to issue warnings explicitly (#2891).
- """
- from _pytest.deprecated import CFG_PYTEST_SECTION
-
- inibasenames = ["pytest.ini", "tox.ini", "setup.cfg"]
- args = [x for x in args if not str(x).startswith("-")]
- if not args:
- args = [py.path.local()]
- for arg in args:
- arg = py.path.local(arg)
- for base in arg.parts(reverse=True):
- for inibasename in inibasenames:
- p = base.join(inibasename)
- if exists(p):
+
+
+def exists(path, ignore=EnvironmentError):
+ try:
+ return path.check()
+ except ignore:
+ return False
+
+
+def getcfg(args, config=None):
+ """
+ Search the list of arguments for a valid ini-file for pytest,
+ and return a tuple of (rootdir, inifile, cfg-dict).
+
+ note: config is optional and used only to issue warnings explicitly (#2891).
+ """
+ from _pytest.deprecated import CFG_PYTEST_SECTION
+
+ inibasenames = ["pytest.ini", "tox.ini", "setup.cfg"]
+ args = [x for x in args if not str(x).startswith("-")]
+ if not args:
+ args = [py.path.local()]
+ for arg in args:
+ arg = py.path.local(arg)
+ for base in arg.parts(reverse=True):
+ for inibasename in inibasenames:
+ p = base.join(inibasename)
+ if exists(p):
try:
iniconfig = py.iniconfig.IniConfig(p)
except py.iniconfig.ParseError as exc:
raise UsageError(str(exc))
-
- if (
- inibasename == "setup.cfg"
- and "tool:pytest" in iniconfig.sections
- ):
- return base, p, iniconfig["tool:pytest"]
+
+ if (
+ inibasename == "setup.cfg"
+ and "tool:pytest" in iniconfig.sections
+ ):
+ return base, p, iniconfig["tool:pytest"]
elif "pytest" in iniconfig.sections:
if inibasename == "setup.cfg" and config is not None:
@@ -51,103 +51,103 @@ def getcfg(args, config=None):
pytrace=False,
)
return base, p, iniconfig["pytest"]
- elif inibasename == "pytest.ini":
- # allowed to be empty
- return base, p, {}
- return None, None, None
-
-
-def get_common_ancestor(paths):
- common_ancestor = None
- for path in paths:
- if not path.exists():
- continue
- if common_ancestor is None:
- common_ancestor = path
- else:
- if path.relto(common_ancestor) or path == common_ancestor:
- continue
- elif common_ancestor.relto(path):
- common_ancestor = path
- else:
- shared = path.common(common_ancestor)
- if shared is not None:
- common_ancestor = shared
- if common_ancestor is None:
- common_ancestor = py.path.local()
- elif common_ancestor.isfile():
- common_ancestor = common_ancestor.dirpath()
- return common_ancestor
-
-
-def get_dirs_from_args(args):
- def is_option(x):
- return str(x).startswith("-")
-
- def get_file_part_from_node_id(x):
- return str(x).split("::")[0]
-
- def get_dir_from_path(path):
- if path.isdir():
- return path
- return py.path.local(path.dirname)
-
- # These look like paths but may not exist
- possible_paths = (
- py.path.local(get_file_part_from_node_id(arg))
- for arg in args
- if not is_option(arg)
- )
-
- return [get_dir_from_path(path) for path in possible_paths if path.exists()]
-
-
-def determine_setup(inifile, args, rootdir_cmd_arg=None, config=None):
- dirs = get_dirs_from_args(args)
- if inifile:
- iniconfig = py.iniconfig.IniConfig(inifile)
- is_cfg_file = str(inifile).endswith(".cfg")
- sections = ["tool:pytest", "pytest"] if is_cfg_file else ["pytest"]
- for section in sections:
- try:
- inicfg = iniconfig[section]
- if is_cfg_file and section == "pytest" and config is not None:
- from _pytest.deprecated import CFG_PYTEST_SECTION
-
+ elif inibasename == "pytest.ini":
+ # allowed to be empty
+ return base, p, {}
+ return None, None, None
+
+
+def get_common_ancestor(paths):
+ common_ancestor = None
+ for path in paths:
+ if not path.exists():
+ continue
+ if common_ancestor is None:
+ common_ancestor = path
+ else:
+ if path.relto(common_ancestor) or path == common_ancestor:
+ continue
+ elif common_ancestor.relto(path):
+ common_ancestor = path
+ else:
+ shared = path.common(common_ancestor)
+ if shared is not None:
+ common_ancestor = shared
+ if common_ancestor is None:
+ common_ancestor = py.path.local()
+ elif common_ancestor.isfile():
+ common_ancestor = common_ancestor.dirpath()
+ return common_ancestor
+
+
+def get_dirs_from_args(args):
+ def is_option(x):
+ return str(x).startswith("-")
+
+ def get_file_part_from_node_id(x):
+ return str(x).split("::")[0]
+
+ def get_dir_from_path(path):
+ if path.isdir():
+ return path
+ return py.path.local(path.dirname)
+
+ # These look like paths but may not exist
+ possible_paths = (
+ py.path.local(get_file_part_from_node_id(arg))
+ for arg in args
+ if not is_option(arg)
+ )
+
+ return [get_dir_from_path(path) for path in possible_paths if path.exists()]
+
+
+def determine_setup(inifile, args, rootdir_cmd_arg=None, config=None):
+ dirs = get_dirs_from_args(args)
+ if inifile:
+ iniconfig = py.iniconfig.IniConfig(inifile)
+ is_cfg_file = str(inifile).endswith(".cfg")
+ sections = ["tool:pytest", "pytest"] if is_cfg_file else ["pytest"]
+ for section in sections:
+ try:
+ inicfg = iniconfig[section]
+ if is_cfg_file and section == "pytest" and config is not None:
+ from _pytest.deprecated import CFG_PYTEST_SECTION
+
fail(
CFG_PYTEST_SECTION.format(filename=str(inifile)), pytrace=False
- )
- break
- except KeyError:
- inicfg = None
+ )
+ break
+ except KeyError:
+ inicfg = None
if rootdir_cmd_arg is None:
rootdir = get_common_ancestor(dirs)
- else:
- ancestor = get_common_ancestor(dirs)
- rootdir, inifile, inicfg = getcfg([ancestor], config=config)
+ else:
+ ancestor = get_common_ancestor(dirs)
+ rootdir, inifile, inicfg = getcfg([ancestor], config=config)
if rootdir is None and rootdir_cmd_arg is None:
for possible_rootdir in ancestor.parts(reverse=True):
if possible_rootdir.join("setup.py").exists():
rootdir = possible_rootdir
- break
- else:
+ break
+ else:
if dirs != [ancestor]:
rootdir, inifile, inicfg = getcfg(dirs, config=config)
- if rootdir is None:
+ if rootdir is None:
if config is not None:
cwd = config.invocation_dir
else:
cwd = py.path.local()
rootdir = get_common_ancestor([cwd, ancestor])
- is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/"
- if is_fs_root:
- rootdir = ancestor
- if rootdir_cmd_arg:
+ is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/"
+ if is_fs_root:
+ rootdir = ancestor
+ if rootdir_cmd_arg:
rootdir = py.path.local(os.path.expandvars(rootdir_cmd_arg))
if not rootdir.isdir():
- raise UsageError(
- "Directory '{}' not found. Check your '--rootdir' option.".format(
+ raise UsageError(
+ "Directory '{}' not found. Check your '--rootdir' option.".format(
rootdir
- )
- )
- return rootdir, inifile, inicfg or {}
+ )
+ )
+ return rootdir, inifile, inicfg or {}
diff --git a/contrib/python/pytest/py2/_pytest/debugging.py b/contrib/python/pytest/py2/_pytest/debugging.py
index 337b48d2f6..bc114c8683 100644
--- a/contrib/python/pytest/py2/_pytest/debugging.py
+++ b/contrib/python/pytest/py2/_pytest/debugging.py
@@ -1,56 +1,56 @@
# -*- coding: utf-8 -*-
-""" interactive debugging with PDB, the Python Debugger. """
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import os
+""" interactive debugging with PDB, the Python Debugger. """
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import os
import argparse
-import pdb
-import sys
-from doctest import UnexpectedException
-
-from _pytest import outcomes
-from _pytest.config import hookimpl
+import pdb
+import sys
+from doctest import UnexpectedException
+
+from _pytest import outcomes
+from _pytest.config import hookimpl
from _pytest.config.exceptions import UsageError
-
-
-def import_readline():
- try:
- import readline
- except ImportError:
- sys.path.append('/usr/lib/python2.7/lib-dynload')
-
- try:
- import readline
- except ImportError as e:
- print('can not import readline:', e)
-
- import subprocess
- try:
- subprocess.check_call('stty icrnl'.split())
- except OSError as e:
- print('can not restore Enter, use Control+J:', e)
-
-
-def tty():
- if os.isatty(1):
- return
-
+
+
+def import_readline():
+ try:
+ import readline
+ except ImportError:
+ sys.path.append('/usr/lib/python2.7/lib-dynload')
+
+ try:
+ import readline
+ except ImportError as e:
+ print('can not import readline:', e)
+
+ import subprocess
+ try:
+ subprocess.check_call('stty icrnl'.split())
+ except OSError as e:
+ print('can not restore Enter, use Control+J:', e)
+
+
+def tty():
+ if os.isatty(1):
+ return
+
fd = os.open('/dev/tty', os.O_RDWR)
- os.dup2(fd, 0)
- os.dup2(fd, 1)
- os.dup2(fd, 2)
+ os.dup2(fd, 0)
+ os.dup2(fd, 1)
+ os.dup2(fd, 2)
os.close(fd)
-
- old_sys_path = sys.path
- sys.path = list(sys.path)
- try:
- import_readline()
- finally:
- sys.path = old_sys_path
-
-
+
+ old_sys_path = sys.path
+ sys.path = list(sys.path)
+ try:
+ import_readline()
+ finally:
+ sys.path = old_sys_path
+
+
def _validate_usepdb_cls(value):
"""Validate syntax of --pdbcls option."""
try:
@@ -62,65 +62,65 @@ def _validate_usepdb_cls(value):
return (modname, classname)
-def pytest_addoption(parser):
- group = parser.getgroup("general")
- group._addoption(
- "--pdb",
- dest="usepdb",
- action="store_true",
- help="start the interactive Python debugger on errors or KeyboardInterrupt.",
- )
- group._addoption(
- "--pdbcls",
- dest="usepdb_cls",
- metavar="modulename:classname",
+def pytest_addoption(parser):
+ group = parser.getgroup("general")
+ group._addoption(
+ "--pdb",
+ dest="usepdb",
+ action="store_true",
+ help="start the interactive Python debugger on errors or KeyboardInterrupt.",
+ )
+ group._addoption(
+ "--pdbcls",
+ dest="usepdb_cls",
+ metavar="modulename:classname",
type=_validate_usepdb_cls,
- help="start a custom interactive Python debugger on errors. "
- "For example: --pdbcls=IPython.terminal.debugger:TerminalPdb",
- )
- group._addoption(
- "--trace",
- dest="trace",
- action="store_true",
- help="Immediately break when running each test.",
- )
-
-
-def pytest_configure(config):
- if config.getvalue("trace"):
- config.pluginmanager.register(PdbTrace(), "pdbtrace")
- if config.getvalue("usepdb"):
- config.pluginmanager.register(PdbInvoke(), "pdbinvoke")
-
- pytestPDB._saved.append(
+ help="start a custom interactive Python debugger on errors. "
+ "For example: --pdbcls=IPython.terminal.debugger:TerminalPdb",
+ )
+ group._addoption(
+ "--trace",
+ dest="trace",
+ action="store_true",
+ help="Immediately break when running each test.",
+ )
+
+
+def pytest_configure(config):
+ if config.getvalue("trace"):
+ config.pluginmanager.register(PdbTrace(), "pdbtrace")
+ if config.getvalue("usepdb"):
+ config.pluginmanager.register(PdbInvoke(), "pdbinvoke")
+
+ pytestPDB._saved.append(
(pdb.set_trace, pytestPDB._pluginmanager, pytestPDB._config)
- )
- pdb.set_trace = pytestPDB.set_trace
- pytestPDB._pluginmanager = config.pluginmanager
- pytestPDB._config = config
-
- # NOTE: not using pytest_unconfigure, since it might get called although
- # pytest_configure was not (if another plugin raises UsageError).
- def fin():
- (
- pdb.set_trace,
- pytestPDB._pluginmanager,
- pytestPDB._config,
- ) = pytestPDB._saved.pop()
-
- config._cleanup.append(fin)
-
-
-class pytestPDB(object):
- """ Pseudo PDB that defers to the real pdb. """
-
- _pluginmanager = None
- _config = None
- _saved = []
+ )
+ pdb.set_trace = pytestPDB.set_trace
+ pytestPDB._pluginmanager = config.pluginmanager
+ pytestPDB._config = config
+
+ # NOTE: not using pytest_unconfigure, since it might get called although
+ # pytest_configure was not (if another plugin raises UsageError).
+ def fin():
+ (
+ pdb.set_trace,
+ pytestPDB._pluginmanager,
+ pytestPDB._config,
+ ) = pytestPDB._saved.pop()
+
+ config._cleanup.append(fin)
+
+
+class pytestPDB(object):
+ """ Pseudo PDB that defers to the real pdb. """
+
+ _pluginmanager = None
+ _config = None
+ _saved = []
_recursive_debug = 0
_wrapped_pdb_cls = None
-
- @classmethod
+
+ @classmethod
def _is_capturing(cls, capman):
if capman:
return capman.is_capturing()
@@ -163,18 +163,18 @@ class pytestPDB(object):
@classmethod
def _get_pdb_wrapper_class(cls, pdb_cls, capman):
- import _pytest.config
-
+ import _pytest.config
+
class PytestPdbWrapper(pdb_cls, object):
_pytest_capman = capman
_continued = False
-
+
def do_debug(self, arg):
cls._recursive_debug += 1
ret = super(PytestPdbWrapper, self).do_debug(arg)
cls._recursive_debug -= 1
return ret
-
+
def do_continue(self, arg):
ret = super(PytestPdbWrapper, self).do_continue(arg)
if cls._recursive_debug == 0:
@@ -185,8 +185,8 @@ class pytestPDB(object):
capturing = pytestPDB._is_capturing(capman)
if capturing:
if capturing == "global":
- tw.sep(">", "PDB continue (IO-capturing resumed)")
- else:
+ tw.sep(">", "PDB continue (IO-capturing resumed)")
+ else:
tw.sep(
">",
"PDB continue (IO-capturing resumed for %s)"
@@ -198,18 +198,18 @@ class pytestPDB(object):
cls._pluginmanager.hook.pytest_leave_pdb(config=cls._config, pdb=self)
self._continued = True
return ret
-
+
do_c = do_cont = do_continue
-
+
def do_quit(self, arg):
"""Raise Exit outcome when quit command is used in pdb.
-
+
This is a bit of a hack - it would be better if BdbQuit
could be handled, but this would require to wrap the
whole pytest run, and adjust the report etc.
"""
ret = super(PytestPdbWrapper, self).do_quit(arg)
-
+
if cls._recursive_debug == 0:
outcomes.exit("Quitting debugger")
@@ -250,15 +250,15 @@ class pytestPDB(object):
if cls._pluginmanager is not None:
capman = cls._pluginmanager.getplugin("capturemanager")
- else:
+ else:
capman = None
if capman:
capman.suspend(in_=True)
-
+
if cls._config:
tw = _pytest.config.create_terminal_writer(cls._config)
tw.line()
-
+
if cls._recursive_debug == 0:
# Handle header similar to pdb.set_trace in py37+.
header = kwargs.pop("header", None)
@@ -276,7 +276,7 @@ class pytestPDB(object):
)
else:
tw.sep(">", "PDB %s" % (method,))
-
+
_pdb = cls._import_pdb_cls(capman)(**kwargs)
if cls._pluginmanager:
@@ -292,32 +292,32 @@ class pytestPDB(object):
_pdb.set_trace(frame)
-class PdbInvoke(object):
- def pytest_exception_interact(self, node, call, report):
- capman = node.config.pluginmanager.getplugin("capturemanager")
- if capman:
- capman.suspend_global_capture(in_=True)
- out, err = capman.read_global_capture()
- sys.stdout.write(out)
- sys.stdout.write(err)
- tty()
- _enter_pdb(node, call.excinfo, report)
-
- def pytest_internalerror(self, excrepr, excinfo):
- tb = _postmortem_traceback(excinfo)
- post_mortem(tb)
-
-
-class PdbTrace(object):
- @hookimpl(hookwrapper=True)
- def pytest_pyfunc_call(self, pyfuncitem):
- _test_pytest_function(pyfuncitem)
- yield
-
-
-def _test_pytest_function(pyfuncitem):
+class PdbInvoke(object):
+ def pytest_exception_interact(self, node, call, report):
+ capman = node.config.pluginmanager.getplugin("capturemanager")
+ if capman:
+ capman.suspend_global_capture(in_=True)
+ out, err = capman.read_global_capture()
+ sys.stdout.write(out)
+ sys.stdout.write(err)
+ tty()
+ _enter_pdb(node, call.excinfo, report)
+
+ def pytest_internalerror(self, excrepr, excinfo):
+ tb = _postmortem_traceback(excinfo)
+ post_mortem(tb)
+
+
+class PdbTrace(object):
+ @hookimpl(hookwrapper=True)
+ def pytest_pyfunc_call(self, pyfuncitem):
+ _test_pytest_function(pyfuncitem)
+ yield
+
+
+def _test_pytest_function(pyfuncitem):
_pdb = pytestPDB._init_pdb("runcall")
- testfunction = pyfuncitem.obj
+ testfunction = pyfuncitem.obj
pyfuncitem.obj = _pdb.runcall
if "func" in pyfuncitem._fixtureinfo.argnames: # pragma: no branch
raise ValueError("--trace can't be used with a fixture named func!")
@@ -325,49 +325,49 @@ def _test_pytest_function(pyfuncitem):
new_list = list(pyfuncitem._fixtureinfo.argnames)
new_list.append("func")
pyfuncitem._fixtureinfo.argnames = tuple(new_list)
-
-
-def _enter_pdb(node, excinfo, rep):
- # XXX we re-use the TerminalReporter's terminalwriter
- # because this seems to avoid some encoding related troubles
- # for not completely clear reasons.
- tw = node.config.pluginmanager.getplugin("terminalreporter")._tw
- tw.line()
-
- showcapture = node.config.option.showcapture
-
- for sectionname, content in (
- ("stdout", rep.capstdout),
- ("stderr", rep.capstderr),
- ("log", rep.caplog),
- ):
- if showcapture in (sectionname, "all") and content:
- tw.sep(">", "captured " + sectionname)
- if content[-1:] == "\n":
- content = content[:-1]
- tw.line(content)
-
- tw.sep(">", "traceback")
- rep.toterminal(tw)
- tw.sep(">", "entering PDB")
- tb = _postmortem_traceback(excinfo)
- rep._pdbshown = True
+
+
+def _enter_pdb(node, excinfo, rep):
+ # XXX we re-use the TerminalReporter's terminalwriter
+ # because this seems to avoid some encoding related troubles
+ # for not completely clear reasons.
+ tw = node.config.pluginmanager.getplugin("terminalreporter")._tw
+ tw.line()
+
+ showcapture = node.config.option.showcapture
+
+ for sectionname, content in (
+ ("stdout", rep.capstdout),
+ ("stderr", rep.capstderr),
+ ("log", rep.caplog),
+ ):
+ if showcapture in (sectionname, "all") and content:
+ tw.sep(">", "captured " + sectionname)
+ if content[-1:] == "\n":
+ content = content[:-1]
+ tw.line(content)
+
+ tw.sep(">", "traceback")
+ rep.toterminal(tw)
+ tw.sep(">", "entering PDB")
+ tb = _postmortem_traceback(excinfo)
+ rep._pdbshown = True
post_mortem(tb)
- return rep
-
-
-def _postmortem_traceback(excinfo):
- if isinstance(excinfo.value, UnexpectedException):
- # A doctest.UnexpectedException is not useful for post_mortem.
- # Use the underlying exception instead:
- return excinfo.value.exc_info[2]
- else:
- return excinfo._excinfo[2]
-
-
-def post_mortem(t):
+ return rep
+
+
+def _postmortem_traceback(excinfo):
+ if isinstance(excinfo.value, UnexpectedException):
+ # A doctest.UnexpectedException is not useful for post_mortem.
+ # Use the underlying exception instead:
+ return excinfo.value.exc_info[2]
+ else:
+ return excinfo._excinfo[2]
+
+
+def post_mortem(t):
p = pytestPDB._init_pdb("post_mortem")
- p.reset()
- p.interaction(None, t)
+ p.reset()
+ p.interaction(None, t)
if p.quitting:
outcomes.exit("Quitting debugger")
diff --git a/contrib/python/pytest/py2/_pytest/deprecated.py b/contrib/python/pytest/py2/_pytest/deprecated.py
index c7b9b9d046..12394aca3f 100644
--- a/contrib/python/pytest/py2/_pytest/deprecated.py
+++ b/contrib/python/pytest/py2/_pytest/deprecated.py
@@ -1,63 +1,63 @@
# -*- coding: utf-8 -*-
-"""
-This module contains deprecation messages and bits of code used elsewhere in the codebase
-that is planned to be removed in the next pytest release.
-
-Keeping it in a central location makes it easy to track what is deprecated and should
-be removed when the time comes.
-
-All constants defined in this module should be either PytestWarning instances or UnformattedWarning
-in case of warnings which need to format their messages.
-"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-from _pytest.warning_types import PytestDeprecationWarning
-from _pytest.warning_types import RemovedInPytest4Warning
-from _pytest.warning_types import UnformattedWarning
-
+"""
+This module contains deprecation messages and bits of code used elsewhere in the codebase
+that is planned to be removed in the next pytest release.
+
+Keeping it in a central location makes it easy to track what is deprecated and should
+be removed when the time comes.
+
+All constants defined in this module should be either PytestWarning instances or UnformattedWarning
+in case of warnings which need to format their messages.
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+from _pytest.warning_types import PytestDeprecationWarning
+from _pytest.warning_types import RemovedInPytest4Warning
+from _pytest.warning_types import UnformattedWarning
+
YIELD_TESTS = "yield tests were removed in pytest 4.0 - {name} will be ignored"
-
-
+
+
FIXTURE_FUNCTION_CALL = (
'Fixture "{name}" called directly. Fixtures are not meant to be called directly,\n'
"but are created automatically when test functions request them as parameters.\n"
"See https://docs.pytest.org/en/latest/fixture.html for more information about fixtures, and\n"
"https://docs.pytest.org/en/latest/deprecations.html#calling-fixtures-directly about how to update your code."
-)
-
-FIXTURE_NAMED_REQUEST = PytestDeprecationWarning(
- "'request' is a reserved name for fixtures and will raise an error in future versions"
-)
-
+)
+
+FIXTURE_NAMED_REQUEST = PytestDeprecationWarning(
+ "'request' is a reserved name for fixtures and will raise an error in future versions"
+)
+
CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supported, change to [tool:pytest] instead."
-
-GETFUNCARGVALUE = RemovedInPytest4Warning(
- "getfuncargvalue is deprecated, use getfixturevalue"
-)
-
+
+GETFUNCARGVALUE = RemovedInPytest4Warning(
+ "getfuncargvalue is deprecated, use getfixturevalue"
+)
+
RAISES_MESSAGE_PARAMETER = PytestDeprecationWarning(
"The 'message' parameter is deprecated.\n"
"(did you mean to use `match='some regex'` to check the exception message?)\n"
"Please see:\n"
" https://docs.pytest.org/en/4.6-maintenance/deprecations.html#message-parameter-of-pytest-raises"
-)
-
+)
+
RESULT_LOG = PytestDeprecationWarning(
"--result-log is deprecated and scheduled for removal in pytest 5.0.\n"
"See https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log for more information."
-)
-
+)
+
RAISES_EXEC = PytestDeprecationWarning(
"raises(..., 'code(as_a_string)') is deprecated, use the context manager form or use `exec()` directly\n\n"
"See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec"
-)
+)
WARNS_EXEC = PytestDeprecationWarning(
"warns(..., 'code(as_a_string)') is deprecated, use the context manager form or use `exec()` directly.\n\n"
"See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec"
-)
-
+)
+
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST = (
"Defining 'pytest_plugins' in a non-top-level conftest is no longer supported "
"because it affects the entire directory tree in a non-explicit way.\n"
@@ -66,31 +66,31 @@ PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST = (
" {}\n"
"For more information, visit:\n"
" https://docs.pytest.org/en/latest/deprecations.html#pytest-plugins-in-non-top-level-conftest-files"
-)
-
+)
+
PYTEST_CONFIG_GLOBAL = PytestDeprecationWarning(
"the `pytest.config` global is deprecated. Please use `request.config` "
"or `pytest_configure` (if you're a pytest plugin) instead."
-)
-
+)
+
PYTEST_ENSURETEMP = RemovedInPytest4Warning(
"pytest/tmpdir_factory.ensuretemp is deprecated, \n"
"please use the tmp_path fixture or tmp_path_factory.mktemp"
-)
-
+)
+
PYTEST_LOGWARNING = PytestDeprecationWarning(
"pytest_logwarning is deprecated, no longer being called, and will be removed soon\n"
"please use pytest_warning_captured instead"
-)
-
+)
+
PYTEST_WARNS_UNKNOWN_KWARGS = UnformattedWarning(
PytestDeprecationWarning,
"pytest.warns() got unexpected keyword arguments: {args!r}.\n"
"This will be an error in future versions.",
-)
-
+)
+
PYTEST_PARAM_UNKNOWN_KWARGS = UnformattedWarning(
PytestDeprecationWarning,
"pytest.param() got unexpected keyword arguments: {args!r}.\n"
"This will be an error in future versions.",
-)
+)
diff --git a/contrib/python/pytest/py2/_pytest/doctest.py b/contrib/python/pytest/py2/_pytest/doctest.py
index eaedb7bb13..659d24aeeb 100644
--- a/contrib/python/pytest/py2/_pytest/doctest.py
+++ b/contrib/python/pytest/py2/_pytest/doctest.py
@@ -1,359 +1,359 @@
# -*- coding: utf-8 -*-
-""" discover and run doctests in modules and test files."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
+""" discover and run doctests in modules and test files."""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
import inspect
-import platform
-import sys
-import traceback
+import platform
+import sys
+import traceback
import warnings
from contextlib import contextmanager
-
-import pytest
-from _pytest._code.code import ExceptionInfo
-from _pytest._code.code import ReprFileLocation
-from _pytest._code.code import TerminalRepr
+
+import pytest
+from _pytest._code.code import ExceptionInfo
+from _pytest._code.code import ReprFileLocation
+from _pytest._code.code import TerminalRepr
from _pytest.compat import safe_getattr
-from _pytest.fixtures import FixtureRequest
+from _pytest.fixtures import FixtureRequest
from _pytest.outcomes import Skipped
from _pytest.warning_types import PytestWarning
-
-DOCTEST_REPORT_CHOICE_NONE = "none"
-DOCTEST_REPORT_CHOICE_CDIFF = "cdiff"
-DOCTEST_REPORT_CHOICE_NDIFF = "ndiff"
-DOCTEST_REPORT_CHOICE_UDIFF = "udiff"
-DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE = "only_first_failure"
-
-DOCTEST_REPORT_CHOICES = (
- DOCTEST_REPORT_CHOICE_NONE,
- DOCTEST_REPORT_CHOICE_CDIFF,
- DOCTEST_REPORT_CHOICE_NDIFF,
- DOCTEST_REPORT_CHOICE_UDIFF,
- DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE,
-)
-
-# Lazy definition of runner class
-RUNNER_CLASS = None
-
-
-def pytest_addoption(parser):
- parser.addini(
- "doctest_optionflags",
- "option flags for doctests",
- type="args",
- default=["ELLIPSIS"],
- )
- parser.addini(
- "doctest_encoding", "encoding used for doctest files", default="utf-8"
- )
- group = parser.getgroup("collect")
- group.addoption(
- "--doctest-modules",
- action="store_true",
- default=False,
- help="run doctests in all .py modules",
- dest="doctestmodules",
- )
- group.addoption(
- "--doctest-report",
- type=str.lower,
- default="udiff",
- help="choose another output format for diffs on doctest failure",
- choices=DOCTEST_REPORT_CHOICES,
- dest="doctestreport",
- )
- group.addoption(
- "--doctest-glob",
- action="append",
- default=[],
- metavar="pat",
- help="doctests file matching pattern, default: test*.txt",
- dest="doctestglob",
- )
- group.addoption(
- "--doctest-ignore-import-errors",
- action="store_true",
- default=False,
- help="ignore doctest ImportErrors",
- dest="doctest_ignore_import_errors",
- )
- group.addoption(
- "--doctest-continue-on-failure",
- action="store_true",
- default=False,
- help="for a given doctest, continue to run after the first failure",
- dest="doctest_continue_on_failure",
- )
-
-
-def pytest_collect_file(path, parent):
- config = parent.config
- if path.ext == ".py":
- if config.option.doctestmodules and not _is_setup_py(config, path, parent):
- return DoctestModule(path, parent)
- elif _is_doctest(config, path, parent):
- return DoctestTextfile(path, parent)
-
-
-def _is_setup_py(config, path, parent):
- if path.basename != "setup.py":
- return False
- contents = path.read()
- return "setuptools" in contents or "distutils" in contents
-
-
-def _is_doctest(config, path, parent):
- if path.ext in (".txt", ".rst") and parent.session.isinitpath(path):
- return True
- globs = config.getoption("doctestglob") or ["test*.txt"]
- for glob in globs:
- if path.check(fnmatch=glob):
- return True
- return False
-
-
-class ReprFailDoctest(TerminalRepr):
- def __init__(self, reprlocation_lines):
- # List of (reprlocation, lines) tuples
- self.reprlocation_lines = reprlocation_lines
-
- def toterminal(self, tw):
- for reprlocation, lines in self.reprlocation_lines:
- for line in lines:
- tw.line(line)
- reprlocation.toterminal(tw)
-
-
-class MultipleDoctestFailures(Exception):
- def __init__(self, failures):
- super(MultipleDoctestFailures, self).__init__()
- self.failures = failures
-
-
-def _init_runner_class():
- import doctest
-
- class PytestDoctestRunner(doctest.DebugRunner):
- """
- Runner to collect failures. Note that the out variable in this case is
- a list instead of a stdout-like object
- """
-
- def __init__(
- self, checker=None, verbose=None, optionflags=0, continue_on_failure=True
- ):
- doctest.DebugRunner.__init__(
- self, checker=checker, verbose=verbose, optionflags=optionflags
- )
- self.continue_on_failure = continue_on_failure
-
- def report_failure(self, out, test, example, got):
- failure = doctest.DocTestFailure(test, example, got)
- if self.continue_on_failure:
- out.append(failure)
- else:
- raise failure
-
- def report_unexpected_exception(self, out, test, example, exc_info):
+
+DOCTEST_REPORT_CHOICE_NONE = "none"
+DOCTEST_REPORT_CHOICE_CDIFF = "cdiff"
+DOCTEST_REPORT_CHOICE_NDIFF = "ndiff"
+DOCTEST_REPORT_CHOICE_UDIFF = "udiff"
+DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE = "only_first_failure"
+
+DOCTEST_REPORT_CHOICES = (
+ DOCTEST_REPORT_CHOICE_NONE,
+ DOCTEST_REPORT_CHOICE_CDIFF,
+ DOCTEST_REPORT_CHOICE_NDIFF,
+ DOCTEST_REPORT_CHOICE_UDIFF,
+ DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE,
+)
+
+# Lazy definition of runner class
+RUNNER_CLASS = None
+
+
+def pytest_addoption(parser):
+ parser.addini(
+ "doctest_optionflags",
+ "option flags for doctests",
+ type="args",
+ default=["ELLIPSIS"],
+ )
+ parser.addini(
+ "doctest_encoding", "encoding used for doctest files", default="utf-8"
+ )
+ group = parser.getgroup("collect")
+ group.addoption(
+ "--doctest-modules",
+ action="store_true",
+ default=False,
+ help="run doctests in all .py modules",
+ dest="doctestmodules",
+ )
+ group.addoption(
+ "--doctest-report",
+ type=str.lower,
+ default="udiff",
+ help="choose another output format for diffs on doctest failure",
+ choices=DOCTEST_REPORT_CHOICES,
+ dest="doctestreport",
+ )
+ group.addoption(
+ "--doctest-glob",
+ action="append",
+ default=[],
+ metavar="pat",
+ help="doctests file matching pattern, default: test*.txt",
+ dest="doctestglob",
+ )
+ group.addoption(
+ "--doctest-ignore-import-errors",
+ action="store_true",
+ default=False,
+ help="ignore doctest ImportErrors",
+ dest="doctest_ignore_import_errors",
+ )
+ group.addoption(
+ "--doctest-continue-on-failure",
+ action="store_true",
+ default=False,
+ help="for a given doctest, continue to run after the first failure",
+ dest="doctest_continue_on_failure",
+ )
+
+
+def pytest_collect_file(path, parent):
+ config = parent.config
+ if path.ext == ".py":
+ if config.option.doctestmodules and not _is_setup_py(config, path, parent):
+ return DoctestModule(path, parent)
+ elif _is_doctest(config, path, parent):
+ return DoctestTextfile(path, parent)
+
+
+def _is_setup_py(config, path, parent):
+ if path.basename != "setup.py":
+ return False
+ contents = path.read()
+ return "setuptools" in contents or "distutils" in contents
+
+
+def _is_doctest(config, path, parent):
+ if path.ext in (".txt", ".rst") and parent.session.isinitpath(path):
+ return True
+ globs = config.getoption("doctestglob") or ["test*.txt"]
+ for glob in globs:
+ if path.check(fnmatch=glob):
+ return True
+ return False
+
+
+class ReprFailDoctest(TerminalRepr):
+ def __init__(self, reprlocation_lines):
+ # List of (reprlocation, lines) tuples
+ self.reprlocation_lines = reprlocation_lines
+
+ def toterminal(self, tw):
+ for reprlocation, lines in self.reprlocation_lines:
+ for line in lines:
+ tw.line(line)
+ reprlocation.toterminal(tw)
+
+
+class MultipleDoctestFailures(Exception):
+ def __init__(self, failures):
+ super(MultipleDoctestFailures, self).__init__()
+ self.failures = failures
+
+
+def _init_runner_class():
+ import doctest
+
+ class PytestDoctestRunner(doctest.DebugRunner):
+ """
+ Runner to collect failures. Note that the out variable in this case is
+ a list instead of a stdout-like object
+ """
+
+ def __init__(
+ self, checker=None, verbose=None, optionflags=0, continue_on_failure=True
+ ):
+ doctest.DebugRunner.__init__(
+ self, checker=checker, verbose=verbose, optionflags=optionflags
+ )
+ self.continue_on_failure = continue_on_failure
+
+ def report_failure(self, out, test, example, got):
+ failure = doctest.DocTestFailure(test, example, got)
+ if self.continue_on_failure:
+ out.append(failure)
+ else:
+ raise failure
+
+ def report_unexpected_exception(self, out, test, example, exc_info):
if isinstance(exc_info[1], Skipped):
raise exc_info[1]
- failure = doctest.UnexpectedException(test, example, exc_info)
- if self.continue_on_failure:
- out.append(failure)
- else:
- raise failure
-
- return PytestDoctestRunner
-
-
-def _get_runner(checker=None, verbose=None, optionflags=0, continue_on_failure=True):
- # We need this in order to do a lazy import on doctest
- global RUNNER_CLASS
- if RUNNER_CLASS is None:
- RUNNER_CLASS = _init_runner_class()
- return RUNNER_CLASS(
- checker=checker,
- verbose=verbose,
- optionflags=optionflags,
- continue_on_failure=continue_on_failure,
- )
-
-
-class DoctestItem(pytest.Item):
- def __init__(self, name, parent, runner=None, dtest=None):
- super(DoctestItem, self).__init__(name, parent)
- self.runner = runner
- self.dtest = dtest
- self.obj = None
- self.fixture_request = None
-
- def setup(self):
- if self.dtest is not None:
- self.fixture_request = _setup_fixtures(self)
- globs = dict(getfixture=self.fixture_request.getfixturevalue)
- for name, value in self.fixture_request.getfixturevalue(
- "doctest_namespace"
- ).items():
- globs[name] = value
- self.dtest.globs.update(globs)
-
- def runtest(self):
- _check_all_skipped(self.dtest)
- self._disable_output_capturing_for_darwin()
- failures = []
- self.runner.run(self.dtest, out=failures)
- if failures:
- raise MultipleDoctestFailures(failures)
-
- def _disable_output_capturing_for_darwin(self):
- """
- Disable output capturing. Otherwise, stdout is lost to doctest (#985)
- """
- if platform.system() != "Darwin":
- return
- capman = self.config.pluginmanager.getplugin("capturemanager")
- if capman:
- capman.suspend_global_capture(in_=True)
- out, err = capman.read_global_capture()
- sys.stdout.write(out)
- sys.stderr.write(err)
-
- def repr_failure(self, excinfo):
- import doctest
-
- failures = None
- if excinfo.errisinstance((doctest.DocTestFailure, doctest.UnexpectedException)):
- failures = [excinfo.value]
- elif excinfo.errisinstance(MultipleDoctestFailures):
- failures = excinfo.value.failures
-
- if failures is not None:
- reprlocation_lines = []
- for failure in failures:
- example = failure.example
- test = failure.test
- filename = test.filename
- if test.lineno is None:
- lineno = None
- else:
- lineno = test.lineno + example.lineno + 1
- message = type(failure).__name__
- reprlocation = ReprFileLocation(filename, lineno, message)
- checker = _get_checker()
- report_choice = _get_report_choice(
- self.config.getoption("doctestreport")
- )
- if lineno is not None:
- lines = failure.test.docstring.splitlines(False)
- # add line numbers to the left of the error message
- lines = [
- "%03d %s" % (i + test.lineno + 1, x)
- for (i, x) in enumerate(lines)
- ]
- # trim docstring error lines to 10
- lines = lines[max(example.lineno - 9, 0) : example.lineno + 1]
- else:
- lines = [
- "EXAMPLE LOCATION UNKNOWN, not showing all tests of that example"
- ]
- indent = ">>>"
- for line in example.source.splitlines():
- lines.append("??? %s %s" % (indent, line))
- indent = "..."
- if isinstance(failure, doctest.DocTestFailure):
- lines += checker.output_difference(
- example, failure.got, report_choice
- ).split("\n")
- else:
- inner_excinfo = ExceptionInfo(failure.exc_info)
- lines += ["UNEXPECTED EXCEPTION: %s" % repr(inner_excinfo.value)]
- lines += traceback.format_exception(*failure.exc_info)
- reprlocation_lines.append((reprlocation, lines))
- return ReprFailDoctest(reprlocation_lines)
- else:
- return super(DoctestItem, self).repr_failure(excinfo)
-
- def reportinfo(self):
- return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name
-
-
-def _get_flag_lookup():
- import doctest
-
- return dict(
- DONT_ACCEPT_TRUE_FOR_1=doctest.DONT_ACCEPT_TRUE_FOR_1,
- DONT_ACCEPT_BLANKLINE=doctest.DONT_ACCEPT_BLANKLINE,
- NORMALIZE_WHITESPACE=doctest.NORMALIZE_WHITESPACE,
- ELLIPSIS=doctest.ELLIPSIS,
- IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL,
- COMPARISON_FLAGS=doctest.COMPARISON_FLAGS,
- ALLOW_UNICODE=_get_allow_unicode_flag(),
- ALLOW_BYTES=_get_allow_bytes_flag(),
- )
-
-
-def get_optionflags(parent):
- optionflags_str = parent.config.getini("doctest_optionflags")
- flag_lookup_table = _get_flag_lookup()
- flag_acc = 0
- for flag in optionflags_str:
- flag_acc |= flag_lookup_table[flag]
- return flag_acc
-
-
-def _get_continue_on_failure(config):
- continue_on_failure = config.getvalue("doctest_continue_on_failure")
- if continue_on_failure:
- # We need to turn off this if we use pdb since we should stop at
- # the first failure
- if config.getvalue("usepdb"):
- continue_on_failure = False
- return continue_on_failure
-
-
-class DoctestTextfile(pytest.Module):
- obj = None
-
- def collect(self):
- import doctest
-
- # inspired by doctest.testfile; ideally we would use it directly,
- # but it doesn't support passing a custom checker
- encoding = self.config.getini("doctest_encoding")
- text = self.fspath.read_text(encoding)
- filename = str(self.fspath)
- name = self.fspath.basename
- globs = {"__name__": "__main__"}
-
- optionflags = get_optionflags(self)
-
- runner = _get_runner(
- verbose=0,
- optionflags=optionflags,
- checker=_get_checker(),
- continue_on_failure=_get_continue_on_failure(self.config),
- )
- _fix_spoof_python2(runner, encoding)
-
- parser = doctest.DocTestParser()
- test = parser.get_doctest(text, globs, name, filename, 0)
- if test.examples:
- yield DoctestItem(test.name, self, runner, test)
-
-
-def _check_all_skipped(test):
- """raises pytest.skip() if all examples in the given DocTest have the SKIP
- option set.
- """
- import doctest
-
- all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples)
- if all_skipped:
- pytest.skip("all tests skipped by +SKIP option")
-
-
+ failure = doctest.UnexpectedException(test, example, exc_info)
+ if self.continue_on_failure:
+ out.append(failure)
+ else:
+ raise failure
+
+ return PytestDoctestRunner
+
+
+def _get_runner(checker=None, verbose=None, optionflags=0, continue_on_failure=True):
+ # We need this in order to do a lazy import on doctest
+ global RUNNER_CLASS
+ if RUNNER_CLASS is None:
+ RUNNER_CLASS = _init_runner_class()
+ return RUNNER_CLASS(
+ checker=checker,
+ verbose=verbose,
+ optionflags=optionflags,
+ continue_on_failure=continue_on_failure,
+ )
+
+
+class DoctestItem(pytest.Item):
+ def __init__(self, name, parent, runner=None, dtest=None):
+ super(DoctestItem, self).__init__(name, parent)
+ self.runner = runner
+ self.dtest = dtest
+ self.obj = None
+ self.fixture_request = None
+
+ def setup(self):
+ if self.dtest is not None:
+ self.fixture_request = _setup_fixtures(self)
+ globs = dict(getfixture=self.fixture_request.getfixturevalue)
+ for name, value in self.fixture_request.getfixturevalue(
+ "doctest_namespace"
+ ).items():
+ globs[name] = value
+ self.dtest.globs.update(globs)
+
+ def runtest(self):
+ _check_all_skipped(self.dtest)
+ self._disable_output_capturing_for_darwin()
+ failures = []
+ self.runner.run(self.dtest, out=failures)
+ if failures:
+ raise MultipleDoctestFailures(failures)
+
+ def _disable_output_capturing_for_darwin(self):
+ """
+ Disable output capturing. Otherwise, stdout is lost to doctest (#985)
+ """
+ if platform.system() != "Darwin":
+ return
+ capman = self.config.pluginmanager.getplugin("capturemanager")
+ if capman:
+ capman.suspend_global_capture(in_=True)
+ out, err = capman.read_global_capture()
+ sys.stdout.write(out)
+ sys.stderr.write(err)
+
+ def repr_failure(self, excinfo):
+ import doctest
+
+ failures = None
+ if excinfo.errisinstance((doctest.DocTestFailure, doctest.UnexpectedException)):
+ failures = [excinfo.value]
+ elif excinfo.errisinstance(MultipleDoctestFailures):
+ failures = excinfo.value.failures
+
+ if failures is not None:
+ reprlocation_lines = []
+ for failure in failures:
+ example = failure.example
+ test = failure.test
+ filename = test.filename
+ if test.lineno is None:
+ lineno = None
+ else:
+ lineno = test.lineno + example.lineno + 1
+ message = type(failure).__name__
+ reprlocation = ReprFileLocation(filename, lineno, message)
+ checker = _get_checker()
+ report_choice = _get_report_choice(
+ self.config.getoption("doctestreport")
+ )
+ if lineno is not None:
+ lines = failure.test.docstring.splitlines(False)
+ # add line numbers to the left of the error message
+ lines = [
+ "%03d %s" % (i + test.lineno + 1, x)
+ for (i, x) in enumerate(lines)
+ ]
+ # trim docstring error lines to 10
+ lines = lines[max(example.lineno - 9, 0) : example.lineno + 1]
+ else:
+ lines = [
+ "EXAMPLE LOCATION UNKNOWN, not showing all tests of that example"
+ ]
+ indent = ">>>"
+ for line in example.source.splitlines():
+ lines.append("??? %s %s" % (indent, line))
+ indent = "..."
+ if isinstance(failure, doctest.DocTestFailure):
+ lines += checker.output_difference(
+ example, failure.got, report_choice
+ ).split("\n")
+ else:
+ inner_excinfo = ExceptionInfo(failure.exc_info)
+ lines += ["UNEXPECTED EXCEPTION: %s" % repr(inner_excinfo.value)]
+ lines += traceback.format_exception(*failure.exc_info)
+ reprlocation_lines.append((reprlocation, lines))
+ return ReprFailDoctest(reprlocation_lines)
+ else:
+ return super(DoctestItem, self).repr_failure(excinfo)
+
+ def reportinfo(self):
+ return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name
+
+
+def _get_flag_lookup():
+ import doctest
+
+ return dict(
+ DONT_ACCEPT_TRUE_FOR_1=doctest.DONT_ACCEPT_TRUE_FOR_1,
+ DONT_ACCEPT_BLANKLINE=doctest.DONT_ACCEPT_BLANKLINE,
+ NORMALIZE_WHITESPACE=doctest.NORMALIZE_WHITESPACE,
+ ELLIPSIS=doctest.ELLIPSIS,
+ IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL,
+ COMPARISON_FLAGS=doctest.COMPARISON_FLAGS,
+ ALLOW_UNICODE=_get_allow_unicode_flag(),
+ ALLOW_BYTES=_get_allow_bytes_flag(),
+ )
+
+
+def get_optionflags(parent):
+ optionflags_str = parent.config.getini("doctest_optionflags")
+ flag_lookup_table = _get_flag_lookup()
+ flag_acc = 0
+ for flag in optionflags_str:
+ flag_acc |= flag_lookup_table[flag]
+ return flag_acc
+
+
+def _get_continue_on_failure(config):
+ continue_on_failure = config.getvalue("doctest_continue_on_failure")
+ if continue_on_failure:
+ # We need to turn off this if we use pdb since we should stop at
+ # the first failure
+ if config.getvalue("usepdb"):
+ continue_on_failure = False
+ return continue_on_failure
+
+
+class DoctestTextfile(pytest.Module):
+ obj = None
+
+ def collect(self):
+ import doctest
+
+ # inspired by doctest.testfile; ideally we would use it directly,
+ # but it doesn't support passing a custom checker
+ encoding = self.config.getini("doctest_encoding")
+ text = self.fspath.read_text(encoding)
+ filename = str(self.fspath)
+ name = self.fspath.basename
+ globs = {"__name__": "__main__"}
+
+ optionflags = get_optionflags(self)
+
+ runner = _get_runner(
+ verbose=0,
+ optionflags=optionflags,
+ checker=_get_checker(),
+ continue_on_failure=_get_continue_on_failure(self.config),
+ )
+ _fix_spoof_python2(runner, encoding)
+
+ parser = doctest.DocTestParser()
+ test = parser.get_doctest(text, globs, name, filename, 0)
+ if test.examples:
+ yield DoctestItem(test.name, self, runner, test)
+
+
+def _check_all_skipped(test):
+ """raises pytest.skip() if all examples in the given DocTest have the SKIP
+ option set.
+ """
+ import doctest
+
+ all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples)
+ if all_skipped:
+ pytest.skip("all tests skipped by +SKIP option")
+
+
def _is_mocked(obj):
"""
returns if a object is possibly a mock object by checking the existence of a highly improbable attribute
@@ -396,10 +396,10 @@ def _patch_unwrap_mock_aware():
inspect.unwrap = real_unwrap
-class DoctestModule(pytest.Module):
- def collect(self):
- import doctest
-
+class DoctestModule(pytest.Module):
+ def collect(self):
+ import doctest
+
class MockAwareDocTestFinder(doctest.DocTestFinder):
"""
a hackish doctest finder that overrides stdlib internals to fix a stdlib bug
@@ -417,167 +417,167 @@ class DoctestModule(pytest.Module):
self, tests, obj, name, module, source_lines, globs, seen
)
- if self.fspath.basename == "conftest.py":
- module = self.config.pluginmanager._importconftest(self.fspath)
- else:
- try:
- module = self.fspath.pyimport()
- except ImportError:
- if self.config.getvalue("doctest_ignore_import_errors"):
- pytest.skip("unable to import module %r" % self.fspath)
- else:
- raise
- # uses internal doctest module parsing mechanism
+ if self.fspath.basename == "conftest.py":
+ module = self.config.pluginmanager._importconftest(self.fspath)
+ else:
+ try:
+ module = self.fspath.pyimport()
+ except ImportError:
+ if self.config.getvalue("doctest_ignore_import_errors"):
+ pytest.skip("unable to import module %r" % self.fspath)
+ else:
+ raise
+ # uses internal doctest module parsing mechanism
finder = MockAwareDocTestFinder()
- optionflags = get_optionflags(self)
- runner = _get_runner(
- verbose=0,
- optionflags=optionflags,
- checker=_get_checker(),
- continue_on_failure=_get_continue_on_failure(self.config),
- )
-
- for test in finder.find(module, module.__name__):
- if test.examples: # skip empty doctests
- yield DoctestItem(test.name, self, runner, test)
-
-
-def _setup_fixtures(doctest_item):
- """
- Used by DoctestTextfile and DoctestItem to setup fixture information.
- """
-
- def func():
- pass
-
- doctest_item.funcargs = {}
- fm = doctest_item.session._fixturemanager
- doctest_item._fixtureinfo = fm.getfixtureinfo(
- node=doctest_item, func=func, cls=None, funcargs=False
- )
- fixture_request = FixtureRequest(doctest_item)
- fixture_request._fillfixtures()
- return fixture_request
-
-
-def _get_checker():
- """
- Returns a doctest.OutputChecker subclass that takes in account the
- ALLOW_UNICODE option to ignore u'' prefixes in strings and ALLOW_BYTES
- to strip b'' prefixes.
- Useful when the same doctest should run in Python 2 and Python 3.
-
- An inner class is used to avoid importing "doctest" at the module
- level.
- """
- if hasattr(_get_checker, "LiteralsOutputChecker"):
- return _get_checker.LiteralsOutputChecker()
-
- import doctest
- import re
-
- class LiteralsOutputChecker(doctest.OutputChecker):
- """
- Copied from doctest_nose_plugin.py from the nltk project:
- https://github.com/nltk/nltk
-
- Further extended to also support byte literals.
- """
-
- _unicode_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE)
- _bytes_literal_re = re.compile(r"(\W|^)[bB]([rR]?[\'\"])", re.UNICODE)
-
- def check_output(self, want, got, optionflags):
- res = doctest.OutputChecker.check_output(self, want, got, optionflags)
- if res:
- return True
-
- allow_unicode = optionflags & _get_allow_unicode_flag()
- allow_bytes = optionflags & _get_allow_bytes_flag()
- if not allow_unicode and not allow_bytes:
- return False
-
- else: # pragma: no cover
-
- def remove_prefixes(regex, txt):
- return re.sub(regex, r"\1\2", txt)
-
- if allow_unicode:
- want = remove_prefixes(self._unicode_literal_re, want)
- got = remove_prefixes(self._unicode_literal_re, got)
- if allow_bytes:
- want = remove_prefixes(self._bytes_literal_re, want)
- got = remove_prefixes(self._bytes_literal_re, got)
- res = doctest.OutputChecker.check_output(self, want, got, optionflags)
- return res
-
- _get_checker.LiteralsOutputChecker = LiteralsOutputChecker
- return _get_checker.LiteralsOutputChecker()
-
-
-def _get_allow_unicode_flag():
- """
- Registers and returns the ALLOW_UNICODE flag.
- """
- import doctest
-
- return doctest.register_optionflag("ALLOW_UNICODE")
-
-
-def _get_allow_bytes_flag():
- """
- Registers and returns the ALLOW_BYTES flag.
- """
- import doctest
-
- return doctest.register_optionflag("ALLOW_BYTES")
-
-
-def _get_report_choice(key):
- """
- This function returns the actual `doctest` module flag value, we want to do it as late as possible to avoid
- importing `doctest` and all its dependencies when parsing options, as it adds overhead and breaks tests.
- """
- import doctest
-
- return {
- DOCTEST_REPORT_CHOICE_UDIFF: doctest.REPORT_UDIFF,
- DOCTEST_REPORT_CHOICE_CDIFF: doctest.REPORT_CDIFF,
- DOCTEST_REPORT_CHOICE_NDIFF: doctest.REPORT_NDIFF,
- DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE: doctest.REPORT_ONLY_FIRST_FAILURE,
- DOCTEST_REPORT_CHOICE_NONE: 0,
- }[key]
-
-
-def _fix_spoof_python2(runner, encoding):
- """
- Installs a "SpoofOut" into the given DebugRunner so it properly deals with unicode output. This
- should patch only doctests for text files because they don't have a way to declare their
- encoding. Doctests in docstrings from Python modules don't have the same problem given that
- Python already decoded the strings.
-
- This fixes the problem related in issue #2434.
- """
- from _pytest.compat import _PY2
-
- if not _PY2:
- return
-
- from doctest import _SpoofOut
-
- class UnicodeSpoof(_SpoofOut):
- def getvalue(self):
- result = _SpoofOut.getvalue(self)
- if encoding and isinstance(result, bytes):
- result = result.decode(encoding)
- return result
-
- runner._fakeout = UnicodeSpoof()
-
-
-@pytest.fixture(scope="session")
-def doctest_namespace():
- """
- Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests.
- """
- return dict()
+ optionflags = get_optionflags(self)
+ runner = _get_runner(
+ verbose=0,
+ optionflags=optionflags,
+ checker=_get_checker(),
+ continue_on_failure=_get_continue_on_failure(self.config),
+ )
+
+ for test in finder.find(module, module.__name__):
+ if test.examples: # skip empty doctests
+ yield DoctestItem(test.name, self, runner, test)
+
+
+def _setup_fixtures(doctest_item):
+ """
+ Used by DoctestTextfile and DoctestItem to setup fixture information.
+ """
+
+ def func():
+ pass
+
+ doctest_item.funcargs = {}
+ fm = doctest_item.session._fixturemanager
+ doctest_item._fixtureinfo = fm.getfixtureinfo(
+ node=doctest_item, func=func, cls=None, funcargs=False
+ )
+ fixture_request = FixtureRequest(doctest_item)
+ fixture_request._fillfixtures()
+ return fixture_request
+
+
+def _get_checker():
+ """
+ Returns a doctest.OutputChecker subclass that takes in account the
+ ALLOW_UNICODE option to ignore u'' prefixes in strings and ALLOW_BYTES
+ to strip b'' prefixes.
+ Useful when the same doctest should run in Python 2 and Python 3.
+
+ An inner class is used to avoid importing "doctest" at the module
+ level.
+ """
+ if hasattr(_get_checker, "LiteralsOutputChecker"):
+ return _get_checker.LiteralsOutputChecker()
+
+ import doctest
+ import re
+
+ class LiteralsOutputChecker(doctest.OutputChecker):
+ """
+ Copied from doctest_nose_plugin.py from the nltk project:
+ https://github.com/nltk/nltk
+
+ Further extended to also support byte literals.
+ """
+
+ _unicode_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE)
+ _bytes_literal_re = re.compile(r"(\W|^)[bB]([rR]?[\'\"])", re.UNICODE)
+
+ def check_output(self, want, got, optionflags):
+ res = doctest.OutputChecker.check_output(self, want, got, optionflags)
+ if res:
+ return True
+
+ allow_unicode = optionflags & _get_allow_unicode_flag()
+ allow_bytes = optionflags & _get_allow_bytes_flag()
+ if not allow_unicode and not allow_bytes:
+ return False
+
+ else: # pragma: no cover
+
+ def remove_prefixes(regex, txt):
+ return re.sub(regex, r"\1\2", txt)
+
+ if allow_unicode:
+ want = remove_prefixes(self._unicode_literal_re, want)
+ got = remove_prefixes(self._unicode_literal_re, got)
+ if allow_bytes:
+ want = remove_prefixes(self._bytes_literal_re, want)
+ got = remove_prefixes(self._bytes_literal_re, got)
+ res = doctest.OutputChecker.check_output(self, want, got, optionflags)
+ return res
+
+ _get_checker.LiteralsOutputChecker = LiteralsOutputChecker
+ return _get_checker.LiteralsOutputChecker()
+
+
+def _get_allow_unicode_flag():
+ """
+ Registers and returns the ALLOW_UNICODE flag.
+ """
+ import doctest
+
+ return doctest.register_optionflag("ALLOW_UNICODE")
+
+
+def _get_allow_bytes_flag():
+ """
+ Registers and returns the ALLOW_BYTES flag.
+ """
+ import doctest
+
+ return doctest.register_optionflag("ALLOW_BYTES")
+
+
+def _get_report_choice(key):
+ """
+ This function returns the actual `doctest` module flag value, we want to do it as late as possible to avoid
+ importing `doctest` and all its dependencies when parsing options, as it adds overhead and breaks tests.
+ """
+ import doctest
+
+ return {
+ DOCTEST_REPORT_CHOICE_UDIFF: doctest.REPORT_UDIFF,
+ DOCTEST_REPORT_CHOICE_CDIFF: doctest.REPORT_CDIFF,
+ DOCTEST_REPORT_CHOICE_NDIFF: doctest.REPORT_NDIFF,
+ DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE: doctest.REPORT_ONLY_FIRST_FAILURE,
+ DOCTEST_REPORT_CHOICE_NONE: 0,
+ }[key]
+
+
+def _fix_spoof_python2(runner, encoding):
+ """
+ Installs a "SpoofOut" into the given DebugRunner so it properly deals with unicode output. This
+ should patch only doctests for text files because they don't have a way to declare their
+ encoding. Doctests in docstrings from Python modules don't have the same problem given that
+ Python already decoded the strings.
+
+ This fixes the problem related in issue #2434.
+ """
+ from _pytest.compat import _PY2
+
+ if not _PY2:
+ return
+
+ from doctest import _SpoofOut
+
+ class UnicodeSpoof(_SpoofOut):
+ def getvalue(self):
+ result = _SpoofOut.getvalue(self)
+ if encoding and isinstance(result, bytes):
+ result = result.decode(encoding)
+ return result
+
+ runner._fakeout = UnicodeSpoof()
+
+
+@pytest.fixture(scope="session")
+def doctest_namespace():
+ """
+ Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests.
+ """
+ return dict()
diff --git a/contrib/python/pytest/py2/_pytest/fixtures.py b/contrib/python/pytest/py2/_pytest/fixtures.py
index bbf88a44fc..280a48608b 100644
--- a/contrib/python/pytest/py2/_pytest/fixtures.py
+++ b/contrib/python/pytest/py2/_pytest/fixtures.py
@@ -1,667 +1,667 @@
# -*- coding: utf-8 -*-
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import functools
-import inspect
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import functools
+import inspect
import itertools
-import sys
-import warnings
-from collections import defaultdict
-from collections import deque
-from collections import OrderedDict
-
-import attr
-import py
-import six
-
-import _pytest
-from _pytest import nodes
+import sys
+import warnings
+from collections import defaultdict
+from collections import deque
+from collections import OrderedDict
+
+import attr
+import py
+import six
+
+import _pytest
+from _pytest import nodes
from _pytest._code.code import FormattedExcinfo
-from _pytest._code.code import TerminalRepr
-from _pytest.compat import _format_args
-from _pytest.compat import _PytestWrapper
-from _pytest.compat import exc_clear
-from _pytest.compat import FuncargnamesCompatAttr
-from _pytest.compat import get_real_func
-from _pytest.compat import get_real_method
-from _pytest.compat import getfslineno
-from _pytest.compat import getfuncargnames
-from _pytest.compat import getimfunc
-from _pytest.compat import getlocation
-from _pytest.compat import is_generator
-from _pytest.compat import isclass
-from _pytest.compat import NOTSET
-from _pytest.compat import safe_getattr
-from _pytest.deprecated import FIXTURE_FUNCTION_CALL
-from _pytest.deprecated import FIXTURE_NAMED_REQUEST
-from _pytest.outcomes import fail
-from _pytest.outcomes import TEST_OUTCOME
-
-
-@attr.s(frozen=True)
-class PseudoFixtureDef(object):
- cached_result = attr.ib()
- scope = attr.ib()
-
-
-def pytest_sessionstart(session):
- import _pytest.python
- import _pytest.nodes
-
- scopename2class.update(
- {
- "package": _pytest.python.Package,
- "class": _pytest.python.Class,
- "module": _pytest.python.Module,
- "function": _pytest.nodes.Item,
- "session": _pytest.main.Session,
- }
- )
- session._fixturemanager = FixtureManager(session)
-
-
-scopename2class = {}
-
-
-scope2props = dict(session=())
-scope2props["package"] = ("fspath",)
-scope2props["module"] = ("fspath", "module")
-scope2props["class"] = scope2props["module"] + ("cls",)
-scope2props["instance"] = scope2props["class"] + ("instance",)
-scope2props["function"] = scope2props["instance"] + ("function", "keywords")
-
-
-def scopeproperty(name=None, doc=None):
- def decoratescope(func):
- scopename = name or func.__name__
-
- def provide(self):
- if func.__name__ in scope2props[self.scope]:
- return func(self)
- raise AttributeError(
- "%s not available in %s-scoped context" % (scopename, self.scope)
- )
-
- return property(provide, None, None, func.__doc__)
-
- return decoratescope
-
-
-def get_scope_package(node, fixturedef):
- import pytest
-
- cls = pytest.Package
- current = node
- fixture_package_name = "%s/%s" % (fixturedef.baseid, "__init__.py")
- while current and (
- type(current) is not cls or fixture_package_name != current.nodeid
- ):
- current = current.parent
- if current is None:
- return node.session
- return current
-
-
-def get_scope_node(node, scope):
- cls = scopename2class.get(scope)
- if cls is None:
- raise ValueError("unknown scope")
- return node.getparent(cls)
-
-
-def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager):
- # this function will transform all collected calls to a functions
- # if they use direct funcargs (i.e. direct parametrization)
- # because we want later test execution to be able to rely on
- # an existing FixtureDef structure for all arguments.
- # XXX we can probably avoid this algorithm if we modify CallSpec2
- # to directly care for creating the fixturedefs within its methods.
- if not metafunc._calls[0].funcargs:
- return # this function call does not have direct parametrization
- # collect funcargs of all callspecs into a list of values
- arg2params = {}
- arg2scope = {}
- for callspec in metafunc._calls:
- for argname, argvalue in callspec.funcargs.items():
- assert argname not in callspec.params
- callspec.params[argname] = argvalue
- arg2params_list = arg2params.setdefault(argname, [])
- callspec.indices[argname] = len(arg2params_list)
- arg2params_list.append(argvalue)
- if argname not in arg2scope:
- scopenum = callspec._arg2scopenum.get(argname, scopenum_function)
- arg2scope[argname] = scopes[scopenum]
- callspec.funcargs.clear()
-
- # register artificial FixtureDef's so that later at test execution
- # time we can rely on a proper FixtureDef to exist for fixture setup.
- arg2fixturedefs = metafunc._arg2fixturedefs
- for argname, valuelist in arg2params.items():
- # if we have a scope that is higher than function we need
- # to make sure we only ever create an according fixturedef on
- # a per-scope basis. We thus store and cache the fixturedef on the
- # node related to the scope.
- scope = arg2scope[argname]
- node = None
- if scope != "function":
- node = get_scope_node(collector, scope)
- if node is None:
- assert scope == "class" and isinstance(collector, _pytest.python.Module)
- # use module-level collector for class-scope (for now)
- node = collector
- if node and argname in node._name2pseudofixturedef:
- arg2fixturedefs[argname] = [node._name2pseudofixturedef[argname]]
- else:
- fixturedef = FixtureDef(
- fixturemanager,
- "",
- argname,
- get_direct_param_fixture_func,
- arg2scope[argname],
- valuelist,
- False,
- False,
- )
- arg2fixturedefs[argname] = [fixturedef]
- if node is not None:
- node._name2pseudofixturedef[argname] = fixturedef
-
-
-def getfixturemarker(obj):
- """ return fixturemarker or None if it doesn't exist or raised
- exceptions."""
- try:
- return getattr(obj, "_pytestfixturefunction", None)
- except TEST_OUTCOME:
- # some objects raise errors like request (from flask import request)
- # we don't expect them to be fixture functions
- return None
-
-
-def get_parametrized_fixture_keys(item, scopenum):
- """ return list of keys for all parametrized arguments which match
- the specified scope. """
- assert scopenum < scopenum_function # function
- try:
- cs = item.callspec
- except AttributeError:
- pass
- else:
- # cs.indices.items() is random order of argnames. Need to
- # sort this so that different calls to
- # get_parametrized_fixture_keys will be deterministic.
- for argname, param_index in sorted(cs.indices.items()):
- if cs._arg2scopenum[argname] != scopenum:
- continue
- if scopenum == 0: # session
- key = (argname, param_index)
- elif scopenum == 1: # package
- key = (argname, param_index, item.fspath.dirpath())
- elif scopenum == 2: # module
- key = (argname, param_index, item.fspath)
- elif scopenum == 3: # class
- key = (argname, param_index, item.fspath, item.cls)
- yield key
-
-
-# algorithm for sorting on a per-parametrized resource setup basis
-# it is called for scopenum==0 (session) first and performs sorting
-# down to the lower scopes such as to minimize number of "high scope"
-# setups and teardowns
-
-
-def reorder_items(items):
- argkeys_cache = {}
- items_by_argkey = {}
- for scopenum in range(0, scopenum_function):
- argkeys_cache[scopenum] = d = {}
- items_by_argkey[scopenum] = item_d = defaultdict(deque)
- for item in items:
- keys = OrderedDict.fromkeys(get_parametrized_fixture_keys(item, scopenum))
- if keys:
- d[item] = keys
- for key in keys:
- item_d[key].append(item)
- items = OrderedDict.fromkeys(items)
- return list(reorder_items_atscope(items, argkeys_cache, items_by_argkey, 0))
-
-
-def fix_cache_order(item, argkeys_cache, items_by_argkey):
- for scopenum in range(0, scopenum_function):
- for key in argkeys_cache[scopenum].get(item, []):
- items_by_argkey[scopenum][key].appendleft(item)
-
-
-def reorder_items_atscope(items, argkeys_cache, items_by_argkey, scopenum):
- if scopenum >= scopenum_function or len(items) < 3:
- return items
- ignore = set()
- items_deque = deque(items)
- items_done = OrderedDict()
- scoped_items_by_argkey = items_by_argkey[scopenum]
- scoped_argkeys_cache = argkeys_cache[scopenum]
- while items_deque:
- no_argkey_group = OrderedDict()
- slicing_argkey = None
- while items_deque:
- item = items_deque.popleft()
- if item in items_done or item in no_argkey_group:
- continue
- argkeys = OrderedDict.fromkeys(
- k for k in scoped_argkeys_cache.get(item, []) if k not in ignore
- )
- if not argkeys:
- no_argkey_group[item] = None
- else:
- slicing_argkey, _ = argkeys.popitem()
- # we don't have to remove relevant items from later in the deque because they'll just be ignored
- matching_items = [
- i for i in scoped_items_by_argkey[slicing_argkey] if i in items
- ]
- for i in reversed(matching_items):
- fix_cache_order(i, argkeys_cache, items_by_argkey)
- items_deque.appendleft(i)
- break
- if no_argkey_group:
- no_argkey_group = reorder_items_atscope(
- no_argkey_group, argkeys_cache, items_by_argkey, scopenum + 1
- )
- for item in no_argkey_group:
- items_done[item] = None
- ignore.add(slicing_argkey)
- return items_done
-
-
-def fillfixtures(function):
- """ fill missing funcargs for a test function. """
- try:
- request = function._request
- except AttributeError:
- # XXX this special code path is only expected to execute
- # with the oejskit plugin. It uses classes with funcargs
- # and we thus have to work a bit to allow this.
- fm = function.session._fixturemanager
- fi = fm.getfixtureinfo(function.parent, function.obj, None)
- function._fixtureinfo = fi
- request = function._request = FixtureRequest(function)
- request._fillfixtures()
- # prune out funcargs for jstests
- newfuncargs = {}
- for name in fi.argnames:
- newfuncargs[name] = function.funcargs[name]
- function.funcargs = newfuncargs
- else:
- request._fillfixtures()
-
-
-def get_direct_param_fixture_func(request):
- return request.param
-
-
-@attr.s(slots=True)
-class FuncFixtureInfo(object):
- # original function argument names
- argnames = attr.ib(type=tuple)
- # argnames that function immediately requires. These include argnames +
- # fixture names specified via usefixtures and via autouse=True in fixture
- # definitions.
- initialnames = attr.ib(type=tuple)
+from _pytest._code.code import TerminalRepr
+from _pytest.compat import _format_args
+from _pytest.compat import _PytestWrapper
+from _pytest.compat import exc_clear
+from _pytest.compat import FuncargnamesCompatAttr
+from _pytest.compat import get_real_func
+from _pytest.compat import get_real_method
+from _pytest.compat import getfslineno
+from _pytest.compat import getfuncargnames
+from _pytest.compat import getimfunc
+from _pytest.compat import getlocation
+from _pytest.compat import is_generator
+from _pytest.compat import isclass
+from _pytest.compat import NOTSET
+from _pytest.compat import safe_getattr
+from _pytest.deprecated import FIXTURE_FUNCTION_CALL
+from _pytest.deprecated import FIXTURE_NAMED_REQUEST
+from _pytest.outcomes import fail
+from _pytest.outcomes import TEST_OUTCOME
+
+
+@attr.s(frozen=True)
+class PseudoFixtureDef(object):
+ cached_result = attr.ib()
+ scope = attr.ib()
+
+
+def pytest_sessionstart(session):
+ import _pytest.python
+ import _pytest.nodes
+
+ scopename2class.update(
+ {
+ "package": _pytest.python.Package,
+ "class": _pytest.python.Class,
+ "module": _pytest.python.Module,
+ "function": _pytest.nodes.Item,
+ "session": _pytest.main.Session,
+ }
+ )
+ session._fixturemanager = FixtureManager(session)
+
+
+scopename2class = {}
+
+
+scope2props = dict(session=())
+scope2props["package"] = ("fspath",)
+scope2props["module"] = ("fspath", "module")
+scope2props["class"] = scope2props["module"] + ("cls",)
+scope2props["instance"] = scope2props["class"] + ("instance",)
+scope2props["function"] = scope2props["instance"] + ("function", "keywords")
+
+
+def scopeproperty(name=None, doc=None):
+ def decoratescope(func):
+ scopename = name or func.__name__
+
+ def provide(self):
+ if func.__name__ in scope2props[self.scope]:
+ return func(self)
+ raise AttributeError(
+ "%s not available in %s-scoped context" % (scopename, self.scope)
+ )
+
+ return property(provide, None, None, func.__doc__)
+
+ return decoratescope
+
+
+def get_scope_package(node, fixturedef):
+ import pytest
+
+ cls = pytest.Package
+ current = node
+ fixture_package_name = "%s/%s" % (fixturedef.baseid, "__init__.py")
+ while current and (
+ type(current) is not cls or fixture_package_name != current.nodeid
+ ):
+ current = current.parent
+ if current is None:
+ return node.session
+ return current
+
+
+def get_scope_node(node, scope):
+ cls = scopename2class.get(scope)
+ if cls is None:
+ raise ValueError("unknown scope")
+ return node.getparent(cls)
+
+
+def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager):
+ # this function will transform all collected calls to a functions
+ # if they use direct funcargs (i.e. direct parametrization)
+ # because we want later test execution to be able to rely on
+ # an existing FixtureDef structure for all arguments.
+ # XXX we can probably avoid this algorithm if we modify CallSpec2
+ # to directly care for creating the fixturedefs within its methods.
+ if not metafunc._calls[0].funcargs:
+ return # this function call does not have direct parametrization
+ # collect funcargs of all callspecs into a list of values
+ arg2params = {}
+ arg2scope = {}
+ for callspec in metafunc._calls:
+ for argname, argvalue in callspec.funcargs.items():
+ assert argname not in callspec.params
+ callspec.params[argname] = argvalue
+ arg2params_list = arg2params.setdefault(argname, [])
+ callspec.indices[argname] = len(arg2params_list)
+ arg2params_list.append(argvalue)
+ if argname not in arg2scope:
+ scopenum = callspec._arg2scopenum.get(argname, scopenum_function)
+ arg2scope[argname] = scopes[scopenum]
+ callspec.funcargs.clear()
+
+ # register artificial FixtureDef's so that later at test execution
+ # time we can rely on a proper FixtureDef to exist for fixture setup.
+ arg2fixturedefs = metafunc._arg2fixturedefs
+ for argname, valuelist in arg2params.items():
+ # if we have a scope that is higher than function we need
+ # to make sure we only ever create an according fixturedef on
+ # a per-scope basis. We thus store and cache the fixturedef on the
+ # node related to the scope.
+ scope = arg2scope[argname]
+ node = None
+ if scope != "function":
+ node = get_scope_node(collector, scope)
+ if node is None:
+ assert scope == "class" and isinstance(collector, _pytest.python.Module)
+ # use module-level collector for class-scope (for now)
+ node = collector
+ if node and argname in node._name2pseudofixturedef:
+ arg2fixturedefs[argname] = [node._name2pseudofixturedef[argname]]
+ else:
+ fixturedef = FixtureDef(
+ fixturemanager,
+ "",
+ argname,
+ get_direct_param_fixture_func,
+ arg2scope[argname],
+ valuelist,
+ False,
+ False,
+ )
+ arg2fixturedefs[argname] = [fixturedef]
+ if node is not None:
+ node._name2pseudofixturedef[argname] = fixturedef
+
+
+def getfixturemarker(obj):
+ """ return fixturemarker or None if it doesn't exist or raised
+ exceptions."""
+ try:
+ return getattr(obj, "_pytestfixturefunction", None)
+ except TEST_OUTCOME:
+ # some objects raise errors like request (from flask import request)
+ # we don't expect them to be fixture functions
+ return None
+
+
+def get_parametrized_fixture_keys(item, scopenum):
+ """ return list of keys for all parametrized arguments which match
+ the specified scope. """
+ assert scopenum < scopenum_function # function
+ try:
+ cs = item.callspec
+ except AttributeError:
+ pass
+ else:
+ # cs.indices.items() is random order of argnames. Need to
+ # sort this so that different calls to
+ # get_parametrized_fixture_keys will be deterministic.
+ for argname, param_index in sorted(cs.indices.items()):
+ if cs._arg2scopenum[argname] != scopenum:
+ continue
+ if scopenum == 0: # session
+ key = (argname, param_index)
+ elif scopenum == 1: # package
+ key = (argname, param_index, item.fspath.dirpath())
+ elif scopenum == 2: # module
+ key = (argname, param_index, item.fspath)
+ elif scopenum == 3: # class
+ key = (argname, param_index, item.fspath, item.cls)
+ yield key
+
+
+# algorithm for sorting on a per-parametrized resource setup basis
+# it is called for scopenum==0 (session) first and performs sorting
+# down to the lower scopes such as to minimize number of "high scope"
+# setups and teardowns
+
+
+def reorder_items(items):
+ argkeys_cache = {}
+ items_by_argkey = {}
+ for scopenum in range(0, scopenum_function):
+ argkeys_cache[scopenum] = d = {}
+ items_by_argkey[scopenum] = item_d = defaultdict(deque)
+ for item in items:
+ keys = OrderedDict.fromkeys(get_parametrized_fixture_keys(item, scopenum))
+ if keys:
+ d[item] = keys
+ for key in keys:
+ item_d[key].append(item)
+ items = OrderedDict.fromkeys(items)
+ return list(reorder_items_atscope(items, argkeys_cache, items_by_argkey, 0))
+
+
+def fix_cache_order(item, argkeys_cache, items_by_argkey):
+ for scopenum in range(0, scopenum_function):
+ for key in argkeys_cache[scopenum].get(item, []):
+ items_by_argkey[scopenum][key].appendleft(item)
+
+
+def reorder_items_atscope(items, argkeys_cache, items_by_argkey, scopenum):
+ if scopenum >= scopenum_function or len(items) < 3:
+ return items
+ ignore = set()
+ items_deque = deque(items)
+ items_done = OrderedDict()
+ scoped_items_by_argkey = items_by_argkey[scopenum]
+ scoped_argkeys_cache = argkeys_cache[scopenum]
+ while items_deque:
+ no_argkey_group = OrderedDict()
+ slicing_argkey = None
+ while items_deque:
+ item = items_deque.popleft()
+ if item in items_done or item in no_argkey_group:
+ continue
+ argkeys = OrderedDict.fromkeys(
+ k for k in scoped_argkeys_cache.get(item, []) if k not in ignore
+ )
+ if not argkeys:
+ no_argkey_group[item] = None
+ else:
+ slicing_argkey, _ = argkeys.popitem()
+ # we don't have to remove relevant items from later in the deque because they'll just be ignored
+ matching_items = [
+ i for i in scoped_items_by_argkey[slicing_argkey] if i in items
+ ]
+ for i in reversed(matching_items):
+ fix_cache_order(i, argkeys_cache, items_by_argkey)
+ items_deque.appendleft(i)
+ break
+ if no_argkey_group:
+ no_argkey_group = reorder_items_atscope(
+ no_argkey_group, argkeys_cache, items_by_argkey, scopenum + 1
+ )
+ for item in no_argkey_group:
+ items_done[item] = None
+ ignore.add(slicing_argkey)
+ return items_done
+
+
+def fillfixtures(function):
+ """ fill missing funcargs for a test function. """
+ try:
+ request = function._request
+ except AttributeError:
+ # XXX this special code path is only expected to execute
+ # with the oejskit plugin. It uses classes with funcargs
+ # and we thus have to work a bit to allow this.
+ fm = function.session._fixturemanager
+ fi = fm.getfixtureinfo(function.parent, function.obj, None)
+ function._fixtureinfo = fi
+ request = function._request = FixtureRequest(function)
+ request._fillfixtures()
+ # prune out funcargs for jstests
+ newfuncargs = {}
+ for name in fi.argnames:
+ newfuncargs[name] = function.funcargs[name]
+ function.funcargs = newfuncargs
+ else:
+ request._fillfixtures()
+
+
+def get_direct_param_fixture_func(request):
+ return request.param
+
+
+@attr.s(slots=True)
+class FuncFixtureInfo(object):
+ # original function argument names
+ argnames = attr.ib(type=tuple)
+ # argnames that function immediately requires. These include argnames +
+ # fixture names specified via usefixtures and via autouse=True in fixture
+ # definitions.
+ initialnames = attr.ib(type=tuple)
names_closure = attr.ib() # List[str]
name2fixturedefs = attr.ib() # List[str, List[FixtureDef]]
-
- def prune_dependency_tree(self):
- """Recompute names_closure from initialnames and name2fixturedefs
-
- Can only reduce names_closure, which means that the new closure will
- always be a subset of the old one. The order is preserved.
-
- This method is needed because direct parametrization may shadow some
- of the fixtures that were included in the originally built dependency
- tree. In this way the dependency tree can get pruned, and the closure
- of argnames may get reduced.
- """
- closure = set()
- working_set = set(self.initialnames)
- while working_set:
- argname = working_set.pop()
- # argname may be smth not included in the original names_closure,
- # in which case we ignore it. This currently happens with pseudo
- # FixtureDefs which wrap 'get_direct_param_fixture_func(request)'.
- # So they introduce the new dependency 'request' which might have
- # been missing in the original tree (closure).
- if argname not in closure and argname in self.names_closure:
- closure.add(argname)
- if argname in self.name2fixturedefs:
- working_set.update(self.name2fixturedefs[argname][-1].argnames)
-
- self.names_closure[:] = sorted(closure, key=self.names_closure.index)
-
-
-class FixtureRequest(FuncargnamesCompatAttr):
- """ A request for a fixture from a test or fixture function.
-
- A request object gives access to the requesting test context
- and has an optional ``param`` attribute in case
- the fixture is parametrized indirectly.
- """
-
- def __init__(self, pyfuncitem):
- self._pyfuncitem = pyfuncitem
- #: fixture for which this request is being performed
- self.fixturename = None
- #: Scope string, one of "function", "class", "module", "session"
- self.scope = "function"
- self._fixture_defs = {} # argname -> FixtureDef
- fixtureinfo = pyfuncitem._fixtureinfo
- self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
- self._arg2index = {}
- self._fixturemanager = pyfuncitem.session._fixturemanager
-
- @property
- def fixturenames(self):
- """names of all active fixtures in this request"""
- result = list(self._pyfuncitem._fixtureinfo.names_closure)
- result.extend(set(self._fixture_defs).difference(result))
- return result
-
- @property
- def node(self):
- """ underlying collection node (depends on current request scope)"""
- return self._getscopeitem(self.scope)
-
- def _getnextfixturedef(self, argname):
- fixturedefs = self._arg2fixturedefs.get(argname, None)
- if fixturedefs is None:
- # we arrive here because of a dynamic call to
- # getfixturevalue(argname) usage which was naturally
- # not known at parsing/collection time
- parentid = self._pyfuncitem.parent.nodeid
- fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid)
- self._arg2fixturedefs[argname] = fixturedefs
- # fixturedefs list is immutable so we maintain a decreasing index
- index = self._arg2index.get(argname, 0) - 1
- if fixturedefs is None or (-index > len(fixturedefs)):
- raise FixtureLookupError(argname, self)
- self._arg2index[argname] = index
- return fixturedefs[index]
-
- @property
- def config(self):
- """ the pytest config object associated with this request. """
- return self._pyfuncitem.config
-
- @scopeproperty()
- def function(self):
- """ test function object if the request has a per-function scope. """
- return self._pyfuncitem.obj
-
- @scopeproperty("class")
- def cls(self):
- """ class (can be None) where the test function was collected. """
- clscol = self._pyfuncitem.getparent(_pytest.python.Class)
- if clscol:
- return clscol.obj
-
- @property
- def instance(self):
- """ instance (can be None) on which test function was collected. """
- # unittest support hack, see _pytest.unittest.TestCaseFunction
- try:
- return self._pyfuncitem._testcase
- except AttributeError:
- function = getattr(self, "function", None)
- return getattr(function, "__self__", None)
-
- @scopeproperty()
- def module(self):
- """ python module object where the test function was collected. """
- return self._pyfuncitem.getparent(_pytest.python.Module).obj
-
- @scopeproperty()
- def fspath(self):
- """ the file system path of the test module which collected this test. """
- return self._pyfuncitem.fspath
-
- @property
- def keywords(self):
- """ keywords/markers dictionary for the underlying node. """
- return self.node.keywords
-
- @property
- def session(self):
- """ pytest session object. """
- return self._pyfuncitem.session
-
- def addfinalizer(self, finalizer):
- """ add finalizer/teardown function to be called after the
- last test within the requesting test context finished
- execution. """
- # XXX usually this method is shadowed by fixturedef specific ones
- self._addfinalizer(finalizer, scope=self.scope)
-
- def _addfinalizer(self, finalizer, scope):
- colitem = self._getscopeitem(scope)
- self._pyfuncitem.session._setupstate.addfinalizer(
- finalizer=finalizer, colitem=colitem
- )
-
- def applymarker(self, marker):
- """ Apply a marker to a single test function invocation.
- This method is useful if you don't want to have a keyword/marker
- on all function invocations.
-
- :arg marker: a :py:class:`_pytest.mark.MarkDecorator` object
- created by a call to ``pytest.mark.NAME(...)``.
- """
- self.node.add_marker(marker)
-
- def raiseerror(self, msg):
- """ raise a FixtureLookupError with the given message. """
- raise self._fixturemanager.FixtureLookupError(None, self, msg)
-
- def _fillfixtures(self):
- item = self._pyfuncitem
- fixturenames = getattr(item, "fixturenames", self.fixturenames)
- for argname in fixturenames:
- if argname not in item.funcargs:
- item.funcargs[argname] = self.getfixturevalue(argname)
-
- def getfixturevalue(self, argname):
- """ Dynamically run a named fixture function.
-
- Declaring fixtures via function argument is recommended where possible.
- But if you can only decide whether to use another fixture at test
- setup time, you may use this function to retrieve it inside a fixture
- or test function body.
- """
- return self._get_active_fixturedef(argname).cached_result[0]
-
- def getfuncargvalue(self, argname):
- """ Deprecated, use getfixturevalue. """
- from _pytest import deprecated
-
- warnings.warn(deprecated.GETFUNCARGVALUE, stacklevel=2)
- return self.getfixturevalue(argname)
-
- def _get_active_fixturedef(self, argname):
- try:
- return self._fixture_defs[argname]
- except KeyError:
- try:
- fixturedef = self._getnextfixturedef(argname)
- except FixtureLookupError:
- if argname == "request":
- cached_result = (self, [0], None)
- scope = "function"
- return PseudoFixtureDef(cached_result, scope)
- raise
- # remove indent to prevent the python3 exception
- # from leaking into the call
- self._compute_fixture_value(fixturedef)
- self._fixture_defs[argname] = fixturedef
- return fixturedef
-
- def _get_fixturestack(self):
- current = self
- values = []
- while 1:
- fixturedef = getattr(current, "_fixturedef", None)
- if fixturedef is None:
- values.reverse()
- return values
- values.append(fixturedef)
- current = current._parent_request
-
- 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
- funcitem = self._pyfuncitem
- scope = fixturedef.scope
- try:
- param = funcitem.callspec.getparam(argname)
- except (AttributeError, ValueError):
- param = NOTSET
- param_index = 0
- has_params = fixturedef.params is not None
- fixtures_not_supported = getattr(funcitem, "nofuncargs", False)
- if has_params and fixtures_not_supported:
- msg = (
- "{name} does not support fixtures, maybe unittest.TestCase subclass?\n"
- "Node id: {nodeid}\n"
- "Function type: {typename}"
- ).format(
- name=funcitem.name,
- nodeid=funcitem.nodeid,
- typename=type(funcitem).__name__,
- )
- fail(msg, pytrace=False)
- if has_params:
- frame = inspect.stack()[3]
- frameinfo = inspect.getframeinfo(frame[0])
- source_path = frameinfo.filename
- source_lineno = frameinfo.lineno
- source_path = py.path.local(source_path)
- if source_path.relto(funcitem.config.rootdir):
- source_path = source_path.relto(funcitem.config.rootdir)
- msg = (
- "The requested fixture has no parameter defined for test:\n"
- " {}\n\n"
- "Requested fixture '{}' defined in:\n{}"
- "\n\nRequested here:\n{}:{}".format(
- funcitem.nodeid,
- fixturedef.argname,
- getlocation(fixturedef.func, funcitem.config.rootdir),
- source_path,
- source_lineno,
- )
- )
- fail(msg, pytrace=False)
- else:
+
+ def prune_dependency_tree(self):
+ """Recompute names_closure from initialnames and name2fixturedefs
+
+ Can only reduce names_closure, which means that the new closure will
+ always be a subset of the old one. The order is preserved.
+
+ This method is needed because direct parametrization may shadow some
+ of the fixtures that were included in the originally built dependency
+ tree. In this way the dependency tree can get pruned, and the closure
+ of argnames may get reduced.
+ """
+ closure = set()
+ working_set = set(self.initialnames)
+ while working_set:
+ argname = working_set.pop()
+ # argname may be smth not included in the original names_closure,
+ # in which case we ignore it. This currently happens with pseudo
+ # FixtureDefs which wrap 'get_direct_param_fixture_func(request)'.
+ # So they introduce the new dependency 'request' which might have
+ # been missing in the original tree (closure).
+ if argname not in closure and argname in self.names_closure:
+ closure.add(argname)
+ if argname in self.name2fixturedefs:
+ working_set.update(self.name2fixturedefs[argname][-1].argnames)
+
+ self.names_closure[:] = sorted(closure, key=self.names_closure.index)
+
+
+class FixtureRequest(FuncargnamesCompatAttr):
+ """ A request for a fixture from a test or fixture function.
+
+ A request object gives access to the requesting test context
+ and has an optional ``param`` attribute in case
+ the fixture is parametrized indirectly.
+ """
+
+ def __init__(self, pyfuncitem):
+ self._pyfuncitem = pyfuncitem
+ #: fixture for which this request is being performed
+ self.fixturename = None
+ #: Scope string, one of "function", "class", "module", "session"
+ self.scope = "function"
+ self._fixture_defs = {} # argname -> FixtureDef
+ fixtureinfo = pyfuncitem._fixtureinfo
+ self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
+ self._arg2index = {}
+ self._fixturemanager = pyfuncitem.session._fixturemanager
+
+ @property
+ def fixturenames(self):
+ """names of all active fixtures in this request"""
+ result = list(self._pyfuncitem._fixtureinfo.names_closure)
+ result.extend(set(self._fixture_defs).difference(result))
+ return result
+
+ @property
+ def node(self):
+ """ underlying collection node (depends on current request scope)"""
+ return self._getscopeitem(self.scope)
+
+ def _getnextfixturedef(self, argname):
+ fixturedefs = self._arg2fixturedefs.get(argname, None)
+ if fixturedefs is None:
+ # we arrive here because of a dynamic call to
+ # getfixturevalue(argname) usage which was naturally
+ # not known at parsing/collection time
+ parentid = self._pyfuncitem.parent.nodeid
+ fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid)
+ self._arg2fixturedefs[argname] = fixturedefs
+ # fixturedefs list is immutable so we maintain a decreasing index
+ index = self._arg2index.get(argname, 0) - 1
+ if fixturedefs is None or (-index > len(fixturedefs)):
+ raise FixtureLookupError(argname, self)
+ self._arg2index[argname] = index
+ return fixturedefs[index]
+
+ @property
+ def config(self):
+ """ the pytest config object associated with this request. """
+ return self._pyfuncitem.config
+
+ @scopeproperty()
+ def function(self):
+ """ test function object if the request has a per-function scope. """
+ return self._pyfuncitem.obj
+
+ @scopeproperty("class")
+ def cls(self):
+ """ class (can be None) where the test function was collected. """
+ clscol = self._pyfuncitem.getparent(_pytest.python.Class)
+ if clscol:
+ return clscol.obj
+
+ @property
+ def instance(self):
+ """ instance (can be None) on which test function was collected. """
+ # unittest support hack, see _pytest.unittest.TestCaseFunction
+ try:
+ return self._pyfuncitem._testcase
+ except AttributeError:
+ function = getattr(self, "function", None)
+ return getattr(function, "__self__", None)
+
+ @scopeproperty()
+ def module(self):
+ """ python module object where the test function was collected. """
+ return self._pyfuncitem.getparent(_pytest.python.Module).obj
+
+ @scopeproperty()
+ def fspath(self):
+ """ the file system path of the test module which collected this test. """
+ return self._pyfuncitem.fspath
+
+ @property
+ def keywords(self):
+ """ keywords/markers dictionary for the underlying node. """
+ return self.node.keywords
+
+ @property
+ def session(self):
+ """ pytest session object. """
+ return self._pyfuncitem.session
+
+ def addfinalizer(self, finalizer):
+ """ add finalizer/teardown function to be called after the
+ last test within the requesting test context finished
+ execution. """
+ # XXX usually this method is shadowed by fixturedef specific ones
+ self._addfinalizer(finalizer, scope=self.scope)
+
+ def _addfinalizer(self, finalizer, scope):
+ colitem = self._getscopeitem(scope)
+ self._pyfuncitem.session._setupstate.addfinalizer(
+ finalizer=finalizer, colitem=colitem
+ )
+
+ def applymarker(self, marker):
+ """ Apply a marker to a single test function invocation.
+ This method is useful if you don't want to have a keyword/marker
+ on all function invocations.
+
+ :arg marker: a :py:class:`_pytest.mark.MarkDecorator` object
+ created by a call to ``pytest.mark.NAME(...)``.
+ """
+ self.node.add_marker(marker)
+
+ def raiseerror(self, msg):
+ """ raise a FixtureLookupError with the given message. """
+ raise self._fixturemanager.FixtureLookupError(None, self, msg)
+
+ def _fillfixtures(self):
+ item = self._pyfuncitem
+ fixturenames = getattr(item, "fixturenames", self.fixturenames)
+ for argname in fixturenames:
+ if argname not in item.funcargs:
+ item.funcargs[argname] = self.getfixturevalue(argname)
+
+ def getfixturevalue(self, argname):
+ """ Dynamically run a named fixture function.
+
+ Declaring fixtures via function argument is recommended where possible.
+ But if you can only decide whether to use another fixture at test
+ setup time, you may use this function to retrieve it inside a fixture
+ or test function body.
+ """
+ return self._get_active_fixturedef(argname).cached_result[0]
+
+ def getfuncargvalue(self, argname):
+ """ Deprecated, use getfixturevalue. """
+ from _pytest import deprecated
+
+ warnings.warn(deprecated.GETFUNCARGVALUE, stacklevel=2)
+ return self.getfixturevalue(argname)
+
+ def _get_active_fixturedef(self, argname):
+ try:
+ return self._fixture_defs[argname]
+ except KeyError:
+ try:
+ fixturedef = self._getnextfixturedef(argname)
+ except FixtureLookupError:
+ if argname == "request":
+ cached_result = (self, [0], None)
+ scope = "function"
+ return PseudoFixtureDef(cached_result, scope)
+ raise
+ # remove indent to prevent the python3 exception
+ # from leaking into the call
+ self._compute_fixture_value(fixturedef)
+ self._fixture_defs[argname] = fixturedef
+ return fixturedef
+
+ def _get_fixturestack(self):
+ current = self
+ values = []
+ while 1:
+ fixturedef = getattr(current, "_fixturedef", None)
+ if fixturedef is None:
+ values.reverse()
+ return values
+ values.append(fixturedef)
+ current = current._parent_request
+
+ 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
+ funcitem = self._pyfuncitem
+ scope = fixturedef.scope
+ try:
+ param = funcitem.callspec.getparam(argname)
+ except (AttributeError, ValueError):
+ param = NOTSET
+ param_index = 0
+ has_params = fixturedef.params is not None
+ fixtures_not_supported = getattr(funcitem, "nofuncargs", False)
+ if has_params and fixtures_not_supported:
+ msg = (
+ "{name} does not support fixtures, maybe unittest.TestCase subclass?\n"
+ "Node id: {nodeid}\n"
+ "Function type: {typename}"
+ ).format(
+ name=funcitem.name,
+ nodeid=funcitem.nodeid,
+ typename=type(funcitem).__name__,
+ )
+ fail(msg, pytrace=False)
+ if has_params:
+ frame = inspect.stack()[3]
+ frameinfo = inspect.getframeinfo(frame[0])
+ source_path = frameinfo.filename
+ source_lineno = frameinfo.lineno
+ source_path = py.path.local(source_path)
+ if source_path.relto(funcitem.config.rootdir):
+ source_path = source_path.relto(funcitem.config.rootdir)
+ msg = (
+ "The requested fixture has no parameter defined for test:\n"
+ " {}\n\n"
+ "Requested fixture '{}' defined in:\n{}"
+ "\n\nRequested here:\n{}:{}".format(
+ funcitem.nodeid,
+ fixturedef.argname,
+ getlocation(fixturedef.func, funcitem.config.rootdir),
+ source_path,
+ source_lineno,
+ )
+ )
+ fail(msg, pytrace=False)
+ else:
param_index = funcitem.callspec.indices[argname]
- # if a parametrize invocation set a scope it will override
- # the static scope defined with the fixture function
- paramscopenum = funcitem.callspec._arg2scopenum.get(argname)
- if paramscopenum is not None:
- scope = scopes[paramscopenum]
-
- subrequest = SubRequest(self, scope, param, param_index, fixturedef)
-
- # check if a higher-level scoped fixture accesses a lower level one
- subrequest._check_scope(argname, self.scope, scope)
-
- # clear sys.exc_info before invoking the fixture (python bug?)
- # if it's not explicitly cleared it will leak into the call
- exc_clear()
- try:
- # call the fixture function
- fixturedef.execute(request=subrequest)
- finally:
+ # if a parametrize invocation set a scope it will override
+ # the static scope defined with the fixture function
+ paramscopenum = funcitem.callspec._arg2scopenum.get(argname)
+ if paramscopenum is not None:
+ scope = scopes[paramscopenum]
+
+ subrequest = SubRequest(self, scope, param, param_index, fixturedef)
+
+ # check if a higher-level scoped fixture accesses a lower level one
+ subrequest._check_scope(argname, self.scope, scope)
+
+ # clear sys.exc_info before invoking the fixture (python bug?)
+ # if it's not explicitly cleared it will leak into the call
+ exc_clear()
+ try:
+ # call the fixture function
+ fixturedef.execute(request=subrequest)
+ finally:
self._schedule_finalizers(fixturedef, subrequest)
-
+
def _schedule_finalizers(self, fixturedef, subrequest):
# if fixture function failed it might have registered finalizers
self.session._setupstate.addfinalizer(
functools.partial(fixturedef.finish, request=subrequest), subrequest.node
)
- def _check_scope(self, argname, invoking_scope, requested_scope):
- if argname == "request":
- return
- if scopemismatch(invoking_scope, requested_scope):
- # try to report something helpful
- lines = self._factorytraceback()
- fail(
- "ScopeMismatch: You tried to access the %r scoped "
- "fixture %r with a %r scoped request object, "
- "involved factories\n%s"
- % ((requested_scope, argname, invoking_scope, "\n".join(lines))),
- pytrace=False,
- )
-
- def _factorytraceback(self):
- lines = []
- for fixturedef in self._get_fixturestack():
- factory = fixturedef.func
- fs, lineno = getfslineno(factory)
- p = self._pyfuncitem.session.fspath.bestrelpath(fs)
- args = _format_args(factory)
+ def _check_scope(self, argname, invoking_scope, requested_scope):
+ if argname == "request":
+ return
+ if scopemismatch(invoking_scope, requested_scope):
+ # try to report something helpful
+ lines = self._factorytraceback()
+ fail(
+ "ScopeMismatch: You tried to access the %r scoped "
+ "fixture %r with a %r scoped request object, "
+ "involved factories\n%s"
+ % ((requested_scope, argname, invoking_scope, "\n".join(lines))),
+ pytrace=False,
+ )
+
+ def _factorytraceback(self):
+ lines = []
+ for fixturedef in self._get_fixturestack():
+ factory = fixturedef.func
+ fs, lineno = getfslineno(factory)
+ p = self._pyfuncitem.session.fspath.bestrelpath(fs)
+ args = _format_args(factory)
lines.append("%s:%d: def %s%s" % (p, lineno + 1, factory.__name__, args))
- return lines
-
- def _getscopeitem(self, scope):
- if scope == "function":
- # this might also be a non-function Item despite its attribute name
- return self._pyfuncitem
- if scope == "package":
- node = get_scope_package(self._pyfuncitem, self._fixturedef)
- else:
- node = get_scope_node(self._pyfuncitem, scope)
- if node is None and scope == "class":
- # fallback to function item itself
- node = self._pyfuncitem
- assert node, 'Could not obtain a node for scope "{}" for function {!r}'.format(
- scope, self._pyfuncitem
- )
- return node
-
- def __repr__(self):
- return "<FixtureRequest for %r>" % (self.node)
-
-
-class SubRequest(FixtureRequest):
- """ a sub request for handling getting a fixture from a
- test function/fixture. """
-
- def __init__(self, request, scope, param, param_index, fixturedef):
- self._parent_request = request
- self.fixturename = fixturedef.argname
- if param is not NOTSET:
- self.param = param
- self.param_index = param_index
- self.scope = scope
- self._fixturedef = fixturedef
- self._pyfuncitem = request._pyfuncitem
- self._fixture_defs = request._fixture_defs
- self._arg2fixturedefs = request._arg2fixturedefs
- self._arg2index = request._arg2index
- self._fixturemanager = request._fixturemanager
-
- def __repr__(self):
- return "<SubRequest %r for %r>" % (self.fixturename, self._pyfuncitem)
-
- def addfinalizer(self, finalizer):
- self._fixturedef.addfinalizer(finalizer)
-
+ return lines
+
+ def _getscopeitem(self, scope):
+ if scope == "function":
+ # this might also be a non-function Item despite its attribute name
+ return self._pyfuncitem
+ if scope == "package":
+ node = get_scope_package(self._pyfuncitem, self._fixturedef)
+ else:
+ node = get_scope_node(self._pyfuncitem, scope)
+ if node is None and scope == "class":
+ # fallback to function item itself
+ node = self._pyfuncitem
+ assert node, 'Could not obtain a node for scope "{}" for function {!r}'.format(
+ scope, self._pyfuncitem
+ )
+ return node
+
+ def __repr__(self):
+ return "<FixtureRequest for %r>" % (self.node)
+
+
+class SubRequest(FixtureRequest):
+ """ a sub request for handling getting a fixture from a
+ test function/fixture. """
+
+ def __init__(self, request, scope, param, param_index, fixturedef):
+ self._parent_request = request
+ self.fixturename = fixturedef.argname
+ if param is not NOTSET:
+ self.param = param
+ self.param_index = param_index
+ self.scope = scope
+ self._fixturedef = fixturedef
+ self._pyfuncitem = request._pyfuncitem
+ self._fixture_defs = request._fixture_defs
+ self._arg2fixturedefs = request._arg2fixturedefs
+ self._arg2index = request._arg2index
+ self._fixturemanager = request._fixturemanager
+
+ def __repr__(self):
+ return "<SubRequest %r for %r>" % (self.fixturename, self._pyfuncitem)
+
+ def addfinalizer(self, finalizer):
+ self._fixturedef.addfinalizer(finalizer)
+
def _schedule_finalizers(self, fixturedef, subrequest):
# if the executing fixturedef was not explicitly requested in the argument list (via
# getfixturevalue inside the fixture call) then ensure this fixture def will be finished
@@ -671,409 +671,409 @@ class SubRequest(FixtureRequest):
functools.partial(self._fixturedef.finish, request=self)
)
super(SubRequest, self)._schedule_finalizers(fixturedef, subrequest)
-
-
-scopes = "session package module class function".split()
-scopenum_function = scopes.index("function")
-
-
-def scopemismatch(currentscope, newscope):
- return scopes.index(newscope) > scopes.index(currentscope)
-
-
-def scope2index(scope, descr, where=None):
- """Look up the index of ``scope`` and raise a descriptive value error
- if not defined.
- """
- try:
- return scopes.index(scope)
- except ValueError:
- fail(
- "{} {}got an unexpected scope value '{}'".format(
- descr, "from {} ".format(where) if where else "", scope
- ),
- pytrace=False,
- )
-
-
-class FixtureLookupError(LookupError):
- """ could not return a requested Fixture (missing or invalid). """
-
- def __init__(self, argname, request, msg=None):
- self.argname = argname
- self.request = request
- self.fixturestack = request._get_fixturestack()
- self.msg = msg
-
- def formatrepr(self):
- tblines = []
- addline = tblines.append
- stack = [self.request._pyfuncitem.obj]
- stack.extend(map(lambda x: x.func, self.fixturestack))
- msg = self.msg
- if msg is not None:
- # the last fixture raise an error, let's present
- # it at the requesting side
- stack = stack[:-1]
- for function in stack:
- fspath, lineno = getfslineno(function)
- try:
- lines, _ = inspect.getsourcelines(get_real_func(function))
- except (IOError, IndexError, TypeError):
- error_msg = "file %s, line %s: source code not available"
- addline(error_msg % (fspath, lineno + 1))
- else:
- addline("file %s, line %s" % (fspath, lineno + 1))
- for i, line in enumerate(lines):
- line = line.rstrip()
- addline(" " + line)
- if line.lstrip().startswith("def"):
- break
-
- if msg is None:
- fm = self.request._fixturemanager
- available = set()
- parentid = self.request._pyfuncitem.parent.nodeid
- for name, fixturedefs in fm._arg2fixturedefs.items():
- faclist = list(fm._matchfactories(fixturedefs, parentid))
- if faclist:
- available.add(name)
- if self.argname in available:
- msg = " recursive dependency involving fixture '{}' detected".format(
- self.argname
- )
- else:
- msg = "fixture '{}' not found".format(self.argname)
- msg += "\n available fixtures: {}".format(", ".join(sorted(available)))
- msg += "\n use 'pytest --fixtures [testpath]' for help on them."
-
- return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname)
-
-
-class FixtureLookupErrorRepr(TerminalRepr):
- def __init__(self, filename, firstlineno, tblines, errorstring, argname):
- self.tblines = tblines
- self.errorstring = errorstring
- self.filename = filename
- self.firstlineno = firstlineno
- self.argname = argname
-
- def toterminal(self, tw):
- # tw.line("FixtureLookupError: %s" %(self.argname), red=True)
- for tbline in self.tblines:
- tw.line(tbline.rstrip())
- lines = self.errorstring.split("\n")
- if lines:
- tw.line(
- "{} {}".format(FormattedExcinfo.fail_marker, lines[0].strip()),
- red=True,
- )
- for line in lines[1:]:
- tw.line(
- "{} {}".format(FormattedExcinfo.flow_marker, line.strip()),
- red=True,
- )
- tw.line()
- tw.line("%s:%d" % (self.filename, self.firstlineno + 1))
-
-
-def fail_fixturefunc(fixturefunc, msg):
- fs, lineno = getfslineno(fixturefunc)
- location = "%s:%s" % (fs, lineno + 1)
- source = _pytest._code.Source(fixturefunc)
- fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, pytrace=False)
-
-
-def call_fixture_func(fixturefunc, request, kwargs):
- yieldctx = is_generator(fixturefunc)
- if yieldctx:
- it = fixturefunc(**kwargs)
- res = next(it)
- finalizer = functools.partial(_teardown_yield_fixture, fixturefunc, it)
- request.addfinalizer(finalizer)
- else:
- res = fixturefunc(**kwargs)
- return res
-
-
-def _teardown_yield_fixture(fixturefunc, it):
- """Executes the teardown of a fixture function by advancing the iterator after the
- yield and ensure the iteration ends (if not it means there is more than one yield in the function)"""
- try:
- next(it)
- except StopIteration:
- pass
- else:
- fail_fixturefunc(
- fixturefunc, "yield_fixture function has more than one 'yield'"
- )
-
-
-class FixtureDef(object):
- """ A container for a factory definition. """
-
- def __init__(
- self,
- fixturemanager,
- baseid,
- argname,
- func,
- scope,
- params,
- unittest=False,
- ids=None,
- ):
- self._fixturemanager = fixturemanager
- self.baseid = baseid or ""
- self.has_location = baseid is not None
- self.func = func
- self.argname = argname
- self.scope = scope
- self.scopenum = scope2index(
- scope or "function",
- descr="Fixture '{}'".format(func.__name__),
- where=baseid,
- )
- self.params = params
- self.argnames = getfuncargnames(func, is_method=unittest)
- self.unittest = unittest
- self.ids = ids
- self._finalizers = []
-
- def addfinalizer(self, finalizer):
- self._finalizers.append(finalizer)
-
- def finish(self, request):
- exceptions = []
- try:
- while self._finalizers:
- try:
- func = self._finalizers.pop()
- func()
- except: # noqa
- exceptions.append(sys.exc_info())
- if exceptions:
- e = exceptions[0]
+
+
+scopes = "session package module class function".split()
+scopenum_function = scopes.index("function")
+
+
+def scopemismatch(currentscope, newscope):
+ return scopes.index(newscope) > scopes.index(currentscope)
+
+
+def scope2index(scope, descr, where=None):
+ """Look up the index of ``scope`` and raise a descriptive value error
+ if not defined.
+ """
+ try:
+ return scopes.index(scope)
+ except ValueError:
+ fail(
+ "{} {}got an unexpected scope value '{}'".format(
+ descr, "from {} ".format(where) if where else "", scope
+ ),
+ pytrace=False,
+ )
+
+
+class FixtureLookupError(LookupError):
+ """ could not return a requested Fixture (missing or invalid). """
+
+ def __init__(self, argname, request, msg=None):
+ self.argname = argname
+ self.request = request
+ self.fixturestack = request._get_fixturestack()
+ self.msg = msg
+
+ def formatrepr(self):
+ tblines = []
+ addline = tblines.append
+ stack = [self.request._pyfuncitem.obj]
+ stack.extend(map(lambda x: x.func, self.fixturestack))
+ msg = self.msg
+ if msg is not None:
+ # the last fixture raise an error, let's present
+ # it at the requesting side
+ stack = stack[:-1]
+ for function in stack:
+ fspath, lineno = getfslineno(function)
+ try:
+ lines, _ = inspect.getsourcelines(get_real_func(function))
+ except (IOError, IndexError, TypeError):
+ error_msg = "file %s, line %s: source code not available"
+ addline(error_msg % (fspath, lineno + 1))
+ else:
+ addline("file %s, line %s" % (fspath, lineno + 1))
+ for i, line in enumerate(lines):
+ line = line.rstrip()
+ addline(" " + line)
+ if line.lstrip().startswith("def"):
+ break
+
+ if msg is None:
+ fm = self.request._fixturemanager
+ available = set()
+ parentid = self.request._pyfuncitem.parent.nodeid
+ for name, fixturedefs in fm._arg2fixturedefs.items():
+ faclist = list(fm._matchfactories(fixturedefs, parentid))
+ if faclist:
+ available.add(name)
+ if self.argname in available:
+ msg = " recursive dependency involving fixture '{}' detected".format(
+ self.argname
+ )
+ else:
+ msg = "fixture '{}' not found".format(self.argname)
+ msg += "\n available fixtures: {}".format(", ".join(sorted(available)))
+ msg += "\n use 'pytest --fixtures [testpath]' for help on them."
+
+ return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname)
+
+
+class FixtureLookupErrorRepr(TerminalRepr):
+ def __init__(self, filename, firstlineno, tblines, errorstring, argname):
+ self.tblines = tblines
+ self.errorstring = errorstring
+ self.filename = filename
+ self.firstlineno = firstlineno
+ self.argname = argname
+
+ def toterminal(self, tw):
+ # tw.line("FixtureLookupError: %s" %(self.argname), red=True)
+ for tbline in self.tblines:
+ tw.line(tbline.rstrip())
+ lines = self.errorstring.split("\n")
+ if lines:
+ tw.line(
+ "{} {}".format(FormattedExcinfo.fail_marker, lines[0].strip()),
+ red=True,
+ )
+ for line in lines[1:]:
+ tw.line(
+ "{} {}".format(FormattedExcinfo.flow_marker, line.strip()),
+ red=True,
+ )
+ tw.line()
+ tw.line("%s:%d" % (self.filename, self.firstlineno + 1))
+
+
+def fail_fixturefunc(fixturefunc, msg):
+ fs, lineno = getfslineno(fixturefunc)
+ location = "%s:%s" % (fs, lineno + 1)
+ source = _pytest._code.Source(fixturefunc)
+ fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, pytrace=False)
+
+
+def call_fixture_func(fixturefunc, request, kwargs):
+ yieldctx = is_generator(fixturefunc)
+ if yieldctx:
+ it = fixturefunc(**kwargs)
+ res = next(it)
+ finalizer = functools.partial(_teardown_yield_fixture, fixturefunc, it)
+ request.addfinalizer(finalizer)
+ else:
+ res = fixturefunc(**kwargs)
+ return res
+
+
+def _teardown_yield_fixture(fixturefunc, it):
+ """Executes the teardown of a fixture function by advancing the iterator after the
+ yield and ensure the iteration ends (if not it means there is more than one yield in the function)"""
+ try:
+ next(it)
+ except StopIteration:
+ pass
+ else:
+ fail_fixturefunc(
+ fixturefunc, "yield_fixture function has more than one 'yield'"
+ )
+
+
+class FixtureDef(object):
+ """ A container for a factory definition. """
+
+ def __init__(
+ self,
+ fixturemanager,
+ baseid,
+ argname,
+ func,
+ scope,
+ params,
+ unittest=False,
+ ids=None,
+ ):
+ self._fixturemanager = fixturemanager
+ self.baseid = baseid or ""
+ self.has_location = baseid is not None
+ self.func = func
+ self.argname = argname
+ self.scope = scope
+ self.scopenum = scope2index(
+ scope or "function",
+ descr="Fixture '{}'".format(func.__name__),
+ where=baseid,
+ )
+ self.params = params
+ self.argnames = getfuncargnames(func, is_method=unittest)
+ self.unittest = unittest
+ self.ids = ids
+ self._finalizers = []
+
+ def addfinalizer(self, finalizer):
+ self._finalizers.append(finalizer)
+
+ def finish(self, request):
+ exceptions = []
+ try:
+ while self._finalizers:
+ try:
+ func = self._finalizers.pop()
+ func()
+ except: # noqa
+ exceptions.append(sys.exc_info())
+ if exceptions:
+ e = exceptions[0]
# Ensure to not keep frame references through traceback.
del exceptions
- six.reraise(*e)
- finally:
- hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
- hook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
- # even if finalization fails, we invalidate
- # the cached fixture value and remove
- # all finalizers because they may be bound methods which will
- # keep instances alive
- if hasattr(self, "cached_result"):
- del self.cached_result
- self._finalizers = []
-
- def execute(self, request):
- # get required arguments and register our own finish()
- # with their finalization
- for argname in self.argnames:
- fixturedef = request._get_active_fixturedef(argname)
- if argname != "request":
- fixturedef.addfinalizer(functools.partial(self.finish, request=request))
-
- my_cache_key = request.param_index
- cached_result = getattr(self, "cached_result", None)
- if cached_result is not None:
- result, cache_key, err = cached_result
- if my_cache_key == cache_key:
- if err is not None:
- six.reraise(*err)
- else:
- return result
- # we have a previous but differently parametrized fixture instance
- # so we need to tear it down before creating a new one
- self.finish(request)
- assert not hasattr(self, "cached_result")
-
- hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
- return hook.pytest_fixture_setup(fixturedef=self, request=request)
-
- def __repr__(self):
- return "<FixtureDef argname=%r scope=%r baseid=%r>" % (
- self.argname,
- self.scope,
- self.baseid,
- )
-
-
-def resolve_fixture_function(fixturedef, request):
- """Gets the actual callable that can be called to obtain the fixture value, dealing with unittest-specific
- instances and bound methods.
- """
- fixturefunc = fixturedef.func
- if fixturedef.unittest:
- if request.instance is not None:
- # bind the unbound method to the TestCase instance
- fixturefunc = fixturedef.func.__get__(request.instance)
- else:
- # the fixture function needs to be bound to the actual
- # request.instance so that code working with "fixturedef" behaves
- # as expected.
- if request.instance is not None:
- fixturefunc = getimfunc(fixturedef.func)
- if fixturefunc != fixturedef.func:
- fixturefunc = fixturefunc.__get__(request.instance)
- return fixturefunc
-
-
-def pytest_fixture_setup(fixturedef, request):
- """ Execution of fixture setup. """
- kwargs = {}
- for argname in fixturedef.argnames:
- fixdef = request._get_active_fixturedef(argname)
- result, arg_cache_key, exc = fixdef.cached_result
- request._check_scope(argname, request.scope, fixdef.scope)
- kwargs[argname] = result
-
- fixturefunc = resolve_fixture_function(fixturedef, request)
- my_cache_key = request.param_index
- try:
- result = call_fixture_func(fixturefunc, request, kwargs)
- except TEST_OUTCOME:
- fixturedef.cached_result = (None, my_cache_key, sys.exc_info())
- raise
- fixturedef.cached_result = (result, my_cache_key, None)
- return result
-
-
-def _ensure_immutable_ids(ids):
- if ids is None:
- return
- if callable(ids):
- return ids
- return tuple(ids)
-
-
+ six.reraise(*e)
+ finally:
+ hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
+ hook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
+ # even if finalization fails, we invalidate
+ # the cached fixture value and remove
+ # all finalizers because they may be bound methods which will
+ # keep instances alive
+ if hasattr(self, "cached_result"):
+ del self.cached_result
+ self._finalizers = []
+
+ def execute(self, request):
+ # get required arguments and register our own finish()
+ # with their finalization
+ for argname in self.argnames:
+ fixturedef = request._get_active_fixturedef(argname)
+ if argname != "request":
+ fixturedef.addfinalizer(functools.partial(self.finish, request=request))
+
+ my_cache_key = request.param_index
+ cached_result = getattr(self, "cached_result", None)
+ if cached_result is not None:
+ result, cache_key, err = cached_result
+ if my_cache_key == cache_key:
+ if err is not None:
+ six.reraise(*err)
+ else:
+ return result
+ # we have a previous but differently parametrized fixture instance
+ # so we need to tear it down before creating a new one
+ self.finish(request)
+ assert not hasattr(self, "cached_result")
+
+ hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
+ return hook.pytest_fixture_setup(fixturedef=self, request=request)
+
+ def __repr__(self):
+ return "<FixtureDef argname=%r scope=%r baseid=%r>" % (
+ self.argname,
+ self.scope,
+ self.baseid,
+ )
+
+
+def resolve_fixture_function(fixturedef, request):
+ """Gets the actual callable that can be called to obtain the fixture value, dealing with unittest-specific
+ instances and bound methods.
+ """
+ fixturefunc = fixturedef.func
+ if fixturedef.unittest:
+ if request.instance is not None:
+ # bind the unbound method to the TestCase instance
+ fixturefunc = fixturedef.func.__get__(request.instance)
+ else:
+ # the fixture function needs to be bound to the actual
+ # request.instance so that code working with "fixturedef" behaves
+ # as expected.
+ if request.instance is not None:
+ fixturefunc = getimfunc(fixturedef.func)
+ if fixturefunc != fixturedef.func:
+ fixturefunc = fixturefunc.__get__(request.instance)
+ return fixturefunc
+
+
+def pytest_fixture_setup(fixturedef, request):
+ """ Execution of fixture setup. """
+ kwargs = {}
+ for argname in fixturedef.argnames:
+ fixdef = request._get_active_fixturedef(argname)
+ result, arg_cache_key, exc = fixdef.cached_result
+ request._check_scope(argname, request.scope, fixdef.scope)
+ kwargs[argname] = result
+
+ fixturefunc = resolve_fixture_function(fixturedef, request)
+ my_cache_key = request.param_index
+ try:
+ result = call_fixture_func(fixturefunc, request, kwargs)
+ except TEST_OUTCOME:
+ fixturedef.cached_result = (None, my_cache_key, sys.exc_info())
+ raise
+ fixturedef.cached_result = (result, my_cache_key, None)
+ return result
+
+
+def _ensure_immutable_ids(ids):
+ if ids is None:
+ return
+ if callable(ids):
+ return ids
+ return tuple(ids)
+
+
def wrap_function_to_error_out_if_called_directly(function, fixture_marker):
"""Wrap the given fixture function so we can raise an error about it being called directly,
instead of used as an argument in a test function.
- """
+ """
message = FIXTURE_FUNCTION_CALL.format(
- name=fixture_marker.name or function.__name__
- )
-
+ name=fixture_marker.name or function.__name__
+ )
+
@six.wraps(function)
def result(*args, **kwargs):
fail(message, pytrace=False)
-
- # keep reference to the original function in our own custom attribute so we don't unwrap
- # further than this point and lose useful wrappings like @mock.patch (#3774)
- result.__pytest_wrapped__ = _PytestWrapper(function)
-
- return result
-
-
-@attr.s(frozen=True)
-class FixtureFunctionMarker(object):
- scope = attr.ib()
- params = attr.ib(converter=attr.converters.optional(tuple))
- autouse = attr.ib(default=False)
- ids = attr.ib(default=None, converter=_ensure_immutable_ids)
- name = attr.ib(default=None)
-
- def __call__(self, function):
- if isclass(function):
- raise ValueError("class fixtures not supported (maybe in the future)")
-
- if getattr(function, "_pytestfixturefunction", False):
- raise ValueError(
- "fixture is being applied more than once to the same function"
- )
-
+
+ # keep reference to the original function in our own custom attribute so we don't unwrap
+ # further than this point and lose useful wrappings like @mock.patch (#3774)
+ result.__pytest_wrapped__ = _PytestWrapper(function)
+
+ return result
+
+
+@attr.s(frozen=True)
+class FixtureFunctionMarker(object):
+ scope = attr.ib()
+ params = attr.ib(converter=attr.converters.optional(tuple))
+ autouse = attr.ib(default=False)
+ ids = attr.ib(default=None, converter=_ensure_immutable_ids)
+ name = attr.ib(default=None)
+
+ def __call__(self, function):
+ if isclass(function):
+ raise ValueError("class fixtures not supported (maybe in the future)")
+
+ if getattr(function, "_pytestfixturefunction", False):
+ raise ValueError(
+ "fixture is being applied more than once to the same function"
+ )
+
function = wrap_function_to_error_out_if_called_directly(function, self)
-
- name = self.name or function.__name__
- if name == "request":
- warnings.warn(FIXTURE_NAMED_REQUEST)
- function._pytestfixturefunction = self
- return function
-
-
-def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
- """Decorator to mark a fixture factory function.
-
- This decorator can be used, with or without parameters, to define a
- fixture function.
-
- The name of the fixture function can later be referenced to cause its
- invocation ahead of running tests: test
- modules or classes can use the ``pytest.mark.usefixtures(fixturename)``
- marker.
-
- Test functions can directly use fixture names as input
- arguments in which case the fixture instance returned from the fixture
- function will be injected.
-
- Fixtures can provide their values to test functions using ``return`` or ``yield``
- statements. When using ``yield`` the code block after the ``yield`` statement is executed
- as teardown code regardless of the test outcome, and must yield exactly once.
-
- :arg scope: the scope for which this fixture is shared, one of
- ``"function"`` (default), ``"class"``, ``"module"``,
- ``"package"`` or ``"session"``.
-
- ``"package"`` is considered **experimental** at this time.
-
- :arg params: an optional list of parameters which will cause multiple
- invocations of the fixture function and all of the tests
- using it.
+
+ name = self.name or function.__name__
+ if name == "request":
+ warnings.warn(FIXTURE_NAMED_REQUEST)
+ function._pytestfixturefunction = self
+ return function
+
+
+def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
+ """Decorator to mark a fixture factory function.
+
+ This decorator can be used, with or without parameters, to define a
+ fixture function.
+
+ The name of the fixture function can later be referenced to cause its
+ invocation ahead of running tests: test
+ modules or classes can use the ``pytest.mark.usefixtures(fixturename)``
+ marker.
+
+ Test functions can directly use fixture names as input
+ arguments in which case the fixture instance returned from the fixture
+ function will be injected.
+
+ Fixtures can provide their values to test functions using ``return`` or ``yield``
+ statements. When using ``yield`` the code block after the ``yield`` statement is executed
+ as teardown code regardless of the test outcome, and must yield exactly once.
+
+ :arg scope: the scope for which this fixture is shared, one of
+ ``"function"`` (default), ``"class"``, ``"module"``,
+ ``"package"`` or ``"session"``.
+
+ ``"package"`` is considered **experimental** at this time.
+
+ :arg params: an optional list of parameters which will cause multiple
+ invocations of the fixture function and all of the tests
+ using it.
The current parameter is available in ``request.param``.
-
- :arg autouse: if True, the fixture func is activated for all tests that
- can see it. If False (the default) then an explicit
- reference is needed to activate the fixture.
-
- :arg ids: list of string ids each corresponding to the params
- so that they are part of the test id. If no ids are provided
- they will be generated automatically from the params.
-
- :arg name: the name of the fixture. This defaults to the name of the
- decorated function. If a fixture is used in the same module in
- which it is defined, the function name of the fixture will be
- shadowed by the function arg that requests the fixture; one way
- to resolve this is to name the decorated function
- ``fixture_<fixturename>`` and then use
- ``@pytest.fixture(name='<fixturename>')``.
- """
- if callable(scope) and params is None and autouse is False:
- # direct decoration
- return FixtureFunctionMarker("function", params, autouse, name=name)(scope)
- if params is not None and not isinstance(params, (list, tuple)):
- params = list(params)
- return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name)
-
-
-def yield_fixture(scope="function", params=None, autouse=False, ids=None, name=None):
- """ (return a) decorator to mark a yield-fixture factory function.
-
- .. deprecated:: 3.0
- Use :py:func:`pytest.fixture` directly instead.
- """
- return fixture(scope=scope, params=params, autouse=autouse, ids=ids, name=name)
-
-
-defaultfuncargprefixmarker = fixture()
-
-
-@fixture(scope="session")
-def pytestconfig(request):
- """Session-scoped fixture that returns the :class:`_pytest.config.Config` object.
-
- Example::
-
- def test_foo(pytestconfig):
+
+ :arg autouse: if True, the fixture func is activated for all tests that
+ can see it. If False (the default) then an explicit
+ reference is needed to activate the fixture.
+
+ :arg ids: list of string ids each corresponding to the params
+ so that they are part of the test id. If no ids are provided
+ they will be generated automatically from the params.
+
+ :arg name: the name of the fixture. This defaults to the name of the
+ decorated function. If a fixture is used in the same module in
+ which it is defined, the function name of the fixture will be
+ shadowed by the function arg that requests the fixture; one way
+ to resolve this is to name the decorated function
+ ``fixture_<fixturename>`` and then use
+ ``@pytest.fixture(name='<fixturename>')``.
+ """
+ if callable(scope) and params is None and autouse is False:
+ # direct decoration
+ return FixtureFunctionMarker("function", params, autouse, name=name)(scope)
+ if params is not None and not isinstance(params, (list, tuple)):
+ params = list(params)
+ return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name)
+
+
+def yield_fixture(scope="function", params=None, autouse=False, ids=None, name=None):
+ """ (return a) decorator to mark a yield-fixture factory function.
+
+ .. deprecated:: 3.0
+ Use :py:func:`pytest.fixture` directly instead.
+ """
+ return fixture(scope=scope, params=params, autouse=autouse, ids=ids, name=name)
+
+
+defaultfuncargprefixmarker = fixture()
+
+
+@fixture(scope="session")
+def pytestconfig(request):
+ """Session-scoped fixture that returns the :class:`_pytest.config.Config` object.
+
+ Example::
+
+ def test_foo(pytestconfig):
if pytestconfig.getoption("verbose") > 0:
- ...
-
- """
- return request.config
-
-
+ ...
+
+ """
+ return request.config
+
+
def pytest_addoption(parser):
parser.addini(
"usefixtures",
@@ -1083,50 +1083,50 @@ def pytest_addoption(parser):
)
-class FixtureManager(object):
- """
- pytest fixtures definitions and information is stored and managed
- from this class.
-
- During collection fm.parsefactories() is called multiple times to parse
- fixture function definitions into FixtureDef objects and internal
- data structures.
-
- During collection of test functions, metafunc-mechanics instantiate
- a FuncFixtureInfo object which is cached per node/func-name.
- This FuncFixtureInfo object is later retrieved by Function nodes
- which themselves offer a fixturenames attribute.
-
- The FuncFixtureInfo object holds information about fixtures and FixtureDefs
- relevant for a particular function. An initial list of fixtures is
- assembled like this:
-
- - ini-defined usefixtures
- - autouse-marked fixtures along the collection chain up from the function
- - usefixtures markers at module/class/function level
- - test function funcargs
-
- Subsequently the funcfixtureinfo.fixturenames attribute is computed
- as the closure of the fixtures needed to setup the initial fixtures,
- i. e. fixtures needed by fixture functions themselves are appended
- to the fixturenames list.
-
- Upon the test-setup phases all fixturenames are instantiated, retrieved
- by a lookup of their FuncFixtureInfo.
- """
-
- FixtureLookupError = FixtureLookupError
- FixtureLookupErrorRepr = FixtureLookupErrorRepr
-
- def __init__(self, session):
- self.session = session
- self.config = session.config
- self._arg2fixturedefs = {}
- self._holderobjseen = set()
- self._arg2finish = {}
- self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))]
- session.config.pluginmanager.register(self, "funcmanage")
-
+class FixtureManager(object):
+ """
+ pytest fixtures definitions and information is stored and managed
+ from this class.
+
+ During collection fm.parsefactories() is called multiple times to parse
+ fixture function definitions into FixtureDef objects and internal
+ data structures.
+
+ During collection of test functions, metafunc-mechanics instantiate
+ a FuncFixtureInfo object which is cached per node/func-name.
+ This FuncFixtureInfo object is later retrieved by Function nodes
+ which themselves offer a fixturenames attribute.
+
+ The FuncFixtureInfo object holds information about fixtures and FixtureDefs
+ relevant for a particular function. An initial list of fixtures is
+ assembled like this:
+
+ - ini-defined usefixtures
+ - autouse-marked fixtures along the collection chain up from the function
+ - usefixtures markers at module/class/function level
+ - test function funcargs
+
+ Subsequently the funcfixtureinfo.fixturenames attribute is computed
+ as the closure of the fixtures needed to setup the initial fixtures,
+ i. e. fixtures needed by fixture functions themselves are appended
+ to the fixturenames list.
+
+ Upon the test-setup phases all fixturenames are instantiated, retrieved
+ by a lookup of their FuncFixtureInfo.
+ """
+
+ FixtureLookupError = FixtureLookupError
+ FixtureLookupErrorRepr = FixtureLookupErrorRepr
+
+ def __init__(self, session):
+ self.session = session
+ self.config = session.config
+ self._arg2fixturedefs = {}
+ self._holderobjseen = set()
+ self._arg2finish = {}
+ self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))]
+ session.config.pluginmanager.register(self, "funcmanage")
+
def _get_direct_parametrize_args(self, node):
"""This function returns all the direct parametrization
arguments of a node, so we don't mistake them for fixtures
@@ -1148,106 +1148,106 @@ class FixtureManager(object):
return parametrize_argnames
- def getfixtureinfo(self, node, func, cls, funcargs=True):
- if funcargs and not getattr(node, "nofuncargs", False):
- argnames = getfuncargnames(func, cls=cls)
- else:
- argnames = ()
+ def getfixtureinfo(self, node, func, cls, funcargs=True):
+ if funcargs and not getattr(node, "nofuncargs", False):
+ argnames = getfuncargnames(func, cls=cls)
+ else:
+ argnames = ()
usefixtures = itertools.chain.from_iterable(
- mark.args for mark in node.iter_markers(name="usefixtures")
- )
- initialnames = tuple(usefixtures) + argnames
- fm = node.session._fixturemanager
- initialnames, names_closure, arg2fixturedefs = fm.getfixtureclosure(
+ mark.args for mark in node.iter_markers(name="usefixtures")
+ )
+ initialnames = tuple(usefixtures) + argnames
+ fm = node.session._fixturemanager
+ initialnames, names_closure, arg2fixturedefs = fm.getfixtureclosure(
initialnames, node, ignore_args=self._get_direct_parametrize_args(node)
- )
- return FuncFixtureInfo(argnames, initialnames, names_closure, arg2fixturedefs)
-
- def pytest_plugin_registered(self, plugin):
- nodeid = None
- try:
- p = py.path.local(plugin.__file__).realpath()
- except AttributeError:
- pass
- else:
- # construct the base nodeid which is later used to check
- # what fixtures are visible for particular tests (as denoted
- # by their test id)
- if p.basename.startswith("conftest.py"):
- nodeid = p.dirpath().relto(self.config.rootdir)
- if p.sep != nodes.SEP:
- nodeid = nodeid.replace(p.sep, nodes.SEP)
-
- self.parsefactories(plugin, nodeid)
-
- def _getautousenames(self, nodeid):
- """ return a tuple of fixture names to be used. """
- autousenames = []
- for baseid, basenames in self._nodeid_and_autousenames:
- if nodeid.startswith(baseid):
- if baseid:
- i = len(baseid)
- nextchar = nodeid[i : i + 1]
- if nextchar and nextchar not in ":/":
- continue
- autousenames.extend(basenames)
- return autousenames
-
+ )
+ return FuncFixtureInfo(argnames, initialnames, names_closure, arg2fixturedefs)
+
+ def pytest_plugin_registered(self, plugin):
+ nodeid = None
+ try:
+ p = py.path.local(plugin.__file__).realpath()
+ except AttributeError:
+ pass
+ else:
+ # construct the base nodeid which is later used to check
+ # what fixtures are visible for particular tests (as denoted
+ # by their test id)
+ if p.basename.startswith("conftest.py"):
+ nodeid = p.dirpath().relto(self.config.rootdir)
+ if p.sep != nodes.SEP:
+ nodeid = nodeid.replace(p.sep, nodes.SEP)
+
+ self.parsefactories(plugin, nodeid)
+
+ def _getautousenames(self, nodeid):
+ """ return a tuple of fixture names to be used. """
+ autousenames = []
+ for baseid, basenames in self._nodeid_and_autousenames:
+ if nodeid.startswith(baseid):
+ if baseid:
+ i = len(baseid)
+ nextchar = nodeid[i : i + 1]
+ if nextchar and nextchar not in ":/":
+ continue
+ autousenames.extend(basenames)
+ return autousenames
+
def getfixtureclosure(self, fixturenames, parentnode, ignore_args=()):
- # collect the closure of all fixtures , starting with the given
- # fixturenames as the initial set. As we have to visit all
- # factory definitions anyway, we also return an arg2fixturedefs
- # mapping so that the caller can reuse it and does not have
- # to re-discover fixturedefs again for each fixturename
- # (discovering matching fixtures for a given name/node is expensive)
-
- parentid = parentnode.nodeid
- fixturenames_closure = self._getautousenames(parentid)
-
- def merge(otherlist):
- for arg in otherlist:
- if arg not in fixturenames_closure:
- fixturenames_closure.append(arg)
-
- merge(fixturenames)
-
- # at this point, fixturenames_closure contains what we call "initialnames",
- # which is a set of fixturenames the function immediately requests. We
- # need to return it as well, so save this.
- initialnames = tuple(fixturenames_closure)
-
- arg2fixturedefs = {}
- lastlen = -1
- while lastlen != len(fixturenames_closure):
- lastlen = len(fixturenames_closure)
- for argname in fixturenames_closure:
+ # collect the closure of all fixtures , starting with the given
+ # fixturenames as the initial set. As we have to visit all
+ # factory definitions anyway, we also return an arg2fixturedefs
+ # mapping so that the caller can reuse it and does not have
+ # to re-discover fixturedefs again for each fixturename
+ # (discovering matching fixtures for a given name/node is expensive)
+
+ parentid = parentnode.nodeid
+ fixturenames_closure = self._getautousenames(parentid)
+
+ def merge(otherlist):
+ for arg in otherlist:
+ if arg not in fixturenames_closure:
+ fixturenames_closure.append(arg)
+
+ merge(fixturenames)
+
+ # at this point, fixturenames_closure contains what we call "initialnames",
+ # which is a set of fixturenames the function immediately requests. We
+ # need to return it as well, so save this.
+ initialnames = tuple(fixturenames_closure)
+
+ arg2fixturedefs = {}
+ lastlen = -1
+ while lastlen != len(fixturenames_closure):
+ lastlen = len(fixturenames_closure)
+ for argname in fixturenames_closure:
if argname in ignore_args:
continue
- if argname in arg2fixturedefs:
- continue
- fixturedefs = self.getfixturedefs(argname, parentid)
- if fixturedefs:
- arg2fixturedefs[argname] = fixturedefs
- merge(fixturedefs[-1].argnames)
-
- def sort_by_scope(arg_name):
- try:
- fixturedefs = arg2fixturedefs[arg_name]
- except KeyError:
- return scopes.index("function")
- else:
- return fixturedefs[-1].scopenum
-
- fixturenames_closure.sort(key=sort_by_scope)
- return initialnames, fixturenames_closure, arg2fixturedefs
-
- def pytest_generate_tests(self, metafunc):
- for argname in metafunc.fixturenames:
- faclist = metafunc._arg2fixturedefs.get(argname)
- if faclist:
- fixturedef = faclist[-1]
- if fixturedef.params is not None:
+ if argname in arg2fixturedefs:
+ continue
+ fixturedefs = self.getfixturedefs(argname, parentid)
+ if fixturedefs:
+ arg2fixturedefs[argname] = fixturedefs
+ merge(fixturedefs[-1].argnames)
+
+ def sort_by_scope(arg_name):
+ try:
+ fixturedefs = arg2fixturedefs[arg_name]
+ except KeyError:
+ return scopes.index("function")
+ else:
+ return fixturedefs[-1].scopenum
+
+ fixturenames_closure.sort(key=sort_by_scope)
+ return initialnames, fixturenames_closure, arg2fixturedefs
+
+ def pytest_generate_tests(self, metafunc):
+ for argname in metafunc.fixturenames:
+ faclist = metafunc._arg2fixturedefs.get(argname)
+ if faclist:
+ fixturedef = faclist[-1]
+ if fixturedef.params is not None:
markers = list(metafunc.definition.iter_markers("parametrize"))
for parametrize_mark in markers:
if "argnames" in parametrize_mark.kwargs:
@@ -1261,96 +1261,96 @@ class FixtureManager(object):
]
if argname in argnames:
break
- else:
- metafunc.parametrize(
- argname,
- fixturedef.params,
- indirect=True,
- scope=fixturedef.scope,
- ids=fixturedef.ids,
- )
- else:
- continue # will raise FixtureLookupError at setup time
-
- def pytest_collection_modifyitems(self, items):
- # separate parametrized setups
- items[:] = reorder_items(items)
-
- def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False):
- if nodeid is not NOTSET:
- holderobj = node_or_obj
- else:
- holderobj = node_or_obj.obj
- nodeid = node_or_obj.nodeid
- if holderobj in self._holderobjseen:
- return
-
- self._holderobjseen.add(holderobj)
- autousenames = []
- for name in dir(holderobj):
- # The attribute can be an arbitrary descriptor, so the attribute
- # access below can raise. safe_getatt() ignores such exceptions.
- obj = safe_getattr(holderobj, name, None)
- marker = getfixturemarker(obj)
+ else:
+ metafunc.parametrize(
+ argname,
+ fixturedef.params,
+ indirect=True,
+ scope=fixturedef.scope,
+ ids=fixturedef.ids,
+ )
+ else:
+ continue # will raise FixtureLookupError at setup time
+
+ def pytest_collection_modifyitems(self, items):
+ # separate parametrized setups
+ items[:] = reorder_items(items)
+
+ def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False):
+ if nodeid is not NOTSET:
+ holderobj = node_or_obj
+ else:
+ holderobj = node_or_obj.obj
+ nodeid = node_or_obj.nodeid
+ if holderobj in self._holderobjseen:
+ return
+
+ self._holderobjseen.add(holderobj)
+ autousenames = []
+ for name in dir(holderobj):
+ # The attribute can be an arbitrary descriptor, so the attribute
+ # access below can raise. safe_getatt() ignores such exceptions.
+ obj = safe_getattr(holderobj, name, None)
+ marker = getfixturemarker(obj)
if not isinstance(marker, FixtureFunctionMarker):
- # magic globals with __getattr__ might have got us a wrong
- # fixture attribute
- continue
-
+ # magic globals with __getattr__ might have got us a wrong
+ # fixture attribute
+ continue
+
if marker.name:
name = marker.name
- # during fixture definition we wrap the original fixture function
- # to issue a warning if called directly, so here we unwrap it in order to not emit the warning
- # when pytest itself calls the fixture function
- if six.PY2 and unittest:
- # hack on Python 2 because of the unbound methods
- obj = get_real_func(obj)
- else:
- obj = get_real_method(obj, holderobj)
-
- fixture_def = FixtureDef(
- self,
- nodeid,
- name,
- obj,
- marker.scope,
- marker.params,
- unittest=unittest,
- ids=marker.ids,
- )
-
- faclist = self._arg2fixturedefs.setdefault(name, [])
- if fixture_def.has_location:
- faclist.append(fixture_def)
- else:
- # fixturedefs with no location are at the front
- # so this inserts the current fixturedef after the
- # existing fixturedefs from external plugins but
- # before the fixturedefs provided in conftests.
- i = len([f for f in faclist if not f.has_location])
- faclist.insert(i, fixture_def)
- if marker.autouse:
- autousenames.append(name)
-
- if autousenames:
- self._nodeid_and_autousenames.append((nodeid or "", autousenames))
-
- def getfixturedefs(self, argname, nodeid):
- """
- Gets a list of fixtures which are applicable to the given node id.
-
- :param str argname: name of the fixture to search for
- :param str nodeid: full node id of the requesting test.
- :return: list[FixtureDef]
- """
- try:
- fixturedefs = self._arg2fixturedefs[argname]
- except KeyError:
- return None
- return tuple(self._matchfactories(fixturedefs, nodeid))
-
- def _matchfactories(self, fixturedefs, nodeid):
- for fixturedef in fixturedefs:
- if nodes.ischildnode(fixturedef.baseid, nodeid):
- yield fixturedef
+ # during fixture definition we wrap the original fixture function
+ # to issue a warning if called directly, so here we unwrap it in order to not emit the warning
+ # when pytest itself calls the fixture function
+ if six.PY2 and unittest:
+ # hack on Python 2 because of the unbound methods
+ obj = get_real_func(obj)
+ else:
+ obj = get_real_method(obj, holderobj)
+
+ fixture_def = FixtureDef(
+ self,
+ nodeid,
+ name,
+ obj,
+ marker.scope,
+ marker.params,
+ unittest=unittest,
+ ids=marker.ids,
+ )
+
+ faclist = self._arg2fixturedefs.setdefault(name, [])
+ if fixture_def.has_location:
+ faclist.append(fixture_def)
+ else:
+ # fixturedefs with no location are at the front
+ # so this inserts the current fixturedef after the
+ # existing fixturedefs from external plugins but
+ # before the fixturedefs provided in conftests.
+ i = len([f for f in faclist if not f.has_location])
+ faclist.insert(i, fixture_def)
+ if marker.autouse:
+ autousenames.append(name)
+
+ if autousenames:
+ self._nodeid_and_autousenames.append((nodeid or "", autousenames))
+
+ def getfixturedefs(self, argname, nodeid):
+ """
+ Gets a list of fixtures which are applicable to the given node id.
+
+ :param str argname: name of the fixture to search for
+ :param str nodeid: full node id of the requesting test.
+ :return: list[FixtureDef]
+ """
+ try:
+ fixturedefs = self._arg2fixturedefs[argname]
+ except KeyError:
+ return None
+ return tuple(self._matchfactories(fixturedefs, nodeid))
+
+ def _matchfactories(self, fixturedefs, nodeid):
+ for fixturedef in fixturedefs:
+ if nodes.ischildnode(fixturedef.baseid, nodeid):
+ yield fixturedef
diff --git a/contrib/python/pytest/py2/_pytest/freeze_support.py b/contrib/python/pytest/py2/_pytest/freeze_support.py
index 1743e38bf6..aeeec2a56b 100644
--- a/contrib/python/pytest/py2/_pytest/freeze_support.py
+++ b/contrib/python/pytest/py2/_pytest/freeze_support.py
@@ -1,48 +1,48 @@
# -*- coding: utf-8 -*-
-"""
-Provides a function to report all internal modules for using freezing tools
-pytest
-"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-
-def freeze_includes():
- """
- Returns a list of module names used by pytest that should be
- included by cx_freeze.
- """
- import py
- import _pytest
-
- result = list(_iter_all_modules(py))
- result += list(_iter_all_modules(_pytest))
- return result
-
-
-def _iter_all_modules(package, prefix=""):
- """
- Iterates over the names of all modules that can be found in the given
- package, recursively.
- Example:
- _iter_all_modules(_pytest) ->
- ['_pytest.assertion.newinterpret',
- '_pytest.capture',
- '_pytest.core',
- ...
- ]
- """
- import os
- import pkgutil
-
- if type(package) is not str:
- path, prefix = package.__path__[0], package.__name__ + "."
- else:
- path = package
- for _, name, is_package in pkgutil.iter_modules([path]):
- if is_package:
- for m in _iter_all_modules(os.path.join(path, name), prefix=name + "."):
- yield prefix + m
- else:
- yield prefix + name
+"""
+Provides a function to report all internal modules for using freezing tools
+pytest
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+
+def freeze_includes():
+ """
+ Returns a list of module names used by pytest that should be
+ included by cx_freeze.
+ """
+ import py
+ import _pytest
+
+ result = list(_iter_all_modules(py))
+ result += list(_iter_all_modules(_pytest))
+ return result
+
+
+def _iter_all_modules(package, prefix=""):
+ """
+ Iterates over the names of all modules that can be found in the given
+ package, recursively.
+ Example:
+ _iter_all_modules(_pytest) ->
+ ['_pytest.assertion.newinterpret',
+ '_pytest.capture',
+ '_pytest.core',
+ ...
+ ]
+ """
+ import os
+ import pkgutil
+
+ if type(package) is not str:
+ path, prefix = package.__path__[0], package.__name__ + "."
+ else:
+ path = package
+ for _, name, is_package in pkgutil.iter_modules([path]):
+ if is_package:
+ for m in _iter_all_modules(os.path.join(path, name), prefix=name + "."):
+ yield prefix + m
+ else:
+ yield prefix + name
diff --git a/contrib/python/pytest/py2/_pytest/helpconfig.py b/contrib/python/pytest/py2/_pytest/helpconfig.py
index c886794733..5681160160 100644
--- a/contrib/python/pytest/py2/_pytest/helpconfig.py
+++ b/contrib/python/pytest/py2/_pytest/helpconfig.py
@@ -1,124 +1,124 @@
# -*- coding: utf-8 -*-
-""" version info, help messages, tracing configuration. """
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import os
-import sys
-from argparse import Action
-
-import py
-
-import pytest
-from _pytest.config import PrintHelp
-
-
-class HelpAction(Action):
- """This is an argparse Action that will raise an exception in
- order to skip the rest of the argument parsing when --help is passed.
- This prevents argparse from quitting due to missing required arguments
- when any are defined, for example by ``pytest_addoption``.
- This is similar to the way that the builtin argparse --help option is
- implemented by raising SystemExit.
- """
-
- def __init__(self, option_strings, dest=None, default=False, help=None):
- super(HelpAction, self).__init__(
- option_strings=option_strings,
- dest=dest,
- const=True,
- default=default,
- nargs=0,
- help=help,
- )
-
- def __call__(self, parser, namespace, values, option_string=None):
- setattr(namespace, self.dest, self.const)
-
- # We should only skip the rest of the parsing after preparse is done
- if getattr(parser._parser, "after_preparse", False):
- raise PrintHelp
-
-
-def pytest_addoption(parser):
- group = parser.getgroup("debugconfig")
- group.addoption(
- "--version",
- action="store_true",
- help="display pytest lib version and import information.",
- )
- group._addoption(
- "-h",
- "--help",
- action=HelpAction,
- dest="help",
- help="show help message and configuration info",
- )
- group._addoption(
- "-p",
- action="append",
- dest="plugins",
- default=[],
- metavar="name",
+""" version info, help messages, tracing configuration. """
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import os
+import sys
+from argparse import Action
+
+import py
+
+import pytest
+from _pytest.config import PrintHelp
+
+
+class HelpAction(Action):
+ """This is an argparse Action that will raise an exception in
+ order to skip the rest of the argument parsing when --help is passed.
+ This prevents argparse from quitting due to missing required arguments
+ when any are defined, for example by ``pytest_addoption``.
+ This is similar to the way that the builtin argparse --help option is
+ implemented by raising SystemExit.
+ """
+
+ def __init__(self, option_strings, dest=None, default=False, help=None):
+ super(HelpAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ const=True,
+ default=default,
+ nargs=0,
+ help=help,
+ )
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ setattr(namespace, self.dest, self.const)
+
+ # We should only skip the rest of the parsing after preparse is done
+ if getattr(parser._parser, "after_preparse", False):
+ raise PrintHelp
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("debugconfig")
+ group.addoption(
+ "--version",
+ action="store_true",
+ help="display pytest lib version and import information.",
+ )
+ group._addoption(
+ "-h",
+ "--help",
+ action=HelpAction,
+ dest="help",
+ help="show help message and configuration info",
+ )
+ group._addoption(
+ "-p",
+ action="append",
+ dest="plugins",
+ default=[],
+ metavar="name",
help="early-load given plugin module name or entry point (multi-allowed). "
- "To avoid loading of plugins, use the `no:` prefix, e.g. "
- "`no:doctest`.",
- )
- group.addoption(
- "--traceconfig",
- "--trace-config",
- action="store_true",
- default=False,
- help="trace considerations of conftest.py files.",
- ),
- group.addoption(
- "--debug",
- action="store_true",
- dest="debug",
- default=False,
- help="store internal tracing debug information in 'pytestdebug.log'.",
- )
- group._addoption(
- "-o",
- "--override-ini",
- dest="override_ini",
- action="append",
- help='override ini option with "option=value" style, e.g. `-o xfail_strict=True -o cache_dir=cache`.',
- )
-
-
-@pytest.hookimpl(hookwrapper=True)
-def pytest_cmdline_parse():
- outcome = yield
- config = outcome.get_result()
- if config.option.debug:
- path = os.path.abspath("pytestdebug.log")
- debugfile = open(path, "w")
- debugfile.write(
- "versions pytest-%s, py-%s, "
- "python-%s\ncwd=%s\nargs=%s\n\n"
- % (
- pytest.__version__,
- py.__version__,
- ".".join(map(str, sys.version_info)),
- os.getcwd(),
- config._origargs,
- )
- )
- config.trace.root.setwriter(debugfile.write)
- undo_tracing = config.pluginmanager.enable_tracing()
- sys.stderr.write("writing pytestdebug information to %s\n" % path)
-
- def unset_tracing():
- debugfile.close()
- sys.stderr.write("wrote pytestdebug information to %s\n" % debugfile.name)
- config.trace.root.setwriter(None)
- undo_tracing()
-
- config.add_cleanup(unset_tracing)
-
-
+ "To avoid loading of plugins, use the `no:` prefix, e.g. "
+ "`no:doctest`.",
+ )
+ group.addoption(
+ "--traceconfig",
+ "--trace-config",
+ action="store_true",
+ default=False,
+ help="trace considerations of conftest.py files.",
+ ),
+ group.addoption(
+ "--debug",
+ action="store_true",
+ dest="debug",
+ default=False,
+ help="store internal tracing debug information in 'pytestdebug.log'.",
+ )
+ group._addoption(
+ "-o",
+ "--override-ini",
+ dest="override_ini",
+ action="append",
+ help='override ini option with "option=value" style, e.g. `-o xfail_strict=True -o cache_dir=cache`.',
+ )
+
+
+@pytest.hookimpl(hookwrapper=True)
+def pytest_cmdline_parse():
+ outcome = yield
+ config = outcome.get_result()
+ if config.option.debug:
+ path = os.path.abspath("pytestdebug.log")
+ debugfile = open(path, "w")
+ debugfile.write(
+ "versions pytest-%s, py-%s, "
+ "python-%s\ncwd=%s\nargs=%s\n\n"
+ % (
+ pytest.__version__,
+ py.__version__,
+ ".".join(map(str, sys.version_info)),
+ os.getcwd(),
+ config._origargs,
+ )
+ )
+ config.trace.root.setwriter(debugfile.write)
+ undo_tracing = config.pluginmanager.enable_tracing()
+ sys.stderr.write("writing pytestdebug information to %s\n" % path)
+
+ def unset_tracing():
+ debugfile.close()
+ sys.stderr.write("wrote pytestdebug information to %s\n" % debugfile.name)
+ config.trace.root.setwriter(None)
+ undo_tracing()
+
+ config.add_cleanup(unset_tracing)
+
+
def showversion(config):
p = py.path.local(pytest.__file__)
sys.stderr.write(
@@ -130,36 +130,36 @@ def showversion(config):
sys.stderr.write(line + "\n")
-def pytest_cmdline_main(config):
- if config.option.version:
+def pytest_cmdline_main(config):
+ if config.option.version:
showversion(config)
- return 0
- elif config.option.help:
- config._do_configure()
- showhelp(config)
- config._ensure_unconfigure()
- return 0
-
-
-def showhelp(config):
+ return 0
+ elif config.option.help:
+ config._do_configure()
+ showhelp(config)
+ config._ensure_unconfigure()
+ return 0
+
+
+def showhelp(config):
import textwrap
- reporter = config.pluginmanager.get_plugin("terminalreporter")
- tw = reporter._tw
- tw.write(config._parser.optparser.format_help())
- tw.line()
- tw.line(
- "[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:"
- )
- tw.line()
-
+ reporter = config.pluginmanager.get_plugin("terminalreporter")
+ tw = reporter._tw
+ tw.write(config._parser.optparser.format_help())
+ tw.line()
+ tw.line(
+ "[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:"
+ )
+ tw.line()
+
columns = tw.fullwidth # costly call
indent_len = 24 # based on argparse's max_help_position=24
indent = " " * indent_len
- for name in config._parser._ininames:
- help, type, default = config._parser._inidict[name]
- if type is None:
- type = "string"
+ for name in config._parser._ininames:
+ help, type, default = config._parser._inidict[name]
+ if type is None:
+ type = "string"
spec = "%s (%s):" % (name, type)
tw.write(" %s" % spec)
spec_len = len(spec)
@@ -173,7 +173,7 @@ def showhelp(config):
subsequent_indent=indent,
break_on_hyphens=False,
)
-
+
for line in helplines:
tw.line(line)
else:
@@ -185,63 +185,63 @@ def showhelp(config):
for line in wrapped[1:]:
tw.line(indent + line)
- tw.line()
- tw.line("environment variables:")
- vars = [
- ("PYTEST_ADDOPTS", "extra command line options"),
- ("PYTEST_PLUGINS", "comma-separated plugins to load during startup"),
- ("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "set to disable plugin auto-loading"),
- ("PYTEST_DEBUG", "set to enable debug tracing of pytest's internals"),
- ]
- for name, help in vars:
- tw.line(" %-24s %s" % (name, help))
- tw.line()
- tw.line()
-
- tw.line("to see available markers type: pytest --markers")
- tw.line("to see available fixtures type: pytest --fixtures")
- tw.line(
- "(shown according to specified file_or_dir or current dir "
- "if not specified; fixtures with leading '_' are only shown "
- "with the '-v' option"
- )
-
- for warningreport in reporter.stats.get("warnings", []):
- tw.line("warning : " + warningreport.message, red=True)
- return
-
-
-conftest_options = [("pytest_plugins", "list of plugin names to load")]
-
-
-def getpluginversioninfo(config):
- lines = []
- plugininfo = config.pluginmanager.list_plugin_distinfo()
- if plugininfo:
- lines.append("setuptools registered plugins:")
- for plugin, dist in plugininfo:
- loc = getattr(plugin, "__file__", repr(plugin))
- content = "%s-%s at %s" % (dist.project_name, dist.version, loc)
- lines.append(" " + content)
- return lines
-
-
-def pytest_report_header(config):
- lines = []
- if config.option.debug or config.option.traceconfig:
- lines.append("using: pytest-%s pylib-%s" % (pytest.__version__, py.__version__))
-
- verinfo = getpluginversioninfo(config)
- if verinfo:
- lines.extend(verinfo)
-
- if config.option.traceconfig:
- lines.append("active plugins:")
- items = config.pluginmanager.list_name_plugin()
- for name, plugin in items:
- if hasattr(plugin, "__file__"):
- r = plugin.__file__
- else:
- r = repr(plugin)
- lines.append(" %-20s: %s" % (name, r))
- return lines
+ tw.line()
+ tw.line("environment variables:")
+ vars = [
+ ("PYTEST_ADDOPTS", "extra command line options"),
+ ("PYTEST_PLUGINS", "comma-separated plugins to load during startup"),
+ ("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "set to disable plugin auto-loading"),
+ ("PYTEST_DEBUG", "set to enable debug tracing of pytest's internals"),
+ ]
+ for name, help in vars:
+ tw.line(" %-24s %s" % (name, help))
+ tw.line()
+ tw.line()
+
+ tw.line("to see available markers type: pytest --markers")
+ tw.line("to see available fixtures type: pytest --fixtures")
+ tw.line(
+ "(shown according to specified file_or_dir or current dir "
+ "if not specified; fixtures with leading '_' are only shown "
+ "with the '-v' option"
+ )
+
+ for warningreport in reporter.stats.get("warnings", []):
+ tw.line("warning : " + warningreport.message, red=True)
+ return
+
+
+conftest_options = [("pytest_plugins", "list of plugin names to load")]
+
+
+def getpluginversioninfo(config):
+ lines = []
+ plugininfo = config.pluginmanager.list_plugin_distinfo()
+ if plugininfo:
+ lines.append("setuptools registered plugins:")
+ for plugin, dist in plugininfo:
+ loc = getattr(plugin, "__file__", repr(plugin))
+ content = "%s-%s at %s" % (dist.project_name, dist.version, loc)
+ lines.append(" " + content)
+ return lines
+
+
+def pytest_report_header(config):
+ lines = []
+ if config.option.debug or config.option.traceconfig:
+ lines.append("using: pytest-%s pylib-%s" % (pytest.__version__, py.__version__))
+
+ verinfo = getpluginversioninfo(config)
+ if verinfo:
+ lines.extend(verinfo)
+
+ if config.option.traceconfig:
+ lines.append("active plugins:")
+ items = config.pluginmanager.list_name_plugin()
+ for name, plugin in items:
+ if hasattr(plugin, "__file__"):
+ r = plugin.__file__
+ else:
+ r = repr(plugin)
+ lines.append(" %-20s: %s" % (name, r))
+ return lines
diff --git a/contrib/python/pytest/py2/_pytest/hookspec.py b/contrib/python/pytest/py2/_pytest/hookspec.py
index 7a78bbd3b1..7ab6154b17 100644
--- a/contrib/python/pytest/py2/_pytest/hookspec.py
+++ b/contrib/python/pytest/py2/_pytest/hookspec.py
@@ -1,385 +1,385 @@
# -*- coding: utf-8 -*-
-""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
-from pluggy import HookspecMarker
-
+""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
+from pluggy import HookspecMarker
+
from _pytest.deprecated import PYTEST_LOGWARNING
-
-hookspec = HookspecMarker("pytest")
-
-# -------------------------------------------------------------------------
-# Initialization hooks called for every plugin
-# -------------------------------------------------------------------------
-
-
-@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)``.
-
-
- :param _pytest.config.PytestPluginManager pluginmanager: pytest plugin manager
-
- .. note::
- This hook is incompatible with ``hookwrapper=True``.
- """
-
-
-@hookspec(historic=True)
-def pytest_plugin_registered(plugin, manager):
- """ a new pytest plugin got registered.
-
- :param plugin: the plugin module or instance
- :param _pytest.config.PytestPluginManager manager: pytest plugin manager
-
- .. note::
- This hook is incompatible with ``hookwrapper=True``.
- """
-
-
-@hookspec(historic=True)
-def pytest_addoption(parser):
- """register argparse-style options and ini-style config values,
- called once at the beginning of a test run.
-
- .. note::
-
- This function should be implemented only in plugins or ``conftest.py``
- files situated at the tests root directory due to how pytest
- :ref:`discovers plugins during startup <pluginorder>`.
-
- :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>`.
-
- Options can later be accessed through the
- :py:class:`config <_pytest.config.Config>` object, respectively:
-
- - :py:func:`config.getoption(name) <_pytest.config.Config.getoption>` to
- retrieve the value of a command line option.
-
- - :py:func:`config.getini(name) <_pytest.config.Config.getini>` to retrieve
- 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.
-
- .. note::
- This hook is incompatible with ``hookwrapper=True``.
- """
-
-
-@hookspec(historic=True)
-def pytest_configure(config):
- """
- Allows plugins and conftest files to perform initial configuration.
-
- This hook is called for every plugin and initial conftest file
- after command line options have been parsed.
-
- After that, the hook is called for other conftest files as they are
- imported.
-
- .. note::
- This hook is incompatible with ``hookwrapper=True``.
-
- :arg _pytest.config.Config config: pytest config object
- """
-
-
-# -------------------------------------------------------------------------
-# Bootstrapping hooks called for plugins registered early enough:
-# internal and 3rd party plugins.
-# -------------------------------------------------------------------------
-
-
-@hookspec(firstresult=True)
-def pytest_cmdline_parse(pluginmanager, args):
- """return initialized config object, parsing the specified args.
-
- Stops at first non-None result, see :ref:`firstresult`
-
- .. note::
+
+hookspec = HookspecMarker("pytest")
+
+# -------------------------------------------------------------------------
+# Initialization hooks called for every plugin
+# -------------------------------------------------------------------------
+
+
+@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)``.
+
+
+ :param _pytest.config.PytestPluginManager pluginmanager: pytest plugin manager
+
+ .. note::
+ This hook is incompatible with ``hookwrapper=True``.
+ """
+
+
+@hookspec(historic=True)
+def pytest_plugin_registered(plugin, manager):
+ """ a new pytest plugin got registered.
+
+ :param plugin: the plugin module or instance
+ :param _pytest.config.PytestPluginManager manager: pytest plugin manager
+
+ .. note::
+ This hook is incompatible with ``hookwrapper=True``.
+ """
+
+
+@hookspec(historic=True)
+def pytest_addoption(parser):
+ """register argparse-style options and ini-style config values,
+ called once at the beginning of a test run.
+
+ .. note::
+
+ This function should be implemented only in plugins or ``conftest.py``
+ files situated at the tests root directory due to how pytest
+ :ref:`discovers plugins during startup <pluginorder>`.
+
+ :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>`.
+
+ Options can later be accessed through the
+ :py:class:`config <_pytest.config.Config>` object, respectively:
+
+ - :py:func:`config.getoption(name) <_pytest.config.Config.getoption>` to
+ retrieve the value of a command line option.
+
+ - :py:func:`config.getini(name) <_pytest.config.Config.getini>` to retrieve
+ 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.
+
+ .. note::
+ This hook is incompatible with ``hookwrapper=True``.
+ """
+
+
+@hookspec(historic=True)
+def pytest_configure(config):
+ """
+ Allows plugins and conftest files to perform initial configuration.
+
+ This hook is called for every plugin and initial conftest file
+ after command line options have been parsed.
+
+ After that, the hook is called for other conftest files as they are
+ imported.
+
+ .. note::
+ This hook is incompatible with ``hookwrapper=True``.
+
+ :arg _pytest.config.Config config: pytest config object
+ """
+
+
+# -------------------------------------------------------------------------
+# Bootstrapping hooks called for plugins registered early enough:
+# internal and 3rd party plugins.
+# -------------------------------------------------------------------------
+
+
+@hookspec(firstresult=True)
+def pytest_cmdline_parse(pluginmanager, args):
+ """return initialized config object, parsing the specified args.
+
+ Stops at first non-None result, see :ref:`firstresult`
+
+ .. note::
This hook will only be called for plugin classes passed to the ``plugins`` arg when using `pytest.main`_ to
perform an in-process test run.
-
- :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.
-
- This hook is considered deprecated and will be removed in a future pytest version. Consider
- using :func:`pytest_load_initial_conftests` instead.
-
- .. note::
- This hook will not be called for ``conftest.py`` files, only for setuptools plugins.
-
- :param _pytest.config.Config config: pytest config object
- :param list[str] args: list of arguments passed on the command line
- """
-
-
-@hookspec(firstresult=True)
-def pytest_cmdline_main(config):
- """ called for performing the main command line action. The default
- implementation will invoke the configure hooks and runtest_mainloop.
-
- .. note::
- This hook will not be called for ``conftest.py`` files, only for setuptools plugins.
-
- 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.
-
- .. note::
- This hook will not be called for ``conftest.py`` files, only for setuptools plugins.
-
- :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
- """
-
-
-# -------------------------------------------------------------------------
-# collection hooks
-# -------------------------------------------------------------------------
-
-
-@hookspec(firstresult=True)
-def pytest_collection(session):
- """Perform the collection protocol for the given session.
-
- 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.
-
- :param _pytest.main.Session session: the pytest session object
- :param _pytest.config.Config config: pytest config object
- :param List[_pytest.nodes.Item] items: list of item objects
- """
-
-
-def pytest_collection_finish(session):
- """ called after collection has been performed and modified.
-
- :param _pytest.main.Session session: the pytest session object
- """
-
-
-@hookspec(firstresult=True)
-def pytest_ignore_collect(path, config):
- """ return True to prevent considering this path for collection.
- This hook is consulted for all files and directories prior to calling
- more specific hooks.
-
- 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.
+
+ This hook is considered deprecated and will be removed in a future pytest version. Consider
+ using :func:`pytest_load_initial_conftests` instead.
+
+ .. note::
+ This hook will not be called for ``conftest.py`` files, only for setuptools plugins.
+
+ :param _pytest.config.Config config: pytest config object
+ :param list[str] args: list of arguments passed on the command line
+ """
+
+
+@hookspec(firstresult=True)
+def pytest_cmdline_main(config):
+ """ called for performing the main command line action. The default
+ implementation will invoke the configure hooks and runtest_mainloop.
+
+ .. note::
+ This hook will not be called for ``conftest.py`` files, only for setuptools plugins.
+
+ 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.
+
+ .. note::
+ This hook will not be called for ``conftest.py`` files, only for setuptools plugins.
+
+ :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
+ """
+
+
+# -------------------------------------------------------------------------
+# collection hooks
+# -------------------------------------------------------------------------
+
+
+@hookspec(firstresult=True)
+def pytest_collection(session):
+ """Perform the collection protocol for the given session.
+
+ 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.
+
+ :param _pytest.main.Session session: the pytest session object
+ :param _pytest.config.Config config: pytest config object
+ :param List[_pytest.nodes.Item] items: list of item objects
+ """
+
+
+def pytest_collection_finish(session):
+ """ called after collection has been performed and modified.
+
+ :param _pytest.main.Session session: the pytest session object
+ """
+
+
+@hookspec(firstresult=True)
+def pytest_ignore_collect(path, config):
+ """ return True to prevent considering this path for collection.
+ This hook is consulted for all files and directories prior to calling
+ more specific hooks.
+
+ Stops at first non-None result, see :ref:`firstresult`
+
:param path: a :py:class:`py.path.local` - the path to analyze
- :param _pytest.config.Config config: pytest config object
- """
-
-
-@hookspec(firstresult=True)
-def pytest_collect_directory(path, parent):
- """ called before traversing a directory for collection files.
-
- Stops at first non-None result, see :ref:`firstresult`
-
+ :param _pytest.config.Config config: pytest config object
+ """
+
+
+@hookspec(firstresult=True)
+def pytest_collect_directory(path, parent):
+ """ called before traversing a directory for collection files.
+
+ Stops at first non-None result, see :ref:`firstresult`
+
:param path: a :py:class:`py.path.local` - 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.
-
+ """
+
+
+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.
+
:param path: a :py:class:`py.path.local` - the path to collect
- """
-
-
-# logging hooks for collection
-
-
-def pytest_collectstart(collector):
- """ collector starts collecting. """
-
-
-def pytest_itemcollected(item):
- """ we just collected a test item. """
-
-
-def pytest_collectreport(report):
- """ collector finished collecting. """
-
-
-def pytest_deselected(items):
+ """
+
+
+# logging hooks for collection
+
+
+def pytest_collectstart(collector):
+ """ collector starts collecting. """
+
+
+def pytest_itemcollected(item):
+ """ we just collected a test item. """
+
+
+def pytest_collectreport(report):
+ """ collector finished collecting. """
+
+
+def pytest_deselected(items):
""" called for test items deselected, e.g. by keyword. """
-
-
-@hookspec(firstresult=True)
-def pytest_make_collect_report(collector):
- """ perform ``collector.collect()`` and return a CollectReport.
-
- Stops at first non-None result, see :ref:`firstresult` """
-
-
-# -------------------------------------------------------------------------
-# Python test function related hooks
-# -------------------------------------------------------------------------
-
-
-@hookspec(firstresult=True)
-def pytest_pycollect_makemodule(path, parent):
- """ return a Module collector or None for the given path.
- This hook will be called for each matching test module path.
- The pytest_collect_file hook needs to be used if you want to
- create test modules for files that do not match as a test module.
-
+
+
+@hookspec(firstresult=True)
+def pytest_make_collect_report(collector):
+ """ perform ``collector.collect()`` and return a CollectReport.
+
+ Stops at first non-None result, see :ref:`firstresult` """
+
+
+# -------------------------------------------------------------------------
+# Python test function related hooks
+# -------------------------------------------------------------------------
+
+
+@hookspec(firstresult=True)
+def pytest_pycollect_makemodule(path, parent):
+ """ return a Module collector or None for the given path.
+ This hook will be called for each matching test module path.
+ The pytest_collect_file hook needs to be used if you want to
+ create test modules for files that do not match as a test module.
+
Stops at first non-None result, see :ref:`firstresult`
-
+
:param path: a :py:class:`py.path.local` - the path of module to collect
"""
-
-
-@hookspec(firstresult=True)
-def pytest_pycollect_makeitem(collector, name, obj):
- """ return custom item/collector for a python object in a module, or None.
-
- Stops at first non-None result, see :ref:`firstresult` """
-
-
-@hookspec(firstresult=True)
-def pytest_pyfunc_call(pyfuncitem):
- """ call underlying test function.
-
- Stops at first non-None result, see :ref:`firstresult` """
-
-
-def pytest_generate_tests(metafunc):
- """ generate (multiple) parametrized calls to a test function."""
-
-
-@hookspec(firstresult=True)
-def pytest_make_parametrize_id(config, val, argname):
- """Return a user-friendly string representation of the given ``val`` that will be used
- 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`
-
- :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
-# -------------------------------------------------------------------------
-
-
-@hookspec(firstresult=True)
-def pytest_runtestloop(session):
- """ called for performing the main runtest loop
- (after collection finished).
-
- 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. """
-
-
-@hookspec(firstresult=True)
-def pytest_runtest_protocol(item, nextitem):
- """ implements the runtest_setup/call/teardown protocol for
- the given test item, including capturing exceptions and calling
- reporting hooks.
-
- :arg item: test item for which the runtest protocol is performed.
-
- :arg nextitem: the scheduled-to-be-next test item (or None if this
- is the end my friend). This argument is passed on to
- :py:func:`pytest_runtest_teardown`.
-
- :return boolean: True if no further hook implementations should be invoked.
-
-
- Stops at first non-None result, see :ref:`firstresult` """
-
-
-def pytest_runtest_logstart(nodeid, location):
- """ signal the start of running a single test item.
-
- This hook will be called **before** :func:`pytest_runtest_setup`, :func:`pytest_runtest_call` and
- :func:`pytest_runtest_teardown` hooks.
-
- :param str nodeid: full id of the item
- :param location: a triple of ``(filename, linenum, testname)``
- """
-
-
-def pytest_runtest_logfinish(nodeid, location):
- """ signal the complete finish of running a single test item.
-
- This hook will be called **after** :func:`pytest_runtest_setup`, :func:`pytest_runtest_call` and
- :func:`pytest_runtest_teardown` hooks.
-
- :param str nodeid: full id of the item
- :param location: a triple of ``(filename, linenum, testname)``
- """
-
-
-def pytest_runtest_setup(item):
- """ called before ``pytest_runtest_call(item)``. """
-
-
-def pytest_runtest_call(item):
- """ called to execute the test ``item``. """
-
-
-def pytest_runtest_teardown(item, nextitem):
- """ called after ``pytest_runtest_call``.
-
- :arg nextitem: the scheduled-to-be-next test item (None if no further
- test item is scheduled). This argument can be used to
- perform exact teardowns, i.e. calling just enough finalizers
- so that nextitem only needs to call setup-functions.
- """
-
-
-@hookspec(firstresult=True)
-def pytest_runtest_makereport(item, call):
- """ return a :py:class:`_pytest.runner.TestReport` object
- for the given :py:class:`pytest.Item <_pytest.main.Item>` and
- :py:class:`_pytest.runner.CallInfo`.
-
- Stops at first non-None result, see :ref:`firstresult` """
-
-
-def pytest_runtest_logreport(report):
- """ process a test setup/call/teardown report relating to
- the respective phase of executing a test. """
-
-
+
+
+@hookspec(firstresult=True)
+def pytest_pycollect_makeitem(collector, name, obj):
+ """ return custom item/collector for a python object in a module, or None.
+
+ Stops at first non-None result, see :ref:`firstresult` """
+
+
+@hookspec(firstresult=True)
+def pytest_pyfunc_call(pyfuncitem):
+ """ call underlying test function.
+
+ Stops at first non-None result, see :ref:`firstresult` """
+
+
+def pytest_generate_tests(metafunc):
+ """ generate (multiple) parametrized calls to a test function."""
+
+
+@hookspec(firstresult=True)
+def pytest_make_parametrize_id(config, val, argname):
+ """Return a user-friendly string representation of the given ``val`` that will be used
+ 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`
+
+ :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
+# -------------------------------------------------------------------------
+
+
+@hookspec(firstresult=True)
+def pytest_runtestloop(session):
+ """ called for performing the main runtest loop
+ (after collection finished).
+
+ 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. """
+
+
+@hookspec(firstresult=True)
+def pytest_runtest_protocol(item, nextitem):
+ """ implements the runtest_setup/call/teardown protocol for
+ the given test item, including capturing exceptions and calling
+ reporting hooks.
+
+ :arg item: test item for which the runtest protocol is performed.
+
+ :arg nextitem: the scheduled-to-be-next test item (or None if this
+ is the end my friend). This argument is passed on to
+ :py:func:`pytest_runtest_teardown`.
+
+ :return boolean: True if no further hook implementations should be invoked.
+
+
+ Stops at first non-None result, see :ref:`firstresult` """
+
+
+def pytest_runtest_logstart(nodeid, location):
+ """ signal the start of running a single test item.
+
+ This hook will be called **before** :func:`pytest_runtest_setup`, :func:`pytest_runtest_call` and
+ :func:`pytest_runtest_teardown` hooks.
+
+ :param str nodeid: full id of the item
+ :param location: a triple of ``(filename, linenum, testname)``
+ """
+
+
+def pytest_runtest_logfinish(nodeid, location):
+ """ signal the complete finish of running a single test item.
+
+ This hook will be called **after** :func:`pytest_runtest_setup`, :func:`pytest_runtest_call` and
+ :func:`pytest_runtest_teardown` hooks.
+
+ :param str nodeid: full id of the item
+ :param location: a triple of ``(filename, linenum, testname)``
+ """
+
+
+def pytest_runtest_setup(item):
+ """ called before ``pytest_runtest_call(item)``. """
+
+
+def pytest_runtest_call(item):
+ """ called to execute the test ``item``. """
+
+
+def pytest_runtest_teardown(item, nextitem):
+ """ called after ``pytest_runtest_call``.
+
+ :arg nextitem: the scheduled-to-be-next test item (None if no further
+ test item is scheduled). This argument can be used to
+ perform exact teardowns, i.e. calling just enough finalizers
+ so that nextitem only needs to call setup-functions.
+ """
+
+
+@hookspec(firstresult=True)
+def pytest_runtest_makereport(item, call):
+ """ return a :py:class:`_pytest.runner.TestReport` object
+ for the given :py:class:`pytest.Item <_pytest.main.Item>` and
+ :py:class:`_pytest.runner.CallInfo`.
+
+ Stops at first non-None result, see :ref:`firstresult` """
+
+
+def pytest_runtest_logreport(report):
+ """ process a test setup/call/teardown report relating to
+ the respective phase of executing a test. """
+
+
@hookspec(firstresult=True)
def pytest_report_to_serializable(config, report):
"""
@@ -415,225 +415,225 @@ def pytest_report_from_serializable(config, data):
"""
-# -------------------------------------------------------------------------
-# Fixture related hooks
-# -------------------------------------------------------------------------
-
-
-@hookspec(firstresult=True)
-def pytest_fixture_setup(fixturedef, request):
- """ performs fixture setup execution.
-
- :return: The return value of the call to the fixture function
-
- Stops at first non-None result, see :ref:`firstresult`
-
- .. note::
- If the fixture function returns None, other implementations of
- this hook function will continue to be called, according to the
- behavior of the :ref:`firstresult` option.
- """
-
-
-def pytest_fixture_post_finalizer(fixturedef, request):
- """ called after fixture teardown, but before the cache is cleared so
- the fixture result cache ``fixturedef.cached_result`` can
- still be accessed."""
-
-
-# -------------------------------------------------------------------------
-# test session related hooks
-# -------------------------------------------------------------------------
-
-
-def pytest_sessionstart(session):
- """ called after the ``Session`` object has been created and before performing collection
- and entering the run test loop.
-
- :param _pytest.main.Session session: the pytest session object
- """
-
-
-def pytest_sessionfinish(session, exitstatus):
- """ called after whole test run finished, right before returning the exit status to the system.
-
- :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.
-
- :param _pytest.config.Config config: pytest config object
- """
-
-
-# -------------------------------------------------------------------------
-# hooks for customizing the assert methods
-# -------------------------------------------------------------------------
-
-
-def pytest_assertrepr_compare(config, op, left, right):
- """return explanation for comparisons in failing assert expressions.
-
- Return None for no custom explanation, otherwise return a list
- 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
- """
-
-
-# -------------------------------------------------------------------------
-# hooks for influencing reporting (invoked from _pytest_terminal)
-# -------------------------------------------------------------------------
-
-
-def pytest_report_header(config, startdir):
- """ return a string or list of strings to be displayed as header info for terminal reporting.
-
- :param _pytest.config.Config config: pytest config object
- :param startdir: py.path object with the starting dir
-
- .. note::
-
- This function should be implemented only in plugins or ``conftest.py``
- files situated at the tests root directory due to how pytest
- :ref:`discovers plugins during startup <pluginorder>`.
- """
-
-
-def pytest_report_collectionfinish(config, startdir, items):
- """
- .. versionadded:: 3.2
-
- return a string or list of strings to be displayed after collection has finished successfully.
-
- This strings will be displayed after the standard "collected X items" message.
-
- :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.
- """
-
-
-@hookspec(firstresult=True)
+# -------------------------------------------------------------------------
+# Fixture related hooks
+# -------------------------------------------------------------------------
+
+
+@hookspec(firstresult=True)
+def pytest_fixture_setup(fixturedef, request):
+ """ performs fixture setup execution.
+
+ :return: The return value of the call to the fixture function
+
+ Stops at first non-None result, see :ref:`firstresult`
+
+ .. note::
+ If the fixture function returns None, other implementations of
+ this hook function will continue to be called, according to the
+ behavior of the :ref:`firstresult` option.
+ """
+
+
+def pytest_fixture_post_finalizer(fixturedef, request):
+ """ called after fixture teardown, but before the cache is cleared so
+ the fixture result cache ``fixturedef.cached_result`` can
+ still be accessed."""
+
+
+# -------------------------------------------------------------------------
+# test session related hooks
+# -------------------------------------------------------------------------
+
+
+def pytest_sessionstart(session):
+ """ called after the ``Session`` object has been created and before performing collection
+ and entering the run test loop.
+
+ :param _pytest.main.Session session: the pytest session object
+ """
+
+
+def pytest_sessionfinish(session, exitstatus):
+ """ called after whole test run finished, right before returning the exit status to the system.
+
+ :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.
+
+ :param _pytest.config.Config config: pytest config object
+ """
+
+
+# -------------------------------------------------------------------------
+# hooks for customizing the assert methods
+# -------------------------------------------------------------------------
+
+
+def pytest_assertrepr_compare(config, op, left, right):
+ """return explanation for comparisons in failing assert expressions.
+
+ Return None for no custom explanation, otherwise return a list
+ 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
+ """
+
+
+# -------------------------------------------------------------------------
+# hooks for influencing reporting (invoked from _pytest_terminal)
+# -------------------------------------------------------------------------
+
+
+def pytest_report_header(config, startdir):
+ """ return a string or list of strings to be displayed as header info for terminal reporting.
+
+ :param _pytest.config.Config config: pytest config object
+ :param startdir: py.path object with the starting dir
+
+ .. note::
+
+ This function should be implemented only in plugins or ``conftest.py``
+ files situated at the tests root directory due to how pytest
+ :ref:`discovers plugins during startup <pluginorder>`.
+ """
+
+
+def pytest_report_collectionfinish(config, startdir, items):
+ """
+ .. versionadded:: 3.2
+
+ return a string or list of strings to be displayed after collection has finished successfully.
+
+ This strings will be displayed after the standard "collected X items" message.
+
+ :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.
+ """
+
+
+@hookspec(firstresult=True)
def pytest_report_teststatus(report, config):
- """ return result-category, shortletter and verbose word for reporting.
-
+ """ return result-category, shortletter and verbose word for reporting.
+
:param _pytest.config.Config config: pytest config object
- Stops at first non-None result, see :ref:`firstresult` """
-
-
+ Stops at first non-None result, see :ref:`firstresult` """
+
+
def pytest_terminal_summary(terminalreporter, exitstatus, config):
- """Add a section to terminal summary reporting.
-
- :param _pytest.terminal.TerminalReporter terminalreporter: the internal terminal reporter object
- :param int exitstatus: the exit status that will be reported back to the OS
+ """Add a section to terminal summary reporting.
+
+ :param _pytest.terminal.TerminalReporter terminalreporter: the internal terminal reporter object
+ :param int exitstatus: the exit status that will be reported back to the OS
:param _pytest.config.Config config: pytest config object
-
+
.. versionadded:: 4.2
- The ``config`` parameter.
- """
-
-
+ The ``config`` parameter.
+ """
+
+
@hookspec(historic=True, warn_on_impl=PYTEST_LOGWARNING)
-def pytest_logwarning(message, code, nodeid, fslocation):
- """
- .. deprecated:: 3.8
-
- This hook is will stop working in a future release.
-
- pytest no longer triggers this hook, but the
- terminal writer still implements it to display warnings issued by
- :meth:`_pytest.config.Config.warn` and :meth:`_pytest.nodes.Node.warn`. Calling those functions will be
- an error in future releases.
-
- process a warning specified by a message, a code string,
- a nodeid and fslocation (both of which may be None
- if the warning is not tied to a particular node/location).
-
- .. note::
- This hook is incompatible with ``hookwrapper=True``.
- """
-
-
-@hookspec(historic=True)
-def pytest_warning_captured(warning_message, when, item):
- """
- Process a warning captured by the internal pytest warnings plugin.
-
- :param warnings.WarningMessage warning_message:
- The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains
- the same attributes as the parameters of :py:func:`warnings.showwarning`.
-
- :param str when:
- Indicates when the warning was captured. Possible values:
-
- * ``"config"``: during pytest configuration/initialization stage.
- * ``"collect"``: during test collection.
- * ``"runtest"``: during test execution.
-
- :param pytest.Item|None item:
- **DEPRECATED**: This parameter is incompatible with ``pytest-xdist``, and will always receive ``None``
- in a future release.
-
- The item being executed if ``when`` is ``"runtest"``, otherwise ``None``.
- """
-
-
-# -------------------------------------------------------------------------
-# doctest hooks
-# -------------------------------------------------------------------------
-
-
-@hookspec(firstresult=True)
-def pytest_doctest_prepare_content(content):
- """ return processed content for a given doctest
-
- Stops at first non-None result, see :ref:`firstresult` """
-
-
-# -------------------------------------------------------------------------
-# error handling and internal debugging hooks
-# -------------------------------------------------------------------------
-
-
-def pytest_internalerror(excrepr, excinfo):
- """ called for internal errors. """
-
-
-def pytest_keyboard_interrupt(excinfo):
- """ called for keyboard interrupt. """
-
-
-def pytest_exception_interact(node, call, report):
- """called when an exception was raised which can potentially be
- interactively handled.
-
- This hook is only called if an exception was raised
- that is not an internal exception like ``skip.Exception``.
- """
-
-
-def pytest_enter_pdb(config, pdb):
- """ called upon pdb.set_trace(), can be used by plugins to take special
- action just before the python debugger enters in interactive mode.
-
- :param _pytest.config.Config config: pytest config object
- :param pdb.Pdb pdb: Pdb instance
- """
-
-
-def pytest_leave_pdb(config, pdb):
- """ called when leaving pdb (e.g. with continue after pdb.set_trace()).
-
- Can be used by plugins to take special action just after the python
- debugger leaves interactive mode.
-
- :param _pytest.config.Config config: pytest config object
- :param pdb.Pdb pdb: Pdb instance
- """
+def pytest_logwarning(message, code, nodeid, fslocation):
+ """
+ .. deprecated:: 3.8
+
+ This hook is will stop working in a future release.
+
+ pytest no longer triggers this hook, but the
+ terminal writer still implements it to display warnings issued by
+ :meth:`_pytest.config.Config.warn` and :meth:`_pytest.nodes.Node.warn`. Calling those functions will be
+ an error in future releases.
+
+ process a warning specified by a message, a code string,
+ a nodeid and fslocation (both of which may be None
+ if the warning is not tied to a particular node/location).
+
+ .. note::
+ This hook is incompatible with ``hookwrapper=True``.
+ """
+
+
+@hookspec(historic=True)
+def pytest_warning_captured(warning_message, when, item):
+ """
+ Process a warning captured by the internal pytest warnings plugin.
+
+ :param warnings.WarningMessage warning_message:
+ The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains
+ the same attributes as the parameters of :py:func:`warnings.showwarning`.
+
+ :param str when:
+ Indicates when the warning was captured. Possible values:
+
+ * ``"config"``: during pytest configuration/initialization stage.
+ * ``"collect"``: during test collection.
+ * ``"runtest"``: during test execution.
+
+ :param pytest.Item|None item:
+ **DEPRECATED**: This parameter is incompatible with ``pytest-xdist``, and will always receive ``None``
+ in a future release.
+
+ The item being executed if ``when`` is ``"runtest"``, otherwise ``None``.
+ """
+
+
+# -------------------------------------------------------------------------
+# doctest hooks
+# -------------------------------------------------------------------------
+
+
+@hookspec(firstresult=True)
+def pytest_doctest_prepare_content(content):
+ """ return processed content for a given doctest
+
+ Stops at first non-None result, see :ref:`firstresult` """
+
+
+# -------------------------------------------------------------------------
+# error handling and internal debugging hooks
+# -------------------------------------------------------------------------
+
+
+def pytest_internalerror(excrepr, excinfo):
+ """ called for internal errors. """
+
+
+def pytest_keyboard_interrupt(excinfo):
+ """ called for keyboard interrupt. """
+
+
+def pytest_exception_interact(node, call, report):
+ """called when an exception was raised which can potentially be
+ interactively handled.
+
+ This hook is only called if an exception was raised
+ that is not an internal exception like ``skip.Exception``.
+ """
+
+
+def pytest_enter_pdb(config, pdb):
+ """ called upon pdb.set_trace(), can be used by plugins to take special
+ action just before the python debugger enters in interactive mode.
+
+ :param _pytest.config.Config config: pytest config object
+ :param pdb.Pdb pdb: Pdb instance
+ """
+
+
+def pytest_leave_pdb(config, pdb):
+ """ called when leaving pdb (e.g. with continue after pdb.set_trace()).
+
+ Can be used by plugins to take special action just after the python
+ debugger leaves interactive mode.
+
+ :param _pytest.config.Config config: pytest config object
+ :param pdb.Pdb pdb: Pdb instance
+ """
diff --git a/contrib/python/pytest/py2/_pytest/junitxml.py b/contrib/python/pytest/py2/_pytest/junitxml.py
index 8ae968f07b..853dcb7744 100644
--- a/contrib/python/pytest/py2/_pytest/junitxml.py
+++ b/contrib/python/pytest/py2/_pytest/junitxml.py
@@ -1,74 +1,74 @@
# -*- coding: utf-8 -*-
-"""
- report test results in JUnit-XML format,
- for use with Jenkins and build integration servers.
-
-
-Based on initial code from Ross Lawley.
-
-Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/
-src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
-"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import functools
-import os
+"""
+ report test results in JUnit-XML format,
+ for use with Jenkins and build integration servers.
+
+
+Based on initial code from Ross Lawley.
+
+Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/
+src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import functools
+import os
import platform
-import re
-import sys
-import time
+import re
+import sys
+import time
from datetime import datetime
-
-import py
+
+import py
import six
-
-import pytest
-from _pytest import nodes
-from _pytest.config import filename_arg
-
-# Python 2.X and 3.X compatibility
-if sys.version_info[0] < 3:
- from codecs import open
-
-
-class Junit(py.xml.Namespace):
- pass
-
-
-# We need to get the subset of the invalid unicode ranges according to
-# XML 1.0 which are valid in this python build. Hence we calculate
-# this dynamically instead of hardcoding it. The spec range of valid
-# chars is: Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
-# | [#x10000-#x10FFFF]
-_legal_chars = (0x09, 0x0A, 0x0D)
-_legal_ranges = ((0x20, 0x7E), (0x80, 0xD7FF), (0xE000, 0xFFFD), (0x10000, 0x10FFFF))
-_legal_xml_re = [
+
+import pytest
+from _pytest import nodes
+from _pytest.config import filename_arg
+
+# Python 2.X and 3.X compatibility
+if sys.version_info[0] < 3:
+ from codecs import open
+
+
+class Junit(py.xml.Namespace):
+ pass
+
+
+# We need to get the subset of the invalid unicode ranges according to
+# XML 1.0 which are valid in this python build. Hence we calculate
+# this dynamically instead of hardcoding it. The spec range of valid
+# chars is: Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
+# | [#x10000-#x10FFFF]
+_legal_chars = (0x09, 0x0A, 0x0D)
+_legal_ranges = ((0x20, 0x7E), (0x80, 0xD7FF), (0xE000, 0xFFFD), (0x10000, 0x10FFFF))
+_legal_xml_re = [
u"%s-%s" % (six.unichr(low), six.unichr(high))
- for (low, high) in _legal_ranges
- if low < sys.maxunicode
-]
+ for (low, high) in _legal_ranges
+ if low < sys.maxunicode
+]
_legal_xml_re = [six.unichr(x) for x in _legal_chars] + _legal_xml_re
illegal_xml_re = re.compile(u"[^%s]" % u"".join(_legal_xml_re))
-del _legal_chars
-del _legal_ranges
-del _legal_xml_re
-
-_py_ext_re = re.compile(r"\.py$")
-
-
-def bin_xml_escape(arg):
- def repl(matchobj):
- i = ord(matchobj.group())
- if i <= 0xFF:
+del _legal_chars
+del _legal_ranges
+del _legal_xml_re
+
+_py_ext_re = re.compile(r"\.py$")
+
+
+def bin_xml_escape(arg):
+ def repl(matchobj):
+ i = ord(matchobj.group())
+ if i <= 0xFF:
return u"#x%02X" % i
- else:
+ else:
return u"#x%04X" % i
-
- return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg)))
-
-
+
+ return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg)))
+
+
def merge_family(left, right):
result = {}
for kl, vl in left.items():
@@ -91,59 +91,59 @@ merge_family(families["xunit1"], families["_base_legacy"])
families["xunit2"] = families["_base"]
-class _NodeReporter(object):
- def __init__(self, nodeid, xml):
- self.id = nodeid
- self.xml = xml
- self.add_stats = self.xml.add_stats
+class _NodeReporter(object):
+ def __init__(self, nodeid, xml):
+ self.id = nodeid
+ self.xml = xml
+ self.add_stats = self.xml.add_stats
self.family = self.xml.family
- self.duration = 0
- self.properties = []
- self.nodes = []
- self.testcase = None
- self.attrs = {}
-
- def append(self, node):
- self.xml.add_stats(type(node).__name__)
- self.nodes.append(node)
-
- def add_property(self, name, value):
- self.properties.append((str(name), bin_xml_escape(value)))
-
- def add_attribute(self, name, value):
- self.attrs[str(name)] = bin_xml_escape(value)
-
- def make_properties_node(self):
- """Return a Junit node containing custom properties, if any.
- """
- if self.properties:
- return Junit.properties(
- [
- Junit.property(name=name, value=value)
- for name, value in self.properties
- ]
- )
- return ""
-
- def record_testreport(self, testreport):
- assert not self.testcase
- names = mangle_test_address(testreport.nodeid)
- existing_attrs = self.attrs
- classnames = names[:-1]
- if self.xml.prefix:
- classnames.insert(0, self.xml.prefix)
- attrs = {
- "classname": ".".join(classnames),
- "name": bin_xml_escape(names[-1]),
- "file": testreport.location[0],
- }
- if testreport.location[1] is not None:
- attrs["line"] = testreport.location[1]
- if hasattr(testreport, "url"):
- attrs["url"] = testreport.url
- self.attrs = attrs
- self.attrs.update(existing_attrs) # restore any user-defined attributes
-
+ self.duration = 0
+ self.properties = []
+ self.nodes = []
+ self.testcase = None
+ self.attrs = {}
+
+ def append(self, node):
+ self.xml.add_stats(type(node).__name__)
+ self.nodes.append(node)
+
+ def add_property(self, name, value):
+ self.properties.append((str(name), bin_xml_escape(value)))
+
+ def add_attribute(self, name, value):
+ self.attrs[str(name)] = bin_xml_escape(value)
+
+ def make_properties_node(self):
+ """Return a Junit node containing custom properties, if any.
+ """
+ if self.properties:
+ return Junit.properties(
+ [
+ Junit.property(name=name, value=value)
+ for name, value in self.properties
+ ]
+ )
+ return ""
+
+ def record_testreport(self, testreport):
+ assert not self.testcase
+ names = mangle_test_address(testreport.nodeid)
+ existing_attrs = self.attrs
+ classnames = names[:-1]
+ if self.xml.prefix:
+ classnames.insert(0, self.xml.prefix)
+ attrs = {
+ "classname": ".".join(classnames),
+ "name": bin_xml_escape(names[-1]),
+ "file": testreport.location[0],
+ }
+ if testreport.location[1] is not None:
+ attrs["line"] = testreport.location[1]
+ if hasattr(testreport, "url"):
+ attrs["url"] = testreport.url
+ self.attrs = attrs
+ self.attrs.update(existing_attrs) # restore any user-defined attributes
+
# Preserve legacy testcase behavior
if self.family == "xunit1":
return
@@ -156,108 +156,108 @@ class _NodeReporter(object):
temp_attrs[key] = self.attrs[key]
self.attrs = temp_attrs
- def to_xml(self):
+ def to_xml(self):
testcase = Junit.testcase(time="%.3f" % self.duration, **self.attrs)
- testcase.append(self.make_properties_node())
- for node in self.nodes:
- testcase.append(node)
- return testcase
-
- def _add_simple(self, kind, message, data=None):
- data = bin_xml_escape(data)
- node = kind(data, message=message)
- self.append(node)
-
- def write_captured_output(self, report):
+ testcase.append(self.make_properties_node())
+ for node in self.nodes:
+ testcase.append(node)
+ return testcase
+
+ def _add_simple(self, kind, message, data=None):
+ data = bin_xml_escape(data)
+ node = kind(data, message=message)
+ self.append(node)
+
+ def write_captured_output(self, report):
if not self.xml.log_passing_tests and report.passed:
return
- content_out = report.capstdout
- content_log = report.caplog
- content_err = report.capstderr
-
- if content_log or content_out:
- if content_log and self.xml.logging == "system-out":
- if content_out:
- # syncing stdout and the log-output is not done yet. It's
- # probably not worth the effort. Therefore, first the captured
- # stdout is shown and then the captured logs.
- content = "\n".join(
- [
- " Captured Stdout ".center(80, "-"),
- content_out,
- "",
- " Captured Log ".center(80, "-"),
- content_log,
- ]
- )
- else:
- content = content_log
- else:
- content = content_out
-
- if content:
- tag = getattr(Junit, "system-out")
- self.append(tag(bin_xml_escape(content)))
-
- if content_log or content_err:
- if content_log and self.xml.logging == "system-err":
- if content_err:
- content = "\n".join(
- [
- " Captured Stderr ".center(80, "-"),
- content_err,
- "",
- " Captured Log ".center(80, "-"),
- content_log,
- ]
- )
- else:
- content = content_log
- else:
- content = content_err
-
- if content:
- tag = getattr(Junit, "system-err")
- self.append(tag(bin_xml_escape(content)))
-
- def append_pass(self, report):
- self.add_stats("passed")
-
- def append_failure(self, report):
- # msg = str(report.longrepr.reprtraceback.extraline)
- if hasattr(report, "wasxfail"):
- self._add_simple(Junit.skipped, "xfail-marked test passes unexpectedly")
- else:
- if hasattr(report.longrepr, "reprcrash"):
- message = report.longrepr.reprcrash.message
+ content_out = report.capstdout
+ content_log = report.caplog
+ content_err = report.capstderr
+
+ if content_log or content_out:
+ if content_log and self.xml.logging == "system-out":
+ if content_out:
+ # syncing stdout and the log-output is not done yet. It's
+ # probably not worth the effort. Therefore, first the captured
+ # stdout is shown and then the captured logs.
+ content = "\n".join(
+ [
+ " Captured Stdout ".center(80, "-"),
+ content_out,
+ "",
+ " Captured Log ".center(80, "-"),
+ content_log,
+ ]
+ )
+ else:
+ content = content_log
+ else:
+ content = content_out
+
+ if content:
+ tag = getattr(Junit, "system-out")
+ self.append(tag(bin_xml_escape(content)))
+
+ if content_log or content_err:
+ if content_log and self.xml.logging == "system-err":
+ if content_err:
+ content = "\n".join(
+ [
+ " Captured Stderr ".center(80, "-"),
+ content_err,
+ "",
+ " Captured Log ".center(80, "-"),
+ content_log,
+ ]
+ )
+ else:
+ content = content_log
+ else:
+ content = content_err
+
+ if content:
+ tag = getattr(Junit, "system-err")
+ self.append(tag(bin_xml_escape(content)))
+
+ def append_pass(self, report):
+ self.add_stats("passed")
+
+ def append_failure(self, report):
+ # msg = str(report.longrepr.reprtraceback.extraline)
+ if hasattr(report, "wasxfail"):
+ self._add_simple(Junit.skipped, "xfail-marked test passes unexpectedly")
+ else:
+ if hasattr(report.longrepr, "reprcrash"):
+ message = report.longrepr.reprcrash.message
elif isinstance(report.longrepr, six.string_types):
- message = report.longrepr
- else:
- message = str(report.longrepr)
- message = bin_xml_escape(message)
- fail = Junit.failure(message=message)
- fail.append(bin_xml_escape(report.longrepr))
- self.append(fail)
-
- def append_collect_error(self, report):
- # msg = str(report.longrepr.reprtraceback.extraline)
- self.append(
- Junit.error(bin_xml_escape(report.longrepr), message="collection failure")
- )
-
- def append_collect_skipped(self, report):
- self._add_simple(Junit.skipped, "collection skipped", report.longrepr)
-
- def append_error(self, report):
+ message = report.longrepr
+ else:
+ message = str(report.longrepr)
+ message = bin_xml_escape(message)
+ fail = Junit.failure(message=message)
+ fail.append(bin_xml_escape(report.longrepr))
+ self.append(fail)
+
+ def append_collect_error(self, report):
+ # msg = str(report.longrepr.reprtraceback.extraline)
+ self.append(
+ Junit.error(bin_xml_escape(report.longrepr), message="collection failure")
+ )
+
+ def append_collect_skipped(self, report):
+ self._add_simple(Junit.skipped, "collection skipped", report.longrepr)
+
+ def append_error(self, report):
if report.when == "teardown":
- msg = "test teardown failure"
- else:
- msg = "test setup failure"
- self._add_simple(Junit.error, msg, report.longrepr)
-
- def append_skipped(self, report):
- if hasattr(report, "wasxfail"):
+ msg = "test teardown failure"
+ else:
+ msg = "test setup failure"
+ self._add_simple(Junit.error, msg, report.longrepr)
+
+ def append_skipped(self, report):
+ if hasattr(report, "wasxfail"):
xfailreason = report.wasxfail
if xfailreason.startswith("reason: "):
xfailreason = xfailreason[8:]
@@ -266,27 +266,27 @@ class _NodeReporter(object):
"", type="pytest.xfail", message=bin_xml_escape(xfailreason)
)
)
- else:
- filename, lineno, skipreason = report.longrepr
- if skipreason.startswith("Skipped: "):
- skipreason = skipreason[9:]
- details = "%s:%s: %s" % (filename, lineno, skipreason)
-
- self.append(
- Junit.skipped(
- bin_xml_escape(details),
- type="pytest.skip",
- message=bin_xml_escape(skipreason),
- )
- )
- self.write_captured_output(report)
-
- def finalize(self):
- data = self.to_xml().unicode(indent=0)
- self.__dict__.clear()
- self.to_xml = lambda: py.xml.raw(data)
-
-
+ else:
+ filename, lineno, skipreason = report.longrepr
+ if skipreason.startswith("Skipped: "):
+ skipreason = skipreason[9:]
+ details = "%s:%s: %s" % (filename, lineno, skipreason)
+
+ self.append(
+ Junit.skipped(
+ bin_xml_escape(details),
+ type="pytest.skip",
+ message=bin_xml_escape(skipreason),
+ )
+ )
+ self.write_captured_output(report)
+
+ def finalize(self):
+ data = self.to_xml().unicode(indent=0)
+ self.__dict__.clear()
+ self.to_xml = lambda: py.xml.raw(data)
+
+
def _warn_incompatibility_with_xunit2(request, fixture_name):
"""Emits a PytestWarning about the given fixture being incompatible with newer xunit revisions"""
from _pytest.warning_types import PytestWarning
@@ -302,35 +302,35 @@ def _warn_incompatibility_with_xunit2(request, fixture_name):
)
-@pytest.fixture
-def record_property(request):
- """Add an extra properties the calling test.
- User properties become part of the test report and are available to the
- configured reporters, like JUnit XML.
- The fixture is callable with ``(name, value)``, with value being automatically
- xml-encoded.
-
- Example::
-
- def test_function(record_property):
- record_property("example_key", 1)
- """
+@pytest.fixture
+def record_property(request):
+ """Add an extra properties the calling test.
+ User properties become part of the test report and are available to the
+ configured reporters, like JUnit XML.
+ The fixture is callable with ``(name, value)``, with value being automatically
+ xml-encoded.
+
+ Example::
+
+ def test_function(record_property):
+ record_property("example_key", 1)
+ """
_warn_incompatibility_with_xunit2(request, "record_property")
-
- def append_property(name, value):
- request.node.user_properties.append((name, value))
-
- return append_property
-
-
-@pytest.fixture
-def record_xml_attribute(request):
- """Add extra xml attributes to the tag for the calling test.
- The fixture is callable with ``(name, value)``, with value being
- automatically xml-encoded
- """
+
+ def append_property(name, value):
+ request.node.user_properties.append((name, value))
+
+ return append_property
+
+
+@pytest.fixture
+def record_xml_attribute(request):
+ """Add extra xml attributes to the tag for the calling test.
+ The fixture is callable with ``(name, value)``, with value being
+ automatically xml-encoded
+ """
from _pytest.warning_types import PytestExperimentalApiWarning
-
+
request.node.warn(
PytestExperimentalApiWarning("record_xml_attribute is an experimental feature")
)
@@ -343,14 +343,14 @@ def record_xml_attribute(request):
attr_func = add_attr_noop
- xml = getattr(request.config, "_xml", None)
- if xml is not None:
- node_reporter = xml.node_reporter(request.node.nodeid)
+ xml = getattr(request.config, "_xml", None)
+ if xml is not None:
+ node_reporter = xml.node_reporter(request.node.nodeid)
attr_func = node_reporter.add_attribute
-
+
return attr_func
-
-
+
+
def _check_record_param_type(param, v):
"""Used by record_testsuite_property to check that the given parameter name is of the proper
type"""
@@ -358,7 +358,7 @@ def _check_record_param_type(param, v):
if not isinstance(v, six.string_types):
msg = "{param} parameter needs to be a string, but {g} given"
raise TypeError(msg.format(param=param, g=type(v).__name__))
-
+
@pytest.fixture(scope="session")
def record_testsuite_property(request):
@@ -390,35 +390,35 @@ def record_testsuite_property(request):
return record_func
-def pytest_addoption(parser):
- group = parser.getgroup("terminal reporting")
- group.addoption(
- "--junitxml",
- "--junit-xml",
- action="store",
- dest="xmlpath",
- metavar="path",
- type=functools.partial(filename_arg, optname="--junitxml"),
- default=None,
- help="create junit-xml style report file at given path.",
- )
- group.addoption(
- "--junitprefix",
- "--junit-prefix",
- action="store",
- metavar="str",
- default=None,
- help="prepend prefix to classnames in junit-xml output",
- )
- parser.addini(
- "junit_suite_name", "Test suite name for JUnit report", default="pytest"
- )
- parser.addini(
- "junit_logging",
- "Write captured log messages to JUnit report: "
- "one of no|system-out|system-err",
- default="no",
- ) # choices=['no', 'stdout', 'stderr'])
+def pytest_addoption(parser):
+ group = parser.getgroup("terminal reporting")
+ group.addoption(
+ "--junitxml",
+ "--junit-xml",
+ action="store",
+ dest="xmlpath",
+ metavar="path",
+ type=functools.partial(filename_arg, optname="--junitxml"),
+ default=None,
+ help="create junit-xml style report file at given path.",
+ )
+ group.addoption(
+ "--junitprefix",
+ "--junit-prefix",
+ action="store",
+ metavar="str",
+ default=None,
+ help="prepend prefix to classnames in junit-xml output",
+ )
+ parser.addini(
+ "junit_suite_name", "Test suite name for JUnit report", default="pytest"
+ )
+ parser.addini(
+ "junit_logging",
+ "Write captured log messages to JUnit report: "
+ "one of no|system-out|system-err",
+ default="no",
+ ) # choices=['no', 'stdout', 'stderr'])
parser.addini(
"junit_log_passing_tests",
"Capture log information for passing tests to JUnit report: ",
@@ -435,47 +435,47 @@ def pytest_addoption(parser):
"Emit XML for schema: one of legacy|xunit1|xunit2",
default="xunit1",
)
-
-
-def pytest_configure(config):
- xmlpath = config.option.xmlpath
- # prevent opening xmllog on slave nodes (xdist)
- if xmlpath and not hasattr(config, "slaveinput"):
- config._xml = LogXML(
- xmlpath,
- config.option.junitprefix,
- config.getini("junit_suite_name"),
- config.getini("junit_logging"),
+
+
+def pytest_configure(config):
+ xmlpath = config.option.xmlpath
+ # prevent opening xmllog on slave nodes (xdist)
+ if xmlpath and not hasattr(config, "slaveinput"):
+ config._xml = LogXML(
+ xmlpath,
+ config.option.junitprefix,
+ config.getini("junit_suite_name"),
+ config.getini("junit_logging"),
config.getini("junit_duration_report"),
config.getini("junit_family"),
config.getini("junit_log_passing_tests"),
- )
- config.pluginmanager.register(config._xml)
-
-
-def pytest_unconfigure(config):
- xml = getattr(config, "_xml", None)
- if xml:
- del config._xml
- config.pluginmanager.unregister(xml)
-
-
-def mangle_test_address(address):
- path, possible_open_bracket, params = address.partition("[")
- names = path.split("::")
- try:
- names.remove("()")
- except ValueError:
- pass
- # convert file path to dotted path
- names[0] = names[0].replace(nodes.SEP, ".")
- names[0] = _py_ext_re.sub("", names[0])
- # put any params back
- names[-1] += possible_open_bracket + params
- return names
-
-
-class LogXML(object):
+ )
+ config.pluginmanager.register(config._xml)
+
+
+def pytest_unconfigure(config):
+ xml = getattr(config, "_xml", None)
+ if xml:
+ del config._xml
+ config.pluginmanager.unregister(xml)
+
+
+def mangle_test_address(address):
+ path, possible_open_bracket, params = address.partition("[")
+ names = path.split("::")
+ try:
+ names.remove("()")
+ except ValueError:
+ pass
+ # convert file path to dotted path
+ names[0] = names[0].replace(nodes.SEP, ".")
+ names[0] = _py_ext_re.sub("", names[0])
+ # put any params back
+ names[-1] += possible_open_bracket + params
+ return names
+
+
+class LogXML(object):
def __init__(
self,
logfile,
@@ -486,191 +486,191 @@ class LogXML(object):
family="xunit1",
log_passing_tests=True,
):
- logfile = os.path.expanduser(os.path.expandvars(logfile))
- self.logfile = os.path.normpath(os.path.abspath(logfile))
- self.prefix = prefix
- self.suite_name = suite_name
- self.logging = logging
+ logfile = os.path.expanduser(os.path.expandvars(logfile))
+ self.logfile = os.path.normpath(os.path.abspath(logfile))
+ self.prefix = prefix
+ self.suite_name = suite_name
+ self.logging = logging
self.log_passing_tests = log_passing_tests
self.report_duration = report_duration
self.family = family
- self.stats = dict.fromkeys(["error", "passed", "failure", "skipped"], 0)
- self.node_reporters = {} # nodeid -> _NodeReporter
- self.node_reporters_ordered = []
- self.global_properties = []
-
- # List of reports that failed on call but teardown is pending.
- self.open_reports = []
- self.cnt_double_fail_tests = 0
-
+ self.stats = dict.fromkeys(["error", "passed", "failure", "skipped"], 0)
+ self.node_reporters = {} # nodeid -> _NodeReporter
+ self.node_reporters_ordered = []
+ self.global_properties = []
+
+ # List of reports that failed on call but teardown is pending.
+ self.open_reports = []
+ self.cnt_double_fail_tests = 0
+
# Replaces convenience family with real family
if self.family == "legacy":
self.family = "xunit1"
- def finalize(self, report):
- nodeid = getattr(report, "nodeid", report)
- # local hack to handle xdist report order
- slavenode = getattr(report, "node", None)
- reporter = self.node_reporters.pop((nodeid, slavenode))
- if reporter is not None:
- reporter.finalize()
-
- def node_reporter(self, report):
- nodeid = getattr(report, "nodeid", report)
- # local hack to handle xdist report order
- slavenode = getattr(report, "node", None)
-
- key = nodeid, slavenode
-
- if key in self.node_reporters:
- # TODO: breasks for --dist=each
- return self.node_reporters[key]
-
- reporter = _NodeReporter(nodeid, self)
-
- self.node_reporters[key] = reporter
- self.node_reporters_ordered.append(reporter)
-
- return reporter
-
- def add_stats(self, key):
- if key in self.stats:
- self.stats[key] += 1
-
- def _opentestcase(self, report):
- reporter = self.node_reporter(report)
- reporter.record_testreport(report)
- return reporter
-
- def pytest_runtest_logreport(self, report):
- """handle a setup/call/teardown report, generating the appropriate
- xml tags as necessary.
-
- note: due to plugins like xdist, this hook may be called in interlaced
- order with reports from other nodes. for example:
-
- usual call order:
- -> setup node1
- -> call node1
- -> teardown node1
- -> setup node2
- -> call node2
- -> teardown node2
-
- possible call order in xdist:
- -> setup node1
- -> call node1
- -> setup node2
- -> call node2
- -> teardown node2
- -> teardown node1
- """
- close_report = None
- if report.passed:
- if report.when == "call": # ignore setup/teardown
- reporter = self._opentestcase(report)
- reporter.append_pass(report)
- elif report.failed:
- if report.when == "teardown":
- # The following vars are needed when xdist plugin is used
- report_wid = getattr(report, "worker_id", None)
- report_ii = getattr(report, "item_index", None)
- close_report = next(
- (
- rep
- for rep in self.open_reports
- if (
- rep.nodeid == report.nodeid
- and getattr(rep, "item_index", None) == report_ii
- and getattr(rep, "worker_id", None) == report_wid
- )
- ),
- None,
- )
- if close_report:
- # We need to open new testcase in case we have failure in
- # call and error in teardown in order to follow junit
- # schema
- self.finalize(close_report)
- self.cnt_double_fail_tests += 1
- reporter = self._opentestcase(report)
- if report.when == "call":
- reporter.append_failure(report)
- self.open_reports.append(report)
+ def finalize(self, report):
+ nodeid = getattr(report, "nodeid", report)
+ # local hack to handle xdist report order
+ slavenode = getattr(report, "node", None)
+ reporter = self.node_reporters.pop((nodeid, slavenode))
+ if reporter is not None:
+ reporter.finalize()
+
+ def node_reporter(self, report):
+ nodeid = getattr(report, "nodeid", report)
+ # local hack to handle xdist report order
+ slavenode = getattr(report, "node", None)
+
+ key = nodeid, slavenode
+
+ if key in self.node_reporters:
+ # TODO: breasks for --dist=each
+ return self.node_reporters[key]
+
+ reporter = _NodeReporter(nodeid, self)
+
+ self.node_reporters[key] = reporter
+ self.node_reporters_ordered.append(reporter)
+
+ return reporter
+
+ def add_stats(self, key):
+ if key in self.stats:
+ self.stats[key] += 1
+
+ def _opentestcase(self, report):
+ reporter = self.node_reporter(report)
+ reporter.record_testreport(report)
+ return reporter
+
+ def pytest_runtest_logreport(self, report):
+ """handle a setup/call/teardown report, generating the appropriate
+ xml tags as necessary.
+
+ note: due to plugins like xdist, this hook may be called in interlaced
+ order with reports from other nodes. for example:
+
+ usual call order:
+ -> setup node1
+ -> call node1
+ -> teardown node1
+ -> setup node2
+ -> call node2
+ -> teardown node2
+
+ possible call order in xdist:
+ -> setup node1
+ -> call node1
+ -> setup node2
+ -> call node2
+ -> teardown node2
+ -> teardown node1
+ """
+ close_report = None
+ if report.passed:
+ if report.when == "call": # ignore setup/teardown
+ reporter = self._opentestcase(report)
+ reporter.append_pass(report)
+ elif report.failed:
+ if report.when == "teardown":
+ # The following vars are needed when xdist plugin is used
+ report_wid = getattr(report, "worker_id", None)
+ report_ii = getattr(report, "item_index", None)
+ close_report = next(
+ (
+ rep
+ for rep in self.open_reports
+ if (
+ rep.nodeid == report.nodeid
+ and getattr(rep, "item_index", None) == report_ii
+ and getattr(rep, "worker_id", None) == report_wid
+ )
+ ),
+ None,
+ )
+ if close_report:
+ # We need to open new testcase in case we have failure in
+ # call and error in teardown in order to follow junit
+ # schema
+ self.finalize(close_report)
+ self.cnt_double_fail_tests += 1
+ reporter = self._opentestcase(report)
+ if report.when == "call":
+ reporter.append_failure(report)
+ self.open_reports.append(report)
if not self.log_passing_tests:
reporter.write_captured_output(report)
- else:
- reporter.append_error(report)
- elif report.skipped:
- reporter = self._opentestcase(report)
- reporter.append_skipped(report)
- self.update_testcase_duration(report)
- if report.when == "teardown":
- reporter = self._opentestcase(report)
- reporter.write_captured_output(report)
-
- for propname, propvalue in report.user_properties:
- reporter.add_property(propname, propvalue)
-
- self.finalize(report)
- report_wid = getattr(report, "worker_id", None)
- report_ii = getattr(report, "item_index", None)
- close_report = next(
- (
- rep
- for rep in self.open_reports
- if (
- rep.nodeid == report.nodeid
- and getattr(rep, "item_index", None) == report_ii
- and getattr(rep, "worker_id", None) == report_wid
- )
- ),
- None,
- )
- if close_report:
- self.open_reports.remove(close_report)
-
- def update_testcase_duration(self, report):
- """accumulates total duration for nodeid from given report and updates
- the Junit.testcase with the new total if already created.
- """
+ else:
+ reporter.append_error(report)
+ elif report.skipped:
+ reporter = self._opentestcase(report)
+ reporter.append_skipped(report)
+ self.update_testcase_duration(report)
+ if report.when == "teardown":
+ reporter = self._opentestcase(report)
+ reporter.write_captured_output(report)
+
+ for propname, propvalue in report.user_properties:
+ reporter.add_property(propname, propvalue)
+
+ self.finalize(report)
+ report_wid = getattr(report, "worker_id", None)
+ report_ii = getattr(report, "item_index", None)
+ close_report = next(
+ (
+ rep
+ for rep in self.open_reports
+ if (
+ rep.nodeid == report.nodeid
+ and getattr(rep, "item_index", None) == report_ii
+ and getattr(rep, "worker_id", None) == report_wid
+ )
+ ),
+ None,
+ )
+ if close_report:
+ self.open_reports.remove(close_report)
+
+ def update_testcase_duration(self, report):
+ """accumulates total duration for nodeid from given report and updates
+ the Junit.testcase with the new total if already created.
+ """
if self.report_duration == "total" or report.when == self.report_duration:
reporter = self.node_reporter(report)
reporter.duration += getattr(report, "duration", 0.0)
-
- def pytest_collectreport(self, report):
- if not report.passed:
- reporter = self._opentestcase(report)
- if report.failed:
- reporter.append_collect_error(report)
- else:
- reporter.append_collect_skipped(report)
-
- def pytest_internalerror(self, excrepr):
- reporter = self.node_reporter("internal")
- reporter.attrs.update(classname="pytest", name="internal")
- reporter._add_simple(Junit.error, "internal error", excrepr)
-
- def pytest_sessionstart(self):
- self.suite_start_time = time.time()
-
- def pytest_sessionfinish(self):
- dirname = os.path.dirname(os.path.abspath(self.logfile))
- if not os.path.isdir(dirname):
- os.makedirs(dirname)
- logfile = open(self.logfile, "w", encoding="utf-8")
- suite_stop_time = time.time()
- suite_time_delta = suite_stop_time - self.suite_start_time
-
- numtests = (
- self.stats["passed"]
- + self.stats["failure"]
- + self.stats["skipped"]
- + self.stats["error"]
- - self.cnt_double_fail_tests
- )
- logfile.write('<?xml version="1.0" encoding="utf-8"?>')
-
+
+ def pytest_collectreport(self, report):
+ if not report.passed:
+ reporter = self._opentestcase(report)
+ if report.failed:
+ reporter.append_collect_error(report)
+ else:
+ reporter.append_collect_skipped(report)
+
+ def pytest_internalerror(self, excrepr):
+ reporter = self.node_reporter("internal")
+ reporter.attrs.update(classname="pytest", name="internal")
+ reporter._add_simple(Junit.error, "internal error", excrepr)
+
+ def pytest_sessionstart(self):
+ self.suite_start_time = time.time()
+
+ def pytest_sessionfinish(self):
+ dirname = os.path.dirname(os.path.abspath(self.logfile))
+ if not os.path.isdir(dirname):
+ os.makedirs(dirname)
+ logfile = open(self.logfile, "w", encoding="utf-8")
+ suite_stop_time = time.time()
+ suite_time_delta = suite_stop_time - self.suite_start_time
+
+ numtests = (
+ self.stats["passed"]
+ + self.stats["failure"]
+ + self.stats["skipped"]
+ + self.stats["error"]
+ - self.cnt_double_fail_tests
+ )
+ logfile.write('<?xml version="1.0" encoding="utf-8"?>')
+
suite_node = Junit.testsuite(
self._get_global_properties_node(),
[x.to_xml() for x in self.node_reporters_ordered],
@@ -682,26 +682,26 @@ class LogXML(object):
time="%.3f" % suite_time_delta,
timestamp=datetime.fromtimestamp(self.suite_start_time).isoformat(),
hostname=platform.node(),
- )
+ )
logfile.write(Junit.testsuites([suite_node]).unicode(indent=0))
- logfile.close()
-
- def pytest_terminal_summary(self, terminalreporter):
- terminalreporter.write_sep("-", "generated xml file: %s" % (self.logfile))
-
- def add_global_property(self, name, value):
+ logfile.close()
+
+ def pytest_terminal_summary(self, terminalreporter):
+ terminalreporter.write_sep("-", "generated xml file: %s" % (self.logfile))
+
+ def add_global_property(self, name, value):
__tracebackhide__ = True
_check_record_param_type("name", name)
self.global_properties.append((name, bin_xml_escape(value)))
-
- def _get_global_properties_node(self):
- """Return a Junit node containing custom properties, if any.
- """
- if self.global_properties:
- return Junit.properties(
- [
- Junit.property(name=name, value=value)
- for name, value in self.global_properties
- ]
- )
- return ""
+
+ def _get_global_properties_node(self):
+ """Return a Junit node containing custom properties, if any.
+ """
+ if self.global_properties:
+ return Junit.properties(
+ [
+ Junit.property(name=name, value=value)
+ for name, value in self.global_properties
+ ]
+ )
+ return ""
diff --git a/contrib/python/pytest/py2/_pytest/logging.py b/contrib/python/pytest/py2/_pytest/logging.py
index 3e57bc42eb..2400737ee4 100644
--- a/contrib/python/pytest/py2/_pytest/logging.py
+++ b/contrib/python/pytest/py2/_pytest/logging.py
@@ -1,82 +1,82 @@
# -*- coding: utf-8 -*-
-""" Access and control log capturing. """
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import logging
-import re
-from contextlib import contextmanager
-
-import py
-import six
-
-import pytest
-from _pytest.compat import dummy_context_manager
-from _pytest.config import create_terminal_writer
+""" Access and control log capturing. """
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import logging
+import re
+from contextlib import contextmanager
+
+import py
+import six
+
+import pytest
+from _pytest.compat import dummy_context_manager
+from _pytest.config import create_terminal_writer
from _pytest.pathlib import Path
-
+
DEFAULT_LOG_FORMAT = "%(levelname)-8s %(name)s:%(filename)s:%(lineno)d %(message)s"
-DEFAULT_LOG_DATE_FORMAT = "%H:%M:%S"
+DEFAULT_LOG_DATE_FORMAT = "%H:%M:%S"
_ANSI_ESCAPE_SEQ = re.compile(r"\x1b\[[\d;]+m")
-
-
+
+
def _remove_ansi_escape_sequences(text):
return _ANSI_ESCAPE_SEQ.sub("", text)
-class ColoredLevelFormatter(logging.Formatter):
- """
- Colorize the %(levelname)..s part of the log format passed to __init__.
- """
-
- LOGLEVEL_COLOROPTS = {
- logging.CRITICAL: {"red"},
- logging.ERROR: {"red", "bold"},
- logging.WARNING: {"yellow"},
- logging.WARN: {"yellow"},
- logging.INFO: {"green"},
- logging.DEBUG: {"purple"},
- logging.NOTSET: set(),
- }
- LEVELNAME_FMT_REGEX = re.compile(r"%\(levelname\)([+-]?\d*s)")
-
- def __init__(self, terminalwriter, *args, **kwargs):
- super(ColoredLevelFormatter, self).__init__(*args, **kwargs)
- if six.PY2:
- self._original_fmt = self._fmt
- else:
- self._original_fmt = self._style._fmt
- self._level_to_fmt_mapping = {}
-
- levelname_fmt_match = self.LEVELNAME_FMT_REGEX.search(self._fmt)
- if not levelname_fmt_match:
- return
- levelname_fmt = levelname_fmt_match.group()
-
- for level, color_opts in self.LOGLEVEL_COLOROPTS.items():
- formatted_levelname = levelname_fmt % {
- "levelname": logging.getLevelName(level)
- }
-
- # add ANSI escape sequences around the formatted levelname
- color_kwargs = {name: True for name in color_opts}
- colorized_formatted_levelname = terminalwriter.markup(
- formatted_levelname, **color_kwargs
- )
- self._level_to_fmt_mapping[level] = self.LEVELNAME_FMT_REGEX.sub(
- colorized_formatted_levelname, self._fmt
- )
-
- def format(self, record):
- fmt = self._level_to_fmt_mapping.get(record.levelno, self._original_fmt)
- if six.PY2:
- self._fmt = fmt
- else:
- self._style._fmt = fmt
- return super(ColoredLevelFormatter, self).format(record)
-
-
+class ColoredLevelFormatter(logging.Formatter):
+ """
+ Colorize the %(levelname)..s part of the log format passed to __init__.
+ """
+
+ LOGLEVEL_COLOROPTS = {
+ logging.CRITICAL: {"red"},
+ logging.ERROR: {"red", "bold"},
+ logging.WARNING: {"yellow"},
+ logging.WARN: {"yellow"},
+ logging.INFO: {"green"},
+ logging.DEBUG: {"purple"},
+ logging.NOTSET: set(),
+ }
+ LEVELNAME_FMT_REGEX = re.compile(r"%\(levelname\)([+-]?\d*s)")
+
+ def __init__(self, terminalwriter, *args, **kwargs):
+ super(ColoredLevelFormatter, self).__init__(*args, **kwargs)
+ if six.PY2:
+ self._original_fmt = self._fmt
+ else:
+ self._original_fmt = self._style._fmt
+ self._level_to_fmt_mapping = {}
+
+ levelname_fmt_match = self.LEVELNAME_FMT_REGEX.search(self._fmt)
+ if not levelname_fmt_match:
+ return
+ levelname_fmt = levelname_fmt_match.group()
+
+ for level, color_opts in self.LOGLEVEL_COLOROPTS.items():
+ formatted_levelname = levelname_fmt % {
+ "levelname": logging.getLevelName(level)
+ }
+
+ # add ANSI escape sequences around the formatted levelname
+ color_kwargs = {name: True for name in color_opts}
+ colorized_formatted_levelname = terminalwriter.markup(
+ formatted_levelname, **color_kwargs
+ )
+ self._level_to_fmt_mapping[level] = self.LEVELNAME_FMT_REGEX.sub(
+ colorized_formatted_levelname, self._fmt
+ )
+
+ def format(self, record):
+ fmt = self._level_to_fmt_mapping.get(record.levelno, self._original_fmt)
+ if six.PY2:
+ self._fmt = fmt
+ else:
+ self._style._fmt = fmt
+ return super(ColoredLevelFormatter, self).format(record)
+
+
if not six.PY2:
# Formatter classes don't support format styles in PY2
@@ -107,330 +107,330 @@ if not six.PY2:
return self._fmt % record.__dict__
-def get_option_ini(config, *names):
- for name in names:
- ret = config.getoption(name) # 'default' arg won't work as expected
- if ret is None:
- ret = config.getini(name)
- if ret:
- return ret
-
-
-def pytest_addoption(parser):
- """Add options to control log capturing."""
- group = parser.getgroup("logging")
-
- def add_option_ini(option, dest, default=None, type=None, **kwargs):
- parser.addini(
- dest, default=default, type=type, help="default value for " + option
- )
- group.addoption(option, dest=dest, **kwargs)
-
- add_option_ini(
- "--no-print-logs",
- dest="log_print",
- action="store_const",
- const=False,
- default=True,
- type="bool",
- help="disable printing caught logs on failed tests.",
- )
- add_option_ini(
- "--log-level",
- dest="log_level",
- default=None,
- help="logging level used by the logging module",
- )
- add_option_ini(
- "--log-format",
- dest="log_format",
- default=DEFAULT_LOG_FORMAT,
- help="log format as used by the logging module.",
- )
- add_option_ini(
- "--log-date-format",
- dest="log_date_format",
- default=DEFAULT_LOG_DATE_FORMAT,
- help="log date format as used by the logging module.",
- )
- parser.addini(
- "log_cli",
- default=False,
- type="bool",
- help='enable log display during test run (also known as "live logging").',
- )
- add_option_ini(
- "--log-cli-level", dest="log_cli_level", default=None, help="cli logging level."
- )
- add_option_ini(
- "--log-cli-format",
- dest="log_cli_format",
- default=None,
- help="log format as used by the logging module.",
- )
- add_option_ini(
- "--log-cli-date-format",
- dest="log_cli_date_format",
- default=None,
- help="log date format as used by the logging module.",
- )
- add_option_ini(
- "--log-file",
- dest="log_file",
- default=None,
- help="path to a file when logging will be written to.",
- )
- add_option_ini(
- "--log-file-level",
- dest="log_file_level",
- default=None,
- help="log file logging level.",
- )
- add_option_ini(
- "--log-file-format",
- dest="log_file_format",
- default=DEFAULT_LOG_FORMAT,
- help="log format as used by the logging module.",
- )
- add_option_ini(
- "--log-file-date-format",
- dest="log_file_date_format",
- default=DEFAULT_LOG_DATE_FORMAT,
- help="log date format as used by the logging module.",
- )
-
-
-@contextmanager
-def catching_logs(handler, formatter=None, level=None):
- """Context manager that prepares the whole logging machinery properly."""
- root_logger = logging.getLogger()
-
- if formatter is not None:
- handler.setFormatter(formatter)
- if level is not None:
- handler.setLevel(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)
- if level is not None:
- orig_level = root_logger.level
- root_logger.setLevel(min(orig_level, level))
- try:
- yield handler
- finally:
- if level is not None:
- root_logger.setLevel(orig_level)
- if add_new_handler:
- root_logger.removeHandler(handler)
-
-
-class LogCaptureHandler(logging.StreamHandler):
- """A logging handler that stores log records and the log text."""
-
- def __init__(self):
- """Creates a new log handler."""
- logging.StreamHandler.__init__(self, py.io.TextIO())
- self.records = []
-
- def emit(self, record):
- """Keep the log records in a list in addition to the log text."""
- self.records.append(record)
- logging.StreamHandler.emit(self, record)
-
- def reset(self):
- self.records = []
- self.stream = py.io.TextIO()
-
-
-class LogCaptureFixture(object):
- """Provides access and control of log capturing."""
-
- def __init__(self, item):
- """Creates a new funcarg."""
- self._item = item
- # dict of log name -> log level
+def get_option_ini(config, *names):
+ for name in names:
+ ret = config.getoption(name) # 'default' arg won't work as expected
+ if ret is None:
+ ret = config.getini(name)
+ if ret:
+ return ret
+
+
+def pytest_addoption(parser):
+ """Add options to control log capturing."""
+ group = parser.getgroup("logging")
+
+ def add_option_ini(option, dest, default=None, type=None, **kwargs):
+ parser.addini(
+ dest, default=default, type=type, help="default value for " + option
+ )
+ group.addoption(option, dest=dest, **kwargs)
+
+ add_option_ini(
+ "--no-print-logs",
+ dest="log_print",
+ action="store_const",
+ const=False,
+ default=True,
+ type="bool",
+ help="disable printing caught logs on failed tests.",
+ )
+ add_option_ini(
+ "--log-level",
+ dest="log_level",
+ default=None,
+ help="logging level used by the logging module",
+ )
+ add_option_ini(
+ "--log-format",
+ dest="log_format",
+ default=DEFAULT_LOG_FORMAT,
+ help="log format as used by the logging module.",
+ )
+ add_option_ini(
+ "--log-date-format",
+ dest="log_date_format",
+ default=DEFAULT_LOG_DATE_FORMAT,
+ help="log date format as used by the logging module.",
+ )
+ parser.addini(
+ "log_cli",
+ default=False,
+ type="bool",
+ help='enable log display during test run (also known as "live logging").',
+ )
+ add_option_ini(
+ "--log-cli-level", dest="log_cli_level", default=None, help="cli logging level."
+ )
+ add_option_ini(
+ "--log-cli-format",
+ dest="log_cli_format",
+ default=None,
+ help="log format as used by the logging module.",
+ )
+ add_option_ini(
+ "--log-cli-date-format",
+ dest="log_cli_date_format",
+ default=None,
+ help="log date format as used by the logging module.",
+ )
+ add_option_ini(
+ "--log-file",
+ dest="log_file",
+ default=None,
+ help="path to a file when logging will be written to.",
+ )
+ add_option_ini(
+ "--log-file-level",
+ dest="log_file_level",
+ default=None,
+ help="log file logging level.",
+ )
+ add_option_ini(
+ "--log-file-format",
+ dest="log_file_format",
+ default=DEFAULT_LOG_FORMAT,
+ help="log format as used by the logging module.",
+ )
+ add_option_ini(
+ "--log-file-date-format",
+ dest="log_file_date_format",
+ default=DEFAULT_LOG_DATE_FORMAT,
+ help="log date format as used by the logging module.",
+ )
+
+
+@contextmanager
+def catching_logs(handler, formatter=None, level=None):
+ """Context manager that prepares the whole logging machinery properly."""
+ root_logger = logging.getLogger()
+
+ if formatter is not None:
+ handler.setFormatter(formatter)
+ if level is not None:
+ handler.setLevel(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)
+ if level is not None:
+ orig_level = root_logger.level
+ root_logger.setLevel(min(orig_level, level))
+ try:
+ yield handler
+ finally:
+ if level is not None:
+ root_logger.setLevel(orig_level)
+ if add_new_handler:
+ root_logger.removeHandler(handler)
+
+
+class LogCaptureHandler(logging.StreamHandler):
+ """A logging handler that stores log records and the log text."""
+
+ def __init__(self):
+ """Creates a new log handler."""
+ logging.StreamHandler.__init__(self, py.io.TextIO())
+ self.records = []
+
+ def emit(self, record):
+ """Keep the log records in a list in addition to the log text."""
+ self.records.append(record)
+ logging.StreamHandler.emit(self, record)
+
+ def reset(self):
+ self.records = []
+ self.stream = py.io.TextIO()
+
+
+class LogCaptureFixture(object):
+ """Provides access and control of log capturing."""
+
+ def __init__(self, item):
+ """Creates a new funcarg."""
+ self._item = item
+ # dict of log name -> log level
self._initial_log_levels = {} # Dict[str, int]
-
- def _finalize(self):
- """Finalizes the fixture.
-
- This restores the log levels changed by :meth:`set_level`.
- """
- # restore log levels
- for logger_name, level in self._initial_log_levels.items():
- logger = logging.getLogger(logger_name)
- logger.setLevel(level)
-
- @property
- def handler(self):
- """
- :rtype: LogCaptureHandler
- """
- return self._item.catch_log_handler
-
- def get_records(self, when):
- """
- Get the logging records for one of the possible test phases.
-
- :param str when:
- Which test phase to obtain the records from. Valid values are: "setup", "call" and "teardown".
-
- :rtype: List[logging.LogRecord]
- :return: the list of captured records at the given stage
-
- .. versionadded:: 3.4
- """
- handler = self._item.catch_log_handlers.get(when)
- if handler:
- return handler.records
- else:
- return []
-
- @property
- def text(self):
+
+ def _finalize(self):
+ """Finalizes the fixture.
+
+ This restores the log levels changed by :meth:`set_level`.
+ """
+ # restore log levels
+ for logger_name, level in self._initial_log_levels.items():
+ logger = logging.getLogger(logger_name)
+ logger.setLevel(level)
+
+ @property
+ def handler(self):
+ """
+ :rtype: LogCaptureHandler
+ """
+ return self._item.catch_log_handler
+
+ def get_records(self, when):
+ """
+ Get the logging records for one of the possible test phases.
+
+ :param str when:
+ Which test phase to obtain the records from. Valid values are: "setup", "call" and "teardown".
+
+ :rtype: List[logging.LogRecord]
+ :return: the list of captured records at the given stage
+
+ .. versionadded:: 3.4
+ """
+ handler = self._item.catch_log_handlers.get(when)
+ if handler:
+ return handler.records
+ else:
+ return []
+
+ @property
+ def text(self):
"""Returns the formatted log text."""
return _remove_ansi_escape_sequences(self.handler.stream.getvalue())
-
- @property
- def records(self):
- """Returns the list of log records."""
- return self.handler.records
-
- @property
- def record_tuples(self):
- """Returns a list of a stripped down version of log records intended
- for use in assertion comparison.
-
- The format of the tuple is:
-
- (logger_name, log_level, message)
- """
- return [(r.name, r.levelno, r.getMessage()) for r in self.records]
-
- @property
- def messages(self):
- """Returns a list of format-interpolated log messages.
-
- Unlike 'records', which contains the format string and parameters for interpolation, log messages in this list
- are all interpolated.
- Unlike 'text', which contains the output from the handler, log messages in this list are unadorned with
- levels, timestamps, etc, making exact comparisons more reliable.
-
- Note that traceback or stack info (from :func:`logging.exception` or the `exc_info` or `stack_info` arguments
- to the logging functions) is not included, as this is added by the formatter in the handler.
-
- .. versionadded:: 3.7
- """
- return [r.getMessage() for r in self.records]
-
- def clear(self):
- """Reset the list of log records and the captured log text."""
- self.handler.reset()
-
- def set_level(self, level, logger=None):
- """Sets the level for capturing of logs. The level will be restored to its previous value at the end of
- the test.
-
- :param int level: the logger to level.
- :param str logger: the logger to update the level. If not given, the root logger level is updated.
-
- .. versionchanged:: 3.4
- The levels of the loggers changed by this function will be restored to their initial values at the
- end of the test.
- """
- logger_name = logger
- logger = logging.getLogger(logger_name)
- # save the original log-level to restore it during teardown
- self._initial_log_levels.setdefault(logger_name, logger.level)
- logger.setLevel(level)
-
- @contextmanager
- def at_level(self, level, logger=None):
- """Context manager that sets the level for capturing of logs. After the end of the 'with' statement the
- level is restored to its original value.
-
- :param int level: the logger to level.
- :param str logger: the logger to update the level. If not given, the root logger level is updated.
- """
- logger = logging.getLogger(logger)
- orig_level = logger.level
- logger.setLevel(level)
- try:
- yield
- finally:
- logger.setLevel(orig_level)
-
-
-@pytest.fixture
-def caplog(request):
- """Access and control log capturing.
-
- Captured logs are available through the following properties/methods::
-
- * caplog.text -> string containing formatted log output
- * caplog.records -> list of logging.LogRecord instances
- * caplog.record_tuples -> list of (logger_name, level, message) tuples
- * caplog.clear() -> clear captured records and formatted log output string
- """
- result = LogCaptureFixture(request.node)
- yield result
- result._finalize()
-
-
-def get_actual_log_level(config, *setting_names):
- """Return the actual logging level."""
-
- for setting_name in setting_names:
- log_level = config.getoption(setting_name)
- if log_level is None:
- log_level = config.getini(setting_name)
- if log_level:
- break
- else:
- return
-
- if isinstance(log_level, six.string_types):
- log_level = log_level.upper()
- try:
- return int(getattr(logging, log_level, log_level))
- except ValueError:
- # Python logging does not recognise this as a logging level
- raise pytest.UsageError(
- "'{}' is not recognized as a logging level name for "
- "'{}'. Please consider passing the "
- "logging level num instead.".format(log_level, setting_name)
- )
-
-
+
+ @property
+ def records(self):
+ """Returns the list of log records."""
+ return self.handler.records
+
+ @property
+ def record_tuples(self):
+ """Returns a list of a stripped down version of log records intended
+ for use in assertion comparison.
+
+ The format of the tuple is:
+
+ (logger_name, log_level, message)
+ """
+ return [(r.name, r.levelno, r.getMessage()) for r in self.records]
+
+ @property
+ def messages(self):
+ """Returns a list of format-interpolated log messages.
+
+ Unlike 'records', which contains the format string and parameters for interpolation, log messages in this list
+ are all interpolated.
+ Unlike 'text', which contains the output from the handler, log messages in this list are unadorned with
+ levels, timestamps, etc, making exact comparisons more reliable.
+
+ Note that traceback or stack info (from :func:`logging.exception` or the `exc_info` or `stack_info` arguments
+ to the logging functions) is not included, as this is added by the formatter in the handler.
+
+ .. versionadded:: 3.7
+ """
+ return [r.getMessage() for r in self.records]
+
+ def clear(self):
+ """Reset the list of log records and the captured log text."""
+ self.handler.reset()
+
+ def set_level(self, level, logger=None):
+ """Sets the level for capturing of logs. The level will be restored to its previous value at the end of
+ the test.
+
+ :param int level: the logger to level.
+ :param str logger: the logger to update the level. If not given, the root logger level is updated.
+
+ .. versionchanged:: 3.4
+ The levels of the loggers changed by this function will be restored to their initial values at the
+ end of the test.
+ """
+ logger_name = logger
+ logger = logging.getLogger(logger_name)
+ # save the original log-level to restore it during teardown
+ self._initial_log_levels.setdefault(logger_name, logger.level)
+ logger.setLevel(level)
+
+ @contextmanager
+ def at_level(self, level, logger=None):
+ """Context manager that sets the level for capturing of logs. After the end of the 'with' statement the
+ level is restored to its original value.
+
+ :param int level: the logger to level.
+ :param str logger: the logger to update the level. If not given, the root logger level is updated.
+ """
+ logger = logging.getLogger(logger)
+ orig_level = logger.level
+ logger.setLevel(level)
+ try:
+ yield
+ finally:
+ logger.setLevel(orig_level)
+
+
+@pytest.fixture
+def caplog(request):
+ """Access and control log capturing.
+
+ Captured logs are available through the following properties/methods::
+
+ * caplog.text -> string containing formatted log output
+ * caplog.records -> list of logging.LogRecord instances
+ * caplog.record_tuples -> list of (logger_name, level, message) tuples
+ * caplog.clear() -> clear captured records and formatted log output string
+ """
+ result = LogCaptureFixture(request.node)
+ yield result
+ result._finalize()
+
+
+def get_actual_log_level(config, *setting_names):
+ """Return the actual logging level."""
+
+ for setting_name in setting_names:
+ log_level = config.getoption(setting_name)
+ if log_level is None:
+ log_level = config.getini(setting_name)
+ if log_level:
+ break
+ else:
+ return
+
+ if isinstance(log_level, six.string_types):
+ log_level = log_level.upper()
+ try:
+ return int(getattr(logging, log_level, log_level))
+ except ValueError:
+ # Python logging does not recognise this as a logging level
+ raise pytest.UsageError(
+ "'{}' is not recognized as a logging level name for "
+ "'{}'. Please consider passing the "
+ "logging level num instead.".format(log_level, setting_name)
+ )
+
+
# run after terminalreporter/capturemanager are configured
@pytest.hookimpl(trylast=True)
-def pytest_configure(config):
- config.pluginmanager.register(LoggingPlugin(config), "logging-plugin")
-
-
-class LoggingPlugin(object):
- """Attaches to the logging module and captures log messages for each test.
- """
-
- def __init__(self, config):
- """Creates a new plugin to capture log messages.
-
- The formatter can be safely shared across all handlers so
- create a single one for the entire test session here.
- """
- self._config = config
-
- self.print_logs = get_option_ini(config, "log_print")
+def pytest_configure(config):
+ config.pluginmanager.register(LoggingPlugin(config), "logging-plugin")
+
+
+class LoggingPlugin(object):
+ """Attaches to the logging module and captures log messages for each test.
+ """
+
+ def __init__(self, config):
+ """Creates a new plugin to capture log messages.
+
+ The formatter can be safely shared across all handlers so
+ create a single one for the entire test session here.
+ """
+ self._config = config
+
+ self.print_logs = get_option_ini(config, "log_print")
self.formatter = self._create_formatter(
- get_option_ini(config, "log_format"),
- get_option_ini(config, "log_date_format"),
- )
- self.log_level = get_actual_log_level(config, "log_level")
-
+ get_option_ini(config, "log_format"),
+ get_option_ini(config, "log_date_format"),
+ )
+ self.log_level = get_actual_log_level(config, "log_level")
+
self.log_file_level = get_actual_log_level(config, "log_file_level")
self.log_file_format = get_option_ini(config, "log_file_format", "log_format")
self.log_file_date_format = get_option_ini(
@@ -440,17 +440,17 @@ class LoggingPlugin(object):
self.log_file_format, datefmt=self.log_file_date_format
)
- log_file = get_option_ini(config, "log_file")
- if log_file:
- self.log_file_handler = logging.FileHandler(
- log_file, mode="w", encoding="UTF-8"
- )
+ log_file = get_option_ini(config, "log_file")
+ if log_file:
+ self.log_file_handler = logging.FileHandler(
+ log_file, mode="w", encoding="UTF-8"
+ )
self.log_file_handler.setFormatter(self.log_file_formatter)
- else:
- self.log_file_handler = None
-
- self.log_cli_handler = None
-
+ else:
+ self.log_file_handler = None
+
+ self.log_cli_handler = None
+
self.live_logs_context = lambda: dummy_context_manager()
# Note that the lambda for the live_logs_context is needed because
# live_logs_context can otherwise not be entered multiple times due
@@ -518,28 +518,28 @@ class LoggingPlugin(object):
)
self.log_file_handler.setFormatter(self.log_file_formatter)
- def _log_cli_enabled(self):
- """Return True if log_cli should be considered enabled, either explicitly
- or because --log-cli-level was given in the command-line.
- """
- return self._config.getoption(
- "--log-cli-level"
- ) is not None or self._config.getini("log_cli")
-
- @pytest.hookimpl(hookwrapper=True, tryfirst=True)
- def pytest_collection(self):
- with self.live_logs_context():
- if self.log_cli_handler:
- self.log_cli_handler.set_when("collection")
-
- if self.log_file_handler is not None:
- with catching_logs(self.log_file_handler, level=self.log_file_level):
- yield
- else:
- yield
-
- @contextmanager
- def _runtest_for(self, item, when):
+ def _log_cli_enabled(self):
+ """Return True if log_cli should be considered enabled, either explicitly
+ or because --log-cli-level was given in the command-line.
+ """
+ return self._config.getoption(
+ "--log-cli-level"
+ ) is not None or self._config.getini("log_cli")
+
+ @pytest.hookimpl(hookwrapper=True, tryfirst=True)
+ def pytest_collection(self):
+ with self.live_logs_context():
+ if self.log_cli_handler:
+ self.log_cli_handler.set_when("collection")
+
+ if self.log_file_handler is not None:
+ with catching_logs(self.log_file_handler, level=self.log_file_level):
+ yield
+ else:
+ yield
+
+ @contextmanager
+ def _runtest_for(self, item, when):
with self._runtest_for_main(item, when):
if self.log_file_handler is not None:
with catching_logs(self.log_file_handler, level=self.log_file_level):
@@ -549,71 +549,71 @@ class LoggingPlugin(object):
@contextmanager
def _runtest_for_main(self, item, when):
- """Implements the internals of pytest_runtest_xxx() hook."""
- with catching_logs(
- LogCaptureHandler(), formatter=self.formatter, level=self.log_level
- ) as log_handler:
- if self.log_cli_handler:
- self.log_cli_handler.set_when(when)
-
- if item is None:
- yield # run the test
- return
-
- if not hasattr(item, "catch_log_handlers"):
- item.catch_log_handlers = {}
- item.catch_log_handlers[when] = log_handler
- item.catch_log_handler = log_handler
- try:
- yield # run test
- finally:
- if when == "teardown":
- del item.catch_log_handler
- del item.catch_log_handlers
-
- if self.print_logs:
- # Add a captured log section to the report.
- log = log_handler.stream.getvalue().strip()
- item.add_report_section(when, "log", log)
-
- @pytest.hookimpl(hookwrapper=True)
- def pytest_runtest_setup(self, item):
- with self._runtest_for(item, "setup"):
- yield
-
- @pytest.hookimpl(hookwrapper=True)
- def pytest_runtest_call(self, item):
- with self._runtest_for(item, "call"):
- yield
-
- @pytest.hookimpl(hookwrapper=True)
- def pytest_runtest_teardown(self, item):
- with self._runtest_for(item, "teardown"):
- yield
-
- @pytest.hookimpl(hookwrapper=True)
- def pytest_runtest_logstart(self):
- if self.log_cli_handler:
- self.log_cli_handler.reset()
- with self._runtest_for(None, "start"):
- yield
-
- @pytest.hookimpl(hookwrapper=True)
- def pytest_runtest_logfinish(self):
- with self._runtest_for(None, "finish"):
- yield
-
+ """Implements the internals of pytest_runtest_xxx() hook."""
+ with catching_logs(
+ LogCaptureHandler(), formatter=self.formatter, level=self.log_level
+ ) as log_handler:
+ if self.log_cli_handler:
+ self.log_cli_handler.set_when(when)
+
+ if item is None:
+ yield # run the test
+ return
+
+ if not hasattr(item, "catch_log_handlers"):
+ item.catch_log_handlers = {}
+ item.catch_log_handlers[when] = log_handler
+ item.catch_log_handler = log_handler
+ try:
+ yield # run test
+ finally:
+ if when == "teardown":
+ del item.catch_log_handler
+ del item.catch_log_handlers
+
+ if self.print_logs:
+ # Add a captured log section to the report.
+ log = log_handler.stream.getvalue().strip()
+ item.add_report_section(when, "log", log)
+
+ @pytest.hookimpl(hookwrapper=True)
+ def pytest_runtest_setup(self, item):
+ with self._runtest_for(item, "setup"):
+ yield
+
+ @pytest.hookimpl(hookwrapper=True)
+ def pytest_runtest_call(self, item):
+ with self._runtest_for(item, "call"):
+ yield
+
+ @pytest.hookimpl(hookwrapper=True)
+ def pytest_runtest_teardown(self, item):
+ with self._runtest_for(item, "teardown"):
+ yield
+
+ @pytest.hookimpl(hookwrapper=True)
+ def pytest_runtest_logstart(self):
+ if self.log_cli_handler:
+ self.log_cli_handler.reset()
+ with self._runtest_for(None, "start"):
+ yield
+
+ @pytest.hookimpl(hookwrapper=True)
+ def pytest_runtest_logfinish(self):
+ with self._runtest_for(None, "finish"):
+ yield
+
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_logreport(self):
with self._runtest_for(None, "logreport"):
yield
- @pytest.hookimpl(hookwrapper=True, tryfirst=True)
- def pytest_sessionfinish(self):
- with self.live_logs_context():
- if self.log_cli_handler:
- self.log_cli_handler.set_when("sessionfinish")
- if self.log_file_handler is not None:
+ @pytest.hookimpl(hookwrapper=True, tryfirst=True)
+ def pytest_sessionfinish(self):
+ with self.live_logs_context():
+ if self.log_cli_handler:
+ self.log_cli_handler.set_when("sessionfinish")
+ if self.log_file_handler is not None:
try:
with catching_logs(
self.log_file_handler, level=self.log_file_level
@@ -623,23 +623,23 @@ class LoggingPlugin(object):
# Close the FileHandler explicitly.
# (logging.shutdown might have lost the weakref?!)
self.log_file_handler.close()
- else:
- yield
-
- @pytest.hookimpl(hookwrapper=True, tryfirst=True)
- def pytest_sessionstart(self):
- with self.live_logs_context():
- if self.log_cli_handler:
- self.log_cli_handler.set_when("sessionstart")
- if self.log_file_handler is not None:
- with catching_logs(self.log_file_handler, level=self.log_file_level):
- yield
- else:
- yield
-
- @pytest.hookimpl(hookwrapper=True)
- def pytest_runtestloop(self, session):
- """Runs all collected test items."""
+ else:
+ yield
+
+ @pytest.hookimpl(hookwrapper=True, tryfirst=True)
+ def pytest_sessionstart(self):
+ with self.live_logs_context():
+ if self.log_cli_handler:
+ self.log_cli_handler.set_when("sessionstart")
+ if self.log_file_handler is not None:
+ with catching_logs(self.log_file_handler, level=self.log_file_level):
+ yield
+ else:
+ yield
+
+ @pytest.hookimpl(hookwrapper=True)
+ def pytest_runtestloop(self, session):
+ """Runs all collected test items."""
if session.config.option.collectonly:
yield
@@ -649,60 +649,60 @@ class LoggingPlugin(object):
# setting verbose flag is needed to avoid messy test progress output
self._config.option.verbose = 1
- with self.live_logs_context():
- if self.log_file_handler is not None:
- with catching_logs(self.log_file_handler, level=self.log_file_level):
- yield # run all the tests
- else:
- yield # run all the tests
-
-
-class _LiveLoggingStreamHandler(logging.StreamHandler):
- """
- Custom StreamHandler used by the live logging feature: it will write a newline before the first log message
- in each test.
-
- During live logging we must also explicitly disable stdout/stderr capturing otherwise it will get captured
- and won't appear in the terminal.
- """
-
- def __init__(self, terminal_reporter, capture_manager):
- """
- :param _pytest.terminal.TerminalReporter terminal_reporter:
- :param _pytest.capture.CaptureManager capture_manager:
- """
- logging.StreamHandler.__init__(self, stream=terminal_reporter)
- self.capture_manager = capture_manager
- self.reset()
- self.set_when(None)
- self._test_outcome_written = False
-
- def reset(self):
- """Reset the handler; should be called before the start of each test"""
- self._first_record_emitted = False
-
- def set_when(self, when):
- """Prepares for the given test phase (setup/call/teardown)"""
- self._when = when
- self._section_name_shown = False
- if when == "start":
- self._test_outcome_written = False
-
- def emit(self, record):
- ctx_manager = (
- self.capture_manager.global_and_fixture_disabled()
- if self.capture_manager
- else dummy_context_manager()
- )
- with ctx_manager:
- if not self._first_record_emitted:
- self.stream.write("\n")
- self._first_record_emitted = True
- elif self._when in ("teardown", "finish"):
- if not self._test_outcome_written:
- self._test_outcome_written = True
- self.stream.write("\n")
- if not self._section_name_shown and self._when:
- self.stream.section("live log " + self._when, sep="-", bold=True)
- self._section_name_shown = True
- logging.StreamHandler.emit(self, record)
+ with self.live_logs_context():
+ if self.log_file_handler is not None:
+ with catching_logs(self.log_file_handler, level=self.log_file_level):
+ yield # run all the tests
+ else:
+ yield # run all the tests
+
+
+class _LiveLoggingStreamHandler(logging.StreamHandler):
+ """
+ Custom StreamHandler used by the live logging feature: it will write a newline before the first log message
+ in each test.
+
+ During live logging we must also explicitly disable stdout/stderr capturing otherwise it will get captured
+ and won't appear in the terminal.
+ """
+
+ def __init__(self, terminal_reporter, capture_manager):
+ """
+ :param _pytest.terminal.TerminalReporter terminal_reporter:
+ :param _pytest.capture.CaptureManager capture_manager:
+ """
+ logging.StreamHandler.__init__(self, stream=terminal_reporter)
+ self.capture_manager = capture_manager
+ self.reset()
+ self.set_when(None)
+ self._test_outcome_written = False
+
+ def reset(self):
+ """Reset the handler; should be called before the start of each test"""
+ self._first_record_emitted = False
+
+ def set_when(self, when):
+ """Prepares for the given test phase (setup/call/teardown)"""
+ self._when = when
+ self._section_name_shown = False
+ if when == "start":
+ self._test_outcome_written = False
+
+ def emit(self, record):
+ ctx_manager = (
+ self.capture_manager.global_and_fixture_disabled()
+ if self.capture_manager
+ else dummy_context_manager()
+ )
+ with ctx_manager:
+ if not self._first_record_emitted:
+ self.stream.write("\n")
+ self._first_record_emitted = True
+ elif self._when in ("teardown", "finish"):
+ if not self._test_outcome_written:
+ self._test_outcome_written = True
+ self.stream.write("\n")
+ if not self._section_name_shown and self._when:
+ self.stream.section("live log " + self._when, sep="-", bold=True)
+ self._section_name_shown = True
+ logging.StreamHandler.emit(self, record)
diff --git a/contrib/python/pytest/py2/_pytest/main.py b/contrib/python/pytest/py2/_pytest/main.py
index 5bfa2e1ff1..a9d310cb62 100644
--- a/contrib/python/pytest/py2/_pytest/main.py
+++ b/contrib/python/pytest/py2/_pytest/main.py
@@ -1,177 +1,177 @@
# -*- coding: utf-8 -*-
-""" core implementation of testing process: init, session, runtest loop. """
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import contextlib
+""" core implementation of testing process: init, session, runtest loop. """
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import contextlib
import fnmatch
-import functools
-import os
-import pkgutil
-import sys
+import functools
+import os
+import pkgutil
+import sys
import warnings
-
-import attr
-import py
-import six
-
-import _pytest._code
-from _pytest import nodes
-from _pytest.config import directory_arg
-from _pytest.config import hookimpl
-from _pytest.config import UsageError
+
+import attr
+import py
+import six
+
+import _pytest._code
+from _pytest import nodes
+from _pytest.config import directory_arg
+from _pytest.config import hookimpl
+from _pytest.config import UsageError
from _pytest.deprecated import PYTEST_CONFIG_GLOBAL
-from _pytest.outcomes import exit
-from _pytest.runner import collect_one_node
-
-# exitcodes for the command line
-EXIT_OK = 0
-EXIT_TESTSFAILED = 1
-EXIT_INTERRUPTED = 2
-EXIT_INTERNALERROR = 3
-EXIT_USAGEERROR = 4
-EXIT_NOTESTSCOLLECTED = 5
-
-
-def pytest_addoption(parser):
- parser.addini(
- "norecursedirs",
- "directory patterns to avoid for recursion",
- type="args",
- default=[".*", "build", "dist", "CVS", "_darcs", "{arch}", "*.egg", "venv"],
- )
- parser.addini(
- "testpaths",
- "directories to search for tests when no files or directories are given in the "
- "command line.",
- type="args",
- default=[],
- )
- group = parser.getgroup("general", "running and selection options")
- group._addoption(
- "-x",
- "--exitfirst",
- action="store_const",
- dest="maxfail",
- const=1,
- help="exit instantly on first error or failed test.",
- ),
- group._addoption(
- "--maxfail",
- metavar="num",
- action="store",
- type=int,
- dest="maxfail",
- default=0,
- help="exit after first num failures or errors.",
- )
- group._addoption(
+from _pytest.outcomes import exit
+from _pytest.runner import collect_one_node
+
+# exitcodes for the command line
+EXIT_OK = 0
+EXIT_TESTSFAILED = 1
+EXIT_INTERRUPTED = 2
+EXIT_INTERNALERROR = 3
+EXIT_USAGEERROR = 4
+EXIT_NOTESTSCOLLECTED = 5
+
+
+def pytest_addoption(parser):
+ parser.addini(
+ "norecursedirs",
+ "directory patterns to avoid for recursion",
+ type="args",
+ default=[".*", "build", "dist", "CVS", "_darcs", "{arch}", "*.egg", "venv"],
+ )
+ parser.addini(
+ "testpaths",
+ "directories to search for tests when no files or directories are given in the "
+ "command line.",
+ type="args",
+ default=[],
+ )
+ group = parser.getgroup("general", "running and selection options")
+ group._addoption(
+ "-x",
+ "--exitfirst",
+ action="store_const",
+ dest="maxfail",
+ const=1,
+ help="exit instantly on first error or failed test.",
+ ),
+ group._addoption(
+ "--maxfail",
+ metavar="num",
+ action="store",
+ type=int,
+ dest="maxfail",
+ default=0,
+ help="exit after first num failures or errors.",
+ )
+ group._addoption(
"--strict-markers",
- "--strict",
- action="store_true",
+ "--strict",
+ action="store_true",
help="markers not registered in the `markers` section of the configuration file raise errors.",
- )
- group._addoption(
- "-c",
- metavar="file",
- type=str,
- dest="inifilename",
- help="load configuration from `file` instead of trying to locate one of the implicit "
- "configuration files.",
- )
- group._addoption(
- "--continue-on-collection-errors",
- action="store_true",
- default=False,
- dest="continue_on_collection_errors",
- help="Force test execution even if collection errors occur.",
- )
- group._addoption(
- "--rootdir",
- action="store",
- dest="rootdir",
- help="Define root directory for tests. Can be relative path: 'root_dir', './root_dir', "
- "'root_dir/another_dir/'; absolute path: '/home/user/root_dir'; path with variables: "
- "'$HOME/root_dir'.",
- )
-
- group = parser.getgroup("collect", "collection")
- group.addoption(
- "--collectonly",
- "--collect-only",
- action="store_true",
- help="only collect tests, don't execute them.",
- ),
- group.addoption(
- "--pyargs",
- action="store_true",
- help="try to interpret all arguments as python packages.",
- )
- group.addoption(
- "--ignore",
- action="append",
- metavar="path",
- help="ignore path during collection (multi-allowed).",
- )
- group.addoption(
+ )
+ group._addoption(
+ "-c",
+ metavar="file",
+ type=str,
+ dest="inifilename",
+ help="load configuration from `file` instead of trying to locate one of the implicit "
+ "configuration files.",
+ )
+ group._addoption(
+ "--continue-on-collection-errors",
+ action="store_true",
+ default=False,
+ dest="continue_on_collection_errors",
+ help="Force test execution even if collection errors occur.",
+ )
+ group._addoption(
+ "--rootdir",
+ action="store",
+ dest="rootdir",
+ help="Define root directory for tests. Can be relative path: 'root_dir', './root_dir', "
+ "'root_dir/another_dir/'; absolute path: '/home/user/root_dir'; path with variables: "
+ "'$HOME/root_dir'.",
+ )
+
+ group = parser.getgroup("collect", "collection")
+ group.addoption(
+ "--collectonly",
+ "--collect-only",
+ action="store_true",
+ help="only collect tests, don't execute them.",
+ ),
+ group.addoption(
+ "--pyargs",
+ action="store_true",
+ help="try to interpret all arguments as python packages.",
+ )
+ group.addoption(
+ "--ignore",
+ action="append",
+ metavar="path",
+ help="ignore path during collection (multi-allowed).",
+ )
+ group.addoption(
"--ignore-glob",
action="append",
metavar="path",
help="ignore path pattern during collection (multi-allowed).",
)
group.addoption(
- "--deselect",
- action="append",
- metavar="nodeid_prefix",
- help="deselect item during collection (multi-allowed).",
- )
- # when changing this to --conf-cut-dir, config.py Conftest.setinitial
- # needs upgrading as well
- group.addoption(
- "--confcutdir",
- dest="confcutdir",
- default=None,
- metavar="dir",
- type=functools.partial(directory_arg, optname="--confcutdir"),
- help="only load conftest.py's relative to specified dir.",
- )
- group.addoption(
- "--noconftest",
- action="store_true",
- dest="noconftest",
- default=False,
- help="Don't load any conftest.py files.",
- )
- group.addoption(
- "--keepduplicates",
- "--keep-duplicates",
- action="store_true",
- dest="keepduplicates",
- default=False,
- help="Keep duplicate tests.",
- )
- group.addoption(
- "--collect-in-virtualenv",
- action="store_true",
- dest="collect_in_virtualenv",
- default=False,
- help="Don't ignore tests in a local virtualenv directory",
- )
-
- group = parser.getgroup("debugconfig", "test session debugging and configuration")
- group.addoption(
- "--basetemp",
- dest="basetemp",
- default=None,
- metavar="dir",
- help=(
- "base temporary directory for this test run."
- "(warning: this directory is removed if it exists)"
- ),
- )
-
-
+ "--deselect",
+ action="append",
+ metavar="nodeid_prefix",
+ help="deselect item during collection (multi-allowed).",
+ )
+ # when changing this to --conf-cut-dir, config.py Conftest.setinitial
+ # needs upgrading as well
+ group.addoption(
+ "--confcutdir",
+ dest="confcutdir",
+ default=None,
+ metavar="dir",
+ type=functools.partial(directory_arg, optname="--confcutdir"),
+ help="only load conftest.py's relative to specified dir.",
+ )
+ group.addoption(
+ "--noconftest",
+ action="store_true",
+ dest="noconftest",
+ default=False,
+ help="Don't load any conftest.py files.",
+ )
+ group.addoption(
+ "--keepduplicates",
+ "--keep-duplicates",
+ action="store_true",
+ dest="keepduplicates",
+ default=False,
+ help="Keep duplicate tests.",
+ )
+ group.addoption(
+ "--collect-in-virtualenv",
+ action="store_true",
+ dest="collect_in_virtualenv",
+ default=False,
+ help="Don't ignore tests in a local virtualenv directory",
+ )
+
+ group = parser.getgroup("debugconfig", "test session debugging and configuration")
+ group.addoption(
+ "--basetemp",
+ dest="basetemp",
+ default=None,
+ metavar="dir",
+ help=(
+ "base temporary directory for this test run."
+ "(warning: this directory is removed if it exists)"
+ ),
+ )
+
+
class _ConfigDeprecated(object):
def __init__(self, config):
self.__dict__["_config"] = config
@@ -188,121 +188,121 @@ class _ConfigDeprecated(object):
return "{}({!r})".format(type(self).__name__, self._config)
-def pytest_configure(config):
+def pytest_configure(config):
__import__("pytest").config = _ConfigDeprecated(config) # compatibility
-
-
-def wrap_session(config, doit):
- """Skeleton command line program"""
- session = Session(config)
- session.exitstatus = EXIT_OK
- initstate = 0
- try:
- try:
- config._do_configure()
- initstate = 1
- config.hook.pytest_sessionstart(session=session)
- initstate = 2
- session.exitstatus = doit(config, session) or 0
- except UsageError:
+
+
+def wrap_session(config, doit):
+ """Skeleton command line program"""
+ session = Session(config)
+ session.exitstatus = EXIT_OK
+ initstate = 0
+ try:
+ try:
+ config._do_configure()
+ initstate = 1
+ config.hook.pytest_sessionstart(session=session)
+ initstate = 2
+ session.exitstatus = doit(config, session) or 0
+ except UsageError:
session.exitstatus = EXIT_USAGEERROR
- raise
- except Failed:
- session.exitstatus = EXIT_TESTSFAILED
+ raise
+ except Failed:
+ session.exitstatus = EXIT_TESTSFAILED
except (KeyboardInterrupt, exit.Exception):
excinfo = _pytest._code.ExceptionInfo.from_current()
- exitstatus = EXIT_INTERRUPTED
+ exitstatus = EXIT_INTERRUPTED
if isinstance(excinfo.value, exit.Exception):
- if excinfo.value.returncode is not None:
- exitstatus = excinfo.value.returncode
+ if excinfo.value.returncode is not None:
+ exitstatus = excinfo.value.returncode
if initstate < 2:
sys.stderr.write(
"{}: {}\n".format(excinfo.typename, excinfo.value.msg)
)
- config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
- session.exitstatus = exitstatus
- except: # noqa
+ config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
+ session.exitstatus = exitstatus
+ except: # noqa
excinfo = _pytest._code.ExceptionInfo.from_current()
- config.notify_exception(excinfo, config.option)
- session.exitstatus = EXIT_INTERNALERROR
- if excinfo.errisinstance(SystemExit):
+ config.notify_exception(excinfo, config.option)
+ session.exitstatus = EXIT_INTERNALERROR
+ if excinfo.errisinstance(SystemExit):
sys.stderr.write("mainloop: caught unexpected SystemExit!\n")
-
- finally:
- excinfo = None # Explicitly break reference cycle.
- session.startdir.chdir()
- if initstate >= 2:
- config.hook.pytest_sessionfinish(
- session=session, exitstatus=session.exitstatus
- )
- config._ensure_unconfigure()
- return session.exitstatus
-
-
-def pytest_cmdline_main(config):
- return wrap_session(config, _main)
-
-
-def _main(config, session):
- """ default command line protocol for initialization, session,
- running tests and reporting. """
- config.hook.pytest_collection(session=session)
- config.hook.pytest_runtestloop(session=session)
-
- if session.testsfailed:
- return EXIT_TESTSFAILED
- elif session.testscollected == 0:
- return EXIT_NOTESTSCOLLECTED
-
-
-def pytest_collection(session):
- return session.perform_collect()
-
-
-def pytest_runtestloop(session):
- if session.testsfailed and not session.config.option.continue_on_collection_errors:
- raise session.Interrupted("%d errors during collection" % session.testsfailed)
-
- if session.config.option.collectonly:
- return True
-
- for i, item in enumerate(session.items):
- nextitem = session.items[i + 1] if i + 1 < len(session.items) else None
- item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
- if session.shouldfail:
- raise session.Failed(session.shouldfail)
- if session.shouldstop:
- raise session.Interrupted(session.shouldstop)
- return True
-
-
-def _in_venv(path):
- """Attempts to detect if ``path`` is the root of a Virtual Environment by
- checking for the existence of the appropriate activate script"""
- bindir = path.join("Scripts" if sys.platform.startswith("win") else "bin")
- if not bindir.isdir():
- return False
- activates = (
- "activate",
- "activate.csh",
- "activate.fish",
- "Activate",
- "Activate.bat",
- "Activate.ps1",
- )
- return any([fname.basename in activates for fname in bindir.listdir()])
-
-
-def pytest_ignore_collect(path, config):
- ignore_paths = config._getconftest_pathlist("collect_ignore", path=path.dirpath())
- ignore_paths = ignore_paths or []
- excludeopt = config.getoption("ignore")
- if excludeopt:
- ignore_paths.extend([py.path.local(x) for x in excludeopt])
-
- if py.path.local(path) in ignore_paths:
- return True
-
+
+ finally:
+ excinfo = None # Explicitly break reference cycle.
+ session.startdir.chdir()
+ if initstate >= 2:
+ config.hook.pytest_sessionfinish(
+ session=session, exitstatus=session.exitstatus
+ )
+ config._ensure_unconfigure()
+ return session.exitstatus
+
+
+def pytest_cmdline_main(config):
+ return wrap_session(config, _main)
+
+
+def _main(config, session):
+ """ default command line protocol for initialization, session,
+ running tests and reporting. """
+ config.hook.pytest_collection(session=session)
+ config.hook.pytest_runtestloop(session=session)
+
+ if session.testsfailed:
+ return EXIT_TESTSFAILED
+ elif session.testscollected == 0:
+ return EXIT_NOTESTSCOLLECTED
+
+
+def pytest_collection(session):
+ return session.perform_collect()
+
+
+def pytest_runtestloop(session):
+ if session.testsfailed and not session.config.option.continue_on_collection_errors:
+ raise session.Interrupted("%d errors during collection" % session.testsfailed)
+
+ if session.config.option.collectonly:
+ return True
+
+ for i, item in enumerate(session.items):
+ nextitem = session.items[i + 1] if i + 1 < len(session.items) else None
+ item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
+ if session.shouldfail:
+ raise session.Failed(session.shouldfail)
+ if session.shouldstop:
+ raise session.Interrupted(session.shouldstop)
+ return True
+
+
+def _in_venv(path):
+ """Attempts to detect if ``path`` is the root of a Virtual Environment by
+ checking for the existence of the appropriate activate script"""
+ bindir = path.join("Scripts" if sys.platform.startswith("win") else "bin")
+ if not bindir.isdir():
+ return False
+ activates = (
+ "activate",
+ "activate.csh",
+ "activate.fish",
+ "Activate",
+ "Activate.bat",
+ "Activate.ps1",
+ )
+ return any([fname.basename in activates for fname in bindir.listdir()])
+
+
+def pytest_ignore_collect(path, config):
+ ignore_paths = config._getconftest_pathlist("collect_ignore", path=path.dirpath())
+ ignore_paths = ignore_paths or []
+ excludeopt = config.getoption("ignore")
+ if excludeopt:
+ ignore_paths.extend([py.path.local(x) for x in excludeopt])
+
+ if py.path.local(path) in ignore_paths:
+ return True
+
ignore_globs = config._getconftest_pathlist(
"collect_ignore_glob", path=path.dirpath()
)
@@ -317,131 +317,131 @@ def pytest_ignore_collect(path, config):
):
return True
- allow_in_venv = config.getoption("collect_in_virtualenv")
- if not allow_in_venv and _in_venv(path):
- return True
-
- return False
-
-
-def pytest_collection_modifyitems(items, config):
- deselect_prefixes = tuple(config.getoption("deselect") or [])
- if not deselect_prefixes:
- return
-
- remaining = []
- deselected = []
- for colitem in items:
- if colitem.nodeid.startswith(deselect_prefixes):
- deselected.append(colitem)
- else:
- remaining.append(colitem)
-
- if deselected:
- config.hook.pytest_deselected(items=deselected)
- items[:] = remaining
-
-
-@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(object):
- def __init__(self, fspath, pm, remove_mods):
- self.fspath = fspath
- self.pm = pm
- self.remove_mods = remove_mods
-
- def __getattr__(self, name):
- x = self.pm.subset_hook_caller(name, remove_plugins=self.remove_mods)
- self.__dict__[name] = x
- return x
-
-
-class NoMatch(Exception):
- """ raised if matching cannot locate a matching names. """
-
-
-class Interrupted(KeyboardInterrupt):
- """ signals an interrupted test run. """
-
- __module__ = "builtins" # for py3
-
-
-class Failed(Exception):
- """ signals a stop as failed test run. """
-
-
-@attr.s
-class _bestrelpath_cache(dict):
- path = attr.ib()
-
- def __missing__(self, path):
- r = self.path.bestrelpath(path)
- self[path] = r
- return r
-
-
-class Session(nodes.FSCollector):
- Interrupted = Interrupted
- Failed = Failed
-
- def __init__(self, config):
- nodes.FSCollector.__init__(
- self, config.rootdir, parent=None, config=config, session=self, nodeid=""
- )
- self.testsfailed = 0
- self.testscollected = 0
- self.shouldstop = False
- self.shouldfail = False
- self.trace = config.trace.root.get("collection")
- self._norecursepatterns = config.getini("norecursedirs")
+ allow_in_venv = config.getoption("collect_in_virtualenv")
+ if not allow_in_venv and _in_venv(path):
+ return True
+
+ return False
+
+
+def pytest_collection_modifyitems(items, config):
+ deselect_prefixes = tuple(config.getoption("deselect") or [])
+ if not deselect_prefixes:
+ return
+
+ remaining = []
+ deselected = []
+ for colitem in items:
+ if colitem.nodeid.startswith(deselect_prefixes):
+ deselected.append(colitem)
+ else:
+ remaining.append(colitem)
+
+ if deselected:
+ config.hook.pytest_deselected(items=deselected)
+ items[:] = remaining
+
+
+@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(object):
+ def __init__(self, fspath, pm, remove_mods):
+ self.fspath = fspath
+ self.pm = pm
+ self.remove_mods = remove_mods
+
+ def __getattr__(self, name):
+ x = self.pm.subset_hook_caller(name, remove_plugins=self.remove_mods)
+ self.__dict__[name] = x
+ return x
+
+
+class NoMatch(Exception):
+ """ raised if matching cannot locate a matching names. """
+
+
+class Interrupted(KeyboardInterrupt):
+ """ signals an interrupted test run. """
+
+ __module__ = "builtins" # for py3
+
+
+class Failed(Exception):
+ """ signals a stop as failed test run. """
+
+
+@attr.s
+class _bestrelpath_cache(dict):
+ path = attr.ib()
+
+ def __missing__(self, path):
+ r = self.path.bestrelpath(path)
+ self[path] = r
+ return r
+
+
+class Session(nodes.FSCollector):
+ Interrupted = Interrupted
+ Failed = Failed
+
+ def __init__(self, config):
+ nodes.FSCollector.__init__(
+ self, config.rootdir, parent=None, config=config, session=self, nodeid=""
+ )
+ self.testsfailed = 0
+ self.testscollected = 0
+ self.shouldstop = False
+ self.shouldfail = False
+ self.trace = config.trace.root.get("collection")
+ self._norecursepatterns = config.getini("norecursedirs")
self.startdir = config.invocation_dir
- self._initialpaths = frozenset()
- # Keep track of any collected nodes in here, so we don't duplicate fixtures
- self._node_cache = {}
- self._bestrelpathcache = _bestrelpath_cache(config.rootdir)
- # Dirnames of pkgs with dunder-init files.
- self._pkg_roots = {}
-
- self.config.pluginmanager.register(self, name="session")
-
+ self._initialpaths = frozenset()
+ # Keep track of any collected nodes in here, so we don't duplicate fixtures
+ self._node_cache = {}
+ self._bestrelpathcache = _bestrelpath_cache(config.rootdir)
+ # Dirnames of pkgs with dunder-init files.
+ self._pkg_roots = {}
+
+ self.config.pluginmanager.register(self, name="session")
+
def __repr__(self):
return "<%s %s exitstatus=%r testsfailed=%d testscollected=%d>" % (
self.__class__.__name__,
@@ -451,176 +451,176 @@ class Session(nodes.FSCollector):
self.testscollected,
)
- def _node_location_to_relpath(self, node_path):
- # bestrelpath is a quite slow function
- return self._bestrelpathcache[node_path]
-
- @hookimpl(tryfirst=True)
- def pytest_collectstart(self):
- if self.shouldfail:
- raise self.Failed(self.shouldfail)
- if self.shouldstop:
- raise self.Interrupted(self.shouldstop)
-
- @hookimpl(tryfirst=True)
- def pytest_runtest_logreport(self, report):
- if report.failed and not hasattr(report, "wasxfail"):
- self.testsfailed += 1
- maxfail = self.config.getvalue("maxfail")
- if maxfail and self.testsfailed >= maxfail:
- self.shouldfail = "stopping after %d failures" % (self.testsfailed)
-
- pytest_collectreport = pytest_runtest_logreport
-
- def isinitpath(self, path):
- return path in self._initialpaths
-
- def gethookproxy(self, fspath):
- # check if we have the common case of running
- # hooks with all conftest.py files
- pm = self.config.pluginmanager
- my_conftestmodules = pm._getconftestmodules(fspath)
- remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
- if remove_mods:
- # one or more conftests are not in use at this fspath
- proxy = FSHookProxy(fspath, pm, remove_mods)
- else:
- # all plugis are active for this fspath
- proxy = self.config.hook
- return proxy
-
- def perform_collect(self, args=None, genitems=True):
- hook = self.config.hook
- try:
- items = self._perform_collect(args, genitems)
- self.config.pluginmanager.check_pending()
- hook.pytest_collection_modifyitems(
- session=self, config=self.config, items=items
- )
- finally:
- hook.pytest_collection_finish(session=self)
- self.testscollected = len(items)
- return items
-
- def _perform_collect(self, args, genitems):
- if args is None:
- args = self.config.args
- self.trace("perform_collect", self, args)
- self.trace.root.indent += 1
- self._notfound = []
- initialpaths = []
- self._initialparts = []
- self.items = items = []
- for arg in args:
- parts = self._parsearg(arg)
- self._initialparts.append(parts)
- initialpaths.append(parts[0])
- self._initialpaths = frozenset(initialpaths)
- rep = collect_one_node(self)
- self.ihook.pytest_collectreport(report=rep)
- self.trace.root.indent -= 1
- if self._notfound:
- errors = []
- for arg, exc in self._notfound:
- line = "(no name %r in any of %r)" % (arg, exc.args[0])
- errors.append("not found: %s\n%s" % (arg, line))
- # XXX: test this
- raise UsageError(*errors)
- if not genitems:
- return rep.result
- else:
- if rep.passed:
- for node in rep.result:
- self.items.extend(self.genitems(node))
- return items
-
- def collect(self):
- for initialpart in self._initialparts:
- arg = "::".join(map(str, initialpart))
- self.trace("processing argument", arg)
- self.trace.root.indent += 1
- try:
- for x in self._collect(arg):
- yield x
- except NoMatch:
- # we are inside a make_report hook so
- # we cannot directly pass through the exception
- self._notfound.append((arg, sys.exc_info()[1]))
-
- self.trace.root.indent -= 1
-
- def _collect(self, arg):
- from _pytest.python import Package
-
- names = self._parsearg(arg)
- argpath = names.pop(0)
-
- # Start with a Session root, and delve to argpath item (dir or file)
- # and stack all Packages found on the way.
- # No point in finding packages when collecting doctests
+ def _node_location_to_relpath(self, node_path):
+ # bestrelpath is a quite slow function
+ return self._bestrelpathcache[node_path]
+
+ @hookimpl(tryfirst=True)
+ def pytest_collectstart(self):
+ if self.shouldfail:
+ raise self.Failed(self.shouldfail)
+ if self.shouldstop:
+ raise self.Interrupted(self.shouldstop)
+
+ @hookimpl(tryfirst=True)
+ def pytest_runtest_logreport(self, report):
+ if report.failed and not hasattr(report, "wasxfail"):
+ self.testsfailed += 1
+ maxfail = self.config.getvalue("maxfail")
+ if maxfail and self.testsfailed >= maxfail:
+ self.shouldfail = "stopping after %d failures" % (self.testsfailed)
+
+ pytest_collectreport = pytest_runtest_logreport
+
+ def isinitpath(self, path):
+ return path in self._initialpaths
+
+ def gethookproxy(self, fspath):
+ # check if we have the common case of running
+ # hooks with all conftest.py files
+ pm = self.config.pluginmanager
+ my_conftestmodules = pm._getconftestmodules(fspath)
+ remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
+ if remove_mods:
+ # one or more conftests are not in use at this fspath
+ proxy = FSHookProxy(fspath, pm, remove_mods)
+ else:
+ # all plugis are active for this fspath
+ proxy = self.config.hook
+ return proxy
+
+ def perform_collect(self, args=None, genitems=True):
+ hook = self.config.hook
+ try:
+ items = self._perform_collect(args, genitems)
+ self.config.pluginmanager.check_pending()
+ hook.pytest_collection_modifyitems(
+ session=self, config=self.config, items=items
+ )
+ finally:
+ hook.pytest_collection_finish(session=self)
+ self.testscollected = len(items)
+ return items
+
+ def _perform_collect(self, args, genitems):
+ if args is None:
+ args = self.config.args
+ self.trace("perform_collect", self, args)
+ self.trace.root.indent += 1
+ self._notfound = []
+ initialpaths = []
+ self._initialparts = []
+ self.items = items = []
+ for arg in args:
+ parts = self._parsearg(arg)
+ self._initialparts.append(parts)
+ initialpaths.append(parts[0])
+ self._initialpaths = frozenset(initialpaths)
+ rep = collect_one_node(self)
+ self.ihook.pytest_collectreport(report=rep)
+ self.trace.root.indent -= 1
+ if self._notfound:
+ errors = []
+ for arg, exc in self._notfound:
+ line = "(no name %r in any of %r)" % (arg, exc.args[0])
+ errors.append("not found: %s\n%s" % (arg, line))
+ # XXX: test this
+ raise UsageError(*errors)
+ if not genitems:
+ return rep.result
+ else:
+ if rep.passed:
+ for node in rep.result:
+ self.items.extend(self.genitems(node))
+ return items
+
+ def collect(self):
+ for initialpart in self._initialparts:
+ arg = "::".join(map(str, initialpart))
+ self.trace("processing argument", arg)
+ self.trace.root.indent += 1
+ try:
+ for x in self._collect(arg):
+ yield x
+ except NoMatch:
+ # we are inside a make_report hook so
+ # we cannot directly pass through the exception
+ self._notfound.append((arg, sys.exc_info()[1]))
+
+ self.trace.root.indent -= 1
+
+ def _collect(self, arg):
+ from _pytest.python import Package
+
+ names = self._parsearg(arg)
+ argpath = names.pop(0)
+
+ # Start with a Session root, and delve to argpath item (dir or file)
+ # and stack all Packages found on the way.
+ # No point in finding packages when collecting doctests
if not self.config.getoption("doctestmodules", False):
- pm = self.config.pluginmanager
- for parent in reversed(argpath.parts()):
- if pm._confcutdir and pm._confcutdir.relto(parent):
- break
-
- if parent.isdir():
- pkginit = parent.join("__init__.py")
- if pkginit.isfile():
- if pkginit not in self._node_cache:
- col = self._collectfile(pkginit, handle_dupes=False)
- if col:
- if isinstance(col[0], Package):
- self._pkg_roots[parent] = col[0]
- # always store a list in the cache, matchnodes expects it
- self._node_cache[col[0].fspath] = [col[0]]
-
- # If it's a directory argument, recurse and look for any Subpackages.
- # Let the Package collector deal with subnodes, don't collect here.
- if argpath.check(dir=1):
- assert not names, "invalid arg %r" % (arg,)
-
- seen_dirs = set()
- for path in argpath.visit(
+ pm = self.config.pluginmanager
+ for parent in reversed(argpath.parts()):
+ if pm._confcutdir and pm._confcutdir.relto(parent):
+ break
+
+ if parent.isdir():
+ pkginit = parent.join("__init__.py")
+ if pkginit.isfile():
+ if pkginit not in self._node_cache:
+ col = self._collectfile(pkginit, handle_dupes=False)
+ if col:
+ if isinstance(col[0], Package):
+ self._pkg_roots[parent] = col[0]
+ # always store a list in the cache, matchnodes expects it
+ self._node_cache[col[0].fspath] = [col[0]]
+
+ # If it's a directory argument, recurse and look for any Subpackages.
+ # Let the Package collector deal with subnodes, don't collect here.
+ if argpath.check(dir=1):
+ assert not names, "invalid arg %r" % (arg,)
+
+ seen_dirs = set()
+ for path in argpath.visit(
fil=self._visit_filter, rec=self._recurse, bf=True, sort=True
- ):
- dirpath = path.dirpath()
- if dirpath not in seen_dirs:
- # Collect packages first.
- seen_dirs.add(dirpath)
- pkginit = dirpath.join("__init__.py")
- if pkginit.exists():
- for x in self._collectfile(pkginit):
- yield x
- if isinstance(x, Package):
- self._pkg_roots[dirpath] = x
- if dirpath in self._pkg_roots:
- # Do not collect packages here.
- continue
-
- for x in self._collectfile(path):
- key = (type(x), x.fspath)
- if key in self._node_cache:
- yield self._node_cache[key]
- else:
- self._node_cache[key] = x
- yield x
- else:
- assert argpath.check(file=1)
-
- if argpath in self._node_cache:
- col = self._node_cache[argpath]
- else:
- collect_root = self._pkg_roots.get(argpath.dirname, self)
+ ):
+ dirpath = path.dirpath()
+ if dirpath not in seen_dirs:
+ # Collect packages first.
+ seen_dirs.add(dirpath)
+ pkginit = dirpath.join("__init__.py")
+ if pkginit.exists():
+ for x in self._collectfile(pkginit):
+ yield x
+ if isinstance(x, Package):
+ self._pkg_roots[dirpath] = x
+ if dirpath in self._pkg_roots:
+ # Do not collect packages here.
+ continue
+
+ for x in self._collectfile(path):
+ key = (type(x), x.fspath)
+ if key in self._node_cache:
+ yield self._node_cache[key]
+ else:
+ self._node_cache[key] = x
+ yield x
+ else:
+ assert argpath.check(file=1)
+
+ if argpath in self._node_cache:
+ col = self._node_cache[argpath]
+ else:
+ collect_root = self._pkg_roots.get(argpath.dirname, self)
col = collect_root._collectfile(argpath, handle_dupes=False)
- if col:
- self._node_cache[argpath] = col
- m = self.matchnodes(col, names)
- # If __init__.py was the only file requested, then the matched node will be
- # the corresponding Package, and the first yielded item will be the __init__
- # Module itself, so just use that. If this special case isn't taken, then all
- # the files in the package will be yielded.
- if argpath.basename == "__init__.py":
+ if col:
+ self._node_cache[argpath] = col
+ m = self.matchnodes(col, names)
+ # If __init__.py was the only file requested, then the matched node will be
+ # the corresponding Package, and the first yielded item will be the __init__
+ # Module itself, so just use that. If this special case isn't taken, then all
+ # the files in the package will be yielded.
+ if argpath.basename == "__init__.py":
try:
yield next(m[0].collect())
except StopIteration:
@@ -628,46 +628,46 @@ class Session(nodes.FSCollector):
# file in it, which gets ignored by the default
# "python_files" option.
pass
- return
- for y in m:
- yield y
-
- def _collectfile(self, path, handle_dupes=True):
+ return
+ for y in m:
+ yield y
+
+ def _collectfile(self, path, handle_dupes=True):
assert path.isfile(), "%r is not a file (isdir=%r, exists=%r, islink=%r)" % (
path,
path.isdir(),
path.exists(),
path.islink(),
)
- ihook = self.gethookproxy(path)
- if not self.isinitpath(path):
- if ihook.pytest_ignore_collect(path=path, config=self.config):
- return ()
-
- if handle_dupes:
- keepduplicates = self.config.getoption("keepduplicates")
- if not keepduplicates:
- duplicate_paths = self.config.pluginmanager._duplicatepaths
- if path in duplicate_paths:
- return ()
- else:
- duplicate_paths.add(path)
-
- return ihook.pytest_collect_file(path=path, parent=self)
-
- def _recurse(self, dirpath):
- if dirpath.basename == "__pycache__":
- return False
- ihook = self.gethookproxy(dirpath.dirpath())
- if ihook.pytest_ignore_collect(path=dirpath, config=self.config):
- return False
- for pat in self._norecursepatterns:
- if dirpath.check(fnmatch=pat):
- return False
- ihook = self.gethookproxy(dirpath)
- ihook.pytest_collect_directory(path=dirpath, parent=self)
- return True
-
+ ihook = self.gethookproxy(path)
+ if not self.isinitpath(path):
+ if ihook.pytest_ignore_collect(path=path, config=self.config):
+ return ()
+
+ if handle_dupes:
+ keepduplicates = self.config.getoption("keepduplicates")
+ if not keepduplicates:
+ duplicate_paths = self.config.pluginmanager._duplicatepaths
+ if path in duplicate_paths:
+ return ()
+ else:
+ duplicate_paths.add(path)
+
+ return ihook.pytest_collect_file(path=path, parent=self)
+
+ def _recurse(self, dirpath):
+ if dirpath.basename == "__pycache__":
+ return False
+ ihook = self.gethookproxy(dirpath.dirpath())
+ if ihook.pytest_ignore_collect(path=dirpath, config=self.config):
+ return False
+ for pat in self._norecursepatterns:
+ if dirpath.check(fnmatch=pat):
+ return False
+ ihook = self.gethookproxy(dirpath)
+ ihook.pytest_collect_directory(path=dirpath, parent=self)
+ return True
+
if six.PY2:
@staticmethod
@@ -680,101 +680,101 @@ class Session(nodes.FSCollector):
def _visit_filter(f):
return f.check(file=1)
- def _tryconvertpyarg(self, x):
- """Convert a dotted module name to path."""
- try:
- with _patched_find_module():
- loader = pkgutil.find_loader(x)
- except ImportError:
- return x
- if loader is None:
- return x
- # This method is sometimes invoked when AssertionRewritingHook, which
- # does not define a get_filename method, is already in place:
- try:
- with _patched_find_module():
- path = loader.get_filename(x)
- except AttributeError:
- # Retrieve path from AssertionRewritingHook:
- path = loader.modules[x][0].co_filename
- if loader.is_package(x):
- path = os.path.dirname(path)
- return path
-
- def _parsearg(self, arg):
- """ return (fspath, names) tuple after checking the file exists. """
- parts = str(arg).split("::")
- if self.config.option.pyargs:
- parts[0] = self._tryconvertpyarg(parts[0])
- relpath = parts[0].replace("/", os.sep)
- path = self.config.invocation_dir.join(relpath, abs=True)
- if not path.check():
- if self.config.option.pyargs:
- raise UsageError(
- "file or package not found: " + arg + " (missing __init__.py?)"
- )
- raise UsageError("file not found: " + arg)
- parts[0] = path.realpath()
- return parts
-
- def matchnodes(self, matching, names):
- self.trace("matchnodes", matching, names)
- self.trace.root.indent += 1
- nodes = self._matchnodes(matching, names)
- num = len(nodes)
- self.trace("matchnodes finished -> ", num, "nodes")
- self.trace.root.indent -= 1
- if num == 0:
- raise NoMatch(matching, names[:1])
- return nodes
-
- def _matchnodes(self, matching, names):
- if not matching or not names:
- return matching
- name = names[0]
- assert name
- nextnames = names[1:]
- resultnodes = []
- for node in matching:
- if isinstance(node, nodes.Item):
- if not names:
- resultnodes.append(node)
- continue
- assert isinstance(node, nodes.Collector)
- key = (type(node), node.nodeid)
- if key in self._node_cache:
- rep = self._node_cache[key]
- else:
- rep = collect_one_node(node)
- self._node_cache[key] = rep
- if rep.passed:
- has_matched = False
- for x in rep.result:
- # TODO: remove parametrized workaround once collection structure contains parametrization
- if x.name == name or x.name.split("[")[0] == name:
- resultnodes.extend(self.matchnodes([x], nextnames))
- has_matched = True
- # XXX accept IDs that don't have "()" for class instances
- if not has_matched and len(rep.result) == 1 and x.name == "()":
- nextnames.insert(0, name)
- resultnodes.extend(self.matchnodes([x], nextnames))
- else:
- # report collection failures here to avoid failing to run some test
- # specified in the command line because the module could not be
- # imported (#134)
- node.ihook.pytest_collectreport(report=rep)
- return resultnodes
-
- def genitems(self, node):
- self.trace("genitems", node)
- if isinstance(node, nodes.Item):
- node.ihook.pytest_itemcollected(item=node)
- yield node
- else:
- assert isinstance(node, nodes.Collector)
- rep = collect_one_node(node)
- if rep.passed:
- for subnode in rep.result:
- for x in self.genitems(subnode):
- yield x
- node.ihook.pytest_collectreport(report=rep)
+ def _tryconvertpyarg(self, x):
+ """Convert a dotted module name to path."""
+ try:
+ with _patched_find_module():
+ loader = pkgutil.find_loader(x)
+ except ImportError:
+ return x
+ if loader is None:
+ return x
+ # This method is sometimes invoked when AssertionRewritingHook, which
+ # does not define a get_filename method, is already in place:
+ try:
+ with _patched_find_module():
+ path = loader.get_filename(x)
+ except AttributeError:
+ # Retrieve path from AssertionRewritingHook:
+ path = loader.modules[x][0].co_filename
+ if loader.is_package(x):
+ path = os.path.dirname(path)
+ return path
+
+ def _parsearg(self, arg):
+ """ return (fspath, names) tuple after checking the file exists. """
+ parts = str(arg).split("::")
+ if self.config.option.pyargs:
+ parts[0] = self._tryconvertpyarg(parts[0])
+ relpath = parts[0].replace("/", os.sep)
+ path = self.config.invocation_dir.join(relpath, abs=True)
+ if not path.check():
+ if self.config.option.pyargs:
+ raise UsageError(
+ "file or package not found: " + arg + " (missing __init__.py?)"
+ )
+ raise UsageError("file not found: " + arg)
+ parts[0] = path.realpath()
+ return parts
+
+ def matchnodes(self, matching, names):
+ self.trace("matchnodes", matching, names)
+ self.trace.root.indent += 1
+ nodes = self._matchnodes(matching, names)
+ num = len(nodes)
+ self.trace("matchnodes finished -> ", num, "nodes")
+ self.trace.root.indent -= 1
+ if num == 0:
+ raise NoMatch(matching, names[:1])
+ return nodes
+
+ def _matchnodes(self, matching, names):
+ if not matching or not names:
+ return matching
+ name = names[0]
+ assert name
+ nextnames = names[1:]
+ resultnodes = []
+ for node in matching:
+ if isinstance(node, nodes.Item):
+ if not names:
+ resultnodes.append(node)
+ continue
+ assert isinstance(node, nodes.Collector)
+ key = (type(node), node.nodeid)
+ if key in self._node_cache:
+ rep = self._node_cache[key]
+ else:
+ rep = collect_one_node(node)
+ self._node_cache[key] = rep
+ if rep.passed:
+ has_matched = False
+ for x in rep.result:
+ # TODO: remove parametrized workaround once collection structure contains parametrization
+ if x.name == name or x.name.split("[")[0] == name:
+ resultnodes.extend(self.matchnodes([x], nextnames))
+ has_matched = True
+ # XXX accept IDs that don't have "()" for class instances
+ if not has_matched and len(rep.result) == 1 and x.name == "()":
+ nextnames.insert(0, name)
+ resultnodes.extend(self.matchnodes([x], nextnames))
+ else:
+ # report collection failures here to avoid failing to run some test
+ # specified in the command line because the module could not be
+ # imported (#134)
+ node.ihook.pytest_collectreport(report=rep)
+ return resultnodes
+
+ def genitems(self, node):
+ self.trace("genitems", node)
+ if isinstance(node, nodes.Item):
+ node.ihook.pytest_itemcollected(item=node)
+ yield node
+ else:
+ assert isinstance(node, nodes.Collector)
+ rep = collect_one_node(node)
+ if rep.passed:
+ for subnode in rep.result:
+ for x in self.genitems(subnode):
+ yield x
+ node.ihook.pytest_collectreport(report=rep)
diff --git a/contrib/python/pytest/py2/_pytest/mark/__init__.py b/contrib/python/pytest/py2/_pytest/mark/__init__.py
index fc8ceb57ba..6bc22fe27d 100644
--- a/contrib/python/pytest/py2/_pytest/mark/__init__.py
+++ b/contrib/python/pytest/py2/_pytest/mark/__init__.py
@@ -1,166 +1,166 @@
# -*- coding: utf-8 -*-
-""" generic mechanism for marking and selecting python functions. """
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-from .legacy import matchkeyword
-from .legacy import matchmark
-from .structures import EMPTY_PARAMETERSET_OPTION
-from .structures import get_empty_parameterset_mark
-from .structures import Mark
-from .structures import MARK_GEN
-from .structures import MarkDecorator
-from .structures import MarkGenerator
-from .structures import ParameterSet
-from _pytest.config import UsageError
-
+""" generic mechanism for marking and selecting python functions. """
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+from .legacy import matchkeyword
+from .legacy import matchmark
+from .structures import EMPTY_PARAMETERSET_OPTION
+from .structures import get_empty_parameterset_mark
+from .structures import Mark
+from .structures import MARK_GEN
+from .structures import MarkDecorator
+from .structures import MarkGenerator
+from .structures import ParameterSet
+from _pytest.config import UsageError
+
__all__ = ["Mark", "MarkDecorator", "MarkGenerator", "get_empty_parameterset_mark"]
-
-
-def param(*values, **kw):
- """Specify a parameter in `pytest.mark.parametrize`_ calls or
- :ref:`parametrized fixtures <fixture-parametrize-marks>`.
-
- .. code-block:: python
-
- @pytest.mark.parametrize("test_input,expected", [
- ("3+5", 8),
- pytest.param("6*9", 42, marks=pytest.mark.xfail),
- ])
- def test_eval(test_input, expected):
- assert eval(test_input) == expected
-
- :param values: variable args of the values of the parameter set, in order.
- :keyword marks: a single mark or a list of marks to be applied to this parameter set.
- :keyword str id: the id to attribute to this parameter set.
- """
- return ParameterSet.param(*values, **kw)
-
-
-def pytest_addoption(parser):
- group = parser.getgroup("general")
- group._addoption(
- "-k",
- action="store",
- dest="keyword",
- default="",
- metavar="EXPRESSION",
- help="only run tests which match the given substring expression. "
- "An expression is a python evaluatable expression "
- "where all names are substring-matched against test names "
- "and their parent classes. Example: -k 'test_method or test_"
- "other' matches all test functions and classes whose name "
- "contains 'test_method' or 'test_other', while -k 'not test_method' "
- "matches those that don't contain 'test_method' in their names. "
+
+
+def param(*values, **kw):
+ """Specify a parameter in `pytest.mark.parametrize`_ calls or
+ :ref:`parametrized fixtures <fixture-parametrize-marks>`.
+
+ .. code-block:: python
+
+ @pytest.mark.parametrize("test_input,expected", [
+ ("3+5", 8),
+ pytest.param("6*9", 42, marks=pytest.mark.xfail),
+ ])
+ def test_eval(test_input, expected):
+ assert eval(test_input) == expected
+
+ :param values: variable args of the values of the parameter set, in order.
+ :keyword marks: a single mark or a list of marks to be applied to this parameter set.
+ :keyword str id: the id to attribute to this parameter set.
+ """
+ return ParameterSet.param(*values, **kw)
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("general")
+ group._addoption(
+ "-k",
+ action="store",
+ dest="keyword",
+ default="",
+ metavar="EXPRESSION",
+ help="only run tests which match the given substring expression. "
+ "An expression is a python evaluatable expression "
+ "where all names are substring-matched against test names "
+ "and their parent classes. Example: -k 'test_method or test_"
+ "other' matches all test functions and classes whose name "
+ "contains 'test_method' or 'test_other', while -k 'not test_method' "
+ "matches those that don't contain 'test_method' in their names. "
"-k 'not test_method and not test_other' will eliminate the matches. "
- "Additionally keywords are matched to classes and functions "
- "containing extra names in their 'extra_keyword_matches' set, "
- "as well as functions which have names assigned directly to them.",
- )
-
- group._addoption(
- "-m",
- action="store",
- dest="markexpr",
- default="",
- metavar="MARKEXPR",
- help="only run tests matching given mark expression. "
- "example: -m 'mark1 and not mark2'.",
- )
-
- group.addoption(
- "--markers",
- action="store_true",
- help="show markers (builtin, plugin and per-project ones).",
- )
-
- parser.addini("markers", "markers for test functions", "linelist")
- parser.addini(EMPTY_PARAMETERSET_OPTION, "default marker for empty parametersets")
-
-
-def pytest_cmdline_main(config):
- import _pytest.config
-
- if config.option.markers:
- config._do_configure()
- tw = _pytest.config.create_terminal_writer(config)
- for line in config.getini("markers"):
- parts = line.split(":", 1)
- name = parts[0]
- rest = parts[1] if len(parts) == 2 else ""
- tw.write("@pytest.mark.%s:" % name, bold=True)
- tw.line(rest)
- tw.line()
- config._ensure_unconfigure()
- return 0
-
-
-pytest_cmdline_main.tryfirst = True
-
-
-def deselect_by_keyword(items, config):
- keywordexpr = config.option.keyword.lstrip()
+ "Additionally keywords are matched to classes and functions "
+ "containing extra names in their 'extra_keyword_matches' set, "
+ "as well as functions which have names assigned directly to them.",
+ )
+
+ group._addoption(
+ "-m",
+ action="store",
+ dest="markexpr",
+ default="",
+ metavar="MARKEXPR",
+ help="only run tests matching given mark expression. "
+ "example: -m 'mark1 and not mark2'.",
+ )
+
+ group.addoption(
+ "--markers",
+ action="store_true",
+ help="show markers (builtin, plugin and per-project ones).",
+ )
+
+ parser.addini("markers", "markers for test functions", "linelist")
+ parser.addini(EMPTY_PARAMETERSET_OPTION, "default marker for empty parametersets")
+
+
+def pytest_cmdline_main(config):
+ import _pytest.config
+
+ if config.option.markers:
+ config._do_configure()
+ tw = _pytest.config.create_terminal_writer(config)
+ for line in config.getini("markers"):
+ parts = line.split(":", 1)
+ name = parts[0]
+ rest = parts[1] if len(parts) == 2 else ""
+ tw.write("@pytest.mark.%s:" % name, bold=True)
+ tw.line(rest)
+ tw.line()
+ config._ensure_unconfigure()
+ return 0
+
+
+pytest_cmdline_main.tryfirst = True
+
+
+def deselect_by_keyword(items, config):
+ keywordexpr = config.option.keyword.lstrip()
if not keywordexpr:
return
- if keywordexpr.startswith("-"):
- keywordexpr = "not " + keywordexpr[1:]
- selectuntil = False
- if keywordexpr[-1:] == ":":
- selectuntil = True
- keywordexpr = keywordexpr[:-1]
-
- remaining = []
- deselected = []
- for colitem in items:
- if keywordexpr and not matchkeyword(colitem, keywordexpr):
- deselected.append(colitem)
- else:
- if selectuntil:
- keywordexpr = None
- remaining.append(colitem)
-
- if deselected:
- config.hook.pytest_deselected(items=deselected)
- items[:] = remaining
-
-
-def deselect_by_mark(items, config):
- matchexpr = config.option.markexpr
- if not matchexpr:
- return
-
- remaining = []
- deselected = []
- for item in items:
- if matchmark(item, matchexpr):
- remaining.append(item)
- else:
- deselected.append(item)
-
- if deselected:
- config.hook.pytest_deselected(items=deselected)
- items[:] = remaining
-
-
-def pytest_collection_modifyitems(items, config):
- deselect_by_keyword(items, config)
- deselect_by_mark(items, config)
-
-
-def pytest_configure(config):
- config._old_mark_config = MARK_GEN._config
+ if keywordexpr.startswith("-"):
+ keywordexpr = "not " + keywordexpr[1:]
+ selectuntil = False
+ if keywordexpr[-1:] == ":":
+ selectuntil = True
+ keywordexpr = keywordexpr[:-1]
+
+ remaining = []
+ deselected = []
+ for colitem in items:
+ if keywordexpr and not matchkeyword(colitem, keywordexpr):
+ deselected.append(colitem)
+ else:
+ if selectuntil:
+ keywordexpr = None
+ remaining.append(colitem)
+
+ if deselected:
+ config.hook.pytest_deselected(items=deselected)
+ items[:] = remaining
+
+
+def deselect_by_mark(items, config):
+ matchexpr = config.option.markexpr
+ if not matchexpr:
+ return
+
+ remaining = []
+ deselected = []
+ for item in items:
+ if matchmark(item, matchexpr):
+ remaining.append(item)
+ else:
+ deselected.append(item)
+
+ if deselected:
+ config.hook.pytest_deselected(items=deselected)
+ items[:] = remaining
+
+
+def pytest_collection_modifyitems(items, config):
+ deselect_by_keyword(items, config)
+ deselect_by_mark(items, config)
+
+
+def pytest_configure(config):
+ config._old_mark_config = MARK_GEN._config
MARK_GEN._config = config
-
- empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION)
-
- if empty_parameterset not in ("skip", "xfail", "fail_at_collect", None, ""):
- raise UsageError(
- "{!s} must be one of skip, xfail or fail_at_collect"
- " but it is {!r}".format(EMPTY_PARAMETERSET_OPTION, empty_parameterset)
- )
-
-
-def pytest_unconfigure(config):
- MARK_GEN._config = getattr(config, "_old_mark_config", None)
+
+ empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION)
+
+ if empty_parameterset not in ("skip", "xfail", "fail_at_collect", None, ""):
+ raise UsageError(
+ "{!s} must be one of skip, xfail or fail_at_collect"
+ " but it is {!r}".format(EMPTY_PARAMETERSET_OPTION, empty_parameterset)
+ )
+
+
+def pytest_unconfigure(config):
+ MARK_GEN._config = getattr(config, "_old_mark_config", None)
diff --git a/contrib/python/pytest/py2/_pytest/mark/evaluate.py b/contrib/python/pytest/py2/_pytest/mark/evaluate.py
index a6ce2aad3e..506546e253 100644
--- a/contrib/python/pytest/py2/_pytest/mark/evaluate.py
+++ b/contrib/python/pytest/py2/_pytest/mark/evaluate.py
@@ -1,126 +1,126 @@
# -*- coding: utf-8 -*-
-import os
-import platform
-import sys
-import traceback
-
-import six
-
-from ..outcomes import fail
-from ..outcomes import TEST_OUTCOME
-
-
-def cached_eval(config, expr, d):
- if not hasattr(config, "_evalcache"):
- config._evalcache = {}
- try:
- return config._evalcache[expr]
- except KeyError:
- import _pytest._code
-
- exprcode = _pytest._code.compile(expr, mode="eval")
- config._evalcache[expr] = x = eval(exprcode, d)
- return x
-
-
-class MarkEvaluator(object):
- def __init__(self, item, name):
- self.item = item
- self._marks = None
- self._mark = None
- self._mark_name = name
-
- def __bool__(self):
- # dont cache here to prevent staleness
- return bool(self._get_marks())
-
- __nonzero__ = __bool__
-
- def wasvalid(self):
- return not hasattr(self, "exc")
-
- def _get_marks(self):
- return list(self.item.iter_markers(name=self._mark_name))
-
- def invalidraise(self, exc):
- raises = self.get("raises")
- if not raises:
- return
- return not isinstance(exc, raises)
-
- def istrue(self):
- try:
- return self._istrue()
- except TEST_OUTCOME:
- self.exc = sys.exc_info()
- if isinstance(self.exc[1], SyntaxError):
- msg = [" " * (self.exc[1].offset + 4) + "^"]
- msg.append("SyntaxError: invalid syntax")
- else:
- msg = traceback.format_exception_only(*self.exc[:2])
- fail(
- "Error evaluating %r expression\n"
- " %s\n"
- "%s" % (self._mark_name, self.expr, "\n".join(msg)),
- pytrace=False,
- )
-
- def _getglobals(self):
- d = {"os": os, "sys": sys, "platform": platform, "config": self.item.config}
- if hasattr(self.item, "obj"):
- d.update(self.item.obj.__globals__)
- return d
-
- def _istrue(self):
- if hasattr(self, "result"):
- return self.result
- self._marks = self._get_marks()
-
- if self._marks:
- self.result = False
- for mark in self._marks:
- self._mark = mark
- if "condition" in mark.kwargs:
- args = (mark.kwargs["condition"],)
- else:
- args = mark.args
-
- for expr in args:
- self.expr = expr
- if isinstance(expr, six.string_types):
- d = self._getglobals()
- result = cached_eval(self.item.config, expr, d)
- else:
- if "reason" not in mark.kwargs:
- # XXX better be checked at collection time
- msg = (
- "you need to specify reason=STRING "
- "when using booleans as conditions."
- )
- fail(msg)
- result = bool(expr)
- if result:
- self.result = True
- self.reason = mark.kwargs.get("reason", None)
- self.expr = expr
- return self.result
-
- if not args:
- self.result = True
- self.reason = mark.kwargs.get("reason", None)
- return self.result
- return False
-
- def get(self, attr, default=None):
- if self._mark is None:
- return default
- return self._mark.kwargs.get(attr, default)
-
- def getexplanation(self):
- expl = getattr(self, "reason", None) or self.get("reason", None)
- if not expl:
- if not hasattr(self, "expr"):
- return ""
- else:
- return "condition: " + str(self.expr)
- return expl
+import os
+import platform
+import sys
+import traceback
+
+import six
+
+from ..outcomes import fail
+from ..outcomes import TEST_OUTCOME
+
+
+def cached_eval(config, expr, d):
+ if not hasattr(config, "_evalcache"):
+ config._evalcache = {}
+ try:
+ return config._evalcache[expr]
+ except KeyError:
+ import _pytest._code
+
+ exprcode = _pytest._code.compile(expr, mode="eval")
+ config._evalcache[expr] = x = eval(exprcode, d)
+ return x
+
+
+class MarkEvaluator(object):
+ def __init__(self, item, name):
+ self.item = item
+ self._marks = None
+ self._mark = None
+ self._mark_name = name
+
+ def __bool__(self):
+ # dont cache here to prevent staleness
+ return bool(self._get_marks())
+
+ __nonzero__ = __bool__
+
+ def wasvalid(self):
+ return not hasattr(self, "exc")
+
+ def _get_marks(self):
+ return list(self.item.iter_markers(name=self._mark_name))
+
+ def invalidraise(self, exc):
+ raises = self.get("raises")
+ if not raises:
+ return
+ return not isinstance(exc, raises)
+
+ def istrue(self):
+ try:
+ return self._istrue()
+ except TEST_OUTCOME:
+ self.exc = sys.exc_info()
+ if isinstance(self.exc[1], SyntaxError):
+ msg = [" " * (self.exc[1].offset + 4) + "^"]
+ msg.append("SyntaxError: invalid syntax")
+ else:
+ msg = traceback.format_exception_only(*self.exc[:2])
+ fail(
+ "Error evaluating %r expression\n"
+ " %s\n"
+ "%s" % (self._mark_name, self.expr, "\n".join(msg)),
+ pytrace=False,
+ )
+
+ def _getglobals(self):
+ d = {"os": os, "sys": sys, "platform": platform, "config": self.item.config}
+ if hasattr(self.item, "obj"):
+ d.update(self.item.obj.__globals__)
+ return d
+
+ def _istrue(self):
+ if hasattr(self, "result"):
+ return self.result
+ self._marks = self._get_marks()
+
+ if self._marks:
+ self.result = False
+ for mark in self._marks:
+ self._mark = mark
+ if "condition" in mark.kwargs:
+ args = (mark.kwargs["condition"],)
+ else:
+ args = mark.args
+
+ for expr in args:
+ self.expr = expr
+ if isinstance(expr, six.string_types):
+ d = self._getglobals()
+ result = cached_eval(self.item.config, expr, d)
+ else:
+ if "reason" not in mark.kwargs:
+ # XXX better be checked at collection time
+ msg = (
+ "you need to specify reason=STRING "
+ "when using booleans as conditions."
+ )
+ fail(msg)
+ result = bool(expr)
+ if result:
+ self.result = True
+ self.reason = mark.kwargs.get("reason", None)
+ self.expr = expr
+ return self.result
+
+ if not args:
+ self.result = True
+ self.reason = mark.kwargs.get("reason", None)
+ return self.result
+ return False
+
+ def get(self, attr, default=None):
+ if self._mark is None:
+ return default
+ return self._mark.kwargs.get(attr, default)
+
+ def getexplanation(self):
+ expl = getattr(self, "reason", None) or self.get("reason", None)
+ if not expl:
+ if not hasattr(self, "expr"):
+ return ""
+ else:
+ return "condition: " + str(self.expr)
+ return expl
diff --git a/contrib/python/pytest/py2/_pytest/mark/legacy.py b/contrib/python/pytest/py2/_pytest/mark/legacy.py
index 86dab370e9..c56482f14d 100644
--- a/contrib/python/pytest/py2/_pytest/mark/legacy.py
+++ b/contrib/python/pytest/py2/_pytest/mark/legacy.py
@@ -1,103 +1,103 @@
# -*- coding: utf-8 -*-
-"""
-this is a place where we put datastructures used by legacy apis
-we hope ot remove
-"""
-import keyword
-
-import attr
-
-from _pytest.config import UsageError
-
-
-@attr.s
-class MarkMapping(object):
- """Provides a local mapping for markers where item access
- resolves to True if the marker is present. """
-
- own_mark_names = attr.ib()
-
- @classmethod
- def from_item(cls, item):
- mark_names = {mark.name for mark in item.iter_markers()}
- return cls(mark_names)
-
- def __getitem__(self, name):
- return name in self.own_mark_names
-
-
-class KeywordMapping(object):
- """Provides a local mapping for keywords.
- Given a list of names, map any substring of one of these names to True.
- """
-
- def __init__(self, names):
- self._names = names
-
- @classmethod
- def from_item(cls, item):
- mapped_names = set()
-
- # Add the names of the current item and any parent items
- import pytest
-
- for item in item.listchain():
- if not isinstance(item, pytest.Instance):
- mapped_names.add(item.name)
-
- # Add the names added as extra keywords to current or parent items
+"""
+this is a place where we put datastructures used by legacy apis
+we hope ot remove
+"""
+import keyword
+
+import attr
+
+from _pytest.config import UsageError
+
+
+@attr.s
+class MarkMapping(object):
+ """Provides a local mapping for markers where item access
+ resolves to True if the marker is present. """
+
+ own_mark_names = attr.ib()
+
+ @classmethod
+ def from_item(cls, item):
+ mark_names = {mark.name for mark in item.iter_markers()}
+ return cls(mark_names)
+
+ def __getitem__(self, name):
+ return name in self.own_mark_names
+
+
+class KeywordMapping(object):
+ """Provides a local mapping for keywords.
+ Given a list of names, map any substring of one of these names to True.
+ """
+
+ def __init__(self, names):
+ self._names = names
+
+ @classmethod
+ def from_item(cls, item):
+ mapped_names = set()
+
+ # Add the names of the current item and any parent items
+ import pytest
+
+ for item in item.listchain():
+ if not isinstance(item, pytest.Instance):
+ mapped_names.add(item.name)
+
+ # Add the names added as extra keywords to current or parent items
mapped_names.update(item.listextrakeywords())
-
- # Add the names attached to the current function through direct assignment
- if hasattr(item, "function"):
+
+ # Add the names attached to the current function through direct assignment
+ if hasattr(item, "function"):
mapped_names.update(item.function.__dict__)
-
+
# add the markers to the keywords as we no longer handle them correctly
mapped_names.update(mark.name for mark in item.iter_markers())
- return cls(mapped_names)
-
- def __getitem__(self, subname):
- for name in self._names:
- if subname in name:
- return True
- return False
-
-
-python_keywords_allowed_list = ["or", "and", "not"]
-
-
-def matchmark(colitem, markexpr):
- """Tries to match on any marker names, attached to the given colitem."""
- try:
- return eval(markexpr, {}, MarkMapping.from_item(colitem))
- except SyntaxError as e:
- raise SyntaxError(str(e) + "\nMarker expression must be valid Python!")
-
-
-def matchkeyword(colitem, keywordexpr):
- """Tries to match given keyword expression to given collector item.
-
- Will match on the name of colitem, including the names of its parents.
- Only matches names of items which are either a :class:`Class` or a
- :class:`Function`.
- Additionally, matches on names in the 'extra_keyword_matches' set of
- any item, as well as names directly assigned to test functions.
- """
- mapping = KeywordMapping.from_item(colitem)
- if " " not in keywordexpr:
- # special case to allow for simple "-k pass" and "-k 1.3"
- return mapping[keywordexpr]
- elif keywordexpr.startswith("not ") and " " not in keywordexpr[4:]:
- return not mapping[keywordexpr[4:]]
- for kwd in keywordexpr.split():
- if keyword.iskeyword(kwd) and kwd not in python_keywords_allowed_list:
- raise UsageError(
- "Python keyword '{}' not accepted in expressions passed to '-k'".format(
- kwd
- )
- )
- try:
- return eval(keywordexpr, {}, mapping)
- except SyntaxError:
- raise UsageError("Wrong expression passed to '-k': {}".format(keywordexpr))
+ return cls(mapped_names)
+
+ def __getitem__(self, subname):
+ for name in self._names:
+ if subname in name:
+ return True
+ return False
+
+
+python_keywords_allowed_list = ["or", "and", "not"]
+
+
+def matchmark(colitem, markexpr):
+ """Tries to match on any marker names, attached to the given colitem."""
+ try:
+ return eval(markexpr, {}, MarkMapping.from_item(colitem))
+ except SyntaxError as e:
+ raise SyntaxError(str(e) + "\nMarker expression must be valid Python!")
+
+
+def matchkeyword(colitem, keywordexpr):
+ """Tries to match given keyword expression to given collector item.
+
+ Will match on the name of colitem, including the names of its parents.
+ Only matches names of items which are either a :class:`Class` or a
+ :class:`Function`.
+ Additionally, matches on names in the 'extra_keyword_matches' set of
+ any item, as well as names directly assigned to test functions.
+ """
+ mapping = KeywordMapping.from_item(colitem)
+ if " " not in keywordexpr:
+ # special case to allow for simple "-k pass" and "-k 1.3"
+ return mapping[keywordexpr]
+ elif keywordexpr.startswith("not ") and " " not in keywordexpr[4:]:
+ return not mapping[keywordexpr[4:]]
+ for kwd in keywordexpr.split():
+ if keyword.iskeyword(kwd) and kwd not in python_keywords_allowed_list:
+ raise UsageError(
+ "Python keyword '{}' not accepted in expressions passed to '-k'".format(
+ kwd
+ )
+ )
+ try:
+ return eval(keywordexpr, {}, mapping)
+ except SyntaxError:
+ raise UsageError("Wrong expression passed to '-k': {}".format(keywordexpr))
diff --git a/contrib/python/pytest/py2/_pytest/mark/structures.py b/contrib/python/pytest/py2/_pytest/mark/structures.py
index b9b0e31634..aaebe927be 100644
--- a/contrib/python/pytest/py2/_pytest/mark/structures.py
+++ b/contrib/python/pytest/py2/_pytest/mark/structures.py
@@ -1,76 +1,76 @@
# -*- coding: utf-8 -*-
-import inspect
-import warnings
-from collections import namedtuple
-from operator import attrgetter
-
-import attr
+import inspect
+import warnings
+from collections import namedtuple
+from operator import attrgetter
+
+import attr
import six
-
+
from ..compat import ascii_escaped
from ..compat import ATTRS_EQ_FIELD
-from ..compat import getfslineno
-from ..compat import MappingMixin
-from ..compat import NOTSET
+from ..compat import getfslineno
+from ..compat import MappingMixin
+from ..compat import NOTSET
from _pytest.deprecated import PYTEST_PARAM_UNKNOWN_KWARGS
-from _pytest.outcomes import fail
+from _pytest.outcomes import fail
from _pytest.warning_types import PytestUnknownMarkWarning
-
-EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark"
-
-
-def alias(name, warning=None):
- getter = attrgetter(name)
-
- def warned(self):
- warnings.warn(warning, stacklevel=2)
- return getter(self)
-
- return property(getter if warning is None else warned, doc="alias for " + name)
-
-
-def istestfunc(func):
- return (
- hasattr(func, "__call__")
- and getattr(func, "__name__", "<lambda>") != "<lambda>"
- )
-
-
-def get_empty_parameterset_mark(config, argnames, func):
- from ..nodes import Collector
-
- requested_mark = config.getini(EMPTY_PARAMETERSET_OPTION)
- if requested_mark in ("", None, "skip"):
- mark = MARK_GEN.skip
- elif requested_mark == "xfail":
- mark = MARK_GEN.xfail(run=False)
- elif requested_mark == "fail_at_collect":
- f_name = func.__name__
- _, lineno = getfslineno(func)
- raise Collector.CollectError(
+
+EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark"
+
+
+def alias(name, warning=None):
+ getter = attrgetter(name)
+
+ def warned(self):
+ warnings.warn(warning, stacklevel=2)
+ return getter(self)
+
+ return property(getter if warning is None else warned, doc="alias for " + name)
+
+
+def istestfunc(func):
+ return (
+ hasattr(func, "__call__")
+ and getattr(func, "__name__", "<lambda>") != "<lambda>"
+ )
+
+
+def get_empty_parameterset_mark(config, argnames, func):
+ from ..nodes import Collector
+
+ requested_mark = config.getini(EMPTY_PARAMETERSET_OPTION)
+ if requested_mark in ("", None, "skip"):
+ mark = MARK_GEN.skip
+ elif requested_mark == "xfail":
+ mark = MARK_GEN.xfail(run=False)
+ elif requested_mark == "fail_at_collect":
+ f_name = func.__name__
+ _, lineno = getfslineno(func)
+ raise Collector.CollectError(
"Empty parameter set in '%s' at line %d" % (f_name, lineno + 1)
- )
- else:
- raise LookupError(requested_mark)
- fs, lineno = getfslineno(func)
- reason = "got empty parameter set %r, function %s at %s:%d" % (
- argnames,
- func.__name__,
- fs,
- lineno,
- )
- return mark(reason=reason)
-
-
-class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
- @classmethod
+ )
+ else:
+ raise LookupError(requested_mark)
+ fs, lineno = getfslineno(func)
+ reason = "got empty parameter set %r, function %s at %s:%d" % (
+ argnames,
+ func.__name__,
+ fs,
+ lineno,
+ )
+ return mark(reason=reason)
+
+
+class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
+ @classmethod
def param(cls, *values, **kwargs):
marks = kwargs.pop("marks", ())
- if isinstance(marks, MarkDecorator):
- marks = (marks,)
- else:
- assert isinstance(marks, (tuple, list, set))
-
+ if isinstance(marks, MarkDecorator):
+ marks = (marks,)
+ else:
+ assert isinstance(marks, (tuple, list, set))
+
id_ = kwargs.pop("id", None)
if id_ is not None:
if not isinstance(id_, six.string_types):
@@ -78,63 +78,63 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
"Expected id to be a string, got {}: {!r}".format(type(id_), id_)
)
id_ = ascii_escaped(id_)
-
+
if kwargs:
warnings.warn(
PYTEST_PARAM_UNKNOWN_KWARGS.format(args=sorted(kwargs)), stacklevel=3
)
- return cls(values, marks, id_)
-
- @classmethod
+ return cls(values, marks, id_)
+
+ @classmethod
def extract_from(cls, parameterset, force_tuple=False):
- """
- :param parameterset:
- a legacy style parameterset that may or may not be a tuple,
- and may or may not be wrapped into a mess of mark objects
-
+ """
+ :param parameterset:
+ a legacy style parameterset that may or may not be a tuple,
+ and may or may not be wrapped into a mess of mark objects
+
:param force_tuple:
- enforce tuple wrapping so single argument tuple values
- don't get decomposed and break tests
- """
-
- if isinstance(parameterset, cls):
- return parameterset
+ enforce tuple wrapping so single argument tuple values
+ don't get decomposed and break tests
+ """
+
+ if isinstance(parameterset, cls):
+ return parameterset
if force_tuple:
- return cls.param(parameterset)
+ return cls.param(parameterset)
else:
return cls(parameterset, marks=[], id=None)
-
+
@staticmethod
def _parse_parametrize_args(argnames, argvalues, *args, **kwargs):
- if not isinstance(argnames, (tuple, list)):
- argnames = [x.strip() for x in argnames.split(",") if x.strip()]
- force_tuple = len(argnames) == 1
- else:
- force_tuple = False
+ if not isinstance(argnames, (tuple, list)):
+ argnames = [x.strip() for x in argnames.split(",") if x.strip()]
+ force_tuple = len(argnames) == 1
+ else:
+ force_tuple = False
return argnames, force_tuple
@staticmethod
def _parse_parametrize_parameters(argvalues, force_tuple):
return [
ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues
- ]
+ ]
@classmethod
def _for_parametrize(cls, argnames, argvalues, func, config, function_definition):
argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues)
parameters = cls._parse_parametrize_parameters(argvalues, force_tuple)
- del argvalues
-
- if parameters:
- # check all parameter sets have the correct number of values
- for param in parameters:
- if len(param.values) != len(argnames):
+ del argvalues
+
+ if parameters:
+ # check all parameter sets have the correct number of values
+ for param in parameters:
+ if len(param.values) != len(argnames):
msg = (
'{nodeid}: in "parametrize" the number of names ({names_len}):\n'
" {names}\n"
"must be equal to the number of values ({values_len}):\n"
" {values}"
- )
+ )
fail(
msg.format(
nodeid=function_definition.nodeid,
@@ -145,130 +145,130 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
),
pytrace=False,
)
- else:
- # empty parameter set (likely computed at runtime): create a single
+ else:
+ # empty parameter set (likely computed at runtime): create a single
# parameter set with NOTSET values, with the "empty parameter set" mark applied to it
- mark = get_empty_parameterset_mark(config, argnames, func)
- parameters.append(
- ParameterSet(values=(NOTSET,) * len(argnames), marks=[mark], id=None)
- )
- return argnames, parameters
-
-
-@attr.s(frozen=True)
-class Mark(object):
- #: name of the mark
- name = attr.ib(type=str)
- #: positional arguments of the mark decorator
+ mark = get_empty_parameterset_mark(config, argnames, func)
+ parameters.append(
+ ParameterSet(values=(NOTSET,) * len(argnames), marks=[mark], id=None)
+ )
+ return argnames, parameters
+
+
+@attr.s(frozen=True)
+class Mark(object):
+ #: name of the mark
+ name = attr.ib(type=str)
+ #: positional arguments of the mark decorator
args = attr.ib() # List[object]
- #: keyword arguments of the mark decorator
+ #: keyword arguments of the mark decorator
kwargs = attr.ib() # Dict[str, object]
-
- def combined_with(self, other):
- """
- :param other: the mark to combine with
- :type other: Mark
- :rtype: Mark
-
+
+ def combined_with(self, other):
+ """
+ :param other: the mark to combine with
+ :type other: Mark
+ :rtype: Mark
+
combines by appending args and merging the mappings
- """
- assert self.name == other.name
- return Mark(
- self.name, self.args + other.args, dict(self.kwargs, **other.kwargs)
- )
-
-
-@attr.s
-class MarkDecorator(object):
- """ A decorator for test functions and test classes. When applied
- it will create :class:`MarkInfo` objects which may be
- :ref:`retrieved by hooks as item keywords <excontrolskip>`.
- MarkDecorator instances are often created like this::
-
- mark1 = pytest.mark.NAME # simple MarkDecorator
- mark2 = pytest.mark.NAME(name1=value) # parametrized MarkDecorator
-
- and can then be applied as decorators to test functions::
-
- @mark2
- def test_function():
- pass
-
- When a MarkDecorator instance is called it does the following:
- 1. If called with a single class as its only positional argument and no
- additional keyword arguments, it attaches itself to the class so it
- gets applied automatically to all test cases found in that class.
- 2. If called with a single function as its only positional argument and
- no additional keyword arguments, it attaches a MarkInfo object to the
- function, containing all the arguments already stored internally in
- the MarkDecorator.
- 3. When called in any other case, it performs a 'fake construction' call,
- i.e. it returns a new MarkDecorator instance with the original
- MarkDecorator's content updated with the arguments passed to this
- call.
-
- Note: The rules above prevent MarkDecorator objects from storing only a
- single function or class reference as their positional argument with no
- additional keyword or positional arguments.
-
- """
-
- mark = attr.ib(validator=attr.validators.instance_of(Mark))
-
- name = alias("mark.name")
- args = alias("mark.args")
- kwargs = alias("mark.kwargs")
-
- @property
- def markname(self):
- return self.name # for backward-compat (2.4.1 had this attr)
-
- def __eq__(self, other):
- return self.mark == other.mark if isinstance(other, MarkDecorator) else False
-
- def __repr__(self):
- return "<MarkDecorator %r>" % (self.mark,)
-
- def with_args(self, *args, **kwargs):
- """ return a MarkDecorator with extra arguments added
-
- unlike call this can be used even if the sole argument is a callable/class
-
- :return: MarkDecorator
- """
-
- mark = Mark(self.name, args, kwargs)
- return self.__class__(self.mark.combined_with(mark))
-
- def __call__(self, *args, **kwargs):
- """ if passed a single callable argument: decorate it with mark info.
- otherwise add *args/**kwargs in-place to mark information. """
- if args and not kwargs:
- func = args[0]
- is_class = inspect.isclass(func)
- if len(args) == 1 and (istestfunc(func) or is_class):
+ """
+ assert self.name == other.name
+ return Mark(
+ self.name, self.args + other.args, dict(self.kwargs, **other.kwargs)
+ )
+
+
+@attr.s
+class MarkDecorator(object):
+ """ A decorator for test functions and test classes. When applied
+ it will create :class:`MarkInfo` objects which may be
+ :ref:`retrieved by hooks as item keywords <excontrolskip>`.
+ MarkDecorator instances are often created like this::
+
+ mark1 = pytest.mark.NAME # simple MarkDecorator
+ mark2 = pytest.mark.NAME(name1=value) # parametrized MarkDecorator
+
+ and can then be applied as decorators to test functions::
+
+ @mark2
+ def test_function():
+ pass
+
+ When a MarkDecorator instance is called it does the following:
+ 1. If called with a single class as its only positional argument and no
+ additional keyword arguments, it attaches itself to the class so it
+ gets applied automatically to all test cases found in that class.
+ 2. If called with a single function as its only positional argument and
+ no additional keyword arguments, it attaches a MarkInfo object to the
+ function, containing all the arguments already stored internally in
+ the MarkDecorator.
+ 3. When called in any other case, it performs a 'fake construction' call,
+ i.e. it returns a new MarkDecorator instance with the original
+ MarkDecorator's content updated with the arguments passed to this
+ call.
+
+ Note: The rules above prevent MarkDecorator objects from storing only a
+ single function or class reference as their positional argument with no
+ additional keyword or positional arguments.
+
+ """
+
+ mark = attr.ib(validator=attr.validators.instance_of(Mark))
+
+ name = alias("mark.name")
+ args = alias("mark.args")
+ kwargs = alias("mark.kwargs")
+
+ @property
+ def markname(self):
+ return self.name # for backward-compat (2.4.1 had this attr)
+
+ def __eq__(self, other):
+ return self.mark == other.mark if isinstance(other, MarkDecorator) else False
+
+ def __repr__(self):
+ return "<MarkDecorator %r>" % (self.mark,)
+
+ def with_args(self, *args, **kwargs):
+ """ return a MarkDecorator with extra arguments added
+
+ unlike call this can be used even if the sole argument is a callable/class
+
+ :return: MarkDecorator
+ """
+
+ mark = Mark(self.name, args, kwargs)
+ return self.__class__(self.mark.combined_with(mark))
+
+ def __call__(self, *args, **kwargs):
+ """ if passed a single callable argument: decorate it with mark info.
+ otherwise add *args/**kwargs in-place to mark information. """
+ if args and not kwargs:
+ func = args[0]
+ is_class = inspect.isclass(func)
+ if len(args) == 1 and (istestfunc(func) or is_class):
store_mark(func, self.mark)
- return func
- return self.with_args(*args, **kwargs)
-
-
-def get_unpacked_marks(obj):
- """
- obtain the unpacked marks that are stored on an object
- """
- mark_list = getattr(obj, "pytestmark", [])
- if not isinstance(mark_list, list):
- mark_list = [mark_list]
- return normalize_mark_list(mark_list)
-
-
-def normalize_mark_list(mark_list):
- """
- normalizes marker decorating helpers to mark objects
-
- :type mark_list: List[Union[Mark, Markdecorator]]
- :rtype: List[Mark]
- """
+ return func
+ return self.with_args(*args, **kwargs)
+
+
+def get_unpacked_marks(obj):
+ """
+ obtain the unpacked marks that are stored on an object
+ """
+ mark_list = getattr(obj, "pytestmark", [])
+ if not isinstance(mark_list, list):
+ mark_list = [mark_list]
+ return normalize_mark_list(mark_list)
+
+
+def normalize_mark_list(mark_list):
+ """
+ normalizes marker decorating helpers to mark objects
+
+ :type mark_list: List[Union[Mark, Markdecorator]]
+ :rtype: List[Mark]
+ """
extracted = [
getattr(mark, "mark", mark) for mark in mark_list
] # unpack MarkDecorator
@@ -276,38 +276,38 @@ def normalize_mark_list(mark_list):
if not isinstance(mark, Mark):
raise TypeError("got {!r} instead of Mark".format(mark))
return [x for x in extracted if isinstance(x, Mark)]
-
-
-def store_mark(obj, mark):
- """store a Mark on an object
- this is used to implement the Mark declarations/decorators correctly
- """
- assert isinstance(mark, Mark), mark
- # always reassign name to avoid updating pytestmark
- # in a reference that was only borrowed
- obj.pytestmark = get_unpacked_marks(obj) + [mark]
-
-
-class MarkGenerator(object):
- """ Factory for :class:`MarkDecorator` objects - exposed as
- a ``pytest.mark`` singleton instance. Example::
-
- import pytest
- @pytest.mark.slowtest
- def test_function():
- pass
-
- will set a 'slowtest' :class:`MarkInfo` object
- on the ``test_function`` object. """
-
- _config = None
+
+
+def store_mark(obj, mark):
+ """store a Mark on an object
+ this is used to implement the Mark declarations/decorators correctly
+ """
+ assert isinstance(mark, Mark), mark
+ # always reassign name to avoid updating pytestmark
+ # in a reference that was only borrowed
+ obj.pytestmark = get_unpacked_marks(obj) + [mark]
+
+
+class MarkGenerator(object):
+ """ Factory for :class:`MarkDecorator` objects - exposed as
+ a ``pytest.mark`` singleton instance. Example::
+
+ import pytest
+ @pytest.mark.slowtest
+ def test_function():
+ pass
+
+ will set a 'slowtest' :class:`MarkInfo` object
+ on the ``test_function`` object. """
+
+ _config = None
_markers = set()
-
- def __getattr__(self, name):
- if name[0] == "_":
- raise AttributeError("Marker name must NOT start with underscore")
- if self._config is not None:
+ def __getattr__(self, name):
+ if name[0] == "_":
+ raise AttributeError("Marker name must NOT start with underscore")
+
+ if self._config is not None:
# We store a set of markers as a performance optimisation - if a mark
# name is in the set we definitely know it, but a mark may be known and
# not in the set. We therefore start by updating the set!
@@ -338,76 +338,76 @@ class MarkGenerator(object):
PytestUnknownMarkWarning,
)
- return MarkDecorator(Mark(name, (), {}))
-
-
-MARK_GEN = MarkGenerator()
-
-
-class NodeKeywords(MappingMixin):
- def __init__(self, node):
- self.node = node
- self.parent = node.parent
- self._markers = {node.name: True}
-
- def __getitem__(self, key):
- try:
- return self._markers[key]
- except KeyError:
- if self.parent is None:
- raise
- return self.parent.keywords[key]
-
- def __setitem__(self, key, value):
- self._markers[key] = value
-
- def __delitem__(self, key):
- raise ValueError("cannot delete key in keywords dict")
-
- def __iter__(self):
- seen = self._seen()
- return iter(seen)
-
- def _seen(self):
- seen = set(self._markers)
- if self.parent is not None:
- seen.update(self.parent.keywords)
- return seen
-
- def __len__(self):
- return len(self._seen())
-
- def __repr__(self):
- return "<NodeKeywords for node %s>" % (self.node,)
-
-
+ return MarkDecorator(Mark(name, (), {}))
+
+
+MARK_GEN = MarkGenerator()
+
+
+class NodeKeywords(MappingMixin):
+ def __init__(self, node):
+ self.node = node
+ self.parent = node.parent
+ self._markers = {node.name: True}
+
+ def __getitem__(self, key):
+ try:
+ return self._markers[key]
+ except KeyError:
+ if self.parent is None:
+ raise
+ return self.parent.keywords[key]
+
+ def __setitem__(self, key, value):
+ self._markers[key] = value
+
+ def __delitem__(self, key):
+ raise ValueError("cannot delete key in keywords dict")
+
+ def __iter__(self):
+ seen = self._seen()
+ return iter(seen)
+
+ def _seen(self):
+ seen = set(self._markers)
+ if self.parent is not None:
+ seen.update(self.parent.keywords)
+ return seen
+
+ def __len__(self):
+ return len(self._seen())
+
+ def __repr__(self):
+ return "<NodeKeywords for node %s>" % (self.node,)
+
+
# mypy cannot find this overload, remove when on attrs>=19.2
@attr.s(hash=False, **{ATTRS_EQ_FIELD: False}) # type: ignore
-class NodeMarkers(object):
- """
- internal structure for storing marks belonging to a node
-
- ..warning::
-
- unstable api
-
- """
-
- own_markers = attr.ib(default=attr.Factory(list))
-
- def update(self, add_markers):
- """update the own markers
- """
- self.own_markers.extend(add_markers)
-
- def find(self, name):
- """
- find markers in own nodes or parent nodes
- needs a better place
- """
- for mark in self.own_markers:
- if mark.name == name:
- yield mark
-
- def __iter__(self):
- return iter(self.own_markers)
+class NodeMarkers(object):
+ """
+ internal structure for storing marks belonging to a node
+
+ ..warning::
+
+ unstable api
+
+ """
+
+ own_markers = attr.ib(default=attr.Factory(list))
+
+ def update(self, add_markers):
+ """update the own markers
+ """
+ self.own_markers.extend(add_markers)
+
+ def find(self, name):
+ """
+ find markers in own nodes or parent nodes
+ needs a better place
+ """
+ for mark in self.own_markers:
+ if mark.name == name:
+ yield mark
+
+ def __iter__(self):
+ return iter(self.own_markers)
diff --git a/contrib/python/pytest/py2/_pytest/monkeypatch.py b/contrib/python/pytest/py2/_pytest/monkeypatch.py
index 7a82faad82..e8671b0c70 100644
--- a/contrib/python/pytest/py2/_pytest/monkeypatch.py
+++ b/contrib/python/pytest/py2/_pytest/monkeypatch.py
@@ -1,274 +1,274 @@
# -*- coding: utf-8 -*-
-""" monkeypatching and mocking functionality. """
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import os
-import re
-import sys
-import warnings
-from contextlib import contextmanager
-
-import six
-
-import pytest
-from _pytest.fixtures import fixture
-from _pytest.pathlib import Path
-
-RE_IMPORT_ERROR_NAME = re.compile(r"^No module named (.*)$")
-
-
-@fixture
-def monkeypatch():
- """The returned ``monkeypatch`` fixture provides these
- helper methods to modify objects, dictionaries or os.environ::
-
- monkeypatch.setattr(obj, name, value, raising=True)
- monkeypatch.delattr(obj, name, raising=True)
- monkeypatch.setitem(mapping, name, value)
- monkeypatch.delitem(obj, name, raising=True)
- monkeypatch.setenv(name, value, prepend=False)
- monkeypatch.delenv(name, raising=True)
- monkeypatch.syspath_prepend(path)
- monkeypatch.chdir(path)
-
- All modifications will be undone after the requesting
- test function or fixture has finished. The ``raising``
- parameter determines if a KeyError or AttributeError
- will be raised if the set/deletion operation has no target.
- """
- mpatch = MonkeyPatch()
- yield mpatch
- mpatch.undo()
-
-
-def resolve(name):
- # simplified from zope.dottedname
- parts = name.split(".")
-
- used = parts.pop(0)
- found = __import__(used)
- for part in parts:
- used += "." + part
- try:
- found = getattr(found, part)
- except AttributeError:
- pass
- else:
- continue
- # we use explicit un-nesting of the handling block in order
- # to avoid nested exceptions on python 3
- try:
- __import__(used)
- except ImportError as ex:
- # str is used for py2 vs py3
- expected = str(ex).split()[-1]
- if expected == used:
- raise
- else:
- raise ImportError("import error in %s: %s" % (used, ex))
- found = annotated_getattr(found, part, used)
- return found
-
-
-def annotated_getattr(obj, name, ann):
- try:
- obj = getattr(obj, name)
- except AttributeError:
- raise AttributeError(
- "%r object at %s has no attribute %r" % (type(obj).__name__, ann, name)
- )
- return obj
-
-
-def derive_importpath(import_path, raising):
- if not isinstance(import_path, six.string_types) or "." not in import_path:
- raise TypeError("must be absolute import path string, not %r" % (import_path,))
- module, attr = import_path.rsplit(".", 1)
- target = resolve(module)
- if raising:
- annotated_getattr(target, attr, ann=module)
- return attr, target
-
-
-class Notset(object):
- def __repr__(self):
- return "<notset>"
-
-
-notset = Notset()
-
-
-class MonkeyPatch(object):
- """ Object returned by the ``monkeypatch`` fixture keeping a record of setattr/item/env/syspath changes.
- """
-
- def __init__(self):
- self._setattr = []
- self._setitem = []
- self._cwd = None
- self._savesyspath = None
-
- @contextmanager
- def context(self):
- """
- Context manager that returns a new :class:`MonkeyPatch` object which
- undoes any patching done inside the ``with`` block upon exit:
-
- .. code-block:: python
-
- import functools
- def test_partial(monkeypatch):
- with monkeypatch.context() as m:
- m.setattr(functools, "partial", 3)
-
- Useful in situations where it is desired to undo some patches before the test ends,
- such as mocking ``stdlib`` functions that might break pytest itself if mocked (for examples
- of this see `#3290 <https://github.com/pytest-dev/pytest/issues/3290>`_.
- """
- m = MonkeyPatch()
- try:
- yield m
- finally:
- m.undo()
-
- def setattr(self, target, name, value=notset, raising=True):
- """ Set attribute value on target, memorizing the old value.
- By default raise AttributeError if the attribute did not exist.
-
- For convenience you can specify a string as ``target`` which
- will be interpreted as a dotted import path, with the last part
- being the attribute name. Example:
- ``monkeypatch.setattr("os.getcwd", lambda: "/")``
- would set the ``getcwd`` function of the ``os`` module.
-
- The ``raising`` value determines if the setattr should fail
- if the attribute is not already present (defaults to True
- which means it will raise).
- """
- __tracebackhide__ = True
- import inspect
-
- if value is notset:
- if not isinstance(target, six.string_types):
- raise TypeError(
- "use setattr(target, name, value) or "
- "setattr(target, value) with target being a dotted "
- "import string"
- )
- value = name
- name, target = derive_importpath(target, raising)
-
- oldval = getattr(target, name, notset)
- if raising and oldval is notset:
- raise AttributeError("%r has no attribute %r" % (target, name))
-
- # avoid class descriptors like staticmethod/classmethod
- if inspect.isclass(target):
- oldval = target.__dict__.get(name, notset)
- self._setattr.append((target, name, oldval))
- setattr(target, name, value)
-
- def delattr(self, target, name=notset, raising=True):
- """ Delete attribute ``name`` from ``target``, by default raise
- AttributeError it the attribute did not previously exist.
-
- If no ``name`` is specified and ``target`` is a string
- it will be interpreted as a dotted import path with the
- last part being the attribute name.
-
- If ``raising`` is set to False, no exception will be raised if the
- attribute is missing.
- """
- __tracebackhide__ = True
+""" monkeypatching and mocking functionality. """
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import os
+import re
+import sys
+import warnings
+from contextlib import contextmanager
+
+import six
+
+import pytest
+from _pytest.fixtures import fixture
+from _pytest.pathlib import Path
+
+RE_IMPORT_ERROR_NAME = re.compile(r"^No module named (.*)$")
+
+
+@fixture
+def monkeypatch():
+ """The returned ``monkeypatch`` fixture provides these
+ helper methods to modify objects, dictionaries or os.environ::
+
+ monkeypatch.setattr(obj, name, value, raising=True)
+ monkeypatch.delattr(obj, name, raising=True)
+ monkeypatch.setitem(mapping, name, value)
+ monkeypatch.delitem(obj, name, raising=True)
+ monkeypatch.setenv(name, value, prepend=False)
+ monkeypatch.delenv(name, raising=True)
+ monkeypatch.syspath_prepend(path)
+ monkeypatch.chdir(path)
+
+ All modifications will be undone after the requesting
+ test function or fixture has finished. The ``raising``
+ parameter determines if a KeyError or AttributeError
+ will be raised if the set/deletion operation has no target.
+ """
+ mpatch = MonkeyPatch()
+ yield mpatch
+ mpatch.undo()
+
+
+def resolve(name):
+ # simplified from zope.dottedname
+ parts = name.split(".")
+
+ used = parts.pop(0)
+ found = __import__(used)
+ for part in parts:
+ used += "." + part
+ try:
+ found = getattr(found, part)
+ except AttributeError:
+ pass
+ else:
+ continue
+ # we use explicit un-nesting of the handling block in order
+ # to avoid nested exceptions on python 3
+ try:
+ __import__(used)
+ except ImportError as ex:
+ # str is used for py2 vs py3
+ expected = str(ex).split()[-1]
+ if expected == used:
+ raise
+ else:
+ raise ImportError("import error in %s: %s" % (used, ex))
+ found = annotated_getattr(found, part, used)
+ return found
+
+
+def annotated_getattr(obj, name, ann):
+ try:
+ obj = getattr(obj, name)
+ except AttributeError:
+ raise AttributeError(
+ "%r object at %s has no attribute %r" % (type(obj).__name__, ann, name)
+ )
+ return obj
+
+
+def derive_importpath(import_path, raising):
+ if not isinstance(import_path, six.string_types) or "." not in import_path:
+ raise TypeError("must be absolute import path string, not %r" % (import_path,))
+ module, attr = import_path.rsplit(".", 1)
+ target = resolve(module)
+ if raising:
+ annotated_getattr(target, attr, ann=module)
+ return attr, target
+
+
+class Notset(object):
+ def __repr__(self):
+ return "<notset>"
+
+
+notset = Notset()
+
+
+class MonkeyPatch(object):
+ """ Object returned by the ``monkeypatch`` fixture keeping a record of setattr/item/env/syspath changes.
+ """
+
+ def __init__(self):
+ self._setattr = []
+ self._setitem = []
+ self._cwd = None
+ self._savesyspath = None
+
+ @contextmanager
+ def context(self):
+ """
+ Context manager that returns a new :class:`MonkeyPatch` object which
+ undoes any patching done inside the ``with`` block upon exit:
+
+ .. code-block:: python
+
+ import functools
+ def test_partial(monkeypatch):
+ with monkeypatch.context() as m:
+ m.setattr(functools, "partial", 3)
+
+ Useful in situations where it is desired to undo some patches before the test ends,
+ such as mocking ``stdlib`` functions that might break pytest itself if mocked (for examples
+ of this see `#3290 <https://github.com/pytest-dev/pytest/issues/3290>`_.
+ """
+ m = MonkeyPatch()
+ try:
+ yield m
+ finally:
+ m.undo()
+
+ def setattr(self, target, name, value=notset, raising=True):
+ """ Set attribute value on target, memorizing the old value.
+ By default raise AttributeError if the attribute did not exist.
+
+ For convenience you can specify a string as ``target`` which
+ will be interpreted as a dotted import path, with the last part
+ being the attribute name. Example:
+ ``monkeypatch.setattr("os.getcwd", lambda: "/")``
+ would set the ``getcwd`` function of the ``os`` module.
+
+ The ``raising`` value determines if the setattr should fail
+ if the attribute is not already present (defaults to True
+ which means it will raise).
+ """
+ __tracebackhide__ = True
import inspect
- if name is notset:
- if not isinstance(target, six.string_types):
- raise TypeError(
- "use delattr(target, name) or "
- "delattr(target) with target being a dotted "
- "import string"
- )
- name, target = derive_importpath(target, raising)
-
- if not hasattr(target, name):
- if raising:
- raise AttributeError(name)
- else:
+ if value is notset:
+ if not isinstance(target, six.string_types):
+ raise TypeError(
+ "use setattr(target, name, value) or "
+ "setattr(target, value) with target being a dotted "
+ "import string"
+ )
+ value = name
+ name, target = derive_importpath(target, raising)
+
+ oldval = getattr(target, name, notset)
+ if raising and oldval is notset:
+ raise AttributeError("%r has no attribute %r" % (target, name))
+
+ # avoid class descriptors like staticmethod/classmethod
+ if inspect.isclass(target):
+ oldval = target.__dict__.get(name, notset)
+ self._setattr.append((target, name, oldval))
+ setattr(target, name, value)
+
+ def delattr(self, target, name=notset, raising=True):
+ """ Delete attribute ``name`` from ``target``, by default raise
+ AttributeError it the attribute did not previously exist.
+
+ If no ``name`` is specified and ``target`` is a string
+ it will be interpreted as a dotted import path with the
+ last part being the attribute name.
+
+ If ``raising`` is set to False, no exception will be raised if the
+ attribute is missing.
+ """
+ __tracebackhide__ = True
+ import inspect
+
+ if name is notset:
+ if not isinstance(target, six.string_types):
+ raise TypeError(
+ "use delattr(target, name) or "
+ "delattr(target) with target being a dotted "
+ "import string"
+ )
+ name, target = derive_importpath(target, raising)
+
+ if not hasattr(target, name):
+ if raising:
+ raise AttributeError(name)
+ else:
oldval = getattr(target, name, notset)
# Avoid class descriptors like staticmethod/classmethod.
if inspect.isclass(target):
oldval = target.__dict__.get(name, notset)
self._setattr.append((target, name, oldval))
- delattr(target, name)
-
- def setitem(self, dic, name, value):
- """ Set dictionary entry ``name`` to value. """
- self._setitem.append((dic, name, dic.get(name, notset)))
- dic[name] = value
-
- def delitem(self, dic, name, raising=True):
- """ Delete ``name`` from dict. Raise KeyError if it doesn't exist.
-
- If ``raising`` is set to False, no exception will be raised if the
- key is missing.
- """
- if name not in dic:
- if raising:
- raise KeyError(name)
- else:
- self._setitem.append((dic, name, dic.get(name, notset)))
- del dic[name]
-
- def _warn_if_env_name_is_not_str(self, name):
- """On Python 2, warn if the given environment variable name is not a native str (#4056)"""
- if six.PY2 and not isinstance(name, str):
- warnings.warn(
- pytest.PytestWarning(
- "Environment variable name {!r} should be str".format(name)
- )
- )
-
- def setenv(self, name, value, prepend=None):
- """ Set environment variable ``name`` to ``value``. If ``prepend``
- is a character, read the current environment variable value
- and prepend the ``value`` adjoined with the ``prepend`` character."""
- if not isinstance(value, str):
- warnings.warn(
- pytest.PytestWarning(
- "Value of environment variable {name} type should be str, but got "
- "{value!r} (type: {type}); converted to str implicitly".format(
- name=name, value=value, type=type(value).__name__
- )
- ),
- stacklevel=2,
- )
- value = str(value)
- if prepend and name in os.environ:
- value = value + prepend + os.environ[name]
- self._warn_if_env_name_is_not_str(name)
- self.setitem(os.environ, name, value)
-
- def delenv(self, name, raising=True):
- """ Delete ``name`` from the environment. Raise KeyError if it does
- not exist.
-
- If ``raising`` is set to False, no exception will be raised if the
- environment variable is missing.
- """
- self._warn_if_env_name_is_not_str(name)
- self.delitem(os.environ, name, raising=raising)
-
- def syspath_prepend(self, path):
- """ Prepend ``path`` to ``sys.path`` list of import locations. """
+ delattr(target, name)
+
+ def setitem(self, dic, name, value):
+ """ Set dictionary entry ``name`` to value. """
+ self._setitem.append((dic, name, dic.get(name, notset)))
+ dic[name] = value
+
+ def delitem(self, dic, name, raising=True):
+ """ Delete ``name`` from dict. Raise KeyError if it doesn't exist.
+
+ If ``raising`` is set to False, no exception will be raised if the
+ key is missing.
+ """
+ if name not in dic:
+ if raising:
+ raise KeyError(name)
+ else:
+ self._setitem.append((dic, name, dic.get(name, notset)))
+ del dic[name]
+
+ def _warn_if_env_name_is_not_str(self, name):
+ """On Python 2, warn if the given environment variable name is not a native str (#4056)"""
+ if six.PY2 and not isinstance(name, str):
+ warnings.warn(
+ pytest.PytestWarning(
+ "Environment variable name {!r} should be str".format(name)
+ )
+ )
+
+ def setenv(self, name, value, prepend=None):
+ """ Set environment variable ``name`` to ``value``. If ``prepend``
+ is a character, read the current environment variable value
+ and prepend the ``value`` adjoined with the ``prepend`` character."""
+ if not isinstance(value, str):
+ warnings.warn(
+ pytest.PytestWarning(
+ "Value of environment variable {name} type should be str, but got "
+ "{value!r} (type: {type}); converted to str implicitly".format(
+ name=name, value=value, type=type(value).__name__
+ )
+ ),
+ stacklevel=2,
+ )
+ value = str(value)
+ if prepend and name in os.environ:
+ value = value + prepend + os.environ[name]
+ self._warn_if_env_name_is_not_str(name)
+ self.setitem(os.environ, name, value)
+
+ def delenv(self, name, raising=True):
+ """ Delete ``name`` from the environment. Raise KeyError if it does
+ not exist.
+
+ If ``raising`` is set to False, no exception will be raised if the
+ environment variable is missing.
+ """
+ self._warn_if_env_name_is_not_str(name)
+ self.delitem(os.environ, name, raising=raising)
+
+ def syspath_prepend(self, path):
+ """ Prepend ``path`` to ``sys.path`` list of import locations. """
from pkg_resources import fixup_namespace_packages
- if self._savesyspath is None:
- self._savesyspath = sys.path[:]
- sys.path.insert(0, str(path))
-
+ if self._savesyspath is None:
+ self._savesyspath = sys.path[:]
+ sys.path.insert(0, str(path))
+
# https://github.com/pypa/setuptools/blob/d8b901bc/docs/pkg_resources.txt#L162-L171
fixup_namespace_packages(str(path))
@@ -284,53 +284,53 @@ class MonkeyPatch(object):
invalidate_caches()
- def chdir(self, path):
- """ Change the current working directory to the specified path.
- Path can be a string or a py.path.local object.
- """
- if self._cwd is None:
- self._cwd = os.getcwd()
- if hasattr(path, "chdir"):
- path.chdir()
- elif isinstance(path, Path):
- # modern python uses the fspath protocol here LEGACY
- os.chdir(str(path))
- else:
- os.chdir(path)
-
- def undo(self):
- """ Undo previous changes. This call consumes the
- undo stack. Calling it a second time has no effect unless
- you do more monkeypatching after the undo call.
-
- There is generally no need to call `undo()`, since it is
- called automatically during tear-down.
-
- Note that the same `monkeypatch` fixture is used across a
- single test function invocation. If `monkeypatch` is used both by
- the test function itself and one of the test fixtures,
- calling `undo()` will undo all of the changes made in
- both functions.
- """
- for obj, name, value in reversed(self._setattr):
- if value is not notset:
- setattr(obj, name, value)
- else:
- delattr(obj, name)
- self._setattr[:] = []
- for dictionary, name, value in reversed(self._setitem):
- if value is notset:
- try:
- del dictionary[name]
- except KeyError:
- pass # was already deleted, so we have the desired state
- else:
- dictionary[name] = value
- self._setitem[:] = []
- if self._savesyspath is not None:
- sys.path[:] = self._savesyspath
- self._savesyspath = None
-
- if self._cwd is not None:
- os.chdir(self._cwd)
- self._cwd = None
+ def chdir(self, path):
+ """ Change the current working directory to the specified path.
+ Path can be a string or a py.path.local object.
+ """
+ if self._cwd is None:
+ self._cwd = os.getcwd()
+ if hasattr(path, "chdir"):
+ path.chdir()
+ elif isinstance(path, Path):
+ # modern python uses the fspath protocol here LEGACY
+ os.chdir(str(path))
+ else:
+ os.chdir(path)
+
+ def undo(self):
+ """ Undo previous changes. This call consumes the
+ undo stack. Calling it a second time has no effect unless
+ you do more monkeypatching after the undo call.
+
+ There is generally no need to call `undo()`, since it is
+ called automatically during tear-down.
+
+ Note that the same `monkeypatch` fixture is used across a
+ single test function invocation. If `monkeypatch` is used both by
+ the test function itself and one of the test fixtures,
+ calling `undo()` will undo all of the changes made in
+ both functions.
+ """
+ for obj, name, value in reversed(self._setattr):
+ if value is not notset:
+ setattr(obj, name, value)
+ else:
+ delattr(obj, name)
+ self._setattr[:] = []
+ for dictionary, name, value in reversed(self._setitem):
+ if value is notset:
+ try:
+ del dictionary[name]
+ except KeyError:
+ pass # was already deleted, so we have the desired state
+ else:
+ dictionary[name] = value
+ self._setitem[:] = []
+ if self._savesyspath is not None:
+ sys.path[:] = self._savesyspath
+ self._savesyspath = None
+
+ if self._cwd is not None:
+ os.chdir(self._cwd)
+ self._cwd = None
diff --git a/contrib/python/pytest/py2/_pytest/nodes.py b/contrib/python/pytest/py2/_pytest/nodes.py
index 271c92dbc2..206e9ae163 100644
--- a/contrib/python/pytest/py2/_pytest/nodes.py
+++ b/contrib/python/pytest/py2/_pytest/nodes.py
@@ -1,332 +1,332 @@
# -*- coding: utf-8 -*-
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import os
-import warnings
-
-import py
-import six
-
-import _pytest._code
-from _pytest.compat import getfslineno
-from _pytest.mark.structures import NodeKeywords
-from _pytest.outcomes import fail
-
-SEP = "/"
-
-tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
-
-
-def _splitnode(nodeid):
- """Split a nodeid into constituent 'parts'.
-
- Node IDs are strings, and can be things like:
- ''
- 'testing/code'
- 'testing/code/test_excinfo.py'
- 'testing/code/test_excinfo.py::TestFormattedExcinfo'
-
- Return values are lists e.g.
- []
- ['testing', 'code']
- ['testing', 'code', 'test_excinfo.py']
- ['testing', 'code', 'test_excinfo.py', 'TestFormattedExcinfo', '()']
- """
- if nodeid == "":
- # If there is no root node at all, return an empty list so the caller's logic can remain sane
- return []
- parts = nodeid.split(SEP)
- # Replace single last element 'test_foo.py::Bar' with multiple elements 'test_foo.py', 'Bar'
- parts[-1:] = parts[-1].split("::")
- return parts
-
-
-def ischildnode(baseid, nodeid):
- """Return True if the nodeid is a child node of the baseid.
-
- E.g. 'foo/bar::Baz' is a child of 'foo', 'foo/bar' and 'foo/bar::Baz', but not of 'foo/blorp'
- """
- base_parts = _splitnode(baseid)
- node_parts = _splitnode(nodeid)
- if len(node_parts) < len(base_parts):
- return False
- return node_parts[: len(base_parts)] == base_parts
-
-
-class Node(object):
- """ base class for Collector and Item the test collection tree.
- Collector subclasses have children, Items are terminal nodes."""
-
- def __init__(
- self, name, parent=None, config=None, session=None, fspath=None, nodeid=None
- ):
- #: a unique name within the scope of the parent node
- self.name = name
-
- #: the parent collector node.
- self.parent = parent
-
- #: the pytest config object
- self.config = config or parent.config
-
- #: the session this node is part of
- self.session = session or parent.session
-
- #: filesystem path where this node was collected from (can be None)
- self.fspath = fspath or getattr(parent, "fspath", None)
-
- #: keywords/markers collected from all scopes
- self.keywords = NodeKeywords(self)
-
- #: the marker objects belonging to this node
- self.own_markers = []
-
- #: allow adding of extra keywords to use for matching
- self.extra_keyword_matches = set()
-
- # used for storing artificial fixturedefs for direct parametrization
- self._name2pseudofixturedef = {}
-
- if nodeid is not None:
- assert "::()" not in nodeid
- self._nodeid = nodeid
- else:
- self._nodeid = self.parent.nodeid
- if self.name != "()":
- self._nodeid += "::" + self.name
-
- @property
- def ihook(self):
- """ fspath sensitive hook proxy used to call pytest hooks"""
- return self.session.gethookproxy(self.fspath)
-
- def __repr__(self):
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import os
+import warnings
+
+import py
+import six
+
+import _pytest._code
+from _pytest.compat import getfslineno
+from _pytest.mark.structures import NodeKeywords
+from _pytest.outcomes import fail
+
+SEP = "/"
+
+tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
+
+
+def _splitnode(nodeid):
+ """Split a nodeid into constituent 'parts'.
+
+ Node IDs are strings, and can be things like:
+ ''
+ 'testing/code'
+ 'testing/code/test_excinfo.py'
+ 'testing/code/test_excinfo.py::TestFormattedExcinfo'
+
+ Return values are lists e.g.
+ []
+ ['testing', 'code']
+ ['testing', 'code', 'test_excinfo.py']
+ ['testing', 'code', 'test_excinfo.py', 'TestFormattedExcinfo', '()']
+ """
+ if nodeid == "":
+ # If there is no root node at all, return an empty list so the caller's logic can remain sane
+ return []
+ parts = nodeid.split(SEP)
+ # Replace single last element 'test_foo.py::Bar' with multiple elements 'test_foo.py', 'Bar'
+ parts[-1:] = parts[-1].split("::")
+ return parts
+
+
+def ischildnode(baseid, nodeid):
+ """Return True if the nodeid is a child node of the baseid.
+
+ E.g. 'foo/bar::Baz' is a child of 'foo', 'foo/bar' and 'foo/bar::Baz', but not of 'foo/blorp'
+ """
+ base_parts = _splitnode(baseid)
+ node_parts = _splitnode(nodeid)
+ if len(node_parts) < len(base_parts):
+ return False
+ return node_parts[: len(base_parts)] == base_parts
+
+
+class Node(object):
+ """ base class for Collector and Item the test collection tree.
+ Collector subclasses have children, Items are terminal nodes."""
+
+ def __init__(
+ self, name, parent=None, config=None, session=None, fspath=None, nodeid=None
+ ):
+ #: a unique name within the scope of the parent node
+ self.name = name
+
+ #: the parent collector node.
+ self.parent = parent
+
+ #: the pytest config object
+ self.config = config or parent.config
+
+ #: the session this node is part of
+ self.session = session or parent.session
+
+ #: filesystem path where this node was collected from (can be None)
+ self.fspath = fspath or getattr(parent, "fspath", None)
+
+ #: keywords/markers collected from all scopes
+ self.keywords = NodeKeywords(self)
+
+ #: the marker objects belonging to this node
+ self.own_markers = []
+
+ #: allow adding of extra keywords to use for matching
+ self.extra_keyword_matches = set()
+
+ # used for storing artificial fixturedefs for direct parametrization
+ self._name2pseudofixturedef = {}
+
+ if nodeid is not None:
+ assert "::()" not in nodeid
+ self._nodeid = nodeid
+ else:
+ self._nodeid = self.parent.nodeid
+ if self.name != "()":
+ self._nodeid += "::" + self.name
+
+ @property
+ def ihook(self):
+ """ fspath sensitive hook proxy used to call pytest hooks"""
+ return self.session.gethookproxy(self.fspath)
+
+ def __repr__(self):
return "<%s %s>" % (self.__class__.__name__, getattr(self, "name", None))
-
+
def warn(self, warning):
- """Issue a warning for this item.
-
+ """Issue a warning for this item.
+
Warnings will be displayed after the test session, unless explicitly suppressed
-
+
:param Warning warning: the warning instance to issue. Must be a subclass of PytestWarning.
-
+
:raise ValueError: if ``warning`` instance is not a subclass of PytestWarning.
-
+
Example usage:
-
- .. code-block:: python
-
- node.warn(PytestWarning("some message"))
-
- """
- from _pytest.warning_types import PytestWarning
-
- if not isinstance(warning, PytestWarning):
- raise ValueError(
- "warning must be an instance of PytestWarning or subclass, got {!r}".format(
- warning
- )
- )
- path, lineno = get_fslocation_from_item(self)
- warnings.warn_explicit(
- warning,
- category=None,
- filename=str(path),
- lineno=lineno + 1 if lineno is not None else None,
- )
-
- # methods for ordering nodes
- @property
- def nodeid(self):
- """ a ::-separated string denoting its collection tree address. """
- return self._nodeid
-
- def __hash__(self):
- return hash(self.nodeid)
-
- def setup(self):
- pass
-
- def teardown(self):
- pass
-
- def listchain(self):
- """ return list of all parent collectors up to self,
- starting from root of collection tree. """
- chain = []
- item = self
- while item is not None:
- chain.append(item)
- item = item.parent
- chain.reverse()
- return chain
-
- def add_marker(self, marker, append=True):
- """dynamically add a marker object to the node.
-
- :type marker: ``str`` or ``pytest.mark.*`` object
- :param marker:
- ``append=True`` whether to append the marker,
- if ``False`` insert at position ``0``.
- """
- from _pytest.mark import MarkDecorator, MARK_GEN
-
- if isinstance(marker, six.string_types):
- marker = getattr(MARK_GEN, marker)
- elif not isinstance(marker, MarkDecorator):
- raise ValueError("is not a string or pytest.mark.* Marker")
- self.keywords[marker.name] = marker
- if append:
- self.own_markers.append(marker.mark)
- else:
- self.own_markers.insert(0, marker.mark)
-
- def iter_markers(self, name=None):
- """
- :param name: if given, filter the results by the name attribute
-
- iterate over all markers of the node
- """
- return (x[1] for x in self.iter_markers_with_node(name=name))
-
- def iter_markers_with_node(self, name=None):
- """
- :param name: if given, filter the results by the name attribute
-
- iterate over all markers of the node
- returns sequence of tuples (node, mark)
- """
- for node in reversed(self.listchain()):
- for mark in node.own_markers:
- if name is None or getattr(mark, "name", None) == name:
- yield node, mark
-
- def get_closest_marker(self, name, default=None):
- """return the first marker matching the name, from closest (for example function) to farther level (for example
- module level).
-
- :param default: fallback return value of no marker was found
- :param name: name to filter by
- """
- return next(self.iter_markers(name=name), default)
-
- def listextrakeywords(self):
- """ Return a set of all extra keywords in self and any parents."""
- extra_keywords = set()
- for item in self.listchain():
- extra_keywords.update(item.extra_keyword_matches)
- return extra_keywords
-
- def listnames(self):
- return [x.name for x in self.listchain()]
-
- def addfinalizer(self, fin):
- """ register a function to be called when this node is finalized.
-
- This method can only be called when this node is active
- in a setup chain, for example during self.setup().
- """
- self.session._setupstate.addfinalizer(fin, self)
-
- def getparent(self, cls):
- """ get the next parent node (including ourself)
- which is an instance of the given class"""
- current = self
- while current and not isinstance(current, cls):
- current = current.parent
- return current
-
- def _prunetraceback(self, excinfo):
- pass
-
- def _repr_failure_py(self, excinfo, style=None):
- if excinfo.errisinstance(fail.Exception):
- if not excinfo.value.pytrace:
- return six.text_type(excinfo.value)
- fm = self.session._fixturemanager
- if excinfo.errisinstance(fm.FixtureLookupError):
- return excinfo.value.formatrepr()
- tbfilter = True
+
+ .. code-block:: python
+
+ node.warn(PytestWarning("some message"))
+
+ """
+ from _pytest.warning_types import PytestWarning
+
+ if not isinstance(warning, PytestWarning):
+ raise ValueError(
+ "warning must be an instance of PytestWarning or subclass, got {!r}".format(
+ warning
+ )
+ )
+ path, lineno = get_fslocation_from_item(self)
+ warnings.warn_explicit(
+ warning,
+ category=None,
+ filename=str(path),
+ lineno=lineno + 1 if lineno is not None else None,
+ )
+
+ # methods for ordering nodes
+ @property
+ def nodeid(self):
+ """ a ::-separated string denoting its collection tree address. """
+ return self._nodeid
+
+ def __hash__(self):
+ return hash(self.nodeid)
+
+ def setup(self):
+ pass
+
+ def teardown(self):
+ pass
+
+ def listchain(self):
+ """ return list of all parent collectors up to self,
+ starting from root of collection tree. """
+ chain = []
+ item = self
+ while item is not None:
+ chain.append(item)
+ item = item.parent
+ chain.reverse()
+ return chain
+
+ def add_marker(self, marker, append=True):
+ """dynamically add a marker object to the node.
+
+ :type marker: ``str`` or ``pytest.mark.*`` object
+ :param marker:
+ ``append=True`` whether to append the marker,
+ if ``False`` insert at position ``0``.
+ """
+ from _pytest.mark import MarkDecorator, MARK_GEN
+
+ if isinstance(marker, six.string_types):
+ marker = getattr(MARK_GEN, marker)
+ elif not isinstance(marker, MarkDecorator):
+ raise ValueError("is not a string or pytest.mark.* Marker")
+ self.keywords[marker.name] = marker
+ if append:
+ self.own_markers.append(marker.mark)
+ else:
+ self.own_markers.insert(0, marker.mark)
+
+ def iter_markers(self, name=None):
+ """
+ :param name: if given, filter the results by the name attribute
+
+ iterate over all markers of the node
+ """
+ return (x[1] for x in self.iter_markers_with_node(name=name))
+
+ def iter_markers_with_node(self, name=None):
+ """
+ :param name: if given, filter the results by the name attribute
+
+ iterate over all markers of the node
+ returns sequence of tuples (node, mark)
+ """
+ for node in reversed(self.listchain()):
+ for mark in node.own_markers:
+ if name is None or getattr(mark, "name", None) == name:
+ yield node, mark
+
+ def get_closest_marker(self, name, default=None):
+ """return the first marker matching the name, from closest (for example function) to farther level (for example
+ module level).
+
+ :param default: fallback return value of no marker was found
+ :param name: name to filter by
+ """
+ return next(self.iter_markers(name=name), default)
+
+ def listextrakeywords(self):
+ """ Return a set of all extra keywords in self and any parents."""
+ extra_keywords = set()
+ for item in self.listchain():
+ extra_keywords.update(item.extra_keyword_matches)
+ return extra_keywords
+
+ def listnames(self):
+ return [x.name for x in self.listchain()]
+
+ def addfinalizer(self, fin):
+ """ register a function to be called when this node is finalized.
+
+ This method can only be called when this node is active
+ in a setup chain, for example during self.setup().
+ """
+ self.session._setupstate.addfinalizer(fin, self)
+
+ def getparent(self, cls):
+ """ get the next parent node (including ourself)
+ which is an instance of the given class"""
+ current = self
+ while current and not isinstance(current, cls):
+ current = current.parent
+ return current
+
+ def _prunetraceback(self, excinfo):
+ pass
+
+ def _repr_failure_py(self, excinfo, style=None):
+ if excinfo.errisinstance(fail.Exception):
+ if not excinfo.value.pytrace:
+ return six.text_type(excinfo.value)
+ fm = self.session._fixturemanager
+ if excinfo.errisinstance(fm.FixtureLookupError):
+ return excinfo.value.formatrepr()
+ tbfilter = True
if self.config.getoption("fulltrace", False):
- style = "long"
- else:
- tb = _pytest._code.Traceback([excinfo.traceback[-1]])
- self._prunetraceback(excinfo)
- if len(excinfo.traceback) == 0:
- excinfo.traceback = tb
- tbfilter = False # prunetraceback already does it
- if style == "auto":
- style = "long"
- # XXX should excinfo.getrepr record all data and toterminal() process it?
- if style is None:
+ style = "long"
+ else:
+ tb = _pytest._code.Traceback([excinfo.traceback[-1]])
+ self._prunetraceback(excinfo)
+ if len(excinfo.traceback) == 0:
+ excinfo.traceback = tb
+ tbfilter = False # prunetraceback already does it
+ if style == "auto":
+ style = "long"
+ # XXX should excinfo.getrepr record all data and toterminal() process it?
+ if style is None:
if self.config.getoption("tbstyle", "auto") == "short":
- style = "short"
- else:
- style = "long"
-
+ style = "short"
+ else:
+ style = "long"
+
if self.config.getoption("verbose", 0) > 1:
- truncate_locals = False
- else:
- truncate_locals = True
-
- try:
- os.getcwd()
- abspath = False
- except OSError:
- abspath = True
-
- return excinfo.getrepr(
- funcargs=True,
- abspath=abspath,
+ truncate_locals = False
+ else:
+ truncate_locals = True
+
+ try:
+ os.getcwd()
+ abspath = False
+ except OSError:
+ abspath = True
+
+ return excinfo.getrepr(
+ funcargs=True,
+ abspath=abspath,
showlocals=self.config.getoption("showlocals", False),
- style=style,
- tbfilter=tbfilter,
- truncate_locals=truncate_locals,
- )
-
- repr_failure = _repr_failure_py
-
-
-def get_fslocation_from_item(item):
- """Tries to extract the actual location from an item, depending on available attributes:
-
- * "fslocation": a pair (path, lineno)
- * "obj": a Python object that the item wraps.
- * "fspath": just a path
-
- :rtype: a tuple of (str|LocalPath, int) with filename and line number.
- """
- result = getattr(item, "location", None)
- if result is not None:
- return result[:2]
- obj = getattr(item, "obj", None)
- if obj is not None:
- return getfslineno(obj)
- return getattr(item, "fspath", "unknown location"), -1
-
-
-class Collector(Node):
- """ Collector instances create children through collect()
- and thus iteratively build a tree.
- """
-
- class CollectError(Exception):
- """ an error during collection, contains a custom message. """
-
- def collect(self):
- """ returns a list of children (items and collectors)
- for this collection node.
- """
- raise NotImplementedError("abstract")
-
- def repr_failure(self, excinfo):
- """ represent a collection failure. """
- if excinfo.errisinstance(self.CollectError):
- exc = excinfo.value
- return str(exc.args[0])
-
+ style=style,
+ tbfilter=tbfilter,
+ truncate_locals=truncate_locals,
+ )
+
+ repr_failure = _repr_failure_py
+
+
+def get_fslocation_from_item(item):
+ """Tries to extract the actual location from an item, depending on available attributes:
+
+ * "fslocation": a pair (path, lineno)
+ * "obj": a Python object that the item wraps.
+ * "fspath": just a path
+
+ :rtype: a tuple of (str|LocalPath, int) with filename and line number.
+ """
+ result = getattr(item, "location", None)
+ if result is not None:
+ return result[:2]
+ obj = getattr(item, "obj", None)
+ if obj is not None:
+ return getfslineno(obj)
+ return getattr(item, "fspath", "unknown location"), -1
+
+
+class Collector(Node):
+ """ Collector instances create children through collect()
+ and thus iteratively build a tree.
+ """
+
+ class CollectError(Exception):
+ """ an error during collection, contains a custom message. """
+
+ def collect(self):
+ """ returns a list of children (items and collectors)
+ for this collection node.
+ """
+ raise NotImplementedError("abstract")
+
+ def repr_failure(self, excinfo):
+ """ represent a collection failure. """
+ if excinfo.errisinstance(self.CollectError):
+ exc = excinfo.value
+ return str(exc.args[0])
+
# Respect explicit tbstyle option, but default to "short"
# (None._repr_failure_py defaults to "long" without "fulltrace" option).
tbstyle = self.config.getoption("tbstyle", "auto")
@@ -335,95 +335,95 @@ class Collector(Node):
return self._repr_failure_py(excinfo, style=tbstyle)
- def _prunetraceback(self, excinfo):
- if hasattr(self, "fspath"):
- traceback = excinfo.traceback
- ntraceback = traceback.cut(path=self.fspath)
- if ntraceback == traceback:
- ntraceback = ntraceback.cut(excludepath=tracebackcutdir)
- excinfo.traceback = ntraceback.filter()
-
-
-def _check_initialpaths_for_relpath(session, fspath):
- for initial_path in session._initialpaths:
- if fspath.common(initial_path) == initial_path:
- return fspath.relto(initial_path)
-
-
-class FSCollector(Collector):
- def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None):
- fspath = py.path.local(fspath) # xxx only for test_resultlog.py?
- name = fspath.basename
- if parent is not None:
- rel = fspath.relto(parent.fspath)
- if rel:
- name = rel
- name = name.replace(os.sep, SEP)
- self.fspath = fspath
-
- session = session or parent.session
-
- if nodeid is None:
- nodeid = self.fspath.relto(session.config.rootdir)
-
- if not nodeid:
- nodeid = _check_initialpaths_for_relpath(session, fspath)
- if nodeid and os.sep != SEP:
- nodeid = nodeid.replace(os.sep, SEP)
-
- super(FSCollector, self).__init__(
- name, parent, config, session, nodeid=nodeid, fspath=fspath
- )
-
-
-class File(FSCollector):
- """ base class for collecting tests from a file. """
-
-
-class Item(Node):
- """ a basic test invocation item. Note that for a single function
- there might be multiple test invocation items.
- """
-
- nextitem = None
-
- def __init__(self, name, parent=None, config=None, session=None, nodeid=None):
- super(Item, self).__init__(name, parent, config, session, nodeid=nodeid)
- self._report_sections = []
-
- #: user properties is a list of tuples (name, value) that holds user
- #: defined properties for this test.
- self.user_properties = []
-
- def add_report_section(self, when, key, content):
- """
- Adds a new report section, similar to what's done internally to add stdout and
- stderr captured output::
-
- item.add_report_section("call", "stdout", "report section contents")
-
- :param str when:
- One of the possible capture states, ``"setup"``, ``"call"``, ``"teardown"``.
- :param str key:
- Name of the section, can be customized at will. Pytest uses ``"stdout"`` and
- ``"stderr"`` internally.
-
- :param str content:
- The full contents as a string.
- """
- if content:
- self._report_sections.append((when, key, content))
-
- def reportinfo(self):
- return self.fspath, None, ""
-
- @property
- def location(self):
- try:
- return self._location
- except AttributeError:
- location = self.reportinfo()
- fspath = self.session._node_location_to_relpath(location[0])
- location = (fspath, location[1], str(location[2]))
- self._location = location
- return location
+ def _prunetraceback(self, excinfo):
+ if hasattr(self, "fspath"):
+ traceback = excinfo.traceback
+ ntraceback = traceback.cut(path=self.fspath)
+ if ntraceback == traceback:
+ ntraceback = ntraceback.cut(excludepath=tracebackcutdir)
+ excinfo.traceback = ntraceback.filter()
+
+
+def _check_initialpaths_for_relpath(session, fspath):
+ for initial_path in session._initialpaths:
+ if fspath.common(initial_path) == initial_path:
+ return fspath.relto(initial_path)
+
+
+class FSCollector(Collector):
+ def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None):
+ fspath = py.path.local(fspath) # xxx only for test_resultlog.py?
+ name = fspath.basename
+ if parent is not None:
+ rel = fspath.relto(parent.fspath)
+ if rel:
+ name = rel
+ name = name.replace(os.sep, SEP)
+ self.fspath = fspath
+
+ session = session or parent.session
+
+ if nodeid is None:
+ nodeid = self.fspath.relto(session.config.rootdir)
+
+ if not nodeid:
+ nodeid = _check_initialpaths_for_relpath(session, fspath)
+ if nodeid and os.sep != SEP:
+ nodeid = nodeid.replace(os.sep, SEP)
+
+ super(FSCollector, self).__init__(
+ name, parent, config, session, nodeid=nodeid, fspath=fspath
+ )
+
+
+class File(FSCollector):
+ """ base class for collecting tests from a file. """
+
+
+class Item(Node):
+ """ a basic test invocation item. Note that for a single function
+ there might be multiple test invocation items.
+ """
+
+ nextitem = None
+
+ def __init__(self, name, parent=None, config=None, session=None, nodeid=None):
+ super(Item, self).__init__(name, parent, config, session, nodeid=nodeid)
+ self._report_sections = []
+
+ #: user properties is a list of tuples (name, value) that holds user
+ #: defined properties for this test.
+ self.user_properties = []
+
+ def add_report_section(self, when, key, content):
+ """
+ Adds a new report section, similar to what's done internally to add stdout and
+ stderr captured output::
+
+ item.add_report_section("call", "stdout", "report section contents")
+
+ :param str when:
+ One of the possible capture states, ``"setup"``, ``"call"``, ``"teardown"``.
+ :param str key:
+ Name of the section, can be customized at will. Pytest uses ``"stdout"`` and
+ ``"stderr"`` internally.
+
+ :param str content:
+ The full contents as a string.
+ """
+ if content:
+ self._report_sections.append((when, key, content))
+
+ def reportinfo(self):
+ return self.fspath, None, ""
+
+ @property
+ def location(self):
+ try:
+ return self._location
+ except AttributeError:
+ location = self.reportinfo()
+ fspath = self.session._node_location_to_relpath(location[0])
+ location = (fspath, location[1], str(location[2]))
+ self._location = location
+ return location
diff --git a/contrib/python/pytest/py2/_pytest/nose.py b/contrib/python/pytest/py2/_pytest/nose.py
index 4605e5a3f7..fbab91da24 100644
--- a/contrib/python/pytest/py2/_pytest/nose.py
+++ b/contrib/python/pytest/py2/_pytest/nose.py
@@ -1,70 +1,70 @@
# -*- coding: utf-8 -*-
-""" run test suites written for nose. """
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import sys
-
+""" run test suites written for nose. """
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import sys
+
import six
import pytest
-from _pytest import python
-from _pytest import runner
-from _pytest import unittest
-from _pytest.config import hookimpl
-
-
-def get_skip_exceptions():
- skip_classes = set()
- for module_name in ("unittest", "unittest2", "nose"):
- mod = sys.modules.get(module_name)
- if hasattr(mod, "SkipTest"):
- skip_classes.add(mod.SkipTest)
- return tuple(skip_classes)
-
-
-def pytest_runtest_makereport(item, call):
- if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()):
- # let's substitute the excinfo with a pytest.skip one
+from _pytest import python
+from _pytest import runner
+from _pytest import unittest
+from _pytest.config import hookimpl
+
+
+def get_skip_exceptions():
+ skip_classes = set()
+ for module_name in ("unittest", "unittest2", "nose"):
+ mod = sys.modules.get(module_name)
+ if hasattr(mod, "SkipTest"):
+ skip_classes.add(mod.SkipTest)
+ return tuple(skip_classes)
+
+
+def pytest_runtest_makereport(item, call):
+ if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()):
+ # let's substitute the excinfo with a pytest.skip one
call2 = runner.CallInfo.from_call(
lambda: pytest.skip(six.text_type(call.excinfo.value)), call.when
)
- call.excinfo = call2.excinfo
-
-
-@hookimpl(trylast=True)
-def pytest_runtest_setup(item):
- if is_potential_nosetest(item):
- if not call_optional(item.obj, "setup"):
- # call module level setup if there is no object level one
- call_optional(item.parent.obj, "setup")
- # XXX this implies we only call teardown when setup worked
- item.session._setupstate.addfinalizer((lambda: teardown_nose(item)), item)
-
-
-def teardown_nose(item):
- if is_potential_nosetest(item):
- if not call_optional(item.obj, "teardown"):
- call_optional(item.parent.obj, "teardown")
- # if hasattr(item.parent, '_nosegensetup'):
- # #call_optional(item._nosegensetup, 'teardown')
- # del item.parent._nosegensetup
-
-
-def is_potential_nosetest(item):
- # extra check needed since we do not do nose style setup/teardown
- # on direct unittest style classes
- return isinstance(item, python.Function) and not isinstance(
- item, unittest.TestCaseFunction
- )
-
-
-def call_optional(obj, name):
- method = getattr(obj, name, None)
- isfixture = hasattr(method, "_pytestfixturefunction")
- if method is not None and not isfixture and callable(method):
- # If there's any problems allow the exception to raise rather than
- # silently ignoring them
- method()
- return True
+ call.excinfo = call2.excinfo
+
+
+@hookimpl(trylast=True)
+def pytest_runtest_setup(item):
+ if is_potential_nosetest(item):
+ if not call_optional(item.obj, "setup"):
+ # call module level setup if there is no object level one
+ call_optional(item.parent.obj, "setup")
+ # XXX this implies we only call teardown when setup worked
+ item.session._setupstate.addfinalizer((lambda: teardown_nose(item)), item)
+
+
+def teardown_nose(item):
+ if is_potential_nosetest(item):
+ if not call_optional(item.obj, "teardown"):
+ call_optional(item.parent.obj, "teardown")
+ # if hasattr(item.parent, '_nosegensetup'):
+ # #call_optional(item._nosegensetup, 'teardown')
+ # del item.parent._nosegensetup
+
+
+def is_potential_nosetest(item):
+ # extra check needed since we do not do nose style setup/teardown
+ # on direct unittest style classes
+ return isinstance(item, python.Function) and not isinstance(
+ item, unittest.TestCaseFunction
+ )
+
+
+def call_optional(obj, name):
+ method = getattr(obj, name, None)
+ isfixture = hasattr(method, "_pytestfixturefunction")
+ if method is not None and not isfixture and callable(method):
+ # If there's any problems allow the exception to raise rather than
+ # silently ignoring them
+ method()
+ return True
diff --git a/contrib/python/pytest/py2/_pytest/outcomes.py b/contrib/python/pytest/py2/_pytest/outcomes.py
index 2d0babc7f3..4620f957c7 100644
--- a/contrib/python/pytest/py2/_pytest/outcomes.py
+++ b/contrib/python/pytest/py2/_pytest/outcomes.py
@@ -1,148 +1,148 @@
# -*- coding: utf-8 -*-
-"""
-exception classes and constants handling test outcomes
-as well as functions creating them
-"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import sys
-
+"""
+exception classes and constants handling test outcomes
+as well as functions creating them
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import sys
+
from packaging.version import Version
-
-
-class OutcomeException(BaseException):
- """ OutcomeException and its subclass instances indicate and
- contain info about test and collection outcomes.
- """
-
- def __init__(self, msg=None, pytrace=True):
- BaseException.__init__(self, msg)
- self.msg = msg
- self.pytrace = pytrace
-
- def __repr__(self):
- if self.msg:
- val = self.msg
- if isinstance(val, bytes):
- val = val.decode("UTF-8", errors="replace")
- return val
- return "<%s instance>" % (self.__class__.__name__,)
-
- __str__ = __repr__
-
-
-TEST_OUTCOME = (OutcomeException, Exception)
-
-
-class Skipped(OutcomeException):
- # XXX hackish: on 3k we fake to live in the builtins
- # in order to have Skipped exception printing shorter/nicer
- __module__ = "builtins"
-
- def __init__(self, msg=None, pytrace=True, allow_module_level=False):
- OutcomeException.__init__(self, msg=msg, pytrace=pytrace)
- self.allow_module_level = allow_module_level
-
-
-class Failed(OutcomeException):
- """ raised from an explicit call to pytest.fail() """
-
- __module__ = "builtins"
-
-
+
+
+class OutcomeException(BaseException):
+ """ OutcomeException and its subclass instances indicate and
+ contain info about test and collection outcomes.
+ """
+
+ def __init__(self, msg=None, pytrace=True):
+ BaseException.__init__(self, msg)
+ self.msg = msg
+ self.pytrace = pytrace
+
+ def __repr__(self):
+ if self.msg:
+ val = self.msg
+ if isinstance(val, bytes):
+ val = val.decode("UTF-8", errors="replace")
+ return val
+ return "<%s instance>" % (self.__class__.__name__,)
+
+ __str__ = __repr__
+
+
+TEST_OUTCOME = (OutcomeException, Exception)
+
+
+class Skipped(OutcomeException):
+ # XXX hackish: on 3k we fake to live in the builtins
+ # in order to have Skipped exception printing shorter/nicer
+ __module__ = "builtins"
+
+ def __init__(self, msg=None, pytrace=True, allow_module_level=False):
+ OutcomeException.__init__(self, msg=msg, pytrace=pytrace)
+ self.allow_module_level = allow_module_level
+
+
+class Failed(OutcomeException):
+ """ raised from an explicit call to pytest.fail() """
+
+ __module__ = "builtins"
+
+
class Exit(Exception):
- """ raised for immediate program exits (no tracebacks/summaries)"""
-
- def __init__(self, msg="unknown reason", returncode=None):
- self.msg = msg
- self.returncode = returncode
+ """ raised for immediate program exits (no tracebacks/summaries)"""
+
+ def __init__(self, msg="unknown reason", returncode=None):
+ self.msg = msg
+ self.returncode = returncode
super(Exit, self).__init__(msg)
-
-
-# exposed helper methods
-
-
-def exit(msg, returncode=None):
- """
+
+
+# exposed helper methods
+
+
+def exit(msg, returncode=None):
+ """
Exit testing process.
-
- :param str msg: message to display upon exit.
- :param int returncode: return code to be used when exiting pytest.
- """
- __tracebackhide__ = True
- raise Exit(msg, returncode)
-
-
-exit.Exception = Exit
-
-
-def skip(msg="", **kwargs):
- """
- Skip an executing test with the given message.
-
- This function should be called only during testing (setup, call or teardown) or
+
+ :param str msg: message to display upon exit.
+ :param int returncode: return code to be used when exiting pytest.
+ """
+ __tracebackhide__ = True
+ raise Exit(msg, returncode)
+
+
+exit.Exception = Exit
+
+
+def skip(msg="", **kwargs):
+ """
+ Skip an executing test with the given message.
+
+ This function should be called only during testing (setup, call or teardown) or
during collection by using the ``allow_module_level`` flag. This function can
be called in doctests as well.
-
- :kwarg bool allow_module_level: allows this function to be called at
- module level, skipping the rest of the module. Default to False.
-
- .. note::
- It is better to use the :ref:`pytest.mark.skipif ref` marker when possible to declare a test to be
- skipped under certain conditions like mismatching platforms or
- dependencies.
+
+ :kwarg bool allow_module_level: allows this function to be called at
+ module level, skipping the rest of the module. Default to False.
+
+ .. note::
+ It is better to use the :ref:`pytest.mark.skipif ref` marker when possible to declare a test to be
+ skipped under certain conditions like mismatching platforms or
+ dependencies.
Similarly, use the ``# doctest: +SKIP`` directive (see `doctest.SKIP
<https://docs.python.org/3/library/doctest.html#doctest.SKIP>`_)
to skip a doctest statically.
- """
- __tracebackhide__ = True
- allow_module_level = kwargs.pop("allow_module_level", False)
- if kwargs:
+ """
+ __tracebackhide__ = True
+ allow_module_level = kwargs.pop("allow_module_level", False)
+ if kwargs:
raise TypeError("unexpected keyword arguments: {}".format(sorted(kwargs)))
- raise Skipped(msg=msg, allow_module_level=allow_module_level)
-
-
-skip.Exception = Skipped
-
-
-def fail(msg="", pytrace=True):
- """
- Explicitly fail an executing test with the given message.
-
- :param str msg: the message to show the user as reason for the failure.
- :param bool pytrace: if false the msg represents the full failure information and no
- python traceback will be reported.
- """
- __tracebackhide__ = True
- raise Failed(msg=msg, pytrace=pytrace)
-
-
-fail.Exception = Failed
-
-
-class XFailed(fail.Exception):
- """ raised from an explicit call to pytest.xfail() """
-
-
-def xfail(reason=""):
- """
- Imperatively xfail an executing test or setup functions with the given reason.
-
- This function should be called only during testing (setup, call or teardown).
-
- .. note::
- It is better to use the :ref:`pytest.mark.xfail ref` marker when possible to declare a test to be
- xfailed under certain conditions like known bugs or missing features.
- """
- __tracebackhide__ = True
- raise XFailed(reason)
-
-
-xfail.Exception = XFailed
-
-
+ raise Skipped(msg=msg, allow_module_level=allow_module_level)
+
+
+skip.Exception = Skipped
+
+
+def fail(msg="", pytrace=True):
+ """
+ Explicitly fail an executing test with the given message.
+
+ :param str msg: the message to show the user as reason for the failure.
+ :param bool pytrace: if false the msg represents the full failure information and no
+ python traceback will be reported.
+ """
+ __tracebackhide__ = True
+ raise Failed(msg=msg, pytrace=pytrace)
+
+
+fail.Exception = Failed
+
+
+class XFailed(fail.Exception):
+ """ raised from an explicit call to pytest.xfail() """
+
+
+def xfail(reason=""):
+ """
+ Imperatively xfail an executing test or setup functions with the given reason.
+
+ This function should be called only during testing (setup, call or teardown).
+
+ .. note::
+ It is better to use the :ref:`pytest.mark.xfail ref` marker when possible to declare a test to be
+ xfailed under certain conditions like known bugs or missing features.
+ """
+ __tracebackhide__ = True
+ raise XFailed(reason)
+
+
+xfail.Exception = XFailed
+
+
def importorskip(modname, minversion=None, reason=None):
"""Imports and returns the requested module ``modname``, or skip the current test
if the module cannot be imported.
@@ -152,36 +152,36 @@ def importorskip(modname, minversion=None, reason=None):
at least this minimal version, otherwise the test is still skipped.
:param str reason: if given, this reason is shown as the message when the module
cannot be imported.
- """
- import warnings
-
- __tracebackhide__ = True
- compile(modname, "", "eval") # to catch syntaxerrors
+ """
+ import warnings
+
+ __tracebackhide__ = True
+ compile(modname, "", "eval") # to catch syntaxerrors
import_exc = None
-
- with warnings.catch_warnings():
- # make sure to ignore ImportWarnings that might happen because
- # of existing directories with the same name we're trying to
- # import but without a __init__.py file
- warnings.simplefilter("ignore")
- try:
- __import__(modname)
+
+ with warnings.catch_warnings():
+ # make sure to ignore ImportWarnings that might happen because
+ # of existing directories with the same name we're trying to
+ # import but without a __init__.py file
+ warnings.simplefilter("ignore")
+ try:
+ __import__(modname)
except ImportError as exc:
- # Do not raise chained exception here(#1485)
+ # Do not raise chained exception here(#1485)
import_exc = exc
if import_exc:
if reason is None:
reason = "could not import %r: %s" % (modname, import_exc)
raise Skipped(reason, allow_module_level=True)
- mod = sys.modules[modname]
- if minversion is None:
- return mod
- verattr = getattr(mod, "__version__", None)
- if minversion is not None:
+ mod = sys.modules[modname]
+ if minversion is None:
+ return mod
+ verattr = getattr(mod, "__version__", None)
+ if minversion is not None:
if verattr is None or Version(verattr) < Version(minversion):
- raise Skipped(
- "module %r has __version__ %r, required is: %r"
- % (modname, verattr, minversion),
- allow_module_level=True,
- )
- return mod
+ raise Skipped(
+ "module %r has __version__ %r, required is: %r"
+ % (modname, verattr, minversion),
+ allow_module_level=True,
+ )
+ return mod
diff --git a/contrib/python/pytest/py2/_pytest/pastebin.py b/contrib/python/pytest/py2/_pytest/pastebin.py
index 1150f59105..7a3e80231c 100644
--- a/contrib/python/pytest/py2/_pytest/pastebin.py
+++ b/contrib/python/pytest/py2/_pytest/pastebin.py
@@ -1,110 +1,110 @@
# -*- coding: utf-8 -*-
-""" submit failure or test session information to a pastebin service. """
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import sys
-import tempfile
-
-import six
-
-import pytest
-
-
-def pytest_addoption(parser):
- group = parser.getgroup("terminal reporting")
- group._addoption(
- "--pastebin",
- metavar="mode",
- action="store",
- dest="pastebin",
- default=None,
- choices=["failed", "all"],
- help="send failed|all info to bpaste.net pastebin service.",
- )
-
-
-@pytest.hookimpl(trylast=True)
-def pytest_configure(config):
- if config.option.pastebin == "all":
- tr = config.pluginmanager.getplugin("terminalreporter")
- # if no terminal reporter plugin is present, nothing we can do here;
- # this can happen when this function executes in a slave node
- # when using pytest-xdist, for example
- if tr is not None:
- # pastebin file will be utf-8 encoded binary file
- config._pastebinfile = tempfile.TemporaryFile("w+b")
- oldwrite = tr._tw.write
-
- def tee_write(s, **kwargs):
- oldwrite(s, **kwargs)
- if isinstance(s, six.text_type):
- s = s.encode("utf-8")
- config._pastebinfile.write(s)
-
- tr._tw.write = tee_write
-
-
-def pytest_unconfigure(config):
- if hasattr(config, "_pastebinfile"):
- # get terminal contents and delete file
- config._pastebinfile.seek(0)
- sessionlog = config._pastebinfile.read()
- config._pastebinfile.close()
- del config._pastebinfile
- # undo our patching in the terminal reporter
- tr = config.pluginmanager.getplugin("terminalreporter")
- del tr._tw.__dict__["write"]
- # write summary
- tr.write_sep("=", "Sending information to Paste Service")
- pastebinurl = create_new_paste(sessionlog)
- tr.write_line("pastebin session-log: %s\n" % pastebinurl)
-
-
-def create_new_paste(contents):
- """
- Creates a new paste using bpaste.net service.
-
- :contents: paste contents as utf-8 encoded bytes
- :returns: url to the pasted contents
- """
- import re
-
- if sys.version_info < (3, 0):
- from urllib import urlopen, urlencode
- else:
- from urllib.request import urlopen
- from urllib.parse import urlencode
-
+""" submit failure or test session information to a pastebin service. """
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import sys
+import tempfile
+
+import six
+
+import pytest
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("terminal reporting")
+ group._addoption(
+ "--pastebin",
+ metavar="mode",
+ action="store",
+ dest="pastebin",
+ default=None,
+ choices=["failed", "all"],
+ help="send failed|all info to bpaste.net pastebin service.",
+ )
+
+
+@pytest.hookimpl(trylast=True)
+def pytest_configure(config):
+ if config.option.pastebin == "all":
+ tr = config.pluginmanager.getplugin("terminalreporter")
+ # if no terminal reporter plugin is present, nothing we can do here;
+ # this can happen when this function executes in a slave node
+ # when using pytest-xdist, for example
+ if tr is not None:
+ # pastebin file will be utf-8 encoded binary file
+ config._pastebinfile = tempfile.TemporaryFile("w+b")
+ oldwrite = tr._tw.write
+
+ def tee_write(s, **kwargs):
+ oldwrite(s, **kwargs)
+ if isinstance(s, six.text_type):
+ s = s.encode("utf-8")
+ config._pastebinfile.write(s)
+
+ tr._tw.write = tee_write
+
+
+def pytest_unconfigure(config):
+ if hasattr(config, "_pastebinfile"):
+ # get terminal contents and delete file
+ config._pastebinfile.seek(0)
+ sessionlog = config._pastebinfile.read()
+ config._pastebinfile.close()
+ del config._pastebinfile
+ # undo our patching in the terminal reporter
+ tr = config.pluginmanager.getplugin("terminalreporter")
+ del tr._tw.__dict__["write"]
+ # write summary
+ tr.write_sep("=", "Sending information to Paste Service")
+ pastebinurl = create_new_paste(sessionlog)
+ tr.write_line("pastebin session-log: %s\n" % pastebinurl)
+
+
+def create_new_paste(contents):
+ """
+ Creates a new paste using bpaste.net service.
+
+ :contents: paste contents as utf-8 encoded bytes
+ :returns: url to the pasted contents
+ """
+ import re
+
+ if sys.version_info < (3, 0):
+ from urllib import urlopen, urlencode
+ else:
+ from urllib.request import urlopen
+ from urllib.parse import urlencode
+
params = {"code": contents, "lexer": "text", "expiry": "1week"}
- url = "https://bpaste.net"
- response = urlopen(url, data=urlencode(params).encode("ascii")).read()
- m = re.search(r'href="/raw/(\w+)"', response.decode("utf-8"))
- if m:
- return "%s/show/%s" % (url, m.group(1))
- else:
- return "bad response: " + response
-
-
-def pytest_terminal_summary(terminalreporter):
- import _pytest.config
-
- if terminalreporter.config.option.pastebin != "failed":
- return
- tr = terminalreporter
- if "failed" in tr.stats:
- terminalreporter.write_sep("=", "Sending information to Paste Service")
- for rep in terminalreporter.stats.get("failed"):
- try:
- msg = rep.longrepr.reprtraceback.reprentries[-1].reprfileloc
- except AttributeError:
- msg = tr._getfailureheadline(rep)
- tw = _pytest.config.create_terminal_writer(
- terminalreporter.config, stringio=True
- )
- rep.toterminal(tw)
- s = tw.stringio.getvalue()
- assert len(s)
- pastebinurl = create_new_paste(s)
- tr.write_line("%s --> %s" % (msg, pastebinurl))
+ url = "https://bpaste.net"
+ response = urlopen(url, data=urlencode(params).encode("ascii")).read()
+ m = re.search(r'href="/raw/(\w+)"', response.decode("utf-8"))
+ if m:
+ return "%s/show/%s" % (url, m.group(1))
+ else:
+ return "bad response: " + response
+
+
+def pytest_terminal_summary(terminalreporter):
+ import _pytest.config
+
+ if terminalreporter.config.option.pastebin != "failed":
+ return
+ tr = terminalreporter
+ if "failed" in tr.stats:
+ terminalreporter.write_sep("=", "Sending information to Paste Service")
+ for rep in terminalreporter.stats.get("failed"):
+ try:
+ msg = rep.longrepr.reprtraceback.reprentries[-1].reprfileloc
+ except AttributeError:
+ msg = tr._getfailureheadline(rep)
+ tw = _pytest.config.create_terminal_writer(
+ terminalreporter.config, stringio=True
+ )
+ rep.toterminal(tw)
+ s = tw.stringio.getvalue()
+ assert len(s)
+ pastebinurl = create_new_paste(s)
+ tr.write_line("%s --> %s" % (msg, pastebinurl))
diff --git a/contrib/python/pytest/py2/_pytest/pathlib.py b/contrib/python/pytest/py2/_pytest/pathlib.py
index 668bd2b2be..42071f4310 100644
--- a/contrib/python/pytest/py2/_pytest/pathlib.py
+++ b/contrib/python/pytest/py2/_pytest/pathlib.py
@@ -1,60 +1,60 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
-import atexit
-import errno
-import fnmatch
-import itertools
-import operator
-import os
-import shutil
-import sys
-import uuid
+import atexit
+import errno
+import fnmatch
+import itertools
+import operator
+import os
+import shutil
+import sys
+import uuid
import warnings
from functools import partial
-from functools import reduce
-from os.path import expanduser
-from os.path import expandvars
-from os.path import isabs
-from os.path import sep
-from posixpath import sep as posix_sep
-
-import six
-from six.moves import map
-
-from .compat import PY36
+from functools import reduce
+from os.path import expanduser
+from os.path import expandvars
+from os.path import isabs
+from os.path import sep
+from posixpath import sep as posix_sep
+
+import six
+from six.moves import map
+
+from .compat import PY36
from _pytest.warning_types import PytestWarning
-
-if PY36:
- from pathlib import Path, PurePath
-else:
- from pathlib2 import Path, PurePath
-
-__all__ = ["Path", "PurePath"]
-
-
-LOCK_TIMEOUT = 60 * 60 * 3
-
-get_lock_path = operator.methodcaller("joinpath", ".lock")
-
-
-def ensure_reset_dir(path):
- """
- ensures the given path is an empty directory
- """
- if path.exists():
+
+if PY36:
+ from pathlib import Path, PurePath
+else:
+ from pathlib2 import Path, PurePath
+
+__all__ = ["Path", "PurePath"]
+
+
+LOCK_TIMEOUT = 60 * 60 * 3
+
+get_lock_path = operator.methodcaller("joinpath", ".lock")
+
+
+def ensure_reset_dir(path):
+ """
+ ensures the given path is an empty directory
+ """
+ if path.exists():
rm_rf(path)
- path.mkdir()
-
-
+ path.mkdir()
+
+
def on_rm_rf_error(func, path, exc, **kwargs):
"""Handles known read-only errors during rmtree.
-
+
The returned value is used only by our own tests.
"""
start_path = kwargs["start_path"]
exctype, excvalue = exc[:2]
-
+
# another process removed the file in the middle of the "rm_rf" (xdist for example)
# more context: https://github.com/pytest-dev/pytest/issues/5974#issuecomment-543799018
if isinstance(excvalue, OSError) and excvalue.errno == errno.ENOENT:
@@ -111,270 +111,270 @@ def rm_rf(path):
shutil.rmtree(str(path), onerror=onerror)
-def find_prefixed(root, prefix):
- """finds all elements in root that begin with the prefix, case insensitive"""
- l_prefix = prefix.lower()
- for x in root.iterdir():
- if x.name.lower().startswith(l_prefix):
- yield x
-
-
-def extract_suffixes(iter, prefix):
- """
- :param iter: iterator over path names
- :param prefix: expected prefix of the path names
- :returns: the parts of the paths following the prefix
- """
- p_len = len(prefix)
- for p in iter:
- yield p.name[p_len:]
-
-
-def find_suffixes(root, prefix):
- """combines find_prefixes and extract_suffixes
- """
- return extract_suffixes(find_prefixed(root, prefix), prefix)
-
-
-def parse_num(maybe_num):
- """parses number path suffixes, returns -1 on error"""
- try:
- return int(maybe_num)
- except ValueError:
- return -1
-
-
-if six.PY2:
-
- def _max(iterable, default):
- """needed due to python2.7 lacking the default argument for max"""
- return reduce(max, iterable, default)
-
-
-else:
- _max = max
-
-
-def _force_symlink(root, target, link_to):
- """helper to create the current symlink
-
- it's full of race conditions that are reasonably ok to ignore
- for the context of best effort linking to the latest testrun
-
- the presumption being thatin case of much parallelism
- the inaccuracy is going to be acceptable
- """
- current_symlink = root.joinpath(target)
- try:
- current_symlink.unlink()
- except OSError:
- pass
- try:
- current_symlink.symlink_to(link_to)
- except Exception:
- pass
-
-
-def make_numbered_dir(root, prefix):
- """create a directory with an increased number as suffix for the given prefix"""
- for i in range(10):
- # try up to 10 times to create the folder
- max_existing = _max(map(parse_num, find_suffixes(root, prefix)), default=-1)
- new_number = max_existing + 1
- new_path = root.joinpath("{}{}".format(prefix, new_number))
- try:
- new_path.mkdir()
- except Exception:
- pass
- else:
- _force_symlink(root, prefix + "current", new_path)
- return new_path
- else:
- raise EnvironmentError(
- "could not create numbered dir with prefix "
- "{prefix} in {root} after 10 tries".format(prefix=prefix, root=root)
- )
-
-
-def create_cleanup_lock(p):
- """crates a lock to prevent premature folder cleanup"""
- lock_path = get_lock_path(p)
- try:
- fd = os.open(str(lock_path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644)
- except OSError as e:
- if e.errno == errno.EEXIST:
- six.raise_from(
- EnvironmentError("cannot create lockfile in {path}".format(path=p)), e
- )
- else:
- raise
- else:
- pid = os.getpid()
- spid = str(pid)
- if not isinstance(spid, bytes):
- spid = spid.encode("ascii")
- os.write(fd, spid)
- os.close(fd)
- if not lock_path.is_file():
- raise EnvironmentError("lock path got renamed after successful creation")
- return lock_path
-
-
-def register_cleanup_lock_removal(lock_path, register=atexit.register):
- """registers a cleanup function for removing a lock, by default on atexit"""
- pid = os.getpid()
-
- def cleanup_on_exit(lock_path=lock_path, original_pid=pid):
- current_pid = os.getpid()
- if current_pid != original_pid:
- # fork
- return
- try:
- lock_path.unlink()
- except (OSError, IOError):
- pass
-
- return register(cleanup_on_exit)
-
-
-def maybe_delete_a_numbered_dir(path):
- """removes a numbered directory if its lock can be obtained and it does not seem to be in use"""
- lock_path = None
- try:
- lock_path = create_cleanup_lock(path)
- parent = path.parent
-
- garbage = parent.joinpath("garbage-{}".format(uuid.uuid4()))
- path.rename(garbage)
+def find_prefixed(root, prefix):
+ """finds all elements in root that begin with the prefix, case insensitive"""
+ l_prefix = prefix.lower()
+ for x in root.iterdir():
+ if x.name.lower().startswith(l_prefix):
+ yield x
+
+
+def extract_suffixes(iter, prefix):
+ """
+ :param iter: iterator over path names
+ :param prefix: expected prefix of the path names
+ :returns: the parts of the paths following the prefix
+ """
+ p_len = len(prefix)
+ for p in iter:
+ yield p.name[p_len:]
+
+
+def find_suffixes(root, prefix):
+ """combines find_prefixes and extract_suffixes
+ """
+ return extract_suffixes(find_prefixed(root, prefix), prefix)
+
+
+def parse_num(maybe_num):
+ """parses number path suffixes, returns -1 on error"""
+ try:
+ return int(maybe_num)
+ except ValueError:
+ return -1
+
+
+if six.PY2:
+
+ def _max(iterable, default):
+ """needed due to python2.7 lacking the default argument for max"""
+ return reduce(max, iterable, default)
+
+
+else:
+ _max = max
+
+
+def _force_symlink(root, target, link_to):
+ """helper to create the current symlink
+
+ it's full of race conditions that are reasonably ok to ignore
+ for the context of best effort linking to the latest testrun
+
+ the presumption being thatin case of much parallelism
+ the inaccuracy is going to be acceptable
+ """
+ current_symlink = root.joinpath(target)
+ try:
+ current_symlink.unlink()
+ except OSError:
+ pass
+ try:
+ current_symlink.symlink_to(link_to)
+ except Exception:
+ pass
+
+
+def make_numbered_dir(root, prefix):
+ """create a directory with an increased number as suffix for the given prefix"""
+ for i in range(10):
+ # try up to 10 times to create the folder
+ max_existing = _max(map(parse_num, find_suffixes(root, prefix)), default=-1)
+ new_number = max_existing + 1
+ new_path = root.joinpath("{}{}".format(prefix, new_number))
+ try:
+ new_path.mkdir()
+ except Exception:
+ pass
+ else:
+ _force_symlink(root, prefix + "current", new_path)
+ return new_path
+ else:
+ raise EnvironmentError(
+ "could not create numbered dir with prefix "
+ "{prefix} in {root} after 10 tries".format(prefix=prefix, root=root)
+ )
+
+
+def create_cleanup_lock(p):
+ """crates a lock to prevent premature folder cleanup"""
+ lock_path = get_lock_path(p)
+ try:
+ fd = os.open(str(lock_path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644)
+ except OSError as e:
+ if e.errno == errno.EEXIST:
+ six.raise_from(
+ EnvironmentError("cannot create lockfile in {path}".format(path=p)), e
+ )
+ else:
+ raise
+ else:
+ pid = os.getpid()
+ spid = str(pid)
+ if not isinstance(spid, bytes):
+ spid = spid.encode("ascii")
+ os.write(fd, spid)
+ os.close(fd)
+ if not lock_path.is_file():
+ raise EnvironmentError("lock path got renamed after successful creation")
+ return lock_path
+
+
+def register_cleanup_lock_removal(lock_path, register=atexit.register):
+ """registers a cleanup function for removing a lock, by default on atexit"""
+ pid = os.getpid()
+
+ def cleanup_on_exit(lock_path=lock_path, original_pid=pid):
+ current_pid = os.getpid()
+ if current_pid != original_pid:
+ # fork
+ return
+ try:
+ lock_path.unlink()
+ except (OSError, IOError):
+ pass
+
+ return register(cleanup_on_exit)
+
+
+def maybe_delete_a_numbered_dir(path):
+ """removes a numbered directory if its lock can be obtained and it does not seem to be in use"""
+ lock_path = None
+ try:
+ lock_path = create_cleanup_lock(path)
+ parent = path.parent
+
+ garbage = parent.joinpath("garbage-{}".format(uuid.uuid4()))
+ path.rename(garbage)
rm_rf(garbage)
- except (OSError, EnvironmentError):
- # known races:
- # * other process did a cleanup at the same time
- # * deletable folder was found
- # * process cwd (Windows)
- return
- finally:
- # if we created the lock, ensure we remove it even if we failed
- # to properly remove the numbered dir
- if lock_path is not None:
- try:
- lock_path.unlink()
- except (OSError, IOError):
- pass
-
-
-def ensure_deletable(path, consider_lock_dead_if_created_before):
- """checks if a lock exists and breaks it if its considered dead"""
- if path.is_symlink():
- return False
- lock = get_lock_path(path)
- if not lock.exists():
- return True
- try:
- lock_time = lock.stat().st_mtime
- except Exception:
- return False
- else:
- if lock_time < consider_lock_dead_if_created_before:
- lock.unlink()
- return True
- else:
- return False
-
-
-def try_cleanup(path, consider_lock_dead_if_created_before):
- """tries to cleanup a folder if we can ensure it's deletable"""
- if ensure_deletable(path, consider_lock_dead_if_created_before):
- maybe_delete_a_numbered_dir(path)
-
-
-def cleanup_candidates(root, prefix, keep):
- """lists candidates for numbered directories to be removed - follows py.path"""
- max_existing = _max(map(parse_num, find_suffixes(root, prefix)), default=-1)
- max_delete = max_existing - keep
- paths = find_prefixed(root, prefix)
- paths, paths2 = itertools.tee(paths)
- numbers = map(parse_num, extract_suffixes(paths2, prefix))
- for path, number in zip(paths, numbers):
- if number <= max_delete:
- yield path
-
-
-def cleanup_numbered_dir(root, prefix, keep, consider_lock_dead_if_created_before):
- """cleanup for lock driven numbered directories"""
- for path in cleanup_candidates(root, prefix, keep):
- try_cleanup(path, consider_lock_dead_if_created_before)
- for path in root.glob("garbage-*"):
- try_cleanup(path, consider_lock_dead_if_created_before)
-
-
-def make_numbered_dir_with_cleanup(root, prefix, keep, lock_timeout):
- """creates a numbered dir with a cleanup lock and removes old ones"""
- e = None
- for i in range(10):
- try:
- p = make_numbered_dir(root, prefix)
- lock_path = create_cleanup_lock(p)
- register_cleanup_lock_removal(lock_path)
- except Exception as exc:
- e = exc
- else:
- consider_lock_dead_if_created_before = p.stat().st_mtime - lock_timeout
- cleanup_numbered_dir(
- root=root,
- prefix=prefix,
- keep=keep,
- consider_lock_dead_if_created_before=consider_lock_dead_if_created_before,
- )
- return p
- assert e is not None
- raise e
-
-
-def resolve_from_str(input, root):
- assert not isinstance(input, Path), "would break on py2"
- root = Path(root)
- input = expanduser(input)
- input = expandvars(input)
- if isabs(input):
- return Path(input)
- else:
- return root.joinpath(input)
-
-
-def fnmatch_ex(pattern, path):
- """FNMatcher port from py.path.common which works with PurePath() instances.
-
- The difference between this algorithm and PurePath.match() is that the latter matches "**" glob expressions
- for each part of the path, while this algorithm uses the whole path instead.
-
- For example:
- "tests/foo/bar/doc/test_foo.py" matches pattern "tests/**/doc/test*.py" with this algorithm, but not with
- PurePath.match().
-
- This algorithm was ported to keep backward-compatibility with existing settings which assume paths match according
- this logic.
-
- References:
- * https://bugs.python.org/issue29249
- * https://bugs.python.org/issue34731
- """
- path = PurePath(path)
- iswin32 = sys.platform.startswith("win")
-
- if iswin32 and sep not in pattern and posix_sep in pattern:
- # Running on Windows, the pattern has no Windows path separators,
- # and the pattern has one or more Posix path separators. Replace
- # the Posix path separators with the Windows path separator.
- pattern = pattern.replace(posix_sep, sep)
-
- if sep not in pattern:
- name = path.name
- else:
- name = six.text_type(path)
- return fnmatch.fnmatch(name, pattern)
-
-
-def parts(s):
- parts = s.split(sep)
- return {sep.join(parts[: i + 1]) or sep for i in range(len(parts))}
+ except (OSError, EnvironmentError):
+ # known races:
+ # * other process did a cleanup at the same time
+ # * deletable folder was found
+ # * process cwd (Windows)
+ return
+ finally:
+ # if we created the lock, ensure we remove it even if we failed
+ # to properly remove the numbered dir
+ if lock_path is not None:
+ try:
+ lock_path.unlink()
+ except (OSError, IOError):
+ pass
+
+
+def ensure_deletable(path, consider_lock_dead_if_created_before):
+ """checks if a lock exists and breaks it if its considered dead"""
+ if path.is_symlink():
+ return False
+ lock = get_lock_path(path)
+ if not lock.exists():
+ return True
+ try:
+ lock_time = lock.stat().st_mtime
+ except Exception:
+ return False
+ else:
+ if lock_time < consider_lock_dead_if_created_before:
+ lock.unlink()
+ return True
+ else:
+ return False
+
+
+def try_cleanup(path, consider_lock_dead_if_created_before):
+ """tries to cleanup a folder if we can ensure it's deletable"""
+ if ensure_deletable(path, consider_lock_dead_if_created_before):
+ maybe_delete_a_numbered_dir(path)
+
+
+def cleanup_candidates(root, prefix, keep):
+ """lists candidates for numbered directories to be removed - follows py.path"""
+ max_existing = _max(map(parse_num, find_suffixes(root, prefix)), default=-1)
+ max_delete = max_existing - keep
+ paths = find_prefixed(root, prefix)
+ paths, paths2 = itertools.tee(paths)
+ numbers = map(parse_num, extract_suffixes(paths2, prefix))
+ for path, number in zip(paths, numbers):
+ if number <= max_delete:
+ yield path
+
+
+def cleanup_numbered_dir(root, prefix, keep, consider_lock_dead_if_created_before):
+ """cleanup for lock driven numbered directories"""
+ for path in cleanup_candidates(root, prefix, keep):
+ try_cleanup(path, consider_lock_dead_if_created_before)
+ for path in root.glob("garbage-*"):
+ try_cleanup(path, consider_lock_dead_if_created_before)
+
+
+def make_numbered_dir_with_cleanup(root, prefix, keep, lock_timeout):
+ """creates a numbered dir with a cleanup lock and removes old ones"""
+ e = None
+ for i in range(10):
+ try:
+ p = make_numbered_dir(root, prefix)
+ lock_path = create_cleanup_lock(p)
+ register_cleanup_lock_removal(lock_path)
+ except Exception as exc:
+ e = exc
+ else:
+ consider_lock_dead_if_created_before = p.stat().st_mtime - lock_timeout
+ cleanup_numbered_dir(
+ root=root,
+ prefix=prefix,
+ keep=keep,
+ consider_lock_dead_if_created_before=consider_lock_dead_if_created_before,
+ )
+ return p
+ assert e is not None
+ raise e
+
+
+def resolve_from_str(input, root):
+ assert not isinstance(input, Path), "would break on py2"
+ root = Path(root)
+ input = expanduser(input)
+ input = expandvars(input)
+ if isabs(input):
+ return Path(input)
+ else:
+ return root.joinpath(input)
+
+
+def fnmatch_ex(pattern, path):
+ """FNMatcher port from py.path.common which works with PurePath() instances.
+
+ The difference between this algorithm and PurePath.match() is that the latter matches "**" glob expressions
+ for each part of the path, while this algorithm uses the whole path instead.
+
+ For example:
+ "tests/foo/bar/doc/test_foo.py" matches pattern "tests/**/doc/test*.py" with this algorithm, but not with
+ PurePath.match().
+
+ This algorithm was ported to keep backward-compatibility with existing settings which assume paths match according
+ this logic.
+
+ References:
+ * https://bugs.python.org/issue29249
+ * https://bugs.python.org/issue34731
+ """
+ path = PurePath(path)
+ iswin32 = sys.platform.startswith("win")
+
+ if iswin32 and sep not in pattern and posix_sep in pattern:
+ # Running on Windows, the pattern has no Windows path separators,
+ # and the pattern has one or more Posix path separators. Replace
+ # the Posix path separators with the Windows path separator.
+ pattern = pattern.replace(posix_sep, sep)
+
+ if sep not in pattern:
+ name = path.name
+ else:
+ name = six.text_type(path)
+ return fnmatch.fnmatch(name, pattern)
+
+
+def parts(s):
+ parts = s.split(sep)
+ return {sep.join(parts[: i + 1]) or sep for i in range(len(parts))}
diff --git a/contrib/python/pytest/py2/_pytest/pytester.py b/contrib/python/pytest/py2/_pytest/pytester.py
index b907216770..f1d739c991 100644
--- a/contrib/python/pytest/py2/_pytest/pytester.py
+++ b/contrib/python/pytest/py2/_pytest/pytester.py
@@ -1,342 +1,342 @@
# -*- coding: utf-8 -*-
-"""(disabled by default) support for testing pytest and pytest plugins."""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import codecs
-import gc
-import os
-import platform
-import re
-import subprocess
-import sys
-import time
-import traceback
-from fnmatch import fnmatch
-from weakref import WeakKeyDictionary
-
-import py
-import six
-
-import pytest
-from _pytest._code import Source
+"""(disabled by default) support for testing pytest and pytest plugins."""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import codecs
+import gc
+import os
+import platform
+import re
+import subprocess
+import sys
+import time
+import traceback
+from fnmatch import fnmatch
+from weakref import WeakKeyDictionary
+
+import py
+import six
+
+import pytest
+from _pytest._code import Source
from _pytest._io.saferepr import saferepr
-from _pytest.assertion.rewrite import AssertionRewritingHook
-from _pytest.capture import MultiCapture
-from _pytest.capture import SysCapture
-from _pytest.compat import safe_str
+from _pytest.assertion.rewrite import AssertionRewritingHook
+from _pytest.capture import MultiCapture
+from _pytest.capture import SysCapture
+from _pytest.compat import safe_str
from _pytest.compat import Sequence
-from _pytest.main import EXIT_INTERRUPTED
-from _pytest.main import EXIT_OK
-from _pytest.main import Session
+from _pytest.main import EXIT_INTERRUPTED
+from _pytest.main import EXIT_OK
+from _pytest.main import Session
from _pytest.monkeypatch import MonkeyPatch
-from _pytest.pathlib import Path
-
-IGNORE_PAM = [ # filenames added when obtaining details about the current user
- u"/var/lib/sss/mc/passwd"
-]
-
-
-def pytest_addoption(parser):
- 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"),
- help=(
- "run pytest sub runs in tests using an 'inprocess' "
- "or 'subprocess' (python -m main) method"
- ),
- )
-
- parser.addini(
- "pytester_example_dir", help="directory to take the pytester example files from"
- )
-
-
-def pytest_configure(config):
- if config.getvalue("lsof"):
- checker = LsofFdLeakChecker()
- if checker.matching_platform():
- config.pluginmanager.register(checker)
-
+from _pytest.pathlib import Path
+
+IGNORE_PAM = [ # filenames added when obtaining details about the current user
+ u"/var/lib/sss/mc/passwd"
+]
+
+
+def pytest_addoption(parser):
+ 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"),
+ help=(
+ "run pytest sub runs in tests using an 'inprocess' "
+ "or 'subprocess' (python -m main) method"
+ ),
+ )
+
+ parser.addini(
+ "pytester_example_dir", help="directory to take the pytester example files from"
+ )
+
+
+def pytest_configure(config):
+ if config.getvalue("lsof"):
+ checker = LsofFdLeakChecker()
+ if checker.matching_platform():
+ config.pluginmanager.register(checker)
+
config.addinivalue_line(
"markers",
"pytester_example_path(*path_segments): join the given path "
"segments to `pytester_example_dir` for this test.",
)
-
-def raise_on_kwargs(kwargs):
+
+def raise_on_kwargs(kwargs):
__tracebackhide__ = True
if kwargs: # pragma: no branch
raise TypeError(
"Unexpected keyword arguments: {}".format(", ".join(sorted(kwargs)))
)
-
-
-class LsofFdLeakChecker(object):
- def get_open_files(self):
- out = self._exec_lsof()
- open_files = self._parse_lsof_output(out)
- return open_files
-
- def _exec_lsof(self):
- pid = os.getpid()
+
+
+class LsofFdLeakChecker(object):
+ def get_open_files(self):
+ out = self._exec_lsof()
+ open_files = self._parse_lsof_output(out)
+ return open_files
+
+ def _exec_lsof(self):
+ pid = os.getpid()
# py3: use subprocess.DEVNULL directly.
with open(os.devnull, "wb") as devnull:
return subprocess.check_output(
("lsof", "-Ffn0", "-p", str(pid)), stderr=devnull
).decode()
-
- def _parse_lsof_output(self, out):
- def isopen(line):
- return line.startswith("f") and (
- "deleted" not in line
- and "mem" not in line
- and "txt" not in line
- and "cwd" not in line
- )
-
- open_files = []
-
- for line in out.split("\n"):
- if isopen(line):
- 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))
-
- return open_files
-
- def matching_platform(self):
- try:
+
+ def _parse_lsof_output(self, out):
+ def isopen(line):
+ return line.startswith("f") and (
+ "deleted" not in line
+ and "mem" not in line
+ and "txt" not in line
+ and "cwd" not in line
+ )
+
+ open_files = []
+
+ for line in out.split("\n"):
+ if isopen(line):
+ 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))
+
+ return open_files
+
+ def matching_platform(self):
+ try:
subprocess.check_output(("lsof", "-v"))
except (OSError, subprocess.CalledProcessError):
- return False
- else:
- return True
-
- @pytest.hookimpl(hookwrapper=True, tryfirst=True)
- def pytest_runtest_protocol(self, item):
- lines1 = self.get_open_files()
- yield
- if hasattr(sys, "pypy_version_info"):
- gc.collect()
- lines2 = self.get_open_files()
-
- new_fds = {t[0] for t in lines2} - {t[0] for t in lines1}
- leaked_files = [t for t in lines2 if t[0] in new_fds]
- if leaked_files:
- error = []
- error.append("***** %s FD leakage detected" % len(leaked_files))
- error.extend([str(f) for f in leaked_files])
- error.append("*** Before:")
- error.extend([str(f) for f in lines1])
- error.append("*** After:")
- error.extend([str(f) for f in lines2])
- error.append(error[0])
- error.append("*** function %s:%s: %s " % item.location)
- error.append("See issue #2366")
- item.warn(pytest.PytestWarning("\n".join(error)))
-
-
-# used at least by pytest-xdist plugin
-
-
-@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 PytestArg(request)
-
-
-class PytestArg(object):
- def __init__(self, request):
- self.request = request
-
- def gethookrecorder(self, hook):
- hookrecorder = HookRecorder(hook._pm)
- self.request.addfinalizer(hookrecorder.finish_recording)
- return hookrecorder
-
-
-def get_public_names(values):
- """Only return names from iterator values without a leading underscore."""
- return [x for x in values if x[0] != "_"]
-
-
-class ParsedCall(object):
- def __init__(self, name, kwargs):
- self.__dict__.update(kwargs)
- self._name = name
-
- def __repr__(self):
- d = self.__dict__.copy()
- del d["_name"]
- return "<ParsedCall %r(**%r)>" % (self._name, d)
-
-
-class HookRecorder(object):
- """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.
-
- """
-
- def __init__(self, pluginmanager):
- self._pluginmanager = pluginmanager
- self.calls = []
-
- def before(hook_name, hook_impls, kwargs):
- self.calls.append(ParsedCall(hook_name, kwargs))
-
- def after(outcome, hook_name, hook_impls, kwargs):
- pass
-
- self._undo_wrapping = pluginmanager.add_hookcall_monitoring(before, after)
-
- def finish_recording(self):
- self._undo_wrapping()
-
- def getcalls(self, names):
- if isinstance(names, str):
- names = names.split()
- return [call for call in self.calls if call._name in names]
-
- def assert_contains(self, entries):
- __tracebackhide__ = True
- i = 0
- entries = list(entries)
- backlocals = sys._getframe(1).f_locals
- while entries:
- name, check = entries.pop(0)
- for ind, call in enumerate(self.calls[i:]):
- if call._name == name:
- print("NAMEMATCH", name, call)
- if eval(check, backlocals, call.__dict__):
- print("CHECKERMATCH", repr(check), "->", call)
- else:
- print("NOCHECKERMATCH", repr(check), "-", call)
- continue
- i += ind + 1
- break
- print("NONAMEMATCH", name, "with", call)
- else:
- pytest.fail("could not find %r check %r" % (name, check))
-
- def popcall(self, name):
- __tracebackhide__ = True
- for i, call in enumerate(self.calls):
- if call._name == name:
- del self.calls[i]
- return call
- lines = ["could not find call %r, in:" % (name,)]
- lines.extend([" %s" % x for x in self.calls])
- pytest.fail("\n".join(lines))
-
- def getcall(self, name):
- values = self.getcalls(name)
- assert len(values) == 1, (name, values)
- return values[0]
-
- # functionality for test reports
-
- def getreports(self, names="pytest_runtest_logreport pytest_collectreport"):
- return [x.report for x in self.getcalls(names)]
-
- def matchreport(
- self,
- inamepart="",
- names="pytest_runtest_logreport pytest_collectreport",
- when=None,
- ):
- """return a testreport whose dotted import path matches"""
- values = []
- for rep in self.getreports(names=names):
+ return False
+ else:
+ return True
+
+ @pytest.hookimpl(hookwrapper=True, tryfirst=True)
+ def pytest_runtest_protocol(self, item):
+ lines1 = self.get_open_files()
+ yield
+ if hasattr(sys, "pypy_version_info"):
+ gc.collect()
+ lines2 = self.get_open_files()
+
+ new_fds = {t[0] for t in lines2} - {t[0] for t in lines1}
+ leaked_files = [t for t in lines2 if t[0] in new_fds]
+ if leaked_files:
+ error = []
+ error.append("***** %s FD leakage detected" % len(leaked_files))
+ error.extend([str(f) for f in leaked_files])
+ error.append("*** Before:")
+ error.extend([str(f) for f in lines1])
+ error.append("*** After:")
+ error.extend([str(f) for f in lines2])
+ error.append(error[0])
+ error.append("*** function %s:%s: %s " % item.location)
+ error.append("See issue #2366")
+ item.warn(pytest.PytestWarning("\n".join(error)))
+
+
+# used at least by pytest-xdist plugin
+
+
+@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 PytestArg(request)
+
+
+class PytestArg(object):
+ def __init__(self, request):
+ self.request = request
+
+ def gethookrecorder(self, hook):
+ hookrecorder = HookRecorder(hook._pm)
+ self.request.addfinalizer(hookrecorder.finish_recording)
+ return hookrecorder
+
+
+def get_public_names(values):
+ """Only return names from iterator values without a leading underscore."""
+ return [x for x in values if x[0] != "_"]
+
+
+class ParsedCall(object):
+ def __init__(self, name, kwargs):
+ self.__dict__.update(kwargs)
+ self._name = name
+
+ def __repr__(self):
+ d = self.__dict__.copy()
+ del d["_name"]
+ return "<ParsedCall %r(**%r)>" % (self._name, d)
+
+
+class HookRecorder(object):
+ """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.
+
+ """
+
+ def __init__(self, pluginmanager):
+ self._pluginmanager = pluginmanager
+ self.calls = []
+
+ def before(hook_name, hook_impls, kwargs):
+ self.calls.append(ParsedCall(hook_name, kwargs))
+
+ def after(outcome, hook_name, hook_impls, kwargs):
+ pass
+
+ self._undo_wrapping = pluginmanager.add_hookcall_monitoring(before, after)
+
+ def finish_recording(self):
+ self._undo_wrapping()
+
+ def getcalls(self, names):
+ if isinstance(names, str):
+ names = names.split()
+ return [call for call in self.calls if call._name in names]
+
+ def assert_contains(self, entries):
+ __tracebackhide__ = True
+ i = 0
+ entries = list(entries)
+ backlocals = sys._getframe(1).f_locals
+ while entries:
+ name, check = entries.pop(0)
+ for ind, call in enumerate(self.calls[i:]):
+ if call._name == name:
+ print("NAMEMATCH", name, call)
+ if eval(check, backlocals, call.__dict__):
+ print("CHECKERMATCH", repr(check), "->", call)
+ else:
+ print("NOCHECKERMATCH", repr(check), "-", call)
+ continue
+ i += ind + 1
+ break
+ print("NONAMEMATCH", name, "with", call)
+ else:
+ pytest.fail("could not find %r check %r" % (name, check))
+
+ def popcall(self, name):
+ __tracebackhide__ = True
+ for i, call in enumerate(self.calls):
+ if call._name == name:
+ del self.calls[i]
+ return call
+ lines = ["could not find call %r, in:" % (name,)]
+ lines.extend([" %s" % x for x in self.calls])
+ pytest.fail("\n".join(lines))
+
+ def getcall(self, name):
+ values = self.getcalls(name)
+ assert len(values) == 1, (name, values)
+ return values[0]
+
+ # functionality for test reports
+
+ def getreports(self, names="pytest_runtest_logreport pytest_collectreport"):
+ return [x.report for x in self.getcalls(names)]
+
+ def matchreport(
+ self,
+ inamepart="",
+ names="pytest_runtest_logreport pytest_collectreport",
+ when=None,
+ ):
+ """return a testreport whose dotted import path matches"""
+ values = []
+ for rep in self.getreports(names=names):
if not when and rep.when != "call" and rep.passed:
# setup/teardown passing reports - let's ignore those
- continue
+ continue
if when and rep.when != when:
continue
- if not inamepart or inamepart in rep.nodeid.split("::"):
- values.append(rep)
- if not values:
- raise ValueError(
- "could not find test report matching %r: "
- "no test reports at all!" % (inamepart,)
- )
- if len(values) > 1:
- raise ValueError(
- "found 2 or more testreports matching %r: %s" % (inamepart, values)
- )
- return values[0]
-
- def getfailures(self, names="pytest_runtest_logreport pytest_collectreport"):
- return [rep for rep in self.getreports(names) if rep.failed]
-
- def getfailedcollections(self):
- return self.getfailures("pytest_collectreport")
-
- def listoutcomes(self):
- passed = []
- skipped = []
- failed = []
- for rep in self.getreports("pytest_collectreport pytest_runtest_logreport"):
- if rep.passed:
+ if not inamepart or inamepart in rep.nodeid.split("::"):
+ values.append(rep)
+ if not values:
+ raise ValueError(
+ "could not find test report matching %r: "
+ "no test reports at all!" % (inamepart,)
+ )
+ if len(values) > 1:
+ raise ValueError(
+ "found 2 or more testreports matching %r: %s" % (inamepart, values)
+ )
+ return values[0]
+
+ def getfailures(self, names="pytest_runtest_logreport pytest_collectreport"):
+ return [rep for rep in self.getreports(names) if rep.failed]
+
+ def getfailedcollections(self):
+ return self.getfailures("pytest_collectreport")
+
+ def listoutcomes(self):
+ passed = []
+ skipped = []
+ failed = []
+ for rep in self.getreports("pytest_collectreport pytest_runtest_logreport"):
+ if rep.passed:
if rep.when == "call":
- passed.append(rep)
- elif rep.skipped:
- skipped.append(rep)
+ passed.append(rep)
+ elif rep.skipped:
+ skipped.append(rep)
else:
assert rep.failed, "Unexpected outcome: {!r}".format(rep)
- failed.append(rep)
- return passed, skipped, failed
-
- def countoutcomes(self):
- return [len(x) for x in self.listoutcomes()]
-
- def assertoutcome(self, passed=0, skipped=0, failed=0):
- realpassed, realskipped, realfailed = self.listoutcomes()
- assert passed == len(realpassed)
- assert skipped == len(realskipped)
- assert failed == len(realfailed)
-
- def clear(self):
- self.calls[:] = []
-
-
-@pytest.fixture
-def linecomp(request):
- return LineComp()
-
-
-@pytest.fixture(name="LineMatcher")
-def LineMatcher_fixture(request):
- return LineMatcher
-
-
-@pytest.fixture
-def testdir(request, tmpdir_factory):
- return Testdir(request, tmpdir_factory)
-
-
+ failed.append(rep)
+ return passed, skipped, failed
+
+ def countoutcomes(self):
+ return [len(x) for x in self.listoutcomes()]
+
+ def assertoutcome(self, passed=0, skipped=0, failed=0):
+ realpassed, realskipped, realfailed = self.listoutcomes()
+ assert passed == len(realpassed)
+ assert skipped == len(realskipped)
+ assert failed == len(realfailed)
+
+ def clear(self):
+ self.calls[:] = []
+
+
+@pytest.fixture
+def linecomp(request):
+ return LineComp()
+
+
+@pytest.fixture(name="LineMatcher")
+def LineMatcher_fixture(request):
+ return LineMatcher
+
+
+@pytest.fixture
+def testdir(request, tmpdir_factory):
+ return Testdir(request, tmpdir_factory)
+
+
@pytest.fixture
def _sys_snapshot():
snappaths = SysPathsSnapshot()
@@ -355,152 +355,152 @@ def _config_for_test():
config._ensure_unconfigure() # cleanup, e.g. capman closing tmpfiles.
-rex_outcome = re.compile(r"(\d+) ([\w-]+)")
-
-
-class RunResult(object):
- """The result of running a command.
-
- Attributes:
-
- :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
- :stderr: :py:class:`LineMatcher` of stderr
- :duration: duration in seconds
-
- """
-
- def __init__(self, ret, outlines, errlines, duration):
- self.ret = ret
- self.outlines = outlines
- self.errlines = errlines
- self.stdout = LineMatcher(outlines)
- self.stderr = LineMatcher(errlines)
- self.duration = duration
-
+rex_outcome = re.compile(r"(\d+) ([\w-]+)")
+
+
+class RunResult(object):
+ """The result of running a command.
+
+ Attributes:
+
+ :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
+ :stderr: :py:class:`LineMatcher` of stderr
+ :duration: duration in seconds
+
+ """
+
+ def __init__(self, ret, outlines, errlines, duration):
+ self.ret = ret
+ self.outlines = outlines
+ self.errlines = errlines
+ self.stdout = LineMatcher(outlines)
+ self.stderr = LineMatcher(errlines)
+ self.duration = duration
+
def __repr__(self):
return (
"<RunResult ret=%r len(stdout.lines)=%d len(stderr.lines)=%d duration=%.2fs>"
% (self.ret, len(self.stdout.lines), len(self.stderr.lines), self.duration)
)
- def parseoutcomes(self):
- """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)
- if outcomes:
- d = {}
- for num, cat in outcomes:
- d[cat] = int(num)
- return d
- raise ValueError("Pytest terminal report not found")
-
- def assert_outcomes(
- self, passed=0, skipped=0, failed=0, error=0, xpassed=0, xfailed=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.
-
- """
- d = self.parseoutcomes()
- obtained = {
- "passed": d.get("passed", 0),
- "skipped": d.get("skipped", 0),
- "failed": d.get("failed", 0),
- "error": d.get("error", 0),
- "xpassed": d.get("xpassed", 0),
- "xfailed": d.get("xfailed", 0),
- }
- expected = {
- "passed": passed,
- "skipped": skipped,
- "failed": failed,
- "error": error,
- "xpassed": xpassed,
- "xfailed": xfailed,
- }
- assert obtained == expected
-
-
-class CwdSnapshot(object):
- def __init__(self):
- self.__saved = os.getcwd()
-
- def restore(self):
- os.chdir(self.__saved)
-
-
-class SysModulesSnapshot(object):
- def __init__(self, preserve=None):
- self.__preserve = preserve
- self.__saved = dict(sys.modules)
-
- def restore(self):
- if self.__preserve:
- self.__saved.update(
- (k, m) for k, m in sys.modules.items() if self.__preserve(k)
- )
- sys.modules.clear()
- sys.modules.update(self.__saved)
-
-
-class SysPathsSnapshot(object):
- def __init__(self):
- self.__saved = list(sys.path), list(sys.meta_path)
-
- def restore(self):
- sys.path[:], sys.meta_path[:] = self.__saved
-
-
-class Testdir(object):
- """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 their current working directory.
-
- Attributes:
-
- :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 depends on
- the method using them so refer to them for details.
-
- """
-
+ def parseoutcomes(self):
+ """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)
+ if outcomes:
+ d = {}
+ for num, cat in outcomes:
+ d[cat] = int(num)
+ return d
+ raise ValueError("Pytest terminal report not found")
+
+ def assert_outcomes(
+ self, passed=0, skipped=0, failed=0, error=0, xpassed=0, xfailed=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.
+
+ """
+ d = self.parseoutcomes()
+ obtained = {
+ "passed": d.get("passed", 0),
+ "skipped": d.get("skipped", 0),
+ "failed": d.get("failed", 0),
+ "error": d.get("error", 0),
+ "xpassed": d.get("xpassed", 0),
+ "xfailed": d.get("xfailed", 0),
+ }
+ expected = {
+ "passed": passed,
+ "skipped": skipped,
+ "failed": failed,
+ "error": error,
+ "xpassed": xpassed,
+ "xfailed": xfailed,
+ }
+ assert obtained == expected
+
+
+class CwdSnapshot(object):
+ def __init__(self):
+ self.__saved = os.getcwd()
+
+ def restore(self):
+ os.chdir(self.__saved)
+
+
+class SysModulesSnapshot(object):
+ def __init__(self, preserve=None):
+ self.__preserve = preserve
+ self.__saved = dict(sys.modules)
+
+ def restore(self):
+ if self.__preserve:
+ self.__saved.update(
+ (k, m) for k, m in sys.modules.items() if self.__preserve(k)
+ )
+ sys.modules.clear()
+ sys.modules.update(self.__saved)
+
+
+class SysPathsSnapshot(object):
+ def __init__(self):
+ self.__saved = list(sys.path), list(sys.meta_path)
+
+ def restore(self):
+ sys.path[:], sys.meta_path[:] = self.__saved
+
+
+class Testdir(object):
+ """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 their current working directory.
+
+ Attributes:
+
+ :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 depends on
+ the method using them so refer to them for details.
+
+ """
+
CLOSE_STDIN = object
- class TimeoutExpired(Exception):
- pass
-
- def __init__(self, request, tmpdir_factory):
- self.request = request
- self._mod_collections = WeakKeyDictionary()
- name = request.function.__name__
- self.tmpdir = tmpdir_factory.mktemp(name, numbered=True)
- self.test_tmproot = tmpdir_factory.mktemp("tmp-" + name, numbered=True)
- self.plugins = []
- self._cwd_snapshot = CwdSnapshot()
- self._sys_path_snapshot = SysPathsSnapshot()
- self._sys_modules_snapshot = self.__take_sys_modules_snapshot()
- self.chdir()
- self.request.addfinalizer(self.finalize)
- method = self.request.config.getoption("--runpytest")
- if method == "inprocess":
- self._runpytest_method = self.runpytest_inprocess
- elif method == "subprocess":
- self._runpytest_method = self.runpytest_subprocess
-
+ class TimeoutExpired(Exception):
+ pass
+
+ def __init__(self, request, tmpdir_factory):
+ self.request = request
+ self._mod_collections = WeakKeyDictionary()
+ name = request.function.__name__
+ self.tmpdir = tmpdir_factory.mktemp(name, numbered=True)
+ self.test_tmproot = tmpdir_factory.mktemp("tmp-" + name, numbered=True)
+ self.plugins = []
+ self._cwd_snapshot = CwdSnapshot()
+ self._sys_path_snapshot = SysPathsSnapshot()
+ self._sys_modules_snapshot = self.__take_sys_modules_snapshot()
+ self.chdir()
+ self.request.addfinalizer(self.finalize)
+ method = self.request.config.getoption("--runpytest")
+ if method == "inprocess":
+ self._runpytest_method = self.runpytest_inprocess
+ elif method == "subprocess":
+ self._runpytest_method = self.runpytest_subprocess
+
mp = self.monkeypatch = MonkeyPatch()
mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self.test_tmproot))
# Ensure no unexpected caching via tox.
@@ -512,523 +512,523 @@ class Testdir(object):
tmphome = str(self.tmpdir)
self._env_run_update = {"HOME": tmphome, "USERPROFILE": tmphome}
- def __repr__(self):
- return "<Testdir %r>" % (self.tmpdir,)
-
- def __str__(self):
- return str(self.tmpdir)
-
- 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.
-
- """
- self._sys_modules_snapshot.restore()
- self._sys_path_snapshot.restore()
- self._cwd_snapshot.restore()
+ def __repr__(self):
+ return "<Testdir %r>" % (self.tmpdir,)
+
+ def __str__(self):
+ return str(self.tmpdir)
+
+ 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.
+
+ """
+ self._sys_modules_snapshot.restore()
+ self._sys_path_snapshot.restore()
+ self._cwd_snapshot.restore()
self.monkeypatch.undo()
-
- def __take_sys_modules_snapshot(self):
- # some zope modules used by twisted-related tests keep internal state
- # and can't be deleted; we had some trouble in the past with
- # `zope.interface` for example
- def preserve_module(name):
- return name.startswith("zope")
-
- return SysModulesSnapshot(preserve=preserve_module)
-
- def make_hook_recorder(self, pluginmanager):
- """Create a new :py:class:`HookRecorder` for a PluginManager."""
- pluginmanager.reprec = reprec = HookRecorder(pluginmanager)
- self.request.addfinalizer(reprec.finish_recording)
- return reprec
-
- def chdir(self):
- """Cd into the temporary directory.
-
- This is done automatically upon instantiation.
-
- """
- self.tmpdir.chdir()
-
- def _makefile(self, ext, args, kwargs, encoding="utf-8"):
- items = list(kwargs.items())
-
- def to_text(s):
- return s.decode(encoding) if isinstance(s, bytes) else six.text_type(s)
-
- if args:
- source = u"\n".join(to_text(x) for x in args)
- basename = self.request.function.__name__
- items.insert(0, (basename, source))
-
- ret = None
- for basename, value in items:
- p = self.tmpdir.join(basename).new(ext=ext)
- p.dirpath().ensure_dir()
- source = Source(value)
- source = u"\n".join(to_text(line) for line in source.lines)
- p.write(source.strip().encode(encoding), "wb")
- if ret is None:
- ret = p
- return ret
-
- def makefile(self, ext, *args, **kwargs):
- r"""Create new file(s) in the testdir.
-
- :param str ext: The extension the file(s) should use, including the dot, e.g. `.py`.
- :param list[str] 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.
- :param kwargs: Each keyword is the name of a file, while the value of it will
- be written as contents of the file.
-
- Examples:
-
- .. code-block:: python
-
- testdir.makefile(".txt", "line1", "line2")
-
- testdir.makefile(".ini", pytest="[pytest]\naddopts=-rs\n")
-
- """
- return self._makefile(ext, args, kwargs)
-
- def makeconftest(self, source):
- """Write a contest.py file with 'source' as contents."""
- return self.makepyfile(conftest=source)
-
- def makeini(self, source):
- """Write a tox.ini file with 'source' as contents."""
- return self.makefile(".ini", tox=source)
-
- def getinicfg(self, source):
- """Return the pytest section from the tox.ini config file."""
- p = self.makeini(source)
- return py.iniconfig.IniConfig(p)["pytest"]
-
- def makepyfile(self, *args, **kwargs):
- """Shortcut for .makefile() with a .py extension."""
- return self._makefile(".py", args, kwargs)
-
- def maketxtfile(self, *args, **kwargs):
- """Shortcut for .makefile() with a .txt extension."""
- return self._makefile(".txt", args, kwargs)
-
- def syspathinsert(self, path=None):
- """Prepend a directory to sys.path, defaults to :py:attr:`tmpdir`.
-
- This is undone automatically when this object dies at the end of each
- test.
- """
- if path is None:
- path = self.tmpdir
-
+
+ def __take_sys_modules_snapshot(self):
+ # some zope modules used by twisted-related tests keep internal state
+ # and can't be deleted; we had some trouble in the past with
+ # `zope.interface` for example
+ def preserve_module(name):
+ return name.startswith("zope")
+
+ return SysModulesSnapshot(preserve=preserve_module)
+
+ def make_hook_recorder(self, pluginmanager):
+ """Create a new :py:class:`HookRecorder` for a PluginManager."""
+ pluginmanager.reprec = reprec = HookRecorder(pluginmanager)
+ self.request.addfinalizer(reprec.finish_recording)
+ return reprec
+
+ def chdir(self):
+ """Cd into the temporary directory.
+
+ This is done automatically upon instantiation.
+
+ """
+ self.tmpdir.chdir()
+
+ def _makefile(self, ext, args, kwargs, encoding="utf-8"):
+ items = list(kwargs.items())
+
+ def to_text(s):
+ return s.decode(encoding) if isinstance(s, bytes) else six.text_type(s)
+
+ if args:
+ source = u"\n".join(to_text(x) for x in args)
+ basename = self.request.function.__name__
+ items.insert(0, (basename, source))
+
+ ret = None
+ for basename, value in items:
+ p = self.tmpdir.join(basename).new(ext=ext)
+ p.dirpath().ensure_dir()
+ source = Source(value)
+ source = u"\n".join(to_text(line) for line in source.lines)
+ p.write(source.strip().encode(encoding), "wb")
+ if ret is None:
+ ret = p
+ return ret
+
+ def makefile(self, ext, *args, **kwargs):
+ r"""Create new file(s) in the testdir.
+
+ :param str ext: The extension the file(s) should use, including the dot, e.g. `.py`.
+ :param list[str] 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.
+ :param kwargs: Each keyword is the name of a file, while the value of it will
+ be written as contents of the file.
+
+ Examples:
+
+ .. code-block:: python
+
+ testdir.makefile(".txt", "line1", "line2")
+
+ testdir.makefile(".ini", pytest="[pytest]\naddopts=-rs\n")
+
+ """
+ return self._makefile(ext, args, kwargs)
+
+ def makeconftest(self, source):
+ """Write a contest.py file with 'source' as contents."""
+ return self.makepyfile(conftest=source)
+
+ def makeini(self, source):
+ """Write a tox.ini file with 'source' as contents."""
+ return self.makefile(".ini", tox=source)
+
+ def getinicfg(self, source):
+ """Return the pytest section from the tox.ini config file."""
+ p = self.makeini(source)
+ return py.iniconfig.IniConfig(p)["pytest"]
+
+ def makepyfile(self, *args, **kwargs):
+ """Shortcut for .makefile() with a .py extension."""
+ return self._makefile(".py", args, kwargs)
+
+ def maketxtfile(self, *args, **kwargs):
+ """Shortcut for .makefile() with a .txt extension."""
+ return self._makefile(".txt", args, kwargs)
+
+ def syspathinsert(self, path=None):
+ """Prepend a directory to sys.path, defaults to :py:attr:`tmpdir`.
+
+ This is undone automatically when this object dies at the end of each
+ test.
+ """
+ if path is None:
+ path = self.tmpdir
+
self.monkeypatch.syspath_prepend(str(path))
-
- def mkdir(self, name):
- """Create a new (sub)directory."""
- return self.tmpdir.mkdir(name)
-
- def mkpydir(self, name):
- """Create a new 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)
- p.ensure("__init__.py")
- return p
-
- def copy_example(self, name=None):
- import warnings
- from _pytest.warning_types import PYTESTER_COPY_EXAMPLE
-
- warnings.warn(PYTESTER_COPY_EXAMPLE, stacklevel=2)
- example_dir = self.request.config.getini("pytester_example_dir")
- if example_dir is None:
- raise ValueError("pytester_example_dir is unset, can't copy examples")
- example_dir = self.request.config.rootdir.join(example_dir)
-
- for extra_element in self.request.node.iter_markers("pytester_example_path"):
- assert extra_element.args
- example_dir = example_dir.join(*extra_element.args)
-
- if name is None:
- func_name = self.request.function.__name__
- maybe_dir = example_dir / func_name
- maybe_file = example_dir / (func_name + ".py")
-
- if maybe_dir.isdir():
- example_path = maybe_dir
- elif maybe_file.isfile():
- example_path = maybe_file
- else:
- raise LookupError(
- "{} cant be found as module or package in {}".format(
+
+ def mkdir(self, name):
+ """Create a new (sub)directory."""
+ return self.tmpdir.mkdir(name)
+
+ def mkpydir(self, name):
+ """Create a new 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)
+ p.ensure("__init__.py")
+ return p
+
+ def copy_example(self, name=None):
+ import warnings
+ from _pytest.warning_types import PYTESTER_COPY_EXAMPLE
+
+ warnings.warn(PYTESTER_COPY_EXAMPLE, stacklevel=2)
+ example_dir = self.request.config.getini("pytester_example_dir")
+ if example_dir is None:
+ raise ValueError("pytester_example_dir is unset, can't copy examples")
+ example_dir = self.request.config.rootdir.join(example_dir)
+
+ for extra_element in self.request.node.iter_markers("pytester_example_path"):
+ assert extra_element.args
+ example_dir = example_dir.join(*extra_element.args)
+
+ if name is None:
+ func_name = self.request.function.__name__
+ maybe_dir = example_dir / func_name
+ maybe_file = example_dir / (func_name + ".py")
+
+ if maybe_dir.isdir():
+ example_path = maybe_dir
+ elif maybe_file.isfile():
+ example_path = maybe_file
+ else:
+ raise LookupError(
+ "{} cant be found as module or package in {}".format(
func_name, example_dir.bestrelpath(self.request.config.rootdir)
- )
- )
- else:
- example_path = example_dir.join(name)
-
- if example_path.isdir() and not example_path.join("__init__.py").isfile():
- example_path.copy(self.tmpdir)
- return self.tmpdir
- elif example_path.isfile():
- result = self.tmpdir.join(example_path.basename)
- example_path.copy(result)
- return result
- else:
- raise LookupError(
- 'example "{}" is not found as a file or directory'.format(example_path)
- )
-
- Session = Session
-
- def getnode(self, config, arg):
- """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
-
- :param arg: a :py:class:`py.path.local` instance of the file
-
- """
- session = Session(config)
- assert "::" not in str(arg)
- p = py.path.local(arg)
- config.hook.pytest_sessionstart(session=session)
- res = session.perform_collect([str(p)], genitems=False)[0]
- config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
- return res
-
- 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.
-
- :param path: a :py:class:`py.path.local` instance of the file
-
- """
- config = self.parseconfigure(path)
- session = Session(config)
- x = session.fspath.bestrelpath(path)
- config.hook.pytest_sessionstart(session=session)
- res = session.perform_collect([x], genitems=False)[0]
- config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
- return res
-
- 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.
-
- """
- session = colitems[0].session
- result = []
- for colitem in colitems:
- result.extend(session.genitems(colitem))
- return result
-
- def runitem(self, source):
- """Run the "test_func" Item.
-
- 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
- item = self.getitem(source)
- # the test class where we are called from wants to provide the runner
- testclassinstance = self.request.instance
- runner = testclassinstance.getrunner()
- return runner(item)
-
- def inline_runsource(self, source, *cmdlineargs):
- """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.
-
- :param source: the source code of the test module
-
- :param cmdlineargs: any extra command line arguments to use
-
- :return: :py:class:`HookRecorder` instance of the result
-
- """
- p = self.makepyfile(source)
- values = list(cmdlineargs) + [p]
- return self.inline_run(*values)
-
- def inline_genitems(self, *args):
- """Run ``pytest.main(['--collectonly'])`` in-process.
-
- 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)
- items = [x.item for x in rec.getcalls("pytest_itemcollected")]
- return items, rec
-
- def inline_run(self, *args, **kwargs):
- """Run ``pytest.main()`` in-process, returning a HookRecorder.
-
- 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: command line arguments to pass to :py:func:`pytest.main`
-
+ )
+ )
+ else:
+ example_path = example_dir.join(name)
+
+ if example_path.isdir() and not example_path.join("__init__.py").isfile():
+ example_path.copy(self.tmpdir)
+ return self.tmpdir
+ elif example_path.isfile():
+ result = self.tmpdir.join(example_path.basename)
+ example_path.copy(result)
+ return result
+ else:
+ raise LookupError(
+ 'example "{}" is not found as a file or directory'.format(example_path)
+ )
+
+ Session = Session
+
+ def getnode(self, config, arg):
+ """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
+
+ :param arg: a :py:class:`py.path.local` instance of the file
+
+ """
+ session = Session(config)
+ assert "::" not in str(arg)
+ p = py.path.local(arg)
+ config.hook.pytest_sessionstart(session=session)
+ res = session.perform_collect([str(p)], genitems=False)[0]
+ config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
+ return res
+
+ 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.
+
+ :param path: a :py:class:`py.path.local` instance of the file
+
+ """
+ config = self.parseconfigure(path)
+ session = Session(config)
+ x = session.fspath.bestrelpath(path)
+ config.hook.pytest_sessionstart(session=session)
+ res = session.perform_collect([x], genitems=False)[0]
+ config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
+ return res
+
+ 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.
+
+ """
+ session = colitems[0].session
+ result = []
+ for colitem in colitems:
+ result.extend(session.genitems(colitem))
+ return result
+
+ def runitem(self, source):
+ """Run the "test_func" Item.
+
+ 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
+ item = self.getitem(source)
+ # the test class where we are called from wants to provide the runner
+ testclassinstance = self.request.instance
+ runner = testclassinstance.getrunner()
+ return runner(item)
+
+ def inline_runsource(self, source, *cmdlineargs):
+ """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.
+
+ :param source: the source code of the test module
+
+ :param cmdlineargs: any extra command line arguments to use
+
+ :return: :py:class:`HookRecorder` instance of the result
+
+ """
+ p = self.makepyfile(source)
+ values = list(cmdlineargs) + [p]
+ return self.inline_run(*values)
+
+ def inline_genitems(self, *args):
+ """Run ``pytest.main(['--collectonly'])`` in-process.
+
+ 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)
+ items = [x.item for x in rec.getcalls("pytest_itemcollected")]
+ return items, rec
+
+ def inline_run(self, *args, **kwargs):
+ """Run ``pytest.main()`` in-process, returning a HookRecorder.
+
+ 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: command line arguments to pass to :py:func:`pytest.main`
+
:param plugins: (keyword-only) extra plugin instances the
- ``pytest.main()`` instance should use
-
- :return: a :py:class:`HookRecorder` instance
+ ``pytest.main()`` instance should use
+
+ :return: a :py:class:`HookRecorder` instance
"""
plugins = kwargs.pop("plugins", [])
no_reraise_ctrlc = kwargs.pop("no_reraise_ctrlc", None)
raise_on_kwargs(kwargs)
-
- finalizers = []
- try:
+
+ finalizers = []
+ try:
# Do not load user config (during runs only).
mp_run = MonkeyPatch()
for k, v in self._env_run_update.items():
mp_run.setenv(k, v)
finalizers.append(mp_run.undo)
- # When running pytest 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_warn_already_imported():
- AssertionRewritingHook._warn_already_imported = orig_warn
-
- finalizers.append(revert_warn_already_imported)
- AssertionRewritingHook._warn_already_imported = lambda *a: None
-
- # Any sys.module or sys.path changes done while running pytest
- # inline should be reverted after the test run completes to avoid
- # clashing with later inline tests run within the same pytest test,
- # e.g. just because they use matching test module names.
- finalizers.append(self.__take_sys_modules_snapshot().restore)
- finalizers.append(SysPathsSnapshot().restore)
-
- # Important note:
- # - our tests should not leave any other references/registrations
- # laying around other than possibly loaded test modules
- # referenced from sys.modules, as nothing will clean those up
- # automatically
-
- rec = []
-
- class Collect(object):
- def pytest_configure(x, config):
- rec.append(self.make_hook_recorder(config.pluginmanager))
-
- plugins.append(Collect())
- ret = pytest.main(list(args), plugins=plugins)
- if len(rec) == 1:
- reprec = rec.pop()
- else:
-
- class reprec(object):
- pass
-
- reprec.ret = ret
-
- # typically we reraise keyboard interrupts from the child run
- # because it's our user requesting interruption of the testing
+ # When running pytest 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_warn_already_imported():
+ AssertionRewritingHook._warn_already_imported = orig_warn
+
+ finalizers.append(revert_warn_already_imported)
+ AssertionRewritingHook._warn_already_imported = lambda *a: None
+
+ # Any sys.module or sys.path changes done while running pytest
+ # inline should be reverted after the test run completes to avoid
+ # clashing with later inline tests run within the same pytest test,
+ # e.g. just because they use matching test module names.
+ finalizers.append(self.__take_sys_modules_snapshot().restore)
+ finalizers.append(SysPathsSnapshot().restore)
+
+ # Important note:
+ # - our tests should not leave any other references/registrations
+ # laying around other than possibly loaded test modules
+ # referenced from sys.modules, as nothing will clean those up
+ # automatically
+
+ rec = []
+
+ class Collect(object):
+ def pytest_configure(x, config):
+ rec.append(self.make_hook_recorder(config.pluginmanager))
+
+ plugins.append(Collect())
+ ret = pytest.main(list(args), plugins=plugins)
+ if len(rec) == 1:
+ reprec = rec.pop()
+ else:
+
+ class reprec(object):
+ pass
+
+ reprec.ret = ret
+
+ # typically we reraise keyboard interrupts from the child run
+ # because it's our user requesting interruption of the testing
if ret == EXIT_INTERRUPTED and not no_reraise_ctrlc:
- calls = reprec.getcalls("pytest_keyboard_interrupt")
- if calls and calls[-1].excinfo.type == KeyboardInterrupt:
- raise KeyboardInterrupt()
- return reprec
- finally:
- for finalizer in finalizers:
- finalizer()
-
- def runpytest_inprocess(self, *args, **kwargs):
- """Return result of running pytest in-process, providing a similar
- interface to what self.runpytest() provides.
+ calls = reprec.getcalls("pytest_keyboard_interrupt")
+ if calls and calls[-1].excinfo.type == KeyboardInterrupt:
+ raise KeyboardInterrupt()
+ return reprec
+ finally:
+ for finalizer in finalizers:
+ finalizer()
+
+ def runpytest_inprocess(self, *args, **kwargs):
+ """Return result of running pytest in-process, providing a similar
+ interface to what self.runpytest() provides.
"""
syspathinsert = kwargs.pop("syspathinsert", False)
-
+
if syspathinsert:
- self.syspathinsert()
- now = time.time()
- capture = MultiCapture(Capture=SysCapture)
- capture.start_capturing()
- try:
- try:
- reprec = self.inline_run(*args, **kwargs)
- except SystemExit as e:
-
- class reprec(object):
- ret = e.args[0]
-
- except Exception:
- traceback.print_exc()
-
- class reprec(object):
- ret = 3
-
- finally:
- out, err = capture.readouterr()
- capture.stop_capturing()
- sys.stdout.write(out)
- sys.stderr.write(err)
-
- res = RunResult(reprec.ret, out.split("\n"), err.split("\n"), time.time() - now)
- res.reprec = reprec
- return res
-
- def runpytest(self, *args, **kwargs):
- """Run pytest inline or in a subprocess, depending on the command line
- option "--runpytest" and return a :py:class:`RunResult`.
-
- """
- args = self._ensure_basetemp(args)
- return self._runpytest_method(*args, **kwargs)
-
- def _ensure_basetemp(self, args):
- args = list(args)
- for x in args:
- if safe_str(x).startswith("--basetemp"):
- break
- else:
- args.append("--basetemp=%s" % self.tmpdir.dirpath("basetemp"))
- return args
-
- 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 a new
- :py:class:`_pytest.config.Config` instance.
-
- If :py:attr:`plugins` has been populated they should be plugin modules
- to be registered with the PluginManager.
-
- """
- args = self._ensure_basetemp(args)
-
- import _pytest.config
-
- config = _pytest.config._prepareconfig(args, self.plugins)
- # we don't know what the test will do with this half-setup config
- # object and thus we make sure it gets unconfigured properly in any
- # case (otherwise capturing could still be active, for example)
- self.request.addfinalizer(config._ensure_unconfigure)
- return config
-
- 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.
-
- """
- config = self.parseconfig(*args)
- config._do_configure()
- self.request.addfinalizer(config._ensure_unconfigure)
- return config
-
- 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.
-
- :param source: the module source
-
- :param funcname: the name of the test function for which to return a
- test item
-
- """
- items = self.getitems(source)
- for item in items:
- if item.name == funcname:
- return item
- assert 0, "%r item not found in module:\n%s\nitems: %s" % (
- funcname,
- source,
- items,
- )
-
- 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.
-
- """
- modcol = self.getmodulecol(source)
- return self.genitems([modcol])
-
- 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.
-
- :param source: the source code of the module to collect
-
- :param configargs: any extra arguments to pass to
- :py:meth:`parseconfigure`
-
- :param withinit: whether to also write an ``__init__.py`` file to the
- same directory to ensure it is a package
-
- """
- if isinstance(source, Path):
- path = self.tmpdir.join(str(source))
- assert not withinit, "not supported for paths"
- else:
- kw = {self.request.function.__name__: Source(source).strip()}
- path = self.makepyfile(**kw)
- if withinit:
- self.makepyfile(__init__="#")
- self.config = config = self.parseconfigure(path, *configargs)
- return self.getnode(config, path)
-
- 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.
-
- :param modcol: a module collection node; see :py:meth:`getmodulecol`
-
- :param name: the name of the node to return
-
- """
- if modcol not in self._mod_collections:
- self._mod_collections[modcol] = list(modcol.collect())
- for colitem in self._mod_collections[modcol]:
- if colitem.name == name:
- return colitem
-
+ self.syspathinsert()
+ now = time.time()
+ capture = MultiCapture(Capture=SysCapture)
+ capture.start_capturing()
+ try:
+ try:
+ reprec = self.inline_run(*args, **kwargs)
+ except SystemExit as e:
+
+ class reprec(object):
+ ret = e.args[0]
+
+ except Exception:
+ traceback.print_exc()
+
+ class reprec(object):
+ ret = 3
+
+ finally:
+ out, err = capture.readouterr()
+ capture.stop_capturing()
+ sys.stdout.write(out)
+ sys.stderr.write(err)
+
+ res = RunResult(reprec.ret, out.split("\n"), err.split("\n"), time.time() - now)
+ res.reprec = reprec
+ return res
+
+ def runpytest(self, *args, **kwargs):
+ """Run pytest inline or in a subprocess, depending on the command line
+ option "--runpytest" and return a :py:class:`RunResult`.
+
+ """
+ args = self._ensure_basetemp(args)
+ return self._runpytest_method(*args, **kwargs)
+
+ def _ensure_basetemp(self, args):
+ args = list(args)
+ for x in args:
+ if safe_str(x).startswith("--basetemp"):
+ break
+ else:
+ args.append("--basetemp=%s" % self.tmpdir.dirpath("basetemp"))
+ return args
+
+ 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 a new
+ :py:class:`_pytest.config.Config` instance.
+
+ If :py:attr:`plugins` has been populated they should be plugin modules
+ to be registered with the PluginManager.
+
+ """
+ args = self._ensure_basetemp(args)
+
+ import _pytest.config
+
+ config = _pytest.config._prepareconfig(args, self.plugins)
+ # we don't know what the test will do with this half-setup config
+ # object and thus we make sure it gets unconfigured properly in any
+ # case (otherwise capturing could still be active, for example)
+ self.request.addfinalizer(config._ensure_unconfigure)
+ return config
+
+ 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.
+
+ """
+ config = self.parseconfig(*args)
+ config._do_configure()
+ self.request.addfinalizer(config._ensure_unconfigure)
+ return config
+
+ 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.
+
+ :param source: the module source
+
+ :param funcname: the name of the test function for which to return a
+ test item
+
+ """
+ items = self.getitems(source)
+ for item in items:
+ if item.name == funcname:
+ return item
+ assert 0, "%r item not found in module:\n%s\nitems: %s" % (
+ funcname,
+ source,
+ items,
+ )
+
+ 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.
+
+ """
+ modcol = self.getmodulecol(source)
+ return self.genitems([modcol])
+
+ 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.
+
+ :param source: the source code of the module to collect
+
+ :param configargs: any extra arguments to pass to
+ :py:meth:`parseconfigure`
+
+ :param withinit: whether to also write an ``__init__.py`` file to the
+ same directory to ensure it is a package
+
+ """
+ if isinstance(source, Path):
+ path = self.tmpdir.join(str(source))
+ assert not withinit, "not supported for paths"
+ else:
+ kw = {self.request.function.__name__: Source(source).strip()}
+ path = self.makepyfile(**kw)
+ if withinit:
+ self.makepyfile(__init__="#")
+ self.config = config = self.parseconfigure(path, *configargs)
+ return self.getnode(config, path)
+
+ 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.
+
+ :param modcol: a module collection node; see :py:meth:`getmodulecol`
+
+ :param name: the name of the node to return
+
+ """
+ if modcol not in self._mod_collections:
+ self._mod_collections[modcol] = list(modcol.collect())
+ for colitem in self._mod_collections[modcol]:
+ if colitem.name == name:
+ return colitem
+
def popen(
self,
cmdargs,
@@ -1037,377 +1037,377 @@ class Testdir(object):
stdin=CLOSE_STDIN,
**kw
):
- """Invoke subprocess.Popen.
-
- This calls subprocess.Popen making sure the current working directory
- is in the PYTHONPATH.
-
- You probably want to use :py:meth:`run` instead.
-
- """
- env = os.environ.copy()
- env["PYTHONPATH"] = os.pathsep.join(
- filter(None, [os.getcwd(), env.get("PYTHONPATH", "")])
- )
+ """Invoke subprocess.Popen.
+
+ This calls subprocess.Popen making sure the current working directory
+ is in the PYTHONPATH.
+
+ You probably want to use :py:meth:`run` instead.
+
+ """
+ env = os.environ.copy()
+ env["PYTHONPATH"] = os.pathsep.join(
+ filter(None, [os.getcwd(), env.get("PYTHONPATH", "")])
+ )
env.update(self._env_run_update)
- kw["env"] = env
-
+ kw["env"] = env
+
if stdin is Testdir.CLOSE_STDIN:
kw["stdin"] = subprocess.PIPE
elif isinstance(stdin, bytes):
kw["stdin"] = subprocess.PIPE
else:
kw["stdin"] = stdin
-
+
popen = subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw)
if stdin is Testdir.CLOSE_STDIN:
popen.stdin.close()
elif isinstance(stdin, bytes):
popen.stdin.write(stdin)
- return popen
-
- def run(self, *cmdargs, **kwargs):
- """Run a command with arguments.
-
- Run a process using subprocess.Popen saving the stdout and stderr.
-
- :param args: the sequence of arguments to pass to `subprocess.Popen()`
- :param timeout: the period in seconds after which to timeout and raise
- :py:class:`Testdir.TimeoutExpired`
+ return popen
+
+ def run(self, *cmdargs, **kwargs):
+ """Run a command with arguments.
+
+ Run a process using subprocess.Popen saving the stdout and stderr.
+
+ :param args: the sequence of arguments to pass to `subprocess.Popen()`
+ :param timeout: the period in seconds after which to timeout and raise
+ :py:class:`Testdir.TimeoutExpired`
:param stdin: optional standard input. Bytes are being send, closing
the pipe, otherwise it is passed through to ``popen``.
Defaults to ``CLOSE_STDIN``, which translates to using a pipe
(``subprocess.PIPE``) that gets closed.
-
- Returns a :py:class:`RunResult`.
-
- """
- __tracebackhide__ = True
-
- timeout = kwargs.pop("timeout", None)
+
+ Returns a :py:class:`RunResult`.
+
+ """
+ __tracebackhide__ = True
+
+ timeout = kwargs.pop("timeout", None)
stdin = kwargs.pop("stdin", Testdir.CLOSE_STDIN)
- raise_on_kwargs(kwargs)
-
- cmdargs = [
- str(arg) if isinstance(arg, py.path.local) else arg for arg in cmdargs
- ]
- p1 = self.tmpdir.join("stdout")
- p2 = self.tmpdir.join("stderr")
- print("running:", *cmdargs)
- print(" in:", py.path.local())
- f1 = codecs.open(str(p1), "w", encoding="utf8")
- f2 = codecs.open(str(p2), "w", encoding="utf8")
- try:
- now = time.time()
- popen = self.popen(
+ raise_on_kwargs(kwargs)
+
+ cmdargs = [
+ str(arg) if isinstance(arg, py.path.local) else arg for arg in cmdargs
+ ]
+ p1 = self.tmpdir.join("stdout")
+ p2 = self.tmpdir.join("stderr")
+ print("running:", *cmdargs)
+ print(" in:", py.path.local())
+ f1 = codecs.open(str(p1), "w", encoding="utf8")
+ f2 = codecs.open(str(p2), "w", encoding="utf8")
+ try:
+ now = time.time()
+ popen = self.popen(
cmdargs,
stdin=stdin,
stdout=f1,
stderr=f2,
close_fds=(sys.platform != "win32"),
- )
+ )
if isinstance(stdin, bytes):
popen.stdin.close()
-
- def handle_timeout():
- __tracebackhide__ = True
-
- timeout_message = (
- "{seconds} second timeout expired running:"
- " {command}".format(seconds=timeout, command=cmdargs)
- )
-
- popen.kill()
- popen.wait()
- raise self.TimeoutExpired(timeout_message)
-
- if timeout is None:
- ret = popen.wait()
+
+ def handle_timeout():
+ __tracebackhide__ = True
+
+ timeout_message = (
+ "{seconds} second timeout expired running:"
+ " {command}".format(seconds=timeout, command=cmdargs)
+ )
+
+ popen.kill()
+ popen.wait()
+ raise self.TimeoutExpired(timeout_message)
+
+ if timeout is None:
+ ret = popen.wait()
elif not six.PY2:
- try:
- ret = popen.wait(timeout)
- except subprocess.TimeoutExpired:
- handle_timeout()
- else:
- end = time.time() + timeout
-
- resolution = min(0.1, timeout / 10)
-
- while True:
- ret = popen.poll()
- if ret is not None:
- break
-
- if time.time() > end:
- handle_timeout()
-
- time.sleep(resolution)
- finally:
- f1.close()
- f2.close()
- f1 = codecs.open(str(p1), "r", encoding="utf8")
- f2 = codecs.open(str(p2), "r", encoding="utf8")
- try:
- out = f1.read().splitlines()
- err = f2.read().splitlines()
- finally:
- f1.close()
- f2.close()
- self._dump_lines(out, sys.stdout)
- self._dump_lines(err, sys.stderr)
- return RunResult(ret, out, err, time.time() - now)
-
- def _dump_lines(self, lines, fp):
- try:
- for line in lines:
- print(line, file=fp)
- except UnicodeEncodeError:
- print("couldn't print to %s because of encoding" % (fp,))
-
- def _getpytestargs(self):
- return sys.executable, "-mpytest"
-
- def runpython(self, script):
- """Run a python script using sys.executable as interpreter.
-
- Returns a :py:class:`RunResult`.
-
- """
- return self.run(sys.executable, script)
-
- def runpython_c(self, command):
- """Run python -c "command", return a :py:class:`RunResult`."""
- return self.run(sys.executable, "-c", command)
-
- def runpytest_subprocess(self, *args, **kwargs):
- """Run pytest as a subprocess with given arguments.
-
- Any plugins added to the :py:attr:`plugins` list will be added using the
- ``-p`` command line option. Additionally ``--basetemp`` is used to put
- any temporary files and directories in a numbered directory prefixed
- with "runpytest-" to not conflict with the normal numbered pytest
- location for temporary files and directories.
-
- :param args: the sequence of arguments to pass to the pytest subprocess
- :param timeout: the period in seconds after which to timeout and raise
- :py:class:`Testdir.TimeoutExpired`
-
- Returns a :py:class:`RunResult`.
- """
- __tracebackhide__ = True
+ try:
+ ret = popen.wait(timeout)
+ except subprocess.TimeoutExpired:
+ handle_timeout()
+ else:
+ end = time.time() + timeout
+
+ resolution = min(0.1, timeout / 10)
+
+ while True:
+ ret = popen.poll()
+ if ret is not None:
+ break
+
+ if time.time() > end:
+ handle_timeout()
+
+ time.sleep(resolution)
+ finally:
+ f1.close()
+ f2.close()
+ f1 = codecs.open(str(p1), "r", encoding="utf8")
+ f2 = codecs.open(str(p2), "r", encoding="utf8")
+ try:
+ out = f1.read().splitlines()
+ err = f2.read().splitlines()
+ finally:
+ f1.close()
+ f2.close()
+ self._dump_lines(out, sys.stdout)
+ self._dump_lines(err, sys.stderr)
+ return RunResult(ret, out, err, time.time() - now)
+
+ def _dump_lines(self, lines, fp):
+ try:
+ for line in lines:
+ print(line, file=fp)
+ except UnicodeEncodeError:
+ print("couldn't print to %s because of encoding" % (fp,))
+
+ def _getpytestargs(self):
+ return sys.executable, "-mpytest"
+
+ def runpython(self, script):
+ """Run a python script using sys.executable as interpreter.
+
+ Returns a :py:class:`RunResult`.
+
+ """
+ return self.run(sys.executable, script)
+
+ def runpython_c(self, command):
+ """Run python -c "command", return a :py:class:`RunResult`."""
+ return self.run(sys.executable, "-c", command)
+
+ def runpytest_subprocess(self, *args, **kwargs):
+ """Run pytest as a subprocess with given arguments.
+
+ Any plugins added to the :py:attr:`plugins` list will be added using the
+ ``-p`` command line option. Additionally ``--basetemp`` is used to put
+ any temporary files and directories in a numbered directory prefixed
+ with "runpytest-" to not conflict with the normal numbered pytest
+ location for temporary files and directories.
+
+ :param args: the sequence of arguments to pass to the pytest subprocess
+ :param timeout: the period in seconds after which to timeout and raise
+ :py:class:`Testdir.TimeoutExpired`
+
+ Returns a :py:class:`RunResult`.
+ """
+ __tracebackhide__ = True
timeout = kwargs.pop("timeout", None)
raise_on_kwargs(kwargs)
-
- p = py.path.local.make_numbered_dir(
- prefix="runpytest-", keep=None, rootdir=self.tmpdir
- )
- args = ("--basetemp=%s" % p,) + args
- plugins = [x for x in self.plugins if isinstance(x, str)]
- if plugins:
- args = ("-p", plugins[0]) + args
- args = self._getpytestargs() + args
+
+ p = py.path.local.make_numbered_dir(
+ prefix="runpytest-", keep=None, rootdir=self.tmpdir
+ )
+ args = ("--basetemp=%s" % p,) + args
+ plugins = [x for x in self.plugins if isinstance(x, str)]
+ if plugins:
+ args = ("-p", plugins[0]) + args
+ args = self._getpytestargs() + args
return self.run(*args, timeout=timeout)
-
- 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.
-
- The pexpect child is returned.
-
- """
- basetemp = self.tmpdir.mkdir("temp-pexpect")
- invoke = " ".join(map(str, self._getpytestargs()))
- cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
- return self.spawn(cmd, expect_timeout=expect_timeout)
-
- def spawn(self, cmd, expect_timeout=10.0):
- """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():
- pytest.skip("pypy-64 bit not supported")
- if sys.platform.startswith("freebsd"):
- pytest.xfail("pexpect does not work reliably on freebsd")
- logfile = self.tmpdir.join("spawn.out").open("wb")
+
+ 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.
+
+ The pexpect child is returned.
+
+ """
+ basetemp = self.tmpdir.mkdir("temp-pexpect")
+ invoke = " ".join(map(str, self._getpytestargs()))
+ cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
+ return self.spawn(cmd, expect_timeout=expect_timeout)
+
+ def spawn(self, cmd, expect_timeout=10.0):
+ """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():
+ pytest.skip("pypy-64 bit not supported")
+ if sys.platform.startswith("freebsd"):
+ pytest.xfail("pexpect does not work reliably on freebsd")
+ logfile = self.tmpdir.join("spawn.out").open("wb")
# Do not load user config.
env = os.environ.copy()
env.update(self._env_run_update)
child = pexpect.spawn(cmd, logfile=logfile, env=env)
- self.request.addfinalizer(logfile.close)
- child.timeout = expect_timeout
- return child
-
-
-def getdecoded(out):
- try:
- return out.decode("utf-8")
- except UnicodeDecodeError:
+ self.request.addfinalizer(logfile.close)
+ child.timeout = expect_timeout
+ return child
+
+
+def getdecoded(out):
+ try:
+ return out.decode("utf-8")
+ except UnicodeDecodeError:
return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (saferepr(out),)
-
-
-class LineComp(object):
- def __init__(self):
- 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.
-
- """
- __tracebackhide__ = True
- val = self.stringio.getvalue()
- self.stringio.truncate(0)
- self.stringio.seek(0)
- lines1 = val.split("\n")
- return LineMatcher(lines1).fnmatch_lines(lines2)
-
-
-class LineMatcher(object):
- """Flexible matching of text.
-
- 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()``.
-
- """
-
- def __init__(self, lines):
- self.lines = lines
- self._log_output = []
-
- def str(self):
- """Return the entire original text."""
- return "\n".join(self.lines)
-
- def _getlines(self, lines2):
- if isinstance(lines2, str):
- lines2 = Source(lines2)
- if isinstance(lines2, Source):
- lines2 = lines2.strip().lines
- return lines2
-
- def fnmatch_lines_random(self, lines2):
- """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.
-
- """
- 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.
-
- """
- self._match_lines_random(lines2, lambda name, pat: re.match(pat, name))
-
- 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.
-
- """
- lines2 = self._getlines(lines2)
- for line in lines2:
- for x in self.lines:
- if line == x or match_func(x, line):
- self._log("matched: ", repr(line))
- break
- else:
- self._log("line %r not found in output" % line)
- raise ValueError(self._log_text)
-
- def get_lines_after(self, fnline):
- """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):
- return self.lines[i + 1 :]
- raise ValueError("line %r not found in output" % fnline)
-
- def _log(self, *args):
+
+
+class LineComp(object):
+ def __init__(self):
+ 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.
+
+ """
+ __tracebackhide__ = True
+ val = self.stringio.getvalue()
+ self.stringio.truncate(0)
+ self.stringio.seek(0)
+ lines1 = val.split("\n")
+ return LineMatcher(lines1).fnmatch_lines(lines2)
+
+
+class LineMatcher(object):
+ """Flexible matching of text.
+
+ 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()``.
+
+ """
+
+ def __init__(self, lines):
+ self.lines = lines
+ self._log_output = []
+
+ def str(self):
+ """Return the entire original text."""
+ return "\n".join(self.lines)
+
+ def _getlines(self, lines2):
+ if isinstance(lines2, str):
+ lines2 = Source(lines2)
+ if isinstance(lines2, Source):
+ lines2 = lines2.strip().lines
+ return lines2
+
+ def fnmatch_lines_random(self, lines2):
+ """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.
+
+ """
+ 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.
+
+ """
+ self._match_lines_random(lines2, lambda name, pat: re.match(pat, name))
+
+ 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.
+
+ """
+ lines2 = self._getlines(lines2)
+ for line in lines2:
+ for x in self.lines:
+ if line == x or match_func(x, line):
+ self._log("matched: ", repr(line))
+ break
+ else:
+ self._log("line %r not found in output" % line)
+ raise ValueError(self._log_text)
+
+ def get_lines_after(self, fnline):
+ """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):
+ return self.lines[i + 1 :]
+ raise ValueError("line %r not found in output" % fnline)
+
+ def _log(self, *args):
self._log_output.append(" ".join(str(x) for x in args))
-
- @property
- def _log_text(self):
- return "\n".join(self._log_output)
-
- 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.
-
- """
- __tracebackhide__ = True
- self._match_lines(lines2, fnmatch, "fnmatch")
-
- def re_match_lines(self, lines2):
- """Search captured text for matching lines using ``re.match``.
-
- 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.
-
- """
- __tracebackhide__ = True
- 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 str match_nickname: the nickname for the match function that
- will be logged to stdout when a match occurs
-
- """
+
+ @property
+ def _log_text(self):
+ return "\n".join(self._log_output)
+
+ 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.
+
+ """
+ __tracebackhide__ = True
+ self._match_lines(lines2, fnmatch, "fnmatch")
+
+ def re_match_lines(self, lines2):
+ """Search captured text for matching lines using ``re.match``.
+
+ 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.
+
+ """
+ __tracebackhide__ = True
+ 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 str match_nickname: the nickname for the match function that
+ will be logged to stdout when a match occurs
+
+ """
assert isinstance(lines2, Sequence)
- lines2 = self._getlines(lines2)
- lines1 = self.lines[:]
- nextline = None
- extralines = []
- __tracebackhide__ = True
- for line in lines2:
- nomatchprinted = False
- while lines1:
- nextline = lines1.pop(0)
- if line == nextline:
- self._log("exact match:", repr(line))
- break
- elif match_func(nextline, line):
- self._log("%s:" % match_nickname, repr(line))
- self._log(" with:", repr(nextline))
- break
- else:
- if not nomatchprinted:
- self._log("nomatch:", repr(line))
- nomatchprinted = True
- self._log(" and:", repr(nextline))
- extralines.append(nextline)
- else:
- self._log("remains unmatched: %r" % (line,))
- pytest.fail(self._log_text)
+ lines2 = self._getlines(lines2)
+ lines1 = self.lines[:]
+ nextline = None
+ extralines = []
+ __tracebackhide__ = True
+ for line in lines2:
+ nomatchprinted = False
+ while lines1:
+ nextline = lines1.pop(0)
+ if line == nextline:
+ self._log("exact match:", repr(line))
+ break
+ elif match_func(nextline, line):
+ self._log("%s:" % match_nickname, repr(line))
+ self._log(" with:", repr(nextline))
+ break
+ else:
+ if not nomatchprinted:
+ self._log("nomatch:", repr(line))
+ nomatchprinted = True
+ self._log(" and:", repr(nextline))
+ extralines.append(nextline)
+ else:
+ self._log("remains unmatched: %r" % (line,))
+ pytest.fail(self._log_text)
diff --git a/contrib/python/pytest/py2/_pytest/python.py b/contrib/python/pytest/py2/_pytest/python.py
index d8e11024ae..f7c368b0c4 100644
--- a/contrib/python/pytest/py2/_pytest/python.py
+++ b/contrib/python/pytest/py2/_pytest/python.py
@@ -1,103 +1,103 @@
# -*- coding: utf-8 -*-
-""" Python test discovery, setup and run of test functions. """
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import collections
-import fnmatch
-import inspect
-import os
-import sys
-import warnings
+""" Python test discovery, setup and run of test functions. """
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import collections
+import fnmatch
+import inspect
+import os
+import sys
+import warnings
from functools import partial
-from textwrap import dedent
-
-import py
-import six
-
-import _pytest
-from _pytest import deprecated
-from _pytest import fixtures
-from _pytest import nodes
-from _pytest._code import filter_traceback
-from _pytest.compat import ascii_escaped
-from _pytest.compat import enum
-from _pytest.compat import get_default_arg_names
-from _pytest.compat import get_real_func
-from _pytest.compat import getfslineno
-from _pytest.compat import getimfunc
-from _pytest.compat import getlocation
-from _pytest.compat import is_generator
-from _pytest.compat import isclass
-from _pytest.compat import isfunction
-from _pytest.compat import NOTSET
-from _pytest.compat import REGEX_TYPE
-from _pytest.compat import safe_getattr
-from _pytest.compat import safe_isclass
-from _pytest.compat import safe_str
-from _pytest.compat import STRING_TYPES
-from _pytest.config import hookimpl
-from _pytest.main import FSHookProxy
+from textwrap import dedent
+
+import py
+import six
+
+import _pytest
+from _pytest import deprecated
+from _pytest import fixtures
+from _pytest import nodes
+from _pytest._code import filter_traceback
+from _pytest.compat import ascii_escaped
+from _pytest.compat import enum
+from _pytest.compat import get_default_arg_names
+from _pytest.compat import get_real_func
+from _pytest.compat import getfslineno
+from _pytest.compat import getimfunc
+from _pytest.compat import getlocation
+from _pytest.compat import is_generator
+from _pytest.compat import isclass
+from _pytest.compat import isfunction
+from _pytest.compat import NOTSET
+from _pytest.compat import REGEX_TYPE
+from _pytest.compat import safe_getattr
+from _pytest.compat import safe_isclass
+from _pytest.compat import safe_str
+from _pytest.compat import STRING_TYPES
+from _pytest.config import hookimpl
+from _pytest.main import FSHookProxy
from _pytest.mark import MARK_GEN
-from _pytest.mark.structures import get_unpacked_marks
-from _pytest.mark.structures import normalize_mark_list
-from _pytest.outcomes import fail
+from _pytest.mark.structures import get_unpacked_marks
+from _pytest.mark.structures import normalize_mark_list
+from _pytest.outcomes import fail
from _pytest.outcomes import skip
-from _pytest.pathlib import parts
+from _pytest.pathlib import parts
from _pytest.warning_types import PytestCollectionWarning
from _pytest.warning_types import PytestUnhandledCoroutineWarning
-
-
-def pyobj_property(name):
- def get(self):
- node = self.getparent(getattr(__import__("pytest"), name))
- if node is not None:
- return node.obj
-
- doc = "python %s object this node was collected from (can be None)." % (
- name.lower(),
- )
- return property(get, None, None, doc)
-
-
-def pytest_addoption(parser):
- group = parser.getgroup("general")
- group.addoption(
- "--fixtures",
- "--funcargs",
- action="store_true",
- dest="showfixtures",
- default=False,
- help="show available fixtures, sorted by plugin appearance "
- "(fixtures with leading '_' are only shown with '-v')",
- )
- group.addoption(
- "--fixtures-per-test",
- action="store_true",
- dest="show_fixtures_per_test",
- default=False,
- help="show fixtures per test",
- )
- parser.addini(
- "python_files",
- type="args",
+
+
+def pyobj_property(name):
+ def get(self):
+ node = self.getparent(getattr(__import__("pytest"), name))
+ if node is not None:
+ return node.obj
+
+ doc = "python %s object this node was collected from (can be None)." % (
+ name.lower(),
+ )
+ return property(get, None, None, doc)
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("general")
+ group.addoption(
+ "--fixtures",
+ "--funcargs",
+ action="store_true",
+ dest="showfixtures",
+ default=False,
+ help="show available fixtures, sorted by plugin appearance "
+ "(fixtures with leading '_' are only shown with '-v')",
+ )
+ group.addoption(
+ "--fixtures-per-test",
+ action="store_true",
+ dest="show_fixtures_per_test",
+ default=False,
+ help="show fixtures per test",
+ )
+ parser.addini(
+ "python_files",
+ type="args",
# NOTE: default is also used in AssertionRewritingHook.
- default=["test_*.py", "*_test.py"],
- help="glob-style file patterns for Python test module discovery",
- )
- parser.addini(
- "python_classes",
- type="args",
- default=["Test"],
- help="prefixes or glob names for Python test class discovery",
- )
- parser.addini(
- "python_functions",
- type="args",
- default=["test"],
- help="prefixes or glob names for Python test function and method discovery",
- )
+ default=["test_*.py", "*_test.py"],
+ help="glob-style file patterns for Python test module discovery",
+ )
+ parser.addini(
+ "python_classes",
+ type="args",
+ default=["Test"],
+ help="prefixes or glob names for Python test class discovery",
+ )
+ parser.addini(
+ "python_functions",
+ type="args",
+ default=["test"],
+ help="prefixes or glob names for Python test function and method discovery",
+ )
parser.addini(
"disable_test_id_escaping_and_forfeit_all_rights_to_community_support",
type="bool",
@@ -105,61 +105,61 @@ def pytest_addoption(parser):
help="disable string escape non-ascii characters, might cause unwanted "
"side effects(use at your own risk)",
)
-
- group.addoption(
- "--import-mode",
- default="prepend",
- choices=["prepend", "append"],
- dest="importmode",
- help="prepend/append to sys.path when importing test modules, "
- "default is to prepend.",
- )
-
-
-def pytest_cmdline_main(config):
- if config.option.showfixtures:
- showfixtures(config)
- return 0
- if config.option.show_fixtures_per_test:
- show_fixtures_per_test(config)
- return 0
-
-
-def pytest_generate_tests(metafunc):
- # those alternative spellings are common - raise a specific error to alert
- # the user
- alt_spellings = ["parameterize", "parametrise", "parameterise"]
+
+ group.addoption(
+ "--import-mode",
+ default="prepend",
+ choices=["prepend", "append"],
+ dest="importmode",
+ help="prepend/append to sys.path when importing test modules, "
+ "default is to prepend.",
+ )
+
+
+def pytest_cmdline_main(config):
+ if config.option.showfixtures:
+ showfixtures(config)
+ return 0
+ if config.option.show_fixtures_per_test:
+ show_fixtures_per_test(config)
+ return 0
+
+
+def pytest_generate_tests(metafunc):
+ # those alternative spellings are common - raise a specific error to alert
+ # the user
+ alt_spellings = ["parameterize", "parametrise", "parameterise"]
for mark_name in alt_spellings:
if metafunc.definition.get_closest_marker(mark_name):
- msg = "{0} has '{1}' mark, spelling should be 'parametrize'"
+ msg = "{0} has '{1}' mark, spelling should be 'parametrize'"
fail(msg.format(metafunc.function.__name__, mark_name), pytrace=False)
- for marker in metafunc.definition.iter_markers(name="parametrize"):
- metafunc.parametrize(*marker.args, **marker.kwargs)
-
-
-def pytest_configure(config):
- config.addinivalue_line(
- "markers",
- "parametrize(argnames, argvalues): call a test function multiple "
- "times passing in different arguments in turn. argvalues generally "
- "needs to be a list of values if argnames specifies only one name "
- "or a list of tuples of values if argnames specifies multiple names. "
- "Example: @parametrize('arg1', [1,2]) would lead to two calls of the "
- "decorated test function, one with arg1=1 and another with arg1=2."
- "see https://docs.pytest.org/en/latest/parametrize.html for more info "
- "and examples.",
- )
- config.addinivalue_line(
- "markers",
- "usefixtures(fixturename1, fixturename2, ...): mark tests as needing "
- "all of the specified fixtures. see "
- "https://docs.pytest.org/en/latest/fixture.html#usefixtures ",
- )
-
-
-@hookimpl(trylast=True)
-def pytest_pyfunc_call(pyfuncitem):
- testfunction = pyfuncitem.obj
+ for marker in metafunc.definition.iter_markers(name="parametrize"):
+ metafunc.parametrize(*marker.args, **marker.kwargs)
+
+
+def pytest_configure(config):
+ config.addinivalue_line(
+ "markers",
+ "parametrize(argnames, argvalues): call a test function multiple "
+ "times passing in different arguments in turn. argvalues generally "
+ "needs to be a list of values if argnames specifies only one name "
+ "or a list of tuples of values if argnames specifies multiple names. "
+ "Example: @parametrize('arg1', [1,2]) would lead to two calls of the "
+ "decorated test function, one with arg1=1 and another with arg1=2."
+ "see https://docs.pytest.org/en/latest/parametrize.html for more info "
+ "and examples.",
+ )
+ config.addinivalue_line(
+ "markers",
+ "usefixtures(fixturename1, fixturename2, ...): mark tests as needing "
+ "all of the specified fixtures. see "
+ "https://docs.pytest.org/en/latest/fixture.html#usefixtures ",
+ )
+
+
+@hookimpl(trylast=True)
+def pytest_pyfunc_call(pyfuncitem):
+ testfunction = pyfuncitem.obj
iscoroutinefunction = getattr(inspect, "iscoroutinefunction", None)
if iscoroutinefunction is not None and iscoroutinefunction(testfunction):
msg = "Coroutine functions are not natively supported and have been skipped.\n"
@@ -172,85 +172,85 @@ def pytest_pyfunc_call(pyfuncitem):
funcargs = pyfuncitem.funcargs
testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames}
testfunction(**testargs)
- return True
-
-
-def pytest_collect_file(path, parent):
- ext = path.ext
- if ext == ".py":
- if not parent.session.isinitpath(path):
- if not path_matches_patterns(
- path, parent.config.getini("python_files") + ["__init__.py"]
- ):
- return
- ihook = parent.session.gethookproxy(path)
- return ihook.pytest_pycollect_makemodule(path=path, parent=parent)
-
-
-def path_matches_patterns(path, patterns):
- """Returns True if the given py.path.local matches one of the patterns in the list of globs given"""
- return any(path.fnmatch(pattern) for pattern in patterns)
-
-
-def pytest_pycollect_makemodule(path, parent):
- if path.basename == "__init__.py":
- return Package(path, parent)
- return Module(path, parent)
-
-
-@hookimpl(hookwrapper=True)
-def pytest_pycollect_makeitem(collector, name, obj):
- outcome = yield
- res = outcome.get_result()
- if res is not None:
- return
- # nothing was collected elsewhere, let's do it here
- if safe_isclass(obj):
- if collector.istestclass(obj, name):
- outcome.force_result(Class(name, parent=collector))
- elif collector.istestfunction(obj, name):
- # mock seems to store unbound methods (issue473), normalize it
- obj = getattr(obj, "__func__", obj)
- # We need to try and unwrap the function if it's a functools.partial
- # or a funtools.wrapped.
- # We musn't if it's been wrapped with mock.patch (python 2 only)
- if not (isfunction(obj) or isfunction(get_real_func(obj))):
- filename, lineno = getfslineno(obj)
- warnings.warn_explicit(
+ return True
+
+
+def pytest_collect_file(path, parent):
+ ext = path.ext
+ if ext == ".py":
+ if not parent.session.isinitpath(path):
+ if not path_matches_patterns(
+ path, parent.config.getini("python_files") + ["__init__.py"]
+ ):
+ return
+ ihook = parent.session.gethookproxy(path)
+ return ihook.pytest_pycollect_makemodule(path=path, parent=parent)
+
+
+def path_matches_patterns(path, patterns):
+ """Returns True if the given py.path.local matches one of the patterns in the list of globs given"""
+ return any(path.fnmatch(pattern) for pattern in patterns)
+
+
+def pytest_pycollect_makemodule(path, parent):
+ if path.basename == "__init__.py":
+ return Package(path, parent)
+ return Module(path, parent)
+
+
+@hookimpl(hookwrapper=True)
+def pytest_pycollect_makeitem(collector, name, obj):
+ outcome = yield
+ res = outcome.get_result()
+ if res is not None:
+ return
+ # nothing was collected elsewhere, let's do it here
+ if safe_isclass(obj):
+ if collector.istestclass(obj, name):
+ outcome.force_result(Class(name, parent=collector))
+ elif collector.istestfunction(obj, name):
+ # mock seems to store unbound methods (issue473), normalize it
+ obj = getattr(obj, "__func__", obj)
+ # We need to try and unwrap the function if it's a functools.partial
+ # or a funtools.wrapped.
+ # We musn't if it's been wrapped with mock.patch (python 2 only)
+ if not (isfunction(obj) or isfunction(get_real_func(obj))):
+ filename, lineno = getfslineno(obj)
+ warnings.warn_explicit(
message=PytestCollectionWarning(
- "cannot collect %r because it is not a function." % name
- ),
- category=None,
- filename=str(filename),
- lineno=lineno + 1,
- )
- elif getattr(obj, "__test__", True):
- if is_generator(obj):
+ "cannot collect %r because it is not a function." % name
+ ),
+ category=None,
+ filename=str(filename),
+ lineno=lineno + 1,
+ )
+ elif getattr(obj, "__test__", True):
+ if is_generator(obj):
res = Function(name, parent=collector)
reason = deprecated.YIELD_TESTS.format(name=name)
res.add_marker(MARK_GEN.xfail(run=False, reason=reason))
res.warn(PytestCollectionWarning(reason))
- else:
- res = list(collector._genfunctions(name, obj))
- outcome.force_result(res)
-
-
-def pytest_make_parametrize_id(config, val, argname=None):
- return None
-
-
-class PyobjContext(object):
- module = pyobj_property("Module")
- cls = pyobj_property("Class")
- instance = pyobj_property("Instance")
-
-
-class PyobjMixin(PyobjContext):
- _ALLOW_MARKERS = True
-
- def __init__(self, *k, **kw):
- super(PyobjMixin, self).__init__(*k, **kw)
-
+ else:
+ res = list(collector._genfunctions(name, obj))
+ outcome.force_result(res)
+
+
+def pytest_make_parametrize_id(config, val, argname=None):
+ return None
+
+
+class PyobjContext(object):
+ module = pyobj_property("Module")
+ cls = pyobj_property("Class")
+ instance = pyobj_property("Instance")
+
+
+class PyobjMixin(PyobjContext):
+ _ALLOW_MARKERS = True
+
+ def __init__(self, *k, **kw):
+ super(PyobjMixin, self).__init__(*k, **kw)
+
@property
def obj(self):
"""Underlying Python object."""
@@ -262,189 +262,189 @@ class PyobjMixin(PyobjContext):
if self._ALLOW_MARKERS:
self.own_markers.extend(get_unpacked_marks(self.obj))
return obj
-
+
@obj.setter
def obj(self, value):
self._obj = value
-
- def _getobj(self):
+
+ def _getobj(self):
"""Gets the underlying Python object. May be overwritten by subclasses."""
- return getattr(self.parent.obj, self.name)
-
- def getmodpath(self, stopatmodule=True, includemodule=False):
- """ return python path relative to the containing module. """
- chain = self.listchain()
- chain.reverse()
- parts = []
- for node in chain:
- if isinstance(node, Instance):
- continue
- name = node.name
- if isinstance(node, Module):
- name = os.path.splitext(name)[0]
- if stopatmodule:
- if includemodule:
- parts.append(name)
- break
- parts.append(name)
- parts.reverse()
- s = ".".join(parts)
- return s.replace(".[", "[")
-
- def reportinfo(self):
- # XXX caching?
- obj = self.obj
- compat_co_firstlineno = getattr(obj, "compat_co_firstlineno", None)
- if isinstance(compat_co_firstlineno, int):
- # nose compatibility
- fspath = sys.modules[obj.__module__].__file__
- if fspath.endswith(".pyc"):
- fspath = fspath[:-1]
- lineno = compat_co_firstlineno
- else:
- fspath, lineno = getfslineno(obj)
- modpath = self.getmodpath()
- assert isinstance(lineno, int)
- return fspath, lineno, modpath
-
-
-class PyCollector(PyobjMixin, nodes.Collector):
- def funcnamefilter(self, name):
- return self._matches_prefix_or_glob_option("python_functions", name)
-
- def isnosetest(self, obj):
- """ Look for the __test__ attribute, which is applied by the
- @nose.tools.istest decorator
- """
- # We explicitly check for "is True" here to not mistakenly treat
- # classes with a custom __getattr__ returning something truthy (like a
- # function) as test classes.
- return safe_getattr(obj, "__test__", False) is True
-
- def classnamefilter(self, name):
- return self._matches_prefix_or_glob_option("python_classes", name)
-
- def istestfunction(self, obj, name):
- if self.funcnamefilter(name) or self.isnosetest(obj):
- if isinstance(obj, staticmethod):
- # static methods need to be unwrapped
- obj = safe_getattr(obj, "__func__", False)
- return (
- safe_getattr(obj, "__call__", False)
- and fixtures.getfixturemarker(obj) is None
- )
- else:
- return False
-
- def istestclass(self, obj, name):
- return self.classnamefilter(name) or self.isnosetest(obj)
-
- def _matches_prefix_or_glob_option(self, option_name, name):
- """
- checks if the given name matches the prefix or glob-pattern defined
- in ini configuration.
- """
- for option in self.config.getini(option_name):
- if name.startswith(option):
- return True
- # check that name looks like a glob-string before calling fnmatch
- # because this is called for every name in each collected module,
- # and fnmatch is somewhat expensive to call
- elif ("*" in option or "?" in option or "[" in option) and fnmatch.fnmatch(
- name, option
- ):
- return True
- return False
-
- def collect(self):
- if not getattr(self.obj, "__test__", True):
- return []
-
- # NB. we avoid random getattrs and peek in the __dict__ instead
- # (XXX originally introduced from a PyPy need, still true?)
- dicts = [getattr(self.obj, "__dict__", {})]
- for basecls in inspect.getmro(self.obj.__class__):
- dicts.append(basecls.__dict__)
- seen = {}
- values = []
- for dic in dicts:
- for name, obj in list(dic.items()):
- if name in seen:
- continue
- seen[name] = True
- res = self._makeitem(name, obj)
- if res is None:
- continue
- if not isinstance(res, list):
- res = [res]
- values.extend(res)
- values.sort(key=lambda item: item.reportinfo()[:2])
- return values
-
- def _makeitem(self, name, obj):
- # assert self.ihook.fspath == self.fspath, self
- return self.ihook.pytest_pycollect_makeitem(collector=self, name=name, obj=obj)
-
- def _genfunctions(self, name, funcobj):
- module = self.getparent(Module).obj
- clscol = self.getparent(Class)
- cls = clscol and clscol.obj or None
- fm = self.session._fixturemanager
-
- definition = FunctionDefinition(name=name, parent=self, callobj=funcobj)
- fixtureinfo = fm.getfixtureinfo(definition, funcobj, cls)
-
- metafunc = Metafunc(
- definition, fixtureinfo, self.config, cls=cls, module=module
- )
- methods = []
- if hasattr(module, "pytest_generate_tests"):
- methods.append(module.pytest_generate_tests)
- if hasattr(cls, "pytest_generate_tests"):
- methods.append(cls().pytest_generate_tests)
- if methods:
- self.ihook.pytest_generate_tests.call_extra(
- methods, dict(metafunc=metafunc)
- )
- else:
- self.ihook.pytest_generate_tests(metafunc=metafunc)
-
- if not metafunc._calls:
- yield Function(name, parent=self, fixtureinfo=fixtureinfo)
- else:
- # add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs
- fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm)
-
- # add_funcarg_pseudo_fixture_def may have shadowed some fixtures
- # with direct parametrization, so make sure we update what the
- # function really needs.
- fixtureinfo.prune_dependency_tree()
-
- for callspec in metafunc._calls:
- subname = "%s[%s]" % (name, callspec.id)
- yield Function(
- name=subname,
- parent=self,
- callspec=callspec,
- callobj=funcobj,
- fixtureinfo=fixtureinfo,
- keywords={callspec.id: True},
- originalname=name,
- )
-
-
-class Module(nodes.File, PyCollector):
- """ Collector for test classes and functions. """
-
- def _getobj(self):
- return self._importtestmodule()
-
- def collect(self):
+ return getattr(self.parent.obj, self.name)
+
+ def getmodpath(self, stopatmodule=True, includemodule=False):
+ """ return python path relative to the containing module. """
+ chain = self.listchain()
+ chain.reverse()
+ parts = []
+ for node in chain:
+ if isinstance(node, Instance):
+ continue
+ name = node.name
+ if isinstance(node, Module):
+ name = os.path.splitext(name)[0]
+ if stopatmodule:
+ if includemodule:
+ parts.append(name)
+ break
+ parts.append(name)
+ parts.reverse()
+ s = ".".join(parts)
+ return s.replace(".[", "[")
+
+ def reportinfo(self):
+ # XXX caching?
+ obj = self.obj
+ compat_co_firstlineno = getattr(obj, "compat_co_firstlineno", None)
+ if isinstance(compat_co_firstlineno, int):
+ # nose compatibility
+ fspath = sys.modules[obj.__module__].__file__
+ if fspath.endswith(".pyc"):
+ fspath = fspath[:-1]
+ lineno = compat_co_firstlineno
+ else:
+ fspath, lineno = getfslineno(obj)
+ modpath = self.getmodpath()
+ assert isinstance(lineno, int)
+ return fspath, lineno, modpath
+
+
+class PyCollector(PyobjMixin, nodes.Collector):
+ def funcnamefilter(self, name):
+ return self._matches_prefix_or_glob_option("python_functions", name)
+
+ def isnosetest(self, obj):
+ """ Look for the __test__ attribute, which is applied by the
+ @nose.tools.istest decorator
+ """
+ # We explicitly check for "is True" here to not mistakenly treat
+ # classes with a custom __getattr__ returning something truthy (like a
+ # function) as test classes.
+ return safe_getattr(obj, "__test__", False) is True
+
+ def classnamefilter(self, name):
+ return self._matches_prefix_or_glob_option("python_classes", name)
+
+ def istestfunction(self, obj, name):
+ if self.funcnamefilter(name) or self.isnosetest(obj):
+ if isinstance(obj, staticmethod):
+ # static methods need to be unwrapped
+ obj = safe_getattr(obj, "__func__", False)
+ return (
+ safe_getattr(obj, "__call__", False)
+ and fixtures.getfixturemarker(obj) is None
+ )
+ else:
+ return False
+
+ def istestclass(self, obj, name):
+ return self.classnamefilter(name) or self.isnosetest(obj)
+
+ def _matches_prefix_or_glob_option(self, option_name, name):
+ """
+ checks if the given name matches the prefix or glob-pattern defined
+ in ini configuration.
+ """
+ for option in self.config.getini(option_name):
+ if name.startswith(option):
+ return True
+ # check that name looks like a glob-string before calling fnmatch
+ # because this is called for every name in each collected module,
+ # and fnmatch is somewhat expensive to call
+ elif ("*" in option or "?" in option or "[" in option) and fnmatch.fnmatch(
+ name, option
+ ):
+ return True
+ return False
+
+ def collect(self):
+ if not getattr(self.obj, "__test__", True):
+ return []
+
+ # NB. we avoid random getattrs and peek in the __dict__ instead
+ # (XXX originally introduced from a PyPy need, still true?)
+ dicts = [getattr(self.obj, "__dict__", {})]
+ for basecls in inspect.getmro(self.obj.__class__):
+ dicts.append(basecls.__dict__)
+ seen = {}
+ values = []
+ for dic in dicts:
+ for name, obj in list(dic.items()):
+ if name in seen:
+ continue
+ seen[name] = True
+ res = self._makeitem(name, obj)
+ if res is None:
+ continue
+ if not isinstance(res, list):
+ res = [res]
+ values.extend(res)
+ values.sort(key=lambda item: item.reportinfo()[:2])
+ return values
+
+ def _makeitem(self, name, obj):
+ # assert self.ihook.fspath == self.fspath, self
+ return self.ihook.pytest_pycollect_makeitem(collector=self, name=name, obj=obj)
+
+ def _genfunctions(self, name, funcobj):
+ module = self.getparent(Module).obj
+ clscol = self.getparent(Class)
+ cls = clscol and clscol.obj or None
+ fm = self.session._fixturemanager
+
+ definition = FunctionDefinition(name=name, parent=self, callobj=funcobj)
+ fixtureinfo = fm.getfixtureinfo(definition, funcobj, cls)
+
+ metafunc = Metafunc(
+ definition, fixtureinfo, self.config, cls=cls, module=module
+ )
+ methods = []
+ if hasattr(module, "pytest_generate_tests"):
+ methods.append(module.pytest_generate_tests)
+ if hasattr(cls, "pytest_generate_tests"):
+ methods.append(cls().pytest_generate_tests)
+ if methods:
+ self.ihook.pytest_generate_tests.call_extra(
+ methods, dict(metafunc=metafunc)
+ )
+ else:
+ self.ihook.pytest_generate_tests(metafunc=metafunc)
+
+ if not metafunc._calls:
+ yield Function(name, parent=self, fixtureinfo=fixtureinfo)
+ else:
+ # add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs
+ fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm)
+
+ # add_funcarg_pseudo_fixture_def may have shadowed some fixtures
+ # with direct parametrization, so make sure we update what the
+ # function really needs.
+ fixtureinfo.prune_dependency_tree()
+
+ for callspec in metafunc._calls:
+ subname = "%s[%s]" % (name, callspec.id)
+ yield Function(
+ name=subname,
+ parent=self,
+ callspec=callspec,
+ callobj=funcobj,
+ fixtureinfo=fixtureinfo,
+ keywords={callspec.id: True},
+ originalname=name,
+ )
+
+
+class Module(nodes.File, PyCollector):
+ """ Collector for test classes and functions. """
+
+ def _getobj(self):
+ return self._importtestmodule()
+
+ def collect(self):
self._inject_setup_module_fixture()
self._inject_setup_function_fixture()
- self.session._fixturemanager.parsefactories(self)
- return super(Module, self).collect()
-
+ self.session._fixturemanager.parsefactories(self)
+ return super(Module, self).collect()
+
def _inject_setup_module_fixture(self):
"""Injects a hidden autouse, module scoped fixture into the collected module object
that invokes setUpModule/tearDownModule if either or both are available.
@@ -500,68 +500,68 @@ class Module(nodes.File, PyCollector):
self.obj.__pytest_setup_function = xunit_setup_function_fixture
- def _importtestmodule(self):
- # we assume we are only called once per module
- importmode = self.config.getoption("--import-mode")
- try:
- mod = self.fspath.pyimport(ensuresyspath=importmode)
- except SyntaxError:
- raise self.CollectError(
+ def _importtestmodule(self):
+ # we assume we are only called once per module
+ importmode = self.config.getoption("--import-mode")
+ try:
+ mod = self.fspath.pyimport(ensuresyspath=importmode)
+ except SyntaxError:
+ raise self.CollectError(
_pytest._code.ExceptionInfo.from_current().getrepr(style="short")
- )
- except self.fspath.ImportMismatchError:
- e = sys.exc_info()[1]
- raise self.CollectError(
- "import file mismatch:\n"
- "imported module %r has this __file__ attribute:\n"
- " %s\n"
- "which is not the same as the test file we want to collect:\n"
- " %s\n"
- "HINT: remove __pycache__ / .pyc files and/or use a "
- "unique basename for your test file modules" % e.args
- )
- except ImportError:
- from _pytest._code.code import ExceptionInfo
-
+ )
+ except self.fspath.ImportMismatchError:
+ e = sys.exc_info()[1]
+ raise self.CollectError(
+ "import file mismatch:\n"
+ "imported module %r has this __file__ attribute:\n"
+ " %s\n"
+ "which is not the same as the test file we want to collect:\n"
+ " %s\n"
+ "HINT: remove __pycache__ / .pyc files and/or use a "
+ "unique basename for your test file modules" % e.args
+ )
+ except ImportError:
+ from _pytest._code.code import ExceptionInfo
+
exc_info = ExceptionInfo.from_current()
- if self.config.getoption("verbose") < 2:
- exc_info.traceback = exc_info.traceback.filter(filter_traceback)
- exc_repr = (
- exc_info.getrepr(style="short")
- if exc_info.traceback
- else exc_info.exconly()
- )
- formatted_tb = safe_str(exc_repr)
- raise self.CollectError(
- "ImportError while importing test module '{fspath}'.\n"
- "Hint: make sure your test modules/packages have valid Python names.\n"
- "Traceback:\n"
- "{traceback}".format(fspath=self.fspath, traceback=formatted_tb)
- )
- except _pytest.runner.Skipped as e:
- if e.allow_module_level:
- raise
- raise self.CollectError(
- "Using pytest.skip outside of a test is not allowed. "
- "To decorate a test function, use the @pytest.mark.skip "
- "or @pytest.mark.skipif decorators instead, and to skip a "
- "module use `pytestmark = pytest.mark.{skip,skipif}."
- )
- self.config.pluginmanager.consider_module(mod)
- return mod
-
-
-class Package(Module):
- def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None):
- session = parent.session
- nodes.FSCollector.__init__(
- self, fspath, parent=parent, config=config, session=session, nodeid=nodeid
- )
- self.name = fspath.dirname
- self.trace = session.trace
- self._norecursepatterns = session._norecursepatterns
- self.fspath = fspath
-
+ if self.config.getoption("verbose") < 2:
+ exc_info.traceback = exc_info.traceback.filter(filter_traceback)
+ exc_repr = (
+ exc_info.getrepr(style="short")
+ if exc_info.traceback
+ else exc_info.exconly()
+ )
+ formatted_tb = safe_str(exc_repr)
+ raise self.CollectError(
+ "ImportError while importing test module '{fspath}'.\n"
+ "Hint: make sure your test modules/packages have valid Python names.\n"
+ "Traceback:\n"
+ "{traceback}".format(fspath=self.fspath, traceback=formatted_tb)
+ )
+ except _pytest.runner.Skipped as e:
+ if e.allow_module_level:
+ raise
+ raise self.CollectError(
+ "Using pytest.skip outside of a test is not allowed. "
+ "To decorate a test function, use the @pytest.mark.skip "
+ "or @pytest.mark.skipif decorators instead, and to skip a "
+ "module use `pytestmark = pytest.mark.{skip,skipif}."
+ )
+ self.config.pluginmanager.consider_module(mod)
+ return mod
+
+
+class Package(Module):
+ def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None):
+ session = parent.session
+ nodes.FSCollector.__init__(
+ self, fspath, parent=parent, config=config, session=session, nodeid=nodeid
+ )
+ self.name = fspath.dirname
+ self.trace = session.trace
+ self._norecursepatterns = session._norecursepatterns
+ self.fspath = fspath
+
def setup(self):
# not using fixtures to call setup_module here because autouse fixtures
# from packages are not called automatically (#4085)
@@ -578,84 +578,84 @@ class Package(Module):
func = partial(_call_with_optional_argument, teardown_module, self.obj)
self.addfinalizer(func)
- def _recurse(self, dirpath):
- if dirpath.basename == "__pycache__":
- return False
- ihook = self.gethookproxy(dirpath.dirpath())
- if ihook.pytest_ignore_collect(path=dirpath, config=self.config):
- return
- for pat in self._norecursepatterns:
- if dirpath.check(fnmatch=pat):
- return False
- ihook = self.gethookproxy(dirpath)
- ihook.pytest_collect_directory(path=dirpath, parent=self)
- return True
-
- def gethookproxy(self, fspath):
- # check if we have the common case of running
- # hooks with all conftest.py filesall conftest.py
- pm = self.config.pluginmanager
- my_conftestmodules = pm._getconftestmodules(fspath)
- remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
- if remove_mods:
- # one or more conftests are not in use at this fspath
- proxy = FSHookProxy(fspath, pm, remove_mods)
- else:
- # all plugis are active for this fspath
- proxy = self.config.hook
- return proxy
-
- def _collectfile(self, path, handle_dupes=True):
+ def _recurse(self, dirpath):
+ if dirpath.basename == "__pycache__":
+ return False
+ ihook = self.gethookproxy(dirpath.dirpath())
+ if ihook.pytest_ignore_collect(path=dirpath, config=self.config):
+ return
+ for pat in self._norecursepatterns:
+ if dirpath.check(fnmatch=pat):
+ return False
+ ihook = self.gethookproxy(dirpath)
+ ihook.pytest_collect_directory(path=dirpath, parent=self)
+ return True
+
+ def gethookproxy(self, fspath):
+ # check if we have the common case of running
+ # hooks with all conftest.py filesall conftest.py
+ pm = self.config.pluginmanager
+ my_conftestmodules = pm._getconftestmodules(fspath)
+ remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
+ if remove_mods:
+ # one or more conftests are not in use at this fspath
+ proxy = FSHookProxy(fspath, pm, remove_mods)
+ else:
+ # all plugis are active for this fspath
+ proxy = self.config.hook
+ return proxy
+
+ def _collectfile(self, path, handle_dupes=True):
assert path.isfile(), "%r is not a file (isdir=%r, exists=%r, islink=%r)" % (
path,
path.isdir(),
path.exists(),
path.islink(),
)
- ihook = self.gethookproxy(path)
- if not self.isinitpath(path):
- if ihook.pytest_ignore_collect(path=path, config=self.config):
- return ()
-
- if handle_dupes:
- keepduplicates = self.config.getoption("keepduplicates")
- if not keepduplicates:
- duplicate_paths = self.config.pluginmanager._duplicatepaths
- if path in duplicate_paths:
- return ()
- else:
- duplicate_paths.add(path)
-
- if self.fspath == path: # __init__.py
- return [self]
-
- return ihook.pytest_collect_file(path=path, parent=self)
-
- def isinitpath(self, path):
- return path in self.session._initialpaths
-
- def collect(self):
- this_path = self.fspath.dirpath()
- init_module = this_path.join("__init__.py")
- if init_module.check(file=1) and path_matches_patterns(
- init_module, self.config.getini("python_files")
- ):
- yield Module(init_module, self)
- pkg_prefixes = set()
- for path in this_path.visit(rec=self._recurse, bf=True, sort=True):
- # We will visit our own __init__.py file, in which case we skip it.
+ ihook = self.gethookproxy(path)
+ if not self.isinitpath(path):
+ if ihook.pytest_ignore_collect(path=path, config=self.config):
+ return ()
+
+ if handle_dupes:
+ keepduplicates = self.config.getoption("keepduplicates")
+ if not keepduplicates:
+ duplicate_paths = self.config.pluginmanager._duplicatepaths
+ if path in duplicate_paths:
+ return ()
+ else:
+ duplicate_paths.add(path)
+
+ if self.fspath == path: # __init__.py
+ return [self]
+
+ return ihook.pytest_collect_file(path=path, parent=self)
+
+ def isinitpath(self, path):
+ return path in self.session._initialpaths
+
+ def collect(self):
+ this_path = self.fspath.dirpath()
+ init_module = this_path.join("__init__.py")
+ if init_module.check(file=1) and path_matches_patterns(
+ init_module, self.config.getini("python_files")
+ ):
+ yield Module(init_module, self)
+ pkg_prefixes = set()
+ for path in this_path.visit(rec=self._recurse, bf=True, sort=True):
+ # We will visit our own __init__.py file, in which case we skip it.
is_file = path.isfile()
if is_file:
- if path.basename == "__init__.py" and path.dirpath() == this_path:
- continue
-
- parts_ = parts(path.strpath)
- if any(
- pkg_prefix in parts_ and pkg_prefix.join("__init__.py") != path
- for pkg_prefix in pkg_prefixes
- ):
- continue
-
+ if path.basename == "__init__.py" and path.dirpath() == this_path:
+ continue
+
+ parts_ = parts(path.strpath)
+ if any(
+ pkg_prefix in parts_ and pkg_prefix.join("__init__.py") != path
+ for pkg_prefix in pkg_prefixes
+ ):
+ continue
+
if is_file:
for x in self._collectfile(path):
yield x
@@ -663,30 +663,30 @@ class Package(Module):
# Broken symlink or invalid/missing file.
continue
elif path.join("__init__.py").check(file=1):
- pkg_prefixes.add(path)
-
-
-def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
- """
- Return a callable to perform xunit-style setup or teardown if
- the function exists in the ``holder`` object.
- The ``param_obj`` parameter is the parameter which will be passed to the function
- when the callable is called without arguments, defaults to the ``holder`` object.
- Return ``None`` if a suitable callable is not found.
- """
+ pkg_prefixes.add(path)
+
+
+def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
+ """
+ Return a callable to perform xunit-style setup or teardown if
+ the function exists in the ``holder`` object.
+ The ``param_obj`` parameter is the parameter which will be passed to the function
+ when the callable is called without arguments, defaults to the ``holder`` object.
+ Return ``None`` if a suitable callable is not found.
+ """
# TODO: only needed because of Package!
- param_obj = param_obj if param_obj is not None else holder
+ param_obj = param_obj if param_obj is not None else holder
result = _get_non_fixture_func(holder, attr_name)
- if result is not None:
- arg_count = result.__code__.co_argcount
- if inspect.ismethod(result):
- arg_count -= 1
- if arg_count:
- return lambda: result(param_obj)
- else:
- return result
-
-
+ if result is not None:
+ arg_count = result.__code__.co_argcount
+ if inspect.ismethod(result):
+ arg_count -= 1
+ if arg_count:
+ return lambda: result(param_obj)
+ else:
+ return result
+
+
def _call_with_optional_argument(func, arg):
"""Call the given function with the given argument if func accepts one argument, otherwise
calls func without arguments"""
@@ -700,49 +700,49 @@ def _call_with_optional_argument(func, arg):
def _get_non_fixture_func(obj, name):
- """Return the attribute from the given object to be used as a setup/teardown
- xunit-style function, but only if not marked as a fixture to
- avoid calling it twice.
- """
- meth = getattr(obj, name, None)
- if fixtures.getfixturemarker(meth) is None:
- return meth
-
-
-class Class(PyCollector):
- """ Collector for test methods. """
-
- def collect(self):
- if not safe_getattr(self.obj, "__test__", True):
- return []
- if hasinit(self.obj):
- self.warn(
+ """Return the attribute from the given object to be used as a setup/teardown
+ xunit-style function, but only if not marked as a fixture to
+ avoid calling it twice.
+ """
+ meth = getattr(obj, name, None)
+ if fixtures.getfixturemarker(meth) is None:
+ return meth
+
+
+class Class(PyCollector):
+ """ Collector for test methods. """
+
+ def collect(self):
+ if not safe_getattr(self.obj, "__test__", True):
+ return []
+ if hasinit(self.obj):
+ self.warn(
PytestCollectionWarning(
- "cannot collect test class %r because it has a "
+ "cannot collect test class %r because it has a "
"__init__ constructor (from: %s)"
% (self.obj.__name__, self.parent.nodeid)
- )
- )
- return []
- elif hasnew(self.obj):
- self.warn(
+ )
+ )
+ return []
+ elif hasnew(self.obj):
+ self.warn(
PytestCollectionWarning(
- "cannot collect test class %r because it has a "
+ "cannot collect test class %r because it has a "
"__new__ constructor (from: %s)"
% (self.obj.__name__, self.parent.nodeid)
- )
- )
- return []
-
+ )
+ )
+ return []
+
self._inject_setup_class_fixture()
self._inject_setup_method_fixture()
-
+
return [Instance(name="()", parent=self)]
-
+
def _inject_setup_class_fixture(self):
"""Injects a hidden autouse, class scoped fixture into the collected class object
that invokes setup_class/teardown_class if either or both are available.
-
+
Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
other fixtures (#517).
"""
@@ -789,378 +789,378 @@ class Class(PyCollector):
self.obj.__pytest_setup_method = xunit_setup_method_fixture
-class Instance(PyCollector):
- _ALLOW_MARKERS = False # hack, destroy later
- # instances share the object with their parents in a way
- # that duplicates markers instances if not taken out
- # can be removed at node structure reorganization time
-
- def _getobj(self):
- return self.parent.obj()
-
- def collect(self):
- self.session._fixturemanager.parsefactories(self)
- return super(Instance, self).collect()
-
- def newinstance(self):
- self.obj = self._getobj()
- return self.obj
-
-
-class FunctionMixin(PyobjMixin):
- """ mixin for the code common to Function and Generator.
- """
-
- def setup(self):
- """ perform setup for this test function. """
+class Instance(PyCollector):
+ _ALLOW_MARKERS = False # hack, destroy later
+ # instances share the object with their parents in a way
+ # that duplicates markers instances if not taken out
+ # can be removed at node structure reorganization time
+
+ def _getobj(self):
+ return self.parent.obj()
+
+ def collect(self):
+ self.session._fixturemanager.parsefactories(self)
+ return super(Instance, self).collect()
+
+ def newinstance(self):
+ self.obj = self._getobj()
+ return self.obj
+
+
+class FunctionMixin(PyobjMixin):
+ """ mixin for the code common to Function and Generator.
+ """
+
+ def setup(self):
+ """ perform setup for this test function. """
if isinstance(self.parent, Instance):
self.parent.newinstance()
- self.obj = self._getobj()
-
- def _prunetraceback(self, excinfo):
+ self.obj = self._getobj()
+
+ def _prunetraceback(self, excinfo):
if hasattr(self, "_obj") and not self.config.getoption("fulltrace", False):
- code = _pytest._code.Code(get_real_func(self.obj))
- path, firstlineno = code.path, code.firstlineno
- traceback = excinfo.traceback
- ntraceback = traceback.cut(path=path, firstlineno=firstlineno)
- if ntraceback == traceback:
- ntraceback = ntraceback.cut(path=path)
- if ntraceback == traceback:
- ntraceback = ntraceback.filter(filter_traceback)
- if not ntraceback:
- ntraceback = traceback
-
- excinfo.traceback = ntraceback.filter()
- # issue364: mark all but first and last frames to
- # only show a single-line message for each frame
+ code = _pytest._code.Code(get_real_func(self.obj))
+ path, firstlineno = code.path, code.firstlineno
+ traceback = excinfo.traceback
+ ntraceback = traceback.cut(path=path, firstlineno=firstlineno)
+ if ntraceback == traceback:
+ ntraceback = ntraceback.cut(path=path)
+ if ntraceback == traceback:
+ ntraceback = ntraceback.filter(filter_traceback)
+ if not ntraceback:
+ ntraceback = traceback
+
+ excinfo.traceback = ntraceback.filter()
+ # issue364: mark all but first and last frames to
+ # only show a single-line message for each frame
if self.config.getoption("tbstyle", "auto") == "auto":
- if len(excinfo.traceback) > 2:
- for entry in excinfo.traceback[1:-1]:
- entry.set_repr_style("short")
-
- def repr_failure(self, excinfo, outerr=None):
- assert outerr is None, "XXX outerr usage is deprecated"
+ if len(excinfo.traceback) > 2:
+ for entry in excinfo.traceback[1:-1]:
+ entry.set_repr_style("short")
+
+ def repr_failure(self, excinfo, outerr=None):
+ assert outerr is None, "XXX outerr usage is deprecated"
style = self.config.getoption("tbstyle", "auto")
- if style == "auto":
- style = "long"
- return self._repr_failure_py(excinfo, style=style)
-
-
-def hasinit(obj):
- init = getattr(obj, "__init__", None)
- if init:
- return init != object.__init__
-
-
-def hasnew(obj):
- new = getattr(obj, "__new__", None)
- if new:
- return new != object.__new__
-
-
-class CallSpec2(object):
- def __init__(self, metafunc):
- self.metafunc = metafunc
- self.funcargs = {}
- self._idlist = []
- self.params = {}
- self._globalid = NOTSET
- self._globalparam = NOTSET
- self._arg2scopenum = {} # used for sorting parametrized resources
- self.marks = []
- self.indices = {}
-
- def copy(self):
- cs = CallSpec2(self.metafunc)
- cs.funcargs.update(self.funcargs)
- cs.params.update(self.params)
- cs.marks.extend(self.marks)
- cs.indices.update(self.indices)
- cs._arg2scopenum.update(self._arg2scopenum)
- cs._idlist = list(self._idlist)
- cs._globalid = self._globalid
- cs._globalparam = self._globalparam
- return cs
-
- def _checkargnotcontained(self, arg):
- if arg in self.params or arg in self.funcargs:
- raise ValueError("duplicate %r" % (arg,))
-
- def getparam(self, name):
- try:
- return self.params[name]
- except KeyError:
- if self._globalparam is NOTSET:
- raise ValueError(name)
- return self._globalparam
-
- @property
- def id(self):
+ if style == "auto":
+ style = "long"
+ return self._repr_failure_py(excinfo, style=style)
+
+
+def hasinit(obj):
+ init = getattr(obj, "__init__", None)
+ if init:
+ return init != object.__init__
+
+
+def hasnew(obj):
+ new = getattr(obj, "__new__", None)
+ if new:
+ return new != object.__new__
+
+
+class CallSpec2(object):
+ def __init__(self, metafunc):
+ self.metafunc = metafunc
+ self.funcargs = {}
+ self._idlist = []
+ self.params = {}
+ self._globalid = NOTSET
+ self._globalparam = NOTSET
+ self._arg2scopenum = {} # used for sorting parametrized resources
+ self.marks = []
+ self.indices = {}
+
+ def copy(self):
+ cs = CallSpec2(self.metafunc)
+ cs.funcargs.update(self.funcargs)
+ cs.params.update(self.params)
+ cs.marks.extend(self.marks)
+ cs.indices.update(self.indices)
+ cs._arg2scopenum.update(self._arg2scopenum)
+ cs._idlist = list(self._idlist)
+ cs._globalid = self._globalid
+ cs._globalparam = self._globalparam
+ return cs
+
+ def _checkargnotcontained(self, arg):
+ if arg in self.params or arg in self.funcargs:
+ raise ValueError("duplicate %r" % (arg,))
+
+ def getparam(self, name):
+ try:
+ return self.params[name]
+ except KeyError:
+ if self._globalparam is NOTSET:
+ raise ValueError(name)
+ return self._globalparam
+
+ @property
+ def id(self):
return "-".join(map(safe_str, self._idlist))
-
- def setmulti2(self, valtypes, argnames, valset, id, marks, scopenum, param_index):
- for arg, val in zip(argnames, valset):
- self._checkargnotcontained(arg)
- valtype_for_arg = valtypes[arg]
- getattr(self, valtype_for_arg)[arg] = val
- self.indices[arg] = param_index
- self._arg2scopenum[arg] = scopenum
- self._idlist.append(id)
- self.marks.extend(normalize_mark_list(marks))
-
- def setall(self, funcargs, id, param):
- for x in funcargs:
- self._checkargnotcontained(x)
- self.funcargs.update(funcargs)
- if id is not NOTSET:
- self._idlist.append(id)
- if param is not NOTSET:
- assert self._globalparam is NOTSET
- self._globalparam = param
- for arg in funcargs:
- self._arg2scopenum[arg] = fixtures.scopenum_function
-
-
-class Metafunc(fixtures.FuncargnamesCompatAttr):
- """
- Metafunc objects are passed to the :func:`pytest_generate_tests <_pytest.hookspec.pytest_generate_tests>` hook.
- They help to inspect a test function and to generate tests according to
- test configuration or values specified in the class or module where a
- test function is defined.
- """
-
- def __init__(self, definition, fixtureinfo, config, cls=None, module=None):
- assert (
- isinstance(definition, FunctionDefinition)
- or type(definition).__name__ == "DefinitionMock"
- )
- self.definition = definition
-
- #: access to the :class:`_pytest.config.Config` object for the test session
- self.config = config
-
- #: the module object where the test function is defined in.
- self.module = module
-
- #: underlying python test function
- self.function = definition.obj
-
- #: set of fixture names required by the test function
- self.fixturenames = fixtureinfo.names_closure
-
- #: class object where the test function is defined in or ``None``.
- self.cls = cls
-
- self._calls = []
- self._ids = set()
- self._arg2fixturedefs = fixtureinfo.name2fixturedefs
-
- def parametrize(self, argnames, argvalues, indirect=False, ids=None, scope=None):
- """ Add new invocations to the underlying test function using the list
- of argvalues for the given argnames. Parametrization is performed
- during the collection phase. If you need to setup expensive resources
- see about setting indirect to do it rather at test setup time.
-
- :arg argnames: a comma-separated string denoting one or more argument
- names, or a list/tuple of argument strings.
-
- :arg argvalues: The list of argvalues determines how often a
- test is invoked with different argument values. If only one
- argname was specified argvalues is a list of values. If N
- argnames were specified, argvalues must be a list of N-tuples,
- where each tuple-element specifies a value for its respective
- argname.
-
- :arg indirect: The list of argnames or boolean. A list of arguments'
- names (subset of argnames). If True the list contains all names from
- the argnames. Each argvalue corresponding to an argname in this list will
- be passed as request.param to its respective argname fixture
- function so that it can perform more expensive setups during the
- setup phase of a test rather than at collection time.
-
- :arg ids: list of string ids, or a callable.
- If strings, each is corresponding to the argvalues so that they are
- part of the test id. If None is given as id of specific test, the
- automatically generated id for that argument will be used.
- If callable, it should take one argument (a single argvalue) and return
- a string or return None. If None, the automatically generated id for that
- argument will be used.
- If no ids are provided they will be generated automatically from
- the argvalues.
-
- :arg scope: if specified it denotes the scope of the parameters.
- The scope is used for grouping tests by parameter instances.
- It will also override any fixture-function defined scope, allowing
- to set a dynamic scope using test context or configuration.
- """
- from _pytest.fixtures import scope2index
- from _pytest.mark import ParameterSet
-
- argnames, parameters = ParameterSet._for_parametrize(
- argnames,
- argvalues,
- self.function,
- self.config,
- function_definition=self.definition,
- )
- del argvalues
-
- if scope is None:
- scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
-
- self._validate_if_using_arg_names(argnames, indirect)
-
- arg_values_types = self._resolve_arg_value_types(argnames, indirect)
-
- ids = self._resolve_arg_ids(argnames, ids, parameters, item=self.definition)
-
- scopenum = scope2index(
- scope, descr="parametrize() call in {}".format(self.function.__name__)
- )
-
- # create the new calls: if we are parametrize() multiple times (by applying the decorator
- # more than once) then we accumulate those calls generating the cartesian product
- # of all calls
- newcalls = []
- for callspec in self._calls or [CallSpec2(self)]:
- for param_index, (param_id, param_set) in enumerate(zip(ids, parameters)):
- newcallspec = callspec.copy()
- newcallspec.setmulti2(
- arg_values_types,
- argnames,
- param_set.values,
- param_id,
- param_set.marks,
- scopenum,
- param_index,
- )
- newcalls.append(newcallspec)
- self._calls = newcalls
-
- def _resolve_arg_ids(self, argnames, ids, parameters, item):
- """Resolves the actual ids for the given argnames, based on the ``ids`` parameter given
- to ``parametrize``.
-
- :param List[str] argnames: list of argument names passed to ``parametrize()``.
- :param ids: the ids parameter of the parametrized call (see docs).
- :param List[ParameterSet] parameters: the list of parameter values, same size as ``argnames``.
- :param Item item: the item that generated this parametrized call.
- :rtype: List[str]
- :return: the list of ids for each argname given
- """
+
+ def setmulti2(self, valtypes, argnames, valset, id, marks, scopenum, param_index):
+ for arg, val in zip(argnames, valset):
+ self._checkargnotcontained(arg)
+ valtype_for_arg = valtypes[arg]
+ getattr(self, valtype_for_arg)[arg] = val
+ self.indices[arg] = param_index
+ self._arg2scopenum[arg] = scopenum
+ self._idlist.append(id)
+ self.marks.extend(normalize_mark_list(marks))
+
+ def setall(self, funcargs, id, param):
+ for x in funcargs:
+ self._checkargnotcontained(x)
+ self.funcargs.update(funcargs)
+ if id is not NOTSET:
+ self._idlist.append(id)
+ if param is not NOTSET:
+ assert self._globalparam is NOTSET
+ self._globalparam = param
+ for arg in funcargs:
+ self._arg2scopenum[arg] = fixtures.scopenum_function
+
+
+class Metafunc(fixtures.FuncargnamesCompatAttr):
+ """
+ Metafunc objects are passed to the :func:`pytest_generate_tests <_pytest.hookspec.pytest_generate_tests>` hook.
+ They help to inspect a test function and to generate tests according to
+ test configuration or values specified in the class or module where a
+ test function is defined.
+ """
+
+ def __init__(self, definition, fixtureinfo, config, cls=None, module=None):
+ assert (
+ isinstance(definition, FunctionDefinition)
+ or type(definition).__name__ == "DefinitionMock"
+ )
+ self.definition = definition
+
+ #: access to the :class:`_pytest.config.Config` object for the test session
+ self.config = config
+
+ #: the module object where the test function is defined in.
+ self.module = module
+
+ #: underlying python test function
+ self.function = definition.obj
+
+ #: set of fixture names required by the test function
+ self.fixturenames = fixtureinfo.names_closure
+
+ #: class object where the test function is defined in or ``None``.
+ self.cls = cls
+
+ self._calls = []
+ self._ids = set()
+ self._arg2fixturedefs = fixtureinfo.name2fixturedefs
+
+ def parametrize(self, argnames, argvalues, indirect=False, ids=None, scope=None):
+ """ Add new invocations to the underlying test function using the list
+ of argvalues for the given argnames. Parametrization is performed
+ during the collection phase. If you need to setup expensive resources
+ see about setting indirect to do it rather at test setup time.
+
+ :arg argnames: a comma-separated string denoting one or more argument
+ names, or a list/tuple of argument strings.
+
+ :arg argvalues: The list of argvalues determines how often a
+ test is invoked with different argument values. If only one
+ argname was specified argvalues is a list of values. If N
+ argnames were specified, argvalues must be a list of N-tuples,
+ where each tuple-element specifies a value for its respective
+ argname.
+
+ :arg indirect: The list of argnames or boolean. A list of arguments'
+ names (subset of argnames). If True the list contains all names from
+ the argnames. Each argvalue corresponding to an argname in this list will
+ be passed as request.param to its respective argname fixture
+ function so that it can perform more expensive setups during the
+ setup phase of a test rather than at collection time.
+
+ :arg ids: list of string ids, or a callable.
+ If strings, each is corresponding to the argvalues so that they are
+ part of the test id. If None is given as id of specific test, the
+ automatically generated id for that argument will be used.
+ If callable, it should take one argument (a single argvalue) and return
+ a string or return None. If None, the automatically generated id for that
+ argument will be used.
+ If no ids are provided they will be generated automatically from
+ the argvalues.
+
+ :arg scope: if specified it denotes the scope of the parameters.
+ The scope is used for grouping tests by parameter instances.
+ It will also override any fixture-function defined scope, allowing
+ to set a dynamic scope using test context or configuration.
+ """
+ from _pytest.fixtures import scope2index
+ from _pytest.mark import ParameterSet
+
+ argnames, parameters = ParameterSet._for_parametrize(
+ argnames,
+ argvalues,
+ self.function,
+ self.config,
+ function_definition=self.definition,
+ )
+ del argvalues
+
+ if scope is None:
+ scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
+
+ self._validate_if_using_arg_names(argnames, indirect)
+
+ arg_values_types = self._resolve_arg_value_types(argnames, indirect)
+
+ ids = self._resolve_arg_ids(argnames, ids, parameters, item=self.definition)
+
+ scopenum = scope2index(
+ scope, descr="parametrize() call in {}".format(self.function.__name__)
+ )
+
+ # create the new calls: if we are parametrize() multiple times (by applying the decorator
+ # more than once) then we accumulate those calls generating the cartesian product
+ # of all calls
+ newcalls = []
+ for callspec in self._calls or [CallSpec2(self)]:
+ for param_index, (param_id, param_set) in enumerate(zip(ids, parameters)):
+ newcallspec = callspec.copy()
+ newcallspec.setmulti2(
+ arg_values_types,
+ argnames,
+ param_set.values,
+ param_id,
+ param_set.marks,
+ scopenum,
+ param_index,
+ )
+ newcalls.append(newcallspec)
+ self._calls = newcalls
+
+ def _resolve_arg_ids(self, argnames, ids, parameters, item):
+ """Resolves the actual ids for the given argnames, based on the ``ids`` parameter given
+ to ``parametrize``.
+
+ :param List[str] argnames: list of argument names passed to ``parametrize()``.
+ :param ids: the ids parameter of the parametrized call (see docs).
+ :param List[ParameterSet] parameters: the list of parameter values, same size as ``argnames``.
+ :param Item item: the item that generated this parametrized call.
+ :rtype: List[str]
+ :return: the list of ids for each argname given
+ """
from _pytest._io.saferepr import saferepr
-
- idfn = None
- if callable(ids):
- idfn = ids
- ids = None
- if ids:
- func_name = self.function.__name__
- if len(ids) != len(parameters):
- msg = "In {}: {} parameter sets specified, with different number of ids: {}"
- fail(msg.format(func_name, len(parameters), len(ids)), pytrace=False)
- for id_value in ids:
- if id_value is not None and not isinstance(id_value, six.string_types):
- msg = "In {}: ids must be list of strings, found: {} (type: {!r})"
- fail(
- msg.format(func_name, saferepr(id_value), type(id_value)),
- pytrace=False,
- )
- ids = idmaker(argnames, parameters, idfn, ids, self.config, item=item)
- return ids
-
- def _resolve_arg_value_types(self, argnames, indirect):
- """Resolves if each parametrized argument must be considered a parameter to a fixture or a "funcarg"
- to the function, based on the ``indirect`` parameter of the parametrized() call.
-
- :param List[str] argnames: list of argument names passed to ``parametrize()``.
- :param indirect: same ``indirect`` parameter of ``parametrize()``.
- :rtype: Dict[str, str]
- A dict mapping each arg name to either:
- * "params" if the argname should be the parameter of a fixture of the same name.
- * "funcargs" if the argname should be a parameter to the parametrized test function.
- """
- valtypes = {}
- if indirect is True:
- valtypes = dict.fromkeys(argnames, "params")
- elif indirect is False:
- valtypes = dict.fromkeys(argnames, "funcargs")
- elif isinstance(indirect, (tuple, list)):
- valtypes = dict.fromkeys(argnames, "funcargs")
- for arg in indirect:
- if arg not in argnames:
- fail(
- "In {}: indirect fixture '{}' doesn't exist".format(
- self.function.__name__, arg
- ),
- pytrace=False,
- )
- valtypes[arg] = "params"
- return valtypes
-
- def _validate_if_using_arg_names(self, argnames, indirect):
- """
- Check if all argnames are being used, by default values, or directly/indirectly.
-
- :param List[str] argnames: list of argument names passed to ``parametrize()``.
- :param indirect: same ``indirect`` parameter of ``parametrize()``.
- :raise ValueError: if validation fails.
- """
- default_arg_names = set(get_default_arg_names(self.function))
- func_name = self.function.__name__
- for arg in argnames:
- if arg not in self.fixturenames:
- if arg in default_arg_names:
- fail(
- "In {}: function already takes an argument '{}' with a default value".format(
- func_name, arg
- ),
- pytrace=False,
- )
- else:
- if isinstance(indirect, (tuple, list)):
- name = "fixture" if arg in indirect else "argument"
- else:
- name = "fixture" if indirect else "argument"
- fail(
- "In {}: function uses no {} '{}'".format(func_name, name, arg),
- pytrace=False,
- )
-
-
-def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
- """Find the most appropriate scope for a parametrized call based on its arguments.
-
- When there's at least one direct argument, always use "function" scope.
-
- When a test function is parametrized and all its arguments are indirect
- (e.g. fixtures), return the most narrow scope based on the fixtures used.
-
- Related to issue #1832, based on code posted by @Kingdread.
- """
- from _pytest.fixtures import scopes
-
- if isinstance(indirect, (list, tuple)):
- all_arguments_are_fixtures = len(indirect) == len(argnames)
- else:
- all_arguments_are_fixtures = bool(indirect)
-
- if all_arguments_are_fixtures:
- fixturedefs = arg2fixturedefs or {}
- used_scopes = [
- fixturedef[0].scope
- for name, fixturedef in fixturedefs.items()
- if name in argnames
- ]
- if used_scopes:
- # Takes the most narrow scope from used fixtures
- for scope in reversed(scopes):
- if scope in used_scopes:
- return scope
-
- return "function"
-
-
+
+ idfn = None
+ if callable(ids):
+ idfn = ids
+ ids = None
+ if ids:
+ func_name = self.function.__name__
+ if len(ids) != len(parameters):
+ msg = "In {}: {} parameter sets specified, with different number of ids: {}"
+ fail(msg.format(func_name, len(parameters), len(ids)), pytrace=False)
+ for id_value in ids:
+ if id_value is not None and not isinstance(id_value, six.string_types):
+ msg = "In {}: ids must be list of strings, found: {} (type: {!r})"
+ fail(
+ msg.format(func_name, saferepr(id_value), type(id_value)),
+ pytrace=False,
+ )
+ ids = idmaker(argnames, parameters, idfn, ids, self.config, item=item)
+ return ids
+
+ def _resolve_arg_value_types(self, argnames, indirect):
+ """Resolves if each parametrized argument must be considered a parameter to a fixture or a "funcarg"
+ to the function, based on the ``indirect`` parameter of the parametrized() call.
+
+ :param List[str] argnames: list of argument names passed to ``parametrize()``.
+ :param indirect: same ``indirect`` parameter of ``parametrize()``.
+ :rtype: Dict[str, str]
+ A dict mapping each arg name to either:
+ * "params" if the argname should be the parameter of a fixture of the same name.
+ * "funcargs" if the argname should be a parameter to the parametrized test function.
+ """
+ valtypes = {}
+ if indirect is True:
+ valtypes = dict.fromkeys(argnames, "params")
+ elif indirect is False:
+ valtypes = dict.fromkeys(argnames, "funcargs")
+ elif isinstance(indirect, (tuple, list)):
+ valtypes = dict.fromkeys(argnames, "funcargs")
+ for arg in indirect:
+ if arg not in argnames:
+ fail(
+ "In {}: indirect fixture '{}' doesn't exist".format(
+ self.function.__name__, arg
+ ),
+ pytrace=False,
+ )
+ valtypes[arg] = "params"
+ return valtypes
+
+ def _validate_if_using_arg_names(self, argnames, indirect):
+ """
+ Check if all argnames are being used, by default values, or directly/indirectly.
+
+ :param List[str] argnames: list of argument names passed to ``parametrize()``.
+ :param indirect: same ``indirect`` parameter of ``parametrize()``.
+ :raise ValueError: if validation fails.
+ """
+ default_arg_names = set(get_default_arg_names(self.function))
+ func_name = self.function.__name__
+ for arg in argnames:
+ if arg not in self.fixturenames:
+ if arg in default_arg_names:
+ fail(
+ "In {}: function already takes an argument '{}' with a default value".format(
+ func_name, arg
+ ),
+ pytrace=False,
+ )
+ else:
+ if isinstance(indirect, (tuple, list)):
+ name = "fixture" if arg in indirect else "argument"
+ else:
+ name = "fixture" if indirect else "argument"
+ fail(
+ "In {}: function uses no {} '{}'".format(func_name, name, arg),
+ pytrace=False,
+ )
+
+
+def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
+ """Find the most appropriate scope for a parametrized call based on its arguments.
+
+ When there's at least one direct argument, always use "function" scope.
+
+ When a test function is parametrized and all its arguments are indirect
+ (e.g. fixtures), return the most narrow scope based on the fixtures used.
+
+ Related to issue #1832, based on code posted by @Kingdread.
+ """
+ from _pytest.fixtures import scopes
+
+ if isinstance(indirect, (list, tuple)):
+ all_arguments_are_fixtures = len(indirect) == len(argnames)
+ else:
+ all_arguments_are_fixtures = bool(indirect)
+
+ if all_arguments_are_fixtures:
+ fixturedefs = arg2fixturedefs or {}
+ used_scopes = [
+ fixturedef[0].scope
+ for name, fixturedef in fixturedefs.items()
+ if name in argnames
+ ]
+ if used_scopes:
+ # Takes the most narrow scope from used fixtures
+ for scope in reversed(scopes):
+ if scope in used_scopes:
+ return scope
+
+ return "function"
+
+
def _ascii_escaped_by_config(val, config):
if config is None:
escape_option = False
@@ -1171,39 +1171,39 @@ def _ascii_escaped_by_config(val, config):
return val if escape_option else ascii_escaped(val)
-def _idval(val, argname, idx, idfn, item, config):
- if idfn:
- try:
+def _idval(val, argname, idx, idfn, item, config):
+ if idfn:
+ try:
generated_id = idfn(val)
if generated_id is not None:
val = generated_id
- except Exception as e:
- # See issue https://github.com/pytest-dev/pytest/issues/2169
+ except Exception as e:
+ # See issue https://github.com/pytest-dev/pytest/issues/2169
msg = "{}: error raised while trying to determine id of parameter '{}' at position {}\n"
msg = msg.format(item.nodeid, argname, idx)
# we only append the exception type and message because on Python 2 reraise does nothing
- msg += " {}: {}\n".format(type(e).__name__, e)
+ msg += " {}: {}\n".format(type(e).__name__, e)
six.raise_from(ValueError(msg), e)
elif config:
- hook_id = config.hook.pytest_make_parametrize_id(
- config=config, val=val, argname=argname
- )
- if hook_id:
- return hook_id
-
- if isinstance(val, STRING_TYPES):
+ hook_id = config.hook.pytest_make_parametrize_id(
+ config=config, val=val, argname=argname
+ )
+ if hook_id:
+ return hook_id
+
+ if isinstance(val, STRING_TYPES):
return _ascii_escaped_by_config(val, config)
elif val is None or isinstance(val, (float, int, bool)):
- return str(val)
- elif isinstance(val, REGEX_TYPE):
- return ascii_escaped(val.pattern)
- elif enum is not None and isinstance(val, enum.Enum):
- return str(val)
- elif (isclass(val) or isfunction(val)) and hasattr(val, "__name__"):
- return val.__name__
- return str(argname) + str(idx)
-
-
+ return str(val)
+ elif isinstance(val, REGEX_TYPE):
+ return ascii_escaped(val.pattern)
+ elif enum is not None and isinstance(val, enum.Enum):
+ return str(val)
+ elif (isclass(val) or isfunction(val)) and hasattr(val, "__name__"):
+ return val.__name__
+ return str(argname) + str(idx)
+
+
def limit_idval(limit):
import functools
@@ -1231,212 +1231,212 @@ def limit_idval(limit):
# XXX limit testnames in the name of sanity and readability
@limit_idval(limit=500)
-def _idvalset(idx, parameterset, argnames, idfn, ids, item, config):
- if parameterset.id is not None:
- return parameterset.id
- if ids is None or (idx >= len(ids) or ids[idx] is None):
- this_id = [
- _idval(val, argname, idx, idfn, item=item, config=config)
- for val, argname in zip(parameterset.values, argnames)
- ]
- return "-".join(this_id)
- else:
+def _idvalset(idx, parameterset, argnames, idfn, ids, item, config):
+ if parameterset.id is not None:
+ return parameterset.id
+ if ids is None or (idx >= len(ids) or ids[idx] is None):
+ this_id = [
+ _idval(val, argname, idx, idfn, item=item, config=config)
+ for val, argname in zip(parameterset.values, argnames)
+ ]
+ return "-".join(this_id)
+ else:
return _ascii_escaped_by_config(ids[idx], config)
-
-
-def idmaker(argnames, parametersets, idfn=None, ids=None, config=None, item=None):
- ids = [
- _idvalset(valindex, parameterset, argnames, idfn, ids, config=config, item=item)
- for valindex, parameterset in enumerate(parametersets)
- ]
- if len(set(ids)) != len(ids):
- # The ids are not unique
- duplicates = [testid for testid in ids if ids.count(testid) > 1]
- counters = collections.defaultdict(lambda: 0)
- for index, testid in enumerate(ids):
- if testid in duplicates:
- ids[index] = testid + str(counters[testid])
- counters[testid] += 1
- return ids
-
-
-def show_fixtures_per_test(config):
- from _pytest.main import wrap_session
-
- return wrap_session(config, _show_fixtures_per_test)
-
-
-def _show_fixtures_per_test(config, session):
- import _pytest.config
-
- session.perform_collect()
- curdir = py.path.local()
- tw = _pytest.config.create_terminal_writer(config)
- verbose = config.getvalue("verbose")
-
- def get_best_relpath(func):
- loc = getlocation(func, curdir)
- return curdir.bestrelpath(loc)
-
- def write_fixture(fixture_def):
- argname = fixture_def.argname
- if verbose <= 0 and argname.startswith("_"):
- return
- if verbose > 0:
- bestrel = get_best_relpath(fixture_def.func)
- funcargspec = "{} -- {}".format(argname, bestrel)
- else:
- funcargspec = argname
- tw.line(funcargspec, green=True)
- fixture_doc = fixture_def.func.__doc__
- if fixture_doc:
- write_docstring(tw, fixture_doc)
- else:
- tw.line(" no docstring available", red=True)
-
- def write_item(item):
- try:
- info = item._fixtureinfo
- except AttributeError:
- # doctests items have no _fixtureinfo attribute
- return
- if not info.name2fixturedefs:
- # this test item does not use any fixtures
- return
- tw.line()
- tw.sep("-", "fixtures used by {}".format(item.name))
- tw.sep("-", "({})".format(get_best_relpath(item.function)))
- # dict key not used in loop but needed for sorting
- for _, fixturedefs in sorted(info.name2fixturedefs.items()):
- assert fixturedefs is not None
- if not fixturedefs:
- continue
- # last item is expected to be the one used by the test item
- write_fixture(fixturedefs[-1])
-
- for session_item in session.items:
- write_item(session_item)
-
-
-def showfixtures(config):
- from _pytest.main import wrap_session
-
- return wrap_session(config, _showfixtures_main)
-
-
-def _showfixtures_main(config, session):
- import _pytest.config
-
- session.perform_collect()
- curdir = py.path.local()
- tw = _pytest.config.create_terminal_writer(config)
- verbose = config.getvalue("verbose")
-
- fm = session._fixturemanager
-
- available = []
- seen = set()
-
- for argname, fixturedefs in fm._arg2fixturedefs.items():
- assert fixturedefs is not None
- if not fixturedefs:
- continue
- for fixturedef in fixturedefs:
- loc = getlocation(fixturedef.func, curdir)
- if (fixturedef.argname, loc) in seen:
- continue
- seen.add((fixturedef.argname, loc))
- available.append(
- (
- len(fixturedef.baseid),
- fixturedef.func.__module__,
- curdir.bestrelpath(loc),
- fixturedef.argname,
- fixturedef,
- )
- )
-
- available.sort()
- currentmodule = None
- for baseid, module, bestrel, argname, fixturedef in available:
- if currentmodule != module:
- if not module.startswith("_pytest."):
- tw.line()
- tw.sep("-", "fixtures defined from %s" % (module,))
- currentmodule = module
- if verbose <= 0 and argname[0] == "_":
- continue
+
+
+def idmaker(argnames, parametersets, idfn=None, ids=None, config=None, item=None):
+ ids = [
+ _idvalset(valindex, parameterset, argnames, idfn, ids, config=config, item=item)
+ for valindex, parameterset in enumerate(parametersets)
+ ]
+ if len(set(ids)) != len(ids):
+ # The ids are not unique
+ duplicates = [testid for testid in ids if ids.count(testid) > 1]
+ counters = collections.defaultdict(lambda: 0)
+ for index, testid in enumerate(ids):
+ if testid in duplicates:
+ ids[index] = testid + str(counters[testid])
+ counters[testid] += 1
+ return ids
+
+
+def show_fixtures_per_test(config):
+ from _pytest.main import wrap_session
+
+ return wrap_session(config, _show_fixtures_per_test)
+
+
+def _show_fixtures_per_test(config, session):
+ import _pytest.config
+
+ session.perform_collect()
+ curdir = py.path.local()
+ tw = _pytest.config.create_terminal_writer(config)
+ verbose = config.getvalue("verbose")
+
+ def get_best_relpath(func):
+ loc = getlocation(func, curdir)
+ return curdir.bestrelpath(loc)
+
+ def write_fixture(fixture_def):
+ argname = fixture_def.argname
+ if verbose <= 0 and argname.startswith("_"):
+ return
+ if verbose > 0:
+ bestrel = get_best_relpath(fixture_def.func)
+ funcargspec = "{} -- {}".format(argname, bestrel)
+ else:
+ funcargspec = argname
+ tw.line(funcargspec, green=True)
+ fixture_doc = fixture_def.func.__doc__
+ if fixture_doc:
+ write_docstring(tw, fixture_doc)
+ else:
+ tw.line(" no docstring available", red=True)
+
+ def write_item(item):
+ try:
+ info = item._fixtureinfo
+ except AttributeError:
+ # doctests items have no _fixtureinfo attribute
+ return
+ if not info.name2fixturedefs:
+ # this test item does not use any fixtures
+ return
+ tw.line()
+ tw.sep("-", "fixtures used by {}".format(item.name))
+ tw.sep("-", "({})".format(get_best_relpath(item.function)))
+ # dict key not used in loop but needed for sorting
+ for _, fixturedefs in sorted(info.name2fixturedefs.items()):
+ assert fixturedefs is not None
+ if not fixturedefs:
+ continue
+ # last item is expected to be the one used by the test item
+ write_fixture(fixturedefs[-1])
+
+ for session_item in session.items:
+ write_item(session_item)
+
+
+def showfixtures(config):
+ from _pytest.main import wrap_session
+
+ return wrap_session(config, _showfixtures_main)
+
+
+def _showfixtures_main(config, session):
+ import _pytest.config
+
+ session.perform_collect()
+ curdir = py.path.local()
+ tw = _pytest.config.create_terminal_writer(config)
+ verbose = config.getvalue("verbose")
+
+ fm = session._fixturemanager
+
+ available = []
+ seen = set()
+
+ for argname, fixturedefs in fm._arg2fixturedefs.items():
+ assert fixturedefs is not None
+ if not fixturedefs:
+ continue
+ for fixturedef in fixturedefs:
+ loc = getlocation(fixturedef.func, curdir)
+ if (fixturedef.argname, loc) in seen:
+ continue
+ seen.add((fixturedef.argname, loc))
+ available.append(
+ (
+ len(fixturedef.baseid),
+ fixturedef.func.__module__,
+ curdir.bestrelpath(loc),
+ fixturedef.argname,
+ fixturedef,
+ )
+ )
+
+ available.sort()
+ currentmodule = None
+ for baseid, module, bestrel, argname, fixturedef in available:
+ if currentmodule != module:
+ if not module.startswith("_pytest."):
+ tw.line()
+ tw.sep("-", "fixtures defined from %s" % (module,))
+ currentmodule = module
+ if verbose <= 0 and argname[0] == "_":
+ continue
tw.write(argname, green=True)
if fixturedef.scope != "function":
tw.write(" [%s scope]" % fixturedef.scope, cyan=True)
- if verbose > 0:
+ if verbose > 0:
tw.write(" -- %s" % bestrel, yellow=True)
tw.write("\n")
- loc = getlocation(fixturedef.func, curdir)
- doc = fixturedef.func.__doc__ or ""
- if doc:
- write_docstring(tw, doc)
- else:
- tw.line(" %s: no docstring available" % (loc,), red=True)
+ loc = getlocation(fixturedef.func, curdir)
+ doc = fixturedef.func.__doc__ or ""
+ if doc:
+ write_docstring(tw, doc)
+ else:
+ tw.line(" %s: no docstring available" % (loc,), red=True)
tw.line()
-
-
+
+
def write_docstring(tw, doc, indent=" "):
- doc = doc.rstrip()
- if "\n" in doc:
- firstline, rest = doc.split("\n", 1)
- else:
- firstline, rest = doc, ""
-
- if firstline.strip():
+ doc = doc.rstrip()
+ if "\n" in doc:
+ firstline, rest = doc.split("\n", 1)
+ else:
+ firstline, rest = doc, ""
+
+ if firstline.strip():
tw.line(indent + firstline.strip())
-
- if rest:
- for line in dedent(rest).split("\n"):
+
+ if rest:
+ for line in dedent(rest).split("\n"):
tw.write(indent + line + "\n")
-
-
-class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
- """ a Function Item is responsible for setting up and executing a
- Python test function.
- """
-
- # disable since functions handle it themselves
- _ALLOW_MARKERS = False
-
- def __init__(
- self,
- name,
- parent,
- args=None,
- config=None,
- callspec=None,
- callobj=NOTSET,
- keywords=None,
- session=None,
- fixtureinfo=None,
- originalname=None,
- ):
- super(Function, self).__init__(name, parent, config=config, session=session)
- self._args = args
- if callobj is not NOTSET:
- self.obj = callobj
-
- self.keywords.update(self.obj.__dict__)
- self.own_markers.extend(get_unpacked_marks(self.obj))
- if callspec:
- self.callspec = callspec
- # this is total hostile and a mess
- # keywords are broken by design by now
- # this will be redeemed later
- for mark in callspec.marks:
- # feel free to cry, this was broken for years before
- # and keywords cant fix it per design
- self.keywords[mark.name] = mark
- self.own_markers.extend(normalize_mark_list(callspec.marks))
- if keywords:
- self.keywords.update(keywords)
-
+
+
+class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
+ """ a Function Item is responsible for setting up and executing a
+ Python test function.
+ """
+
+ # disable since functions handle it themselves
+ _ALLOW_MARKERS = False
+
+ def __init__(
+ self,
+ name,
+ parent,
+ args=None,
+ config=None,
+ callspec=None,
+ callobj=NOTSET,
+ keywords=None,
+ session=None,
+ fixtureinfo=None,
+ originalname=None,
+ ):
+ super(Function, self).__init__(name, parent, config=config, session=session)
+ self._args = args
+ if callobj is not NOTSET:
+ self.obj = callobj
+
+ self.keywords.update(self.obj.__dict__)
+ self.own_markers.extend(get_unpacked_marks(self.obj))
+ if callspec:
+ self.callspec = callspec
+ # this is total hostile and a mess
+ # keywords are broken by design by now
+ # this will be redeemed later
+ for mark in callspec.marks:
+ # feel free to cry, this was broken for years before
+ # and keywords cant fix it per design
+ self.keywords[mark.name] = mark
+ self.own_markers.extend(normalize_mark_list(callspec.marks))
+ if keywords:
+ self.keywords.update(keywords)
+
# todo: this is a hell of a hack
# https://github.com/pytest-dev/pytest/issues/4569
@@ -1451,57 +1451,57 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
)
)
- if fixtureinfo is None:
- fixtureinfo = self.session._fixturemanager.getfixtureinfo(
+ if fixtureinfo is None:
+ fixtureinfo = self.session._fixturemanager.getfixtureinfo(
self, self.obj, self.cls, funcargs=True
- )
- self._fixtureinfo = fixtureinfo
- self.fixturenames = fixtureinfo.names_closure
- self._initrequest()
-
- #: original function name, without any decorations (for example
- #: parametrization adds a ``"[...]"`` suffix to function names).
- #:
- #: .. versionadded:: 3.0
- self.originalname = originalname
-
- def _initrequest(self):
- self.funcargs = {}
- self._request = fixtures.FixtureRequest(self)
-
- @property
- def function(self):
- "underlying python 'function' object"
- return getimfunc(self.obj)
-
- def _getobj(self):
- name = self.name
- i = name.find("[") # parametrization
- if i != -1:
- name = name[:i]
- return getattr(self.parent.obj, name)
-
- @property
- def _pyfuncitem(self):
- "(compatonly) for code expecting pytest-2.2 style request objects"
- return self
-
- def runtest(self):
- """ execute the underlying test function. """
- self.ihook.pytest_pyfunc_call(pyfuncitem=self)
-
- def setup(self):
- super(Function, self).setup()
- fixtures.fillfixtures(self)
-
-
-class FunctionDefinition(Function):
- """
- internal hack until we get actual definition nodes instead of the
- crappy metafunc hack
- """
-
- def runtest(self):
- raise RuntimeError("function definitions are not supposed to be used")
-
- setup = runtest
+ )
+ self._fixtureinfo = fixtureinfo
+ self.fixturenames = fixtureinfo.names_closure
+ self._initrequest()
+
+ #: original function name, without any decorations (for example
+ #: parametrization adds a ``"[...]"`` suffix to function names).
+ #:
+ #: .. versionadded:: 3.0
+ self.originalname = originalname
+
+ def _initrequest(self):
+ self.funcargs = {}
+ self._request = fixtures.FixtureRequest(self)
+
+ @property
+ def function(self):
+ "underlying python 'function' object"
+ return getimfunc(self.obj)
+
+ def _getobj(self):
+ name = self.name
+ i = name.find("[") # parametrization
+ if i != -1:
+ name = name[:i]
+ return getattr(self.parent.obj, name)
+
+ @property
+ def _pyfuncitem(self):
+ "(compatonly) for code expecting pytest-2.2 style request objects"
+ return self
+
+ def runtest(self):
+ """ execute the underlying test function. """
+ self.ihook.pytest_pyfunc_call(pyfuncitem=self)
+
+ def setup(self):
+ super(Function, self).setup()
+ fixtures.fillfixtures(self)
+
+
+class FunctionDefinition(Function):
+ """
+ internal hack until we get actual definition nodes instead of the
+ crappy metafunc hack
+ """
+
+ def runtest(self):
+ raise RuntimeError("function definitions are not supposed to be used")
+
+ setup = runtest
diff --git a/contrib/python/pytest/py2/_pytest/python_api.py b/contrib/python/pytest/py2/_pytest/python_api.py
index 66253dad15..f6e475c3a2 100644
--- a/contrib/python/pytest/py2/_pytest/python_api.py
+++ b/contrib/python/pytest/py2/_pytest/python_api.py
@@ -1,591 +1,591 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
-import math
-import pprint
-import sys
+import math
+import pprint
+import sys
import warnings
-from decimal import Decimal
-from numbers import Number
-
-from more_itertools.more import always_iterable
-from six.moves import filterfalse
-from six.moves import zip
-
-import _pytest._code
+from decimal import Decimal
+from numbers import Number
+
+from more_itertools.more import always_iterable
+from six.moves import filterfalse
+from six.moves import zip
+
+import _pytest._code
from _pytest import deprecated
-from _pytest.compat import isclass
+from _pytest.compat import isclass
from _pytest.compat import Iterable
-from _pytest.compat import Mapping
+from _pytest.compat import Mapping
from _pytest.compat import Sized
-from _pytest.compat import STRING_TYPES
-from _pytest.outcomes import fail
-
-BASE_TYPE = (type, STRING_TYPES)
-
-
-def _cmp_raises_type_error(self, other):
- """__cmp__ implementation which raises TypeError. Used
- by Approx base classes to implement only == and != and raise a
- TypeError for other comparisons.
-
- Needed in Python 2 only, Python 3 all it takes is not implementing the
- other operators at all.
- """
- __tracebackhide__ = True
- raise TypeError(
- "Comparison operators other than == and != not supported by approx objects"
- )
-
-
-def _non_numeric_type_error(value, at):
- at_str = " at {}".format(at) if at else ""
- return TypeError(
- "cannot make approximate comparisons to non-numeric values: {!r} {}".format(
- value, at_str
- )
- )
-
-
-# builtin pytest.approx helper
-
-
-class ApproxBase(object):
- """
- Provide shared utilities for making approximate comparisons between numbers
- or sequences of numbers.
- """
-
- # Tell numpy to use our `__eq__` operator instead of its.
- __array_ufunc__ = None
- __array_priority__ = 100
-
- def __init__(self, expected, rel=None, abs=None, nan_ok=False):
- __tracebackhide__ = True
- self.expected = expected
- self.abs = abs
- self.rel = rel
- self.nan_ok = nan_ok
- self._check_type()
-
- def __repr__(self):
- raise NotImplementedError
-
- def __eq__(self, actual):
- return all(
- a == self._approx_scalar(x) for a, x in self._yield_comparisons(actual)
- )
-
- __hash__ = None
-
- def __ne__(self, actual):
- return not (actual == self)
-
- if sys.version_info[0] == 2:
- __cmp__ = _cmp_raises_type_error
-
- def _approx_scalar(self, x):
- return ApproxScalar(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok)
-
- def _yield_comparisons(self, actual):
- """
- Yield all the pairs of numbers to be compared. This is used to
- implement the `__eq__` method.
- """
- raise NotImplementedError
-
- def _check_type(self):
- """
- Raise a TypeError if the expected value is not a valid type.
- """
- # This is only a concern if the expected value is a sequence. In every
- # other case, the approx() function ensures that the expected value has
- # a numeric type. For this reason, the default is to do nothing. The
- # classes that deal with sequences should reimplement this method to
- # raise if there are any non-numeric elements in the sequence.
- pass
-
-
-def _recursive_list_map(f, x):
- if isinstance(x, list):
- return list(_recursive_list_map(f, xi) for xi in x)
- else:
- return f(x)
-
-
-class ApproxNumpy(ApproxBase):
- """
- Perform approximate comparisons where the expected value is numpy array.
- """
-
- def __repr__(self):
- list_scalars = _recursive_list_map(self._approx_scalar, self.expected.tolist())
- return "approx({!r})".format(list_scalars)
-
- if sys.version_info[0] == 2:
- __cmp__ = _cmp_raises_type_error
-
- def __eq__(self, actual):
- import numpy as np
-
- # self.expected is supposed to always be an array here
-
- if not np.isscalar(actual):
- try:
- actual = np.asarray(actual)
- except: # noqa
- raise TypeError("cannot compare '{}' to numpy.ndarray".format(actual))
-
- if not np.isscalar(actual) and actual.shape != self.expected.shape:
- return False
-
- return ApproxBase.__eq__(self, actual)
-
- def _yield_comparisons(self, actual):
- import numpy as np
-
- # `actual` can either be a numpy array or a scalar, it is treated in
- # `__eq__` before being passed to `ApproxBase.__eq__`, which is the
- # only method that calls this one.
-
- if np.isscalar(actual):
- for i in np.ndindex(self.expected.shape):
+from _pytest.compat import STRING_TYPES
+from _pytest.outcomes import fail
+
+BASE_TYPE = (type, STRING_TYPES)
+
+
+def _cmp_raises_type_error(self, other):
+ """__cmp__ implementation which raises TypeError. Used
+ by Approx base classes to implement only == and != and raise a
+ TypeError for other comparisons.
+
+ Needed in Python 2 only, Python 3 all it takes is not implementing the
+ other operators at all.
+ """
+ __tracebackhide__ = True
+ raise TypeError(
+ "Comparison operators other than == and != not supported by approx objects"
+ )
+
+
+def _non_numeric_type_error(value, at):
+ at_str = " at {}".format(at) if at else ""
+ return TypeError(
+ "cannot make approximate comparisons to non-numeric values: {!r} {}".format(
+ value, at_str
+ )
+ )
+
+
+# builtin pytest.approx helper
+
+
+class ApproxBase(object):
+ """
+ Provide shared utilities for making approximate comparisons between numbers
+ or sequences of numbers.
+ """
+
+ # Tell numpy to use our `__eq__` operator instead of its.
+ __array_ufunc__ = None
+ __array_priority__ = 100
+
+ def __init__(self, expected, rel=None, abs=None, nan_ok=False):
+ __tracebackhide__ = True
+ self.expected = expected
+ self.abs = abs
+ self.rel = rel
+ self.nan_ok = nan_ok
+ self._check_type()
+
+ def __repr__(self):
+ raise NotImplementedError
+
+ def __eq__(self, actual):
+ return all(
+ a == self._approx_scalar(x) for a, x in self._yield_comparisons(actual)
+ )
+
+ __hash__ = None
+
+ def __ne__(self, actual):
+ return not (actual == self)
+
+ if sys.version_info[0] == 2:
+ __cmp__ = _cmp_raises_type_error
+
+ def _approx_scalar(self, x):
+ return ApproxScalar(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok)
+
+ def _yield_comparisons(self, actual):
+ """
+ Yield all the pairs of numbers to be compared. This is used to
+ implement the `__eq__` method.
+ """
+ raise NotImplementedError
+
+ def _check_type(self):
+ """
+ Raise a TypeError if the expected value is not a valid type.
+ """
+ # This is only a concern if the expected value is a sequence. In every
+ # other case, the approx() function ensures that the expected value has
+ # a numeric type. For this reason, the default is to do nothing. The
+ # classes that deal with sequences should reimplement this method to
+ # raise if there are any non-numeric elements in the sequence.
+ pass
+
+
+def _recursive_list_map(f, x):
+ if isinstance(x, list):
+ return list(_recursive_list_map(f, xi) for xi in x)
+ else:
+ return f(x)
+
+
+class ApproxNumpy(ApproxBase):
+ """
+ Perform approximate comparisons where the expected value is numpy array.
+ """
+
+ def __repr__(self):
+ list_scalars = _recursive_list_map(self._approx_scalar, self.expected.tolist())
+ return "approx({!r})".format(list_scalars)
+
+ if sys.version_info[0] == 2:
+ __cmp__ = _cmp_raises_type_error
+
+ def __eq__(self, actual):
+ import numpy as np
+
+ # self.expected is supposed to always be an array here
+
+ if not np.isscalar(actual):
+ try:
+ actual = np.asarray(actual)
+ except: # noqa
+ raise TypeError("cannot compare '{}' to numpy.ndarray".format(actual))
+
+ if not np.isscalar(actual) and actual.shape != self.expected.shape:
+ return False
+
+ return ApproxBase.__eq__(self, actual)
+
+ def _yield_comparisons(self, actual):
+ import numpy as np
+
+ # `actual` can either be a numpy array or a scalar, it is treated in
+ # `__eq__` before being passed to `ApproxBase.__eq__`, which is the
+ # only method that calls this one.
+
+ if np.isscalar(actual):
+ for i in np.ndindex(self.expected.shape):
yield actual, self.expected[i].item()
- else:
- for i in np.ndindex(self.expected.shape):
+ else:
+ for i in np.ndindex(self.expected.shape):
yield actual[i].item(), self.expected[i].item()
-
-
-class ApproxMapping(ApproxBase):
- """
- Perform approximate comparisons where the expected value is a mapping with
- numeric values (the keys can be anything).
- """
-
- def __repr__(self):
- return "approx({!r})".format(
- {k: self._approx_scalar(v) for k, v in self.expected.items()}
- )
-
- def __eq__(self, actual):
- if set(actual.keys()) != set(self.expected.keys()):
- return False
-
- return ApproxBase.__eq__(self, actual)
-
- def _yield_comparisons(self, actual):
- for k in self.expected.keys():
- yield actual[k], self.expected[k]
-
- def _check_type(self):
- __tracebackhide__ = True
- for key, value in self.expected.items():
- if isinstance(value, type(self.expected)):
- msg = "pytest.approx() does not support nested dictionaries: key={!r} value={!r}\n full mapping={}"
- raise TypeError(msg.format(key, value, pprint.pformat(self.expected)))
- elif not isinstance(value, Number):
- raise _non_numeric_type_error(self.expected, at="key={!r}".format(key))
-
-
+
+
+class ApproxMapping(ApproxBase):
+ """
+ Perform approximate comparisons where the expected value is a mapping with
+ numeric values (the keys can be anything).
+ """
+
+ def __repr__(self):
+ return "approx({!r})".format(
+ {k: self._approx_scalar(v) for k, v in self.expected.items()}
+ )
+
+ def __eq__(self, actual):
+ if set(actual.keys()) != set(self.expected.keys()):
+ return False
+
+ return ApproxBase.__eq__(self, actual)
+
+ def _yield_comparisons(self, actual):
+ for k in self.expected.keys():
+ yield actual[k], self.expected[k]
+
+ def _check_type(self):
+ __tracebackhide__ = True
+ for key, value in self.expected.items():
+ if isinstance(value, type(self.expected)):
+ msg = "pytest.approx() does not support nested dictionaries: key={!r} value={!r}\n full mapping={}"
+ raise TypeError(msg.format(key, value, pprint.pformat(self.expected)))
+ elif not isinstance(value, Number):
+ raise _non_numeric_type_error(self.expected, at="key={!r}".format(key))
+
+
class ApproxSequencelike(ApproxBase):
- """
- Perform approximate comparisons where the expected value is a sequence of
- numbers.
- """
-
- def __repr__(self):
- seq_type = type(self.expected)
- if seq_type not in (tuple, list, set):
- seq_type = list
- return "approx({!r})".format(
- seq_type(self._approx_scalar(x) for x in self.expected)
- )
-
- def __eq__(self, actual):
- if len(actual) != len(self.expected):
- return False
- return ApproxBase.__eq__(self, actual)
-
- def _yield_comparisons(self, actual):
- return zip(actual, self.expected)
-
- def _check_type(self):
- __tracebackhide__ = True
- for index, x in enumerate(self.expected):
- if isinstance(x, type(self.expected)):
- msg = "pytest.approx() does not support nested data structures: {!r} at index {}\n full sequence: {}"
- raise TypeError(msg.format(x, index, pprint.pformat(self.expected)))
- elif not isinstance(x, Number):
- raise _non_numeric_type_error(
- self.expected, at="index {}".format(index)
- )
-
-
-class ApproxScalar(ApproxBase):
- """
- Perform approximate comparisons where the expected value is a single number.
- """
-
- DEFAULT_ABSOLUTE_TOLERANCE = 1e-12
- DEFAULT_RELATIVE_TOLERANCE = 1e-6
-
- def __repr__(self):
- """
- Return a string communicating both the expected value and the tolerance
- for the comparison being made, e.g. '1.0 +- 1e-6'. Use the unicode
- plus/minus symbol if this is python3 (it's too hard to get right for
- python2).
- """
- if isinstance(self.expected, complex):
- return str(self.expected)
-
- # Infinities aren't compared using tolerances, so don't show a
- # tolerance.
- if math.isinf(self.expected):
- return str(self.expected)
-
- # If a sensible tolerance can't be calculated, self.tolerance will
- # raise a ValueError. In this case, display '???'.
- try:
- vetted_tolerance = "{:.1e}".format(self.tolerance)
- except ValueError:
- vetted_tolerance = "???"
-
- if sys.version_info[0] == 2:
- return "{} +- {}".format(self.expected, vetted_tolerance)
- else:
- return u"{} \u00b1 {}".format(self.expected, vetted_tolerance)
-
- def __eq__(self, actual):
- """
- Return true if the given value is equal to the expected value within
- the pre-specified tolerance.
- """
- if _is_numpy_array(actual):
- # Call ``__eq__()`` manually to prevent infinite-recursion with
- # numpy<1.13. See #3748.
- return all(self.__eq__(a) for a in actual.flat)
-
- # Short-circuit exact equality.
- if actual == self.expected:
- return True
-
- # Allow the user to control whether NaNs are considered equal to each
- # other or not. The abs() calls are for compatibility with complex
- # numbers.
- if math.isnan(abs(self.expected)):
- return self.nan_ok and math.isnan(abs(actual))
-
- # Infinity shouldn't be approximately equal to anything but itself, but
- # if there's a relative tolerance, it will be infinite and infinity
- # will seem approximately equal to everything. The equal-to-itself
- # case would have been short circuited above, so here we can just
- # return false if the expected value is infinite. The abs() call is
- # for compatibility with complex numbers.
- if math.isinf(abs(self.expected)):
- return False
-
- # Return true if the two numbers are within the tolerance.
- return abs(self.expected - actual) <= self.tolerance
-
- __hash__ = None
-
- @property
- def tolerance(self):
- """
- Return the tolerance for the comparison. This could be either an
- absolute tolerance or a relative tolerance, depending on what the user
- specified or which would be larger.
- """
-
- def set_default(x, default):
- return x if x is not None else default
-
- # Figure out what the absolute tolerance should be. ``self.abs`` is
- # either None or a value specified by the user.
- absolute_tolerance = set_default(self.abs, self.DEFAULT_ABSOLUTE_TOLERANCE)
-
- if absolute_tolerance < 0:
- raise ValueError(
- "absolute tolerance can't be negative: {}".format(absolute_tolerance)
- )
- if math.isnan(absolute_tolerance):
- raise ValueError("absolute tolerance can't be NaN.")
-
- # If the user specified an absolute tolerance but not a relative one,
- # just return the absolute tolerance.
- if self.rel is None:
- if self.abs is not None:
- return absolute_tolerance
-
- # Figure out what the relative tolerance should be. ``self.rel`` is
- # either None or a value specified by the user. This is done after
- # we've made sure the user didn't ask for an absolute tolerance only,
- # because we don't want to raise errors about the relative tolerance if
- # we aren't even going to use it.
- relative_tolerance = set_default(
- self.rel, self.DEFAULT_RELATIVE_TOLERANCE
- ) * abs(self.expected)
-
- if relative_tolerance < 0:
- raise ValueError(
- "relative tolerance can't be negative: {}".format(absolute_tolerance)
- )
- if math.isnan(relative_tolerance):
- raise ValueError("relative tolerance can't be NaN.")
-
- # Return the larger of the relative and absolute tolerances.
- return max(relative_tolerance, absolute_tolerance)
-
-
-class ApproxDecimal(ApproxScalar):
- """
- Perform approximate comparisons where the expected value is a decimal.
- """
-
- DEFAULT_ABSOLUTE_TOLERANCE = Decimal("1e-12")
- DEFAULT_RELATIVE_TOLERANCE = Decimal("1e-6")
-
-
-def approx(expected, rel=None, abs=None, nan_ok=False):
- """
- Assert that two numbers (or two sets of numbers) are equal to each other
- within some tolerance.
-
- Due to the `intricacies of floating-point arithmetic`__, numbers that we
- would intuitively expect to be equal are not always so::
-
- >>> 0.1 + 0.2 == 0.3
- False
-
- __ https://docs.python.org/3/tutorial/floatingpoint.html
-
- This problem is commonly encountered when writing tests, e.g. when making
- sure that floating-point values are what you expect them to be. One way to
- deal with this problem is to assert that two floating-point numbers are
- equal to within some appropriate tolerance::
-
- >>> abs((0.1 + 0.2) - 0.3) < 1e-6
- True
-
- However, comparisons like this are tedious to write and difficult to
- understand. Furthermore, absolute comparisons like the one above are
- usually discouraged because there's no tolerance that works well for all
- situations. ``1e-6`` is good for numbers around ``1``, but too small for
- very big numbers and too big for very small ones. It's better to express
- the tolerance as a fraction of the expected value, but relative comparisons
- like that are even more difficult to write correctly and concisely.
-
- The ``approx`` class performs floating-point comparisons using a syntax
- that's as intuitive as possible::
-
- >>> from pytest import approx
- >>> 0.1 + 0.2 == approx(0.3)
- True
-
- The same syntax also works for sequences of numbers::
-
- >>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6))
- True
-
- Dictionary *values*::
-
- >>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
- True
-
- ``numpy`` arrays::
-
- >>> import numpy as np # doctest: +SKIP
- >>> np.array([0.1, 0.2]) + np.array([0.2, 0.4]) == approx(np.array([0.3, 0.6])) # doctest: +SKIP
- True
-
- And for a ``numpy`` array against a scalar::
-
- >>> import numpy as np # doctest: +SKIP
- >>> np.array([0.1, 0.2]) + np.array([0.2, 0.1]) == approx(0.3) # doctest: +SKIP
- True
-
- By default, ``approx`` considers numbers within a relative tolerance of
- ``1e-6`` (i.e. one part in a million) of its expected value to be equal.
- This treatment would lead to surprising results if the expected value was
- ``0.0``, because nothing but ``0.0`` itself is relatively close to ``0.0``.
- To handle this case less surprisingly, ``approx`` also considers numbers
- within an absolute tolerance of ``1e-12`` of its expected value to be
- equal. Infinity and NaN are special cases. Infinity is only considered
- equal to itself, regardless of the relative tolerance. NaN is not
- considered equal to anything by default, but you can make it be equal to
- itself by setting the ``nan_ok`` argument to True. (This is meant to
- facilitate comparing arrays that use NaN to mean "no data".)
-
- Both the relative and absolute tolerances can be changed by passing
- arguments to the ``approx`` constructor::
-
- >>> 1.0001 == approx(1)
- False
- >>> 1.0001 == approx(1, rel=1e-3)
- True
- >>> 1.0001 == approx(1, abs=1e-3)
- True
-
- If you specify ``abs`` but not ``rel``, the comparison will not consider
- the relative tolerance at all. In other words, two numbers that are within
- the default relative tolerance of ``1e-6`` will still be considered unequal
- if they exceed the specified absolute tolerance. If you specify both
- ``abs`` and ``rel``, the numbers will be considered equal if either
- tolerance is met::
-
- >>> 1 + 1e-8 == approx(1)
- True
- >>> 1 + 1e-8 == approx(1, abs=1e-12)
- False
- >>> 1 + 1e-8 == approx(1, rel=1e-6, abs=1e-12)
- True
-
- If you're thinking about using ``approx``, then you might want to know how
- it compares to other good ways of comparing floating-point numbers. All of
- these algorithms are based on relative and absolute tolerances and should
- agree for the most part, but they do have meaningful differences:
-
- - ``math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)``: True if the relative
- tolerance is met w.r.t. either ``a`` or ``b`` or if the absolute
- tolerance is met. Because the relative tolerance is calculated w.r.t.
- both ``a`` and ``b``, this test is symmetric (i.e. neither ``a`` nor
- ``b`` is a "reference value"). You have to specify an absolute tolerance
- if you want to compare to ``0.0`` because there is no tolerance by
- default. Only available in python>=3.5. `More information...`__
-
- __ https://docs.python.org/3/library/math.html#math.isclose
-
- - ``numpy.isclose(a, b, rtol=1e-5, atol=1e-8)``: True if the difference
- between ``a`` and ``b`` is less that the sum of the relative tolerance
- w.r.t. ``b`` and the absolute tolerance. Because the relative tolerance
- is only calculated w.r.t. ``b``, this test is asymmetric and you can
- think of ``b`` as the reference value. Support for comparing sequences
- is provided by ``numpy.allclose``. `More information...`__
-
- __ http://docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.isclose.html
-
- - ``unittest.TestCase.assertAlmostEqual(a, b)``: True if ``a`` and ``b``
- are within an absolute tolerance of ``1e-7``. No relative tolerance is
- considered and the absolute tolerance cannot be changed, so this function
- is not appropriate for very large or very small numbers. Also, it's only
- available in subclasses of ``unittest.TestCase`` and it's ugly because it
- doesn't follow PEP8. `More information...`__
-
+ """
+ Perform approximate comparisons where the expected value is a sequence of
+ numbers.
+ """
+
+ def __repr__(self):
+ seq_type = type(self.expected)
+ if seq_type not in (tuple, list, set):
+ seq_type = list
+ return "approx({!r})".format(
+ seq_type(self._approx_scalar(x) for x in self.expected)
+ )
+
+ def __eq__(self, actual):
+ if len(actual) != len(self.expected):
+ return False
+ return ApproxBase.__eq__(self, actual)
+
+ def _yield_comparisons(self, actual):
+ return zip(actual, self.expected)
+
+ def _check_type(self):
+ __tracebackhide__ = True
+ for index, x in enumerate(self.expected):
+ if isinstance(x, type(self.expected)):
+ msg = "pytest.approx() does not support nested data structures: {!r} at index {}\n full sequence: {}"
+ raise TypeError(msg.format(x, index, pprint.pformat(self.expected)))
+ elif not isinstance(x, Number):
+ raise _non_numeric_type_error(
+ self.expected, at="index {}".format(index)
+ )
+
+
+class ApproxScalar(ApproxBase):
+ """
+ Perform approximate comparisons where the expected value is a single number.
+ """
+
+ DEFAULT_ABSOLUTE_TOLERANCE = 1e-12
+ DEFAULT_RELATIVE_TOLERANCE = 1e-6
+
+ def __repr__(self):
+ """
+ Return a string communicating both the expected value and the tolerance
+ for the comparison being made, e.g. '1.0 +- 1e-6'. Use the unicode
+ plus/minus symbol if this is python3 (it's too hard to get right for
+ python2).
+ """
+ if isinstance(self.expected, complex):
+ return str(self.expected)
+
+ # Infinities aren't compared using tolerances, so don't show a
+ # tolerance.
+ if math.isinf(self.expected):
+ return str(self.expected)
+
+ # If a sensible tolerance can't be calculated, self.tolerance will
+ # raise a ValueError. In this case, display '???'.
+ try:
+ vetted_tolerance = "{:.1e}".format(self.tolerance)
+ except ValueError:
+ vetted_tolerance = "???"
+
+ if sys.version_info[0] == 2:
+ return "{} +- {}".format(self.expected, vetted_tolerance)
+ else:
+ return u"{} \u00b1 {}".format(self.expected, vetted_tolerance)
+
+ def __eq__(self, actual):
+ """
+ Return true if the given value is equal to the expected value within
+ the pre-specified tolerance.
+ """
+ if _is_numpy_array(actual):
+ # Call ``__eq__()`` manually to prevent infinite-recursion with
+ # numpy<1.13. See #3748.
+ return all(self.__eq__(a) for a in actual.flat)
+
+ # Short-circuit exact equality.
+ if actual == self.expected:
+ return True
+
+ # Allow the user to control whether NaNs are considered equal to each
+ # other or not. The abs() calls are for compatibility with complex
+ # numbers.
+ if math.isnan(abs(self.expected)):
+ return self.nan_ok and math.isnan(abs(actual))
+
+ # Infinity shouldn't be approximately equal to anything but itself, but
+ # if there's a relative tolerance, it will be infinite and infinity
+ # will seem approximately equal to everything. The equal-to-itself
+ # case would have been short circuited above, so here we can just
+ # return false if the expected value is infinite. The abs() call is
+ # for compatibility with complex numbers.
+ if math.isinf(abs(self.expected)):
+ return False
+
+ # Return true if the two numbers are within the tolerance.
+ return abs(self.expected - actual) <= self.tolerance
+
+ __hash__ = None
+
+ @property
+ def tolerance(self):
+ """
+ Return the tolerance for the comparison. This could be either an
+ absolute tolerance or a relative tolerance, depending on what the user
+ specified or which would be larger.
+ """
+
+ def set_default(x, default):
+ return x if x is not None else default
+
+ # Figure out what the absolute tolerance should be. ``self.abs`` is
+ # either None or a value specified by the user.
+ absolute_tolerance = set_default(self.abs, self.DEFAULT_ABSOLUTE_TOLERANCE)
+
+ if absolute_tolerance < 0:
+ raise ValueError(
+ "absolute tolerance can't be negative: {}".format(absolute_tolerance)
+ )
+ if math.isnan(absolute_tolerance):
+ raise ValueError("absolute tolerance can't be NaN.")
+
+ # If the user specified an absolute tolerance but not a relative one,
+ # just return the absolute tolerance.
+ if self.rel is None:
+ if self.abs is not None:
+ return absolute_tolerance
+
+ # Figure out what the relative tolerance should be. ``self.rel`` is
+ # either None or a value specified by the user. This is done after
+ # we've made sure the user didn't ask for an absolute tolerance only,
+ # because we don't want to raise errors about the relative tolerance if
+ # we aren't even going to use it.
+ relative_tolerance = set_default(
+ self.rel, self.DEFAULT_RELATIVE_TOLERANCE
+ ) * abs(self.expected)
+
+ if relative_tolerance < 0:
+ raise ValueError(
+ "relative tolerance can't be negative: {}".format(absolute_tolerance)
+ )
+ if math.isnan(relative_tolerance):
+ raise ValueError("relative tolerance can't be NaN.")
+
+ # Return the larger of the relative and absolute tolerances.
+ return max(relative_tolerance, absolute_tolerance)
+
+
+class ApproxDecimal(ApproxScalar):
+ """
+ Perform approximate comparisons where the expected value is a decimal.
+ """
+
+ DEFAULT_ABSOLUTE_TOLERANCE = Decimal("1e-12")
+ DEFAULT_RELATIVE_TOLERANCE = Decimal("1e-6")
+
+
+def approx(expected, rel=None, abs=None, nan_ok=False):
+ """
+ Assert that two numbers (or two sets of numbers) are equal to each other
+ within some tolerance.
+
+ Due to the `intricacies of floating-point arithmetic`__, numbers that we
+ would intuitively expect to be equal are not always so::
+
+ >>> 0.1 + 0.2 == 0.3
+ False
+
+ __ https://docs.python.org/3/tutorial/floatingpoint.html
+
+ This problem is commonly encountered when writing tests, e.g. when making
+ sure that floating-point values are what you expect them to be. One way to
+ deal with this problem is to assert that two floating-point numbers are
+ equal to within some appropriate tolerance::
+
+ >>> abs((0.1 + 0.2) - 0.3) < 1e-6
+ True
+
+ However, comparisons like this are tedious to write and difficult to
+ understand. Furthermore, absolute comparisons like the one above are
+ usually discouraged because there's no tolerance that works well for all
+ situations. ``1e-6`` is good for numbers around ``1``, but too small for
+ very big numbers and too big for very small ones. It's better to express
+ the tolerance as a fraction of the expected value, but relative comparisons
+ like that are even more difficult to write correctly and concisely.
+
+ The ``approx`` class performs floating-point comparisons using a syntax
+ that's as intuitive as possible::
+
+ >>> from pytest import approx
+ >>> 0.1 + 0.2 == approx(0.3)
+ True
+
+ The same syntax also works for sequences of numbers::
+
+ >>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6))
+ True
+
+ Dictionary *values*::
+
+ >>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
+ True
+
+ ``numpy`` arrays::
+
+ >>> import numpy as np # doctest: +SKIP
+ >>> np.array([0.1, 0.2]) + np.array([0.2, 0.4]) == approx(np.array([0.3, 0.6])) # doctest: +SKIP
+ True
+
+ And for a ``numpy`` array against a scalar::
+
+ >>> import numpy as np # doctest: +SKIP
+ >>> np.array([0.1, 0.2]) + np.array([0.2, 0.1]) == approx(0.3) # doctest: +SKIP
+ True
+
+ By default, ``approx`` considers numbers within a relative tolerance of
+ ``1e-6`` (i.e. one part in a million) of its expected value to be equal.
+ This treatment would lead to surprising results if the expected value was
+ ``0.0``, because nothing but ``0.0`` itself is relatively close to ``0.0``.
+ To handle this case less surprisingly, ``approx`` also considers numbers
+ within an absolute tolerance of ``1e-12`` of its expected value to be
+ equal. Infinity and NaN are special cases. Infinity is only considered
+ equal to itself, regardless of the relative tolerance. NaN is not
+ considered equal to anything by default, but you can make it be equal to
+ itself by setting the ``nan_ok`` argument to True. (This is meant to
+ facilitate comparing arrays that use NaN to mean "no data".)
+
+ Both the relative and absolute tolerances can be changed by passing
+ arguments to the ``approx`` constructor::
+
+ >>> 1.0001 == approx(1)
+ False
+ >>> 1.0001 == approx(1, rel=1e-3)
+ True
+ >>> 1.0001 == approx(1, abs=1e-3)
+ True
+
+ If you specify ``abs`` but not ``rel``, the comparison will not consider
+ the relative tolerance at all. In other words, two numbers that are within
+ the default relative tolerance of ``1e-6`` will still be considered unequal
+ if they exceed the specified absolute tolerance. If you specify both
+ ``abs`` and ``rel``, the numbers will be considered equal if either
+ tolerance is met::
+
+ >>> 1 + 1e-8 == approx(1)
+ True
+ >>> 1 + 1e-8 == approx(1, abs=1e-12)
+ False
+ >>> 1 + 1e-8 == approx(1, rel=1e-6, abs=1e-12)
+ True
+
+ If you're thinking about using ``approx``, then you might want to know how
+ it compares to other good ways of comparing floating-point numbers. All of
+ these algorithms are based on relative and absolute tolerances and should
+ agree for the most part, but they do have meaningful differences:
+
+ - ``math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)``: True if the relative
+ tolerance is met w.r.t. either ``a`` or ``b`` or if the absolute
+ tolerance is met. Because the relative tolerance is calculated w.r.t.
+ both ``a`` and ``b``, this test is symmetric (i.e. neither ``a`` nor
+ ``b`` is a "reference value"). You have to specify an absolute tolerance
+ if you want to compare to ``0.0`` because there is no tolerance by
+ default. Only available in python>=3.5. `More information...`__
+
+ __ https://docs.python.org/3/library/math.html#math.isclose
+
+ - ``numpy.isclose(a, b, rtol=1e-5, atol=1e-8)``: True if the difference
+ between ``a`` and ``b`` is less that the sum of the relative tolerance
+ w.r.t. ``b`` and the absolute tolerance. Because the relative tolerance
+ is only calculated w.r.t. ``b``, this test is asymmetric and you can
+ think of ``b`` as the reference value. Support for comparing sequences
+ is provided by ``numpy.allclose``. `More information...`__
+
+ __ http://docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.isclose.html
+
+ - ``unittest.TestCase.assertAlmostEqual(a, b)``: True if ``a`` and ``b``
+ are within an absolute tolerance of ``1e-7``. No relative tolerance is
+ considered and the absolute tolerance cannot be changed, so this function
+ is not appropriate for very large or very small numbers. Also, it's only
+ available in subclasses of ``unittest.TestCase`` and it's ugly because it
+ doesn't follow PEP8. `More information...`__
+
__ https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertAlmostEqual
-
- - ``a == pytest.approx(b, rel=1e-6, abs=1e-12)``: True if the relative
- tolerance is met w.r.t. ``b`` or if the absolute tolerance is met.
- Because the relative tolerance is only calculated w.r.t. ``b``, this test
- is asymmetric and you can think of ``b`` as the reference value. In the
- special case that you explicitly specify an absolute tolerance but not a
- relative tolerance, only the absolute tolerance is considered.
-
- .. warning::
-
- .. versionchanged:: 3.2
-
- In order to avoid inconsistent behavior, ``TypeError`` is
- raised for ``>``, ``>=``, ``<`` and ``<=`` comparisons.
- The example below illustrates the problem::
-
- assert approx(0.1) > 0.1 + 1e-10 # calls approx(0.1).__gt__(0.1 + 1e-10)
- assert 0.1 + 1e-10 > approx(0.1) # calls approx(0.1).__lt__(0.1 + 1e-10)
-
- In the second example one expects ``approx(0.1).__le__(0.1 + 1e-10)``
- to be called. But instead, ``approx(0.1).__lt__(0.1 + 1e-10)`` is used to
- comparison. This is because the call hierarchy of rich comparisons
- follows a fixed behavior. `More information...`__
-
- __ https://docs.python.org/3/reference/datamodel.html#object.__ge__
- """
-
- # Delegate the comparison to a class that knows how to deal with the type
- # of the expected value (e.g. int, float, list, dict, numpy.array, etc).
- #
- # The primary responsibility of these classes is to implement ``__eq__()``
- # and ``__repr__()``. The former is used to actually check if some
- # "actual" value is equivalent to the given expected value within the
- # allowed tolerance. The latter is used to show the user the expected
- # value and tolerance, in the case that a test failed.
- #
- # The actual logic for making approximate comparisons can be found in
- # ApproxScalar, which is used to compare individual numbers. All of the
- # other Approx classes eventually delegate to this class. The ApproxBase
- # class provides some convenient methods and overloads, but isn't really
- # essential.
-
- __tracebackhide__ = True
-
- if isinstance(expected, Decimal):
- cls = ApproxDecimal
- elif isinstance(expected, Number):
- cls = ApproxScalar
- elif isinstance(expected, Mapping):
- cls = ApproxMapping
- elif _is_numpy_array(expected):
- cls = ApproxNumpy
+
+ - ``a == pytest.approx(b, rel=1e-6, abs=1e-12)``: True if the relative
+ tolerance is met w.r.t. ``b`` or if the absolute tolerance is met.
+ Because the relative tolerance is only calculated w.r.t. ``b``, this test
+ is asymmetric and you can think of ``b`` as the reference value. In the
+ special case that you explicitly specify an absolute tolerance but not a
+ relative tolerance, only the absolute tolerance is considered.
+
+ .. warning::
+
+ .. versionchanged:: 3.2
+
+ In order to avoid inconsistent behavior, ``TypeError`` is
+ raised for ``>``, ``>=``, ``<`` and ``<=`` comparisons.
+ The example below illustrates the problem::
+
+ assert approx(0.1) > 0.1 + 1e-10 # calls approx(0.1).__gt__(0.1 + 1e-10)
+ assert 0.1 + 1e-10 > approx(0.1) # calls approx(0.1).__lt__(0.1 + 1e-10)
+
+ In the second example one expects ``approx(0.1).__le__(0.1 + 1e-10)``
+ to be called. But instead, ``approx(0.1).__lt__(0.1 + 1e-10)`` is used to
+ comparison. This is because the call hierarchy of rich comparisons
+ follows a fixed behavior. `More information...`__
+
+ __ https://docs.python.org/3/reference/datamodel.html#object.__ge__
+ """
+
+ # Delegate the comparison to a class that knows how to deal with the type
+ # of the expected value (e.g. int, float, list, dict, numpy.array, etc).
+ #
+ # The primary responsibility of these classes is to implement ``__eq__()``
+ # and ``__repr__()``. The former is used to actually check if some
+ # "actual" value is equivalent to the given expected value within the
+ # allowed tolerance. The latter is used to show the user the expected
+ # value and tolerance, in the case that a test failed.
+ #
+ # The actual logic for making approximate comparisons can be found in
+ # ApproxScalar, which is used to compare individual numbers. All of the
+ # other Approx classes eventually delegate to this class. The ApproxBase
+ # class provides some convenient methods and overloads, but isn't really
+ # essential.
+
+ __tracebackhide__ = True
+
+ if isinstance(expected, Decimal):
+ cls = ApproxDecimal
+ elif isinstance(expected, Number):
+ cls = ApproxScalar
+ elif isinstance(expected, Mapping):
+ cls = ApproxMapping
+ elif _is_numpy_array(expected):
+ cls = ApproxNumpy
elif (
isinstance(expected, Iterable)
and isinstance(expected, Sized)
and not isinstance(expected, STRING_TYPES)
):
cls = ApproxSequencelike
- else:
- raise _non_numeric_type_error(expected, at=None)
-
- return cls(expected, rel, abs, nan_ok)
-
-
-def _is_numpy_array(obj):
- """
- Return true if the given object is a numpy array. Make a special effort to
- avoid importing numpy unless it's really necessary.
- """
- import sys
-
- np = sys.modules.get("numpy")
- if np is not None:
- return isinstance(obj, np.ndarray)
- return False
-
-
-# builtin pytest.raises helper
-
-
-def raises(expected_exception, *args, **kwargs):
- r"""
- Assert that a code block/function call raises ``expected_exception``
+ else:
+ raise _non_numeric_type_error(expected, at=None)
+
+ return cls(expected, rel, abs, nan_ok)
+
+
+def _is_numpy_array(obj):
+ """
+ Return true if the given object is a numpy array. Make a special effort to
+ avoid importing numpy unless it's really necessary.
+ """
+ import sys
+
+ np = sys.modules.get("numpy")
+ if np is not None:
+ return isinstance(obj, np.ndarray)
+ return False
+
+
+# builtin pytest.raises helper
+
+
+def raises(expected_exception, *args, **kwargs):
+ r"""
+ Assert that a code block/function call raises ``expected_exception``
or raise a failure exception otherwise.
-
+
:kwparam match: if specified, a string containing a regular expression,
or a regular expression object, that is tested against the string
representation of the exception using ``re.search``. To match a literal
string that may contain `special characters`__, the pattern can
first be escaped with ``re.escape``.
-
+
__ https://docs.python.org/3/library/re.html#regular-expression-syntax
-
+
:kwparam message: **(deprecated since 4.1)** if specified, provides a custom failure message
if the exception is not raised. See :ref:`the deprecation docs <raises message deprecated>` for a workaround.
-
+
.. currentmodule:: _pytest._code
Use ``pytest.raises`` as a context manager, which will capture the exception of the given
type::
- >>> with raises(ZeroDivisionError):
- ... 1/0
-
+ >>> with raises(ZeroDivisionError):
+ ... 1/0
+
If the code block does not raise the expected exception (``ZeroDivisionError`` in the example
above), or no exception at all, the check will fail instead.
-
+
You can also use the keyword argument ``match`` to assert that the
exception matches a text or regex::
-
+
>>> with raises(ValueError, match='must be 0 or None'):
... raise ValueError("value must be 0 or None")
-
+
>>> with raises(ValueError, match=r'must be \d+$'):
... raise ValueError("value must be 42")
@@ -605,139 +605,139 @@ def raises(expected_exception, *args, **kwargs):
is considered error prone as users often mean to use ``match`` instead.
See :ref:`the deprecation docs <raises message deprecated>` for a workaround.
- .. note::
-
- When using ``pytest.raises`` as a context manager, it's worthwhile to
- note that normal context manager rules apply and that the exception
- raised *must* be the final line in the scope of the context manager.
- Lines of code after that, within the scope of the context manager will
- not be executed. For example::
-
- >>> value = 15
- >>> with raises(ValueError) as exc_info:
- ... if value > 10:
- ... raise ValueError("value must be <= 10")
+ .. note::
+
+ When using ``pytest.raises`` as a context manager, it's worthwhile to
+ note that normal context manager rules apply and that the exception
+ raised *must* be the final line in the scope of the context manager.
+ Lines of code after that, within the scope of the context manager will
+ not be executed. For example::
+
+ >>> value = 15
+ >>> with raises(ValueError) as exc_info:
+ ... if value > 10:
+ ... raise ValueError("value must be <= 10")
... assert exc_info.type is ValueError # this will not execute
-
- Instead, the following approach must be taken (note the difference in
- scope)::
-
- >>> with raises(ValueError) as exc_info:
- ... if value > 10:
- ... raise ValueError("value must be <= 10")
- ...
+
+ Instead, the following approach must be taken (note the difference in
+ scope)::
+
+ >>> with raises(ValueError) as exc_info:
+ ... if value > 10:
+ ... raise ValueError("value must be <= 10")
+ ...
>>> assert exc_info.type is ValueError
-
+
**Using with** ``pytest.mark.parametrize``
-
+
When using :ref:`pytest.mark.parametrize ref`
it is possible to parametrize tests such that
some runs raise an exception and others do not.
-
+
See :ref:`parametrizing_conditional_raising` for an example.
-
+
**Legacy form**
-
- It is possible to specify a callable by passing a to-be-called lambda::
-
- >>> raises(ZeroDivisionError, lambda: 1/0)
- <ExceptionInfo ...>
-
- or you can specify an arbitrary callable with arguments::
-
- >>> def f(x): return 1/x
- ...
- >>> raises(ZeroDivisionError, f, 0)
- <ExceptionInfo ...>
- >>> raises(ZeroDivisionError, f, x=0)
- <ExceptionInfo ...>
-
+
+ It is possible to specify a callable by passing a to-be-called lambda::
+
+ >>> raises(ZeroDivisionError, lambda: 1/0)
+ <ExceptionInfo ...>
+
+ or you can specify an arbitrary callable with arguments::
+
+ >>> def f(x): return 1/x
+ ...
+ >>> raises(ZeroDivisionError, f, 0)
+ <ExceptionInfo ...>
+ >>> raises(ZeroDivisionError, f, x=0)
+ <ExceptionInfo ...>
+
The form above is fully supported but discouraged for new code because the
context manager form is regarded as more readable and less error-prone.
-
- .. note::
- Similar to caught exception objects in Python, explicitly clearing
- local references to returned ``ExceptionInfo`` objects can
- help the Python interpreter speed up its garbage collection.
-
- Clearing those references breaks a reference cycle
- (``ExceptionInfo`` --> caught exception --> frame stack raising
- the exception --> current frame stack --> local variables -->
- ``ExceptionInfo``) which makes Python keep all objects referenced
- from that cycle (including all local variables in the current
- frame) alive until the next cyclic garbage collection run. See the
- official Python ``try`` statement documentation for more detailed
- information.
-
- """
- __tracebackhide__ = True
- for exc in filterfalse(isclass, always_iterable(expected_exception, BASE_TYPE)):
- msg = (
- "exceptions must be old-style classes or"
- " derived from BaseException, not %s"
- )
- raise TypeError(msg % type(exc))
-
- message = "DID NOT RAISE {}".format(expected_exception)
- match_expr = None
-
- if not args:
- if "message" in kwargs:
- message = kwargs.pop("message")
+
+ .. note::
+ Similar to caught exception objects in Python, explicitly clearing
+ local references to returned ``ExceptionInfo`` objects can
+ help the Python interpreter speed up its garbage collection.
+
+ Clearing those references breaks a reference cycle
+ (``ExceptionInfo`` --> caught exception --> frame stack raising
+ the exception --> current frame stack --> local variables -->
+ ``ExceptionInfo``) which makes Python keep all objects referenced
+ from that cycle (including all local variables in the current
+ frame) alive until the next cyclic garbage collection run. See the
+ official Python ``try`` statement documentation for more detailed
+ information.
+
+ """
+ __tracebackhide__ = True
+ for exc in filterfalse(isclass, always_iterable(expected_exception, BASE_TYPE)):
+ msg = (
+ "exceptions must be old-style classes or"
+ " derived from BaseException, not %s"
+ )
+ raise TypeError(msg % type(exc))
+
+ message = "DID NOT RAISE {}".format(expected_exception)
+ match_expr = None
+
+ if not args:
+ if "message" in kwargs:
+ message = kwargs.pop("message")
warnings.warn(deprecated.RAISES_MESSAGE_PARAMETER, stacklevel=2)
- if "match" in kwargs:
- match_expr = kwargs.pop("match")
- if kwargs:
- msg = "Unexpected keyword arguments passed to pytest.raises: "
+ if "match" in kwargs:
+ match_expr = kwargs.pop("match")
+ if kwargs:
+ msg = "Unexpected keyword arguments passed to pytest.raises: "
msg += ", ".join(sorted(kwargs))
- raise TypeError(msg)
- return RaisesContext(expected_exception, message, match_expr)
- elif isinstance(args[0], str):
+ raise TypeError(msg)
+ return RaisesContext(expected_exception, message, match_expr)
+ elif isinstance(args[0], str):
warnings.warn(deprecated.RAISES_EXEC, stacklevel=2)
(code,) = args
- assert isinstance(code, str)
- frame = sys._getframe(1)
- loc = frame.f_locals.copy()
- loc.update(kwargs)
- # print "raises frame scope: %r" % frame.f_locals
- try:
+ assert isinstance(code, str)
+ frame = sys._getframe(1)
+ loc = frame.f_locals.copy()
+ loc.update(kwargs)
+ # print "raises frame scope: %r" % frame.f_locals
+ try:
code = _pytest._code.Source(code).compile(_genframe=frame)
exec(code, frame.f_globals, loc)
- # XXX didn't mean f_globals == f_locals something special?
- # this is destroyed here ...
- except expected_exception:
+ # XXX didn't mean f_globals == f_locals something special?
+ # this is destroyed here ...
+ except expected_exception:
return _pytest._code.ExceptionInfo.from_current()
- else:
- func = args[0]
- try:
- func(*args[1:], **kwargs)
- except expected_exception:
+ else:
+ func = args[0]
+ try:
+ func(*args[1:], **kwargs)
+ except expected_exception:
return _pytest._code.ExceptionInfo.from_current()
- fail(message)
-
-
-raises.Exception = fail.Exception
-
-
-class RaisesContext(object):
- def __init__(self, expected_exception, message, match_expr):
- self.expected_exception = expected_exception
- self.message = message
- self.match_expr = match_expr
- self.excinfo = None
-
- def __enter__(self):
+ fail(message)
+
+
+raises.Exception = fail.Exception
+
+
+class RaisesContext(object):
+ def __init__(self, expected_exception, message, match_expr):
+ self.expected_exception = expected_exception
+ self.message = message
+ self.match_expr = match_expr
+ self.excinfo = None
+
+ def __enter__(self):
self.excinfo = _pytest._code.ExceptionInfo.for_later()
- return self.excinfo
-
- def __exit__(self, *tp):
- __tracebackhide__ = True
- if tp[0] is None:
- fail(self.message)
- self.excinfo.__init__(tp)
- suppress_exception = issubclass(self.excinfo.type, self.expected_exception)
- if sys.version_info[0] == 2 and suppress_exception:
- sys.exc_clear()
+ return self.excinfo
+
+ def __exit__(self, *tp):
+ __tracebackhide__ = True
+ if tp[0] is None:
+ fail(self.message)
+ self.excinfo.__init__(tp)
+ suppress_exception = issubclass(self.excinfo.type, self.expected_exception)
+ if sys.version_info[0] == 2 and suppress_exception:
+ sys.exc_clear()
if self.match_expr is not None and suppress_exception:
- self.excinfo.match(self.match_expr)
- return suppress_exception
+ self.excinfo.match(self.match_expr)
+ return suppress_exception
diff --git a/contrib/python/pytest/py2/_pytest/recwarn.py b/contrib/python/pytest/py2/_pytest/recwarn.py
index 8f7e999eef..7abf2e9355 100644
--- a/contrib/python/pytest/py2/_pytest/recwarn.py
+++ b/contrib/python/pytest/py2/_pytest/recwarn.py
@@ -1,251 +1,251 @@
# -*- coding: utf-8 -*-
-""" recording warnings during test function execution. """
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import inspect
-import re
-import sys
-import warnings
-
-import six
-
-import _pytest._code
+""" recording warnings during test function execution. """
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import inspect
+import re
+import sys
+import warnings
+
+import six
+
+import _pytest._code
from _pytest.deprecated import PYTEST_WARNS_UNKNOWN_KWARGS
from _pytest.deprecated import WARNS_EXEC
-from _pytest.fixtures import yield_fixture
-from _pytest.outcomes import fail
-
-
-@yield_fixture
-def recwarn():
- """Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions.
-
- See http://docs.python.org/library/warnings.html for information
- on warning categories.
- """
- wrec = WarningsRecorder()
- with wrec:
- warnings.simplefilter("default")
- yield wrec
-
-
-def deprecated_call(func=None, *args, **kwargs):
- """context manager that can be used to ensure a block of code triggers a
- ``DeprecationWarning`` or ``PendingDeprecationWarning``::
-
- >>> import warnings
- >>> def api_call_v2():
- ... warnings.warn('use v3 of this api', DeprecationWarning)
- ... return 200
-
- >>> with deprecated_call():
- ... assert api_call_v2() == 200
-
- ``deprecated_call`` can also be used by passing a function and ``*args`` and ``*kwargs``,
- in which case it will ensure calling ``func(*args, **kwargs)`` produces one of the warnings
- types above.
- """
- __tracebackhide__ = True
- if func is not None:
- args = (func,) + args
- return warns((DeprecationWarning, PendingDeprecationWarning), *args, **kwargs)
-
-
-def warns(expected_warning, *args, **kwargs):
- r"""Assert that code raises a particular class of warning.
-
- Specifically, the parameter ``expected_warning`` can be a warning class or
- sequence of warning classes, and the inside the ``with`` block must issue a warning of that class or
- classes.
-
- This helper produces a list of :class:`warnings.WarningMessage` objects,
- one for each warning raised.
-
- This function can be used as a context manager, or any of the other ways
- ``pytest.raises`` can be used::
-
- >>> with warns(RuntimeWarning):
- ... warnings.warn("my warning", RuntimeWarning)
-
- In the context manager form you may use the keyword argument ``match`` to assert
- that the exception matches a text or regex::
-
- >>> with warns(UserWarning, match='must be 0 or None'):
- ... warnings.warn("value must be 0 or None", UserWarning)
-
- >>> with warns(UserWarning, match=r'must be \d+$'):
- ... warnings.warn("value must be 42", UserWarning)
-
- >>> with warns(UserWarning, match=r'must be \d+$'):
- ... warnings.warn("this is not here", UserWarning)
- Traceback (most recent call last):
- ...
- Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted...
-
- """
- __tracebackhide__ = True
- if not args:
+from _pytest.fixtures import yield_fixture
+from _pytest.outcomes import fail
+
+
+@yield_fixture
+def recwarn():
+ """Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions.
+
+ See http://docs.python.org/library/warnings.html for information
+ on warning categories.
+ """
+ wrec = WarningsRecorder()
+ with wrec:
+ warnings.simplefilter("default")
+ yield wrec
+
+
+def deprecated_call(func=None, *args, **kwargs):
+ """context manager that can be used to ensure a block of code triggers a
+ ``DeprecationWarning`` or ``PendingDeprecationWarning``::
+
+ >>> import warnings
+ >>> def api_call_v2():
+ ... warnings.warn('use v3 of this api', DeprecationWarning)
+ ... return 200
+
+ >>> with deprecated_call():
+ ... assert api_call_v2() == 200
+
+ ``deprecated_call`` can also be used by passing a function and ``*args`` and ``*kwargs``,
+ in which case it will ensure calling ``func(*args, **kwargs)`` produces one of the warnings
+ types above.
+ """
+ __tracebackhide__ = True
+ if func is not None:
+ args = (func,) + args
+ return warns((DeprecationWarning, PendingDeprecationWarning), *args, **kwargs)
+
+
+def warns(expected_warning, *args, **kwargs):
+ r"""Assert that code raises a particular class of warning.
+
+ Specifically, the parameter ``expected_warning`` can be a warning class or
+ sequence of warning classes, and the inside the ``with`` block must issue a warning of that class or
+ classes.
+
+ This helper produces a list of :class:`warnings.WarningMessage` objects,
+ one for each warning raised.
+
+ This function can be used as a context manager, or any of the other ways
+ ``pytest.raises`` can be used::
+
+ >>> with warns(RuntimeWarning):
+ ... warnings.warn("my warning", RuntimeWarning)
+
+ In the context manager form you may use the keyword argument ``match`` to assert
+ that the exception matches a text or regex::
+
+ >>> with warns(UserWarning, match='must be 0 or None'):
+ ... warnings.warn("value must be 0 or None", UserWarning)
+
+ >>> with warns(UserWarning, match=r'must be \d+$'):
+ ... warnings.warn("value must be 42", UserWarning)
+
+ >>> with warns(UserWarning, match=r'must be \d+$'):
+ ... warnings.warn("this is not here", UserWarning)
+ Traceback (most recent call last):
+ ...
+ Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted...
+
+ """
+ __tracebackhide__ = True
+ if not args:
match_expr = kwargs.pop("match", None)
if kwargs:
warnings.warn(
PYTEST_WARNS_UNKNOWN_KWARGS.format(args=sorted(kwargs)), stacklevel=2
)
- return WarningsChecker(expected_warning, match_expr=match_expr)
- elif isinstance(args[0], str):
+ return WarningsChecker(expected_warning, match_expr=match_expr)
+ elif isinstance(args[0], str):
warnings.warn(WARNS_EXEC, stacklevel=2)
(code,) = args
- assert isinstance(code, str)
- frame = sys._getframe(1)
- loc = frame.f_locals.copy()
- loc.update(kwargs)
-
+ assert isinstance(code, str)
+ frame = sys._getframe(1)
+ loc = frame.f_locals.copy()
+ loc.update(kwargs)
+
with WarningsChecker(expected_warning):
- code = _pytest._code.Source(code).compile()
+ code = _pytest._code.Source(code).compile()
exec(code, frame.f_globals, loc)
- else:
- func = args[0]
+ else:
+ func = args[0]
with WarningsChecker(expected_warning):
- return func(*args[1:], **kwargs)
-
-
-class WarningsRecorder(warnings.catch_warnings):
- """A context manager to record raised warnings.
-
- Adapted from `warnings.catch_warnings`.
- """
-
- def __init__(self):
- super(WarningsRecorder, self).__init__(record=True)
- self._entered = False
- self._list = []
-
- @property
- def list(self):
- """The list of recorded warnings."""
- return self._list
-
- def __getitem__(self, i):
- """Get a recorded warning by index."""
- return self._list[i]
-
- def __iter__(self):
- """Iterate through the recorded warnings."""
- return iter(self._list)
-
- def __len__(self):
- """The number of recorded warnings."""
- return len(self._list)
-
- def pop(self, cls=Warning):
- """Pop the first recorded warning, raise exception if not exists."""
- for i, w in enumerate(self._list):
- if issubclass(w.category, cls):
- return self._list.pop(i)
- __tracebackhide__ = True
- raise AssertionError("%r not found in warning list" % cls)
-
- def clear(self):
- """Clear the list of recorded warnings."""
- self._list[:] = []
-
- def __enter__(self):
- if self._entered:
- __tracebackhide__ = True
- raise RuntimeError("Cannot enter %r twice" % self)
- self._list = super(WarningsRecorder, self).__enter__()
- warnings.simplefilter("always")
- # python3 keeps track of a "filter version", when the filters are
- # updated previously seen warnings can be re-warned. python2 has no
- # concept of this so we must reset the warnings registry manually.
- # trivial patching of `warnings.warn` seems to be enough somehow?
- if six.PY2:
-
- def warn(message, category=None, stacklevel=1):
- # duplicate the stdlib logic due to
- # bad handing in the c version of warnings
- if isinstance(message, Warning):
- category = message.__class__
- # Check category argument
- if category is None:
- category = UserWarning
- assert issubclass(category, Warning)
-
- # emulate resetting the warn registry
- f_globals = sys._getframe(stacklevel).f_globals
- if "__warningregistry__" in f_globals:
- orig = f_globals["__warningregistry__"]
- f_globals["__warningregistry__"] = None
- try:
- return self._saved_warn(message, category, stacklevel + 1)
- finally:
- f_globals["__warningregistry__"] = orig
- else:
- return self._saved_warn(message, category, stacklevel + 1)
-
- warnings.warn, self._saved_warn = warn, warnings.warn
- return self
-
- def __exit__(self, *exc_info):
- if not self._entered:
- __tracebackhide__ = True
- raise RuntimeError("Cannot exit %r without entering first" % self)
- # see above where `self._saved_warn` is assigned
- if six.PY2:
- warnings.warn = self._saved_warn
- super(WarningsRecorder, self).__exit__(*exc_info)
-
+ return func(*args[1:], **kwargs)
+
+
+class WarningsRecorder(warnings.catch_warnings):
+ """A context manager to record raised warnings.
+
+ Adapted from `warnings.catch_warnings`.
+ """
+
+ def __init__(self):
+ super(WarningsRecorder, self).__init__(record=True)
+ self._entered = False
+ self._list = []
+
+ @property
+ def list(self):
+ """The list of recorded warnings."""
+ return self._list
+
+ def __getitem__(self, i):
+ """Get a recorded warning by index."""
+ return self._list[i]
+
+ def __iter__(self):
+ """Iterate through the recorded warnings."""
+ return iter(self._list)
+
+ def __len__(self):
+ """The number of recorded warnings."""
+ return len(self._list)
+
+ def pop(self, cls=Warning):
+ """Pop the first recorded warning, raise exception if not exists."""
+ for i, w in enumerate(self._list):
+ if issubclass(w.category, cls):
+ return self._list.pop(i)
+ __tracebackhide__ = True
+ raise AssertionError("%r not found in warning list" % cls)
+
+ def clear(self):
+ """Clear the list of recorded warnings."""
+ self._list[:] = []
+
+ def __enter__(self):
+ if self._entered:
+ __tracebackhide__ = True
+ raise RuntimeError("Cannot enter %r twice" % self)
+ self._list = super(WarningsRecorder, self).__enter__()
+ warnings.simplefilter("always")
+ # python3 keeps track of a "filter version", when the filters are
+ # updated previously seen warnings can be re-warned. python2 has no
+ # concept of this so we must reset the warnings registry manually.
+ # trivial patching of `warnings.warn` seems to be enough somehow?
+ if six.PY2:
+
+ def warn(message, category=None, stacklevel=1):
+ # duplicate the stdlib logic due to
+ # bad handing in the c version of warnings
+ if isinstance(message, Warning):
+ category = message.__class__
+ # Check category argument
+ if category is None:
+ category = UserWarning
+ assert issubclass(category, Warning)
+
+ # emulate resetting the warn registry
+ f_globals = sys._getframe(stacklevel).f_globals
+ if "__warningregistry__" in f_globals:
+ orig = f_globals["__warningregistry__"]
+ f_globals["__warningregistry__"] = None
+ try:
+ return self._saved_warn(message, category, stacklevel + 1)
+ finally:
+ f_globals["__warningregistry__"] = orig
+ else:
+ return self._saved_warn(message, category, stacklevel + 1)
+
+ warnings.warn, self._saved_warn = warn, warnings.warn
+ return self
+
+ def __exit__(self, *exc_info):
+ if not self._entered:
+ __tracebackhide__ = True
+ raise RuntimeError("Cannot exit %r without entering first" % self)
+ # see above where `self._saved_warn` is assigned
+ if six.PY2:
+ warnings.warn = self._saved_warn
+ super(WarningsRecorder, self).__exit__(*exc_info)
+
# Built-in catch_warnings does not reset entered state so we do it
# manually here for this context manager to become reusable.
self._entered = False
-
-
-class WarningsChecker(WarningsRecorder):
- def __init__(self, expected_warning=None, match_expr=None):
- super(WarningsChecker, self).__init__()
-
- msg = "exceptions must be old-style classes or derived from Warning, not %s"
- if isinstance(expected_warning, tuple):
- for exc in expected_warning:
- if not inspect.isclass(exc):
- raise TypeError(msg % type(exc))
- elif inspect.isclass(expected_warning):
- expected_warning = (expected_warning,)
- elif expected_warning is not None:
- raise TypeError(msg % type(expected_warning))
-
- self.expected_warning = expected_warning
- self.match_expr = match_expr
-
- def __exit__(self, *exc_info):
- super(WarningsChecker, self).__exit__(*exc_info)
-
- __tracebackhide__ = True
-
- # only check if we're not currently handling an exception
- if all(a is None for a in exc_info):
- if self.expected_warning is not None:
- if not any(issubclass(r.category, self.expected_warning) for r in self):
- __tracebackhide__ = True
- fail(
- "DID NOT WARN. No warnings of type {} was emitted. "
- "The list of emitted warnings is: {}.".format(
- self.expected_warning, [each.message for each in self]
- )
- )
- elif self.match_expr is not None:
- for r in self:
- if issubclass(r.category, self.expected_warning):
- if re.compile(self.match_expr).search(str(r.message)):
- break
- else:
- fail(
- "DID NOT WARN. No warnings of type {} matching"
- " ('{}') was emitted. The list of emitted warnings"
- " is: {}.".format(
- self.expected_warning,
- self.match_expr,
- [each.message for each in self],
- )
- )
+
+
+class WarningsChecker(WarningsRecorder):
+ def __init__(self, expected_warning=None, match_expr=None):
+ super(WarningsChecker, self).__init__()
+
+ msg = "exceptions must be old-style classes or derived from Warning, not %s"
+ if isinstance(expected_warning, tuple):
+ for exc in expected_warning:
+ if not inspect.isclass(exc):
+ raise TypeError(msg % type(exc))
+ elif inspect.isclass(expected_warning):
+ expected_warning = (expected_warning,)
+ elif expected_warning is not None:
+ raise TypeError(msg % type(expected_warning))
+
+ self.expected_warning = expected_warning
+ self.match_expr = match_expr
+
+ def __exit__(self, *exc_info):
+ super(WarningsChecker, self).__exit__(*exc_info)
+
+ __tracebackhide__ = True
+
+ # only check if we're not currently handling an exception
+ if all(a is None for a in exc_info):
+ if self.expected_warning is not None:
+ if not any(issubclass(r.category, self.expected_warning) for r in self):
+ __tracebackhide__ = True
+ fail(
+ "DID NOT WARN. No warnings of type {} was emitted. "
+ "The list of emitted warnings is: {}.".format(
+ self.expected_warning, [each.message for each in self]
+ )
+ )
+ elif self.match_expr is not None:
+ for r in self:
+ if issubclass(r.category, self.expected_warning):
+ if re.compile(self.match_expr).search(str(r.message)):
+ break
+ else:
+ fail(
+ "DID NOT WARN. No warnings of type {} matching"
+ " ('{}') was emitted. The list of emitted warnings"
+ " is: {}.".format(
+ self.expected_warning,
+ self.match_expr,
+ [each.message for each in self],
+ )
+ )
diff --git a/contrib/python/pytest/py2/_pytest/reports.py b/contrib/python/pytest/py2/_pytest/reports.py
index 8e5106e069..0bba6762c3 100644
--- a/contrib/python/pytest/py2/_pytest/reports.py
+++ b/contrib/python/pytest/py2/_pytest/reports.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
from pprint import pprint
-import py
+import py
import six
-
+
from _pytest._code.code import ExceptionInfo
from _pytest._code.code import ReprEntry
from _pytest._code.code import ReprEntryNative
@@ -12,111 +12,111 @@ from _pytest._code.code import ReprFileLocation
from _pytest._code.code import ReprFuncArgs
from _pytest._code.code import ReprLocals
from _pytest._code.code import ReprTraceback
-from _pytest._code.code import TerminalRepr
+from _pytest._code.code import TerminalRepr
from _pytest.outcomes import skip
from _pytest.pathlib import Path
-
-
-def getslaveinfoline(node):
- try:
- return node._slaveinfocache
- except AttributeError:
- d = node.slaveinfo
- ver = "%s.%s.%s" % d["version_info"][:3]
- node._slaveinfocache = s = "[%s] %s -- Python %s %s" % (
- d["id"],
- d["sysplatform"],
- ver,
- d["executable"],
- )
- return s
-
-
-class BaseReport(object):
+
+
+def getslaveinfoline(node):
+ try:
+ return node._slaveinfocache
+ except AttributeError:
+ d = node.slaveinfo
+ ver = "%s.%s.%s" % d["version_info"][:3]
+ node._slaveinfocache = s = "[%s] %s -- Python %s %s" % (
+ d["id"],
+ d["sysplatform"],
+ ver,
+ d["executable"],
+ )
+ return s
+
+
+class BaseReport(object):
when = None
location = None
- def __init__(self, **kw):
- self.__dict__.update(kw)
-
- def toterminal(self, out):
- if hasattr(self, "node"):
- out.line(getslaveinfoline(self.node))
-
- longrepr = self.longrepr
- if longrepr is None:
- return
-
- if hasattr(longrepr, "toterminal"):
- longrepr.toterminal(out)
- else:
- try:
- out.line(longrepr)
- except UnicodeEncodeError:
- out.line("<unprintable longrepr>")
-
- def get_sections(self, prefix):
- for name, content in self.sections:
- if name.startswith(prefix):
- yield prefix, content
-
- @property
- def longreprtext(self):
- """
- Read-only property that returns the full string representation
- of ``longrepr``.
-
- .. versionadded:: 3.0
- """
- tw = py.io.TerminalWriter(stringio=True)
- tw.hasmarkup = False
- self.toterminal(tw)
- exc = tw.stringio.getvalue()
- return exc.strip()
-
- @property
- def caplog(self):
- """Return captured log lines, if log capturing is enabled
-
- .. versionadded:: 3.5
- """
- return "\n".join(
- content for (prefix, content) in self.get_sections("Captured log")
- )
-
- @property
- def capstdout(self):
- """Return captured text from stdout, if capturing is enabled
-
- .. versionadded:: 3.0
- """
- return "".join(
- content for (prefix, content) in self.get_sections("Captured stdout")
- )
-
- @property
- def capstderr(self):
- """Return captured text from stderr, if capturing is enabled
-
- .. versionadded:: 3.0
- """
- return "".join(
- content for (prefix, content) in self.get_sections("Captured stderr")
- )
-
- passed = property(lambda x: x.outcome == "passed")
- failed = property(lambda x: x.outcome == "failed")
- skipped = property(lambda x: x.outcome == "skipped")
-
- @property
- def fspath(self):
- return self.nodeid.split("::")[0]
-
+ def __init__(self, **kw):
+ self.__dict__.update(kw)
+
+ def toterminal(self, out):
+ if hasattr(self, "node"):
+ out.line(getslaveinfoline(self.node))
+
+ longrepr = self.longrepr
+ if longrepr is None:
+ return
+
+ if hasattr(longrepr, "toterminal"):
+ longrepr.toterminal(out)
+ else:
+ try:
+ out.line(longrepr)
+ except UnicodeEncodeError:
+ out.line("<unprintable longrepr>")
+
+ def get_sections(self, prefix):
+ for name, content in self.sections:
+ if name.startswith(prefix):
+ yield prefix, content
+
+ @property
+ def longreprtext(self):
+ """
+ Read-only property that returns the full string representation
+ of ``longrepr``.
+
+ .. versionadded:: 3.0
+ """
+ tw = py.io.TerminalWriter(stringio=True)
+ tw.hasmarkup = False
+ self.toterminal(tw)
+ exc = tw.stringio.getvalue()
+ return exc.strip()
+
+ @property
+ def caplog(self):
+ """Return captured log lines, if log capturing is enabled
+
+ .. versionadded:: 3.5
+ """
+ return "\n".join(
+ content for (prefix, content) in self.get_sections("Captured log")
+ )
+
+ @property
+ def capstdout(self):
+ """Return captured text from stdout, if capturing is enabled
+
+ .. versionadded:: 3.0
+ """
+ return "".join(
+ content for (prefix, content) in self.get_sections("Captured stdout")
+ )
+
+ @property
+ def capstderr(self):
+ """Return captured text from stderr, if capturing is enabled
+
+ .. versionadded:: 3.0
+ """
+ return "".join(
+ content for (prefix, content) in self.get_sections("Captured stderr")
+ )
+
+ passed = property(lambda x: x.outcome == "passed")
+ failed = property(lambda x: x.outcome == "failed")
+ skipped = property(lambda x: x.outcome == "skipped")
+
+ @property
+ def fspath(self):
+ return self.nodeid.split("::")[0]
+
@property
def count_towards_summary(self):
"""
**Experimental**
-
+
Returns True if this report should be counted towards the totals shown at the end of the
test session: "1 passed, 1 failure, etc".
@@ -278,70 +278,70 @@ def _report_unserialization_failure(type_name, report_class, reportdict):
raise RuntimeError(stream.getvalue())
-class TestReport(BaseReport):
- """ Basic test report object (also used for setup and teardown calls if
- they fail).
- """
-
+class TestReport(BaseReport):
+ """ Basic test report object (also used for setup and teardown calls if
+ they fail).
+ """
+
__test__ = False
- def __init__(
- self,
- nodeid,
- location,
- keywords,
- outcome,
- longrepr,
- when,
- sections=(),
- duration=0,
- user_properties=None,
- **extra
- ):
- #: normalized collection node id
- self.nodeid = nodeid
-
- #: a (filesystempath, lineno, domaininfo) tuple indicating the
- #: actual location of a test item - it might be different from the
- #: collected one e.g. if a method is inherited from a different module.
- self.location = location
-
- #: a name -> value dictionary containing all keywords and
- #: markers associated with a test invocation.
- self.keywords = keywords
-
- #: test outcome, always one of "passed", "failed", "skipped".
- self.outcome = outcome
-
- #: None or a failure representation.
- self.longrepr = longrepr
-
- #: one of 'setup', 'call', 'teardown' to indicate runtest phase.
- self.when = when
-
- #: user properties is a list of tuples (name, value) that holds user
- #: defined properties of the test
- self.user_properties = list(user_properties or [])
-
- #: list of pairs ``(str, str)`` of extra information which needs to
- #: marshallable. Used by pytest to add captured text
- #: from ``stdout`` and ``stderr``, but may be used by other plugins
- #: to add arbitrary information to reports.
- self.sections = list(sections)
-
- #: time it took to run just the test
- self.duration = duration
-
- self.__dict__.update(extra)
-
- def __repr__(self):
+ def __init__(
+ self,
+ nodeid,
+ location,
+ keywords,
+ outcome,
+ longrepr,
+ when,
+ sections=(),
+ duration=0,
+ user_properties=None,
+ **extra
+ ):
+ #: normalized collection node id
+ self.nodeid = nodeid
+
+ #: a (filesystempath, lineno, domaininfo) tuple indicating the
+ #: actual location of a test item - it might be different from the
+ #: collected one e.g. if a method is inherited from a different module.
+ self.location = location
+
+ #: a name -> value dictionary containing all keywords and
+ #: markers associated with a test invocation.
+ self.keywords = keywords
+
+ #: test outcome, always one of "passed", "failed", "skipped".
+ self.outcome = outcome
+
+ #: None or a failure representation.
+ self.longrepr = longrepr
+
+ #: one of 'setup', 'call', 'teardown' to indicate runtest phase.
+ self.when = when
+
+ #: user properties is a list of tuples (name, value) that holds user
+ #: defined properties of the test
+ self.user_properties = list(user_properties or [])
+
+ #: list of pairs ``(str, str)`` of extra information which needs to
+ #: marshallable. Used by pytest to add captured text
+ #: from ``stdout`` and ``stderr``, but may be used by other plugins
+ #: to add arbitrary information to reports.
+ self.sections = list(sections)
+
+ #: time it took to run just the test
+ self.duration = duration
+
+ self.__dict__.update(extra)
+
+ def __repr__(self):
return "<%s %r when=%r outcome=%r>" % (
self.__class__.__name__,
- self.nodeid,
- self.when,
- self.outcome,
- )
-
+ self.nodeid,
+ self.when,
+ self.outcome,
+ )
+
@classmethod
def from_item_and_call(cls, item, call):
"""
@@ -384,37 +384,37 @@ class TestReport(BaseReport):
duration,
user_properties=item.user_properties,
)
-
-
+
+
class CollectReport(BaseReport):
when = "collect"
-
- def __init__(self, nodeid, outcome, longrepr, result, sections=(), **extra):
- self.nodeid = nodeid
- self.outcome = outcome
- self.longrepr = longrepr
- self.result = result or []
- self.sections = list(sections)
- self.__dict__.update(extra)
-
- @property
- def location(self):
- return (self.fspath, None, self.fspath)
-
- def __repr__(self):
- return "<CollectReport %r lenresult=%s outcome=%r>" % (
- self.nodeid,
- len(self.result),
- self.outcome,
- )
-
-
-class CollectErrorRepr(TerminalRepr):
- def __init__(self, msg):
- self.longrepr = msg
-
- def toterminal(self, out):
- out.line(self.longrepr, red=True)
+
+ def __init__(self, nodeid, outcome, longrepr, result, sections=(), **extra):
+ self.nodeid = nodeid
+ self.outcome = outcome
+ self.longrepr = longrepr
+ self.result = result or []
+ self.sections = list(sections)
+ self.__dict__.update(extra)
+
+ @property
+ def location(self):
+ return (self.fspath, None, self.fspath)
+
+ def __repr__(self):
+ return "<CollectReport %r lenresult=%s outcome=%r>" % (
+ self.nodeid,
+ len(self.result),
+ self.outcome,
+ )
+
+
+class CollectErrorRepr(TerminalRepr):
+ def __init__(self, msg):
+ self.longrepr = msg
+
+ def toterminal(self, out):
+ out.line(self.longrepr, red=True)
def pytest_report_to_serializable(report):
diff --git a/contrib/python/pytest/py2/_pytest/resultlog.py b/contrib/python/pytest/py2/_pytest/resultlog.py
index aa7e9c9025..bd30b5071e 100644
--- a/contrib/python/pytest/py2/_pytest/resultlog.py
+++ b/contrib/python/pytest/py2/_pytest/resultlog.py
@@ -1,102 +1,102 @@
# -*- coding: utf-8 -*-
-""" log machine-parseable test session result information in a plain
-text file.
-"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import os
-
-import py
-
-
-def pytest_addoption(parser):
- group = parser.getgroup("terminal reporting", "resultlog plugin options")
- group.addoption(
- "--resultlog",
- "--result-log",
- action="store",
- metavar="path",
- default=None,
- help="DEPRECATED path for machine-readable result log.",
- )
-
-
-def pytest_configure(config):
- resultlog = config.option.resultlog
- # prevent opening resultlog on slave nodes (xdist)
- if resultlog and not hasattr(config, "slaveinput"):
- dirname = os.path.dirname(os.path.abspath(resultlog))
- if not os.path.isdir(dirname):
- os.makedirs(dirname)
- logfile = open(resultlog, "w", 1) # line buffered
- config._resultlog = ResultLog(config, logfile)
- config.pluginmanager.register(config._resultlog)
-
- from _pytest.deprecated import RESULT_LOG
+""" log machine-parseable test session result information in a plain
+text file.
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import os
+
+import py
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("terminal reporting", "resultlog plugin options")
+ group.addoption(
+ "--resultlog",
+ "--result-log",
+ action="store",
+ metavar="path",
+ default=None,
+ help="DEPRECATED path for machine-readable result log.",
+ )
+
+
+def pytest_configure(config):
+ resultlog = config.option.resultlog
+ # prevent opening resultlog on slave nodes (xdist)
+ if resultlog and not hasattr(config, "slaveinput"):
+ dirname = os.path.dirname(os.path.abspath(resultlog))
+ if not os.path.isdir(dirname):
+ os.makedirs(dirname)
+ logfile = open(resultlog, "w", 1) # line buffered
+ config._resultlog = ResultLog(config, logfile)
+ config.pluginmanager.register(config._resultlog)
+
+ from _pytest.deprecated import RESULT_LOG
from _pytest.warnings import _issue_warning_captured
-
+
_issue_warning_captured(RESULT_LOG, config.hook, stacklevel=2)
-
-
-def pytest_unconfigure(config):
- resultlog = getattr(config, "_resultlog", None)
- if resultlog:
- resultlog.logfile.close()
- del config._resultlog
- config.pluginmanager.unregister(resultlog)
-
-
-class ResultLog(object):
- def __init__(self, config, logfile):
- self.config = config
- self.logfile = logfile # preferably line buffered
-
- def write_log_entry(self, testpath, lettercode, longrepr):
- print("%s %s" % (lettercode, testpath), file=self.logfile)
- for line in longrepr.splitlines():
- print(" %s" % line, file=self.logfile)
-
- def log_outcome(self, report, lettercode, longrepr):
- testpath = getattr(report, "nodeid", None)
- if testpath is None:
- testpath = report.fspath
- self.write_log_entry(testpath, lettercode, longrepr)
-
- def pytest_runtest_logreport(self, report):
- if report.when != "call" and report.passed:
- return
+
+
+def pytest_unconfigure(config):
+ resultlog = getattr(config, "_resultlog", None)
+ if resultlog:
+ resultlog.logfile.close()
+ del config._resultlog
+ config.pluginmanager.unregister(resultlog)
+
+
+class ResultLog(object):
+ def __init__(self, config, logfile):
+ self.config = config
+ self.logfile = logfile # preferably line buffered
+
+ def write_log_entry(self, testpath, lettercode, longrepr):
+ print("%s %s" % (lettercode, testpath), file=self.logfile)
+ for line in longrepr.splitlines():
+ print(" %s" % line, file=self.logfile)
+
+ def log_outcome(self, report, lettercode, longrepr):
+ testpath = getattr(report, "nodeid", None)
+ if testpath is None:
+ testpath = report.fspath
+ self.write_log_entry(testpath, lettercode, longrepr)
+
+ def pytest_runtest_logreport(self, report):
+ if report.when != "call" and report.passed:
+ return
res = self.config.hook.pytest_report_teststatus(
report=report, config=self.config
)
- code = res[1]
- if code == "x":
- longrepr = str(report.longrepr)
- elif code == "X":
- longrepr = ""
- elif report.passed:
- longrepr = ""
- elif report.failed:
- longrepr = str(report.longrepr)
- elif report.skipped:
- longrepr = str(report.longrepr[2])
- self.log_outcome(report, code, longrepr)
-
- def pytest_collectreport(self, report):
- if not report.passed:
- if report.failed:
- code = "F"
- longrepr = str(report.longrepr)
- else:
- assert report.skipped
- code = "S"
- longrepr = "%s:%d: %s" % report.longrepr
- self.log_outcome(report, code, longrepr)
-
- def pytest_internalerror(self, excrepr):
- reprcrash = getattr(excrepr, "reprcrash", None)
- path = getattr(reprcrash, "path", None)
- if path is None:
- path = "cwd:%s" % py.path.local()
- self.write_log_entry(path, "!", str(excrepr))
+ code = res[1]
+ if code == "x":
+ longrepr = str(report.longrepr)
+ elif code == "X":
+ longrepr = ""
+ elif report.passed:
+ longrepr = ""
+ elif report.failed:
+ longrepr = str(report.longrepr)
+ elif report.skipped:
+ longrepr = str(report.longrepr[2])
+ self.log_outcome(report, code, longrepr)
+
+ def pytest_collectreport(self, report):
+ if not report.passed:
+ if report.failed:
+ code = "F"
+ longrepr = str(report.longrepr)
+ else:
+ assert report.skipped
+ code = "S"
+ longrepr = "%s:%d: %s" % report.longrepr
+ self.log_outcome(report, code, longrepr)
+
+ def pytest_internalerror(self, excrepr):
+ reprcrash = getattr(excrepr, "reprcrash", None)
+ path = getattr(reprcrash, "path", None)
+ if path is None:
+ path = "cwd:%s" % py.path.local()
+ self.write_log_entry(path, "!", str(excrepr))
diff --git a/contrib/python/pytest/py2/_pytest/runner.py b/contrib/python/pytest/py2/_pytest/runner.py
index 154a93983d..34ae917738 100644
--- a/contrib/python/pytest/py2/_pytest/runner.py
+++ b/contrib/python/pytest/py2/_pytest/runner.py
@@ -1,216 +1,216 @@
# -*- coding: utf-8 -*-
-""" basic collect and runtest protocol implementations """
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import bdb
-import os
-import sys
-from time import time
-
+""" basic collect and runtest protocol implementations """
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import bdb
+import os
+import sys
+from time import time
+
import attr
-import six
-
-from .reports import CollectErrorRepr
-from .reports import CollectReport
-from .reports import TestReport
-from _pytest._code.code import ExceptionInfo
+import six
+
+from .reports import CollectErrorRepr
+from .reports import CollectReport
+from .reports import TestReport
+from _pytest._code.code import ExceptionInfo
from _pytest.compat import safe_str
from _pytest.outcomes import Exit
-from _pytest.outcomes import Skipped
-from _pytest.outcomes import TEST_OUTCOME
-
-#
-# pytest plugin hooks
-
-
-def pytest_addoption(parser):
- group = parser.getgroup("terminal reporting", "reporting", after="general")
- group.addoption(
- "--durations",
- action="store",
- type=int,
- default=None,
- metavar="N",
- help="show N slowest setup/test durations (N=0 for all).",
- ),
-
-
-def pytest_terminal_summary(terminalreporter):
- durations = terminalreporter.config.option.durations
- verbose = terminalreporter.config.getvalue("verbose")
- if durations is None:
- return
- tr = terminalreporter
- dlist = []
- for replist in tr.stats.values():
- for rep in replist:
- if hasattr(rep, "duration"):
- dlist.append(rep)
- if not dlist:
- return
- dlist.sort(key=lambda x: x.duration)
- dlist.reverse()
- if not durations:
- tr.write_sep("=", "slowest test durations")
- else:
- tr.write_sep("=", "slowest %s test durations" % durations)
- dlist = dlist[:durations]
-
- for rep in dlist:
- if verbose < 2 and rep.duration < 0.005:
- tr.write_line("")
- tr.write_line("(0.00 durations hidden. Use -vv to show these durations.)")
- break
- tr.write_line("%02.2fs %-8s %s" % (rep.duration, rep.when, rep.nodeid))
-
-
-def pytest_sessionstart(session):
- session._setupstate = SetupState()
-
-
-def pytest_sessionfinish(session):
- session._setupstate.teardown_all()
-
-
-def pytest_runtest_protocol(item, nextitem):
- item.ihook.pytest_runtest_logstart(nodeid=item.nodeid, location=item.location)
- runtestprotocol(item, nextitem=nextitem)
- item.ihook.pytest_runtest_logfinish(nodeid=item.nodeid, location=item.location)
- return True
-
-
-def runtestprotocol(item, log=True, nextitem=None):
- hasrequest = hasattr(item, "_request")
- if hasrequest and not item._request:
- item._initrequest()
- rep = call_and_report(item, "setup", log)
- reports = [rep]
- if rep.passed:
+from _pytest.outcomes import Skipped
+from _pytest.outcomes import TEST_OUTCOME
+
+#
+# pytest plugin hooks
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("terminal reporting", "reporting", after="general")
+ group.addoption(
+ "--durations",
+ action="store",
+ type=int,
+ default=None,
+ metavar="N",
+ help="show N slowest setup/test durations (N=0 for all).",
+ ),
+
+
+def pytest_terminal_summary(terminalreporter):
+ durations = terminalreporter.config.option.durations
+ verbose = terminalreporter.config.getvalue("verbose")
+ if durations is None:
+ return
+ tr = terminalreporter
+ dlist = []
+ for replist in tr.stats.values():
+ for rep in replist:
+ if hasattr(rep, "duration"):
+ dlist.append(rep)
+ if not dlist:
+ return
+ dlist.sort(key=lambda x: x.duration)
+ dlist.reverse()
+ if not durations:
+ tr.write_sep("=", "slowest test durations")
+ else:
+ tr.write_sep("=", "slowest %s test durations" % durations)
+ dlist = dlist[:durations]
+
+ for rep in dlist:
+ if verbose < 2 and rep.duration < 0.005:
+ tr.write_line("")
+ tr.write_line("(0.00 durations hidden. Use -vv to show these durations.)")
+ break
+ tr.write_line("%02.2fs %-8s %s" % (rep.duration, rep.when, rep.nodeid))
+
+
+def pytest_sessionstart(session):
+ session._setupstate = SetupState()
+
+
+def pytest_sessionfinish(session):
+ session._setupstate.teardown_all()
+
+
+def pytest_runtest_protocol(item, nextitem):
+ item.ihook.pytest_runtest_logstart(nodeid=item.nodeid, location=item.location)
+ runtestprotocol(item, nextitem=nextitem)
+ item.ihook.pytest_runtest_logfinish(nodeid=item.nodeid, location=item.location)
+ return True
+
+
+def runtestprotocol(item, log=True, nextitem=None):
+ hasrequest = hasattr(item, "_request")
+ if hasrequest and not item._request:
+ item._initrequest()
+ rep = call_and_report(item, "setup", log)
+ reports = [rep]
+ if rep.passed:
if item.config.getoption("setupshow", False):
- show_test_item(item)
+ show_test_item(item)
if not item.config.getoption("setuponly", False):
- reports.append(call_and_report(item, "call", log))
- reports.append(call_and_report(item, "teardown", log, nextitem=nextitem))
- # after all teardown hooks have been called
- # want funcargs and request info to go away
- if hasrequest:
- item._request = False
- item.funcargs = None
- return reports
-
-
-def show_test_item(item):
- """Show test function, parameters and the fixtures of the test item."""
- tw = item.config.get_terminal_writer()
- tw.line()
- tw.write(" " * 8)
- tw.write(item._nodeid)
- used_fixtures = sorted(item._fixtureinfo.name2fixturedefs.keys())
- if used_fixtures:
- tw.write(" (fixtures used: {})".format(", ".join(used_fixtures)))
-
-
-def pytest_runtest_setup(item):
- _update_current_test_var(item, "setup")
- item.session._setupstate.prepare(item)
-
-
-def pytest_runtest_call(item):
- _update_current_test_var(item, "call")
- sys.last_type, sys.last_value, sys.last_traceback = (None, None, None)
- try:
- item.runtest()
- except Exception:
- # Store trace info to allow postmortem debugging
- type, value, tb = sys.exc_info()
- tb = tb.tb_next # Skip *this* frame
- sys.last_type = type
- sys.last_value = value
- sys.last_traceback = tb
- del type, value, tb # Get rid of these in this frame
- raise
-
-
-def pytest_runtest_teardown(item, nextitem):
- _update_current_test_var(item, "teardown")
- item.session._setupstate.teardown_exact(item, nextitem)
- _update_current_test_var(item, None)
-
-
-def _update_current_test_var(item, when):
- """
- Update PYTEST_CURRENT_TEST to reflect the current item and stage.
-
- If ``when`` is None, delete PYTEST_CURRENT_TEST from the environment.
- """
- var_name = "PYTEST_CURRENT_TEST"
- if when:
- value = "{} ({})".format(item.nodeid, when)
- # don't allow null bytes on environment variables (see #2644, #2957)
- value = value.replace("\x00", "(null)")
- os.environ[var_name] = value
- else:
- os.environ.pop(var_name)
-
-
-def pytest_report_teststatus(report):
- if report.when in ("setup", "teardown"):
- if report.failed:
- # category, shortletter, verbose-word
- return "error", "E", "ERROR"
- elif report.skipped:
- return "skipped", "s", "SKIPPED"
- else:
- return "", "", ""
-
-
-#
-# Implementation
-
-
-def call_and_report(item, when, log=True, **kwds):
- call = call_runtest_hook(item, when, **kwds)
- hook = item.ihook
- report = hook.pytest_runtest_makereport(item=item, call=call)
- if log:
- hook.pytest_runtest_logreport(report=report)
- if check_interactive_exception(call, report):
- hook.pytest_exception_interact(node=item, call=call, report=report)
- return report
-
-
-def check_interactive_exception(call, report):
- return call.excinfo and not (
- hasattr(report, "wasxfail")
+ reports.append(call_and_report(item, "call", log))
+ reports.append(call_and_report(item, "teardown", log, nextitem=nextitem))
+ # after all teardown hooks have been called
+ # want funcargs and request info to go away
+ if hasrequest:
+ item._request = False
+ item.funcargs = None
+ return reports
+
+
+def show_test_item(item):
+ """Show test function, parameters and the fixtures of the test item."""
+ tw = item.config.get_terminal_writer()
+ tw.line()
+ tw.write(" " * 8)
+ tw.write(item._nodeid)
+ used_fixtures = sorted(item._fixtureinfo.name2fixturedefs.keys())
+ if used_fixtures:
+ tw.write(" (fixtures used: {})".format(", ".join(used_fixtures)))
+
+
+def pytest_runtest_setup(item):
+ _update_current_test_var(item, "setup")
+ item.session._setupstate.prepare(item)
+
+
+def pytest_runtest_call(item):
+ _update_current_test_var(item, "call")
+ sys.last_type, sys.last_value, sys.last_traceback = (None, None, None)
+ try:
+ item.runtest()
+ except Exception:
+ # Store trace info to allow postmortem debugging
+ type, value, tb = sys.exc_info()
+ tb = tb.tb_next # Skip *this* frame
+ sys.last_type = type
+ sys.last_value = value
+ sys.last_traceback = tb
+ del type, value, tb # Get rid of these in this frame
+ raise
+
+
+def pytest_runtest_teardown(item, nextitem):
+ _update_current_test_var(item, "teardown")
+ item.session._setupstate.teardown_exact(item, nextitem)
+ _update_current_test_var(item, None)
+
+
+def _update_current_test_var(item, when):
+ """
+ Update PYTEST_CURRENT_TEST to reflect the current item and stage.
+
+ If ``when`` is None, delete PYTEST_CURRENT_TEST from the environment.
+ """
+ var_name = "PYTEST_CURRENT_TEST"
+ if when:
+ value = "{} ({})".format(item.nodeid, when)
+ # don't allow null bytes on environment variables (see #2644, #2957)
+ value = value.replace("\x00", "(null)")
+ os.environ[var_name] = value
+ else:
+ os.environ.pop(var_name)
+
+
+def pytest_report_teststatus(report):
+ if report.when in ("setup", "teardown"):
+ if report.failed:
+ # category, shortletter, verbose-word
+ return "error", "E", "ERROR"
+ elif report.skipped:
+ return "skipped", "s", "SKIPPED"
+ else:
+ return "", "", ""
+
+
+#
+# Implementation
+
+
+def call_and_report(item, when, log=True, **kwds):
+ call = call_runtest_hook(item, when, **kwds)
+ hook = item.ihook
+ report = hook.pytest_runtest_makereport(item=item, call=call)
+ if log:
+ hook.pytest_runtest_logreport(report=report)
+ if check_interactive_exception(call, report):
+ hook.pytest_exception_interact(node=item, call=call, report=report)
+ return report
+
+
+def check_interactive_exception(call, report):
+ return call.excinfo and not (
+ hasattr(report, "wasxfail")
or call.excinfo.errisinstance(Skipped)
- or call.excinfo.errisinstance(bdb.BdbQuit)
- )
-
-
-def call_runtest_hook(item, when, **kwds):
- hookname = "pytest_runtest_" + when
- ihook = getattr(item.ihook, hookname)
+ or call.excinfo.errisinstance(bdb.BdbQuit)
+ )
+
+
+def call_runtest_hook(item, when, **kwds):
+ hookname = "pytest_runtest_" + when
+ ihook = getattr(item.ihook, hookname)
reraise = (Exit,)
if not item.config.getoption("usepdb", False):
reraise += (KeyboardInterrupt,)
return CallInfo.from_call(
lambda: ihook(item=item, **kwds), when=when, reraise=reraise
- )
-
-
+ )
+
+
@attr.s(repr=False)
-class CallInfo(object):
- """ Result/Exception info a function invocation. """
-
+class CallInfo(object):
+ """ Result/Exception info a function invocation. """
+
_result = attr.ib()
# Optional[ExceptionInfo]
excinfo = attr.ib()
start = attr.ib()
stop = attr.ib()
when = attr.ib()
-
+
@property
def result(self):
if self.excinfo is not None:
@@ -219,158 +219,158 @@ class CallInfo(object):
@classmethod
def from_call(cls, func, when, reraise=None):
- #: context of invocation: one of "setup", "call",
- #: "teardown", "memocollect"
+ #: context of invocation: one of "setup", "call",
+ #: "teardown", "memocollect"
start = time()
excinfo = None
- try:
+ try:
result = func()
except: # noqa
excinfo = ExceptionInfo.from_current()
if reraise is not None and excinfo.errisinstance(reraise):
- raise
+ raise
result = None
stop = time()
return cls(start=start, stop=stop, when=when, result=result, excinfo=excinfo)
-
- def __repr__(self):
+
+ def __repr__(self):
if self.excinfo is not None:
status = "exception"
value = self.excinfo.value
- else:
+ else:
# TODO: investigate unification
value = repr(self._result)
status = "result"
return "<CallInfo when={when!r} {status}: {value}>".format(
when=self.when, value=safe_str(value), status=status
)
-
-
-def pytest_runtest_makereport(item, call):
+
+
+def pytest_runtest_makereport(item, call):
return TestReport.from_item_and_call(item, call)
-
-
-def pytest_make_collect_report(collector):
+
+
+def pytest_make_collect_report(collector):
call = CallInfo.from_call(lambda: list(collector.collect()), "collect")
- longrepr = None
- if not call.excinfo:
- outcome = "passed"
- else:
- from _pytest import nose
-
- skip_exceptions = (Skipped,) + nose.get_skip_exceptions()
- if call.excinfo.errisinstance(skip_exceptions):
- outcome = "skipped"
- r = collector._repr_failure_py(call.excinfo, "line").reprcrash
- longrepr = (str(r.path), r.lineno, r.message)
- else:
- outcome = "failed"
- errorinfo = collector.repr_failure(call.excinfo)
- if not hasattr(errorinfo, "toterminal"):
- errorinfo = CollectErrorRepr(errorinfo)
- longrepr = errorinfo
- rep = CollectReport(
- collector.nodeid, outcome, longrepr, getattr(call, "result", None)
- )
- rep.call = call # see collect_one_node
- return rep
-
-
-class SetupState(object):
- """ shared state for setting up/tearing down test items or collectors. """
-
- def __init__(self):
- self.stack = []
- self._finalizers = {}
-
- def addfinalizer(self, finalizer, colitem):
- """ attach a finalizer to the given colitem.
- if colitem is None, this will add a finalizer that
- is called at the end of teardown_all().
- """
- assert colitem and not isinstance(colitem, tuple)
- assert callable(finalizer)
- # assert colitem in self.stack # some unit tests don't setup stack :/
- self._finalizers.setdefault(colitem, []).append(finalizer)
-
- def _pop_and_teardown(self):
- colitem = self.stack.pop()
- self._teardown_with_finalization(colitem)
-
- def _callfinalizers(self, colitem):
- finalizers = self._finalizers.pop(colitem, None)
- exc = None
- while finalizers:
- fin = finalizers.pop()
- try:
- fin()
- except TEST_OUTCOME:
- # XXX Only first exception will be seen by user,
- # ideally all should be reported.
- if exc is None:
- exc = sys.exc_info()
- if exc:
- six.reraise(*exc)
-
- def _teardown_with_finalization(self, colitem):
- self._callfinalizers(colitem)
- if hasattr(colitem, "teardown"):
- colitem.teardown()
- for colitem in self._finalizers:
- assert (
- colitem is None or colitem in self.stack or isinstance(colitem, tuple)
- )
-
- def teardown_all(self):
- while self.stack:
- self._pop_and_teardown()
- for key in list(self._finalizers):
- self._teardown_with_finalization(key)
- assert not self._finalizers
-
- def teardown_exact(self, item, nextitem):
- needed_collectors = nextitem and nextitem.listchain() or []
- self._teardown_towards(needed_collectors)
-
- def _teardown_towards(self, needed_collectors):
- exc = None
- while self.stack:
- if self.stack == needed_collectors[: len(self.stack)]:
- break
- try:
- self._pop_and_teardown()
- except TEST_OUTCOME:
- # XXX Only first exception will be seen by user,
- # ideally all should be reported.
- if exc is None:
- exc = sys.exc_info()
- if exc:
- six.reraise(*exc)
-
- def prepare(self, colitem):
- """ setup objects along the collector chain to the test-method
- and teardown previously setup objects."""
- needed_collectors = colitem.listchain()
- self._teardown_towards(needed_collectors)
-
- # check if the last collection node has raised an error
- for col in self.stack:
- if hasattr(col, "_prepare_exc"):
- six.reraise(*col._prepare_exc)
- for col in needed_collectors[len(self.stack) :]:
- self.stack.append(col)
- try:
- col.setup()
- except TEST_OUTCOME:
- col._prepare_exc = sys.exc_info()
- raise
-
-
-def collect_one_node(collector):
- ihook = collector.ihook
- ihook.pytest_collectstart(collector=collector)
- rep = ihook.pytest_make_collect_report(collector=collector)
- call = rep.__dict__.pop("call", None)
- if call and check_interactive_exception(call, rep):
- ihook.pytest_exception_interact(node=collector, call=call, report=rep)
- return rep
+ longrepr = None
+ if not call.excinfo:
+ outcome = "passed"
+ else:
+ from _pytest import nose
+
+ skip_exceptions = (Skipped,) + nose.get_skip_exceptions()
+ if call.excinfo.errisinstance(skip_exceptions):
+ outcome = "skipped"
+ r = collector._repr_failure_py(call.excinfo, "line").reprcrash
+ longrepr = (str(r.path), r.lineno, r.message)
+ else:
+ outcome = "failed"
+ errorinfo = collector.repr_failure(call.excinfo)
+ if not hasattr(errorinfo, "toterminal"):
+ errorinfo = CollectErrorRepr(errorinfo)
+ longrepr = errorinfo
+ rep = CollectReport(
+ collector.nodeid, outcome, longrepr, getattr(call, "result", None)
+ )
+ rep.call = call # see collect_one_node
+ return rep
+
+
+class SetupState(object):
+ """ shared state for setting up/tearing down test items or collectors. """
+
+ def __init__(self):
+ self.stack = []
+ self._finalizers = {}
+
+ def addfinalizer(self, finalizer, colitem):
+ """ attach a finalizer to the given colitem.
+ if colitem is None, this will add a finalizer that
+ is called at the end of teardown_all().
+ """
+ assert colitem and not isinstance(colitem, tuple)
+ assert callable(finalizer)
+ # assert colitem in self.stack # some unit tests don't setup stack :/
+ self._finalizers.setdefault(colitem, []).append(finalizer)
+
+ def _pop_and_teardown(self):
+ colitem = self.stack.pop()
+ self._teardown_with_finalization(colitem)
+
+ def _callfinalizers(self, colitem):
+ finalizers = self._finalizers.pop(colitem, None)
+ exc = None
+ while finalizers:
+ fin = finalizers.pop()
+ try:
+ fin()
+ except TEST_OUTCOME:
+ # XXX Only first exception will be seen by user,
+ # ideally all should be reported.
+ if exc is None:
+ exc = sys.exc_info()
+ if exc:
+ six.reraise(*exc)
+
+ def _teardown_with_finalization(self, colitem):
+ self._callfinalizers(colitem)
+ if hasattr(colitem, "teardown"):
+ colitem.teardown()
+ for colitem in self._finalizers:
+ assert (
+ colitem is None or colitem in self.stack or isinstance(colitem, tuple)
+ )
+
+ def teardown_all(self):
+ while self.stack:
+ self._pop_and_teardown()
+ for key in list(self._finalizers):
+ self._teardown_with_finalization(key)
+ assert not self._finalizers
+
+ def teardown_exact(self, item, nextitem):
+ needed_collectors = nextitem and nextitem.listchain() or []
+ self._teardown_towards(needed_collectors)
+
+ def _teardown_towards(self, needed_collectors):
+ exc = None
+ while self.stack:
+ if self.stack == needed_collectors[: len(self.stack)]:
+ break
+ try:
+ self._pop_and_teardown()
+ except TEST_OUTCOME:
+ # XXX Only first exception will be seen by user,
+ # ideally all should be reported.
+ if exc is None:
+ exc = sys.exc_info()
+ if exc:
+ six.reraise(*exc)
+
+ def prepare(self, colitem):
+ """ setup objects along the collector chain to the test-method
+ and teardown previously setup objects."""
+ needed_collectors = colitem.listchain()
+ self._teardown_towards(needed_collectors)
+
+ # check if the last collection node has raised an error
+ for col in self.stack:
+ if hasattr(col, "_prepare_exc"):
+ six.reraise(*col._prepare_exc)
+ for col in needed_collectors[len(self.stack) :]:
+ self.stack.append(col)
+ try:
+ col.setup()
+ except TEST_OUTCOME:
+ col._prepare_exc = sys.exc_info()
+ raise
+
+
+def collect_one_node(collector):
+ ihook = collector.ihook
+ ihook.pytest_collectstart(collector=collector)
+ rep = ihook.pytest_make_collect_report(collector=collector)
+ call = rep.__dict__.pop("call", None)
+ if call and check_interactive_exception(call, rep):
+ ihook.pytest_exception_interact(node=collector, call=call, report=rep)
+ return rep
diff --git a/contrib/python/pytest/py2/_pytest/setuponly.py b/contrib/python/pytest/py2/_pytest/setuponly.py
index ee02bc0ceb..0859011241 100644
--- a/contrib/python/pytest/py2/_pytest/setuponly.py
+++ b/contrib/python/pytest/py2/_pytest/setuponly.py
@@ -1,89 +1,89 @@
# -*- coding: utf-8 -*-
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import sys
-
-import pytest
-
-
-def pytest_addoption(parser):
- group = parser.getgroup("debugconfig")
- group.addoption(
- "--setuponly",
- "--setup-only",
- action="store_true",
- help="only setup fixtures, do not execute tests.",
- )
- group.addoption(
- "--setupshow",
- "--setup-show",
- action="store_true",
- help="show setup of fixtures while executing tests.",
- )
-
-
-@pytest.hookimpl(hookwrapper=True)
-def pytest_fixture_setup(fixturedef, request):
- yield
- config = request.config
- if config.option.setupshow:
- if hasattr(request, "param"):
- # Save the fixture parameter so ._show_fixture_action() can
- # display it now and during the teardown (in .finish()).
- if fixturedef.ids:
- if callable(fixturedef.ids):
- fixturedef.cached_param = fixturedef.ids(request.param)
- else:
- fixturedef.cached_param = fixturedef.ids[request.param_index]
- else:
- fixturedef.cached_param = request.param
- _show_fixture_action(fixturedef, "SETUP")
-
-
-def pytest_fixture_post_finalizer(fixturedef):
- if hasattr(fixturedef, "cached_result"):
- config = fixturedef._fixturemanager.config
- if config.option.setupshow:
- _show_fixture_action(fixturedef, "TEARDOWN")
- if hasattr(fixturedef, "cached_param"):
- del fixturedef.cached_param
-
-
-def _show_fixture_action(fixturedef, msg):
- config = fixturedef._fixturemanager.config
- capman = config.pluginmanager.getplugin("capturemanager")
- if capman:
- capman.suspend_global_capture()
- out, err = capman.read_global_capture()
-
- tw = config.get_terminal_writer()
- tw.line()
- tw.write(" " * 2 * fixturedef.scopenum)
- tw.write(
- "{step} {scope} {fixture}".format(
- step=msg.ljust(8), # align the output to TEARDOWN
- scope=fixturedef.scope[0].upper(),
- fixture=fixturedef.argname,
- )
- )
-
- if msg == "SETUP":
- deps = sorted(arg for arg in fixturedef.argnames if arg != "request")
- if deps:
- tw.write(" (fixtures used: {})".format(", ".join(deps)))
-
- if hasattr(fixturedef, "cached_param"):
- tw.write("[{}]".format(fixturedef.cached_param))
-
- if capman:
- capman.resume_global_capture()
- sys.stdout.write(out)
- sys.stderr.write(err)
-
-
-@pytest.hookimpl(tryfirst=True)
-def pytest_cmdline_main(config):
- if config.option.setuponly:
- config.option.setupshow = True
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import sys
+
+import pytest
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("debugconfig")
+ group.addoption(
+ "--setuponly",
+ "--setup-only",
+ action="store_true",
+ help="only setup fixtures, do not execute tests.",
+ )
+ group.addoption(
+ "--setupshow",
+ "--setup-show",
+ action="store_true",
+ help="show setup of fixtures while executing tests.",
+ )
+
+
+@pytest.hookimpl(hookwrapper=True)
+def pytest_fixture_setup(fixturedef, request):
+ yield
+ config = request.config
+ if config.option.setupshow:
+ if hasattr(request, "param"):
+ # Save the fixture parameter so ._show_fixture_action() can
+ # display it now and during the teardown (in .finish()).
+ if fixturedef.ids:
+ if callable(fixturedef.ids):
+ fixturedef.cached_param = fixturedef.ids(request.param)
+ else:
+ fixturedef.cached_param = fixturedef.ids[request.param_index]
+ else:
+ fixturedef.cached_param = request.param
+ _show_fixture_action(fixturedef, "SETUP")
+
+
+def pytest_fixture_post_finalizer(fixturedef):
+ if hasattr(fixturedef, "cached_result"):
+ config = fixturedef._fixturemanager.config
+ if config.option.setupshow:
+ _show_fixture_action(fixturedef, "TEARDOWN")
+ if hasattr(fixturedef, "cached_param"):
+ del fixturedef.cached_param
+
+
+def _show_fixture_action(fixturedef, msg):
+ config = fixturedef._fixturemanager.config
+ capman = config.pluginmanager.getplugin("capturemanager")
+ if capman:
+ capman.suspend_global_capture()
+ out, err = capman.read_global_capture()
+
+ tw = config.get_terminal_writer()
+ tw.line()
+ tw.write(" " * 2 * fixturedef.scopenum)
+ tw.write(
+ "{step} {scope} {fixture}".format(
+ step=msg.ljust(8), # align the output to TEARDOWN
+ scope=fixturedef.scope[0].upper(),
+ fixture=fixturedef.argname,
+ )
+ )
+
+ if msg == "SETUP":
+ deps = sorted(arg for arg in fixturedef.argnames if arg != "request")
+ if deps:
+ tw.write(" (fixtures used: {})".format(", ".join(deps)))
+
+ if hasattr(fixturedef, "cached_param"):
+ tw.write("[{}]".format(fixturedef.cached_param))
+
+ if capman:
+ capman.resume_global_capture()
+ sys.stdout.write(out)
+ sys.stderr.write(err)
+
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_cmdline_main(config):
+ if config.option.setuponly:
+ config.option.setupshow = True
diff --git a/contrib/python/pytest/py2/_pytest/setupplan.py b/contrib/python/pytest/py2/_pytest/setupplan.py
index 0d10515da4..47b0fe82ef 100644
--- a/contrib/python/pytest/py2/_pytest/setupplan.py
+++ b/contrib/python/pytest/py2/_pytest/setupplan.py
@@ -1,32 +1,32 @@
# -*- coding: utf-8 -*-
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import pytest
-
-
-def pytest_addoption(parser):
- group = parser.getgroup("debugconfig")
- group.addoption(
- "--setupplan",
- "--setup-plan",
- action="store_true",
- help="show what fixtures and tests would be executed but "
- "don't execute anything.",
- )
-
-
-@pytest.hookimpl(tryfirst=True)
-def pytest_fixture_setup(fixturedef, request):
- # Will return a dummy fixture if the setuponly option is provided.
- if request.config.option.setupplan:
- fixturedef.cached_result = (None, None, None)
- return fixturedef.cached_result
-
-
-@pytest.hookimpl(tryfirst=True)
-def pytest_cmdline_main(config):
- if config.option.setupplan:
- config.option.setuponly = True
- config.option.setupshow = True
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import pytest
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("debugconfig")
+ group.addoption(
+ "--setupplan",
+ "--setup-plan",
+ action="store_true",
+ help="show what fixtures and tests would be executed but "
+ "don't execute anything.",
+ )
+
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_fixture_setup(fixturedef, request):
+ # Will return a dummy fixture if the setuponly option is provided.
+ if request.config.option.setupplan:
+ fixturedef.cached_result = (None, None, None)
+ return fixturedef.cached_result
+
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_cmdline_main(config):
+ if config.option.setupplan:
+ config.option.setuponly = True
+ config.option.setupshow = True
diff --git a/contrib/python/pytest/py2/_pytest/skipping.py b/contrib/python/pytest/py2/_pytest/skipping.py
index 03e233d8a2..bc8b88e717 100644
--- a/contrib/python/pytest/py2/_pytest/skipping.py
+++ b/contrib/python/pytest/py2/_pytest/skipping.py
@@ -1,186 +1,186 @@
# -*- coding: utf-8 -*-
-""" support for skip/xfail functions and markers. """
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-from _pytest.config import hookimpl
-from _pytest.mark.evaluate import MarkEvaluator
-from _pytest.outcomes import fail
-from _pytest.outcomes import skip
-from _pytest.outcomes import xfail
-
-
-def pytest_addoption(parser):
- group = parser.getgroup("general")
- group.addoption(
- "--runxfail",
- action="store_true",
- dest="runxfail",
- default=False,
+""" support for skip/xfail functions and markers. """
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+from _pytest.config import hookimpl
+from _pytest.mark.evaluate import MarkEvaluator
+from _pytest.outcomes import fail
+from _pytest.outcomes import skip
+from _pytest.outcomes import xfail
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("general")
+ group.addoption(
+ "--runxfail",
+ action="store_true",
+ dest="runxfail",
+ default=False,
help="report the results of xfail tests as if they were not marked",
- )
-
- parser.addini(
- "xfail_strict",
- "default for the strict parameter of xfail "
- "markers when not given explicitly (default: False)",
- default=False,
- type="bool",
- )
-
-
-def pytest_configure(config):
- if config.option.runxfail:
- # yay a hack
- import pytest
-
- old = pytest.xfail
- config._cleanup.append(lambda: setattr(pytest, "xfail", old))
-
- def nop(*args, **kwargs):
- pass
-
- nop.Exception = xfail.Exception
- setattr(pytest, "xfail", nop)
-
- config.addinivalue_line(
- "markers",
- "skip(reason=None): skip the given test function with an optional reason. "
- 'Example: skip(reason="no way of currently testing this") skips the '
- "test.",
- )
- config.addinivalue_line(
- "markers",
- "skipif(condition): skip the given test function if eval(condition) "
- "results in a True value. Evaluation happens within the "
- "module global context. Example: skipif('sys.platform == \"win32\"') "
- "skips the test if we are on the win32 platform. see "
- "https://docs.pytest.org/en/latest/skipping.html",
- )
- config.addinivalue_line(
- "markers",
- "xfail(condition, reason=None, run=True, raises=None, strict=False): "
- "mark the test function as an expected failure if eval(condition) "
- "has a True value. Optionally specify a reason for better reporting "
- "and run=False if you don't even want to execute the test function. "
- "If only specific exception(s) are expected, you can list them in "
- "raises, and if the test fails in other ways, it will be reported as "
- "a true failure. See https://docs.pytest.org/en/latest/skipping.html",
- )
-
-
-@hookimpl(tryfirst=True)
-def pytest_runtest_setup(item):
- # Check if skip or skipif are specified as pytest marks
- item._skipped_by_mark = False
- eval_skipif = MarkEvaluator(item, "skipif")
- if eval_skipif.istrue():
- item._skipped_by_mark = True
- skip(eval_skipif.getexplanation())
-
- for skip_info in item.iter_markers(name="skip"):
- item._skipped_by_mark = True
- if "reason" in skip_info.kwargs:
- skip(skip_info.kwargs["reason"])
- elif skip_info.args:
- skip(skip_info.args[0])
- else:
- skip("unconditional skip")
-
- item._evalxfail = MarkEvaluator(item, "xfail")
- check_xfail_no_run(item)
-
-
-@hookimpl(hookwrapper=True)
-def pytest_pyfunc_call(pyfuncitem):
- check_xfail_no_run(pyfuncitem)
- outcome = yield
- passed = outcome.excinfo is None
- if passed:
- check_strict_xfail(pyfuncitem)
-
-
-def check_xfail_no_run(item):
- """check xfail(run=False)"""
- if not item.config.option.runxfail:
- evalxfail = item._evalxfail
- if evalxfail.istrue():
- if not evalxfail.get("run", True):
- xfail("[NOTRUN] " + evalxfail.getexplanation())
-
-
-def check_strict_xfail(pyfuncitem):
- """check xfail(strict=True) for the given PASSING test"""
- evalxfail = pyfuncitem._evalxfail
- if evalxfail.istrue():
- strict_default = pyfuncitem.config.getini("xfail_strict")
- is_strict_xfail = evalxfail.get("strict", strict_default)
- if is_strict_xfail:
- del pyfuncitem._evalxfail
- explanation = evalxfail.getexplanation()
- fail("[XPASS(strict)] " + explanation, pytrace=False)
-
-
-@hookimpl(hookwrapper=True)
-def pytest_runtest_makereport(item, call):
- outcome = yield
- rep = outcome.get_result()
- evalxfail = getattr(item, "_evalxfail", None)
- # unitttest special case, see setting of _unexpectedsuccess
- if hasattr(item, "_unexpectedsuccess") and rep.when == "call":
- from _pytest.compat import _is_unittest_unexpected_success_a_failure
-
- if item._unexpectedsuccess:
- rep.longrepr = "Unexpected success: {}".format(item._unexpectedsuccess)
- else:
- rep.longrepr = "Unexpected success"
- if _is_unittest_unexpected_success_a_failure():
- rep.outcome = "failed"
- else:
- rep.outcome = "passed"
- rep.wasxfail = rep.longrepr
- elif item.config.option.runxfail:
- pass # don't interefere
- elif call.excinfo and call.excinfo.errisinstance(xfail.Exception):
- rep.wasxfail = "reason: " + call.excinfo.value.msg
- rep.outcome = "skipped"
- elif evalxfail and not rep.skipped and evalxfail.wasvalid() and evalxfail.istrue():
- if call.excinfo:
- if evalxfail.invalidraise(call.excinfo.value):
- rep.outcome = "failed"
- else:
- rep.outcome = "skipped"
- rep.wasxfail = evalxfail.getexplanation()
- elif call.when == "call":
- strict_default = item.config.getini("xfail_strict")
- is_strict_xfail = evalxfail.get("strict", strict_default)
- explanation = evalxfail.getexplanation()
- if is_strict_xfail:
- rep.outcome = "failed"
- rep.longrepr = "[XPASS(strict)] {}".format(explanation)
- else:
- rep.outcome = "passed"
- rep.wasxfail = explanation
- elif (
- getattr(item, "_skipped_by_mark", False)
- and rep.skipped
- and type(rep.longrepr) is tuple
- ):
- # skipped by mark.skipif; change the location of the failure
- # to point to the item definition, otherwise it will display
- # the location of where the skip exception was raised within pytest
- filename, line, reason = rep.longrepr
- filename, line = item.location[:2]
- rep.longrepr = filename, line, reason
-
-
-# called by terminalreporter progress reporting
-
-
-def pytest_report_teststatus(report):
- if hasattr(report, "wasxfail"):
- if report.skipped:
+ )
+
+ parser.addini(
+ "xfail_strict",
+ "default for the strict parameter of xfail "
+ "markers when not given explicitly (default: False)",
+ default=False,
+ type="bool",
+ )
+
+
+def pytest_configure(config):
+ if config.option.runxfail:
+ # yay a hack
+ import pytest
+
+ old = pytest.xfail
+ config._cleanup.append(lambda: setattr(pytest, "xfail", old))
+
+ def nop(*args, **kwargs):
+ pass
+
+ nop.Exception = xfail.Exception
+ setattr(pytest, "xfail", nop)
+
+ config.addinivalue_line(
+ "markers",
+ "skip(reason=None): skip the given test function with an optional reason. "
+ 'Example: skip(reason="no way of currently testing this") skips the '
+ "test.",
+ )
+ config.addinivalue_line(
+ "markers",
+ "skipif(condition): skip the given test function if eval(condition) "
+ "results in a True value. Evaluation happens within the "
+ "module global context. Example: skipif('sys.platform == \"win32\"') "
+ "skips the test if we are on the win32 platform. see "
+ "https://docs.pytest.org/en/latest/skipping.html",
+ )
+ config.addinivalue_line(
+ "markers",
+ "xfail(condition, reason=None, run=True, raises=None, strict=False): "
+ "mark the test function as an expected failure if eval(condition) "
+ "has a True value. Optionally specify a reason for better reporting "
+ "and run=False if you don't even want to execute the test function. "
+ "If only specific exception(s) are expected, you can list them in "
+ "raises, and if the test fails in other ways, it will be reported as "
+ "a true failure. See https://docs.pytest.org/en/latest/skipping.html",
+ )
+
+
+@hookimpl(tryfirst=True)
+def pytest_runtest_setup(item):
+ # Check if skip or skipif are specified as pytest marks
+ item._skipped_by_mark = False
+ eval_skipif = MarkEvaluator(item, "skipif")
+ if eval_skipif.istrue():
+ item._skipped_by_mark = True
+ skip(eval_skipif.getexplanation())
+
+ for skip_info in item.iter_markers(name="skip"):
+ item._skipped_by_mark = True
+ if "reason" in skip_info.kwargs:
+ skip(skip_info.kwargs["reason"])
+ elif skip_info.args:
+ skip(skip_info.args[0])
+ else:
+ skip("unconditional skip")
+
+ item._evalxfail = MarkEvaluator(item, "xfail")
+ check_xfail_no_run(item)
+
+
+@hookimpl(hookwrapper=True)
+def pytest_pyfunc_call(pyfuncitem):
+ check_xfail_no_run(pyfuncitem)
+ outcome = yield
+ passed = outcome.excinfo is None
+ if passed:
+ check_strict_xfail(pyfuncitem)
+
+
+def check_xfail_no_run(item):
+ """check xfail(run=False)"""
+ if not item.config.option.runxfail:
+ evalxfail = item._evalxfail
+ if evalxfail.istrue():
+ if not evalxfail.get("run", True):
+ xfail("[NOTRUN] " + evalxfail.getexplanation())
+
+
+def check_strict_xfail(pyfuncitem):
+ """check xfail(strict=True) for the given PASSING test"""
+ evalxfail = pyfuncitem._evalxfail
+ if evalxfail.istrue():
+ strict_default = pyfuncitem.config.getini("xfail_strict")
+ is_strict_xfail = evalxfail.get("strict", strict_default)
+ if is_strict_xfail:
+ del pyfuncitem._evalxfail
+ explanation = evalxfail.getexplanation()
+ fail("[XPASS(strict)] " + explanation, pytrace=False)
+
+
+@hookimpl(hookwrapper=True)
+def pytest_runtest_makereport(item, call):
+ outcome = yield
+ rep = outcome.get_result()
+ evalxfail = getattr(item, "_evalxfail", None)
+ # unitttest special case, see setting of _unexpectedsuccess
+ if hasattr(item, "_unexpectedsuccess") and rep.when == "call":
+ from _pytest.compat import _is_unittest_unexpected_success_a_failure
+
+ if item._unexpectedsuccess:
+ rep.longrepr = "Unexpected success: {}".format(item._unexpectedsuccess)
+ else:
+ rep.longrepr = "Unexpected success"
+ if _is_unittest_unexpected_success_a_failure():
+ rep.outcome = "failed"
+ else:
+ rep.outcome = "passed"
+ rep.wasxfail = rep.longrepr
+ elif item.config.option.runxfail:
+ pass # don't interefere
+ elif call.excinfo and call.excinfo.errisinstance(xfail.Exception):
+ rep.wasxfail = "reason: " + call.excinfo.value.msg
+ rep.outcome = "skipped"
+ elif evalxfail and not rep.skipped and evalxfail.wasvalid() and evalxfail.istrue():
+ if call.excinfo:
+ if evalxfail.invalidraise(call.excinfo.value):
+ rep.outcome = "failed"
+ else:
+ rep.outcome = "skipped"
+ rep.wasxfail = evalxfail.getexplanation()
+ elif call.when == "call":
+ strict_default = item.config.getini("xfail_strict")
+ is_strict_xfail = evalxfail.get("strict", strict_default)
+ explanation = evalxfail.getexplanation()
+ if is_strict_xfail:
+ rep.outcome = "failed"
+ rep.longrepr = "[XPASS(strict)] {}".format(explanation)
+ else:
+ rep.outcome = "passed"
+ rep.wasxfail = explanation
+ elif (
+ getattr(item, "_skipped_by_mark", False)
+ and rep.skipped
+ and type(rep.longrepr) is tuple
+ ):
+ # skipped by mark.skipif; change the location of the failure
+ # to point to the item definition, otherwise it will display
+ # the location of where the skip exception was raised within pytest
+ filename, line, reason = rep.longrepr
+ filename, line = item.location[:2]
+ rep.longrepr = filename, line, reason
+
+
+# called by terminalreporter progress reporting
+
+
+def pytest_report_teststatus(report):
+ if hasattr(report, "wasxfail"):
+ if report.skipped:
return "xfailed", "x", "XFAIL"
- elif report.passed:
+ elif report.passed:
return "xpassed", "X", "XPASS"
diff --git a/contrib/python/pytest/py2/_pytest/stepwise.py b/contrib/python/pytest/py2/_pytest/stepwise.py
index f18cb61085..8890259589 100644
--- a/contrib/python/pytest/py2/_pytest/stepwise.py
+++ b/contrib/python/pytest/py2/_pytest/stepwise.py
@@ -1,109 +1,109 @@
# -*- coding: utf-8 -*-
-import pytest
-
-
-def pytest_addoption(parser):
- group = parser.getgroup("general")
- group.addoption(
- "--sw",
- "--stepwise",
- action="store_true",
- dest="stepwise",
+import pytest
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("general")
+ group.addoption(
+ "--sw",
+ "--stepwise",
+ action="store_true",
+ dest="stepwise",
help="exit on test failure and continue from last failing test next time",
- )
- group.addoption(
- "--stepwise-skip",
- action="store_true",
- dest="stepwise_skip",
- help="ignore the first failing test but stop on the next failing test",
- )
-
-
-@pytest.hookimpl
-def pytest_configure(config):
- config.pluginmanager.register(StepwisePlugin(config), "stepwiseplugin")
-
-
-class StepwisePlugin:
- def __init__(self, config):
- self.config = config
- self.active = config.getvalue("stepwise")
- self.session = None
+ )
+ group.addoption(
+ "--stepwise-skip",
+ action="store_true",
+ dest="stepwise_skip",
+ help="ignore the first failing test but stop on the next failing test",
+ )
+
+
+@pytest.hookimpl
+def pytest_configure(config):
+ config.pluginmanager.register(StepwisePlugin(config), "stepwiseplugin")
+
+
+class StepwisePlugin:
+ def __init__(self, config):
+ self.config = config
+ self.active = config.getvalue("stepwise")
+ self.session = None
self.report_status = ""
-
- if self.active:
- self.lastfailed = config.cache.get("cache/stepwise", None)
- self.skip = config.getvalue("stepwise_skip")
-
- def pytest_sessionstart(self, session):
- self.session = session
-
- def pytest_collection_modifyitems(self, session, config, items):
+
+ if self.active:
+ self.lastfailed = config.cache.get("cache/stepwise", None)
+ self.skip = config.getvalue("stepwise_skip")
+
+ def pytest_sessionstart(self, session):
+ self.session = session
+
+ def pytest_collection_modifyitems(self, session, config, items):
if not self.active:
- return
+ return
if not self.lastfailed:
self.report_status = "no previously failed tests, not skipping."
return
-
- already_passed = []
- found = False
-
- # Make a list of all tests that have been run before the last failing one.
- for item in items:
- if item.nodeid == self.lastfailed:
- found = True
- break
- else:
- already_passed.append(item)
-
- # If the previously failed test was not found among the test items,
- # do not skip any tests.
- if not found:
+
+ already_passed = []
+ found = False
+
+ # Make a list of all tests that have been run before the last failing one.
+ for item in items:
+ if item.nodeid == self.lastfailed:
+ found = True
+ break
+ else:
+ already_passed.append(item)
+
+ # If the previously failed test was not found among the test items,
+ # do not skip any tests.
+ if not found:
self.report_status = "previously failed test not found, not skipping."
- already_passed = []
+ already_passed = []
else:
self.report_status = "skipping {} already passed items.".format(
len(already_passed)
)
-
- for item in already_passed:
- items.remove(item)
-
- config.hook.pytest_deselected(items=already_passed)
-
- def pytest_runtest_logreport(self, report):
+
+ for item in already_passed:
+ items.remove(item)
+
+ config.hook.pytest_deselected(items=already_passed)
+
+ def pytest_runtest_logreport(self, report):
if not self.active:
- return
-
- if report.failed:
- if self.skip:
- # Remove test from the failed ones (if it exists) and unset the skip option
- # to make sure the following tests will not be skipped.
- if report.nodeid == self.lastfailed:
- self.lastfailed = None
-
- self.skip = False
- else:
- # Mark test as the last failing and interrupt the test session.
- self.lastfailed = report.nodeid
- self.session.shouldstop = (
- "Test failed, continuing from this test next run."
- )
-
- else:
- # If the test was actually run and did pass.
- if report.when == "call":
- # Remove test from the failed ones, if exists.
- if report.nodeid == self.lastfailed:
- self.lastfailed = None
-
+ return
+
+ if report.failed:
+ if self.skip:
+ # Remove test from the failed ones (if it exists) and unset the skip option
+ # to make sure the following tests will not be skipped.
+ if report.nodeid == self.lastfailed:
+ self.lastfailed = None
+
+ self.skip = False
+ else:
+ # Mark test as the last failing and interrupt the test session.
+ self.lastfailed = report.nodeid
+ self.session.shouldstop = (
+ "Test failed, continuing from this test next run."
+ )
+
+ else:
+ # If the test was actually run and did pass.
+ if report.when == "call":
+ # Remove test from the failed ones, if exists.
+ if report.nodeid == self.lastfailed:
+ self.lastfailed = None
+
def pytest_report_collectionfinish(self):
if self.active and self.config.getoption("verbose") >= 0 and self.report_status:
return "stepwise: %s" % self.report_status
- def pytest_sessionfinish(self, session):
- if self.active:
- self.config.cache.set("cache/stepwise", self.lastfailed)
- else:
- # Clear the list of failing tests if the plugin is not active.
- self.config.cache.set("cache/stepwise", [])
+ def pytest_sessionfinish(self, session):
+ if self.active:
+ self.config.cache.set("cache/stepwise", self.lastfailed)
+ else:
+ # Clear the list of failing tests if the plugin is not active.
+ self.config.cache.set("cache/stepwise", [])
diff --git a/contrib/python/pytest/py2/_pytest/terminal.py b/contrib/python/pytest/py2/_pytest/terminal.py
index 61163d2e51..4418338c65 100644
--- a/contrib/python/pytest/py2/_pytest/terminal.py
+++ b/contrib/python/pytest/py2/_pytest/terminal.py
@@ -1,171 +1,171 @@
# -*- coding: utf-8 -*-
-""" terminal reporting of the full testing process.
-
-This is a good source for looking at the various reporting hooks.
-"""
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import argparse
+""" terminal reporting of the full testing process.
+
+This is a good source for looking at the various reporting hooks.
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import argparse
import collections
-import platform
-import sys
-import time
+import platform
+import sys
+import time
from functools import partial
-
-import attr
-import pluggy
-import py
-import six
-from more_itertools import collapse
-
-import pytest
-from _pytest import nodes
-from _pytest.main import EXIT_INTERRUPTED
-from _pytest.main import EXIT_NOTESTSCOLLECTED
-from _pytest.main import EXIT_OK
-from _pytest.main import EXIT_TESTSFAILED
-from _pytest.main import EXIT_USAGEERROR
-
+
+import attr
+import pluggy
+import py
+import six
+from more_itertools import collapse
+
+import pytest
+from _pytest import nodes
+from _pytest.main import EXIT_INTERRUPTED
+from _pytest.main import EXIT_NOTESTSCOLLECTED
+from _pytest.main import EXIT_OK
+from _pytest.main import EXIT_TESTSFAILED
+from _pytest.main import EXIT_USAGEERROR
+
REPORT_COLLECTING_RESOLUTION = 0.5
-
-
-class MoreQuietAction(argparse.Action):
- """
- a modified copy of the argparse count action which counts down and updates
- the legacy quiet attribute at the same time
-
- used to unify verbosity handling
- """
-
- def __init__(self, option_strings, dest, default=None, required=False, help=None):
- super(MoreQuietAction, self).__init__(
- option_strings=option_strings,
- dest=dest,
- nargs=0,
- default=default,
- required=required,
- help=help,
- )
-
- def __call__(self, parser, namespace, values, option_string=None):
- new_count = getattr(namespace, self.dest, 0) - 1
- setattr(namespace, self.dest, new_count)
- # todo Deprecate config.quiet
- namespace.quiet = getattr(namespace, "quiet", 0) + 1
-
-
-def pytest_addoption(parser):
- group = parser.getgroup("terminal reporting", "reporting", after="general")
- group._addoption(
- "-v",
- "--verbose",
- action="count",
- default=0,
- dest="verbose",
- help="increase verbosity.",
- ),
- group._addoption(
- "-q",
- "--quiet",
- action=MoreQuietAction,
- default=0,
- dest="verbose",
- help="decrease verbosity.",
- ),
- group._addoption(
- "--verbosity", dest="verbose", type=int, default=0, help="set verbosity"
- )
- group._addoption(
- "-r",
- action="store",
- dest="reportchars",
- default="",
- metavar="chars",
+
+
+class MoreQuietAction(argparse.Action):
+ """
+ a modified copy of the argparse count action which counts down and updates
+ the legacy quiet attribute at the same time
+
+ used to unify verbosity handling
+ """
+
+ def __init__(self, option_strings, dest, default=None, required=False, help=None):
+ super(MoreQuietAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ nargs=0,
+ default=default,
+ required=required,
+ help=help,
+ )
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ new_count = getattr(namespace, self.dest, 0) - 1
+ setattr(namespace, self.dest, new_count)
+ # todo Deprecate config.quiet
+ namespace.quiet = getattr(namespace, "quiet", 0) + 1
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("terminal reporting", "reporting", after="general")
+ group._addoption(
+ "-v",
+ "--verbose",
+ action="count",
+ default=0,
+ dest="verbose",
+ help="increase verbosity.",
+ ),
+ group._addoption(
+ "-q",
+ "--quiet",
+ action=MoreQuietAction,
+ default=0,
+ dest="verbose",
+ help="decrease verbosity.",
+ ),
+ group._addoption(
+ "--verbosity", dest="verbose", type=int, default=0, help="set verbosity"
+ )
+ group._addoption(
+ "-r",
+ action="store",
+ dest="reportchars",
+ default="",
+ metavar="chars",
help="show extra test summary info as specified by chars: (f)ailed, "
"(E)rror, (s)kipped, (x)failed, (X)passed, "
"(p)assed, (P)assed with output, (a)ll except passed (p/P), or (A)ll. "
- "Warnings are displayed at all times except when "
+ "Warnings are displayed at all times except when "
"--disable-warnings is set.",
- )
- group._addoption(
- "--disable-warnings",
- "--disable-pytest-warnings",
- default=False,
- dest="disable_warnings",
- action="store_true",
- help="disable warnings summary",
- )
- group._addoption(
- "-l",
- "--showlocals",
- action="store_true",
- dest="showlocals",
- default=False,
- help="show locals in tracebacks (disabled by default).",
- )
- group._addoption(
- "--tb",
- metavar="style",
- action="store",
- dest="tbstyle",
- default="auto",
- choices=["auto", "long", "short", "no", "line", "native"],
- help="traceback print mode (auto/long/short/line/native/no).",
- )
- group._addoption(
- "--show-capture",
- action="store",
- dest="showcapture",
- choices=["no", "stdout", "stderr", "log", "all"],
- default="all",
- help="Controls how captured stdout/stderr/log is shown on failed tests. "
- "Default is 'all'.",
- )
- group._addoption(
- "--fulltrace",
- "--full-trace",
- action="store_true",
- default=False,
- help="don't cut any tracebacks (default is to cut).",
- )
- group._addoption(
- "--color",
- metavar="color",
- action="store",
- dest="color",
- default="auto",
- choices=["yes", "no", "auto"],
- help="color terminal output (yes/no/auto).",
- )
-
- parser.addini(
- "console_output_style",
+ )
+ group._addoption(
+ "--disable-warnings",
+ "--disable-pytest-warnings",
+ default=False,
+ dest="disable_warnings",
+ action="store_true",
+ help="disable warnings summary",
+ )
+ group._addoption(
+ "-l",
+ "--showlocals",
+ action="store_true",
+ dest="showlocals",
+ default=False,
+ help="show locals in tracebacks (disabled by default).",
+ )
+ group._addoption(
+ "--tb",
+ metavar="style",
+ action="store",
+ dest="tbstyle",
+ default="auto",
+ choices=["auto", "long", "short", "no", "line", "native"],
+ help="traceback print mode (auto/long/short/line/native/no).",
+ )
+ group._addoption(
+ "--show-capture",
+ action="store",
+ dest="showcapture",
+ choices=["no", "stdout", "stderr", "log", "all"],
+ default="all",
+ help="Controls how captured stdout/stderr/log is shown on failed tests. "
+ "Default is 'all'.",
+ )
+ group._addoption(
+ "--fulltrace",
+ "--full-trace",
+ action="store_true",
+ default=False,
+ help="don't cut any tracebacks (default is to cut).",
+ )
+ group._addoption(
+ "--color",
+ metavar="color",
+ action="store",
+ dest="color",
+ default="auto",
+ choices=["yes", "no", "auto"],
+ help="color terminal output (yes/no/auto).",
+ )
+
+ parser.addini(
+ "console_output_style",
help='console output: "classic", or with additional progress information ("progress" (percentage) | "count").',
- default="progress",
- )
-
-
-def pytest_configure(config):
- reporter = TerminalReporter(config, sys.stdout)
- config.pluginmanager.register(reporter, "terminalreporter")
- if config.option.debug or config.option.traceconfig:
-
- def mywriter(tags, args):
- msg = " ".join(map(str, args))
- reporter.write_line("[traceconfig] " + msg)
-
- config.trace.root.setprocessor("pytest:config", mywriter)
-
-
-def getreportopt(config):
- reportopts = ""
- reportchars = config.option.reportchars
- if not config.option.disable_warnings and "w" not in reportchars:
- reportchars += "w"
- elif config.option.disable_warnings and "w" in reportchars:
- reportchars = reportchars.replace("w", "")
+ default="progress",
+ )
+
+
+def pytest_configure(config):
+ reporter = TerminalReporter(config, sys.stdout)
+ config.pluginmanager.register(reporter, "terminalreporter")
+ if config.option.debug or config.option.traceconfig:
+
+ def mywriter(tags, args):
+ msg = " ".join(map(str, args))
+ reporter.write_line("[traceconfig] " + msg)
+
+ config.trace.root.setprocessor("pytest:config", mywriter)
+
+
+def getreportopt(config):
+ reportopts = ""
+ reportchars = config.option.reportchars
+ if not config.option.disable_warnings and "w" not in reportchars:
+ reportchars += "w"
+ elif config.option.disable_warnings and "w" in reportchars:
+ reportchars = reportchars.replace("w", "")
aliases = {"F", "S"}
for char in reportchars:
# handle old aliases
@@ -178,98 +178,98 @@ def getreportopt(config):
break
elif char not in reportopts:
reportopts += char
- return reportopts
-
-
+ return reportopts
+
+
@pytest.hookimpl(trylast=True) # after _pytest.runner
-def pytest_report_teststatus(report):
+def pytest_report_teststatus(report):
letter = "F"
- if report.passed:
- letter = "."
- elif report.skipped:
- letter = "s"
-
+ if report.passed:
+ letter = "."
+ elif report.skipped:
+ letter = "s"
+
outcome = report.outcome
if report.when in ("collect", "setup", "teardown") and outcome == "failed":
outcome = "error"
letter = "E"
-
+
return outcome, letter, outcome.upper()
-@attr.s
-class WarningReport(object):
- """
+@attr.s
+class WarningReport(object):
+ """
Simple structure to hold warnings information captured by ``pytest_warning_captured``.
-
- :ivar str message: user friendly message about the warning
- :ivar str|None nodeid: node id that generated the warning (see ``get_location``).
- :ivar tuple|py.path.local fslocation:
- file system location of the source of the warning (see ``get_location``).
- """
-
- message = attr.ib()
- nodeid = attr.ib(default=None)
- fslocation = attr.ib(default=None)
+
+ :ivar str message: user friendly message about the warning
+ :ivar str|None nodeid: node id that generated the warning (see ``get_location``).
+ :ivar tuple|py.path.local fslocation:
+ file system location of the source of the warning (see ``get_location``).
+ """
+
+ message = attr.ib()
+ nodeid = attr.ib(default=None)
+ fslocation = attr.ib(default=None)
count_towards_summary = True
-
- def get_location(self, config):
- """
- Returns the more user-friendly information about the location
- of a warning, or None.
- """
- if self.nodeid:
- return self.nodeid
- if self.fslocation:
- if isinstance(self.fslocation, tuple) and len(self.fslocation) >= 2:
- filename, linenum = self.fslocation[:2]
- relpath = py.path.local(filename).relto(config.invocation_dir)
- if not relpath:
- relpath = str(filename)
- return "%s:%s" % (relpath, linenum)
- else:
- return str(self.fslocation)
- return None
-
-
-class TerminalReporter(object):
- def __init__(self, config, file=None):
- import _pytest.config
-
- self.config = config
- self._numcollected = 0
- self._session = None
+
+ def get_location(self, config):
+ """
+ Returns the more user-friendly information about the location
+ of a warning, or None.
+ """
+ if self.nodeid:
+ return self.nodeid
+ if self.fslocation:
+ if isinstance(self.fslocation, tuple) and len(self.fslocation) >= 2:
+ filename, linenum = self.fslocation[:2]
+ relpath = py.path.local(filename).relto(config.invocation_dir)
+ if not relpath:
+ relpath = str(filename)
+ return "%s:%s" % (relpath, linenum)
+ else:
+ return str(self.fslocation)
+ return None
+
+
+class TerminalReporter(object):
+ def __init__(self, config, file=None):
+ import _pytest.config
+
+ self.config = config
+ self._numcollected = 0
+ self._session = None
self._showfspath = None
-
- self.stats = {}
+
+ self.stats = {}
self.startdir = config.invocation_dir
- if file is None:
- file = sys.stdout
- self._tw = _pytest.config.create_terminal_writer(config, file)
- # self.writer will be deprecated in pytest-3.4
- self.writer = self._tw
- self._screen_width = self._tw.fullwidth
- self.currentfspath = None
- self.reportchars = getreportopt(config)
- self.hasmarkup = self._tw.hasmarkup
- self.isatty = file.isatty()
- self._progress_nodeids_reported = set()
- self._show_progress_info = self._determine_show_progress_info()
- self._collect_report_last_write = None
-
- def _determine_show_progress_info(self):
- """Return True if we should display progress information based on the current config"""
- # do not show progress if we are not capturing output (#3038)
+ if file is None:
+ file = sys.stdout
+ self._tw = _pytest.config.create_terminal_writer(config, file)
+ # self.writer will be deprecated in pytest-3.4
+ self.writer = self._tw
+ self._screen_width = self._tw.fullwidth
+ self.currentfspath = None
+ self.reportchars = getreportopt(config)
+ self.hasmarkup = self._tw.hasmarkup
+ self.isatty = file.isatty()
+ self._progress_nodeids_reported = set()
+ self._show_progress_info = self._determine_show_progress_info()
+ self._collect_report_last_write = None
+
+ def _determine_show_progress_info(self):
+ """Return True if we should display progress information based on the current config"""
+ # do not show progress if we are not capturing output (#3038)
if self.config.getoption("capture", "no") == "no":
- return False
- # do not show progress if we are showing fixture setup/teardown
+ return False
+ # do not show progress if we are showing fixture setup/teardown
if self.config.getoption("setupshow", False):
- return False
+ return False
cfg = self.config.getini("console_output_style")
if cfg in ("progress", "count"):
return cfg
return False
-
+
@property
def verbosity(self):
return self.config.option.verbose
@@ -292,503 +292,503 @@ class TerminalReporter(object):
def showlongtestinfo(self):
return self.verbosity > 0
- def hasopt(self, char):
- char = {"xfailed": "x", "skipped": "s"}.get(char, char)
- return char in self.reportchars
-
- def write_fspath_result(self, nodeid, res, **markup):
- fspath = self.config.rootdir.join(nodeid.split("::")[0])
+ def hasopt(self, char):
+ char = {"xfailed": "x", "skipped": "s"}.get(char, char)
+ return char in self.reportchars
+
+ def write_fspath_result(self, nodeid, res, **markup):
+ fspath = self.config.rootdir.join(nodeid.split("::")[0])
# NOTE: explicitly check for None to work around py bug, and for less
# overhead in general (https://github.com/pytest-dev/py/pull/207).
if self.currentfspath is None or fspath != self.currentfspath:
- if self.currentfspath is not None and self._show_progress_info:
- self._write_progress_information_filling_space()
- self.currentfspath = fspath
- fspath = self.startdir.bestrelpath(fspath)
- self._tw.line()
- self._tw.write(fspath + " ")
- self._tw.write(res, **markup)
-
- def write_ensure_prefix(self, prefix, extra="", **kwargs):
- if self.currentfspath != prefix:
- self._tw.line()
- self.currentfspath = prefix
- self._tw.write(prefix)
- if extra:
- self._tw.write(extra, **kwargs)
- self.currentfspath = -2
-
- def ensure_newline(self):
- if self.currentfspath:
- self._tw.line()
- self.currentfspath = None
-
- def write(self, content, **markup):
- self._tw.write(content, **markup)
-
- def write_line(self, line, **markup):
- if not isinstance(line, six.text_type):
- line = six.text_type(line, errors="replace")
- self.ensure_newline()
- self._tw.line(line, **markup)
-
- def rewrite(self, line, **markup):
- """
- Rewinds the terminal cursor to the beginning and writes the given line.
-
- :kwarg erase: if True, will also add spaces until the full terminal width to ensure
- previous lines are properly erased.
-
- The rest of the keyword arguments are markup instructions.
- """
- erase = markup.pop("erase", False)
- if erase:
- fill_count = self._tw.fullwidth - len(line) - 1
- fill = " " * fill_count
- else:
- fill = ""
- line = str(line)
- self._tw.write("\r" + line + fill, **markup)
-
- def write_sep(self, sep, title=None, **markup):
- self.ensure_newline()
- self._tw.sep(sep, title, **markup)
-
- def section(self, title, sep="=", **kw):
- self._tw.sep(sep, title, **kw)
-
- def line(self, msg, **kw):
- self._tw.line(msg, **kw)
-
- def pytest_internalerror(self, excrepr):
- for line in six.text_type(excrepr).split("\n"):
- self.write_line("INTERNALERROR> " + line)
- return 1
-
- def pytest_warning_captured(self, warning_message, item):
- # from _pytest.nodes import get_fslocation_from_item
- from _pytest.warnings import warning_record_to_str
-
- warnings = self.stats.setdefault("warnings", [])
- fslocation = warning_message.filename, warning_message.lineno
- message = warning_record_to_str(warning_message)
-
- nodeid = item.nodeid if item is not None else ""
- warning_report = WarningReport(
- fslocation=fslocation, message=message, nodeid=nodeid
- )
- warnings.append(warning_report)
-
- def pytest_plugin_registered(self, plugin):
- if self.config.option.traceconfig:
- msg = "PLUGIN registered: %s" % (plugin,)
- # XXX this event may happen during setup/teardown time
- # which unfortunately captures our output here
- # which garbles our output if we use self.write_line
- self.write_line(msg)
-
- def pytest_deselected(self, items):
- self.stats.setdefault("deselected", []).extend(items)
-
- def pytest_runtest_logstart(self, nodeid, location):
- # ensure that the path is printed before the
- # 1st test of a module starts running
- if self.showlongtestinfo:
- line = self._locationline(nodeid, *location)
- self.write_ensure_prefix(line, "")
- elif self.showfspath:
- fsid = nodeid.split("::")[0]
- self.write_fspath_result(fsid, "")
-
- def pytest_runtest_logreport(self, report):
+ if self.currentfspath is not None and self._show_progress_info:
+ self._write_progress_information_filling_space()
+ self.currentfspath = fspath
+ fspath = self.startdir.bestrelpath(fspath)
+ self._tw.line()
+ self._tw.write(fspath + " ")
+ self._tw.write(res, **markup)
+
+ def write_ensure_prefix(self, prefix, extra="", **kwargs):
+ if self.currentfspath != prefix:
+ self._tw.line()
+ self.currentfspath = prefix
+ self._tw.write(prefix)
+ if extra:
+ self._tw.write(extra, **kwargs)
+ self.currentfspath = -2
+
+ def ensure_newline(self):
+ if self.currentfspath:
+ self._tw.line()
+ self.currentfspath = None
+
+ def write(self, content, **markup):
+ self._tw.write(content, **markup)
+
+ def write_line(self, line, **markup):
+ if not isinstance(line, six.text_type):
+ line = six.text_type(line, errors="replace")
+ self.ensure_newline()
+ self._tw.line(line, **markup)
+
+ def rewrite(self, line, **markup):
+ """
+ Rewinds the terminal cursor to the beginning and writes the given line.
+
+ :kwarg erase: if True, will also add spaces until the full terminal width to ensure
+ previous lines are properly erased.
+
+ The rest of the keyword arguments are markup instructions.
+ """
+ erase = markup.pop("erase", False)
+ if erase:
+ fill_count = self._tw.fullwidth - len(line) - 1
+ fill = " " * fill_count
+ else:
+ fill = ""
+ line = str(line)
+ self._tw.write("\r" + line + fill, **markup)
+
+ def write_sep(self, sep, title=None, **markup):
+ self.ensure_newline()
+ self._tw.sep(sep, title, **markup)
+
+ def section(self, title, sep="=", **kw):
+ self._tw.sep(sep, title, **kw)
+
+ def line(self, msg, **kw):
+ self._tw.line(msg, **kw)
+
+ def pytest_internalerror(self, excrepr):
+ for line in six.text_type(excrepr).split("\n"):
+ self.write_line("INTERNALERROR> " + line)
+ return 1
+
+ def pytest_warning_captured(self, warning_message, item):
+ # from _pytest.nodes import get_fslocation_from_item
+ from _pytest.warnings import warning_record_to_str
+
+ warnings = self.stats.setdefault("warnings", [])
+ fslocation = warning_message.filename, warning_message.lineno
+ message = warning_record_to_str(warning_message)
+
+ nodeid = item.nodeid if item is not None else ""
+ warning_report = WarningReport(
+ fslocation=fslocation, message=message, nodeid=nodeid
+ )
+ warnings.append(warning_report)
+
+ def pytest_plugin_registered(self, plugin):
+ if self.config.option.traceconfig:
+ msg = "PLUGIN registered: %s" % (plugin,)
+ # XXX this event may happen during setup/teardown time
+ # which unfortunately captures our output here
+ # which garbles our output if we use self.write_line
+ self.write_line(msg)
+
+ def pytest_deselected(self, items):
+ self.stats.setdefault("deselected", []).extend(items)
+
+ def pytest_runtest_logstart(self, nodeid, location):
+ # ensure that the path is printed before the
+ # 1st test of a module starts running
+ if self.showlongtestinfo:
+ line = self._locationline(nodeid, *location)
+ self.write_ensure_prefix(line, "")
+ elif self.showfspath:
+ fsid = nodeid.split("::")[0]
+ self.write_fspath_result(fsid, "")
+
+ def pytest_runtest_logreport(self, report):
self._tests_ran = True
- rep = report
+ rep = report
res = self.config.hook.pytest_report_teststatus(report=rep, config=self.config)
- category, letter, word = res
- if isinstance(word, tuple):
- word, markup = word
- else:
- markup = None
- self.stats.setdefault(category, []).append(rep)
- if not letter and not word:
- # probably passed setup/teardown
- return
- running_xdist = hasattr(rep, "node")
- if markup is None:
+ category, letter, word = res
+ if isinstance(word, tuple):
+ word, markup = word
+ else:
+ markup = None
+ self.stats.setdefault(category, []).append(rep)
+ if not letter and not word:
+ # probably passed setup/teardown
+ return
+ running_xdist = hasattr(rep, "node")
+ if markup is None:
was_xfail = hasattr(report, "wasxfail")
if rep.passed and not was_xfail:
- markup = {"green": True}
+ markup = {"green": True}
elif rep.passed and was_xfail:
markup = {"yellow": True}
- elif rep.failed:
- markup = {"red": True}
- elif rep.skipped:
- markup = {"yellow": True}
- else:
- markup = {}
- if self.verbosity <= 0:
- if not running_xdist and self.showfspath:
- self.write_fspath_result(rep.nodeid, letter, **markup)
- else:
- self._tw.write(letter, **markup)
- else:
- self._progress_nodeids_reported.add(rep.nodeid)
- line = self._locationline(rep.nodeid, *rep.location)
- if not running_xdist:
- self.write_ensure_prefix(line, word, **markup)
- if self._show_progress_info:
- self._write_progress_information_filling_space()
- else:
- self.ensure_newline()
- self._tw.write("[%s]" % rep.node.gateway.id)
- if self._show_progress_info:
- self._tw.write(
- self._get_progress_information_message() + " ", cyan=True
- )
- else:
- self._tw.write(" ")
- self._tw.write(word, **markup)
- self._tw.write(" " + line)
- self.currentfspath = -2
-
- def pytest_runtest_logfinish(self, nodeid):
+ elif rep.failed:
+ markup = {"red": True}
+ elif rep.skipped:
+ markup = {"yellow": True}
+ else:
+ markup = {}
+ if self.verbosity <= 0:
+ if not running_xdist and self.showfspath:
+ self.write_fspath_result(rep.nodeid, letter, **markup)
+ else:
+ self._tw.write(letter, **markup)
+ else:
+ self._progress_nodeids_reported.add(rep.nodeid)
+ line = self._locationline(rep.nodeid, *rep.location)
+ if not running_xdist:
+ self.write_ensure_prefix(line, word, **markup)
+ if self._show_progress_info:
+ self._write_progress_information_filling_space()
+ else:
+ self.ensure_newline()
+ self._tw.write("[%s]" % rep.node.gateway.id)
+ if self._show_progress_info:
+ self._tw.write(
+ self._get_progress_information_message() + " ", cyan=True
+ )
+ else:
+ self._tw.write(" ")
+ self._tw.write(word, **markup)
+ self._tw.write(" " + line)
+ self.currentfspath = -2
+
+ def pytest_runtest_logfinish(self, nodeid):
if self.verbosity <= 0 and self._show_progress_info:
if self._show_progress_info == "count":
num_tests = self._session.testscollected
progress_length = len(" [{}/{}]".format(str(num_tests), str(num_tests)))
else:
progress_length = len(" [100%]")
-
- self._progress_nodeids_reported.add(nodeid)
+
+ self._progress_nodeids_reported.add(nodeid)
is_last_item = (
- len(self._progress_nodeids_reported) == self._session.testscollected
- )
+ len(self._progress_nodeids_reported) == self._session.testscollected
+ )
if is_last_item:
- self._write_progress_information_filling_space()
- else:
- w = self._width_of_current_line
- past_edge = w + progress_length + 1 >= self._screen_width
- if past_edge:
- msg = self._get_progress_information_message()
- self._tw.write(msg + "\n", cyan=True)
-
- def _get_progress_information_message(self):
- collected = self._session.testscollected
+ self._write_progress_information_filling_space()
+ else:
+ w = self._width_of_current_line
+ past_edge = w + progress_length + 1 >= self._screen_width
+ if past_edge:
+ msg = self._get_progress_information_message()
+ self._tw.write(msg + "\n", cyan=True)
+
+ def _get_progress_information_message(self):
+ collected = self._session.testscollected
if self._show_progress_info == "count":
- if collected:
- progress = self._progress_nodeids_reported
- counter_format = "{{:{}d}}".format(len(str(collected)))
- format_string = " [{}/{{}}]".format(counter_format)
- return format_string.format(len(progress), collected)
- return " [ {} / {} ]".format(collected, collected)
- else:
- if collected:
- progress = len(self._progress_nodeids_reported) * 100 // collected
- return " [{:3d}%]".format(progress)
- return " [100%]"
-
- def _write_progress_information_filling_space(self):
- msg = self._get_progress_information_message()
- w = self._width_of_current_line
- fill = self._tw.fullwidth - w - 1
- self.write(msg.rjust(fill), cyan=True)
-
- @property
- def _width_of_current_line(self):
- """Return the width of current line, using the superior implementation of py-1.6 when available"""
- try:
- return self._tw.width_of_current_line
- except AttributeError:
- # py < 1.6.0
- return self._tw.chars_on_current_line
-
- def pytest_collection(self):
- if self.isatty:
- if self.config.option.verbose >= 0:
- self.write("collecting ... ", bold=True)
- self._collect_report_last_write = time.time()
- elif self.config.option.verbose >= 1:
- self.write("collecting ... ", bold=True)
-
- def pytest_collectreport(self, report):
- if report.failed:
- self.stats.setdefault("error", []).append(report)
- elif report.skipped:
- self.stats.setdefault("skipped", []).append(report)
- items = [x for x in report.result if isinstance(x, pytest.Item)]
- self._numcollected += len(items)
- if self.isatty:
- self.report_collect()
-
- def report_collect(self, final=False):
- if self.config.option.verbose < 0:
- return
-
- if not final:
- # Only write "collecting" report every 0.5s.
- t = time.time()
- if (
- self._collect_report_last_write is not None
+ if collected:
+ progress = self._progress_nodeids_reported
+ counter_format = "{{:{}d}}".format(len(str(collected)))
+ format_string = " [{}/{{}}]".format(counter_format)
+ return format_string.format(len(progress), collected)
+ return " [ {} / {} ]".format(collected, collected)
+ else:
+ if collected:
+ progress = len(self._progress_nodeids_reported) * 100 // collected
+ return " [{:3d}%]".format(progress)
+ return " [100%]"
+
+ def _write_progress_information_filling_space(self):
+ msg = self._get_progress_information_message()
+ w = self._width_of_current_line
+ fill = self._tw.fullwidth - w - 1
+ self.write(msg.rjust(fill), cyan=True)
+
+ @property
+ def _width_of_current_line(self):
+ """Return the width of current line, using the superior implementation of py-1.6 when available"""
+ try:
+ return self._tw.width_of_current_line
+ except AttributeError:
+ # py < 1.6.0
+ return self._tw.chars_on_current_line
+
+ def pytest_collection(self):
+ if self.isatty:
+ if self.config.option.verbose >= 0:
+ self.write("collecting ... ", bold=True)
+ self._collect_report_last_write = time.time()
+ elif self.config.option.verbose >= 1:
+ self.write("collecting ... ", bold=True)
+
+ def pytest_collectreport(self, report):
+ if report.failed:
+ self.stats.setdefault("error", []).append(report)
+ elif report.skipped:
+ self.stats.setdefault("skipped", []).append(report)
+ items = [x for x in report.result if isinstance(x, pytest.Item)]
+ self._numcollected += len(items)
+ if self.isatty:
+ self.report_collect()
+
+ def report_collect(self, final=False):
+ if self.config.option.verbose < 0:
+ return
+
+ if not final:
+ # Only write "collecting" report every 0.5s.
+ t = time.time()
+ if (
+ self._collect_report_last_write is not None
and self._collect_report_last_write > t - REPORT_COLLECTING_RESOLUTION
- ):
- return
- self._collect_report_last_write = t
-
- errors = len(self.stats.get("error", []))
- skipped = len(self.stats.get("skipped", []))
- deselected = len(self.stats.get("deselected", []))
+ ):
+ return
+ self._collect_report_last_write = t
+
+ errors = len(self.stats.get("error", []))
+ skipped = len(self.stats.get("skipped", []))
+ deselected = len(self.stats.get("deselected", []))
selected = self._numcollected - errors - skipped - deselected
- if final:
- line = "collected "
- else:
- line = "collecting "
- line += (
- str(self._numcollected) + " item" + ("" if self._numcollected == 1 else "s")
- )
- if errors:
- line += " / %d errors" % errors
- if deselected:
- line += " / %d deselected" % deselected
- if skipped:
- line += " / %d skipped" % skipped
+ if final:
+ line = "collected "
+ else:
+ line = "collecting "
+ line += (
+ str(self._numcollected) + " item" + ("" if self._numcollected == 1 else "s")
+ )
+ if errors:
+ line += " / %d errors" % errors
+ if deselected:
+ line += " / %d deselected" % deselected
+ if skipped:
+ line += " / %d skipped" % skipped
if self._numcollected > selected > 0:
line += " / %d selected" % selected
- if self.isatty:
- self.rewrite(line, bold=True, erase=True)
- if final:
- self.write("\n")
- else:
- self.write_line(line)
-
- @pytest.hookimpl(trylast=True)
- def pytest_sessionstart(self, session):
- self._session = session
- self._sessionstarttime = time.time()
- if not self.showheader:
- return
- self.write_sep("=", "test session starts", bold=True)
- verinfo = platform.python_version()
- msg = "platform %s -- Python %s" % (sys.platform, verinfo)
- if hasattr(sys, "pypy_version_info"):
- verinfo = ".".join(map(str, sys.pypy_version_info[:3]))
- msg += "[pypy-%s-%s]" % (verinfo, sys.pypy_version_info[3])
- msg += ", pytest-%s, py-%s, pluggy-%s" % (
- pytest.__version__,
- py.__version__,
- pluggy.__version__,
- )
- if (
- self.verbosity > 0
- or self.config.option.debug
- or getattr(self.config.option, "pastebin", None)
- ):
- msg += " -- " + str(sys.executable)
- self.write_line(msg)
- lines = self.config.hook.pytest_report_header(
- config=self.config, startdir=self.startdir
- )
- self._write_report_lines_from_hooks(lines)
-
- def _write_report_lines_from_hooks(self, lines):
- lines.reverse()
- for line in collapse(lines):
- self.write_line(line)
-
- def pytest_report_header(self, config):
+ if self.isatty:
+ self.rewrite(line, bold=True, erase=True)
+ if final:
+ self.write("\n")
+ else:
+ self.write_line(line)
+
+ @pytest.hookimpl(trylast=True)
+ def pytest_sessionstart(self, session):
+ self._session = session
+ self._sessionstarttime = time.time()
+ if not self.showheader:
+ return
+ self.write_sep("=", "test session starts", bold=True)
+ verinfo = platform.python_version()
+ msg = "platform %s -- Python %s" % (sys.platform, verinfo)
+ if hasattr(sys, "pypy_version_info"):
+ verinfo = ".".join(map(str, sys.pypy_version_info[:3]))
+ msg += "[pypy-%s-%s]" % (verinfo, sys.pypy_version_info[3])
+ msg += ", pytest-%s, py-%s, pluggy-%s" % (
+ pytest.__version__,
+ py.__version__,
+ pluggy.__version__,
+ )
+ if (
+ self.verbosity > 0
+ or self.config.option.debug
+ or getattr(self.config.option, "pastebin", None)
+ ):
+ msg += " -- " + str(sys.executable)
+ self.write_line(msg)
+ lines = self.config.hook.pytest_report_header(
+ config=self.config, startdir=self.startdir
+ )
+ self._write_report_lines_from_hooks(lines)
+
+ def _write_report_lines_from_hooks(self, lines):
+ lines.reverse()
+ for line in collapse(lines):
+ self.write_line(line)
+
+ def pytest_report_header(self, config):
line = "rootdir: %s" % config.rootdir
- if config.inifile:
+ if config.inifile:
line += ", inifile: " + config.rootdir.bestrelpath(config.inifile)
-
+
testpaths = config.getini("testpaths")
if testpaths and config.args == testpaths:
rel_paths = [config.rootdir.bestrelpath(x) for x in testpaths]
line += ", testpaths: {}".format(", ".join(rel_paths))
result = [line]
- plugininfo = config.pluginmanager.list_plugin_distinfo()
- if plugininfo:
+ plugininfo = config.pluginmanager.list_plugin_distinfo()
+ if plugininfo:
result.append("plugins: %s" % ", ".join(_plugin_nameversions(plugininfo)))
return result
-
+
def pytest_collection_finish(self, session):
self.report_collect(True)
-
+
if self.config.getoption("collectonly"):
- self._printcollecteditems(session.items)
+ self._printcollecteditems(session.items)
+
+ lines = self.config.hook.pytest_report_collectionfinish(
+ config=self.config, startdir=self.startdir, items=session.items
+ )
+ self._write_report_lines_from_hooks(lines)
- lines = self.config.hook.pytest_report_collectionfinish(
- config=self.config, startdir=self.startdir, items=session.items
- )
- self._write_report_lines_from_hooks(lines)
-
if self.config.getoption("collectonly"):
if self.stats.get("failed"):
self._tw.sep("!", "collection failures")
for rep in self.stats.get("failed"):
rep.toterminal(self._tw)
- def _printcollecteditems(self, items):
- # to print out items and their parent collectors
- # we take care to leave out Instances aka ()
- # because later versions are going to get rid of them anyway
- if self.config.option.verbose < 0:
- if self.config.option.verbose < -1:
- counts = {}
- for item in items:
- name = item.nodeid.split("::", 1)[0]
- counts[name] = counts.get(name, 0) + 1
- for name, count in sorted(counts.items()):
- self._tw.line("%s: %d" % (name, count))
- else:
- for item in items:
- self._tw.line(item.nodeid)
- return
- stack = []
- indent = ""
- for item in items:
- needed_collectors = item.listchain()[1:] # strip root node
- while stack:
- if stack == needed_collectors[: len(stack)]:
- break
- stack.pop()
- for col in needed_collectors[len(stack) :]:
- stack.append(col)
- if col.name == "()": # Skip Instances.
- continue
- indent = (len(stack) - 1) * " "
- self._tw.line("%s%s" % (indent, col))
+ def _printcollecteditems(self, items):
+ # to print out items and their parent collectors
+ # we take care to leave out Instances aka ()
+ # because later versions are going to get rid of them anyway
+ if self.config.option.verbose < 0:
+ if self.config.option.verbose < -1:
+ counts = {}
+ for item in items:
+ name = item.nodeid.split("::", 1)[0]
+ counts[name] = counts.get(name, 0) + 1
+ for name, count in sorted(counts.items()):
+ self._tw.line("%s: %d" % (name, count))
+ else:
+ for item in items:
+ self._tw.line(item.nodeid)
+ return
+ stack = []
+ indent = ""
+ for item in items:
+ needed_collectors = item.listchain()[1:] # strip root node
+ while stack:
+ if stack == needed_collectors[: len(stack)]:
+ break
+ stack.pop()
+ for col in needed_collectors[len(stack) :]:
+ stack.append(col)
+ if col.name == "()": # Skip Instances.
+ continue
+ indent = (len(stack) - 1) * " "
+ self._tw.line("%s%s" % (indent, col))
if self.config.option.verbose >= 1:
if hasattr(col, "_obj") and col._obj.__doc__:
for line in col._obj.__doc__.strip().splitlines():
self._tw.line("%s%s" % (indent + " ", line.strip()))
-
- @pytest.hookimpl(hookwrapper=True)
- def pytest_sessionfinish(self, exitstatus):
- outcome = yield
- outcome.get_result()
- self._tw.line("")
- summary_exit_codes = (
- EXIT_OK,
- EXIT_TESTSFAILED,
- EXIT_INTERRUPTED,
- EXIT_USAGEERROR,
- EXIT_NOTESTSCOLLECTED,
- )
- if exitstatus in summary_exit_codes:
- self.config.hook.pytest_terminal_summary(
+
+ @pytest.hookimpl(hookwrapper=True)
+ def pytest_sessionfinish(self, exitstatus):
+ outcome = yield
+ outcome.get_result()
+ self._tw.line("")
+ summary_exit_codes = (
+ EXIT_OK,
+ EXIT_TESTSFAILED,
+ EXIT_INTERRUPTED,
+ EXIT_USAGEERROR,
+ EXIT_NOTESTSCOLLECTED,
+ )
+ if exitstatus in summary_exit_codes:
+ self.config.hook.pytest_terminal_summary(
terminalreporter=self, exitstatus=exitstatus, config=self.config
- )
- if exitstatus == EXIT_INTERRUPTED:
- self._report_keyboardinterrupt()
- del self._keyboardinterrupt_memo
- self.summary_stats()
-
- @pytest.hookimpl(hookwrapper=True)
- def pytest_terminal_summary(self):
- self.summary_errors()
- self.summary_failures()
- self.summary_warnings()
+ )
+ if exitstatus == EXIT_INTERRUPTED:
+ self._report_keyboardinterrupt()
+ del self._keyboardinterrupt_memo
+ self.summary_stats()
+
+ @pytest.hookimpl(hookwrapper=True)
+ def pytest_terminal_summary(self):
+ self.summary_errors()
+ self.summary_failures()
+ self.summary_warnings()
self.summary_passes()
- yield
+ yield
self.short_test_summary()
- # Display any extra warnings from teardown here (if any).
- self.summary_warnings()
-
- def pytest_keyboard_interrupt(self, excinfo):
- self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
-
- def pytest_unconfigure(self):
- if hasattr(self, "_keyboardinterrupt_memo"):
- self._report_keyboardinterrupt()
-
- def _report_keyboardinterrupt(self):
- excrepr = self._keyboardinterrupt_memo
- msg = excrepr.reprcrash.message
- self.write_sep("!", msg)
- if "KeyboardInterrupt" in msg:
- if self.config.option.fulltrace:
- excrepr.toterminal(self._tw)
- else:
- excrepr.reprcrash.toterminal(self._tw)
- self._tw.line(
- "(to show a full traceback on KeyboardInterrupt use --fulltrace)",
- yellow=True,
- )
-
- def _locationline(self, nodeid, fspath, lineno, domain):
- def mkrel(nodeid):
- line = self.config.cwd_relative_nodeid(nodeid)
- if domain and line.endswith(domain):
- line = line[: -len(domain)]
- values = domain.split("[")
- values[0] = values[0].replace(".", "::") # don't replace '.' in params
- line += "[".join(values)
- return line
-
- # collect_fspath comes from testid which has a "/"-normalized path
-
- if fspath:
- res = mkrel(nodeid)
- if self.verbosity >= 2 and nodeid.split("::")[0] != fspath.replace(
- "\\", nodes.SEP
- ):
- res += " <- " + self.startdir.bestrelpath(fspath)
- else:
- res = "[location]"
- return res + " "
-
- def _getfailureheadline(self, rep):
+ # Display any extra warnings from teardown here (if any).
+ self.summary_warnings()
+
+ def pytest_keyboard_interrupt(self, excinfo):
+ self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
+
+ def pytest_unconfigure(self):
+ if hasattr(self, "_keyboardinterrupt_memo"):
+ self._report_keyboardinterrupt()
+
+ def _report_keyboardinterrupt(self):
+ excrepr = self._keyboardinterrupt_memo
+ msg = excrepr.reprcrash.message
+ self.write_sep("!", msg)
+ if "KeyboardInterrupt" in msg:
+ if self.config.option.fulltrace:
+ excrepr.toterminal(self._tw)
+ else:
+ excrepr.reprcrash.toterminal(self._tw)
+ self._tw.line(
+ "(to show a full traceback on KeyboardInterrupt use --fulltrace)",
+ yellow=True,
+ )
+
+ def _locationline(self, nodeid, fspath, lineno, domain):
+ def mkrel(nodeid):
+ line = self.config.cwd_relative_nodeid(nodeid)
+ if domain and line.endswith(domain):
+ line = line[: -len(domain)]
+ values = domain.split("[")
+ values[0] = values[0].replace(".", "::") # don't replace '.' in params
+ line += "[".join(values)
+ return line
+
+ # collect_fspath comes from testid which has a "/"-normalized path
+
+ if fspath:
+ res = mkrel(nodeid)
+ if self.verbosity >= 2 and nodeid.split("::")[0] != fspath.replace(
+ "\\", nodes.SEP
+ ):
+ res += " <- " + self.startdir.bestrelpath(fspath)
+ else:
+ res = "[location]"
+ return res + " "
+
+ def _getfailureheadline(self, rep):
head_line = rep.head_line
if head_line:
return head_line
return "test session" # XXX?
-
- def _getcrashline(self, rep):
- try:
- return str(rep.longrepr.reprcrash)
- except AttributeError:
- try:
- return str(rep.longrepr)[:50]
- except AttributeError:
- return ""
-
- #
- # summaries for sessionfinish
- #
- def getreports(self, name):
- values = []
- for x in self.stats.get(name, []):
- if not hasattr(x, "_pdbshown"):
- values.append(x)
- return values
-
- def summary_warnings(self):
- if self.hasopt("w"):
- all_warnings = self.stats.get("warnings")
- if not all_warnings:
- return
-
- final = hasattr(self, "_already_displayed_warnings")
- if final:
+
+ def _getcrashline(self, rep):
+ try:
+ return str(rep.longrepr.reprcrash)
+ except AttributeError:
+ try:
+ return str(rep.longrepr)[:50]
+ except AttributeError:
+ return ""
+
+ #
+ # summaries for sessionfinish
+ #
+ def getreports(self, name):
+ values = []
+ for x in self.stats.get(name, []):
+ if not hasattr(x, "_pdbshown"):
+ values.append(x)
+ return values
+
+ def summary_warnings(self):
+ if self.hasopt("w"):
+ all_warnings = self.stats.get("warnings")
+ if not all_warnings:
+ return
+
+ final = hasattr(self, "_already_displayed_warnings")
+ if final:
warning_reports = all_warnings[self._already_displayed_warnings :]
- else:
+ else:
warning_reports = all_warnings
self._already_displayed_warnings = len(warning_reports)
if not warning_reports:
- return
-
+ return
+
reports_grouped_by_message = collections.OrderedDict()
for wr in warning_reports:
reports_grouped_by_message.setdefault(wr.message, []).append(wr)
-
- title = "warnings summary (final)" if final else "warnings summary"
- self.write_sep("=", title, yellow=True, bold=False)
+
+ title = "warnings summary (final)" if final else "warnings summary"
+ self.write_sep("=", title, yellow=True, bold=False)
for message, warning_reports in reports_grouped_by_message.items():
has_any_location = False
for w in warning_reports:
location = w.get_location(self.config)
- if location:
+ if location:
self._tw.line(str(location))
has_any_location = True
if has_any_location:
@@ -798,45 +798,45 @@ class TerminalReporter(object):
else:
message = message.rstrip()
self._tw.line(message)
- self._tw.line()
- self._tw.line("-- Docs: https://docs.pytest.org/en/latest/warnings.html")
-
- def summary_passes(self):
- if self.config.option.tbstyle != "no":
- if self.hasopt("P"):
- reports = self.getreports("passed")
- if not reports:
- return
- self.write_sep("=", "PASSES")
- for rep in reports:
- if rep.sections:
- msg = self._getfailureheadline(rep)
+ self._tw.line()
+ self._tw.line("-- Docs: https://docs.pytest.org/en/latest/warnings.html")
+
+ def summary_passes(self):
+ if self.config.option.tbstyle != "no":
+ if self.hasopt("P"):
+ reports = self.getreports("passed")
+ if not reports:
+ return
+ self.write_sep("=", "PASSES")
+ for rep in reports:
+ if rep.sections:
+ msg = self._getfailureheadline(rep)
self.write_sep("_", msg, green=True, bold=True)
- self._outrep_summary(rep)
-
- def print_teardown_sections(self, rep):
- showcapture = self.config.option.showcapture
- if showcapture == "no":
- return
- for secname, content in rep.sections:
- if showcapture != "all" and showcapture not in secname:
- continue
- if "teardown" in secname:
- self._tw.sep("-", secname)
- if content[-1:] == "\n":
- content = content[:-1]
- self._tw.line(content)
-
- def summary_failures(self):
- if self.config.option.tbstyle != "no":
- reports = self.getreports("failed")
- if not reports:
- return
- self.write_sep("=", "FAILURES")
+ self._outrep_summary(rep)
+
+ def print_teardown_sections(self, rep):
+ showcapture = self.config.option.showcapture
+ if showcapture == "no":
+ return
+ for secname, content in rep.sections:
+ if showcapture != "all" and showcapture not in secname:
+ continue
+ if "teardown" in secname:
+ self._tw.sep("-", secname)
+ if content[-1:] == "\n":
+ content = content[:-1]
+ self._tw.line(content)
+
+ def summary_failures(self):
+ if self.config.option.tbstyle != "no":
+ reports = self.getreports("failed")
+ if not reports:
+ return
+ self.write_sep("=", "FAILURES")
if self.config.option.tbstyle == "line":
for rep in reports:
- line = self._getcrashline(rep)
- self.write_line(line)
+ line = self._getcrashline(rep)
+ self.write_line(line)
else:
teardown_sections = {}
for report in self.getreports(""):
@@ -844,55 +844,55 @@ class TerminalReporter(object):
teardown_sections.setdefault(report.nodeid, []).append(report)
for rep in reports:
- msg = self._getfailureheadline(rep)
- self.write_sep("_", msg, red=True, bold=True)
- self._outrep_summary(rep)
+ msg = self._getfailureheadline(rep)
+ self.write_sep("_", msg, red=True, bold=True)
+ self._outrep_summary(rep)
for report in teardown_sections.get(rep.nodeid, []):
self.print_teardown_sections(report)
-
- def summary_errors(self):
- if self.config.option.tbstyle != "no":
- reports = self.getreports("error")
- if not reports:
- return
- self.write_sep("=", "ERRORS")
- for rep in self.stats["error"]:
- msg = self._getfailureheadline(rep)
+
+ def summary_errors(self):
+ if self.config.option.tbstyle != "no":
+ reports = self.getreports("error")
+ if not reports:
+ return
+ self.write_sep("=", "ERRORS")
+ for rep in self.stats["error"]:
+ msg = self._getfailureheadline(rep)
if rep.when == "collect":
- msg = "ERROR collecting " + msg
+ msg = "ERROR collecting " + msg
else:
msg = "ERROR at %s of %s" % (rep.when, msg)
- self.write_sep("_", msg, red=True, bold=True)
- self._outrep_summary(rep)
-
- def _outrep_summary(self, rep):
- rep.toterminal(self._tw)
- showcapture = self.config.option.showcapture
- if showcapture == "no":
- return
- for secname, content in rep.sections:
- if showcapture != "all" and showcapture not in secname:
- continue
- self._tw.sep("-", secname)
- if content[-1:] == "\n":
- content = content[:-1]
- self._tw.line(content)
-
- def summary_stats(self):
- session_duration = time.time() - self._sessionstarttime
- (line, color) = build_summary_stats_line(self.stats)
- msg = "%s in %.2f seconds" % (line, session_duration)
- markup = {color: True, "bold": True}
-
- if self.verbosity >= 0:
- self.write_sep("=", msg, **markup)
- if self.verbosity == -1:
- self.write_line(msg, **markup)
-
+ self.write_sep("_", msg, red=True, bold=True)
+ self._outrep_summary(rep)
+
+ def _outrep_summary(self, rep):
+ rep.toterminal(self._tw)
+ showcapture = self.config.option.showcapture
+ if showcapture == "no":
+ return
+ for secname, content in rep.sections:
+ if showcapture != "all" and showcapture not in secname:
+ continue
+ self._tw.sep("-", secname)
+ if content[-1:] == "\n":
+ content = content[:-1]
+ self._tw.line(content)
+
+ def summary_stats(self):
+ session_duration = time.time() - self._sessionstarttime
+ (line, color) = build_summary_stats_line(self.stats)
+ msg = "%s in %.2f seconds" % (line, session_duration)
+ markup = {color: True, "bold": True}
+
+ if self.verbosity >= 0:
+ self.write_sep("=", msg, **markup)
+ if self.verbosity == -1:
+ self.write_line(msg, **markup)
+
def short_test_summary(self):
if not self.reportchars:
return
-
+
def show_simple(stat, lines):
failed = self.stats.get(stat, [])
if not failed:
@@ -978,7 +978,7 @@ def _get_line_with_reprcrash_message(config, rep, termwidth):
# No space for an additional message.
return line
- try:
+ try:
msg = rep.longrepr.reprcrash.message
except AttributeError:
pass
@@ -988,7 +988,7 @@ def _get_line_with_reprcrash_message(config, rep, termwidth):
if i != -1:
msg = msg[:i]
len_msg = wcswidth(msg)
-
+
sep, len_sep = " - ", 3
max_len_msg = termwidth - len_line - len_sep
if max_len_msg >= len_ellipsis:
@@ -1015,7 +1015,7 @@ def _get_line_with_reprcrash_message(config, rep, termwidth):
msg += ellipsis
line += sep + msg
return line
-
+
def _folded_skips(skipped):
d = {}
@@ -1039,7 +1039,7 @@ def _folded_skips(skipped):
return values
-def build_summary_stats_line(stats):
+def build_summary_stats_line(stats):
known_types = (
"failed passed skipped deselected xfailed xpassed warnings error".split()
)
@@ -1049,7 +1049,7 @@ def build_summary_stats_line(stats):
if found_type: # setup/teardown reports have an empty key, ignore them
known_types.append(found_type)
unknown_type_seen = True
- parts = []
+ parts = []
for key in known_types:
reports = stats.get(key, None)
if reports:
@@ -1057,34 +1057,34 @@ def build_summary_stats_line(stats):
1 for rep in reports if getattr(rep, "count_towards_summary", True)
)
parts.append("%d %s" % (count, key))
-
- if parts:
- line = ", ".join(parts)
- else:
- line = "no tests ran"
-
- if "failed" in stats or "error" in stats:
- color = "red"
+
+ if parts:
+ line = ", ".join(parts)
+ else:
+ line = "no tests ran"
+
+ if "failed" in stats or "error" in stats:
+ color = "red"
elif "warnings" in stats or unknown_type_seen:
- color = "yellow"
- elif "passed" in stats:
- color = "green"
- else:
- color = "yellow"
-
+ color = "yellow"
+ elif "passed" in stats:
+ color = "green"
+ else:
+ color = "yellow"
+
return line, color
-
-
-def _plugin_nameversions(plugininfo):
- values = []
- for plugin, dist in plugininfo:
- # gets us name and version!
- name = "{dist.project_name}-{dist.version}".format(dist=dist)
- # questionable convenience, but it keeps things short
- if name.startswith("pytest-"):
- name = name[7:]
- # we decided to print python package names
- # they can have more than one plugin
- if name not in values:
- values.append(name)
- return values
+
+
+def _plugin_nameversions(plugininfo):
+ values = []
+ for plugin, dist in plugininfo:
+ # gets us name and version!
+ name = "{dist.project_name}-{dist.version}".format(dist=dist)
+ # questionable convenience, but it keeps things short
+ if name.startswith("pytest-"):
+ name = name[7:]
+ # we decided to print python package names
+ # they can have more than one plugin
+ if name not in values:
+ values.append(name)
+ return values
diff --git a/contrib/python/pytest/py2/_pytest/tmpdir.py b/contrib/python/pytest/py2/_pytest/tmpdir.py
index d146cefa2b..a8a7037713 100644
--- a/contrib/python/pytest/py2/_pytest/tmpdir.py
+++ b/contrib/python/pytest/py2/_pytest/tmpdir.py
@@ -1,68 +1,68 @@
# -*- coding: utf-8 -*-
-""" support for providing temporary directories to test functions. """
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import os
-import re
-import tempfile
-import warnings
-
-import attr
-import py
-import six
-
-import pytest
-from .pathlib import ensure_reset_dir
-from .pathlib import LOCK_TIMEOUT
-from .pathlib import make_numbered_dir
-from .pathlib import make_numbered_dir_with_cleanup
-from .pathlib import Path
-from _pytest.monkeypatch import MonkeyPatch
-
-
-@attr.s
-class TempPathFactory(object):
- """Factory for temporary directories under the common base temp directory.
-
- The base directory can be configured using the ``--basetemp`` option."""
-
- _given_basetemp = attr.ib(
- # using os.path.abspath() to get absolute path instead of resolve() as it
- # does not work the same in all platforms (see #4427)
- # Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012)
+""" support for providing temporary directories to test functions. """
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import os
+import re
+import tempfile
+import warnings
+
+import attr
+import py
+import six
+
+import pytest
+from .pathlib import ensure_reset_dir
+from .pathlib import LOCK_TIMEOUT
+from .pathlib import make_numbered_dir
+from .pathlib import make_numbered_dir_with_cleanup
+from .pathlib import Path
+from _pytest.monkeypatch import MonkeyPatch
+
+
+@attr.s
+class TempPathFactory(object):
+ """Factory for temporary directories under the common base temp directory.
+
+ The base directory can be configured using the ``--basetemp`` option."""
+
+ _given_basetemp = attr.ib(
+ # using os.path.abspath() to get absolute path instead of resolve() as it
+ # does not work the same in all platforms (see #4427)
+ # Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012)
converter=attr.converters.optional(
- lambda p: Path(os.path.abspath(six.text_type(p)))
- )
- )
- _trace = attr.ib()
- _basetemp = attr.ib(default=None)
-
- @classmethod
- def from_config(cls, config):
- """
- :param config: a pytest configuration
- """
- return cls(
- given_basetemp=config.option.basetemp, trace=config.trace.get("tmpdir")
- )
-
- def mktemp(self, basename, numbered=True):
- """makes a temporary directory managed by the factory"""
- if not numbered:
- p = self.getbasetemp().joinpath(basename)
- p.mkdir()
- else:
- p = make_numbered_dir(root=self.getbasetemp(), prefix=basename)
- self._trace("mktemp", p)
- return p
-
- def getbasetemp(self):
- """ return base temporary directory. """
+ lambda p: Path(os.path.abspath(six.text_type(p)))
+ )
+ )
+ _trace = attr.ib()
+ _basetemp = attr.ib(default=None)
+
+ @classmethod
+ def from_config(cls, config):
+ """
+ :param config: a pytest configuration
+ """
+ return cls(
+ given_basetemp=config.option.basetemp, trace=config.trace.get("tmpdir")
+ )
+
+ def mktemp(self, basename, numbered=True):
+ """makes a temporary directory managed by the factory"""
+ if not numbered:
+ p = self.getbasetemp().joinpath(basename)
+ p.mkdir()
+ else:
+ p = make_numbered_dir(root=self.getbasetemp(), prefix=basename)
+ self._trace("mktemp", p)
+ return p
+
+ def getbasetemp(self):
+ """ return base temporary directory. """
if self._basetemp is not None:
- return self._basetemp
-
+ return self._basetemp
+
if self._given_basetemp is not None:
basetemp = self._given_basetemp
ensure_reset_dir(basetemp)
@@ -82,116 +82,116 @@ class TempPathFactory(object):
self._basetemp = t = basetemp
self._trace("new basetemp", t)
return t
-
-
-@attr.s
-class TempdirFactory(object):
- """
- backward comptibility wrapper that implements
- :class:``py.path.local`` for :class:``TempPathFactory``
- """
-
- _tmppath_factory = attr.ib()
-
- def ensuretemp(self, string, dir=1):
- """ (deprecated) return temporary directory path with
- the given string as the trailing part. It is usually
- better to use the 'tmpdir' function argument which
- provides an empty unique-per-test-invocation directory
- and is guaranteed to be empty.
- """
- # py.log._apiwarn(">1.1", "use tmpdir function argument")
- from .deprecated import PYTEST_ENSURETEMP
-
- warnings.warn(PYTEST_ENSURETEMP, stacklevel=2)
- return self.getbasetemp().ensure(string, dir=dir)
-
- def mktemp(self, basename, numbered=True):
- """Create a subdirectory of the base temporary directory and return it.
- If ``numbered``, ensure the directory is unique by adding a number
- prefix greater than any existing one.
- """
- return py.path.local(self._tmppath_factory.mktemp(basename, numbered).resolve())
-
- def getbasetemp(self):
- """backward compat wrapper for ``_tmppath_factory.getbasetemp``"""
- return py.path.local(self._tmppath_factory.getbasetemp().resolve())
-
-
-def get_user():
- """Return the current user name, or None if getuser() does not work
- in the current environment (see #1010).
- """
- import getpass
-
- try:
- return getpass.getuser()
- except (ImportError, KeyError):
- return None
-
-
-def pytest_configure(config):
- """Create a TempdirFactory and attach it to the config object.
-
- This is to comply with existing plugins which expect the handler to be
- available at pytest_configure time, but ideally should be moved entirely
- to the tmpdir_factory session fixture.
- """
- mp = MonkeyPatch()
- tmppath_handler = TempPathFactory.from_config(config)
- t = TempdirFactory(tmppath_handler)
- config._cleanup.append(mp.undo)
- mp.setattr(config, "_tmp_path_factory", tmppath_handler, raising=False)
- mp.setattr(config, "_tmpdirhandler", t, raising=False)
- mp.setattr(pytest, "ensuretemp", t.ensuretemp, raising=False)
-
-
-@pytest.fixture(scope="session")
-def tmpdir_factory(request):
- """Return a :class:`_pytest.tmpdir.TempdirFactory` instance for the test session.
- """
- return request.config._tmpdirhandler
-
-
-@pytest.fixture(scope="session")
-def tmp_path_factory(request):
- """Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session.
- """
- return request.config._tmp_path_factory
-
-
-def _mk_tmp(request, factory):
- name = request.node.name
- name = re.sub(r"[\W]", "_", name)
- MAXVAL = 30
- name = name[:MAXVAL]
- return factory.mktemp(name, numbered=True)
-
-
-@pytest.fixture
+
+
+@attr.s
+class TempdirFactory(object):
+ """
+ backward comptibility wrapper that implements
+ :class:``py.path.local`` for :class:``TempPathFactory``
+ """
+
+ _tmppath_factory = attr.ib()
+
+ def ensuretemp(self, string, dir=1):
+ """ (deprecated) return temporary directory path with
+ the given string as the trailing part. It is usually
+ better to use the 'tmpdir' function argument which
+ provides an empty unique-per-test-invocation directory
+ and is guaranteed to be empty.
+ """
+ # py.log._apiwarn(">1.1", "use tmpdir function argument")
+ from .deprecated import PYTEST_ENSURETEMP
+
+ warnings.warn(PYTEST_ENSURETEMP, stacklevel=2)
+ return self.getbasetemp().ensure(string, dir=dir)
+
+ def mktemp(self, basename, numbered=True):
+ """Create a subdirectory of the base temporary directory and return it.
+ If ``numbered``, ensure the directory is unique by adding a number
+ prefix greater than any existing one.
+ """
+ return py.path.local(self._tmppath_factory.mktemp(basename, numbered).resolve())
+
+ def getbasetemp(self):
+ """backward compat wrapper for ``_tmppath_factory.getbasetemp``"""
+ return py.path.local(self._tmppath_factory.getbasetemp().resolve())
+
+
+def get_user():
+ """Return the current user name, or None if getuser() does not work
+ in the current environment (see #1010).
+ """
+ import getpass
+
+ try:
+ return getpass.getuser()
+ except (ImportError, KeyError):
+ return None
+
+
+def pytest_configure(config):
+ """Create a TempdirFactory and attach it to the config object.
+
+ This is to comply with existing plugins which expect the handler to be
+ available at pytest_configure time, but ideally should be moved entirely
+ to the tmpdir_factory session fixture.
+ """
+ mp = MonkeyPatch()
+ tmppath_handler = TempPathFactory.from_config(config)
+ t = TempdirFactory(tmppath_handler)
+ config._cleanup.append(mp.undo)
+ mp.setattr(config, "_tmp_path_factory", tmppath_handler, raising=False)
+ mp.setattr(config, "_tmpdirhandler", t, raising=False)
+ mp.setattr(pytest, "ensuretemp", t.ensuretemp, raising=False)
+
+
+@pytest.fixture(scope="session")
+def tmpdir_factory(request):
+ """Return a :class:`_pytest.tmpdir.TempdirFactory` instance for the test session.
+ """
+ return request.config._tmpdirhandler
+
+
+@pytest.fixture(scope="session")
+def tmp_path_factory(request):
+ """Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session.
+ """
+ return request.config._tmp_path_factory
+
+
+def _mk_tmp(request, factory):
+ name = request.node.name
+ name = re.sub(r"[\W]", "_", name)
+ MAXVAL = 30
+ name = name[:MAXVAL]
+ return factory.mktemp(name, numbered=True)
+
+
+@pytest.fixture
def tmpdir(tmp_path):
- """Return a temporary directory path object
- which is unique to each test function invocation,
- created as a sub directory of the base temporary
- directory. The returned object is a `py.path.local`_
- path object.
-
- .. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
- """
+ """Return a temporary directory path object
+ which is unique to each test function invocation,
+ created as a sub directory of the base temporary
+ directory. The returned object is a `py.path.local`_
+ path object.
+
+ .. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
+ """
return py.path.local(tmp_path)
-
-
-@pytest.fixture
-def tmp_path(request, tmp_path_factory):
- """Return a temporary directory path object
- which is unique to each test function invocation,
- created as a sub directory of the base temporary
- directory. The returned object is a :class:`pathlib.Path`
- object.
-
- .. note::
-
- in python < 3.6 this is a pathlib2.Path
- """
-
- return _mk_tmp(request, tmp_path_factory)
+
+
+@pytest.fixture
+def tmp_path(request, tmp_path_factory):
+ """Return a temporary directory path object
+ which is unique to each test function invocation,
+ created as a sub directory of the base temporary
+ directory. The returned object is a :class:`pathlib.Path`
+ object.
+
+ .. note::
+
+ in python < 3.6 this is a pathlib2.Path
+ """
+
+ return _mk_tmp(request, tmp_path_factory)
diff --git a/contrib/python/pytest/py2/_pytest/unittest.py b/contrib/python/pytest/py2/_pytest/unittest.py
index a27b8e8945..3ff6f45d8d 100644
--- a/contrib/python/pytest/py2/_pytest/unittest.py
+++ b/contrib/python/pytest/py2/_pytest/unittest.py
@@ -1,69 +1,69 @@
# -*- coding: utf-8 -*-
-""" discovery and running of std-library "unittest" style tests. """
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import sys
-import traceback
-
-import _pytest._code
+""" discovery and running of std-library "unittest" style tests. """
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import sys
+import traceback
+
+import _pytest._code
import pytest
-from _pytest.compat import getimfunc
-from _pytest.config import hookimpl
-from _pytest.outcomes import fail
-from _pytest.outcomes import skip
-from _pytest.outcomes import xfail
-from _pytest.python import Class
-from _pytest.python import Function
-
-
-def pytest_pycollect_makeitem(collector, name, obj):
- # has unittest been imported and is obj a subclass of its TestCase?
- try:
- if not issubclass(obj, sys.modules["unittest"].TestCase):
- return
- except Exception:
- return
- # yes, so let's collect it
- return UnitTestCase(name, parent=collector)
-
-
-class UnitTestCase(Class):
- # marker for fixturemanger.getfixtureinfo()
- # to declare that our children do not support funcargs
- nofuncargs = True
-
- def collect(self):
- from unittest import TestLoader
-
- cls = self.obj
- if not getattr(cls, "__test__", True):
- return
+from _pytest.compat import getimfunc
+from _pytest.config import hookimpl
+from _pytest.outcomes import fail
+from _pytest.outcomes import skip
+from _pytest.outcomes import xfail
+from _pytest.python import Class
+from _pytest.python import Function
+
+
+def pytest_pycollect_makeitem(collector, name, obj):
+ # has unittest been imported and is obj a subclass of its TestCase?
+ try:
+ if not issubclass(obj, sys.modules["unittest"].TestCase):
+ return
+ except Exception:
+ return
+ # yes, so let's collect it
+ return UnitTestCase(name, parent=collector)
+
+
+class UnitTestCase(Class):
+ # marker for fixturemanger.getfixtureinfo()
+ # to declare that our children do not support funcargs
+ nofuncargs = True
+
+ def collect(self):
+ from unittest import TestLoader
+
+ cls = self.obj
+ if not getattr(cls, "__test__", True):
+ return
skipped = getattr(cls, "__unittest_skip__", False)
if not skipped:
self._inject_setup_teardown_fixtures(cls)
self._inject_setup_class_fixture()
- self.session._fixturemanager.parsefactories(self, unittest=True)
- loader = TestLoader()
- foundsomething = False
- for name in loader.getTestCaseNames(self.obj):
- x = getattr(self.obj, name)
- if not getattr(x, "__test__", True):
- continue
- funcobj = getimfunc(x)
- yield TestCaseFunction(name, parent=self, callobj=funcobj)
- foundsomething = True
-
- if not foundsomething:
- runtest = getattr(self.obj, "runTest", None)
- if runtest is not None:
- ut = sys.modules.get("twisted.trial.unittest", None)
- if ut is None or runtest != ut.TestCase.runTest:
- yield TestCaseFunction("runTest", parent=self)
-
+ self.session._fixturemanager.parsefactories(self, unittest=True)
+ loader = TestLoader()
+ foundsomething = False
+ for name in loader.getTestCaseNames(self.obj):
+ x = getattr(self.obj, name)
+ if not getattr(x, "__test__", True):
+ continue
+ funcobj = getimfunc(x)
+ yield TestCaseFunction(name, parent=self, callobj=funcobj)
+ foundsomething = True
+
+ if not foundsomething:
+ runtest = getattr(self.obj, "runTest", None)
+ if runtest is not None:
+ ut = sys.modules.get("twisted.trial.unittest", None)
+ if ut is None or runtest != ut.TestCase.runTest:
+ yield TestCaseFunction("runTest", parent=self)
+
def _inject_setup_teardown_fixtures(self, cls):
"""Injects a hidden auto-use fixture to invoke setUpClass/setup_method and corresponding
teardown functions (#517)"""
@@ -72,7 +72,7 @@ class UnitTestCase(Class):
)
if class_fixture:
cls.__pytest_class_setup = class_fixture
-
+
method_fixture = _make_xunit_fixture(
cls, "setup_method", "teardown_method", scope="function", pass_self=True
)
@@ -106,186 +106,186 @@ def _make_xunit_fixture(obj, setup_name, teardown_name, scope, pass_self):
return fixture
-class TestCaseFunction(Function):
- nofuncargs = True
- _excinfo = None
- _testcase = None
-
- def setup(self):
- self._testcase = self.parent.obj(self.name)
- self._fix_unittest_skip_decorator()
- self._obj = getattr(self._testcase, self.name)
- if hasattr(self, "_request"):
- self._request._fillfixtures()
-
- def _fix_unittest_skip_decorator(self):
- """
- The @unittest.skip decorator calls functools.wraps(self._testcase)
- The call to functools.wraps() fails unless self._testcase
- has a __name__ attribute. This is usually automatically supplied
- if the test is a function or method, but we need to add manually
- here.
-
- See issue #1169
- """
- if sys.version_info[0] == 2:
- setattr(self._testcase, "__name__", self.name)
-
- def teardown(self):
- self._testcase = None
- self._obj = None
-
- def startTest(self, testcase):
- pass
-
- def _addexcinfo(self, rawexcinfo):
- # unwrap potential exception info (see twisted trial support below)
- rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo)
- try:
- excinfo = _pytest._code.ExceptionInfo(rawexcinfo)
+class TestCaseFunction(Function):
+ nofuncargs = True
+ _excinfo = None
+ _testcase = None
+
+ def setup(self):
+ self._testcase = self.parent.obj(self.name)
+ self._fix_unittest_skip_decorator()
+ self._obj = getattr(self._testcase, self.name)
+ if hasattr(self, "_request"):
+ self._request._fillfixtures()
+
+ def _fix_unittest_skip_decorator(self):
+ """
+ The @unittest.skip decorator calls functools.wraps(self._testcase)
+ The call to functools.wraps() fails unless self._testcase
+ has a __name__ attribute. This is usually automatically supplied
+ if the test is a function or method, but we need to add manually
+ here.
+
+ See issue #1169
+ """
+ if sys.version_info[0] == 2:
+ setattr(self._testcase, "__name__", self.name)
+
+ def teardown(self):
+ self._testcase = None
+ self._obj = None
+
+ def startTest(self, testcase):
+ pass
+
+ def _addexcinfo(self, rawexcinfo):
+ # unwrap potential exception info (see twisted trial support below)
+ rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo)
+ try:
+ excinfo = _pytest._code.ExceptionInfo(rawexcinfo)
# invoke the attributes to trigger storing the traceback
# trial causes some issue there
excinfo.value
excinfo.traceback
- except TypeError:
- try:
- try:
- values = traceback.format_exception(*rawexcinfo)
- values.insert(
- 0,
- "NOTE: Incompatible Exception Representation, "
- "displaying natively:\n\n",
- )
- fail("".join(values), pytrace=False)
- except (fail.Exception, KeyboardInterrupt):
- raise
- except: # noqa
- fail(
- "ERROR: Unknown Incompatible Exception "
- "representation:\n%r" % (rawexcinfo,),
- pytrace=False,
- )
- except KeyboardInterrupt:
- raise
- except fail.Exception:
+ except TypeError:
+ try:
+ try:
+ values = traceback.format_exception(*rawexcinfo)
+ values.insert(
+ 0,
+ "NOTE: Incompatible Exception Representation, "
+ "displaying natively:\n\n",
+ )
+ fail("".join(values), pytrace=False)
+ except (fail.Exception, KeyboardInterrupt):
+ raise
+ except: # noqa
+ fail(
+ "ERROR: Unknown Incompatible Exception "
+ "representation:\n%r" % (rawexcinfo,),
+ pytrace=False,
+ )
+ except KeyboardInterrupt:
+ raise
+ except fail.Exception:
excinfo = _pytest._code.ExceptionInfo.from_current()
- self.__dict__.setdefault("_excinfo", []).append(excinfo)
-
- def addError(self, testcase, rawexcinfo):
- self._addexcinfo(rawexcinfo)
-
- def addFailure(self, testcase, rawexcinfo):
- self._addexcinfo(rawexcinfo)
-
- def addSkip(self, testcase, reason):
- try:
- skip(reason)
- except skip.Exception:
- self._skipped_by_mark = True
- self._addexcinfo(sys.exc_info())
-
- def addExpectedFailure(self, testcase, rawexcinfo, reason=""):
- try:
- xfail(str(reason))
- except xfail.Exception:
- self._addexcinfo(sys.exc_info())
-
- def addUnexpectedSuccess(self, testcase, reason=""):
- self._unexpectedsuccess = reason
-
- def addSuccess(self, testcase):
- pass
-
- def stopTest(self, testcase):
- pass
-
- def _handle_skip(self):
- # implements the skipping machinery (see #2137)
- # analog to pythons Lib/unittest/case.py:run
- testMethod = getattr(self._testcase, self._testcase._testMethodName)
- if getattr(self._testcase.__class__, "__unittest_skip__", False) or getattr(
- testMethod, "__unittest_skip__", False
- ):
- # If the class or method was skipped.
- skip_why = getattr(
- self._testcase.__class__, "__unittest_skip_why__", ""
- ) or getattr(testMethod, "__unittest_skip_why__", "")
- try: # PY3, unittest2 on PY2
- self._testcase._addSkip(self, self._testcase, skip_why)
- except TypeError: # PY2
- if sys.version_info[0] != 2:
- raise
- self._testcase._addSkip(self, skip_why)
- return True
- return False
-
- def runtest(self):
- if self.config.pluginmanager.get_plugin("pdbinvoke") is None:
- self._testcase(result=self)
- else:
- # disables tearDown and cleanups for post mortem debugging (see #1890)
- if self._handle_skip():
- return
- self._testcase.debug()
-
- def _prunetraceback(self, excinfo):
- Function._prunetraceback(self, excinfo)
- traceback = excinfo.traceback.filter(
- lambda x: not x.frame.f_globals.get("__unittest")
- )
- if traceback:
- excinfo.traceback = traceback
-
-
-@hookimpl(tryfirst=True)
-def pytest_runtest_makereport(item, call):
- if isinstance(item, TestCaseFunction):
- if item._excinfo:
- call.excinfo = item._excinfo.pop(0)
- try:
- del call.result
- except AttributeError:
- pass
-
-
-# twisted trial support
-
-
-@hookimpl(hookwrapper=True)
-def pytest_runtest_protocol(item):
- if isinstance(item, TestCaseFunction) and "twisted.trial.unittest" in sys.modules:
- ut = sys.modules["twisted.python.failure"]
- Failure__init__ = ut.Failure.__init__
- check_testcase_implements_trial_reporter()
-
- def excstore(
- self, exc_value=None, exc_type=None, exc_tb=None, captureVars=None
- ):
- if exc_value is None:
- self._rawexcinfo = sys.exc_info()
- else:
- if exc_type is None:
- exc_type = type(exc_value)
- self._rawexcinfo = (exc_type, exc_value, exc_tb)
- try:
- Failure__init__(
- self, exc_value, exc_type, exc_tb, captureVars=captureVars
- )
- except TypeError:
- Failure__init__(self, exc_value, exc_type, exc_tb)
-
- ut.Failure.__init__ = excstore
- yield
- ut.Failure.__init__ = Failure__init__
- else:
- yield
-
-
-def check_testcase_implements_trial_reporter(done=[]):
- if done:
- return
- from zope.interface import classImplements
- from twisted.trial.itrial import IReporter
-
- classImplements(TestCaseFunction, IReporter)
- done.append(1)
+ self.__dict__.setdefault("_excinfo", []).append(excinfo)
+
+ def addError(self, testcase, rawexcinfo):
+ self._addexcinfo(rawexcinfo)
+
+ def addFailure(self, testcase, rawexcinfo):
+ self._addexcinfo(rawexcinfo)
+
+ def addSkip(self, testcase, reason):
+ try:
+ skip(reason)
+ except skip.Exception:
+ self._skipped_by_mark = True
+ self._addexcinfo(sys.exc_info())
+
+ def addExpectedFailure(self, testcase, rawexcinfo, reason=""):
+ try:
+ xfail(str(reason))
+ except xfail.Exception:
+ self._addexcinfo(sys.exc_info())
+
+ def addUnexpectedSuccess(self, testcase, reason=""):
+ self._unexpectedsuccess = reason
+
+ def addSuccess(self, testcase):
+ pass
+
+ def stopTest(self, testcase):
+ pass
+
+ def _handle_skip(self):
+ # implements the skipping machinery (see #2137)
+ # analog to pythons Lib/unittest/case.py:run
+ testMethod = getattr(self._testcase, self._testcase._testMethodName)
+ if getattr(self._testcase.__class__, "__unittest_skip__", False) or getattr(
+ testMethod, "__unittest_skip__", False
+ ):
+ # If the class or method was skipped.
+ skip_why = getattr(
+ self._testcase.__class__, "__unittest_skip_why__", ""
+ ) or getattr(testMethod, "__unittest_skip_why__", "")
+ try: # PY3, unittest2 on PY2
+ self._testcase._addSkip(self, self._testcase, skip_why)
+ except TypeError: # PY2
+ if sys.version_info[0] != 2:
+ raise
+ self._testcase._addSkip(self, skip_why)
+ return True
+ return False
+
+ def runtest(self):
+ if self.config.pluginmanager.get_plugin("pdbinvoke") is None:
+ self._testcase(result=self)
+ else:
+ # disables tearDown and cleanups for post mortem debugging (see #1890)
+ if self._handle_skip():
+ return
+ self._testcase.debug()
+
+ def _prunetraceback(self, excinfo):
+ Function._prunetraceback(self, excinfo)
+ traceback = excinfo.traceback.filter(
+ lambda x: not x.frame.f_globals.get("__unittest")
+ )
+ if traceback:
+ excinfo.traceback = traceback
+
+
+@hookimpl(tryfirst=True)
+def pytest_runtest_makereport(item, call):
+ if isinstance(item, TestCaseFunction):
+ if item._excinfo:
+ call.excinfo = item._excinfo.pop(0)
+ try:
+ del call.result
+ except AttributeError:
+ pass
+
+
+# twisted trial support
+
+
+@hookimpl(hookwrapper=True)
+def pytest_runtest_protocol(item):
+ if isinstance(item, TestCaseFunction) and "twisted.trial.unittest" in sys.modules:
+ ut = sys.modules["twisted.python.failure"]
+ Failure__init__ = ut.Failure.__init__
+ check_testcase_implements_trial_reporter()
+
+ def excstore(
+ self, exc_value=None, exc_type=None, exc_tb=None, captureVars=None
+ ):
+ if exc_value is None:
+ self._rawexcinfo = sys.exc_info()
+ else:
+ if exc_type is None:
+ exc_type = type(exc_value)
+ self._rawexcinfo = (exc_type, exc_value, exc_tb)
+ try:
+ Failure__init__(
+ self, exc_value, exc_type, exc_tb, captureVars=captureVars
+ )
+ except TypeError:
+ Failure__init__(self, exc_value, exc_type, exc_tb)
+
+ ut.Failure.__init__ = excstore
+ yield
+ ut.Failure.__init__ = Failure__init__
+ else:
+ yield
+
+
+def check_testcase_implements_trial_reporter(done=[]):
+ if done:
+ return
+ from zope.interface import classImplements
+ from twisted.trial.itrial import IReporter
+
+ classImplements(TestCaseFunction, IReporter)
+ done.append(1)
diff --git a/contrib/python/pytest/py2/_pytest/warning_types.py b/contrib/python/pytest/py2/_pytest/warning_types.py
index 1af4c65eb4..861010a127 100644
--- a/contrib/python/pytest/py2/_pytest/warning_types.py
+++ b/contrib/python/pytest/py2/_pytest/warning_types.py
@@ -1,31 +1,31 @@
# -*- coding: utf-8 -*-
-import attr
-
-
-class PytestWarning(UserWarning):
- """
- Bases: :class:`UserWarning`.
-
- Base class for all warnings emitted by pytest.
- """
-
-
+import attr
+
+
+class PytestWarning(UserWarning):
+ """
+ Bases: :class:`UserWarning`.
+
+ Base class for all warnings emitted by pytest.
+ """
+
+
class PytestAssertRewriteWarning(PytestWarning):
- """
+ """
Bases: :class:`PytestWarning`.
-
+
Warning emitted by the pytest assert rewrite module.
- """
-
-
+ """
+
+
class PytestCacheWarning(PytestWarning):
- """
+ """
Bases: :class:`PytestWarning`.
-
+
Warning emitted by the cache plugin in various situations.
- """
-
-
+ """
+
+
class PytestConfigWarning(PytestWarning):
"""
Bases: :class:`PytestWarning`.
@@ -50,23 +50,23 @@ class PytestDeprecationWarning(PytestWarning, DeprecationWarning):
"""
-class PytestExperimentalApiWarning(PytestWarning, FutureWarning):
- """
- Bases: :class:`pytest.PytestWarning`, :class:`FutureWarning`.
-
- Warning category used to denote experiments in pytest. Use sparingly as the API might change or even be
- removed completely in future version
- """
-
- @classmethod
- def simple(cls, apiname):
- return cls(
- "{apiname} is an experimental api that may change over time".format(
- apiname=apiname
- )
- )
-
-
+class PytestExperimentalApiWarning(PytestWarning, FutureWarning):
+ """
+ Bases: :class:`pytest.PytestWarning`, :class:`FutureWarning`.
+
+ Warning category used to denote experiments in pytest. Use sparingly as the API might change or even be
+ removed completely in future version
+ """
+
+ @classmethod
+ def simple(cls, apiname):
+ return cls(
+ "{apiname} is an experimental api that may change over time".format(
+ apiname=apiname
+ )
+ )
+
+
class PytestUnhandledCoroutineWarning(PytestWarning):
"""
Bases: :class:`PytestWarning`.
@@ -94,19 +94,19 @@ class RemovedInPytest4Warning(PytestDeprecationWarning):
"""
-@attr.s
-class UnformattedWarning(object):
- """Used to hold warnings that need to format their message at runtime, as opposed to a direct message.
-
- Using this class avoids to keep all the warning types and messages in this module, avoiding misuse.
- """
-
- category = attr.ib()
- template = attr.ib()
-
- def format(self, **kwargs):
- """Returns an instance of the warning category, formatted with given kwargs"""
- return self.category(self.template.format(**kwargs))
-
-
-PYTESTER_COPY_EXAMPLE = PytestExperimentalApiWarning.simple("testdir.copy_example")
+@attr.s
+class UnformattedWarning(object):
+ """Used to hold warnings that need to format their message at runtime, as opposed to a direct message.
+
+ Using this class avoids to keep all the warning types and messages in this module, avoiding misuse.
+ """
+
+ category = attr.ib()
+ template = attr.ib()
+
+ def format(self, **kwargs):
+ """Returns an instance of the warning category, formatted with given kwargs"""
+ return self.category(self.template.format(**kwargs))
+
+
+PYTESTER_COPY_EXAMPLE = PytestExperimentalApiWarning.simple("testdir.copy_example")
diff --git a/contrib/python/pytest/py2/_pytest/warnings.py b/contrib/python/pytest/py2/_pytest/warnings.py
index 4e93441205..a3debae462 100644
--- a/contrib/python/pytest/py2/_pytest/warnings.py
+++ b/contrib/python/pytest/py2/_pytest/warnings.py
@@ -1,180 +1,180 @@
# -*- coding: utf-8 -*-
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import sys
-import warnings
-from contextlib import contextmanager
-
-import pytest
-from _pytest import compat
-
-SHOW_PYTEST_WARNINGS_ARG = "-Walways::pytest.RemovedInPytest4Warning"
-
-
-def _setoption(wmod, arg):
- """
- Copy of the warning._setoption function but does not escape arguments.
- """
- parts = arg.split(":")
- if len(parts) > 5:
- raise wmod._OptionError("too many fields (max 5): %r" % (arg,))
- while len(parts) < 5:
- parts.append("")
- action, message, category, module, lineno = [s.strip() for s in parts]
- action = wmod._getaction(action)
- category = wmod._getcategory(category)
- if lineno:
- try:
- lineno = int(lineno)
- if lineno < 0:
- raise ValueError
- except (ValueError, OverflowError):
- raise wmod._OptionError("invalid lineno %r" % (lineno,))
- else:
- lineno = 0
- wmod.filterwarnings(action, message, category, module, lineno)
-
-
-def pytest_addoption(parser):
- group = parser.getgroup("pytest-warnings")
- group.addoption(
- "-W",
- "--pythonwarnings",
- action="append",
- help="set which warnings to report, see -W option of python itself.",
- )
- parser.addini(
- "filterwarnings",
- type="linelist",
- help="Each line specifies a pattern for "
- "warnings.filterwarnings. "
- "Processed after -W and --pythonwarnings.",
- )
-
-
-def pytest_configure(config):
- config.addinivalue_line(
- "markers",
- "filterwarnings(warning): add a warning filter to the given test. "
- "see https://docs.pytest.org/en/latest/warnings.html#pytest-mark-filterwarnings ",
- )
-
-
-@contextmanager
-def catch_warnings_for_item(config, ihook, when, item):
- """
- Context manager that catches warnings generated in the contained execution block.
-
- ``item`` can be None if we are not in the context of an item execution.
-
- Each warning captured triggers the ``pytest_warning_captured`` hook.
- """
- cmdline_filters = config.getoption("pythonwarnings") or []
- inifilters = config.getini("filterwarnings")
- with warnings.catch_warnings(record=True) as log:
-
- if not sys.warnoptions:
- # if user is not explicitly configuring warning filters, show deprecation warnings by default (#2908)
- warnings.filterwarnings("always", category=DeprecationWarning)
- warnings.filterwarnings("always", category=PendingDeprecationWarning)
-
- warnings.filterwarnings("error", category=pytest.RemovedInPytest4Warning)
-
- # filters should have this precedence: mark, cmdline options, ini
- # filters should be applied in the inverse order of precedence
- for arg in inifilters:
- _setoption(warnings, arg)
-
- for arg in cmdline_filters:
- warnings._setoption(arg)
-
- if item is not None:
- for mark in item.iter_markers(name="filterwarnings"):
- for arg in mark.args:
- _setoption(warnings, arg)
-
- yield
-
- for warning_message in log:
- ihook.pytest_warning_captured.call_historic(
- kwargs=dict(warning_message=warning_message, when=when, item=item)
- )
-
-
-def warning_record_to_str(warning_message):
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import sys
+import warnings
+from contextlib import contextmanager
+
+import pytest
+from _pytest import compat
+
+SHOW_PYTEST_WARNINGS_ARG = "-Walways::pytest.RemovedInPytest4Warning"
+
+
+def _setoption(wmod, arg):
+ """
+ Copy of the warning._setoption function but does not escape arguments.
+ """
+ parts = arg.split(":")
+ if len(parts) > 5:
+ raise wmod._OptionError("too many fields (max 5): %r" % (arg,))
+ while len(parts) < 5:
+ parts.append("")
+ action, message, category, module, lineno = [s.strip() for s in parts]
+ action = wmod._getaction(action)
+ category = wmod._getcategory(category)
+ if lineno:
+ try:
+ lineno = int(lineno)
+ if lineno < 0:
+ raise ValueError
+ except (ValueError, OverflowError):
+ raise wmod._OptionError("invalid lineno %r" % (lineno,))
+ else:
+ lineno = 0
+ wmod.filterwarnings(action, message, category, module, lineno)
+
+
+def pytest_addoption(parser):
+ group = parser.getgroup("pytest-warnings")
+ group.addoption(
+ "-W",
+ "--pythonwarnings",
+ action="append",
+ help="set which warnings to report, see -W option of python itself.",
+ )
+ parser.addini(
+ "filterwarnings",
+ type="linelist",
+ help="Each line specifies a pattern for "
+ "warnings.filterwarnings. "
+ "Processed after -W and --pythonwarnings.",
+ )
+
+
+def pytest_configure(config):
+ config.addinivalue_line(
+ "markers",
+ "filterwarnings(warning): add a warning filter to the given test. "
+ "see https://docs.pytest.org/en/latest/warnings.html#pytest-mark-filterwarnings ",
+ )
+
+
+@contextmanager
+def catch_warnings_for_item(config, ihook, when, item):
+ """
+ Context manager that catches warnings generated in the contained execution block.
+
+ ``item`` can be None if we are not in the context of an item execution.
+
+ Each warning captured triggers the ``pytest_warning_captured`` hook.
+ """
+ cmdline_filters = config.getoption("pythonwarnings") or []
+ inifilters = config.getini("filterwarnings")
+ with warnings.catch_warnings(record=True) as log:
+
+ if not sys.warnoptions:
+ # if user is not explicitly configuring warning filters, show deprecation warnings by default (#2908)
+ warnings.filterwarnings("always", category=DeprecationWarning)
+ warnings.filterwarnings("always", category=PendingDeprecationWarning)
+
+ warnings.filterwarnings("error", category=pytest.RemovedInPytest4Warning)
+
+ # filters should have this precedence: mark, cmdline options, ini
+ # filters should be applied in the inverse order of precedence
+ for arg in inifilters:
+ _setoption(warnings, arg)
+
+ for arg in cmdline_filters:
+ warnings._setoption(arg)
+
+ if item is not None:
+ for mark in item.iter_markers(name="filterwarnings"):
+ for arg in mark.args:
+ _setoption(warnings, arg)
+
+ yield
+
+ for warning_message in log:
+ ihook.pytest_warning_captured.call_historic(
+ kwargs=dict(warning_message=warning_message, when=when, item=item)
+ )
+
+
+def warning_record_to_str(warning_message):
"""Convert a warnings.WarningMessage to a string.
-
+
This takes lot of unicode shenaningans into account for Python 2.
- When Python 2 support is dropped this function can be greatly simplified.
- """
- warn_msg = warning_message.message
- unicode_warning = False
- if compat._PY2 and any(isinstance(m, compat.UNICODE_TYPES) for m in warn_msg.args):
- new_args = []
- for m in warn_msg.args:
- new_args.append(
- compat.ascii_escaped(m) if isinstance(m, compat.UNICODE_TYPES) else m
- )
- unicode_warning = list(warn_msg.args) != new_args
- warn_msg.args = new_args
-
- msg = warnings.formatwarning(
- warn_msg,
- warning_message.category,
- warning_message.filename,
- warning_message.lineno,
- warning_message.line,
- )
- if unicode_warning:
- warnings.warn(
- "Warning is using unicode non convertible to ascii, "
- "converting to a safe representation:\n {!r}".format(compat.safe_str(msg)),
- UnicodeWarning,
- )
- return msg
-
-
-@pytest.hookimpl(hookwrapper=True, tryfirst=True)
-def pytest_runtest_protocol(item):
- with catch_warnings_for_item(
- config=item.config, ihook=item.ihook, when="runtest", item=item
- ):
- yield
-
-
-@pytest.hookimpl(hookwrapper=True, tryfirst=True)
-def pytest_collection(session):
- config = session.config
- with catch_warnings_for_item(
- config=config, ihook=config.hook, when="collect", item=None
- ):
- yield
-
-
-@pytest.hookimpl(hookwrapper=True)
-def pytest_terminal_summary(terminalreporter):
- config = terminalreporter.config
- with catch_warnings_for_item(
- config=config, ihook=config.hook, when="config", item=None
- ):
- yield
-
-
+ When Python 2 support is dropped this function can be greatly simplified.
+ """
+ warn_msg = warning_message.message
+ unicode_warning = False
+ if compat._PY2 and any(isinstance(m, compat.UNICODE_TYPES) for m in warn_msg.args):
+ new_args = []
+ for m in warn_msg.args:
+ new_args.append(
+ compat.ascii_escaped(m) if isinstance(m, compat.UNICODE_TYPES) else m
+ )
+ unicode_warning = list(warn_msg.args) != new_args
+ warn_msg.args = new_args
+
+ msg = warnings.formatwarning(
+ warn_msg,
+ warning_message.category,
+ warning_message.filename,
+ warning_message.lineno,
+ warning_message.line,
+ )
+ if unicode_warning:
+ warnings.warn(
+ "Warning is using unicode non convertible to ascii, "
+ "converting to a safe representation:\n {!r}".format(compat.safe_str(msg)),
+ UnicodeWarning,
+ )
+ return msg
+
+
+@pytest.hookimpl(hookwrapper=True, tryfirst=True)
+def pytest_runtest_protocol(item):
+ with catch_warnings_for_item(
+ config=item.config, ihook=item.ihook, when="runtest", item=item
+ ):
+ yield
+
+
+@pytest.hookimpl(hookwrapper=True, tryfirst=True)
+def pytest_collection(session):
+ config = session.config
+ with catch_warnings_for_item(
+ config=config, ihook=config.hook, when="collect", item=None
+ ):
+ yield
+
+
+@pytest.hookimpl(hookwrapper=True)
+def pytest_terminal_summary(terminalreporter):
+ config = terminalreporter.config
+ with catch_warnings_for_item(
+ config=config, ihook=config.hook, when="config", item=None
+ ):
+ yield
+
+
def _issue_warning_captured(warning, hook, stacklevel):
- """
- This function should be used instead of calling ``warnings.warn`` directly when we are in the "configure" stage:
- at this point the actual options might not have been set, so we manually trigger the pytest_warning_captured
- hook so we can display this warnings in the terminal. This is a hack until we can sort out #2891.
-
- :param warning: the warning instance.
+ """
+ This function should be used instead of calling ``warnings.warn`` directly when we are in the "configure" stage:
+ at this point the actual options might not have been set, so we manually trigger the pytest_warning_captured
+ hook so we can display this warnings in the terminal. This is a hack until we can sort out #2891.
+
+ :param warning: the warning instance.
:param hook: the hook caller
- :param stacklevel: stacklevel forwarded to warnings.warn
- """
- with warnings.catch_warnings(record=True) as records:
- warnings.simplefilter("always", type(warning))
- warnings.warn(warning, stacklevel=stacklevel)
+ :param stacklevel: stacklevel forwarded to warnings.warn
+ """
+ with warnings.catch_warnings(record=True) as records:
+ warnings.simplefilter("always", type(warning))
+ warnings.warn(warning, stacklevel=stacklevel)
hook.pytest_warning_captured.call_historic(
- kwargs=dict(warning_message=records[0], when="config", item=None)
- )
+ kwargs=dict(warning_message=records[0], when="config", item=None)
+ )