__init__.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. # Copyright 2019-2021 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. """
  15. Utilities for running the unit tests
  16. """
  17. import json
  18. import sys
  19. import warnings
  20. from asyncio import Future
  21. from binascii import unhexlify
  22. from typing import Awaitable, Callable, Tuple, TypeVar
  23. from unittest.mock import Mock
  24. import attr
  25. import zope.interface
  26. from twisted.python.failure import Failure
  27. from twisted.web.client import ResponseDone
  28. from twisted.web.http import RESPONSES
  29. from twisted.web.http_headers import Headers
  30. from twisted.web.iweb import IResponse
  31. from synapse.types import JsonDict
  32. TV = TypeVar("TV")
  33. def get_awaitable_result(awaitable: Awaitable[TV]) -> TV:
  34. """Get the result from an Awaitable which should have completed
  35. Asserts that the given awaitable has a result ready, and returns its value
  36. """
  37. i = awaitable.__await__()
  38. try:
  39. next(i)
  40. except StopIteration as e:
  41. # awaitable returned a result
  42. return e.value
  43. # if next didn't raise, the awaitable hasn't completed.
  44. raise Exception("awaitable has not yet completed")
  45. def make_awaitable(result: TV) -> Awaitable[TV]:
  46. """
  47. Makes an awaitable, suitable for mocking an `async` function.
  48. This uses Futures as they can be awaited multiple times so can be returned
  49. to multiple callers.
  50. """
  51. future: Future[TV] = Future()
  52. future.set_result(result)
  53. return future
  54. def setup_awaitable_errors() -> Callable[[], None]:
  55. """
  56. Convert warnings from a non-awaited coroutines into errors.
  57. """
  58. warnings.simplefilter("error", RuntimeWarning)
  59. # unraisablehook was added in Python 3.8.
  60. if not hasattr(sys, "unraisablehook"):
  61. return lambda: None
  62. # State shared between unraisablehook and check_for_unraisable_exceptions.
  63. unraisable_exceptions = []
  64. orig_unraisablehook = sys.unraisablehook
  65. def unraisablehook(unraisable):
  66. unraisable_exceptions.append(unraisable.exc_value)
  67. def cleanup():
  68. """
  69. A method to be used as a clean-up that fails a test-case if there are any new unraisable exceptions.
  70. """
  71. sys.unraisablehook = orig_unraisablehook
  72. if unraisable_exceptions:
  73. raise unraisable_exceptions.pop()
  74. sys.unraisablehook = unraisablehook
  75. return cleanup
  76. def simple_async_mock(return_value=None, raises=None) -> Mock:
  77. # AsyncMock is not available in python3.5, this mimics part of its behaviour
  78. async def cb(*args, **kwargs):
  79. if raises:
  80. raise raises
  81. return return_value
  82. return Mock(side_effect=cb)
  83. # Type ignore: it does not fully implement IResponse, but is good enough for tests
  84. @zope.interface.implementer(IResponse)
  85. @attr.s(slots=True, frozen=True, auto_attribs=True)
  86. class FakeResponse: # type: ignore[misc]
  87. """A fake twisted.web.IResponse object
  88. there is a similar class at treq.test.test_response, but it lacks a `phrase`
  89. attribute, and didn't support deliverBody until recently.
  90. """
  91. version: Tuple[bytes, int, int] = (b"HTTP", 1, 1)
  92. # HTTP response code
  93. code: int = 200
  94. # body of the response
  95. body: bytes = b""
  96. headers: Headers = attr.Factory(Headers)
  97. @property
  98. def phrase(self):
  99. return RESPONSES.get(self.code, b"Unknown Status")
  100. @property
  101. def length(self):
  102. return len(self.body)
  103. def deliverBody(self, protocol):
  104. protocol.dataReceived(self.body)
  105. protocol.connectionLost(Failure(ResponseDone()))
  106. @classmethod
  107. def json(cls, *, code: int = 200, payload: JsonDict) -> "FakeResponse":
  108. headers = Headers({"Content-Type": ["application/json"]})
  109. body = json.dumps(payload).encode("utf-8")
  110. return cls(code=code, body=body, headers=headers)
  111. # A small image used in some tests.
  112. #
  113. # Resolution: 1×1, MIME type: image/png, Extension: png, Size: 67 B
  114. SMALL_PNG = unhexlify(
  115. b"89504e470d0a1a0a0000000d4948445200000001000000010806"
  116. b"0000001f15c4890000000a49444154789c63000100000500010d"
  117. b"0a2db40000000049454e44ae426082"
  118. )