conftest.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import os
  2. import sys
  3. from collections.abc import MutableMapping
  4. import psutil
  5. import pytest
  6. import responses
  7. from django.core.cache import cache
  8. from django.db import connections
  9. from sentry.silo.base import SiloMode
  10. from sentry.testutils.pytest.sentry import get_default_silo_mode_for_test_cases
  11. pytest_plugins = ["sentry.testutils.pytest"]
  12. # XXX: The below code is vendored code from https://github.com/utgwkk/pytest-github-actions-annotate-failures
  13. # so that we can add support for pytest_rerunfailures
  14. # retried tests will no longer be annotated in GHA
  15. #
  16. # Reference:
  17. # https://docs.pytest.org/en/latest/writing_plugins.html#hookwrapper-executing-around-other-hooks
  18. # https://docs.pytest.org/en/latest/writing_plugins.html#hook-function-ordering-call-example
  19. # https://docs.pytest.org/en/stable/reference.html#pytest.hookspec.pytest_runtest_makereport
  20. #
  21. # Inspired by:
  22. # https://github.com/pytest-dev/pytest/blob/master/src/_pytest/terminal.py
  23. if sys.platform == "linux":
  24. def _open_files() -> frozenset[str]:
  25. ret = []
  26. pid = os.getpid()
  27. for fd in os.listdir(f"/proc/{pid}/fd"):
  28. try:
  29. path = os.readlink(f"/proc/{pid}/fd/{fd}")
  30. except FileNotFoundError:
  31. continue
  32. else:
  33. if os.path.exists(path):
  34. ret.append(path)
  35. return frozenset(ret)
  36. else:
  37. def _open_files() -> frozenset[str]:
  38. return frozenset(f.path for f in psutil.Process().open_files())
  39. @pytest.fixture(autouse=True)
  40. def unclosed_files():
  41. fds = _open_files()
  42. yield
  43. assert _open_files() == fds
  44. @pytest.fixture(autouse=True)
  45. def validate_silo_mode():
  46. # NOTE! Hybrid cloud uses many mechanisms to simulate multiple different configurations of the application
  47. # during tests. It depends upon `override_settings` using the correct contextmanager behaviors and correct
  48. # thread handling in acceptance tests. If you hit one of these, it's possible either that cleanup logic has
  49. # a bug, or you may be using a contextmanager incorrectly. Let us know and we can help!
  50. expected = get_default_silo_mode_for_test_cases()
  51. message = (
  52. f"Possible test leak bug! SiloMode was not reset to {expected} between tests. "
  53. "Please read the comment for validate_silo_mode() in tests/conftest.py."
  54. )
  55. if SiloMode.get_current_mode() != expected:
  56. raise Exception(message)
  57. yield
  58. if SiloMode.get_current_mode() != expected:
  59. raise Exception(message)
  60. @pytest.fixture(autouse=True)
  61. def setup_simulate_on_commit(request):
  62. from sentry.testutils.hybrid_cloud import simulate_on_commit
  63. with simulate_on_commit(request):
  64. yield
  65. @pytest.fixture(autouse=True)
  66. def setup_enforce_monotonic_transactions(request):
  67. from sentry.testutils.hybrid_cloud import enforce_no_cross_transaction_interactions
  68. with enforce_no_cross_transaction_interactions():
  69. yield
  70. @pytest.fixture(autouse=True)
  71. def audit_hybrid_cloud_writes_and_deletes(request):
  72. """
  73. Ensure that write operations on hybrid cloud foreign keys are recorded
  74. alongside outboxes or use a context manager to indicate that the
  75. caller has considered outbox and didn't accidentally forget.
  76. Generally you can avoid assertion errors from these checks by:
  77. 1. Running deletion/write logic within an `outbox_context`.
  78. 2. Using Model.delete()/save methods that create outbox messages in the
  79. same transaction as a delete operation.
  80. Scenarios that are generally always unsafe are using
  81. `QuerySet.delete()`, `QuerySet.update()` or raw SQL to perform
  82. writes.
  83. The User.delete() method is a good example of how to safely
  84. delete records and generate outbox messages.
  85. """
  86. from sentry.testutils.silo import validate_protected_queries
  87. debug_cursor_state: MutableMapping[str, bool] = {}
  88. for conn in connections.all():
  89. debug_cursor_state[conn.alias] = conn.force_debug_cursor
  90. conn.queries_log.clear()
  91. conn.force_debug_cursor = True
  92. try:
  93. yield
  94. finally:
  95. for conn in connections.all():
  96. conn.force_debug_cursor = debug_cursor_state[conn.alias]
  97. validate_protected_queries(conn.queries)
  98. @pytest.fixture(autouse=True)
  99. def clear_caches():
  100. yield
  101. cache.clear()
  102. @pytest.fixture(autouse=True)
  103. def check_leaked_responses_mocks():
  104. yield
  105. leaked = responses.registered()
  106. if leaked:
  107. responses.reset()
  108. leaked_s = "".join(f"- {item}\n" for item in leaked)
  109. raise AssertionError(
  110. f"`responses` were leaked outside of the test context:\n{leaked_s}"
  111. f"(make sure to use `@responses.activate` or `with responses.mock:`)"
  112. )