def test_call_events(self):
        """UnsafeCircuitBreaker: it should call the appropriate functions on every successful/failed call.
        """
        self.out = ''

        def suc(): return True
        def err(): raise NotImplementedError()

        class Listener(CircuitBreakerListener):
            def __init__(self):
                self.out = ''
            def before_call(self, cb, func, *args, **kwargs):
                assert cb
                self.out += '-'
            def success(self, cb):
                assert cb
                self.out += 'success'
            def failure(self, cb, exc):
                assert cb; assert exc
                self.out += 'failure'

        listener = Listener()
        self.breaker = UnsafeCircuitBreaker(listeners=(listener,))

        self.assertTrue(self.breaker.call(suc))
        self.assertRaises(NotImplementedError, self.breaker.call, err)
        self.assertEqual('-success-failure', listener.out)
    def test_transition_events(self):
        """UnsafeCircuitBreaker: it should call the appropriate functions on every state transition.
        """
        class Listener(CircuitBreakerListener):
            def __init__(self):
                self.out = ''

            def state_change(self, cb, old_state, new_state):
                assert cb
                if old_state: self.out += old_state
                if new_state: self.out += '->' + new_state
                self.out += ','

        listener = Listener()
        self.breaker = UnsafeCircuitBreaker(listeners=(listener,))
        self.assertEqual('closed', self.breaker.current_state)

        self.breaker.open()
        self.assertEqual('open', self.breaker.current_state)

        self.breaker.half_open()
        self.assertEqual('half_open', self.breaker.current_state)

        self.breaker.close()
        self.assertEqual('closed', self.breaker.current_state)

        self.assertEqual('closed->open,open->half_open,half_open->closed,', listener.out)
 def test_new_with_custom_excluded_exceptions(self):
     """UnsafeCircuitBreaker: it should support a custom list of excluded exceptions.
     """
     self.breaker = UnsafeCircuitBreaker(exclude=[Exception])
     self.assertEqual(0, self.breaker.fail_counter)
     self.assertEqual(60, self.breaker.reset_timeout)
     self.assertEqual(5, self.breaker.fail_max)
     self.assertEqual([Exception], self.breaker.excluded_exceptions)
 def test_new_with_custom_fail_max(self):
     """UnsafeCircuitBreaker: it should support a custom maximum number of failures.
     """
     self.breaker = UnsafeCircuitBreaker(fail_max=10)
     self.assertEqual(0, self.breaker.fail_counter)
     self.assertEqual(60, self.breaker.reset_timeout)
     self.assertEqual(10, self.breaker.fail_max)
     self.assertEqual([], self.breaker.excluded_exceptions)
 def test_new_with_custom_reset_timeout(self):
     """UnsafeCircuitBreaker: it should support a custom reset timeout value.
     """
     self.breaker = UnsafeCircuitBreaker(reset_timeout=30)
     self.assertEqual(0, self.breaker.fail_counter)
     self.assertEqual(30, self.breaker.reset_timeout)
     self.assertEqual(5, self.breaker.fail_max)
     self.assertEqual([], self.breaker.excluded_exceptions)
    def test_several_failed_calls(self):
        """UnsafeCircuitBreaker: it should open the circuit after many failures.
        """
        self.breaker = UnsafeCircuitBreaker(fail_max=3)
        def func(): raise NotImplementedError()

        self.assertRaises(NotImplementedError, self.breaker.call, func)
        self.assertRaises(NotImplementedError, self.breaker.call, func)

        # Circuit should open
        self.assertRaises(CircuitBreakerError, self.breaker.call, func)
        self.assertEqual(3, self.breaker.fail_counter)
        self.assertEqual('open', self.breaker.current_state)
    def test_raise_custom_exception_while_open(self):
        class CustomError(CircuitBreakerError):
            pass

        self.breaker = UnsafeCircuitBreaker(fail_max=3, exception=CustomError)
        def func(): raise NotImplementedError()

        self.assertRaises(NotImplementedError, self.breaker.call, func)
        self.assertRaises(NotImplementedError, self.breaker.call, func)

        # Circuit should open
        self.assertRaises(CustomError, self.breaker.call, func)
        self.assertEqual('open', self.breaker.current_state)
        self.assertRaises(CustomError, self.breaker.call, func)
    def test_raise_custom_exception_on_open(self):
        """UnsafeCircuitBreaker: it should raise custom exceptions during open
        """
        class CustomError(CircuitBreakerError):
            pass

        self.breaker = UnsafeCircuitBreaker(fail_max=3, exception=CustomError)
        def func(): raise NotImplementedError()

        self.assertRaises(NotImplementedError, self.breaker.call, func)
        self.assertRaises(NotImplementedError, self.breaker.call, func)

        # Circuit should open
        self.assertRaises(CustomError, self.breaker.call, func)
    def test_raise_custom_exception_on_half_open_fail(self):
        class CustomError(CircuitBreakerError):
            pass

        self.breaker = UnsafeCircuitBreaker(fail_max=3, exception=CustomError, reset_timeout=.01)
        def func(): raise NotImplementedError()

        self.assertRaises(NotImplementedError, self.breaker.call, func)
        self.assertRaises(NotImplementedError, self.breaker.call, func)

        # Circuit should open
        self.assertRaises(CustomError, self.breaker.call, func)
        
        sleep(.05)
        self.assertRaises(CustomError, self.breaker.call, func)
