diff --git a/changelog/3332.feature.rst b/changelog/3332.feature.rst new file mode 100644 index 000000000..e0110c451 --- /dev/null +++ b/changelog/3332.feature.rst @@ -0,0 +1,4 @@ +Improve the error displayed when a ``conftest.py`` file could not be imported. + +In order to implement this, a new ``chain`` parameter was added to ``ExceptionInfo.getrepr`` +to show or hide chained tracebacks in Python 3 (defaults to ``True``). diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index efc5e1235..363412abb 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -57,10 +57,24 @@ def main(args=None, plugins=None): try: config = _prepareconfig(args, plugins) except ConftestImportFailure as e: + from _pytest._code import ExceptionInfo + + exc_info = ExceptionInfo(e.excinfo) tw = py.io.TerminalWriter(sys.stderr) - for line in traceback.format_exception(*e.excinfo): + tw.line( + "ImportError while loading conftest '{e.path}'.".format(e=e), red=True + ) + from _pytest.python import filter_traceback + + 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) - tw.line("ERROR: could not load %s\n" % (e.path,), red=True) return 4 else: try: diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 332af27b5..1728f1ff4 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -133,9 +133,16 @@ class TestGeneralUsage(object): assert result.ret result.stderr.fnmatch_lines(["*ERROR: not found:*{}".format(p2.basename)]) - def test_issue486_better_reporting_on_conftest_load_failure(self, testdir): + def test_better_reporting_on_conftest_load_failure(self, testdir, request): + """Show a user-friendly traceback on conftest import failures (#486, #3332)""" testdir.makepyfile("") - testdir.makeconftest("import qwerty") + testdir.makeconftest( + """ + def foo(): + import qwerty + foo() + """ + ) result = testdir.runpytest("--help") result.stdout.fnmatch_lines( """ @@ -144,10 +151,23 @@ class TestGeneralUsage(object): """ ) result = testdir.runpytest() + dirname = request.node.name + "0" + exc_name = ( + "ModuleNotFoundError" if sys.version_info >= (3, 6) else "ImportError" + ) result.stderr.fnmatch_lines( - """ - *ERROR*could not load*conftest.py* - """ + [ + "ImportError while loading conftest '*{sep}{dirname}{sep}conftest.py'.".format( + dirname=dirname, sep=os.sep + ), + "conftest.py:3: in ", + " foo()", + "conftest.py:2: in foo", + " import qwerty", + "E {}: No module named {q}qwerty{q}".format( + exc_name, q="'" if six.PY3 else "" + ), + ] ) def test_early_skip(self, testdir):