|
@@ -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")
|