Skip to content

Commit 74b6e21

Browse files
authored
test(ci): Ignore annotation for pytest retries (#27870)
Vendoring the `pytest-github-actions-annotate-failures` package until pytest-dev/pytest-github-actions-annotate-failures#30 is closed. We will now ignore failures if there are retries pending. This will get rid of the GHA annotations that appear on test suites that pass.
1 parent d0544bc commit 74b6e21

File tree

2 files changed

+93
-3
lines changed

2 files changed

+93
-3
lines changed

.github/actions/setup-sentry/action.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,6 @@ runs:
111111
pip install -U -e ".[dev]"
112112
cd -
113113
114-
# pytest plugin used for better pytest failure annotations
115-
pip install pytest-github-actions-annotate-failures
116-
117114
- name: Start devservices
118115
shell: bash
119116
env:

conftest.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import sys
3+
from collections import OrderedDict
34

45
import pytest
56

@@ -27,3 +28,95 @@ def pytest_addoption(parser):
2728
def pytest_runtest_setup(item):
2829
if item.get_closest_marker("itunes") and not item.config.getoption("--itunes"):
2930
pytest.skip("Test requires --itunes")
31+
32+
33+
# XXX: The below code is vendored code from https://github.com/utgwkk/pytest-github-actions-annotate-failures
34+
# so that we can add support for pytest_rerunfailures
35+
# retried tests will no longer be annotated in GHA
36+
#
37+
# Reference:
38+
# https://docs.pytest.org/en/latest/writing_plugins.html#hookwrapper-executing-around-other-hooks
39+
# https://docs.pytest.org/en/latest/writing_plugins.html#hook-function-ordering-call-example
40+
# https://docs.pytest.org/en/stable/reference.html#pytest.hookspec.pytest_runtest_makereport
41+
#
42+
# Inspired by:
43+
# https://github.com/pytest-dev/pytest/blob/master/src/_pytest/terminal.py
44+
45+
46+
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
47+
def pytest_runtest_makereport(item, call):
48+
# execute all other hooks to obtain the report object
49+
outcome = yield
50+
report = outcome.get_result()
51+
52+
# enable only in a workflow of GitHub Actions
53+
# ref: https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables
54+
if os.environ.get("GITHUB_ACTIONS") != "true":
55+
return
56+
57+
try:
58+
# If we have the pytest_rerunfailures plugin,
59+
# and there are still retries to be run,
60+
# then do not return the error
61+
import pytest_rerunfailures
62+
63+
if item.execution_count <= pytest_rerunfailures.get_reruns_count(item):
64+
return
65+
except ImportError:
66+
pass
67+
68+
if report.when == "call" and report.failed:
69+
# collect information to be annotated
70+
filesystempath, lineno, _ = report.location
71+
72+
# try to convert to absolute path in GitHub Actions
73+
workspace = os.environ.get("GITHUB_WORKSPACE")
74+
if workspace:
75+
full_path = os.path.abspath(filesystempath)
76+
try:
77+
rel_path = os.path.relpath(full_path, workspace)
78+
except ValueError:
79+
# os.path.relpath() will raise ValueError on Windows
80+
# when full_path and workspace have different mount points.
81+
# https://github.com/utgwkk/pytest-github-actions-annotate-failures/issues/20
82+
rel_path = filesystempath
83+
if not rel_path.startswith(".."):
84+
filesystempath = rel_path
85+
86+
# 0-index to 1-index
87+
lineno += 1
88+
89+
# get the name of the current failed test, with parametrize info
90+
longrepr = report.head_line or item.name
91+
92+
# get the error message and line number from the actual error
93+
try:
94+
longrepr += "\n\n" + report.longrepr.reprcrash.message
95+
lineno = report.longrepr.reprcrash.lineno
96+
97+
except AttributeError:
98+
pass
99+
100+
print( # noqa: B314
101+
_error_workflow_command(filesystempath, lineno, longrepr), file=sys.stderr
102+
)
103+
104+
105+
def _error_workflow_command(filesystempath, lineno, longrepr):
106+
# Build collection of arguments. Ordering is strict for easy testing
107+
details_dict = OrderedDict()
108+
details_dict["file"] = filesystempath
109+
if lineno is not None:
110+
details_dict["line"] = lineno
111+
112+
details = ",".join(f"{k}={v}" for k, v in details_dict.items())
113+
114+
if longrepr is None:
115+
return f"\n::error {details}"
116+
else:
117+
longrepr = _escape(longrepr)
118+
return f"\n::error {details}::{longrepr}"
119+
120+
121+
def _escape(s):
122+
return s.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")

0 commit comments

Comments
 (0)