Skip to content

Commit 06d7c37

Browse files
committed
Use pytest_runtest_logreport hook again
Fixes pytest-dev#76
1 parent d1a4b30 commit 06d7c37

File tree

1 file changed

+83
-64
lines changed
  • pytest_github_actions_annotate_failures

1 file changed

+83
-64
lines changed

pytest_github_actions_annotate_failures/plugin.py

+83-64
Original file line numberDiff line numberDiff line change
@@ -4,84 +4,85 @@
44
import os
55
import sys
66
from collections import OrderedDict
7-
from typing import TYPE_CHECKING
87

98
import pytest
109
from _pytest._code.code import ExceptionRepr
1110

12-
if TYPE_CHECKING:
13-
from _pytest.nodes import Item
14-
from _pytest.reports import CollectReport
11+
try:
12+
from xdist import is_xdist_worker
13+
14+
except ImportError:
15+
def is_xdist_worker(request_or_session):
16+
return hasattr(request_or_session.config, "workerinput")
1517

1618

1719
# Reference:
1820
# https://docs.pytest.org/en/latest/writing_plugins.html#hookwrapper-executing-around-other-hooks
1921
# https://docs.pytest.org/en/latest/writing_plugins.html#hook-function-ordering-call-example
20-
# https://docs.pytest.org/en/stable/reference.html#pytest.hookspec.pytest_runtest_makereport
22+
# https://docs.pytest.org/en/stable/reference.html#pytest.hookspec.pytest_runtest_logreport
2123
#
2224
# Inspired by:
2325
# https://github.com/pytest-dev/pytest/blob/master/src/_pytest/terminal.py
2426

2527

26-
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
27-
def pytest_runtest_makereport(item: Item, call): # noqa: ARG001
28-
# execute all other hooks to obtain the report object
29-
outcome = yield
30-
report: CollectReport = outcome.get_result()
31-
32-
# enable only in a workflow of GitHub Actions
33-
# ref: https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables
34-
if os.environ.get("GITHUB_ACTIONS") != "true":
35-
return
36-
37-
if report.when == "call" and report.failed:
38-
# collect information to be annotated
39-
filesystempath, lineno, _ = report.location
40-
41-
runpath = os.environ.get("PYTEST_RUN_PATH")
42-
if runpath:
43-
filesystempath = os.path.join(runpath, filesystempath)
44-
45-
# try to convert to absolute path in GitHub Actions
46-
workspace = os.environ.get("GITHUB_WORKSPACE")
47-
if workspace:
48-
full_path = os.path.abspath(filesystempath)
49-
try:
50-
rel_path = os.path.relpath(full_path, workspace)
51-
except ValueError:
52-
# os.path.relpath() will raise ValueError on Windows
53-
# when full_path and workspace have different mount points.
54-
# https://github.com/utgwkk/pytest-github-actions-annotate-failures/issues/20
55-
rel_path = filesystempath
56-
if not rel_path.startswith(".."):
57-
filesystempath = rel_path
58-
59-
if lineno is not None:
60-
# 0-index to 1-index
61-
lineno += 1
62-
63-
# get the name of the current failed test, with parametrize info
64-
longrepr = report.head_line or item.name
65-
66-
# get the error message and line number from the actual error
67-
if isinstance(report.longrepr, ExceptionRepr):
68-
if report.longrepr.reprcrash is not None:
69-
longrepr += "\n\n" + report.longrepr.reprcrash.message
70-
tb_entries = report.longrepr.reprtraceback.reprentries
71-
if len(tb_entries) > 1 and tb_entries[0].reprfileloc is not None:
72-
# Handle third-party exceptions
73-
lineno = tb_entries[0].reprfileloc.lineno
74-
elif report.longrepr.reprcrash is not None:
75-
lineno = report.longrepr.reprcrash.lineno
76-
elif isinstance(report.longrepr, tuple):
77-
_, lineno, message = report.longrepr
78-
longrepr += "\n\n" + message
79-
elif isinstance(report.longrepr, str):
80-
longrepr += "\n\n" + report.longrepr
81-
82-
print(
83-
_error_workflow_command(filesystempath, lineno, longrepr), file=sys.stderr
84-
)
28+
class Reporter:
29+
def pytest_runtest_logreport(self, report: pytest.TestReport):
30+
if report.when == "call" and report.failed:
31+
# collect information to be annotated
32+
filesystempath, lineno, domaininfo = report.location
33+
34+
runpath = os.environ.get("PYTEST_RUN_PATH")
35+
if runpath:
36+
filesystempath = os.path.join(runpath, filesystempath)
37+
38+
# try to convert to absolute path in GitHub Actions
39+
workspace = os.environ.get("GITHUB_WORKSPACE")
40+
if workspace:
41+
full_path = os.path.abspath(filesystempath)
42+
try:
43+
rel_path = os.path.relpath(full_path, workspace)
44+
except ValueError:
45+
# os.path.relpath() will raise ValueError on Windows
46+
# when full_path and workspace have different mount points.
47+
# https://github.com/utgwkk/pytest-github-actions-annotate-failures/issues/20
48+
rel_path = filesystempath
49+
if not rel_path.startswith(".."):
50+
filesystempath = rel_path
51+
52+
if lineno is not None:
53+
# 0-index to 1-index
54+
lineno += 1
55+
56+
# get the name of the current failed test, with parametrize info
57+
longrepr = getattr(report, 'head_line', None)
58+
59+
if not longrepr:
60+
# BaseReport.head_line currently does this
61+
longrepr = domaininfo
62+
63+
if not longrepr:
64+
# Should not happen
65+
longrepr = _remove_prefix(report.nodeid, f'{report.fspath}::')
66+
67+
# get the error message and line number from the actual error
68+
if isinstance(report.longrepr, ExceptionRepr):
69+
if report.longrepr.reprcrash is not None:
70+
longrepr += "\n\n" + report.longrepr.reprcrash.message
71+
tb_entries = report.longrepr.reprtraceback.reprentries
72+
if len(tb_entries) > 1 and tb_entries[0].reprfileloc is not None:
73+
# Handle third-party exceptions
74+
lineno = tb_entries[0].reprfileloc.lineno
75+
elif report.longrepr.reprcrash is not None:
76+
lineno = report.longrepr.reprcrash.lineno
77+
elif isinstance(report.longrepr, tuple):
78+
_, lineno, message = report.longrepr
79+
longrepr += "\n\n" + message
80+
elif isinstance(report.longrepr, str):
81+
longrepr += "\n\n" + report.longrepr
82+
83+
print(
84+
_error_workflow_command(filesystempath, lineno, longrepr), file=sys.stderr
85+
)
8586

8687

8788
def _error_workflow_command(filesystempath, lineno, longrepr):
@@ -102,3 +103,21 @@ def _error_workflow_command(filesystempath, lineno, longrepr):
102103

103104
def _escape(s):
104105
return s.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")
106+
107+
108+
def _remove_prefix(s, prefix):
109+
# Replace with built-in `.removeprefix()` when Python 3.8 support is dropped
110+
return s[len(prefix):] if s.startswith(prefix) else s
111+
112+
113+
def pytest_sessionstart(session: pytest.Session):
114+
# enable only in a workflow of GitHub Actions
115+
# ref: https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables
116+
if os.environ.get("GITHUB_ACTIONS") != "true":
117+
return
118+
119+
# print commands only from the main xdist process
120+
if is_xdist_worker(session):
121+
return
122+
123+
session.config.pluginmanager.register(Reporter())

0 commit comments

Comments
 (0)