1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
|
try:
from unittest.mock import Mock, patch
except ImportError:
from mock import Mock, patch
from pytest import raises
from circuitbreaker import CircuitBreaker, CircuitBreakerError, circuit
class FooError(Exception):
def __init__(self, val=None):
self.val = val
class BarError(Exception):
pass
def test_circuitbreaker__str__():
cb = CircuitBreaker(name='Foobar')
assert str(cb) == 'Foobar'
def test_circuitbreaker_error__str__():
cb = CircuitBreaker(name='Foobar')
cb._last_failure = Exception()
error = CircuitBreakerError(cb)
assert str(error).startswith('Circuit "Foobar" OPEN until ')
assert str(error).endswith('(0 failures, 30 sec remaining) (last_failure: Exception())')
def test_circuitbreaker_should_save_last_exception_on_failure_call():
cb = CircuitBreaker(name='Foobar')
func = Mock(side_effect=IOError)
with raises(IOError):
cb.call(func)
assert isinstance(cb.last_failure, IOError)
def test_circuitbreaker_should_clear_last_exception_on_success_call():
cb = CircuitBreaker(name='Foobar')
cb._last_failure = IOError()
assert isinstance(cb.last_failure, IOError)
cb.call(lambda: True)
assert cb.last_failure is None
def test_circuitbreaker_should_call_fallback_function_if_open():
fallback = Mock(return_value=True)
func = Mock(return_value=False, __name__="Mock") # attribute __name__ required for 2.7 compat with functools.wraps
CircuitBreaker.opened = lambda self: True
cb = CircuitBreaker(name='WithFallback', fallback_function=fallback)
decorated_func = cb.decorate(func)
decorated_func()
fallback.assert_called_once_with()
def test_circuitbreaker_should_not_call_function_if_open():
fallback = Mock(return_value=True)
func = Mock(return_value=False, __name__="Mock") # attribute __name__ required for 2.7 compat with functools.wraps
CircuitBreaker.opened = lambda self: True
cb = CircuitBreaker(name='WithFallback', fallback_function=fallback)
decorated_func = cb.decorate(func)
assert decorated_func() == fallback.return_value
assert not func.called
def mocked_function(*args, **kwargs):
pass
def test_circuitbreaker_call_fallback_function_with_parameters():
fallback = Mock(return_value=True)
cb = circuit(name='with_fallback', fallback_function=fallback)
# mock opened prop to see if fallback is called with correct parameters.
cb.opened = lambda self: True
func_decorated = cb.decorate(mocked_function)
func_decorated('test2', test='test')
# check args and kwargs are getting correctly to fallback function
fallback.assert_called_once_with('test2', test='test')
@patch('circuitbreaker.CircuitBreaker.decorate')
def test_circuit_decorator_without_args(circuitbreaker_mock):
def function():
return True
circuit(function)
circuitbreaker_mock.assert_called_once_with(function)
def test_circuit_decorator_with_args():
def function_fallback():
return True
breaker = circuit(10, 20, KeyError, 'foobar', function_fallback)
assert breaker.is_failure(KeyError, None)
assert not breaker.is_failure(Exception, None)
assert not breaker.is_failure(FooError, None)
assert breaker._failure_threshold == 10
assert breaker._recovery_timeout == 20
assert breaker._name == "foobar"
assert breaker._fallback_function == function_fallback
def test_breaker_expected_exception_is_predicate():
def is_four_foo(thrown_type, thrown_value):
return thrown_value.val == 4
breaker_four = circuit(expected_exception=is_four_foo)
assert breaker_four.is_failure(FooError, FooError(4))
assert not breaker_four.is_failure(FooError, FooError(2))
def test_breaker_default_constructor_traps_Exception():
breaker = circuit()
assert breaker.is_failure(Exception, Exception())
assert breaker.is_failure(FooError, FooError())
def test_breaker_expected_exception_is_custom_exception():
breaker = circuit(expected_exception=FooError)
assert breaker.is_failure(FooError, FooError())
assert not breaker.is_failure(Exception, Exception())
def test_breaker_constructor_expected_exception_is_exception_list():
breaker = circuit(expected_exception=(FooError, BarError))
assert breaker.is_failure(FooError, FooError())
assert breaker.is_failure(BarError, BarError())
assert not breaker.is_failure(Exception, Exception())
def test_constructor_mistake_name_bytes():
with raises(ValueError, match="expected_exception cannot be a string *"):
circuit(10, 20, b"foobar")
def test_constructor_mistake_name_unicode():
with raises(ValueError, match="expected_exception cannot be a string *"):
circuit(10, 20, u"foobar")
def test_constructor_mistake_expected_exception():
class Widget:
pass
with raises(ValueError, match="expected_exception does not look like a predicate*"):
circuit(10, 20, expected_exception=Widget)
def test_advanced_usage_circuitbreaker_subclass():
class MyCircuitBreaker(CircuitBreaker):
EXPECTED_EXCEPTION = FooError
mybreaker = circuit(cls=MyCircuitBreaker)
assert not mybreaker.is_failure(Exception, Exception())
assert mybreaker.is_failure(FooError, FooError())
def test_advanced_usage_circuitbreaker_subclass_with_list():
class MyCircuitBreaker(CircuitBreaker):
EXPECTED_EXCEPTION = (FooError, BarError)
mybreaker = circuit(cls=MyCircuitBreaker)
assert not mybreaker.is_failure(Exception, Exception())
assert mybreaker.is_failure(FooError, FooError())
assert mybreaker.is_failure(BarError, BarError())
def test_advanced_usage_circuitbreaker_subclass_with_predicate():
def is_foo_4(thrown_type, thrown_value):
return issubclass(thrown_type, FooError) and thrown_value.val == 4
class FooFourBreaker(CircuitBreaker):
EXPECTED_EXCEPTION = is_foo_4
breaker = circuit(cls=FooFourBreaker)
assert not breaker.is_failure(Exception, Exception())
assert not breaker.is_failure(FooError, FooError())
assert breaker.is_failure(FooError, FooError(4))
def test_advanced_usage_circuitbreaker_default_expected_exception():
class NervousBreaker(CircuitBreaker):
FAILURE_THRESHOLD = 1
breaker = circuit(cls=NervousBreaker)
assert breaker._failure_threshold == 1
assert breaker.is_failure(Exception, Exception())
|