Merge master into features

Conflicts:
	src/_pytest/debugging.py
This commit is contained in:
Daniel Hahler 2019-11-06 14:22:07 +01:00
commit 4e45472405
18 changed files with 79 additions and 36 deletions

View File

@ -60,7 +60,7 @@ jobs:
- env: TOXENV=py37-freeze - env: TOXENV=py37-freeze
- env: TOXENV=py38-xdist - env: TOXENV=py38-xdist
python: '3.8-dev' python: '3.8'
- stage: baseline - stage: baseline
env: TOXENV=py36-xdist env: TOXENV=py36-xdist
@ -94,11 +94,6 @@ jobs:
tags: true tags: true
repo: pytest-dev/pytest repo: pytest-dev/pytest
matrix:
allow_failures:
- python: '3.8-dev'
env: TOXENV=py38-xdist
before_script: before_script:
- | - |
# Do not (re-)upload coverage with cron runs. # Do not (re-)upload coverage with cron runs.

View File

@ -13,7 +13,7 @@ with advance notice in the **Deprecations** section of releases.
file is managed by towncrier. You *may* edit previous change logs to file is managed by towncrier. You *may* edit previous change logs to
fix problems like typo corrections or such. fix problems like typo corrections or such.
To add a new change log entry, please see To add a new change log entry, please see
https://pip.pypa.io/en/latest/development/#adding-a-news-entry https://pip.pypa.io/en/latest/development/contributing/#news-entries
we named the news folder changelog we named the news folder changelog
.. towncrier release notes start .. towncrier release notes start

View File

@ -0,0 +1 @@
Fix ``--trace`` when used with parametrized functions.

View File

@ -137,7 +137,7 @@ class Frame:
def exec_(self, code, **vars): def exec_(self, code, **vars):
""" exec 'code' in the frame """ exec 'code' in the frame
'vars' are optiona; additional local variables 'vars' are optional; additional local variables
""" """
f_locals = self.f_locals.copy() f_locals = self.f_locals.copy()
f_locals.update(vars) f_locals.update(vars)
@ -207,7 +207,7 @@ class TracebackEntry:
@property @property
def locals(self): def locals(self):
""" locals of underlaying frame """ """ locals of underlying frame """
return self.frame.f_locals return self.frame.f_locals
def getfirstlinesource(self): def getfirstlinesource(self):
@ -274,7 +274,7 @@ class TracebackEntry:
@property @property
def name(self): def name(self):
""" co_name of underlaying code """ """ co_name of underlying code """
return self.frame.code.raw.co_name return self.frame.code.raw.co_name
@ -302,7 +302,7 @@ class Traceback(list):
def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None): def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
""" return a Traceback instance wrapping part of this Traceback """ return a Traceback instance wrapping part of this Traceback
by provding any combination of path, lineno and firstlineno, the by providing any combination of path, lineno and firstlineno, the
first frame to start the to-be-returned traceback is determined first frame to start the to-be-returned traceback is determined
this allows cutting the first part of a Traceback instance e.g. this allows cutting the first part of a Traceback instance e.g.
@ -1008,7 +1008,7 @@ class ReprFileLocation(TerminalRepr):
def toterminal(self, tw) -> None: def toterminal(self, tw) -> None:
# filename and lineno output for each entry, # filename and lineno output for each entry,
# using an output format that most editors unterstand # using an output format that most editors understand
msg = self.message msg = self.message
i = msg.find("\n") i = msg.find("\n")
if i != -1: if i != -1:

View File

@ -38,7 +38,6 @@ def format_explanation(explanation: str) -> str:
for when one explanation needs to span multiple lines, e.g. when for when one explanation needs to span multiple lines, e.g. when
displaying diffs. displaying diffs.
""" """
explanation = explanation
lines = _split_explanation(explanation) lines = _split_explanation(explanation)
result = _format_lines(lines) result = _format_lines(lines)
return "\n".join(result) return "\n".join(result)

View File

@ -1,5 +1,6 @@
""" interactive debugging with PDB, the Python Debugger. """ """ interactive debugging with PDB, the Python Debugger. """
import argparse import argparse
import functools
import sys import sys
from _pytest import outcomes from _pytest import outcomes
@ -278,13 +279,16 @@ class PdbTrace:
def _test_pytest_function(pyfuncitem): def _test_pytest_function(pyfuncitem):
_pdb = pytestPDB._init_pdb("runcall") _pdb = pytestPDB._init_pdb("runcall")
testfunction = pyfuncitem.obj testfunction = pyfuncitem.obj
pyfuncitem.obj = _pdb.runcall
if "func" in pyfuncitem._fixtureinfo.argnames: # pragma: no branch # we can't just return `partial(pdb.runcall, testfunction)` because (on
raise ValueError("--trace can't be used with a fixture named func!") # python < 3.7.4) runcall's first param is `func`, which means we'd get
pyfuncitem.funcargs["func"] = testfunction # an exception if one of the kwargs to testfunction was called `func`
new_list = list(pyfuncitem._fixtureinfo.argnames) @functools.wraps(testfunction)
new_list.append("func") def wrapper(*args, **kwargs):
pyfuncitem._fixtureinfo.argnames = tuple(new_list) func = functools.partial(testfunction, *args, **kwargs)
_pdb.runcall(func)
pyfuncitem.obj = wrapper
def _enter_pdb(node, excinfo, rep): def _enter_pdb(node, excinfo, rep):

