test_transactions.py 5.0 KB

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