From 19aab6365ff75861762150834ced49dabb3de6c2 Mon Sep 17 00:00:00 2001 From: feuillemorte Date: Tue, 27 Mar 2018 13:29:08 +0300 Subject: [PATCH 1/5] #3332 Improved tracebacks for ImportErrors in conftest --- _pytest/config.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/_pytest/config.py b/_pytest/config.py index eb9c2a1f25f..ab42c8d64a1 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 test 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: From 9b5b9e38a9d74608a7effe5eb72dbb5abcdf1e1d Mon Sep 17 00:00:00 2001 From: feuillemorte Date: Tue, 27 Mar 2018 13:29:59 +0300 Subject: [PATCH 2/5] #3332 Fix tests for new traceback --- testing/acceptance_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 89a44911f27..9e919caafd6 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 test module*conftest.py*", + "E *Error: No module named*qwerty*"] + ) def test_early_skip(self, testdir): testdir.mkdir("xyz") From fdb5c254e77fae9e7825732518ff156528a9d0c1 Mon Sep 17 00:00:00 2001 From: feuillemorte Date: Tue, 27 Mar 2018 13:30:21 +0300 Subject: [PATCH 3/5] #3332 Added changelog file --- changelog/3332.trivial | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/3332.trivial 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 From acbf49a367337796dbda5dbec703faa19863b383 Mon Sep 17 00:00:00 2001 From: feuillemorte Date: Tue, 27 Mar 2018 13:49:09 +0300 Subject: [PATCH 4/5] #3332 Fix message --- _pytest/config.py | 2 +- testing/acceptance_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/_pytest/config.py b/_pytest/config.py index ab42c8d64a1..7a4622ae88c 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -54,7 +54,7 @@ def main(args=None, plugins=None): tw = py.io.TerminalWriter(sys.stderr) formatted_tb = safe_str(e) tw.line( - "ImportError while importing test module '{path}'.\n" + "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), diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 9e919caafd6..29c941ca6de 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -145,7 +145,7 @@ def test_issue486_better_reporting_on_conftest_load_failure(self, testdir): *warning*conftest.py* """) result = testdir.runpytest() - result.stderr.fnmatch_lines(["*ImportError while importing test module*conftest.py*", + result.stderr.fnmatch_lines(["*ImportError while importing conftest module*conftest.py*", "E *Error: No module named*qwerty*"] ) From 75497b17e66c3e8ede5bd41c8d98bdd8cd1e345d Mon Sep 17 00:00:00 2001 From: feuillemorte Date: Wed, 4 Apr 2018 13:18:20 +0300 Subject: [PATCH 5/5] #3332 Added traceback function --- _pytest/config.py | 25 ++++++++++++++++++++++++- _pytest/python.py | 19 ++++--------------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/_pytest/config.py b/_pytest/config.py index 7a4622ae88c..947e6200082 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -172,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 @@ -209,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): """ @@ -285,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 { @@ -351,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