66from  contextlib  import  ExitStack 
77import  sys 
88from  typing  import  Literal 
9- from  typing  import  TYPE_CHECKING 
109import  warnings 
1110
1211from  _pytest .config  import  apply_warning_filters 
1312from  _pytest .config  import  Config 
1413from  _pytest .config  import  parse_warning_filter 
1514from  _pytest .main  import  Session 
1615from  _pytest .nodes  import  Item 
16+ from  _pytest .reports  import  TestReport 
17+ from  _pytest .runner  import  CallInfo 
1718from  _pytest .stash  import  StashKey 
1819from  _pytest .terminal  import  TerminalReporter 
1920from  _pytest .tracemalloc  import  tracemalloc_message 
2021import  pytest 
2122
2223
23- if  TYPE_CHECKING :
24-     from  _pytest .reports  import  TestReport 
25-     from  _pytest .runner  import  CallInfo 
26- 
2724# StashKey for storing warning log on items 
2825warning_captured_log_key  =  StashKey [list [warnings .WarningMessage ]]()
2926
30- # Key name for storing warning flag in report.user_properties  
31- HAS_WARNINGS_KEY   =   "has_warnings" 
27+ # Track which nodeids have warnings (for pytest_report_teststatus)  
28+ _nodeids_with_warnings :  set [ str ]  =   set () 
3229
3330
3431@contextmanager  
@@ -59,7 +56,6 @@ def catch_warnings_for_item(
5956        apply_warning_filters (config_filters , cmdline_filters )
6057
6158        # apply filters from "filterwarnings" marks 
62-         nodeid  =  ""  if  item  is  None  else  item .nodeid 
6359        if  item  is  not   None :
6460            for  mark  in  item .iter_markers (name = "filterwarnings" ):
6561                for  arg  in  mark .args :
@@ -68,22 +64,9 @@ def catch_warnings_for_item(
6864            if  record  and  log  is  not   None :
6965                item .stash [warning_captured_log_key ] =  log 
7066
71-         try :
72-             yield 
73-         finally :
74-             if  record :
75-                 # mypy can't infer that record=True means log is not None; help it. 
76-                 assert  log  is  not   None 
77- 
78-                 for  warning_message  in  log :
79-                     ihook .pytest_warning_recorded .call_historic (
80-                         kwargs = dict (
81-                             warning_message = warning_message ,
82-                             nodeid = nodeid ,
83-                             when = when ,
84-                             location = None ,
85-                         )
86-                     )
67+         yield 
68+         # Note: pytest_warning_recorded hooks are now dispatched from 
69+         # pytest_runtest_makereport for better timing and integration 
8770
8871
8972def  warning_record_to_str (warning_message : warnings .WarningMessage ) ->  str :
@@ -109,15 +92,35 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
10992def  pytest_runtest_makereport (
11093    item : Item , call : CallInfo [None ]
11194) ->  Generator [None , TestReport , None ]:
112-     """Attach warning information to test reports for terminal coloring .""" 
95+     """Process warnings from stash and dispatch pytest_warning_recorded hooks .""" 
11396    outcome  =  yield 
11497    report : TestReport  =  outcome .get_result ()
11598
116-     # Only mark warnings during the call phase, not setup/teardown 
117-     if  report .passed  and  report .when  ==  "call" :
99+     if  report .when  ==  "call" :
118100        warning_log  =  item .stash .get (warning_captured_log_key , None )
119-         if  warning_log  is  not   None  and  len (warning_log ) >  0 :
120-             report .user_properties .append ((HAS_WARNINGS_KEY , True ))
101+         if  warning_log :
102+             _nodeids_with_warnings .add (item .nodeid )
103+             # Set attribute on report for xdist compatibility 
104+             report .has_warnings  =  True   # type: ignore[attr-defined] 
105+ 
106+             for  warning_message  in  warning_log :
107+                 item .ihook .pytest_warning_recorded .call_historic (
108+                     kwargs = dict (
109+                         warning_message = warning_message ,
110+                         nodeid = item .nodeid ,
111+                         when = "runtest" ,
112+                         location = None ,
113+                     )
114+                 )
115+ 
116+ 
117+ @pytest .hookimpl () 
118+ def  pytest_report_teststatus (report : TestReport , config : Config ):
119+     """Provide yellow markup for passed tests that have warnings.""" 
120+     if  report .passed  and  report .when  ==  "call" :
121+         if  hasattr (report , "has_warnings" ) and  report .has_warnings :
122+             # Return (category, shortletter, verbose_word) with yellow markup 
123+             return  "passed" , "." , ("PASSED" , {"yellow" : True })
121124
122125
123126@pytest .hookimpl (wrapper = True , tryfirst = True ) 
0 commit comments