From 2dcf72cda357bad037bd675c47961c73c9167088 Mon Sep 17 00:00:00 2001 From: Federico Ressi Date: Fri, 25 Sep 2020 13:45:02 +0200 Subject: [PATCH] Handle SetupError exception to avoid spamming into log files. Change-Id: Id360d6e1638fc5bfb6adaa41412a977526795fe5 --- tobiko/common/_exception.py | 13 +++++++++---- tobiko/common/_fixture.py | 16 +++++++++++++++- tobiko/tests/unit/test_exception.py | 22 ++++++++++++++++++++++ tobiko/tests/unit/test_fixture.py | 8 ++++++++ 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/tobiko/common/_exception.py b/tobiko/common/_exception.py index b778aa0ba..423ab405a 100644 --- a/tobiko/common/_exception.py +++ b/tobiko/common/_exception.py @@ -123,18 +123,23 @@ def exc_info(reraise=True): return info +def log_unhandled_exception(exc_type, exc_value, ex_tb): + LOG.exception("Unhandled exception", + exc_info=(exc_type, exc_value, ex_tb)) + + @contextlib.contextmanager -def handle_multiple_exceptions(): +def handle_multiple_exceptions(handle_exception=log_unhandled_exception): try: yield - except testtools.MultipleExceptions as exc: + except testtools.MultipleExceptions as ex: exc_infos = list_exc_infos() if exc_infos: for info in exc_infos[1:]: - LOG.exception("Unhandled exception:", exc_info=info) + handle_exception(*info) reraise(*exc_infos[0]) else: - LOG.debug('empty MultipleExceptions: %s', str(exc)) + LOG.debug(f"Empty MultipleExceptions: '{ex}'") def list_exc_infos(exc_info=None): diff --git a/tobiko/common/_fixture.py b/tobiko/common/_fixture.py index d8b0f3124..146fea3ee 100644 --- a/tobiko/common/_fixture.py +++ b/tobiko/common/_fixture.py @@ -13,6 +13,7 @@ # under the License. from __future__ import absolute_import +import json import os import inspect @@ -99,11 +100,24 @@ def remove_fixture(obj, fixture_id=None, manager=None): def setup_fixture(obj, fixture_id=None, manager=None): '''Get registered fixture and setup it up''' fixture = get_fixture(obj, fixture_id=fixture_id, manager=manager) - with _exception.handle_multiple_exceptions(): + with _exception.handle_multiple_exceptions( + handle_exception=handle_setup_error): fixture.setUp() return fixture +def handle_setup_error(ex_type, ex_value, ex_tb): + if issubclass(ex_type, fixtures.SetupError): + details = ex_value.args[0] + if details: + details = {k: v.as_text() for k, v in details.items()} + pretty_details = json.dumps(details, indent=4, sort_keys=True) + LOG.debug(f"Fixture setup error details:\n{pretty_details}\n") + else: + LOG.exception("Unhandled setup exception", + exc_info=(ex_type, ex_value, ex_tb)) + + def reset_fixture(obj, fixture_id=None, manager=None): '''Get registered fixture and reset it''' fixture = get_fixture(obj, fixture_id=fixture_id, manager=manager) diff --git a/tobiko/tests/unit/test_exception.py b/tobiko/tests/unit/test_exception.py index ae5ef997e..b14ec8db4 100644 --- a/tobiko/tests/unit/test_exception.py +++ b/tobiko/tests/unit/test_exception.py @@ -210,6 +210,28 @@ class TestHandleMultipleExceptions(unit.TobikoUnitTest): ex = self.assertRaises(RuntimeError, run) self.assertEqual(a[1], ex) + def test_handle_multiple_exceptions_with_handle_exceptions(self): + a = make_exception(RuntimeError, 'a') + b = make_exception(ValueError, 'b') + c = make_exception(TypeError, 'c') + d = make_exception(IndexError, 'd') + inner = make_exception(testtools.MultipleExceptions, b, c) + + handled_exceptions = [] + + def handle_exception(ex_type, ex_value, ex_tb): + self.assertIsInstance(ex_value, ex_type) + handled_exceptions.append((ex_type, ex_value, ex_tb)) + + def run(): + with tobiko.handle_multiple_exceptions( + handle_exception=handle_exception): + raise testtools.MultipleExceptions(a, inner, d) + + ex = self.assertRaises(RuntimeError, run) + self.assertIs(a[1], ex) + self.assertEqual([b, c, d], handled_exceptions) + def make_exception(cls, *args, **kwargs): try: diff --git a/tobiko/tests/unit/test_fixture.py b/tobiko/tests/unit/test_fixture.py index 876b3c01f..c6ee9bed3 100644 --- a/tobiko/tests/unit/test_fixture.py +++ b/tobiko/tests/unit/test_fixture.py @@ -19,6 +19,7 @@ import sys import fixtures import mock import testtools +from testtools import content import tobiko from tobiko.tests import unit @@ -228,6 +229,13 @@ class FailingFixture(tobiko.SharedFixture): def cleanup_fixture(self): raise RuntimeError('raised by cleanup_fixture') + def getDetails(self): + content_object = tobiko.details_content( + content_type=content.UTF8_TEXT, + content_id=self.fixture_name, + get_text=lambda: 'My failure details') + return {'failing fixture': content_object} + class FailingSetupFixtureWhenFailingTest(unit.TobikoUnitTest):