View File

@ -513,7 +513,7 @@ class LogXML:
key = nodeid, slavenode key = nodeid, slavenode
if key in self.node_reporters: if key in self.node_reporters:
# TODO: breasks for --dist=each # TODO: breaks for --dist=each
return self.node_reporters[key] return self.node_reporters[key]
reporter = _NodeReporter(nodeid, self) reporter = _NodeReporter(nodeid, self)

View File

@ -437,7 +437,7 @@ class Session(nodes.FSCollector):
# one or more conftests are not in use at this fspath # one or more conftests are not in use at this fspath
proxy = FSHookProxy(fspath, pm, remove_mods) proxy = FSHookProxy(fspath, pm, remove_mods)
else: else:
# all plugis are active for this fspath # all plugins are active for this fspath
proxy = self.config.hook proxy = self.config.hook
return proxy return proxy

View File

@ -28,7 +28,7 @@ class MarkEvaluator:
self._mark_name = name self._mark_name = name
def __bool__(self): def __bool__(self):
# dont cache here to prevent staleness # don't cache here to prevent staleness
return bool(self._get_marks()) return bool(self._get_marks())
__nonzero__ = __bool__ __nonzero__ = __bool__

View File

@ -1,6 +1,6 @@
""" """
this is a place where we put datastructures used by legacy apis this is a place where we put datastructures used by legacy apis
we hope ot remove we hope to remove
""" """
import keyword import keyword

View File

@ -211,8 +211,8 @@ def pytest_pycollect_makeitem(collector, name, obj):
# mock seems to store unbound methods (issue473), normalize it # mock seems to store unbound methods (issue473), normalize it
obj = getattr(obj, "__func__", obj) obj = getattr(obj, "__func__", obj)
# We need to try and unwrap the function if it's a functools.partial # We need to try and unwrap the function if it's a functools.partial
# or a funtools.wrapped. # or a functools.wrapped.
# We musn't if it's been wrapped with mock.patch (python 2 only) # We mustn't if it's been wrapped with mock.patch (python 2 only)
if not (inspect.isfunction(obj) or inspect.isfunction(get_real_func(obj))): if not (inspect.isfunction(obj) or inspect.isfunction(get_real_func(obj))):
filename, lineno = getfslineno(obj) filename, lineno = getfslineno(obj)
warnings.warn_explicit( warnings.warn_explicit(
@ -596,7 +596,7 @@ class Package(Module):
# one or more conftests are not in use at this fspath # one or more conftests are not in use at this fspath
proxy = FSHookProxy(fspath, pm, remove_mods) proxy = FSHookProxy(fspath, pm, remove_mods)
else: else:
# all plugis are active for this fspath # all plugins are active for this fspath
proxy = self.config.hook proxy = self.config.hook
return proxy return proxy

View File

@ -122,7 +122,7 @@ def pytest_runtest_makereport(item, call):
outcome = yield outcome = yield
rep = outcome.get_result() rep = outcome.get_result()
evalxfail = getattr(item, "_evalxfail", None) evalxfail = getattr(item, "_evalxfail", None)
# unitttest special case, see setting of _unexpectedsuccess # unittest special case, see setting of _unexpectedsuccess
if hasattr(item, "_unexpectedsuccess") and rep.when == "call": if hasattr(item, "_unexpectedsuccess") and rep.when == "call":
if item._unexpectedsuccess: if item._unexpectedsuccess:
@ -132,7 +132,7 @@ def pytest_runtest_makereport(item, call):
rep.outcome = "failed" rep.outcome = "failed"
elif item.config.option.runxfail: elif item.config.option.runxfail:
pass # don't interefere pass # don't interfere
elif call.excinfo and call.excinfo.errisinstance(xfail.Exception): elif call.excinfo and call.excinfo.errisinstance(xfail.Exception):
rep.wasxfail = "reason: " + call.excinfo.value.msg rep.wasxfail = "reason: " + call.excinfo.value.msg
rep.outcome = "skipped" rep.outcome = "skipped"

View File

@ -15,7 +15,7 @@ class TestMetafunc:
def Metafunc(self, func, config=None): def Metafunc(self, func, config=None):
# the unit tests of this class check if things work correctly # the unit tests of this class check if things work correctly
# on the funcarg level, so we don't need a full blown # on the funcarg level, so we don't need a full blown
# initiliazation # initialization
class FixtureInfo: class FixtureInfo:
name2fixturedefs = None name2fixturedefs = None

View File

@ -482,7 +482,7 @@ class TestFunctional:
items, rec = testdir.inline_genitems(p) items, rec = testdir.inline_genitems(p)
base_item, sub_item, sub_item_other = items base_item, sub_item, sub_item_other = items
print(items, [x.nodeid for x in items]) print(items, [x.nodeid for x in items])
# new api seregates # new api segregates
assert not list(base_item.iter_markers(name="b")) assert not list(base_item.iter_markers(name="b"))
assert not list(sub_item_other.iter_markers(name="b")) assert not list(sub_item_other.iter_markers(name="b"))
assert list(sub_item.iter_markers(name="b")) assert list(sub_item.iter_markers(name="b"))

View File

@ -229,7 +229,7 @@ def test_nose_setup_ordering(testdir):
def test_apiwrapper_problem_issue260(testdir): def test_apiwrapper_problem_issue260(testdir):
# this would end up trying a call an optional teardown on the class # this would end up trying a call an optional teardown on the class
# for plain unittests we dont want nose behaviour # for plain unittests we don't want nose behaviour
testdir.makepyfile( testdir.makepyfile(
""" """
import unittest import unittest

View File

@ -304,7 +304,7 @@ def test_argcomplete(testdir, monkeypatch):
shlex.quote(sys.executable) shlex.quote(sys.executable)
) )
) )
# alternative would be exteneded Testdir.{run(),_run(),popen()} to be able # alternative would be extended Testdir.{run(),_run(),popen()} to be able
# to handle a keyword argument env that replaces os.environ in popen or # to handle a keyword argument env that replaces os.environ in popen or
# extends the copy, advantage: could not forget to restore # extends the copy, advantage: could not forget to restore
monkeypatch.setenv("_ARGCOMPLETE", "1") monkeypatch.setenv("_ARGCOMPLETE", "1")

