6
6
from typing import TYPE_CHECKING
7
7
8
8
import pytest
9
- from _pytest ._code .code import ExceptionRepr
9
+ from _pytest ._code .code import ExceptionRepr , ReprEntry
10
10
from packaging import version
11
11
12
12
if TYPE_CHECKING :
@@ -38,59 +38,65 @@ def pytest_runtest_makereport(item: Item, call): # noqa: ARG001
38
38
return
39
39
40
40
if report .when == "call" and report .failed :
41
- # collect information to be annotated
42
41
filesystempath , lineno , _ = report .location
43
42
44
- runpath = os .environ .get ("PYTEST_RUN_PATH" )
45
- if runpath :
46
- filesystempath = os .path .join (runpath , filesystempath )
47
-
48
- # try to convert to absolute path in GitHub Actions
49
- workspace = os .environ .get ("GITHUB_WORKSPACE" )
50
- if workspace :
51
- full_path = os .path .abspath (filesystempath )
52
- try :
53
- rel_path = os .path .relpath (full_path , workspace )
54
- except ValueError :
55
- # os.path.relpath() will raise ValueError on Windows
56
- # when full_path and workspace have different mount points.
57
- # https://github.com/utgwkk/pytest-github-actions-annotate-failures/issues/20
58
- rel_path = filesystempath
59
- if not rel_path .startswith (".." ):
60
- filesystempath = rel_path
61
-
62
43
if lineno is not None :
63
44
# 0-index to 1-index
64
45
lineno += 1
65
46
66
- # get the name of the current failed test, with parametrize info
67
47
longrepr = report .head_line or item .name
68
48
69
49
# get the error message and line number from the actual error
70
50
if isinstance (report .longrepr , ExceptionRepr ):
71
51
if report .longrepr .reprcrash is not None :
72
52
longrepr += "\n \n " + report .longrepr .reprcrash .message
73
53
tb_entries = report .longrepr .reprtraceback .reprentries
74
- if len (tb_entries ) > 1 and tb_entries [0 ].reprfileloc is not None :
54
+ if tb_entries :
55
+ entry = tb_entries [0 ]
75
56
# Handle third-party exceptions
76
- lineno = tb_entries [0 ].reprfileloc .lineno
57
+ if isinstance (entry , ReprEntry ) and entry .reprfileloc is not None :
58
+ lineno = entry .reprfileloc .lineno
59
+ filesystempath = entry .reprfileloc .path
60
+
77
61
elif report .longrepr .reprcrash is not None :
78
62
lineno = report .longrepr .reprcrash .lineno
79
63
elif isinstance (report .longrepr , tuple ):
80
- _ , lineno , message = report .longrepr
64
+ filesystempath , lineno , message = report .longrepr
81
65
longrepr += "\n \n " + message
82
66
elif isinstance (report .longrepr , str ):
83
67
longrepr += "\n \n " + report .longrepr
84
68
85
69
workflow_command = _build_workflow_command (
86
70
"error" ,
87
- filesystempath ,
71
+ compute_path ( filesystempath ) ,
88
72
lineno ,
89
73
message = longrepr ,
90
74
)
91
75
print (workflow_command , file = sys .stderr )
92
76
93
77
78
+ def compute_path (filesystempath : str ) -> str :
79
+ """Extract and process location information from the report."""
80
+ runpath = os .environ .get ("PYTEST_RUN_PATH" )
81
+ if runpath :
82
+ filesystempath = os .path .join (runpath , filesystempath )
83
+
84
+ # try to convert to absolute path in GitHub Actions
85
+ workspace = os .environ .get ("GITHUB_WORKSPACE" )
86
+ if workspace :
87
+ full_path = os .path .abspath (filesystempath )
88
+ try :
89
+ rel_path = os .path .relpath (full_path , workspace )
90
+ except ValueError :
91
+ # os.path.relpath() will raise ValueError on Windows
92
+ # when full_path and workspace have different mount points.
93
+ rel_path = filesystempath
94
+ if not rel_path .startswith (".." ):
95
+ filesystempath = rel_path
96
+
97
+ return filesystempath
98
+
99
+
94
100
class _AnnotateWarnings :
95
101
def pytest_warning_recorded (self , warning_message , when , nodeid , location ): # noqa: ARG002
96
102
# enable only in a workflow of GitHub Actions
@@ -139,14 +145,14 @@ def pytest_configure(config):
139
145
140
146
141
147
def _build_workflow_command (
142
- command_name ,
143
- file ,
144
- line ,
145
- end_line = None ,
146
- column = None ,
147
- end_column = None ,
148
- title = None ,
149
- message = None ,
148
+ command_name : str ,
149
+ file : str ,
150
+ line : int ,
151
+ end_line : int | None = None ,
152
+ column : int | None = None ,
153
+ end_column : int | None = None ,
154
+ title : str | None = None ,
155
+ message : str | None = None ,
150
156
):
151
157
"""Build a command to annotate a workflow."""
152
158
result = f"::{ command_name } "
@@ -168,5 +174,5 @@ def _build_workflow_command(
168
174
return result
169
175
170
176
171
- def _escape (s ) :
177
+ def _escape (s : str ) -> str :
172
178
return s .replace ("%" , "%25" ).replace ("\r " , "%0D" ).replace ("\n " , "%0A" )
0 commit comments