conftest.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import contextlib
  2. import os
  3. import pytest
  4. from sentry.silo import SiloMode
  5. pytest_plugins = ["sentry.utils.pytest"]
  6. # XXX: The below code is vendored code from https://github.com/utgwkk/pytest-github-actions-annotate-failures
  7. # so that we can add support for pytest_rerunfailures
  8. # retried tests will no longer be annotated in GHA
  9. #
  10. # Reference:
  11. # https://docs.pytest.org/en/latest/writing_plugins.html#hookwrapper-executing-around-other-hooks
  12. # https://docs.pytest.org/en/latest/writing_plugins.html#hook-function-ordering-call-example
  13. # https://docs.pytest.org/en/stable/reference.html#pytest.hookspec.pytest_runtest_makereport
  14. #
  15. # Inspired by:
  16. # https://github.com/pytest-dev/pytest/blob/master/src/_pytest/terminal.py
  17. @pytest.hookimpl(tryfirst=True, hookwrapper=True)
  18. def pytest_runtest_makereport(item, call):
  19. # execute all other hooks to obtain the report object
  20. outcome = yield
  21. report = outcome.get_result()
  22. # enable only in a workflow of GitHub Actions
  23. # ref: https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables
  24. if os.environ.get("GITHUB_ACTIONS") != "true":
  25. return
  26. # If we have the pytest_rerunfailures plugin,
  27. # and there are still retries to be run,
  28. # then do not return the error
  29. if hasattr(item, "execution_count"):
  30. import pytest_rerunfailures
  31. if item.execution_count <= pytest_rerunfailures.get_reruns_count(item):
  32. return
  33. if report.when == "call" and report.failed:
  34. # collect information to be annotated
  35. filesystempath, lineno, _ = report.location
  36. # try to convert to absolute path in GitHub Actions
  37. workspace = os.environ.get("GITHUB_WORKSPACE")
  38. if workspace:
  39. full_path = os.path.abspath(filesystempath)
  40. try:
  41. rel_path = os.path.relpath(full_path, workspace)
  42. except ValueError:
  43. # os.path.relpath() will raise ValueError on Windows
  44. # when full_path and workspace have different mount points.
  45. # https://github.com/utgwkk/pytest-github-actions-annotate-failures/issues/20
  46. rel_path = filesystempath
  47. if not rel_path.startswith(".."):
  48. filesystempath = rel_path
  49. if lineno is not None:
  50. # 0-index to 1-index
  51. lineno += 1
  52. # get the name of the current failed test, with parametrize info
  53. longrepr = report.head_line or item.name
  54. # get the error message and line number from the actual error
  55. try:
  56. longrepr += "\n\n" + report.longrepr.reprcrash.message
  57. lineno = report.longrepr.reprcrash.lineno
  58. except AttributeError:
  59. pass
  60. print(_error_workflow_command(filesystempath, lineno, longrepr)) # noqa: S002
  61. def _error_workflow_command(filesystempath, lineno, longrepr):
  62. # Build collection of arguments. Ordering is strict for easy testing
  63. details_dict = {"file": filesystempath}
  64. if lineno is not None:
  65. details_dict["line"] = lineno
  66. details = ",".join(f"{k}={v}" for k, v in details_dict.items())
  67. if longrepr is None:
  68. return f"\n::error {details}"
  69. else:
  70. longrepr = _escape(longrepr)
  71. return f"\n::error {details}::{longrepr}"
  72. def _escape(s):
  73. return s.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")
  74. _MODEL_MANIFEST_FILE_PATH = os.getenv("SENTRY_MODEL_MANIFEST_FILE_PATH")
  75. _model_manifest = None
  76. @pytest.fixture(scope="session", autouse=True)
  77. def create_model_manifest_file():
  78. """Audit which models are touched by each test case and write it to file."""
  79. # We have to construct the ModelManifest lazily, because importing
  80. # sentry.testutils.modelmanifest too early causes a dependency cycle.
  81. from sentry.testutils.modelmanifest import ModelManifest
  82. if _MODEL_MANIFEST_FILE_PATH:
  83. global _model_manifest
  84. _model_manifest = ModelManifest.open(_MODEL_MANIFEST_FILE_PATH)
  85. with _model_manifest.write():
  86. yield
  87. else:
  88. yield
  89. @pytest.fixture(scope="class", autouse=True)
  90. def register_class_in_model_manifest(request: pytest.FixtureRequest):
  91. if _model_manifest:
  92. with _model_manifest.register(request.node.nodeid):
  93. yield
  94. else:
  95. yield
  96. @pytest.fixture(autouse=True)
  97. def setup_default_hybrid_cloud_stubs():
  98. from sentry.region_to_control.producer import (
  99. MockRegionToControlMessageService,
  100. region_to_control_message_service,
  101. )
  102. from sentry.silo import SiloMode
  103. from sentry.testutils.hybrid_cloud import service_stubbed
  104. stubs = [
  105. service_stubbed(
  106. region_to_control_message_service, MockRegionToControlMessageService(), SiloMode.REGION
  107. ),
  108. ]
  109. with contextlib.ExitStack() as stack:
  110. for stub in stubs:
  111. stack.enter_context(stub)
  112. yield
  113. @pytest.fixture(autouse=True)
  114. def validate_silo_mode():
  115. # NOTE! Hybrid cloud uses many mechanisms to simulate multiple different configurations of the application
  116. # during tests. It depends upon `override_settings` using the correct contextmanager behaviors and correct
  117. # thread handling in acceptance tests. If you hit one of these, it's possible either that cleanup logic has
  118. # a bug, or you may be using a contextmanager incorrectly. Let us know and we can help!
  119. if SiloMode.get_current_mode() != SiloMode.MONOLITH:
  120. raise Exception(
  121. "Possible test leak bug! SiloMode was not reset to Monolith between tests. Please read the comment for validate_silo_mode() in tests/conftest.py."
  122. )
  123. yield
  124. if SiloMode.get_current_mode() != SiloMode.MONOLITH:
  125. raise Exception(
  126. "Possible test leak bug! SiloMode was not reset to Monolith between tests. Please read the comment for validate_silo_mode() in tests/conftest.py."
  127. )