diff --git a/CHANGELOG b/CHANGELOG index be439fbba..5fad1e64e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ Changes between 1.0.0b7 and 1.0.0b8 ===================================== +* introduced pytest_keyboardinterrupt hook and + refined pytest_sessionfinish hooked. + * workaround a buggy logging module interaction ("closing already closed files"). Thanks to Sridhar Ratnakumar for triggering. diff --git a/py/test/dist/dsession.py b/py/test/dist/dsession.py index 0f673f368..a21e9639b 100644 --- a/py/test/dist/dsession.py +++ b/py/test/dist/dsession.py @@ -75,7 +75,7 @@ class DSession(Session): self.setup() exitstatus = self.loop(colitems) self.teardown() - self.sessionfinishes() + self.sessionfinishes(exitstatus=exitstatus) return exitstatus def loop_once(self, loopstate): diff --git a/py/test/plugin/hookspec.py b/py/test/plugin/hookspec.py index 0199ba723..4da83742c 100644 --- a/py/test/plugin/hookspec.py +++ b/py/test/plugin/hookspec.py @@ -94,11 +94,11 @@ def pytest_runtest_logreport(rep): def pytest_sessionstart(session): """ before session.main() is called. """ -def pytest_sessionfinish(session, exitstatus, excrepr=None): +def pytest_sessionfinish(session, exitstatus): """ whole test run finishes. """ # ------------------------------------------------------------------------- -# generic reporting hooks (invoked from pytest_terminal) +# hooks for influencing reporting (invoked from pytest_terminal) # ------------------------------------------------------------------------- def pytest_report_teststatus(rep): @@ -140,7 +140,7 @@ def pytest_looponfailinfo(failreports, rootdirs): # ------------------------------------------------------------------------- -# internal debugging hooks +# error handling and internal debugging hooks # ------------------------------------------------------------------------- def pytest_plugin_registered(plugin): @@ -152,5 +152,8 @@ def pytest_plugin_unregistered(plugin): def pytest_internalerror(excrepr): """ called for internal errors. """ +def pytest_keyboard_interrupt(excinfo): + """ called for keyboard interrupt. """ + def pytest_trace(category, msg): """ called for debug info. """ diff --git a/py/test/plugin/pytest_execnetcleanup.py b/py/test/plugin/pytest_execnetcleanup.py index 184f3e89a..7a5f65aeb 100644 --- a/py/test/plugin/pytest_execnetcleanup.py +++ b/py/test/plugin/pytest_execnetcleanup.py @@ -24,7 +24,7 @@ class Execnetcleanup: def pytest_sessionstart(self, session): self._gateways = [] - def pytest_sessionfinish(self, session, exitstatus, excrepr=None): + def pytest_sessionfinish(self, session, exitstatus): l = [] for gw in self._gateways: gw.exit() diff --git a/py/test/plugin/pytest_runner.py b/py/test/plugin/pytest_runner.py index cfff2023f..eedb1a2fc 100644 --- a/py/test/plugin/pytest_runner.py +++ b/py/test/plugin/pytest_runner.py @@ -19,11 +19,10 @@ def pytest_addoption(parser): def pytest_configure(config): config._setupstate = SetupState() -def pytest_sessionfinish(session, exitstatus, excrepr=None): +def pytest_sessionfinish(session, exitstatus): # XXX see above if hasattr(session.config, '_setupstate'): session.config._setupstate.teardown_all() - # prevent logging module atexit handler from choking on # its attempt to close already closed streams # see http://bugs.python.org/issue6333 diff --git a/py/test/plugin/pytest_terminal.py b/py/test/plugin/pytest_terminal.py index 4211482d5..fd7bb50e4 100644 --- a/py/test/plugin/pytest_terminal.py +++ b/py/test/plugin/pytest_terminal.py @@ -231,20 +231,29 @@ class TerminalReporter: for i, testarg in py.builtin.enumerate(self.config.args): self.write_line("test object %d: %s" %(i+1, testarg)) - def pytest_sessionfinish(self, __call__, session, exitstatus, excrepr=None): + def pytest_sessionfinish(self, __call__, session, exitstatus): __call__.execute() self._tw.line("") if exitstatus in (0, 1, 2): self.summary_failures() self.summary_skips() self.config.hook.pytest_terminal_summary(terminalreporter=self) - if excrepr is not None: - self.summary_final_exc(excrepr) if exitstatus == 2: - self.write_sep("!", "KEYBOARD INTERRUPT") + self._report_keyboardinterrupt() self.summary_deselected() self.summary_stats() + def pytest_keyboard_interrupt(self, excinfo): + self._keyboardinterrupt_memo = excinfo.getrepr() + + def _report_keyboardinterrupt(self): + self.write_sep("!", "KEYBOARD INTERRUPT") + excrepr = self._keyboardinterrupt_memo + if self.config.option.verbose: + excrepr.toterminal(self._tw) + else: + excrepr.reprcrash.toterminal(self._tw) + def pytest_looponfailinfo(self, failreports, rootdirs): if failreports: self.write_sep("#", "LOOPONFAILING", red=True) @@ -334,14 +343,6 @@ class TerminalReporter: for num, fspath, lineno, reason in fskips: self._tw.line("%s:%d: [%d] %s" %(fspath, lineno, num, reason)) - def summary_final_exc(self, excrepr): - self.write_sep("!") - if self.config.option.verbose: - excrepr.toterminal(self._tw) - else: - excrepr.reprcrash.toterminal(self._tw) - - class CollectonlyReporter: INDENT = " " @@ -373,7 +374,7 @@ class CollectonlyReporter: self._failed.append(rep) self.indent = self.indent[:-len(self.INDENT)] - def pytest_sessionfinish(self, session, exitstatus, excrepr=None): + def pytest_sessionfinish(self, session, exitstatus): if self._failed: self.out.sep("!", "collection failures") for rep in self._failed: diff --git a/py/test/session.py b/py/test/session.py index c83081195..bd8698794 100644 --- a/py/test/session.py +++ b/py/test/session.py @@ -86,12 +86,11 @@ class Session(object): self.shouldstop = True pytest_collectreport = pytest_runtest_logreport - def sessionfinishes(self, exitstatus=0, excinfo=None): + def sessionfinishes(self, exitstatus): """ teardown any resources after a test run. """ self.config.hook.pytest_sessionfinish( session=self, exitstatus=exitstatus, - excrepr=excinfo and excinfo.getrepr() or None ) def getinitialitems(self, colitems): @@ -114,13 +113,14 @@ class Session(object): if not self.config.option.collectonly: item.config.hook.pytest_runtest_protocol(item=item) except KeyboardInterrupt: - captured_excinfo = py.code.ExceptionInfo() + excinfo = py.code.ExceptionInfo() + item.config.hook.pytest_keyboard_interrupt(excinfo=excinfo) exitstatus = outcome.EXIT_INTERRUPTED except: - captured_excinfo = py.code.ExceptionInfo() + excinfo = py.code.ExceptionInfo() self.config.pluginmanager.notify_exception(captured_excinfo) exitstatus = outcome.EXIT_INTERNALERROR if exitstatus == 0 and self._testsfailed: exitstatus = outcome.EXIT_TESTSFAILED - self.sessionfinishes(exitstatus=exitstatus, excinfo=captured_excinfo) + self.sessionfinishes(exitstatus=exitstatus) return exitstatus