test_logcontext.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. # Copyright 2014-2022 The Matrix.org Foundation C.I.C.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from typing import Callable, Generator, cast
  15. import twisted.python.failure
  16. from twisted.internet import defer, reactor as _reactor
  17. from synapse.logging.context import (
  18. SENTINEL_CONTEXT,
  19. LoggingContext,
  20. PreserveLoggingContext,
  21. current_context,
  22. make_deferred_yieldable,
  23. nested_logging_context,
  24. run_in_background,
  25. )
  26. from synapse.types import ISynapseReactor
  27. from synapse.util import Clock
  28. from .. import unittest
  29. reactor = cast(ISynapseReactor, _reactor)
  30. class LoggingContextTestCase(unittest.TestCase):
  31. def _check_test_key(self, value: str) -> None:
  32. context = current_context()
  33. assert isinstance(context, LoggingContext)
  34. self.assertEqual(context.name, value)
  35. def test_with_context(self) -> None:
  36. with LoggingContext("test"):
  37. self._check_test_key("test")
  38. @defer.inlineCallbacks
  39. def test_sleep(self) -> Generator["defer.Deferred[object]", object, None]:
  40. clock = Clock(reactor)
  41. @defer.inlineCallbacks
  42. def competing_callback() -> Generator["defer.Deferred[object]", object, None]:
  43. with LoggingContext("competing"):
  44. yield clock.sleep(0)
  45. self._check_test_key("competing")
  46. reactor.callLater(0, competing_callback)
  47. with LoggingContext("one"):
  48. yield clock.sleep(0)
  49. self._check_test_key("one")
  50. def _test_run_in_background(self, function: Callable[[], object]) -> defer.Deferred:
  51. sentinel_context = current_context()
  52. callback_completed = False
  53. with LoggingContext("one"):
  54. # fire off function, but don't wait on it.
  55. d2 = run_in_background(function)
  56. def cb(res: object) -> object:
  57. nonlocal callback_completed
  58. callback_completed = True
  59. return res
  60. # type-ignore: this doesn't create a new Deferred: allCallback returns self.
  61. d2.addCallback(cb) # type: ignore[unused-awaitable]
  62. self._check_test_key("one")
  63. # now wait for the function under test to have run, and check that
  64. # the logcontext is left in a sane state.
  65. d2 = defer.Deferred()
  66. def check_logcontext() -> None:
  67. if not callback_completed:
  68. reactor.callLater(0.01, check_logcontext)
  69. return
  70. # make sure that the context was reset before it got thrown back
  71. # into the reactor
  72. try:
  73. self.assertIs(current_context(), sentinel_context)
  74. d2.callback(None)
  75. except BaseException:
  76. d2.errback(twisted.python.failure.Failure())
  77. reactor.callLater(0.01, check_logcontext)
  78. # test is done once d2 finishes
  79. return d2
  80. def test_run_in_background_with_blocking_fn(self) -> defer.Deferred:
  81. @defer.inlineCallbacks
  82. def blocking_function() -> Generator["defer.Deferred[object]", object, None]:
  83. yield Clock(reactor).sleep(0)
  84. return self._test_run_in_background(blocking_function)
  85. def test_run_in_background_with_non_blocking_fn(self) -> defer.Deferred:
  86. @defer.inlineCallbacks
  87. def nonblocking_function() -> Generator["defer.Deferred[object]", object, None]:
  88. with PreserveLoggingContext():
  89. yield defer.succeed(None)
  90. return self._test_run_in_background(nonblocking_function)
  91. def test_run_in_background_with_chained_deferred(self) -> defer.Deferred:
  92. # a function which returns a deferred which looks like it has been
  93. # called, but is actually paused
  94. def testfunc() -> defer.Deferred:
  95. return make_deferred_yieldable(_chained_deferred_function())
  96. return self._test_run_in_background(testfunc)
  97. def test_run_in_background_with_coroutine(self) -> defer.Deferred:
  98. async def testfunc() -> None:
  99. self._check_test_key("one")
  100. d = Clock(reactor).sleep(0)
  101. self.assertIs(current_context(), SENTINEL_CONTEXT)
  102. await d
  103. self._check_test_key("one")
  104. return self._test_run_in_background(testfunc)
  105. def test_run_in_background_with_nonblocking_coroutine(self) -> defer.Deferred:
  106. async def testfunc() -> None:
  107. self._check_test_key("one")
  108. return self._test_run_in_background(testfunc)
  109. @defer.inlineCallbacks
  110. def test_make_deferred_yieldable(
  111. self,
  112. ) -> Generator["defer.Deferred[object]", object, None]:
  113. # a function which returns an incomplete deferred, but doesn't follow
  114. # the synapse rules.
  115. def blocking_function() -> defer.Deferred:
  116. d: defer.Deferred = defer.Deferred()
  117. reactor.callLater(0, d.callback, None)
  118. return d
  119. sentinel_context = current_context()
  120. with LoggingContext("one"):
  121. d1 = make_deferred_yieldable(blocking_function())
  122. # make sure that the context was reset by make_deferred_yieldable
  123. self.assertIs(current_context(), sentinel_context)
  124. yield d1
  125. # now it should be restored
  126. self._check_test_key("one")
  127. @defer.inlineCallbacks
  128. def test_make_deferred_yieldable_with_chained_deferreds(
  129. self,
  130. ) -> Generator["defer.Deferred[object]", object, None]:
  131. sentinel_context = current_context()
  132. with LoggingContext("one"):
  133. d1 = make_deferred_yieldable(_chained_deferred_function())
  134. # make sure that the context was reset by make_deferred_yieldable
  135. self.assertIs(current_context(), sentinel_context)
  136. yield d1
  137. # now it should be restored
  138. self._check_test_key("one")
  139. def test_nested_logging_context(self) -> None:
  140. with LoggingContext("foo"):
  141. nested_context = nested_logging_context(suffix="bar")
  142. self.assertEqual(nested_context.name, "foo-bar")
  143. # a function which returns a deferred which has been "called", but
  144. # which had a function which returned another incomplete deferred on
  145. # its callback list, so won't yet call any other new callbacks.
  146. def _chained_deferred_function() -> defer.Deferred:
  147. d = defer.succeed(None)
  148. def cb(res: object) -> defer.Deferred:
  149. d2: defer.Deferred = defer.Deferred()
  150. reactor.callLater(0, d2.callback, res)
  151. return d2
  152. d.addCallback(cb) # type: ignore[unused-awaitable]
  153. return d