| from time import sleep, time |
| from unittest import TestCase |
| |
| from mock import Mock, call |
| from tools import funcutils |
| |
| |
| class Testget_rate_limited_function(TestCase): |
| |
| def setUp(self): |
| self.mock_func, self.mock_limit = Mock(name='func'), Mock(name='limit') |
| self.rate_limited_func = funcutils.get_rate_limited_function(self.mock_func, self.mock_limit) |
| |
| def _assert_initialized_correctly_with_mocks(self, rate_limited_func_arg): |
| """ |
| Assert the rate_limited_func argument was correctly initialized with |
| the mocks created in setUp as its func and limit. |
| """ |
| self.assertIs(rate_limited_func_arg.func, self.mock_func) |
| self.assertIs(rate_limited_func_arg.limit, self.mock_limit) |
| assert rate_limited_func_arg.last_called == False |
| |
| def test_init_with_positional_args(self): |
| """ |
| Rate-limited functions can be initialized with the function and limit in that order. |
| """ |
| self._assert_initialized_correctly_with_mocks( |
| funcutils.get_rate_limited_function(self.mock_func, self.mock_limit) |
| ) |
| |
| def test_init_with_keyword_args(self): |
| """ |
| Rate-limited functions can be initialized with limit and func keyword arguments. |
| """ |
| self._assert_initialized_correctly_with_mocks( |
| funcutils.get_rate_limited_function(limit=self.mock_limit, func=self.mock_func) |
| ) |
| |
| def test_repr(self): |
| """ |
| A rate-limited function's repr is what we expect. |
| """ |
| self.rate_limited_func.last_called = mock_last_called = Mock() |
| self.assertEqual( |
| repr(self.rate_limited_func), |
| ('get_rate_limited_function(' |
| 'func=' + repr(self.mock_func) + ', ' |
| 'limit=' + repr(self.mock_limit) + ', ' |
| 'last_called=' + repr(mock_last_called)) + ')' |
| ) |
| |
| def test_calling_rate_limited_func_delegates_to_wrapped_func(self): |
| """ |
| Calling a rate-limited function delegates to the wrapped function. |
| """ |
| self.rate_limited_func.limit = 1 |
| arg, kwarg = Mock(name='arg'), Mock(name='kwarg') |
| self.rate_limited_func(arg, kwarg=kwarg) |
| self.mock_func.assert_called_once_with(arg, kwarg=kwarg) |
| |
| def test_calling_func_attribute_calls_wrapped_function(self): |
| """ |
| Calling a rate-limited function's func attribute calls the wrapped function. |
| """ |
| arg, kwarg = Mock(name='arg'), Mock(name='kwarg') |
| self.rate_limited_func.func(arg, kwarg=kwarg) |
| self.mock_func.assert_called_once_with(arg, kwarg=kwarg) |
| |
| def test_calling_func_attribute_not_rate_limited(self): |
| """ |
| Calling a rate-limited function's func attribute is not affected by rate limiting. |
| """ |
| self.rate_limited_func.limit = 5 |
| arg0, kwarg0, arg1, kwarg1 = (Mock(name='arg0'), Mock(name='kwarg0'), |
| Mock(name='arg1'), Mock(name='kwarg1')) |
| self.rate_limited_func.func(arg0, kwarg=kwarg0) |
| self.rate_limited_func.func(arg1, kwarg=kwarg1) |
| |
| self.mock_func.assert_has_calls([call(arg0, kwarg=kwarg0), |
| call(arg1, kwarg=kwarg1)]) |
| |
| def test_rate_limit_respected(self): |
| """ |
| If you call a rate-limited function before the time limit is up, it is called not called. |
| |
| This tests behavior with respect to last_called, rather than actually sleeping. |
| """ |
| self.rate_limited_func.limit = 1 |
| self.rate_limited_func.last_called = time() - .5 |
| self.rate_limited_func() |
| self.mock_func.assert_not_called() |
| |
| def test_can_call_again_if_last_called_older_than_limit(self): |
| """ |
| If you call a rate-limited function a second time after the time limit is up, it is called twice. |
| |
| This tests behavior with respect to last_called, rather than actually sleeping. |
| """ |
| self.rate_limited_func.limit = 100 |
| self.rate_limited_func.last_called = time() - 100 |
| self.rate_limited_func() |
| self.mock_func.assert_has_calls([call()]) |
| |
| def test_last_called_set_when_called(self): |
| """ |
| If you call a rate-limited function, last_called is set to a new value. |
| """ |
| self.rate_limited_func.limit = 1 |
| assert self.rate_limited_func.last_called == False |
| self.rate_limited_func() |
| assert abs(round(self.rate_limited_func.last_called, 2) - round(time(), 2)) <= 0.0 |
| |
| def test_last_called_not_set_when_called_within_time_limit(self): |
| """ |
| If you call a rate-limited function during the time limit, last_called is not set to a new value. |
| """ |
| self.rate_limited_func.limit = 1 |
| assert self.rate_limited_func.last_called == False |
| self.rate_limited_func() |
| last_called = self.rate_limited_func.last_called |
| self.rate_limited_func() |
| self.assertIs(last_called, self.rate_limited_func.last_called) |
| |
| def test_end_to_end(self): |
| """ |
| A rate-limited function works as expected. |
| |
| A rate-limited function delegates to the wrapped function, then |
| prevents calls until the time limit has passed, then allows calls |
| again. |
| """ |
| self.rate_limited_func.limit = .5 |
| self.rate_limited_func() |
| self.mock_func.assert_called_once() |
| |
| # If called before limit has elapsed, the wrapped function won't be |
| # called again. |
| self.rate_limited_func() |
| self.mock_func.assert_called_once() |
| |
| # After limit has elapsed, the wrapped function can be called again. |
| sleep(.5) |
| self.rate_limited_func() |
| self.mock_func.assert_has_calls([call(), call()]) |