conftest.py 4.0 KB

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