test_transactions.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. from synapse.rest.client.transactions import HttpTransactionCache
  2. from synapse.rest.client.transactions import CLEANUP_PERIOD_MS
  3. from twisted.internet import defer
  4. from mock import Mock, call
  5. from synapse.util import async
  6. from synapse.util.logcontext import LoggingContext
  7. from tests import unittest
  8. from tests.utils import MockClock
  9. class HttpTransactionCacheTestCase(unittest.TestCase):
  10. def setUp(self):
  11. self.clock = MockClock()
  12. self.cache = HttpTransactionCache(self.clock)
  13. self.mock_http_response = (200, "GOOD JOB!")
  14. self.mock_key = "foo"
  15. @defer.inlineCallbacks
  16. def test_executes_given_function(self):
  17. cb = Mock(
  18. return_value=defer.succeed(self.mock_http_response)
  19. )
  20. res = yield self.cache.fetch_or_execute(
  21. self.mock_key, cb, "some_arg", keyword="arg"
  22. )
  23. cb.assert_called_once_with("some_arg", keyword="arg")
  24. self.assertEqual(res, self.mock_http_response)
  25. @defer.inlineCallbacks
  26. def test_deduplicates_based_on_key(self):
  27. cb = Mock(
  28. return_value=defer.succeed(self.mock_http_response)
  29. )
  30. for i in range(3): # invoke multiple times
  31. res = yield self.cache.fetch_or_execute(
  32. self.mock_key, cb, "some_arg", keyword="arg", changing_args=i
  33. )
  34. self.assertEqual(res, self.mock_http_response)
  35. # expect only a single call to do the work
  36. cb.assert_called_once_with("some_arg", keyword="arg", changing_args=0)
  37. @defer.inlineCallbacks
  38. def test_logcontexts_with_async_result(self):
  39. @defer.inlineCallbacks
  40. def cb():
  41. yield async.sleep(0)
  42. defer.returnValue("yay")
  43. @defer.inlineCallbacks
  44. def test():
  45. with LoggingContext("c") as c1:
  46. res = yield self.cache.fetch_or_execute(self.mock_key, cb)
  47. self.assertIs(LoggingContext.current_context(), c1)
  48. self.assertEqual(res, "yay")
  49. # run the test twice in parallel
  50. d = defer.gatherResults([test(), test()])
  51. self.assertIs(LoggingContext.current_context(), LoggingContext.sentinel)
  52. yield d
  53. self.assertIs(LoggingContext.current_context(), LoggingContext.sentinel)
  54. @defer.inlineCallbacks
  55. def test_does_not_cache_exceptions(self):
  56. """Checks that, if the callback throws an exception, it is called again
  57. for the next request.
  58. """
  59. called = [False]
  60. def cb():
  61. if called[0]:
  62. # return a valid result the second time
  63. return defer.succeed(self.mock_http_response)
  64. called[0] = True
  65. raise Exception("boo")
  66. with LoggingContext("test") as test_context:
  67. try:
  68. yield self.cache.fetch_or_execute(self.mock_key, cb)
  69. except Exception as e:
  70. self.assertEqual(e.message, "boo")
  71. self.assertIs(LoggingContext.current_context(), test_context)
  72. res = yield self.cache.fetch_or_execute(self.mock_key, cb)
  73. self.assertEqual(res, self.mock_http_response)
  74. self.assertIs(LoggingContext.current_context(), test_context)
  75. @defer.inlineCallbacks
  76. def test_does_not_cache_failures(self):
  77. """Checks that, if the callback returns a failure, it is called again
  78. for the next request.
  79. """
  80. called = [False]
  81. def cb():
  82. if called[0]:
  83. # return a valid result the second time
  84. return defer.succeed(self.mock_http_response)
  85. called[0] = True
  86. return defer.fail(Exception("boo"))
  87. with LoggingContext("test") as test_context:
  88. try:
  89. yield self.cache.fetch_or_execute(self.mock_key, cb)
  90. except Exception as e:
  91. self.assertEqual(e.message, "boo")
  92. self.assertIs(LoggingContext.current_context(), test_context)
  93. res = yield self.cache.fetch_or_execute(self.mock_key, cb)
  94. self.assertEqual(res, self.mock_http_response)
  95. self.assertIs(LoggingContext.current_context(), test_context)
  96. @defer.inlineCallbacks
  97. def test_cleans_up(self):
  98. cb = Mock(
  99. return_value=defer.succeed(self.mock_http_response)
  100. )
  101. yield self.cache.fetch_or_execute(
  102. self.mock_key, cb, "an arg"
  103. )
  104. # should NOT have cleaned up yet
  105. self.clock.advance_time_msec(CLEANUP_PERIOD_MS / 2)
  106. yield self.cache.fetch_or_execute(
  107. self.mock_key, cb, "an arg"
  108. )
  109. # still using cache
  110. cb.assert_called_once_with("an arg")
  111. self.clock.advance_time_msec(CLEANUP_PERIOD_MS)
  112. yield self.cache.fetch_or_execute(
  113. self.mock_key, cb, "an arg"
  114. )
  115. # no longer using cache
  116. self.assertEqual(cb.call_count, 2)
  117. self.assertEqual(
  118. cb.call_args_list,
  119. [call("an arg",), call("an arg",)]
  120. )