diff --git a/_pytest/config.py b/_pytest/config.py index eb9c2a1f25f..947e6200082 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -36,7 +36,7 @@ def __str__(self): etype, evalue, etb = self.excinfo formatted = traceback.format_tb(etb) # The level of the tracebacks we want to print is hand crafted :( - return repr(evalue) + '\n' + ''.join(formatted[2:]) + return ''.join(formatted[2:]) + '\nE ' + etype.__name__ + ': ' + str(evalue) def main(args=None, plugins=None): @@ -52,9 +52,15 @@ def main(args=None, plugins=None): config = _prepareconfig(args, plugins) except ConftestImportFailure as e: tw = py.io.TerminalWriter(sys.stderr) - for line in traceback.format_exception(*e.excinfo): - tw.line(line.rstrip(), red=True) - tw.line("ERROR: could not load %s\n" % (e.path,), red=True) + formatted_tb = safe_str(e) + tw.line( + "ImportError while importing conftest module '{path}'.\n" + "Hint: make sure your test modules/packages have valid Python names.\n" + "Traceback:\n" + "{traceback}".format(path=e.path, traceback=formatted_tb), + red=True + ) + return 4 else: try: @@ -166,6 +172,23 @@ def _prepareconfig(args=None, plugins=None): raise +def print_short_traceback(error, config): + from _pytest.nodes import Collector + from _pytest._code.code import ExceptionInfo + from _pytest.python import filter_traceback + exc_info = ExceptionInfo() + if config and 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 = safe_str(exc_repr) + raise Collector.CollectError( + "ImportError while importing test module '{fspath}'.\n" + "Hint: make sure your test modules/packages have valid Python names.\n" + "Traceback:\n" + "{traceback}".format(fspath=error.path, traceback=formatted_tb) + ) from None + + class PytestPluginManager(PluginManager): """ Overwrites :py:class:`pluggy.PluginManager ` to add pytest-specific @@ -203,6 +226,7 @@ def __init__(self): self.rewrite_hook = _pytest.assertion.DummyRewriteHook() # Used to know when we are importing conftests after the pytest_configure stage self._configured = False + self._config = None def addhooks(self, module_or_class): """ @@ -279,6 +303,7 @@ def pytest_configure(self, config): "trylast: mark a hook implementation function such that the " "plugin machinery will try to call it last/as late as possible.") self._configured = True + self._config = config def _warn(self, message): kwargs = message if isinstance(message, dict) else { @@ -345,7 +370,11 @@ def _getconftestmodules(self, path): continue conftestpath = parent.join("conftest.py") if conftestpath.isfile(): - mod = self._importconftest(conftestpath) + mod = None + try: + mod = self._importconftest(conftestpath) + except ConftestImportFailure as e: + print_short_traceback(e, self._config) clist.append(mod) self._path2confmods[path] = clist diff --git a/_pytest/python.py b/_pytest/python.py index f9f17afd794..bcc9eedf9df 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -14,7 +14,7 @@ import py import six from _pytest.mark import MarkerError -from _pytest.config import hookimpl +from _pytest.config import hookimpl, print_short_traceback import _pytest import pluggy @@ -25,7 +25,7 @@ isclass, isfunction, is_generator, ascii_escaped, REGEX_TYPE, STRING_TYPES, NoneType, NOTSET, get_real_func, getfslineno, safe_getattr, - safe_str, getlocation, enum, + getlocation, enum, ) from _pytest.outcomes import fail from _pytest.mark.structures import transfer_markers @@ -424,19 +424,8 @@ def _importtestmodule(self): "unique basename for your test file modules" % e.args ) - except ImportError: - 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 = safe_str(exc_repr) - raise self.CollectError( - "ImportError while importing test module '{fspath}'.\n" - "Hint: make sure your test modules/packages have valid Python names.\n" - "Traceback:\n" - "{traceback}".format(fspath=self.fspath, traceback=formatted_tb) - ) + except ImportError as e: + print_short_traceback(e, self.config) except _pytest.runner.Skipped as e: if e.allow_module_level: raise diff --git a/changelog/3332.trivial b/changelog/3332.trivial new file mode 100644 index 00000000000..d5886ffd4b1 --- /dev/null +++ b/changelog/3332.trivial @@ -0,0 +1 @@ +Improved tracebacks for ImportErrors in conftest.py diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 89a44911f27..29c941ca6de 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -145,9 +145,9 @@ def test_issue486_better_reporting_on_conftest_load_failure(self, testdir): *warning*conftest.py* """) result = testdir.runpytest() - result.stderr.fnmatch_lines(""" - *ERROR*could not load*conftest.py* - """) + result.stderr.fnmatch_lines(["*ImportError while importing conftest module*conftest.py*", + "E *Error: No module named*qwerty*"] + ) def test_early_skip(self, testdir): testdir.mkdir("xyz")