Merge remote-tracking branch 'upstream/master' into features
This commit is contained in:
commit
f8f1a52ea0
|
@ -157,10 +157,10 @@ Bug Fixes
|
||||||
<https://github.com/pytest-dev/pytest/issues/2124>`_)
|
<https://github.com/pytest-dev/pytest/issues/2124>`_)
|
||||||
|
|
||||||
- If an exception happens while loading a plugin, pytest no longer hides the
|
- If an exception happens while loading a plugin, pytest no longer hides the
|
||||||
original traceback. In python2 it will show the original traceback with a new
|
original traceback. In Python 2 it will show the original traceback with a new
|
||||||
message that explains in which plugin. In python3 it will show 2 canonized
|
message that explains in which plugin. In Python 3 it will show 2 canonized
|
||||||
exceptions, the original exception while loading the plugin in addition to an
|
exceptions, the original exception while loading the plugin in addition to an
|
||||||
exception that PyTest throws about loading a plugin. (`#2491
|
exception that pytest throws about loading a plugin. (`#2491
|
||||||
<https://github.com/pytest-dev/pytest/issues/2491>`_)
|
<https://github.com/pytest-dev/pytest/issues/2491>`_)
|
||||||
|
|
||||||
- ``capsys`` and ``capfd`` can now be used by other fixtures. (`#2709
|
- ``capsys`` and ``capfd`` can now be used by other fixtures. (`#2709
|
||||||
|
|
|
@ -1,19 +1,15 @@
|
||||||
from __future__ import absolute_import, division, generators, print_function
|
from __future__ import absolute_import, division, generators, print_function
|
||||||
|
|
||||||
|
import ast
|
||||||
|
from ast import PyCF_ONLY_AST as _AST_FLAG
|
||||||
from bisect import bisect_right
|
from bisect import bisect_right
|
||||||
import sys
|
import sys
|
||||||
import six
|
import six
|
||||||
import inspect
|
import inspect
|
||||||
import tokenize
|
import tokenize
|
||||||
import py
|
import py
|
||||||
cpy_compile = compile
|
|
||||||
|
|
||||||
try:
|
cpy_compile = compile
|
||||||
import _ast
|
|
||||||
from _ast import PyCF_ONLY_AST as _AST_FLAG
|
|
||||||
except ImportError:
|
|
||||||
_AST_FLAG = 0
|
|
||||||
_ast = None
|
|
||||||
|
|
||||||
|
|
||||||
class Source(object):
|
class Source(object):
|
||||||
|
@ -209,7 +205,7 @@ def compile_(source, filename=None, mode='exec', flags=generators.compiler_flag,
|
||||||
retrieval of the source code for the code object
|
retrieval of the source code for the code object
|
||||||
and any recursively created code objects.
|
and any recursively created code objects.
|
||||||
"""
|
"""
|
||||||
if _ast is not None and isinstance(source, _ast.AST):
|
if isinstance(source, ast.AST):
|
||||||
# XXX should Source support having AST?
|
# XXX should Source support having AST?
|
||||||
return cpy_compile(source, filename, mode, flags, dont_inherit)
|
return cpy_compile(source, filename, mode, flags, dont_inherit)
|
||||||
_genframe = sys._getframe(1) # the caller
|
_genframe = sys._getframe(1) # the caller
|
||||||
|
@ -322,7 +318,7 @@ def get_statement_startend2(lineno, node):
|
||||||
# AST's line numbers start indexing at 1
|
# AST's line numbers start indexing at 1
|
||||||
values = []
|
values = []
|
||||||
for x in ast.walk(node):
|
for x in ast.walk(node):
|
||||||
if isinstance(x, _ast.stmt) or isinstance(x, _ast.ExceptHandler):
|
if isinstance(x, ast.stmt) or isinstance(x, ast.ExceptHandler):
|
||||||
values.append(x.lineno - 1)
|
values.append(x.lineno - 1)
|
||||||
for name in "finalbody", "orelse":
|
for name in "finalbody", "orelse":
|
||||||
val = getattr(x, name, None)
|
val = getattr(x, name, None)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
"""Rewrite assertion AST to produce nice error messages"""
|
"""Rewrite assertion AST to produce nice error messages"""
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
import ast
|
import ast
|
||||||
import _ast
|
|
||||||
import errno
|
import errno
|
||||||
import itertools
|
import itertools
|
||||||
import imp
|
import imp
|
||||||
|
@ -914,7 +913,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
def visit_Compare(self, comp):
|
def visit_Compare(self, comp):
|
||||||
self.push_format_context()
|
self.push_format_context()
|
||||||
left_res, left_expl = self.visit(comp.left)
|
left_res, left_expl = self.visit(comp.left)
|
||||||
if isinstance(comp.left, (_ast.Compare, _ast.BoolOp)):
|
if isinstance(comp.left, (ast.Compare, ast.BoolOp)):
|
||||||
left_expl = "({0})".format(left_expl)
|
left_expl = "({0})".format(left_expl)
|
||||||
res_variables = [self.variable() for i in range(len(comp.ops))]
|
res_variables = [self.variable() for i in range(len(comp.ops))]
|
||||||
load_names = [ast.Name(v, ast.Load()) for v in res_variables]
|
load_names = [ast.Name(v, ast.Load()) for v in res_variables]
|
||||||
|
@ -925,7 +924,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
results = [left_res]
|
results = [left_res]
|
||||||
for i, op, next_operand in it:
|
for i, op, next_operand in it:
|
||||||
next_res, next_expl = self.visit(next_operand)
|
next_res, next_expl = self.visit(next_operand)
|
||||||
if isinstance(next_operand, (_ast.Compare, _ast.BoolOp)):
|
if isinstance(next_operand, (ast.Compare, ast.BoolOp)):
|
||||||
next_expl = "({0})".format(next_expl)
|
next_expl = "({0})".format(next_expl)
|
||||||
results.append(next_res)
|
results.append(next_res)
|
||||||
sym = binop_map[op.__class__]
|
sym = binop_map[op.__class__]
|
||||||
|
|
|
@ -5,11 +5,7 @@ import pprint
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
import py
|
import py
|
||||||
import six
|
import six
|
||||||
try:
|
from collections import Sequence
|
||||||
from collections import Sequence
|
|
||||||
except ImportError:
|
|
||||||
Sequence = list
|
|
||||||
|
|
||||||
|
|
||||||
u = six.text_type
|
u = six.text_type
|
||||||
|
|
||||||
|
@ -113,7 +109,7 @@ def assertrepr_compare(config, op, left, right):
|
||||||
summary = u('%s %s %s') % (ecu(left_repr), op, ecu(right_repr))
|
summary = u('%s %s %s') % (ecu(left_repr), op, ecu(right_repr))
|
||||||
|
|
||||||
def issequence(x):
|
def issequence(x):
|
||||||
return (isinstance(x, (list, tuple, Sequence)) and not isinstance(x, basestring))
|
return isinstance(x, Sequence) and not isinstance(x, basestring)
|
||||||
|
|
||||||
def istext(x):
|
def istext(x):
|
||||||
return isinstance(x, basestring)
|
return isinstance(x, basestring)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
""" (disabled by default) support for testing pytest and pytest plugins. """
|
"""(disabled by default) support for testing pytest and pytest plugins."""
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
import codecs
|
import codecs
|
||||||
|
@ -27,13 +27,12 @@ PYTEST_FULLPATH = os.path.abspath(pytest.__file__.rstrip("oc")).replace("$py.cla
|
||||||
|
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
# group = parser.getgroup("pytester", "pytester (self-tests) options")
|
|
||||||
parser.addoption('--lsof',
|
parser.addoption('--lsof',
|
||||||
action="store_true", dest="lsof", default=False,
|
action="store_true", dest="lsof", default=False,
|
||||||
help=("run FD checks if lsof is available"))
|
help=("run FD checks if lsof is available"))
|
||||||
|
|
||||||
parser.addoption('--runpytest', default="inprocess", dest="runpytest",
|
parser.addoption('--runpytest', default="inprocess", dest="runpytest",
|
||||||
choices=("inprocess", "subprocess", ),
|
choices=("inprocess", "subprocess"),
|
||||||
help=("run pytest sub runs in tests using an 'inprocess' "
|
help=("run pytest sub runs in tests using an 'inprocess' "
|
||||||
"or 'subprocess' (python -m main) method"))
|
"or 'subprocess' (python -m main) method"))
|
||||||
|
|
||||||
|
@ -76,8 +75,8 @@ class LsofFdLeakChecker(object):
|
||||||
try:
|
try:
|
||||||
py.process.cmdexec("lsof -v")
|
py.process.cmdexec("lsof -v")
|
||||||
except (py.process.cmdexec.Error, UnicodeDecodeError):
|
except (py.process.cmdexec.Error, UnicodeDecodeError):
|
||||||
# cmdexec may raise UnicodeDecodeError on Windows systems
|
# cmdexec may raise UnicodeDecodeError on Windows systems with
|
||||||
# with locale other than english:
|
# locale other than English:
|
||||||
# https://bitbucket.org/pytest-dev/py/issues/66
|
# https://bitbucket.org/pytest-dev/py/issues/66
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
@ -132,7 +131,7 @@ def getexecutable(name, cache={}):
|
||||||
if "2.5.2" in err:
|
if "2.5.2" in err:
|
||||||
executable = None # http://bugs.jython.org/issue1790
|
executable = None # http://bugs.jython.org/issue1790
|
||||||
elif popen.returncode != 0:
|
elif popen.returncode != 0:
|
||||||
# Handle pyenv's 127.
|
# handle pyenv's 127
|
||||||
executable = None
|
executable = None
|
||||||
cache[name] = executable
|
cache[name] = executable
|
||||||
return executable
|
return executable
|
||||||
|
@ -157,9 +156,10 @@ def anypython(request):
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def _pytest(request):
|
def _pytest(request):
|
||||||
""" Return a helper which offers a gethookrecorder(hook)
|
"""Return a helper which offers a gethookrecorder(hook) method which
|
||||||
method which returns a HookRecorder instance which helps
|
returns a HookRecorder instance which helps to make assertions about called
|
||||||
to make assertions about called hooks.
|
hooks.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return PytestArg(request)
|
return PytestArg(request)
|
||||||
|
|
||||||
|
@ -193,8 +193,8 @@ class ParsedCall:
|
||||||
class HookRecorder:
|
class HookRecorder:
|
||||||
"""Record all hooks called in a plugin manager.
|
"""Record all hooks called in a plugin manager.
|
||||||
|
|
||||||
This wraps all the hook calls in the plugin manager, recording
|
This wraps all the hook calls in the plugin manager, recording each call
|
||||||
each call before propagating the normal calls.
|
before propagating the normal calls.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -262,7 +262,7 @@ class HookRecorder:
|
||||||
|
|
||||||
def matchreport(self, inamepart="",
|
def matchreport(self, inamepart="",
|
||||||
names="pytest_runtest_logreport pytest_collectreport", when=None):
|
names="pytest_runtest_logreport pytest_collectreport", when=None):
|
||||||
""" return a testreport whose dotted import path matches """
|
"""return a testreport whose dotted import path matches"""
|
||||||
values = []
|
values = []
|
||||||
for rep in self.getreports(names=names):
|
for rep in self.getreports(names=names):
|
||||||
try:
|
try:
|
||||||
|
@ -341,14 +341,14 @@ class RunResult:
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
|
|
||||||
:ret: The return value.
|
:ret: the return value
|
||||||
:outlines: List of lines captured from stdout.
|
:outlines: list of lines captured from stdout
|
||||||
:errlines: List of lines captures from stderr.
|
:errlines: list of lines captures from stderr
|
||||||
:stdout: :py:class:`LineMatcher` of stdout, use ``stdout.str()`` to
|
:stdout: :py:class:`LineMatcher` of stdout, use ``stdout.str()`` to
|
||||||
reconstruct stdout or the commonly used
|
reconstruct stdout or the commonly used ``stdout.fnmatch_lines()``
|
||||||
``stdout.fnmatch_lines()`` method.
|
method
|
||||||
:stderrr: :py:class:`LineMatcher` of stderr.
|
:stderrr: :py:class:`LineMatcher` of stderr
|
||||||
:duration: Duration in seconds.
|
:duration: duration in seconds
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -361,8 +361,10 @@ class RunResult:
|
||||||
self.duration = duration
|
self.duration = duration
|
||||||
|
|
||||||
def parseoutcomes(self):
|
def parseoutcomes(self):
|
||||||
""" Return a dictionary of outcomestring->num from parsing
|
"""Return a dictionary of outcomestring->num from parsing the terminal
|
||||||
the terminal output that the test process produced."""
|
output that the test process produced.
|
||||||
|
|
||||||
|
"""
|
||||||
for line in reversed(self.outlines):
|
for line in reversed(self.outlines):
|
||||||
if 'seconds' in line:
|
if 'seconds' in line:
|
||||||
outcomes = rex_outcome.findall(line)
|
outcomes = rex_outcome.findall(line)
|
||||||
|
@ -374,8 +376,10 @@ class RunResult:
|
||||||
raise ValueError("Pytest terminal report not found")
|
raise ValueError("Pytest terminal report not found")
|
||||||
|
|
||||||
def assert_outcomes(self, passed=0, skipped=0, failed=0, error=0):
|
def assert_outcomes(self, passed=0, skipped=0, failed=0, error=0):
|
||||||
""" assert that the specified outcomes appear with the respective
|
"""Assert that the specified outcomes appear with the respective
|
||||||
numbers (0 means it didn't occur) in the text output from a test run."""
|
numbers (0 means it didn't occur) in the text output from a test run.
|
||||||
|
|
||||||
|
"""
|
||||||
d = self.parseoutcomes()
|
d = self.parseoutcomes()
|
||||||
obtained = {
|
obtained = {
|
||||||
'passed': d.get('passed', 0),
|
'passed': d.get('passed', 0),
|
||||||
|
@ -389,21 +393,18 @@ class RunResult:
|
||||||
class Testdir:
|
class Testdir:
|
||||||
"""Temporary test directory with tools to test/run pytest itself.
|
"""Temporary test directory with tools to test/run pytest itself.
|
||||||
|
|
||||||
This is based on the ``tmpdir`` fixture but provides a number of
|
This is based on the ``tmpdir`` fixture but provides a number of methods
|
||||||
methods which aid with testing pytest itself. Unless
|
which aid with testing pytest itself. Unless :py:meth:`chdir` is used all
|
||||||
:py:meth:`chdir` is used all methods will use :py:attr:`tmpdir` as
|
methods will use :py:attr:`tmpdir` as their current working directory.
|
||||||
current working directory.
|
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
|
|
||||||
:tmpdir: The :py:class:`py.path.local` instance of the temporary
|
:tmpdir: The :py:class:`py.path.local` instance of the temporary directory.
|
||||||
directory.
|
|
||||||
|
|
||||||
:plugins: A list of plugins to use with :py:meth:`parseconfig` and
|
:plugins: A list of plugins to use with :py:meth:`parseconfig` and
|
||||||
:py:meth:`runpytest`. Initially this is an empty list but
|
:py:meth:`runpytest`. Initially this is an empty list but plugins can
|
||||||
plugins can be added to the list. The type of items to add to
|
be added to the list. The type of items to add to the list depends on
|
||||||
the list depend on the method which uses them so refer to them
|
the method using them so refer to them for details.
|
||||||
for details.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -429,10 +430,9 @@ class Testdir:
|
||||||
def finalize(self):
|
def finalize(self):
|
||||||
"""Clean up global state artifacts.
|
"""Clean up global state artifacts.
|
||||||
|
|
||||||
Some methods modify the global interpreter state and this
|
Some methods modify the global interpreter state and this tries to
|
||||||
tries to clean this up. It does not remove the temporary
|
clean this up. It does not remove the temporary directory however so
|
||||||
directory however so it can be looked at after the test run
|
it can be looked at after the test run has finished.
|
||||||
has finished.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
sys.path[:], sys.meta_path[:] = self._savesyspath
|
sys.path[:], sys.meta_path[:] = self._savesyspath
|
||||||
|
@ -495,17 +495,15 @@ class Testdir:
|
||||||
def makefile(self, ext, *args, **kwargs):
|
def makefile(self, ext, *args, **kwargs):
|
||||||
"""Create a new file in the testdir.
|
"""Create a new file in the testdir.
|
||||||
|
|
||||||
ext: The extension the file should use, including the dot.
|
ext: The extension the file should use, including the dot, e.g. `.py`.
|
||||||
E.g. ".py".
|
|
||||||
|
|
||||||
args: All args will be treated as strings and joined using
|
args: All args will be treated as strings and joined using newlines.
|
||||||
newlines. The result will be written as contents to the
|
The result will be written as contents to the file. The name of the
|
||||||
file. The name of the file will be based on the test
|
file will be based on the test function requesting this fixture.
|
||||||
function requesting this fixture.
|
|
||||||
E.g. "testdir.makefile('.txt', 'line1', 'line2')"
|
E.g. "testdir.makefile('.txt', 'line1', 'line2')"
|
||||||
|
|
||||||
kwargs: Each keyword is the name of a file, while the value of
|
kwargs: Each keyword is the name of a file, while the value of it will
|
||||||
it will be written as contents of the file.
|
be written as contents of the file.
|
||||||
E.g. "testdir.makefile('.ini', pytest='[pytest]\naddopts=-rs\n')"
|
E.g. "testdir.makefile('.ini', pytest='[pytest]\naddopts=-rs\n')"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -535,14 +533,16 @@ class Testdir:
|
||||||
def syspathinsert(self, path=None):
|
def syspathinsert(self, path=None):
|
||||||
"""Prepend a directory to sys.path, defaults to :py:attr:`tmpdir`.
|
"""Prepend a directory to sys.path, defaults to :py:attr:`tmpdir`.
|
||||||
|
|
||||||
This is undone automatically after the test.
|
This is undone automatically when this object dies at the end of each
|
||||||
|
test.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if path is None:
|
if path is None:
|
||||||
path = self.tmpdir
|
path = self.tmpdir
|
||||||
sys.path.insert(0, str(path))
|
sys.path.insert(0, str(path))
|
||||||
# a call to syspathinsert() usually means that the caller
|
# a call to syspathinsert() usually means that the caller wants to
|
||||||
# wants to import some dynamically created files.
|
# import some dynamically created files, thus with python3 we
|
||||||
# with python3 we thus invalidate import caches.
|
# invalidate its import caches
|
||||||
self._possibly_invalidate_import_caches()
|
self._possibly_invalidate_import_caches()
|
||||||
|
|
||||||
def _possibly_invalidate_import_caches(self):
|
def _possibly_invalidate_import_caches(self):
|
||||||
|
@ -562,8 +562,8 @@ class Testdir:
|
||||||
def mkpydir(self, name):
|
def mkpydir(self, name):
|
||||||
"""Create a new python package.
|
"""Create a new python package.
|
||||||
|
|
||||||
This creates a (sub)directory with an empty ``__init__.py``
|
This creates a (sub)directory with an empty ``__init__.py`` file so it
|
||||||
file so that is recognised as a python package.
|
gets recognised as a python package.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
p = self.mkdir(name)
|
p = self.mkdir(name)
|
||||||
|
@ -576,10 +576,10 @@ class Testdir:
|
||||||
"""Return the collection node of a file.
|
"""Return the collection node of a file.
|
||||||
|
|
||||||
:param config: :py:class:`_pytest.config.Config` instance, see
|
:param config: :py:class:`_pytest.config.Config` instance, see
|
||||||
:py:meth:`parseconfig` and :py:meth:`parseconfigure` to
|
:py:meth:`parseconfig` and :py:meth:`parseconfigure` to create the
|
||||||
create the configuration.
|
configuration
|
||||||
|
|
||||||
:param arg: A :py:class:`py.path.local` instance of the file.
|
:param arg: a :py:class:`py.path.local` instance of the file
|
||||||
|
|
||||||
"""
|
"""
|
||||||
session = Session(config)
|
session = Session(config)
|
||||||
|
@ -593,11 +593,10 @@ class Testdir:
|
||||||
def getpathnode(self, path):
|
def getpathnode(self, path):
|
||||||
"""Return the collection node of a file.
|
"""Return the collection node of a file.
|
||||||
|
|
||||||
This is like :py:meth:`getnode` but uses
|
This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to
|
||||||
:py:meth:`parseconfigure` to create the (configured) pytest
|
create the (configured) pytest Config instance.
|
||||||
Config instance.
|
|
||||||
|
|
||||||
:param path: A :py:class:`py.path.local` instance of the file.
|
:param path: a :py:class:`py.path.local` instance of the file
|
||||||
|
|
||||||
"""
|
"""
|
||||||
config = self.parseconfigure(path)
|
config = self.parseconfigure(path)
|
||||||
|
@ -611,8 +610,8 @@ class Testdir:
|
||||||
def genitems(self, colitems):
|
def genitems(self, colitems):
|
||||||
"""Generate all test items from a collection node.
|
"""Generate all test items from a collection node.
|
||||||
|
|
||||||
This recurses into the collection node and returns a list of
|
This recurses into the collection node and returns a list of all the
|
||||||
all the test items contained within.
|
test items contained within.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
session = colitems[0].session
|
session = colitems[0].session
|
||||||
|
@ -624,10 +623,10 @@ class Testdir:
|
||||||
def runitem(self, source):
|
def runitem(self, source):
|
||||||
"""Run the "test_func" Item.
|
"""Run the "test_func" Item.
|
||||||
|
|
||||||
The calling test instance (the class which contains the test
|
The calling test instance (class containing the test method) must
|
||||||
method) must provide a ``.getrunner()`` method which should
|
provide a ``.getrunner()`` method which should return a runner which
|
||||||
return a runner which can run the test protocol for a single
|
can run the test protocol for a single item, e.g.
|
||||||
item, like e.g. :py:func:`_pytest.runner.runtestprotocol`.
|
:py:func:`_pytest.runner.runtestprotocol`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# used from runner functional tests
|
# used from runner functional tests
|
||||||
|
@ -641,14 +640,14 @@ class Testdir:
|
||||||
"""Run a test module in process using ``pytest.main()``.
|
"""Run a test module in process using ``pytest.main()``.
|
||||||
|
|
||||||
This run writes "source" into a temporary file and runs
|
This run writes "source" into a temporary file and runs
|
||||||
``pytest.main()`` on it, returning a :py:class:`HookRecorder`
|
``pytest.main()`` on it, returning a :py:class:`HookRecorder` instance
|
||||||
instance for the result.
|
for the result.
|
||||||
|
|
||||||
:param source: The source code of the test module.
|
:param source: the source code of the test module
|
||||||
|
|
||||||
:param cmdlineargs: Any extra command line arguments to use.
|
:param cmdlineargs: any extra command line arguments to use
|
||||||
|
|
||||||
:return: :py:class:`HookRecorder` instance of the result.
|
:return: :py:class:`HookRecorder` instance of the result
|
||||||
|
|
||||||
"""
|
"""
|
||||||
p = self.makepyfile(source)
|
p = self.makepyfile(source)
|
||||||
|
@ -658,13 +657,9 @@ class Testdir:
|
||||||
def inline_genitems(self, *args):
|
def inline_genitems(self, *args):
|
||||||
"""Run ``pytest.main(['--collectonly'])`` in-process.
|
"""Run ``pytest.main(['--collectonly'])`` in-process.
|
||||||
|
|
||||||
Returns a tuple of the collected items and a
|
Runs the :py:func:`pytest.main` function to run all of pytest inside
|
||||||
:py:class:`HookRecorder` instance.
|
the test process itself like :py:meth:`inline_run`, but returns a
|
||||||
|
tuple of the collected items and a :py:class:`HookRecorder` instance.
|
||||||
This runs the :py:func:`pytest.main` function to run all of
|
|
||||||
pytest inside the test process itself like
|
|
||||||
:py:meth:`inline_run`. However the return value is a tuple of
|
|
||||||
the collection items and a :py:class:`HookRecorder` instance.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
rec = self.inline_run("--collect-only", *args)
|
rec = self.inline_run("--collect-only", *args)
|
||||||
|
@ -674,24 +669,24 @@ class Testdir:
|
||||||
def inline_run(self, *args, **kwargs):
|
def inline_run(self, *args, **kwargs):
|
||||||
"""Run ``pytest.main()`` in-process, returning a HookRecorder.
|
"""Run ``pytest.main()`` in-process, returning a HookRecorder.
|
||||||
|
|
||||||
This runs the :py:func:`pytest.main` function to run all of
|
Runs the :py:func:`pytest.main` function to run all of pytest inside
|
||||||
pytest inside the test process itself. This means it can
|
the test process itself. This means it can return a
|
||||||
return a :py:class:`HookRecorder` instance which gives more
|
:py:class:`HookRecorder` instance which gives more detailed results
|
||||||
detailed results from then run then can be done by matching
|
from that run than can be done by matching stdout/stderr from
|
||||||
stdout/stderr from :py:meth:`runpytest`.
|
:py:meth:`runpytest`.
|
||||||
|
|
||||||
:param args: Any command line arguments to pass to
|
:param args: command line arguments to pass to :py:func:`pytest.main`
|
||||||
:py:func:`pytest.main`.
|
|
||||||
|
|
||||||
:param plugin: (keyword-only) Extra plugin instances the
|
:param plugin: (keyword-only) extra plugin instances the
|
||||||
``pytest.main()`` instance should use.
|
``pytest.main()`` instance should use
|
||||||
|
|
||||||
|
:return: a :py:class:`HookRecorder` instance
|
||||||
|
|
||||||
:return: A :py:class:`HookRecorder` instance.
|
|
||||||
"""
|
"""
|
||||||
# When running py.test inline any plugins active in the main
|
# When running py.test inline any plugins active in the main test
|
||||||
# test process are already imported. So this disables the
|
# process are already imported. So this disables the warning which
|
||||||
# warning which will trigger to say they can no longer be
|
# will trigger to say they can no longer be rewritten, which is fine as
|
||||||
# rewritten, which is fine as they are already rewritten.
|
# they have already been rewritten.
|
||||||
orig_warn = AssertionRewritingHook._warn_already_imported
|
orig_warn = AssertionRewritingHook._warn_already_imported
|
||||||
|
|
||||||
def revert():
|
def revert():
|
||||||
|
@ -717,8 +712,8 @@ class Testdir:
|
||||||
pass
|
pass
|
||||||
reprec.ret = ret
|
reprec.ret = ret
|
||||||
|
|
||||||
# typically we reraise keyboard interrupts from the child run
|
# typically we reraise keyboard interrupts from the child run because
|
||||||
# because it's our user requesting interruption of the testing
|
# it's our user requesting interruption of the testing
|
||||||
if ret == 2 and not kwargs.get("no_reraise_ctrlc"):
|
if ret == 2 and not kwargs.get("no_reraise_ctrlc"):
|
||||||
calls = reprec.getcalls("pytest_keyboard_interrupt")
|
calls = reprec.getcalls("pytest_keyboard_interrupt")
|
||||||
if calls and calls[-1].excinfo.type == KeyboardInterrupt:
|
if calls and calls[-1].excinfo.type == KeyboardInterrupt:
|
||||||
|
@ -726,8 +721,10 @@ class Testdir:
|
||||||
return reprec
|
return reprec
|
||||||
|
|
||||||
def runpytest_inprocess(self, *args, **kwargs):
|
def runpytest_inprocess(self, *args, **kwargs):
|
||||||
""" Return result of running pytest in-process, providing a similar
|
"""Return result of running pytest in-process, providing a similar
|
||||||
interface to what self.runpytest() provides. """
|
interface to what self.runpytest() provides.
|
||||||
|
|
||||||
|
"""
|
||||||
if kwargs.get("syspathinsert"):
|
if kwargs.get("syspathinsert"):
|
||||||
self.syspathinsert()
|
self.syspathinsert()
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
@ -759,7 +756,7 @@ class Testdir:
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def runpytest(self, *args, **kwargs):
|
def runpytest(self, *args, **kwargs):
|
||||||
""" Run pytest inline or in a subprocess, depending on the command line
|
"""Run pytest inline or in a subprocess, depending on the command line
|
||||||
option "--runpytest" and return a :py:class:`RunResult`.
|
option "--runpytest" and return a :py:class:`RunResult`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -780,13 +777,13 @@ class Testdir:
|
||||||
def parseconfig(self, *args):
|
def parseconfig(self, *args):
|
||||||
"""Return a new pytest Config instance from given commandline args.
|
"""Return a new pytest Config instance from given commandline args.
|
||||||
|
|
||||||
This invokes the pytest bootstrapping code in _pytest.config
|
This invokes the pytest bootstrapping code in _pytest.config to create
|
||||||
to create a new :py:class:`_pytest.core.PluginManager` and
|
a new :py:class:`_pytest.core.PluginManager` and call the
|
||||||
call the pytest_cmdline_parse hook to create new
|
pytest_cmdline_parse hook to create a new
|
||||||
:py:class:`_pytest.config.Config` instance.
|
:py:class:`_pytest.config.Config` instance.
|
||||||
|
|
||||||
If :py:attr:`plugins` has been populated they should be plugin
|
If :py:attr:`plugins` has been populated they should be plugin modules
|
||||||
modules which will be registered with the PluginManager.
|
to be registered with the PluginManager.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
args = self._ensure_basetemp(args)
|
args = self._ensure_basetemp(args)
|
||||||
|
@ -802,9 +799,8 @@ class Testdir:
|
||||||
def parseconfigure(self, *args):
|
def parseconfigure(self, *args):
|
||||||
"""Return a new pytest configured Config instance.
|
"""Return a new pytest configured Config instance.
|
||||||
|
|
||||||
This returns a new :py:class:`_pytest.config.Config` instance
|
This returns a new :py:class:`_pytest.config.Config` instance like
|
||||||
like :py:meth:`parseconfig`, but also calls the
|
:py:meth:`parseconfig`, but also calls the pytest_configure hook.
|
||||||
pytest_configure hook.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
config = self.parseconfig(*args)
|
config = self.parseconfig(*args)
|
||||||
|
@ -815,14 +811,14 @@ class Testdir:
|
||||||
def getitem(self, source, funcname="test_func"):
|
def getitem(self, source, funcname="test_func"):
|
||||||
"""Return the test item for a test function.
|
"""Return the test item for a test function.
|
||||||
|
|
||||||
This writes the source to a python file and runs pytest's
|
This writes the source to a python file and runs pytest's collection on
|
||||||
collection on the resulting module, returning the test item
|
the resulting module, returning the test item for the requested
|
||||||
for the requested function name.
|
function name.
|
||||||
|
|
||||||
:param source: The module source.
|
:param source: the module source
|
||||||
|
|
||||||
:param funcname: The name of the test function for which the
|
:param funcname: the name of the test function for which to return a
|
||||||
Item must be returned.
|
test item
|
||||||
|
|
||||||
"""
|
"""
|
||||||
items = self.getitems(source)
|
items = self.getitems(source)
|
||||||
|
@ -835,9 +831,8 @@ class Testdir:
|
||||||
def getitems(self, source):
|
def getitems(self, source):
|
||||||
"""Return all test items collected from the module.
|
"""Return all test items collected from the module.
|
||||||
|
|
||||||
This writes the source to a python file and runs pytest's
|
This writes the source to a python file and runs pytest's collection on
|
||||||
collection on the resulting module, returning all test items
|
the resulting module, returning all test items contained within.
|
||||||
contained within.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
modcol = self.getmodulecol(source)
|
modcol = self.getmodulecol(source)
|
||||||
|
@ -846,17 +841,17 @@ class Testdir:
|
||||||
def getmodulecol(self, source, configargs=(), withinit=False):
|
def getmodulecol(self, source, configargs=(), withinit=False):
|
||||||
"""Return the module collection node for ``source``.
|
"""Return the module collection node for ``source``.
|
||||||
|
|
||||||
This writes ``source`` to a file using :py:meth:`makepyfile`
|
This writes ``source`` to a file using :py:meth:`makepyfile` and then
|
||||||
and then runs the pytest collection on it, returning the
|
runs the pytest collection on it, returning the collection node for the
|
||||||
collection node for the test module.
|
test module.
|
||||||
|
|
||||||
:param source: The source code of the module to collect.
|
:param source: the source code of the module to collect
|
||||||
|
|
||||||
:param configargs: Any extra arguments to pass to
|
:param configargs: any extra arguments to pass to
|
||||||
:py:meth:`parseconfigure`.
|
:py:meth:`parseconfigure`
|
||||||
|
|
||||||
:param withinit: Whether to also write a ``__init__.py`` file
|
:param withinit: whether to also write an ``__init__.py`` file to the
|
||||||
to the temporary directory to ensure it is a package.
|
same directory to ensure it is a package
|
||||||
|
|
||||||
"""
|
"""
|
||||||
kw = {self.request.function.__name__: Source(source).strip()}
|
kw = {self.request.function.__name__: Source(source).strip()}
|
||||||
|
@ -871,13 +866,12 @@ class Testdir:
|
||||||
def collect_by_name(self, modcol, name):
|
def collect_by_name(self, modcol, name):
|
||||||
"""Return the collection node for name from the module collection.
|
"""Return the collection node for name from the module collection.
|
||||||
|
|
||||||
This will search a module collection node for a collection
|
This will search a module collection node for a collection node
|
||||||
node matching the given name.
|
matching the given name.
|
||||||
|
|
||||||
:param modcol: A module collection node, see
|
:param modcol: a module collection node; see :py:meth:`getmodulecol`
|
||||||
:py:meth:`getmodulecol`.
|
|
||||||
|
|
||||||
:param name: The name of the node to return.
|
:param name: the name of the node to return
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if modcol not in self._mod_collections:
|
if modcol not in self._mod_collections:
|
||||||
|
@ -889,8 +883,8 @@ class Testdir:
|
||||||
def popen(self, cmdargs, stdout, stderr, **kw):
|
def popen(self, cmdargs, stdout, stderr, **kw):
|
||||||
"""Invoke subprocess.Popen.
|
"""Invoke subprocess.Popen.
|
||||||
|
|
||||||
This calls subprocess.Popen making sure the current working
|
This calls subprocess.Popen making sure the current working directory
|
||||||
directory is the PYTHONPATH.
|
is in the PYTHONPATH.
|
||||||
|
|
||||||
You probably want to use :py:meth:`run` instead.
|
You probably want to use :py:meth:`run` instead.
|
||||||
|
|
||||||
|
@ -908,8 +902,7 @@ class Testdir:
|
||||||
def run(self, *cmdargs):
|
def run(self, *cmdargs):
|
||||||
"""Run a command with arguments.
|
"""Run a command with arguments.
|
||||||
|
|
||||||
Run a process using subprocess.Popen saving the stdout and
|
Run a process using subprocess.Popen saving the stdout and stderr.
|
||||||
stderr.
|
|
||||||
|
|
||||||
Returns a :py:class:`RunResult`.
|
Returns a :py:class:`RunResult`.
|
||||||
|
|
||||||
|
@ -952,14 +945,15 @@ class Testdir:
|
||||||
print("couldn't print to %s because of encoding" % (fp,))
|
print("couldn't print to %s because of encoding" % (fp,))
|
||||||
|
|
||||||
def _getpytestargs(self):
|
def _getpytestargs(self):
|
||||||
# we cannot use "(sys.executable,script)"
|
# we cannot use `(sys.executable, script)` because on Windows the
|
||||||
# because on windows the script is e.g. a pytest.exe
|
# script is e.g. `pytest.exe`
|
||||||
return (sys.executable, PYTEST_FULLPATH) # noqa
|
return (sys.executable, PYTEST_FULLPATH) # noqa
|
||||||
|
|
||||||
def runpython(self, script):
|
def runpython(self, script):
|
||||||
"""Run a python script using sys.executable as interpreter.
|
"""Run a python script using sys.executable as interpreter.
|
||||||
|
|
||||||
Returns a :py:class:`RunResult`.
|
Returns a :py:class:`RunResult`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.run(sys.executable, script)
|
return self.run(sys.executable, script)
|
||||||
|
|
||||||
|
@ -970,25 +964,18 @@ class Testdir:
|
||||||
def runpytest_subprocess(self, *args, **kwargs):
|
def runpytest_subprocess(self, *args, **kwargs):
|
||||||
"""Run pytest as a subprocess with given arguments.
|
"""Run pytest as a subprocess with given arguments.
|
||||||
|
|
||||||
Any plugins added to the :py:attr:`plugins` list will added
|
Any plugins added to the :py:attr:`plugins` list will added using the
|
||||||
using the ``-p`` command line option. Addtionally
|
``-p`` command line option. Additionally ``--basetemp`` is used put
|
||||||
``--basetemp`` is used put any temporary files and directories
|
any temporary files and directories in a numbered directory prefixed
|
||||||
in a numbered directory prefixed with "runpytest-" so they do
|
with "runpytest-" so they do not conflict with the normal numbered
|
||||||
not conflict with the normal numberd pytest location for
|
pytest location for temporary files and directories.
|
||||||
temporary files and directories.
|
|
||||||
|
|
||||||
Returns a :py:class:`RunResult`.
|
Returns a :py:class:`RunResult`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
p = py.path.local.make_numbered_dir(prefix="runpytest-",
|
p = py.path.local.make_numbered_dir(prefix="runpytest-",
|
||||||
keep=None, rootdir=self.tmpdir)
|
keep=None, rootdir=self.tmpdir)
|
||||||
args = ('--basetemp=%s' % p, ) + args
|
args = ('--basetemp=%s' % p,) + args
|
||||||
# for x in args:
|
|
||||||
# if '--confcutdir' in str(x):
|
|
||||||
# break
|
|
||||||
# else:
|
|
||||||
# pass
|
|
||||||
# args = ('--confcutdir=.',) + args
|
|
||||||
plugins = [x for x in self.plugins if isinstance(x, str)]
|
plugins = [x for x in self.plugins if isinstance(x, str)]
|
||||||
if plugins:
|
if plugins:
|
||||||
args = ('-p', plugins[0]) + args
|
args = ('-p', plugins[0]) + args
|
||||||
|
@ -998,8 +985,8 @@ class Testdir:
|
||||||
def spawn_pytest(self, string, expect_timeout=10.0):
|
def spawn_pytest(self, string, expect_timeout=10.0):
|
||||||
"""Run pytest using pexpect.
|
"""Run pytest using pexpect.
|
||||||
|
|
||||||
This makes sure to use the right pytest and sets up the
|
This makes sure to use the right pytest and sets up the temporary
|
||||||
temporary directory locations.
|
directory locations.
|
||||||
|
|
||||||
The pexpect child is returned.
|
The pexpect child is returned.
|
||||||
|
|
||||||
|
@ -1013,6 +1000,7 @@ class Testdir:
|
||||||
"""Run a command using pexpect.
|
"""Run a command using pexpect.
|
||||||
|
|
||||||
The pexpect child is returned.
|
The pexpect child is returned.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pexpect = pytest.importorskip("pexpect", "3.0")
|
pexpect = pytest.importorskip("pexpect", "3.0")
|
||||||
if hasattr(sys, 'pypy_version_info') and '64' in platform.machine():
|
if hasattr(sys, 'pypy_version_info') and '64' in platform.machine():
|
||||||
|
@ -1039,8 +1027,10 @@ class LineComp:
|
||||||
self.stringio = py.io.TextIO()
|
self.stringio = py.io.TextIO()
|
||||||
|
|
||||||
def assert_contains_lines(self, lines2):
|
def assert_contains_lines(self, lines2):
|
||||||
""" assert that lines2 are contained (linearly) in lines1.
|
"""Assert that lines2 are contained (linearly) in lines1.
|
||||||
return a list of extralines found.
|
|
||||||
|
Return a list of extralines found.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
val = self.stringio.getvalue()
|
val = self.stringio.getvalue()
|
||||||
|
@ -1056,8 +1046,8 @@ class LineMatcher:
|
||||||
This is a convenience class to test large texts like the output of
|
This is a convenience class to test large texts like the output of
|
||||||
commands.
|
commands.
|
||||||
|
|
||||||
The constructor takes a list of lines without their trailing
|
The constructor takes a list of lines without their trailing newlines, i.e.
|
||||||
newlines, i.e. ``text.splitlines()``.
|
``text.splitlines()``.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -1077,18 +1067,19 @@ class LineMatcher:
|
||||||
return lines2
|
return lines2
|
||||||
|
|
||||||
def fnmatch_lines_random(self, lines2):
|
def fnmatch_lines_random(self, lines2):
|
||||||
"""Check lines exist in the output using ``fnmatch.fnmatch``, in any order.
|
"""Check lines exist in the output using in any order.
|
||||||
|
|
||||||
|
Lines are checked using ``fnmatch.fnmatch``. The argument is a list of
|
||||||
|
lines which have to occur in the output, in any order.
|
||||||
|
|
||||||
The argument is a list of lines which have to occur in the
|
|
||||||
output, in any order.
|
|
||||||
"""
|
"""
|
||||||
self._match_lines_random(lines2, fnmatch)
|
self._match_lines_random(lines2, fnmatch)
|
||||||
|
|
||||||
def re_match_lines_random(self, lines2):
|
def re_match_lines_random(self, lines2):
|
||||||
"""Check lines exist in the output using ``re.match``, in any order.
|
"""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
|
The argument is a list of lines which have to occur in the output, in
|
||||||
output, in any order.
|
any order.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self._match_lines_random(lines2, lambda name, pat: re.match(pat, name))
|
self._match_lines_random(lines2, lambda name, pat: re.match(pat, name))
|
||||||
|
@ -1096,8 +1087,8 @@ class LineMatcher:
|
||||||
def _match_lines_random(self, lines2, match_func):
|
def _match_lines_random(self, lines2, match_func):
|
||||||
"""Check lines exist in the output.
|
"""Check lines exist in the output.
|
||||||
|
|
||||||
The argument is a list of lines which have to occur in the
|
The argument is a list of lines which have to occur in the output, in
|
||||||
output, in any order. Each line can contain glob whildcards.
|
any order. Each line can contain glob whildcards.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
lines2 = self._getlines(lines2)
|
lines2 = self._getlines(lines2)
|
||||||
|
@ -1114,6 +1105,7 @@ class LineMatcher:
|
||||||
"""Return all lines following the given line in the text.
|
"""Return all lines following the given line in the text.
|
||||||
|
|
||||||
The given line can contain glob wildcards.
|
The given line can contain glob wildcards.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for i, line in enumerate(self.lines):
|
for i, line in enumerate(self.lines):
|
||||||
if fnline == line or fnmatch(line, fnline):
|
if fnline == line or fnmatch(line, fnline):
|
||||||
|
@ -1130,10 +1122,9 @@ class LineMatcher:
|
||||||
def fnmatch_lines(self, lines2):
|
def fnmatch_lines(self, lines2):
|
||||||
"""Search captured text for matching lines using ``fnmatch.fnmatch``.
|
"""Search captured text for matching lines using ``fnmatch.fnmatch``.
|
||||||
|
|
||||||
The argument is a list of lines which have to match and can
|
The argument is a list of lines which have to match and can use glob
|
||||||
use glob wildcards. If they do not match a pytest.fail() is
|
wildcards. If they do not match a pytest.fail() is called. The
|
||||||
called. The matches and non-matches are also printed on
|
matches and non-matches are also printed on stdout.
|
||||||
stdout.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self._match_lines(lines2, fnmatch, 'fnmatch')
|
self._match_lines(lines2, fnmatch, 'fnmatch')
|
||||||
|
@ -1144,21 +1135,22 @@ class LineMatcher:
|
||||||
The argument is a list of lines which have to match 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.
|
If they do not match a pytest.fail() is called.
|
||||||
|
|
||||||
The matches and non-matches are also printed on
|
The matches and non-matches are also printed on stdout.
|
||||||
stdout.
|
|
||||||
"""
|
"""
|
||||||
self._match_lines(lines2, lambda name, pat: re.match(pat, name), 're.match')
|
self._match_lines(lines2, lambda name, pat: re.match(pat, name), 're.match')
|
||||||
|
|
||||||
def _match_lines(self, lines2, match_func, match_nickname):
|
def _match_lines(self, lines2, match_func, match_nickname):
|
||||||
"""Underlying implementation of ``fnmatch_lines`` and ``re_match_lines``.
|
"""Underlying implementation of ``fnmatch_lines`` and ``re_match_lines``.
|
||||||
|
|
||||||
:param list[str] lines2: list of string patterns to match. The actual format depends on
|
:param list[str] lines2: list of string patterns to match. The actual
|
||||||
``match_func``.
|
format depends on ``match_func``
|
||||||
:param match_func: a callable ``match_func(line, pattern)`` where line is the captured
|
:param match_func: a callable ``match_func(line, pattern)`` where line
|
||||||
line from stdout/stderr and pattern is the matching pattern.
|
is the captured line from stdout/stderr and pattern is the matching
|
||||||
|
pattern
|
||||||
|
:param str match_nickname: the nickname for the match function that
|
||||||
|
will be logged to stdout when a match occurs
|
||||||
|
|
||||||
:param str match_nickname: the nickname for the match function that will be logged
|
|
||||||
to stdout when a match occurs.
|
|
||||||
"""
|
"""
|
||||||
lines2 = self._getlines(lines2)
|
lines2 = self._getlines(lines2)
|
||||||
lines1 = self.lines[:]
|
lines1 = self.lines[:]
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Code cleanup.
|
|
@ -0,0 +1 @@
|
||||||
|
Clean up code by replacing imports and references of `_ast` to `ast`.
|
|
@ -92,14 +92,14 @@ an example test function that performs some output related checks:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def test_myoutput(capsys): # or use "capfd" for fd-level
|
def test_myoutput(capsys): # or use "capfd" for fd-level
|
||||||
print ("hello")
|
print("hello")
|
||||||
sys.stderr.write("world\n")
|
sys.stderr.write("world\n")
|
||||||
out, err = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert out == "hello\n"
|
assert captured.out == "hello\n"
|
||||||
assert err == "world\n"
|
assert captured.err == "world\n"
|
||||||
print ("next")
|
print("next")
|
||||||
out, err = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert out == "next\n"
|
assert captured.out == "next\n"
|
||||||
|
|
||||||
The ``readouterr()`` call snapshots the output so far -
|
The ``readouterr()`` call snapshots the output so far -
|
||||||
and capturing will be continued. After the test
|
and capturing will be continued. After the test
|
||||||
|
@ -117,6 +117,10 @@ system level output streams (FD1 and FD2).
|
||||||
|
|
||||||
.. versionadded:: 3.3
|
.. versionadded:: 3.3
|
||||||
|
|
||||||
|
The return value from ``readouterr`` changed to a ``namedtuple`` with two attributes, ``out`` and ``err``.
|
||||||
|
|
||||||
|
.. versionadded:: 3.3
|
||||||
|
|
||||||
If the code under test writes non-textual data, you can capture this using
|
If the code under test writes non-textual data, you can capture this using
|
||||||
the ``capsysbinary`` fixture which instead returns ``bytes`` from
|
the ``capsysbinary`` fixture which instead returns ``bytes`` from
|
||||||
the ``readouterr`` method. The ``capfsysbinary`` fixture is currently only
|
the ``readouterr`` method. The ``capfsysbinary`` fixture is currently only
|
||||||
|
|
|
@ -215,8 +215,8 @@ class TestGeneralUsage(object):
|
||||||
assert not result.ret
|
assert not result.ret
|
||||||
|
|
||||||
def test_issue109_sibling_conftests_not_loaded(self, testdir):
|
def test_issue109_sibling_conftests_not_loaded(self, testdir):
|
||||||
sub1 = testdir.tmpdir.mkdir("sub1")
|
sub1 = testdir.mkdir("sub1")
|
||||||
sub2 = testdir.tmpdir.mkdir("sub2")
|
sub2 = testdir.mkdir("sub2")
|
||||||
sub1.join("conftest.py").write("assert 0")
|
sub1.join("conftest.py").write("assert 0")
|
||||||
result = testdir.runpytest(sub2)
|
result = testdir.runpytest(sub2)
|
||||||
assert result.ret == EXIT_NOTESTSCOLLECTED
|
assert result.ret == EXIT_NOTESTSCOLLECTED
|
||||||
|
@ -603,11 +603,11 @@ class TestInvocationVariants(object):
|
||||||
# The structure of the test directory is now:
|
# The structure of the test directory is now:
|
||||||
# .
|
# .
|
||||||
# ├── hello
|
# ├── hello
|
||||||
# │ └── ns_pkg
|
# │ └── ns_pkg
|
||||||
# │ ├── __init__.py
|
# │ ├── __init__.py
|
||||||
# │ └── hello
|
# │ └── hello
|
||||||
# │ ├── __init__.py
|
# │ ├── __init__.py
|
||||||
# │ └── test_hello.py
|
# │ └── test_hello.py
|
||||||
# └── world
|
# └── world
|
||||||
# └── ns_pkg
|
# └── ns_pkg
|
||||||
# ├── __init__.py
|
# ├── __init__.py
|
||||||
|
@ -624,10 +624,9 @@ class TestInvocationVariants(object):
|
||||||
for p in search_path:
|
for p in search_path:
|
||||||
monkeypatch.syspath_prepend(p)
|
monkeypatch.syspath_prepend(p)
|
||||||
|
|
||||||
os.chdir('world')
|
|
||||||
# mixed module and filenames:
|
# mixed module and filenames:
|
||||||
|
os.chdir('world')
|
||||||
result = testdir.runpytest("--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world")
|
result = testdir.runpytest("--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world")
|
||||||
testdir.chdir()
|
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*test_hello.py::test_hello*PASSED*",
|
"*test_hello.py::test_hello*PASSED*",
|
||||||
|
@ -638,6 +637,7 @@ class TestInvocationVariants(object):
|
||||||
])
|
])
|
||||||
|
|
||||||
# specify tests within a module
|
# specify tests within a module
|
||||||
|
testdir.chdir()
|
||||||
result = testdir.runpytest("--pyargs", "-v", "ns_pkg.world.test_world::test_other")
|
result = testdir.runpytest("--pyargs", "-v", "ns_pkg.world.test_world::test_other")
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
|
|
|
@ -8,13 +8,10 @@ import _pytest._code
|
||||||
import py
|
import py
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest._code import Source
|
from _pytest._code import Source
|
||||||
from _pytest._code.source import _ast
|
from _pytest._code.source import ast
|
||||||
|
|
||||||
if _ast is not None:
|
|
||||||
astonly = pytest.mark.nothing
|
|
||||||
else:
|
|
||||||
astonly = pytest.mark.xfail("True", reason="only works with AST-compile")
|
|
||||||
|
|
||||||
|
astonly = pytest.mark.nothing
|
||||||
failsonjython = pytest.mark.xfail("sys.platform.startswith('java')")
|
failsonjython = pytest.mark.xfail("sys.platform.startswith('java')")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ def test_conftest_in_nonpkg_with_init(tmpdir):
|
||||||
|
|
||||||
def test_doubledash_considered(testdir):
|
def test_doubledash_considered(testdir):
|
||||||
conf = testdir.mkdir("--option")
|
conf = testdir.mkdir("--option")
|
||||||
conf.join("conftest.py").ensure()
|
conf.ensure("conftest.py")
|
||||||
conftest = PytestPluginManager()
|
conftest = PytestPluginManager()
|
||||||
conftest_setinitial(conftest, [conf.basename, conf.basename])
|
conftest_setinitial(conftest, [conf.basename, conf.basename])
|
||||||
values = conftest._getconftestmodules(conf)
|
values = conftest._getconftestmodules(conf)
|
||||||
|
@ -270,11 +270,7 @@ def test_conftest_found_with_double_dash(testdir):
|
||||||
parser.addoption("--hello-world", action="store_true")
|
parser.addoption("--hello-world", action="store_true")
|
||||||
"""))
|
"""))
|
||||||
p = sub.join("test_hello.py")
|
p = sub.join("test_hello.py")
|
||||||
p.write(py.std.textwrap.dedent("""
|
p.write("def test_hello(): pass")
|
||||||
import pytest
|
|
||||||
def test_hello(found):
|
|
||||||
assert found == 1
|
|
||||||
"""))
|
|
||||||
result = testdir.runpytest(str(p) + "::test_hello", "-h")
|
result = testdir.runpytest(str(p) + "::test_hello", "-h")
|
||||||
result.stdout.fnmatch_lines("""
|
result.stdout.fnmatch_lines("""
|
||||||
*--hello-world*
|
*--hello-world*
|
||||||
|
|
|
@ -338,7 +338,7 @@ class TestPDB(object):
|
||||||
self.flush(child)
|
self.flush(child)
|
||||||
|
|
||||||
def test_pdb_collection_failure_is_shown(self, testdir):
|
def test_pdb_collection_failure_is_shown(self, testdir):
|
||||||
p1 = testdir.makepyfile("""xxx """)
|
p1 = testdir.makepyfile("xxx")
|
||||||
result = testdir.runpytest_subprocess("--pdb", p1)
|
result = testdir.runpytest_subprocess("--pdb", p1)
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*NameError*xxx*",
|
"*NameError*xxx*",
|
||||||
|
|
|
@ -141,7 +141,7 @@ def test_inline_run_clean_modules(testdir):
|
||||||
assert result2.ret == EXIT_TESTSFAILED
|
assert result2.ret == EXIT_TESTSFAILED
|
||||||
|
|
||||||
|
|
||||||
def test_assert_outcomes_after_pytest_erro(testdir):
|
def test_assert_outcomes_after_pytest_error(testdir):
|
||||||
testdir.makepyfile("def test_foo(): assert True")
|
testdir.makepyfile("def test_foo(): assert True")
|
||||||
|
|
||||||
result = testdir.runpytest('--unexpected-argument')
|
result = testdir.runpytest('--unexpected-argument')
|
||||||
|
|
2
tox.ini
2
tox.ini
|
@ -138,7 +138,7 @@ commands =
|
||||||
basepython = python
|
basepython = python
|
||||||
usedevelop = True
|
usedevelop = True
|
||||||
skipsdist = True
|
skipsdist = True
|
||||||
# ensure the given pyargs cant mean anytrhing else
|
# ensure the given pyargs can't mean anything else
|
||||||
changedir = doc/
|
changedir = doc/
|
||||||
deps =
|
deps =
|
||||||
PyYAML
|
PyYAML
|
||||||
|
|
Loading…
Reference in New Issue