示例#10
0
    def test_close(self):
        """UnsafeCircuitBreaker: it should allow the circuit to be closed manually.
        """
        self.breaker = UnsafeCircuitBreaker(fail_max=3)
        def func(): raise NotImplementedError()

        self.assertRaises(NotImplementedError, self.breaker.call, func)
        self.assertRaises(NotImplementedError, self.breaker.call, func)

        # Circuit should open
        self.assertRaises(CircuitBreakerError, self.breaker.call, func)
        self.assertRaises(CircuitBreakerError, self.breaker.call, func)
        self.assertEqual(3, self.breaker.fail_counter)
        self.assertEqual('open', self.breaker.current_state)

        # Circuit should close again
        self.breaker.close()
        self.assertEqual(0, self.breaker.fail_counter)
        self.assertEqual('closed', self.breaker.current_state)
示例#11
0
    def test_failed_call_after_timeout(self):
        """UnsafeCircuitBreaker: it should half-open the circuit after timeout.
        """
        self.breaker = UnsafeCircuitBreaker(fail_max=3, reset_timeout=0.01)
        def func(): raise NotImplementedError()

        self.assertRaises(NotImplementedError, self.breaker.call, func)
        self.assertRaises(NotImplementedError, self.breaker.call, func)
        self.assertEqual('closed', self.breaker.current_state)

        # Circuit should open
        self.assertRaises(CircuitBreakerError, self.breaker.call, func)
        self.assertEqual(3, self.breaker.fail_counter)

        # Wait for timeout
        sleep(0.05)

        # Circuit should open again
        self.assertRaises(CircuitBreakerError, self.breaker.call, func)
        self.assertEqual(4, self.breaker.fail_counter)
        self.assertEqual('open', self.breaker.current_state)
示例#12
0
    def test_excluded_exceptions(self):
        """UnsafeCircuitBreaker: it should ignore specific exceptions.
        """
        self.breaker = UnsafeCircuitBreaker(exclude=[LookupError])

        def err_1(): raise NotImplementedError()
        def err_2(): raise LookupError()
        def err_3(): raise KeyError()

        self.assertRaises(NotImplementedError, self.breaker.call, err_1)
        self.assertEqual(1, self.breaker.fail_counter)

        # LookupError is not considered a system error
        self.assertRaises(LookupError, self.breaker.call, err_2)
        self.assertEqual(0, self.breaker.fail_counter)

        self.assertRaises(NotImplementedError, self.breaker.call, err_1)
        self.assertEqual(1, self.breaker.fail_counter)

        # Should consider subclasses as well (KeyError is a subclass of LookupError)
        self.assertRaises(KeyError, self.breaker.call, err_3)
        self.assertEqual(0, self.breaker.fail_counter)
