conftest.py 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import os
  2. import pytest
  3. pytest_plugins = ["sentry.utils.pytest"]
  4. # XXX: The below code is vendored code from https://github.com/utgwkk/pytest-github-actions-annotate-failures
  5. # so that we can add support for pytest_rerunfailures
  6. # retried tests will no longer be annotated in GHA
  7. #
  8. # Reference:
  9. # https://docs.pytest.org/en/latest/writing_plugins.html#hookwrapper-executing-around-other-hooks
  10. # https://docs.pytest.org/en/latest/writing_plugins.html#hook-function-ordering-call-example
  11. # https://docs.pytest.org/en/stable/reference.html#pytest.hookspec.pytest_runtest_makereport
  12. #
  13. # Inspired by:
  14. # https://github.com/pytest-dev/pytest/blob/master/src/_pytest/terminal.py
  15. @pytest.hookimpl(tryfirst=True, hookwrapper=True)
  16. def pytest_runtest_makereport(item, call):
  17. # execute all other hooks to obtain the report object
  18. outcome = yield
  19. report = outcome.get_result()
  20. # enable only in a workflow of GitHub Actions
  21. # ref: https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables
  22. if os.environ.get("GITHUB_ACTIONS") != "true":
  23. return
  24. # If we have the pytest_rerunfailures plugin,
  25. # and there are still retries to be run,
  26. # then do not return the error
  27. if hasattr(item, "execution_count"):
  28. import pytest_rerunfailures
  29. if item.execution_count <= pytest_rerunfailures.get_reruns_count(item):
  30. return
  31. if report.when == "call" and report.failed:
  32. # collect information to be annotated
  33. filesystempath, lineno, _ = report.location
  34. # try to convert to absolute path in GitHub Actions
  35. workspace = os.environ.get("GITHUB_WORKSPACE")
  36. if workspace:
  37. full_path = os.path.abspath(filesystempath)
  38. try:
  39. rel_path = os.path.relpath(full_path, workspace)
  40. except ValueError:
  41. # os.path.relpath() will raise ValueError on Windows
  42. # when full_path and workspace have different mount points.
  43. # https://github.com/utgwkk/pytest-github-actions-annotate-failures/issues/20
  44. rel_path = filesystempath
  45. if not rel_path.startswith(".."):
  46. filesystempath = rel_path
  47. if lineno is not None:
  48. # 0-index to 1-index
  49. lineno += 1
  50. # get the name of the current failed test, with parametrize info
  51. longrepr = report.head_line or item.name
  52. # get the error message and line number from the actual error
  53. try:
  54. longrepr += "\n\n" + report.longrepr.reprcrash.message
  55. lineno = report.longrepr.reprcrash.lineno
  56. except AttributeError:
  57. pass
  58. print(_error_workflow_command(filesystempath, lineno, longrepr)) # noqa: S002
  59. def _error_workflow_command(filesystempath, lineno, longrepr):
  60. # Build collection of arguments. Ordering is strict for easy testing
  61. details_dict = {"file": filesystempath}
  62. if lineno is not None:
  63. details_dict["line"] = lineno
  64. details = ",".join(f"{k}={v}" for k, v in details_dict.items())
  65. if longrepr is None:
  66. return f"\n::error {details}"
  67. else:
  68. longrepr = _escape(longrepr)
  69. return f"\n::error {details}::{longrepr}"
  70. def _escape(s):
  71. return s.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")