Merge pull request #1979 from nicoddemus/show-traceback-during-collection
Show traceback during collection
This commit is contained in:
commit
cf13355d3f
|
@ -3,13 +3,20 @@
|
||||||
|
|
||||||
*
|
*
|
||||||
|
|
||||||
*
|
* Import errors when collecting test modules now display the full traceback (`#1976`_).
|
||||||
|
Thanks `@cwitty`_ for the report and `@nicoddemus`_ for the PR.
|
||||||
|
|
||||||
*
|
*
|
||||||
|
|
||||||
*
|
*
|
||||||
|
|
||||||
|
|
||||||
|
.. _@cwitty: https://github.com/cwitty
|
||||||
|
|
||||||
|
.. _#1976: https://github.com/pytest-dev/pytest/issues/1976
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
3.0.3
|
3.0.3
|
||||||
=====
|
=====
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,16 @@ from _pytest.compat import (
|
||||||
getlocation, enum,
|
getlocation, enum,
|
||||||
)
|
)
|
||||||
|
|
||||||
cutdir2 = py.path.local(_pytest.__file__).dirpath()
|
|
||||||
cutdir1 = py.path.local(pluggy.__file__.rstrip("oc"))
|
cutdir1 = py.path.local(pluggy.__file__.rstrip("oc"))
|
||||||
|
cutdir2 = py.path.local(_pytest.__file__).dirpath()
|
||||||
|
cutdir3 = py.path.local(py.__file__).dirpath()
|
||||||
|
|
||||||
|
|
||||||
def filter_traceback(entry):
|
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
|
# entry.path might sometimes return a str object when the entry
|
||||||
# points to dynamically generated code
|
# points to dynamically generated code
|
||||||
# see https://bitbucket.org/pytest-dev/py/issues/71
|
# see https://bitbucket.org/pytest-dev/py/issues/71
|
||||||
|
@ -37,7 +42,7 @@ def filter_traceback(entry):
|
||||||
# entry.path might point to an inexisting file, in which case it will
|
# entry.path might point to an inexisting file, in which case it will
|
||||||
# alsso return a str object. see #1133
|
# alsso return a str object. see #1133
|
||||||
p = py.path.local(entry.path)
|
p = py.path.local(entry.path)
|
||||||
return p != cutdir1 and not p.relto(cutdir2)
|
return p != cutdir1 and not p.relto(cutdir2) and not p.relto(cutdir3)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -424,12 +429,17 @@ class Module(pytest.File, PyCollector):
|
||||||
% e.args
|
% e.args
|
||||||
)
|
)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
exc_class, exc, _ = sys.exc_info()
|
from _pytest._code.code import ExceptionInfo
|
||||||
|
exc_info = ExceptionInfo()
|
||||||
|
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 = py._builtin._totext(exc_repr)
|
||||||
raise self.CollectError(
|
raise self.CollectError(
|
||||||
"ImportError while importing test module '%s'.\n"
|
"ImportError while importing test module '{fspath}'.\n"
|
||||||
"Original error message:\n'%s'\n"
|
"Hint: make sure your test modules/packages have valid Python names.\n"
|
||||||
"Make sure your test modules/packages have valid Python names."
|
"Traceback:\n"
|
||||||
% (self.fspath, exc or exc_class)
|
"{traceback}".format(fspath=self.fspath, traceback=formatted_tb)
|
||||||
)
|
)
|
||||||
except _pytest.runner.Skipped as e:
|
except _pytest.runner.Skipped as e:
|
||||||
if e.allow_module_level:
|
if e.allow_module_level:
|
||||||
|
|
|
@ -120,7 +120,7 @@ class TestGeneralUsage:
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
#XXX on jython this fails: "> import import_fails",
|
#XXX on jython this fails: "> import import_fails",
|
||||||
"ImportError while importing test module*",
|
"ImportError while importing test module*",
|
||||||
"'No module named *does_not_work*",
|
"*No module named *does_not_work*",
|
||||||
])
|
])
|
||||||
assert result.ret == 2
|
assert result.ret == 2
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
|
@ -68,9 +69,41 @@ class TestModule:
|
||||||
result = testdir.runpytest("-rw")
|
result = testdir.runpytest("-rw")
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"ImportError while importing test module*test_one.part1*",
|
"ImportError while importing test module*test_one.part1*",
|
||||||
"Make sure your test modules/packages have valid Python names.",
|
"Hint: make sure your test modules/packages have valid Python names.",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('verbose', [0, 1, 2])
|
||||||
|
def test_show_traceback_import_error(self, testdir, verbose):
|
||||||
|
"""Import errors when collecting modules should display the traceback (#1976).
|
||||||
|
|
||||||
|
With low verbosity we omit pytest and internal modules, otherwise show all traceback entries.
|
||||||
|
"""
|
||||||
|
testdir.makepyfile(
|
||||||
|
foo_traceback_import_error="""
|
||||||
|
from bar_traceback_import_error import NOT_AVAILABLE
|
||||||
|
""",
|
||||||
|
bar_traceback_import_error="",
|
||||||
|
)
|
||||||
|
testdir.makepyfile("""
|
||||||
|
import foo_traceback_import_error
|
||||||
|
""")
|
||||||
|
args = ('-v',) * verbose
|
||||||
|
result = testdir.runpytest(*args)
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"ImportError while importing test module*",
|
||||||
|
"Traceback:",
|
||||||
|
"*from bar_traceback_import_error import NOT_AVAILABLE",
|
||||||
|
"*cannot import name *NOT_AVAILABLE*",
|
||||||
|
])
|
||||||
|
assert result.ret == 2
|
||||||
|
|
||||||
|
stdout = result.stdout.str()
|
||||||
|
for name in ('_pytest', os.path.join('py', '_path')):
|
||||||
|
if verbose == 2:
|
||||||
|
assert name in stdout
|
||||||
|
else:
|
||||||
|
assert name not in stdout
|
||||||
|
|
||||||
|
|
||||||
class TestClass:
|
class TestClass:
|
||||||
def test_class_with_init_warning(self, testdir):
|
def test_class_with_init_warning(self, testdir):
|
||||||
|
|
|
@ -172,17 +172,6 @@ class TestCollectPluginHookRelay:
|
||||||
assert "world" in wascalled
|
assert "world" in wascalled
|
||||||
|
|
||||||
class TestPrunetraceback:
|
class TestPrunetraceback:
|
||||||
def test_collection_error(self, testdir):
|
|
||||||
p = testdir.makepyfile("""
|
|
||||||
import not_exists
|
|
||||||
""")
|
|
||||||
result = testdir.runpytest(p)
|
|
||||||
assert "__import__" not in result.stdout.str(), "too long traceback"
|
|
||||||
result.stdout.fnmatch_lines([
|
|
||||||
"*ERROR collecting*",
|
|
||||||
"ImportError while importing test module*",
|
|
||||||
"'No module named *not_exists*",
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_custom_repr_failure(self, testdir):
|
def test_custom_repr_failure(self, testdir):
|
||||||
p = testdir.makepyfile("""
|
p = testdir.makepyfile("""
|
||||||
|
|
|
@ -667,7 +667,7 @@ class TestGenericReporting:
|
||||||
result = testdir.runpytest(*option.args)
|
result = testdir.runpytest(*option.args)
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"ImportError while importing*",
|
"ImportError while importing*",
|
||||||
"'No module named *xyz*",
|
"*No module named *xyz*",
|
||||||
"*1 error*",
|
"*1 error*",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue