def test_arguments_are_preserved_in_retries(self): for i in xrange(3): doc = search.Document(doc_id='doc%d' % i, fields=[ search.TextField('prop', 'val'), search.NumberField('index', i) ]) search.Index('my_index').put(doc) exception = search.TransientError('oops') failing_index_search = test_utils.FailingFunction( search.Index.search, exception, 3) search_counter = test_utils.CallCounter(gae_search_services.search) gae_search_ctx = self.swap( search.Index, 'search', failing_index_search) search_counter_ctx = self.swap( gae_search_services, 'search', search_counter) with gae_search_ctx, search_counter_ctx: result, cursor = gae_search_services.search( 'prop:val', 'my_index', sort='-index', limit=2, ids_only=True, retries=4) failing_index_search2 = test_utils.FailingFunction( search.Index.search, exception, 3) search_counter2 = test_utils.CallCounter(gae_search_services.search) gae_search_ctx2 = self.swap( search.Index, 'search', failing_index_search2) search_counter_ctx2 = self.swap( gae_search_services, 'search', search_counter2) with gae_search_ctx2, search_counter_ctx2: result2, cursor = gae_search_services.search( 'prop:val', 'my_index', sort='-index', limit=2, cursor=cursor, ids_only=True, retries=4) self.assertEqual(search_counter.times_called, 4) self.assertEqual(result, ['doc2', 'doc1']) # also check that the cursor is preserved self.assertEqual(search_counter2.times_called, 4) self.assertEqual(result2, ['doc0'])
def test_put_error_without_transient_result(self): docs = [{ 'id': 'doc1', 'prop': 'val1' }, { 'id': 'doc2', 'prop': 'val2' }, { 'id': 'doc3', 'prop': 'val3' }] error = self._get_put_error(3) failing_put = test_utils.FailingFunction(search.Index.put, error, 1) add_docs_counter = test_utils.CallCounter( gae_search_services.add_documents_to_index) add_docs_ctx = self.swap(gae_search_services, 'add_documents_to_index', add_docs_counter) put_ctx = self.swap(search.Index, 'put', failing_put) assert_raises_ctx = self.assertRaisesRegexp( gae_search_services.SearchFailureError, '<class \'google.appengine.api.search.search.PutError\'>: lol') with add_docs_ctx, put_ctx, assert_raises_ctx as e: gae_search_services.add_documents_to_index(docs, 'my_index') # Assert that the method only gets called once, since the error is not # transient. self.assertEqual(add_docs_counter.times_called, 1) self.assertEqual(e.exception.original_exception, error)
def test_delete_error_with_transient_result(self): error = self._get_delete_error(3, transient=1) failing_delete = test_utils.FailingFunction(search.Index.delete, error, 4) delete_docs_counter = test_utils.CallCounter( gae_search_services.delete_documents_from_index) index = search.Index('my_index') for i in xrange(3): index.put( search.Document( doc_id='d' + str(i), fields=[search.TextField(name='prop', value='value')])) delete_ctx = self.swap(search.Index, 'delete', failing_delete) delete_docs_ctx = self.swap(gae_search_services, 'delete_documents_from_index', delete_docs_counter) with delete_ctx, delete_docs_ctx: gae_search_services.delete_documents_from_index(['d0', 'd1', 'd2'], 'my_index', retries=5) self.assertEqual(delete_docs_counter.times_called, 5) for i in xrange(3): result = search.Index('my_index').get('doc' + str(i)) self.assertIsNone(result)
def test_arguments_are_preserved_in_retries(self): index = search.Index('index') index.put([ search.Document( doc_id='doc', fields=[search.TextField(name='prop', value='val')]) ]) exception = self._get_delete_error(1, 0) failing_delete = test_utils.FailingFunction(search.Index.delete, exception, 3) delete_docs_counter = test_utils.CallCounter( gae_search_services.delete_documents_from_index) index_ctx = self.swap(search.Index, 'delete', failing_delete) delete_docs_ctx = self.swap(gae_search_services, 'delete_documents_from_index', delete_docs_counter) with index_ctx, delete_docs_ctx: gae_search_services.delete_documents_from_index(['doc'], 'index', 4) self.assertEqual(delete_docs_counter.times_called, 4) result = search.Index('my_index').get('doc') self.assertIsNone(result)
def test_arguments_are_preserved_in_retries(self): doc = {'id': 'doc', 'prop': 'val'} exception = self._get_put_error(1, transient=0) failing_put = test_utils.FailingFunction( search.Index.put, exception, 3 ) add_docs_counter = test_utils.CallCounter( gae_search_services.add_documents_to_index) put_ctx = self.swap(search.Index, 'put', failing_put) add_docs_ctx = self.swap( gae_search_services, 'add_documents_to_index', add_docs_counter) with put_ctx, add_docs_ctx: gae_search_services.add_documents_to_index( [doc], 'my_index', retries=4) self.assertEqual(add_docs_counter.times_called, 4) result = search.Index('my_index').get('doc') self.assertEqual(result.field('prop').value, 'val')
def test_put_error_with_transient_result(self): docs = [{ 'id': 'doc1', 'prop': 'val1' }, { 'id': 'doc2', 'prop': 'val2' }, { 'id': 'doc3', 'prop': 'val3' }] error = self._get_put_error(3, 1) failing_put = test_utils.FailingFunction(search.Index.put, error, 4) add_docs_counter = test_utils.CallCounter( gae_search_services.add_documents_to_index) put_ctx = self.swap(search.Index, 'put', failing_put) add_docs_ctx = self.swap(gae_search_services, 'add_documents_to_index', add_docs_counter) with put_ctx, add_docs_ctx: gae_search_services.add_documents_to_index(docs, 'my_index', 5) self.assertEqual(add_docs_counter.times_called, 5) for i in xrange(1, 4): result = search.Index('my_index').get('doc' + str(i)) self.assertEqual(result.field('prop').value, 'val' + str(i))
def test_use_default_num_retries(self): exception = self._get_delete_error(1, 0) failing_delete = test_utils.FailingFunction( search.Index.delete, exception, gae_search_services.DEFAULT_NUM_RETRIES ) delete_docs_counter = test_utils.CallCounter( gae_search_services.delete_documents_from_index) delete_ctx = self.swap(search.Index, 'delete', failing_delete) delete_docs_ctx = self.swap( gae_search_services, 'delete_documents_from_index', delete_docs_counter) assert_raises_ctx = self.assertRaises( gae_search_services.SearchFailureError) with delete_ctx, delete_docs_ctx, assert_raises_ctx as context_mgr: gae_search_services.delete_documents_from_index( ['doc'], 'my_index') self.assertEqual(context_mgr.exception.original_exception, exception) self.assertEqual( delete_docs_counter.times_called, gae_search_services.DEFAULT_NUM_RETRIES)
def test_use_default_num_retries(self): doc = {'id': 'doc', 'prop': 'val'} exception = self._get_put_error(1, 0) failing_put = test_utils.FailingFunction( search.Index.put, exception, gae_search_services.DEFAULT_NUM_RETRIES, ) add_docs_counter = test_utils.CallCounter( gae_search_services.add_documents_to_index) put_ctx = self.swap(search.Index, 'put', failing_put) add_docs_ctx = self.swap( gae_search_services, 'add_documents_to_index', add_docs_counter) assert_raises_ctx = self.assertRaises( gae_search_services.SearchFailureError) with put_ctx, add_docs_ctx, assert_raises_ctx as context_mgr: gae_search_services.add_documents_to_index([doc], 'my_index') self.assertEqual(context_mgr.exception.original_exception, exception) self.assertEqual( add_docs_counter.times_called, gae_search_services.DEFAULT_NUM_RETRIES)
def test_failing_function_raises_error_with_invalid_num_tries(self): class MockError(Exception): pass function = lambda x: x ** 2 with self.assertRaisesRegex( ValueError, 'num_tries_before_success should either be an integer greater than ' 'or equal to 0, or FailingFunction.INFINITY'): test_utils.FailingFunction(function, MockError, -1)
def test_failing_function_never_succeeds_when_n_is_infinity(self): class MockError(Exception): pass function = lambda x: x**2 failing_func = test_utils.FailingFunction( function, MockError, test_utils.FailingFunction.INFINITY) for i in xrange(20): with self.assertRaises(MockError): failing_func(i)
def test_failing_function_never_succeeds_when_n_is_infinity(self): class MockError(Exception): pass function = lambda x: x ** 2 failing_func = test_utils.FailingFunction( function, MockError('Dummy Exception'), test_utils.FailingFunction.INFINITY) for i in python_utils.RANGE(20): with self.assertRaisesRegexp(MockError, 'Dummy Exception'): failing_func(i)
def test_failing_function_fails_for_first_n_calls(self): class MockError(Exception): pass function = lambda x: x**2 failing_func = test_utils.FailingFunction(function, MockError, 5) for i in xrange(5): with self.assertRaises(MockError): failing_func(i) raise ValueError(str(i)) self.assertEqual(failing_func(5), 25)
def test_use_custom_number_of_retries(self): exception = search.TransientError('oops') failing_index_search = test_utils.FailingFunction( search.Index.search, exception, 3) search_counter = test_utils.CallCounter(gae_search_services.search) index_ctx = self.swap(search.Index, 'search', failing_index_search) search_counter_ctx = self.swap(gae_search_services, 'search', search_counter) assert_raises_ctx = self.assertRaises( gae_search_services.SearchFailureError) with index_ctx, search_counter_ctx, assert_raises_ctx: gae_search_services.search('query', 'my_index', retries=3) self.assertEqual(search_counter.times_called, 3)
def test_use_custom_number_of_retries(self): doc = {'id': 'doc', 'prop': 'val'} exception = self._get_put_error(1, 0) failing_put = test_utils.FailingFunction(search.Index.put, exception, 42) add_docs_counter = test_utils.CallCounter( gae_search_services.add_documents_to_index) put_ctx = self.swap(search.Index, 'put', failing_put) add_docs_ctx = self.swap(gae_search_services, 'add_documents_to_index', add_docs_counter) assert_raises_ctx = self.assertRaises( gae_search_services.SearchFailureError) with put_ctx, add_docs_ctx, assert_raises_ctx: gae_search_services.add_documents_to_index([doc], 'my_index', 42) self.assertEqual(add_docs_counter.times_called, 42)
def test_put_error_without_transient_result(self): error = self._get_delete_error(3) delete_spy = test_utils.FailingFunction(search.Index.delete, error, 1) delete_docs_counter = test_utils.CallCounter( gae_search_services.delete_documents_from_index) delete_docs_ctx = self.swap(gae_search_services, 'delete_documents_from_index', delete_docs_counter) delete_ctx = self.swap(search.Index, 'delete', delete_spy) assert_raises_ctx = self.assertRaises( gae_search_services.SearchFailureError) with delete_docs_ctx, delete_ctx, assert_raises_ctx as e: gae_search_services.delete_documents_from_index(['a', 'b', 'c'], 'my_index') # assert that the method only gets called once, since the error is not # transient. self.assertEqual(delete_docs_counter.times_called, 1) self.assertEqual(e.exception.original_exception, error)
def test_use_custom_number_of_retries(self): exception = self._get_delete_error(1, 0) failing_delete = test_utils.FailingFunction(search.Index.delete, exception, 42) delete_docs_counter = test_utils.CallCounter( gae_search_services.delete_documents_from_index) delete_ctx = self.swap(search.Index, 'delete', failing_delete) delete_docs_ctx = self.swap(gae_search_services, 'delete_documents_from_index', delete_docs_counter) assert_raises_ctx = self.assertRaises( gae_search_services.SearchFailureError) with delete_ctx, delete_docs_ctx, assert_raises_ctx: gae_search_services.delete_documents_from_index(['id'], 'index', 42) self.assertEqual(delete_docs_counter.times_called, 42)
def test_use_default_num_retries(self): exception = search.TransientError('oops') failing_index_search = test_utils.FailingFunction( search.Index.search, exception, 1) search_counter = test_utils.CallCounter(gae_search_services.search) search_ctx = self.swap(search.Index, 'search', failing_index_search) search_counter_ctx = self.swap(gae_search_services, 'search', search_counter) assert_raises_ctx = self.assertRaisesRegexp( gae_search_services.SearchFailureError, '<class \'google.appengine.api.search.search.TransientError\'>: ' 'oops') with search_ctx, search_counter_ctx, assert_raises_ctx as context_mgr: gae_search_services.search('query', 'my_index') self.assertEqual(context_mgr.exception.original_exception, exception) self.assertEqual(search_counter.times_called, 1)
def test_use_default_num_retries(self): exception = search.TransientError('oops') failing_index_search = test_utils.FailingFunction( search.Index.search, exception, gae_search_services.DEFAULT_NUM_RETRIES) search_counter = test_utils.CallCounter(gae_search_services.search) search_ctx = self.swap(search.Index, 'search', failing_index_search) search_counter_ctx = self.swap(gae_search_services, 'search', search_counter) assert_raises_ctx = self.assertRaises( gae_search_services.SearchFailureError) with search_ctx, search_counter_ctx, assert_raises_ctx as context_mgr: gae_search_services.search('query', 'my_index') self.assertEqual(context_mgr.exception.original_exception, exception) self.assertEqual(search_counter.times_called, gae_search_services.DEFAULT_NUM_RETRIES)
def test_use_custom_number_of_retries(self): exception = self._get_delete_error(1, transient=0) failing_delete = test_utils.FailingFunction( search.Index.delete, exception, 42) delete_docs_counter = test_utils.CallCounter( gae_search_services.delete_documents_from_index) delete_ctx = self.swap(search.Index, 'delete', failing_delete) delete_docs_ctx = self.swap( gae_search_services, 'delete_documents_from_index', delete_docs_counter) assert_raises_ctx = self.assertRaisesRegexp( gae_search_services.SearchFailureError, '<class \'google.appengine.api.search.search.DeleteError\'>: lol') with delete_ctx, delete_docs_ctx, assert_raises_ctx: gae_search_services.delete_documents_from_index( ['id'], 'index', retries=42) self.assertEqual(delete_docs_counter.times_called, 42)
def test_fetch_gravatar_failure_exception(self): user_email = '*****@*****.**' error_messages = [] def log_mock(message): error_messages.append(message) gravatar_url = user_services.get_gravatar_url(user_email) expected_error_message = ( 'Failed to fetch Gravatar from %s' % gravatar_url) logging_error_mock = test_utils.CallCounter(log_mock) urlfetch_fail_mock = test_utils.FailingFunction( urlfetch.fetch, urlfetch.InvalidURLError, test_utils.FailingFunction.INFINITY) log_swap_ctx = self.swap(logging, 'error', logging_error_mock) fetch_swap_ctx = self.swap(urlfetch, 'fetch', urlfetch_fail_mock) with log_swap_ctx, fetch_swap_ctx: profile_picture = user_services.fetch_gravatar(user_email) self.assertEqual(logging_error_mock.times_called, 1) self.assertEqual(expected_error_message, error_messages[0]) self.assertEqual( profile_picture, user_services.DEFAULT_IDENTICON_DATA_URL)
def test_use_default_num_retries(self): exception = self._get_delete_error(1, transient=0) failing_delete = test_utils.FailingFunction(search.Index.delete, exception, 1) delete_docs_counter = test_utils.CallCounter( gae_search_services.delete_documents_from_index) delete_ctx = self.swap(search.Index, 'delete', failing_delete) delete_docs_ctx = self.swap(gae_search_services, 'delete_documents_from_index', delete_docs_counter) assert_raises_ctx = self.assertRaisesRegexp( gae_search_services.SearchFailureError, '<class \'google.appengine.api.search.search.DeleteError\'>: lol') with delete_ctx, delete_docs_ctx, assert_raises_ctx as context_mgr: gae_search_services.delete_documents_from_index(['doc'], 'my_index') self.assertEqual(context_mgr.exception.original_exception, exception) self.assertEqual(delete_docs_counter.times_called, 1)