Browse Source

test(ci): Ignore annotation for pytest retries (#27870)

Vendoring the `pytest-github-actions-annotate-failures` package until https://github.com/utgwkk/pytest-github-actions-annotate-failures/issues/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.
Billy Vong 3 years ago
parent
commit
74b6e21986
2 changed files with 93 additions and 3 deletions
  1. 0 3
      .github/actions/setup-sentry/action.yml
  2. 93 0
      conftest.py

+ 0 - 3
.github/actions/setup-sentry/action.yml

@@ -111,9 +111,6 @@ runs:
         pip install -U -e ".[dev]"
         cd -
 
-        # pytest plugin used for better pytest failure annotations
-        pip install pytest-github-actions-annotate-failures
-
     - name: Start devservices
       shell: bash
       env:

+ 93 - 0
conftest.py

@@ -1,5 +1,6 @@
 import os
 import sys
+from collections import OrderedDict
 
 import pytest
 
@@ -27,3 +28,95 @@ def pytest_addoption(parser):
 def pytest_runtest_setup(item):
     if item.get_closest_marker("itunes") and not item.config.getoption("--itunes"):
         pytest.skip("Test requires --itunes")
+
+
+# XXX: The below code is vendored code from https://github.com/utgwkk/pytest-github-actions-annotate-failures
+# so that we can add support for pytest_rerunfailures
+# retried tests will no longer be annotated in GHA
+#
+# Reference:
+# https://docs.pytest.org/en/latest/writing_plugins.html#hookwrapper-executing-around-other-hooks
+# https://docs.pytest.org/en/latest/writing_plugins.html#hook-function-ordering-call-example
+# https://docs.pytest.org/en/stable/reference.html#pytest.hookspec.pytest_runtest_makereport
+#
+# Inspired by:
+# https://github.com/pytest-dev/pytest/blob/master/src/_pytest/terminal.py
+
+
+@pytest.hookimpl(tryfirst=True, hookwrapper=True)
+def pytest_runtest_makereport(item, call):
+    # execute all other hooks to obtain the report object
+    outcome = yield
+    report = outcome.get_result()
+
+    # enable only in a workflow of GitHub Actions
+    # ref: https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables
+    if os.environ.get("GITHUB_ACTIONS") != "true":
+        return
+
+    try:
+        # If we have the pytest_rerunfailures plugin,
+        # and there are still retries to be run,
+        # then do not return the error
+        import pytest_rerunfailures
+
+        if item.execution_count <= pytest_rerunfailures.get_reruns_count(item):
+            return
+    except ImportError:
+        pass
+
+    if report.when == "call" and report.failed:
+        # collect information to be annotated
+        filesystempath, lineno, _ = report.location
+
+        # try to convert to absolute path in GitHub Actions
+        workspace = os.environ.get("GITHUB_WORKSPACE")
+        if workspace:
+            full_path = os.path.abspath(filesystempath)
+            try:
+                rel_path = os.path.relpath(full_path, workspace)
+            except ValueError:
+                # os.path.relpath() will raise ValueError on Windows
+                # when full_path and workspace have different mount points.
+                # https://github.com/utgwkk/pytest-github-actions-annotate-failures/issues/20
+                rel_path = filesystempath
+            if not rel_path.startswith(".."):
+                filesystempath = rel_path
+
+        # 0-index to 1-index
+        lineno += 1
+
+        # get the name of the current failed test, with parametrize info
+        longrepr = report.head_line or item.name
+
+        # get the error message and line number from the actual error
+        try:
+            longrepr += "\n\n" + report.longrepr.reprcrash.message
+            lineno = report.longrepr.reprcrash.lineno
+
+        except AttributeError:
+            pass
+
+        print(  # noqa: B314
+            _error_workflow_command(filesystempath, lineno, longrepr), file=sys.stderr
+        )
+
+
+def _error_workflow_command(filesystempath, lineno, longrepr):
+    # Build collection of arguments. Ordering is strict for easy testing
+    details_dict = OrderedDict()
+    details_dict["file"] = filesystempath
+    if lineno is not None:
+        details_dict["line"] = lineno
+
+    details = ",".join(f"{k}={v}" for k, v in details_dict.items())
+
+    if longrepr is None:
+        return f"\n::error {details}"
+    else:
+        longrepr = _escape(longrepr)
+        return f"\n::error {details}::{longrepr}"
+
+
+def _escape(s):
+    return s.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")