View File

@ -603,7 +603,7 @@ class TestPDB:
# No extra newline. # No extra newline.
assert child.before.endswith(b"c\r\nprint_from_foo\r\n") assert child.before.endswith(b"c\r\nprint_from_foo\r\n")
# set_debug should not raise outcomes.Exit, if used recrursively. # set_debug should not raise outcomes. Exit, if used recursively.
child.sendline("debug 42") child.sendline("debug 42")
child.sendline("q") child.sendline("q")
child.expect("LEAVING RECURSIVE DEBUGGER") child.expect("LEAVING RECURSIVE DEBUGGER")
@ -1047,6 +1047,51 @@ class TestTraceOption:
assert "Exit: Quitting debugger" not in child.before.decode("utf8") assert "Exit: Quitting debugger" not in child.before.decode("utf8")
TestPDB.flush(child) TestPDB.flush(child)
def test_trace_with_parametrize_handles_shared_fixtureinfo(self, testdir):
p1 = testdir.makepyfile(
"""
import pytest
@pytest.mark.parametrize('myparam', [1,2])
def test_1(myparam, request):
assert myparam in (1, 2)
assert request.function.__name__ == "test_1"
@pytest.mark.parametrize('func', [1,2])
def test_func(func, request):
assert func in (1, 2)
assert request.function.__name__ == "test_func"
@pytest.mark.parametrize('myparam', [1,2])
def test_func_kw(myparam, request, func="func_kw"):
assert myparam in (1, 2)
assert func == "func_kw"
assert request.function.__name__ == "test_func_kw"
"""
)
child = testdir.spawn_pytest("--trace " + str(p1))
for func, argname in [
("test_1", "myparam"),
("test_func", "func"),
("test_func_kw", "myparam"),
]:
child.expect_exact("> PDB runcall (IO-capturing turned off) >")
child.expect_exact(func)
child.expect_exact("Pdb")
child.sendline("args")
child.expect_exact("{} = 1\r\n".format(argname))
child.expect_exact("Pdb")
child.sendline("c")
child.expect_exact("Pdb")
child.sendline("args")
child.expect_exact("{} = 2\r\n".format(argname))
child.expect_exact("Pdb")
child.sendline("c")
child.expect_exact("> PDB continue (IO-capturing resumed) >")
rest = child.read().decode("utf8")
assert "6 passed in" in rest
assert "reading from stdin while output" not in rest
# Only printed once - not on stderr.
assert "Exit: Quitting debugger" not in child.before.decode("utf8")
TestPDB.flush(child)
def test_trace_after_runpytest(testdir): def test_trace_after_runpytest(testdir):
"""Test that debugging's pytest_configure is re-entrant.""" """Test that debugging's pytest_configure is re-entrant."""
@ -1172,7 +1217,6 @@ def test_pdbcls_via_local_module(testdir):
def runcall(self, *args, **kwds): def runcall(self, *args, **kwds):
print("runcall_called", args, kwds) print("runcall_called", args, kwds)
assert "func" in kwds
""", """,
) )
result = testdir.runpytest( result = testdir.runpytest(

View File

@ -4,7 +4,7 @@ import pytest
@pytest.fixture @pytest.fixture
def stepwise_testdir(testdir): def stepwise_testdir(testdir):
# Rather than having to modify our testfile between tests, we introduce # Rather than having to modify our testfile between tests, we introduce
# a flag for wether or not the second test should fail. # a flag for whether or not the second test should fail.
testdir.makeconftest( testdir.makeconftest(
""" """
def pytest_addoption(parser): def pytest_addoption(parser):