示例#13
0
    def test_successful_after_timeout(self):
        """UnsafeCircuitBreaker: it should close the circuit when a call succeeds after timeout.
        """
        self.breaker = UnsafeCircuitBreaker(fail_max=3, reset_timeout=0.01)

        def suc(): return True
        def err(): raise NotImplementedError()

        self.assertRaises(NotImplementedError, self.breaker.call, err)
        self.assertRaises(NotImplementedError, self.breaker.call, err)
        self.assertEqual('closed', self.breaker.current_state)

        # Circuit should open
        self.assertRaises(CircuitBreakerError, self.breaker.call, err)
        self.assertRaises(CircuitBreakerError, self.breaker.call, suc)
        self.assertEqual(3, self.breaker.fail_counter)

        # Wait for timeout
        sleep(0.05)

        # Circuit should close again
        self.assertTrue(self.breaker.call(suc))
        self.assertEqual(0, self.breaker.fail_counter)
        self.assertEqual('closed', self.breaker.current_state)
示例#14
0
class UnsafeCircuitBreakerTestCase(unittest.TestCase):
    """
    Tests for the UnsafeCircuitBreaker class.
    """

    def setUp(self):
        self.breaker = UnsafeCircuitBreaker()

    def test_default_params(self):
        """UnsafeCircuitBreaker: it should define smart defaults.
        """
        self.assertEqual(0, self.breaker.fail_counter)
        self.assertEqual(60, self.breaker.reset_timeout)
        self.assertEqual(5, self.breaker.fail_max)
        self.assertEqual('closed', self.breaker.current_state)
        self.assertEqual([], self.breaker.excluded_exceptions)

    def test_new_with_custom_reset_timeout(self):
        """UnsafeCircuitBreaker: it should support a custom reset timeout value.
        """
        self.breaker = UnsafeCircuitBreaker(reset_timeout=30)
        self.assertEqual(0, self.breaker.fail_counter)
        self.assertEqual(30, self.breaker.reset_timeout)
        self.assertEqual(5, self.breaker.fail_max)
        self.assertEqual([], self.breaker.excluded_exceptions)

    def test_new_with_custom_fail_max(self):
        """UnsafeCircuitBreaker: it should support a custom maximum number of failures.
        """
        self.breaker = UnsafeCircuitBreaker(fail_max=10)
        self.assertEqual(0, self.breaker.fail_counter)
        self.assertEqual(60, self.breaker.reset_timeout)
        self.assertEqual(10, self.breaker.fail_max)
        self.assertEqual([], self.breaker.excluded_exceptions)

    def test_new_with_custom_excluded_exceptions(self):
        """UnsafeCircuitBreaker: it should support a custom list of excluded exceptions.
        """
        self.breaker = UnsafeCircuitBreaker(exclude=[Exception])
        self.assertEqual(0, self.breaker.fail_counter)
        self.assertEqual(60, self.breaker.reset_timeout)
        self.assertEqual(5, self.breaker.fail_max)
        self.assertEqual([Exception], self.breaker.excluded_exceptions)

    def test_fail_max_setter(self):
        """UnsafeCircuitBreaker: it should allow the user to set a new value for 'fail_max'.
        """
        self.assertEqual(5, self.breaker.fail_max)
        self.breaker.fail_max = 10
        self.assertEqual(10, self.breaker.fail_max)

    def test_reset_timeout_setter(self):
        """UnsafeCircuitBreaker: it should allow the user to set a new value for 'reset_timeout'.
        """
        self.assertEqual(60, self.breaker.reset_timeout)
        self.breaker.reset_timeout = 30
        self.assertEqual(30, self.breaker.reset_timeout)

    def test_call_with_no_args(self):
        """UnsafeCircuitBreaker: it should be able to invoke functions with no-args.
        """
        def func(): return True
        self.assertTrue(self.breaker.call(func))

    def test_call_with_args(self):
        """UnsafeCircuitBreaker: it should be able to invoke functions with args.
        """
        def func(arg1, arg2): return [arg1, arg2]
        self.assertEqual([42, 'abc'], self.breaker.call(func, 42, 'abc'))

    def test_call_with_kwargs(self):
        """UnsafeCircuitBreaker: it should be able to invoke functions with kwargs.
        """
        def func(**kwargs): return kwargs
        self.assertEqual({'a':1, 'b':2}, self.breaker.call(func, a=1, b=2))

    def test_successful_call(self):
        """UnsafeCircuitBreaker: it should keep the circuit closed after a successful call.
        """
        def func(): return True
        self.assertTrue(self.breaker.call(func))
        self.assertEqual(0, self.breaker.fail_counter)
        self.assertEqual('closed', self.breaker.current_state)

    def test_one_failed_call(self):
        """UnsafeCircuitBreaker: it should keep the circuit closed after a few failures.
        """
        def func(): raise NotImplementedError()
        self.assertRaises(NotImplementedError, self.breaker.call, func)
        self.assertEqual(1, self.breaker.fail_counter)
        self.assertEqual('closed', self.breaker.current_state)

    def test_one_successful_call_after_failed_call(self):
        """UnsafeCircuitBreaker: it should keep the circuit closed after few mixed outcomes.
        """
        def suc(): return True
        def err(): raise NotImplementedError()

        self.assertRaises(NotImplementedError, self.breaker.call, err)
        self.assertEqual(1, self.breaker.fail_counter)

        self.assertTrue(self.breaker.call(suc))
        self.assertEqual(0, self.breaker.fail_counter)
        self.assertEqual('closed', self.breaker.current_state)

    def test_several_failed_calls(self):
        """UnsafeCircuitBreaker: it should open the circuit after many failures.
        """
        self.breaker = UnsafeCircuitBreaker(fail_max=3)
        def func(): raise NotImplementedError()

        self.assertRaises(NotImplementedError, self.breaker.call, func)
        self.assertRaises(NotImplementedError, self.breaker.call, func)

        # Circuit should open
        self.assertRaises(CircuitBreakerError, self.breaker.call, func)
        self.assertEqual(3, self.breaker.fail_counter)
        self.assertEqual('open', self.breaker.current_state)

    def test_failed_call_after_timeout(self):
        """UnsafeCircuitBreaker: it should half-open the circuit after timeout.
        """
        self.breaker = UnsafeCircuitBreaker(fail_max=3, reset_timeout=0.01)
        def func(): raise NotImplementedError()

        self.assertRaises(NotImplementedError, self.breaker.call, func)
        self.assertRaises(NotImplementedError, self.breaker.call, func)
        self.assertEqual('closed', self.breaker.current_state)

        # Circuit should open
        self.assertRaises(CircuitBreakerError, self.breaker.call, func)
        self.assertEqual(3, self.breaker.fail_counter)

        # Wait for timeout
        sleep(0.05)

        # Circuit should open again
        self.assertRaises(CircuitBreakerError, self.breaker.call, func)
        self.assertEqual(4, self.breaker.fail_counter)
        self.assertEqual('open', self.breaker.current_state)

    def test_successful_after_timeout(self):
        """UnsafeCircuitBreaker: it should close the circuit when a call succeeds after timeout.
        """
        self.breaker = UnsafeCircuitBreaker(fail_max=3, reset_timeout=0.01)

        def suc(): return True
        def err(): raise NotImplementedError()

        self.assertRaises(NotImplementedError, self.breaker.call, err)
        self.assertRaises(NotImplementedError, self.breaker.call, err)
        self.assertEqual('closed', self.breaker.current_state)

        # Circuit should open
        self.assertRaises(CircuitBreakerError, self.breaker.call, err)
        self.assertRaises(CircuitBreakerError, self.breaker.call, suc)
        self.assertEqual(3, self.breaker.fail_counter)

        # Wait for timeout
        sleep(0.05)

        # Circuit should close again
        self.assertTrue(self.breaker.call(suc))
        self.assertEqual(0, self.breaker.fail_counter)
        self.assertEqual('closed', self.breaker.current_state)

    def test_failed_call_when_halfopen(self):
        """UnsafeCircuitBreaker: it should open the circuit when a call fails in half-open state.
        """
        def fun(): raise NotImplementedError()

        self.breaker.half_open()
        self.assertEqual(0, self.breaker.fail_counter)
        self.assertEqual('half_open', self.breaker.current_state)

        # Circuit should open
        self.assertRaises(CircuitBreakerError, self.breaker.call, fun)
        self.assertEqual(1, self.breaker.fail_counter)
        self.assertEqual('open', self.breaker.current_state)

    def test_successful_call_when_halfopen(self):
        """UnsafeCircuitBreaker: it should close the circuit when a call succeeds in half-open state.
        """
        def fun(): return True

        self.breaker.half_open()
        self.assertEqual(0, self.breaker.fail_counter)
        self.assertEqual('half_open', self.breaker.current_state)

        # Circuit should open
        self.assertTrue(self.breaker.call(fun))
        self.assertEqual(0, self.breaker.fail_counter)
        self.assertEqual('closed', self.breaker.current_state)

    def test_close(self):
        """UnsafeCircuitBreaker: it should allow the circuit to be closed manually.
        """
        self.breaker = UnsafeCircuitBreaker(fail_max=3)
        def func(): raise NotImplementedError()

        self.assertRaises(NotImplementedError, self.breaker.call, func)
        self.assertRaises(NotImplementedError, self.breaker.call, func)

        # Circuit should open
        self.assertRaises(CircuitBreakerError, self.breaker.call, func)
        self.assertRaises(CircuitBreakerError, self.breaker.call, func)
        self.assertEqual(3, self.breaker.fail_counter)
        self.assertEqual('open', self.breaker.current_state)

        # Circuit should close again
        self.breaker.close()
        self.assertEqual(0, self.breaker.fail_counter)
        self.assertEqual('closed', self.breaker.current_state)

    def test_transition_events(self):
        """UnsafeCircuitBreaker: it should call the appropriate functions on every state transition.
        """
        class Listener(CircuitBreakerListener):
            def __init__(self):
                self.out = ''

            def state_change(self, cb, old_state, new_state):
                assert cb
                if old_state: self.out += old_state
                if new_state: self.out += '->' + new_state
                self.out += ','

        listener = Listener()
        self.breaker = UnsafeCircuitBreaker(listeners=(listener,))
        self.assertEqual('closed', self.breaker.current_state)

        self.breaker.open()
        self.assertEqual('open', self.breaker.current_state)

        self.breaker.half_open()
        self.assertEqual('half_open', self.breaker.current_state)

        self.breaker.close()
        self.assertEqual('closed', self.breaker.current_state)

        self.assertEqual('closed->open,open->half_open,half_open->closed,', listener.out)

    def test_call_events(self):
        """UnsafeCircuitBreaker: it should call the appropriate functions on every successful/failed call.
        """
        self.out = ''

        def suc(): return True
        def err(): raise NotImplementedError()

        class Listener(CircuitBreakerListener):
            def __init__(self):
                self.out = ''
            def before_call(self, cb, func, *args, **kwargs):
                assert cb
                self.out += '-'
            def success(self, cb):
                assert cb
                self.out += 'success'
            def failure(self, cb, exc):
                assert cb; assert exc
                self.out += 'failure'

        listener = Listener()
        self.breaker = UnsafeCircuitBreaker(listeners=(listener,))

        self.assertTrue(self.breaker.call(suc))
        self.assertRaises(NotImplementedError, self.breaker.call, err)
        self.assertEqual('-success-failure', listener.out)

    def test_excluded_exceptions(self):
        """UnsafeCircuitBreaker: it should ignore specific exceptions.
        """
        self.breaker = UnsafeCircuitBreaker(exclude=[LookupError])

        def err_1(): raise NotImplementedError()
        def err_2(): raise LookupError()
        def err_3(): raise KeyError()

        self.assertRaises(NotImplementedError, self.breaker.call, err_1)
        self.assertEqual(1, self.breaker.fail_counter)

        # LookupError is not considered a system error
        self.assertRaises(LookupError, self.breaker.call, err_2)
        self.assertEqual(0, self.breaker.fail_counter)

        self.assertRaises(NotImplementedError, self.breaker.call, err_1)
        self.assertEqual(1, self.breaker.fail_counter)

        # Should consider subclasses as well (KeyError is a subclass of LookupError)
        self.assertRaises(KeyError, self.breaker.call, err_3)
        self.assertEqual(0, self.breaker.fail_counter)

    def test_raise_custom_exception_on_open(self):
        """UnsafeCircuitBreaker: it should raise custom exceptions during open
        """
        class CustomError(CircuitBreakerError):
            pass

        self.breaker = UnsafeCircuitBreaker(fail_max=3, exception=CustomError)
        def func(): raise NotImplementedError()

        self.assertRaises(NotImplementedError, self.breaker.call, func)
        self.assertRaises(NotImplementedError, self.breaker.call, func)

        # Circuit should open
        self.assertRaises(CustomError, self.breaker.call, func)

    def test_raise_custom_exception_while_open(self):
        class CustomError(CircuitBreakerError):
            pass

        self.breaker = UnsafeCircuitBreaker(fail_max=3, exception=CustomError)
        def func(): raise NotImplementedError()

        self.assertRaises(NotImplementedError, self.breaker.call, func)
        self.assertRaises(NotImplementedError, self.breaker.call, func)

        # Circuit should open
        self.assertRaises(CustomError, self.breaker.call, func)
        self.assertEqual('open', self.breaker.current_state)
        self.assertRaises(CustomError, self.breaker.call, func)

    def test_raise_custom_exception_on_half_open_fail(self):
        class CustomError(CircuitBreakerError):
            pass

        self.breaker = UnsafeCircuitBreaker(fail_max=3, exception=CustomError, reset_timeout=.01)
        def func(): raise NotImplementedError()

        self.assertRaises(NotImplementedError, self.breaker.call, func)
        self.assertRaises(NotImplementedError, self.breaker.call, func)

        # Circuit should open
        self.assertRaises(CustomError, self.breaker.call, func)
        
        sleep(.05)
        self.assertRaises(CustomError, self.breaker.call, func)

    def test_decorator(self):
        """UnsafeCircuitBreaker: it should be a decorator.
        """
        @self.breaker
        def suc(value):
            "Docstring"
            return value

        @self.breaker
        def err(value):
            "Docstring"
            raise NotImplementedError()

        self.assertEqual('Docstring', suc.__doc__)
        self.assertEqual('Docstring', err.__doc__)
        self.assertEqual('suc', suc.__name__)
        self.assertEqual('err', err.__name__)

        self.assertRaises(NotImplementedError, err, True)
        self.assertEqual(1, self.breaker.fail_counter)

        self.assertTrue(suc(True))
        self.assertEqual(0, self.breaker.fail_counter)

    def test_forced_open(self):
        """UnsafeCircuitBreaker: when forced_open, it should always raise a circuit_breaker exception
        """
        self.breaker.force_open()
        def suc(): return True
        def err(): raise NotImplementedError()

        self.assertRaises(CircuitBreakerError, self.breaker.call, suc)
        self.assertRaises(CircuitBreakerError, self.breaker.call, err)

    def test_forced_closed(self):
        """UnsafeCircuitBreaker: when forced_closed, it should never raise a circuit_breaker exception
        """
        self.breaker.force_closed()
        def suc(): return True
        def err(): raise NotImplementedError()

        self.assertTrue(self.breaker.call(suc))
        for i in xrange(100):
            self.assertRaises(NotImplementedError, self.breaker.call, err)
示例#15
0
 def setUp(self):
     self.breaker = UnsafeCircuitBreaker()