def test_waiter_transitions_to_retry_but_max_attempts_exceeded(self): acceptors = [ { 'state': 'success', 'matcher': 'status', 'expected': 200 }, { 'state': 'retry', 'matcher': 'error', 'expected': 'RetryMe' }, ] config = self.create_waiter_config(acceptors=acceptors) operation_method = mock.Mock() self.client_responses_are( {'Success': False}, {'Error': { 'Code': 'RetryMe', 'Message': 'foo' }}, {'Success': False}, {'Success': False}, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) with self.assertRaises(WaiterError): waiter.wait()
def test_waiter_handles_retry_state(self): acceptor_with_retry_state = [ { 'state': 'success', 'matcher': 'status', 'expected': 200 }, { 'state': 'retry', 'matcher': 'error', 'expected': 'RetryMe' }, ] config = self.create_waiter_config(acceptors=acceptor_with_retry_state) operation_method = mock.Mock() self.client_responses_are( {'Nothing': 'foo'}, {'Error': { 'Code': 'RetryMe', 'Message': 'foo' }}, { 'Success': True, 'ResponseMetadata': { 'HTTPStatusCode': 200 } }, {'NeverCalled': True}, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) waiter.wait() self.assertEqual(operation_method.call_count, 3)
def test_waiter_transitions_to_failure_state(self): acceptors = [ # A success state that will never be hit. { 'state': 'success', 'matcher': 'status', 'expected': 1000 }, { 'state': 'failure', 'matcher': 'error', 'expected': 'FailError' }, ] config = self.create_waiter_config(acceptors=acceptors) operation_method = mock.Mock() self.client_responses_are( {'Nothing': 'foo'}, # And on the second attempt, a FailError is seen, which # causes the waiter to fail fast. {'Error': { 'Code': 'FailError', 'Message': 'foo' }}, {'WillNeverGetCalled': True}, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) with self.assertRaises(WaiterError): waiter.wait() # Not only should we raise an exception, but we should have # only called the operation_method twice because the second # response triggered a fast fail. self.assertEqual(operation_method.call_count, 2)
def test_last_response_available_on_waiter_error(self): last_response = {'Error': {'Code': 'UnknownError', 'Message': 'bad error'}} config = self.create_waiter_config() operation_method = mock.Mock() self.client_responses_are(last_response, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) with self.assertRaises(WaiterError) as e: waiter.wait() self.assertEqual(e.exception.last_response, last_response)
def _assert_failure_state_error_raised(self, acceptors, responses, expected_msg): config = self.create_waiter_config( acceptors=acceptors) operation_method = mock.Mock() waiter = Waiter('MyWaiter', config, operation_method) self.client_responses_are( *responses, for_operation=operation_method ) with self.assertRaisesRegex(WaiterError, expected_msg): waiter.wait()
def test_waiter_never_matches(self): # Verify that a matcher will fail after max_attempts # is exceeded. config = self.create_waiter_config(max_attempts=3) operation_method = mock.Mock() self.client_responses_are({'Foo': 'FAILURE'}, {'Foo': 'FAILURE'}, {'Foo': 'FAILURE'}, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) with self.assertRaises(WaiterError): waiter.wait()
def test_waiter_invocation_config_honors_max_attempts(self): config = self.create_waiter_config() operation_method = mock.Mock() self.client_responses_are( {'Success': False}, {'Success': False}, for_operation=operation_method ) waiter = Waiter('MyWaiter', config, operation_method) custom_max = 2 with self.assertRaises(WaiterError): waiter.wait(WaiterConfig={'MaxAttempts': custom_max}) self.assertEqual(operation_method.call_count, 2)
def test_kwargs_are_passed_through(self): acceptors = [ {'state': 'success', 'matcher': 'error', 'expected': 'MyError'}, ] config = self.create_waiter_config(acceptors=acceptors) operation_method = mock.Mock() self.client_responses_are( {'Error': {'Code': 'MyError'}}, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) waiter.wait(Foo='foo', Bar='bar', Baz='baz') operation_method.assert_called_with(Foo='foo', Bar='bar', Baz='baz')
def test_waiter_matches_with_invalid_error_response(self): # Verify that the call will not raise WaiterError # because of 'Error' key in success response. config = self.create_waiter_config( max_attempts=3, acceptors=[{'state': 'success', 'matcher': 'path', 'argument': 'Foo', 'expected': 'SUCCESS'}]) operation_method = mock.Mock() waiter = Waiter('MyWaiter', config, operation_method) self.client_responses_are( {'Foo': 'SUCCESS', 'Error': 'foo'}, for_operation=operation_method ) waiter.wait() self.assertEqual(operation_method.call_count, 1)
def test_unspecified_errors_stops_waiter(self): # If a waiter receives an error response, then the # waiter immediately stops. config = self.create_waiter_config() operation_method = mock.Mock() self.client_responses_are( # This is an unknown error that's not called out # in any of the waiter config, so when the # waiter encounters this response it will transition # to the failure state. {'Error': {'Code': 'UnknownError', 'Message': 'bad error'}}, for_operation=operation_method ) waiter = Waiter('MyWaiter', config, operation_method) with self.assertRaises(WaiterError): waiter.wait()
def test_waiter_invocation_config_honors_delay(self, sleep_mock): config = self.create_waiter_config() operation_method = mock.Mock() self.client_responses_are( {'Success': False}, {'Success': False}, {'Success': False}, for_operation=operation_method ) waiter = Waiter('MyWaiter', config, operation_method) custom_delay = 3 with self.assertRaises(WaiterError): waiter.wait(WaiterConfig={'Delay': custom_delay}) # We attempt three times, which means we need to sleep # twice, once before each subsequent request. self.assertEqual(sleep_mock.call_count, 2) sleep_mock.assert_called_with(custom_delay)
def test_waiter_waits_until_acceptor_matches(self): config = self.create_waiter_config( max_attempts=3, acceptors=[{'state': 'success', 'matcher': 'path', 'argument': 'Foo', 'expected': 'SUCCESS'}]) # Simulate the client having two calls that don't # match followed by a third call that matches the # acceptor. operation_method = mock.Mock() waiter = Waiter('MyWaiter', config, operation_method) self.client_responses_are( {'Foo': 'FAILURE'}, {'Foo': 'FAILURE'}, {'Foo': 'SUCCESS'}, for_operation=operation_method ) waiter.wait() self.assertEqual(operation_method.call_count, 3)
def test_unspecified_errors_propagate_error_code(self): # If a waiter receives an error response, then the # waiter should pass along the error code config = self.create_waiter_config() operation_method = mock.Mock() error_code = 'error_message' error_message = 'error_message' self.client_responses_are( # This is an unknown error that's not called out # in any of the waiter config, so when the # waiter encounters this response it will transition # to the failure state. {'Error': {'Code': error_code, 'Message': error_message}}, for_operation=operation_method ) waiter = Waiter('MyWaiter', config, operation_method) with self.assertRaisesRegex(WaiterError, error_message): waiter.wait()
def test_waiter_honors_delay_time_between_retries(self, sleep_mock): delay_time = 5 config = self.create_waiter_config(delay=delay_time) operation_method = mock.Mock() self.client_responses_are( # This is an unknown error that's not called out # in any of the waiter config, so when the # waiter encounters this response it will transition # to the failure state. {'Success': False}, {'Success': False}, {'Success': False}, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) with self.assertRaises(WaiterError): waiter.wait() # We attempt three times, which means we need to sleep # twice, once before each subsequent request. self.assertEqual(sleep_mock.call_count, 2) sleep_mock.assert_called_with(delay_time)