import os import pytest pytest_plugins = ["sentry.utils.pytest"] # 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 # If we have the pytest_rerunfailures plugin, # and there are still retries to be run, # then do not return the error if hasattr(item, "execution_count"): import pytest_rerunfailures if item.execution_count <= pytest_rerunfailures.get_reruns_count(item): return 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 if lineno is not None: # 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(_error_workflow_command(filesystempath, lineno, longrepr)) # noqa: S002 def _error_workflow_command(filesystempath, lineno, longrepr): # Build collection of arguments. Ordering is strict for easy testing 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")