66from typing import TYPE_CHECKING
77
88import pytest
9- from _pytest ._code .code import ExceptionRepr
9+ from _pytest ._code .code import ExceptionRepr , ReprEntry
1010from packaging import version
1111
1212if TYPE_CHECKING :
@@ -38,59 +38,65 @@ def pytest_runtest_makereport(item: Item, call): # noqa: ARG001
3838 return
3939
4040 if report .when == "call" and report .failed :
41- # collect information to be annotated
4241 filesystempath , lineno , _ = report .location
4342
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-
6243 if lineno is not None :
6344 # 0-index to 1-index
6445 lineno += 1
6546
66- # get the name of the current failed test, with parametrize info
6747 longrepr = report .head_line or item .name
6848
6949 # get the error message and line number from the actual error
7050 if isinstance (report .longrepr , ExceptionRepr ):
7151 if report .longrepr .reprcrash is not None :
7252 longrepr += "\n \n " + report .longrepr .reprcrash .message
7353 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 ]
7556 # 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+
7761 elif report .longrepr .reprcrash is not None :
7862 lineno = report .longrepr .reprcrash .lineno
7963 elif isinstance (report .longrepr , tuple ):
80- _ , lineno , message = report .longrepr
64+ filesystempath , lineno , message = report .longrepr
8165 longrepr += "\n \n " + message
8266 elif isinstance (report .longrepr , str ):
8367 longrepr += "\n \n " + report .longrepr
8468
8569 workflow_command = _build_workflow_command (
8670 "error" ,
87- filesystempath ,
71+ compute_path ( filesystempath ) ,
8872 lineno ,
8973 message = longrepr ,
9074 )
9175 print (workflow_command , file = sys .stderr )
9276
9377
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+
94100class _AnnotateWarnings :
95101 def pytest_warning_recorded (self , warning_message , when , nodeid , location ): # noqa: ARG002
96102 # enable only in a workflow of GitHub Actions
@@ -139,14 +145,14 @@ def pytest_configure(config):
139145
140146
141147def _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 ,
150156):
151157 """Build a command to annotate a workflow."""
152158 result = f"::{ command_name } "
@@ -168,5 +174,5 @@ def _build_workflow_command(
168174 return result
169175
170176
171- def _escape (s ) :
177+ def _escape (s : str ) -> str :
172178 return s .replace ("%" , "%25" ).replace ("\r " , "%0D" ).replace ("\n " , "%0A" )
0 commit comments