Skip to content

Commit cf13355

Browse files
authored
Merge pull request #1979 from nicoddemus/show-traceback-during-collection
Show traceback during collection
2 parents 1289cbb + a1d446b commit cf13355

6 files changed

Lines changed: 61 additions & 22 deletions

File tree

CHANGELOG.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,20 @@
33

44
*
55

6-
*
6+
* Import errors when collecting test modules now display the full traceback (`#1976`_).
7+
Thanks `@cwitty`_ for the report and `@nicoddemus`_ for the PR.
78

89
*
910

1011
*
1112

1213

14+
.. _@cwitty: https://github.com/cwitty
15+
16+
.. _#1976: https://github.com/pytest-dev/pytest/issues/1976
17+
18+
19+
1320
3.0.3
1421
=====
1522

_pytest/python.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,16 @@
2222
getlocation, enum,
2323
)
2424

25-
cutdir2 = py.path.local(_pytest.__file__).dirpath()
2625
cutdir1 = py.path.local(pluggy.__file__.rstrip("oc"))
26+
cutdir2 = py.path.local(_pytest.__file__).dirpath()
27+
cutdir3 = py.path.local(py.__file__).dirpath()
2728

2829

2930
def filter_traceback(entry):
31+
"""Return True if a TracebackEntry instance should be removed from tracebacks:
32+
* dynamically generated code (no code to show up for it);
33+
* internal traceback from pytest or its internal libraries, py and pluggy.
34+
"""
3035
# entry.path might sometimes return a str object when the entry
3136
# points to dynamically generated code
3237
# see https://bitbucket.org/pytest-dev/py/issues/71
@@ -37,7 +42,7 @@ def filter_traceback(entry):
3742
# entry.path might point to an inexisting file, in which case it will
3843
# alsso return a str object. see #1133
3944
p = py.path.local(entry.path)
40-
return p != cutdir1 and not p.relto(cutdir2)
45+
return p != cutdir1 and not p.relto(cutdir2) and not p.relto(cutdir3)
4146

4247

4348

@@ -424,12 +429,17 @@ def _importtestmodule(self):
424429
% e.args
425430
)
426431
except ImportError:
427-
exc_class, exc, _ = sys.exc_info()
432+
from _pytest._code.code import ExceptionInfo
433+
exc_info = ExceptionInfo()
434+
if self.config.getoption('verbose') < 2:
435+
exc_info.traceback = exc_info.traceback.filter(filter_traceback)
436+
exc_repr = exc_info.getrepr(style='short') if exc_info.traceback else exc_info.exconly()
437+
formatted_tb = py._builtin._totext(exc_repr)
428438
raise self.CollectError(
429-
"ImportError while importing test module '%s'.\n"
430-
"Original error message:\n'%s'\n"
431-
"Make sure your test modules/packages have valid Python names."
432-
% (self.fspath, exc or exc_class)
439+
"ImportError while importing test module '{fspath}'.\n"
440+
"Hint: make sure your test modules/packages have valid Python names.\n"
441+
"Traceback:\n"
442+
"{traceback}".format(fspath=self.fspath, traceback=formatted_tb)
433443
)
434444
except _pytest.runner.Skipped as e:
435445
if e.allow_module_level:

testing/acceptance_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def test_this():
120120
result.stdout.fnmatch_lines([
121121
#XXX on jython this fails: "> import import_fails",
122122
"ImportError while importing test module*",
123-
"'No module named *does_not_work*",
123+
"*No module named *does_not_work*",
124124
])
125125
assert result.ret == 2
126126

testing/python/collect.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# -*- coding: utf-8 -*-
2+
import os
23
import sys
34
from textwrap import dedent
45

@@ -68,9 +69,41 @@ def test_invalid_test_module_name(self, testdir):
6869
result = testdir.runpytest("-rw")
6970
result.stdout.fnmatch_lines([
7071
"ImportError while importing test module*test_one.part1*",
71-
"Make sure your test modules/packages have valid Python names.",
72+
"Hint: make sure your test modules/packages have valid Python names.",
7273
])
7374

75+
@pytest.mark.parametrize('verbose', [0, 1, 2])
76+
def test_show_traceback_import_error(self, testdir, verbose):
77+
"""Import errors when collecting modules should display the traceback (#1976).
78+
79+
With low verbosity we omit pytest and internal modules, otherwise show all traceback entries.
80+
"""
81+
testdir.makepyfile(
82+
foo_traceback_import_error="""
83+
from bar_traceback_import_error import NOT_AVAILABLE
84+
""",
85+
bar_traceback_import_error="",
86+
)
87+
testdir.makepyfile("""
88+
import foo_traceback_import_error
89+
""")
90+
args = ('-v',) * verbose
91+
result = testdir.runpytest(*args)
92+
result.stdout.fnmatch_lines([
93+
"ImportError while importing test module*",
94+
"Traceback:",
95+
"*from bar_traceback_import_error import NOT_AVAILABLE",
96+
"*cannot import name *NOT_AVAILABLE*",
97+
])
98+
assert result.ret == 2
99+
100+
stdout = result.stdout.str()
101+
for name in ('_pytest', os.path.join('py', '_path')):
102+
if verbose == 2:
103+
assert name in stdout
104+
else:
105+
assert name not in stdout
106+
74107

75108
class TestClass:
76109
def test_class_with_init_warning(self, testdir):

testing/test_collection.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -172,17 +172,6 @@ def pytest_collect_directory(self, path, parent):
172172
assert "world" in wascalled
173173

174174
class TestPrunetraceback:
175-
def test_collection_error(self, testdir):
176-
p = testdir.makepyfile("""
177-
import not_exists
178-
""")
179-
result = testdir.runpytest(p)
180-
assert "__import__" not in result.stdout.str(), "too long traceback"
181-
result.stdout.fnmatch_lines([
182-
"*ERROR collecting*",
183-
"ImportError while importing test module*",
184-
"'No module named *not_exists*",
185-
])
186175

187176
def test_custom_repr_failure(self, testdir):
188177
p = testdir.makepyfile("""

testing/test_terminal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,7 @@ def test_collect_fail(self, testdir, option):
667667
result = testdir.runpytest(*option.args)
668668
result.stdout.fnmatch_lines([
669669
"ImportError while importing*",
670-
"'No module named *xyz*",
670+
"*No module named *xyz*",
671671
"*1 error*",
672672
])
673673

0 commit comments

Comments
 (0)