1010import threading
1111import time
1212import traceback
13+ from _colorize import decolor # type: ignore[import-not-found]
1314from typing import Any , Literal , TextIO
1415
1516from test import support
@@ -519,6 +520,11 @@ def __init__(self, num_workers: int, runtests: RunTests,
519520 else :
520521 self .worker_timeout = None
521522 self .workers : list [WorkerThread ] = []
523+ # On GitHub Actions, fold the first run's log into groups: ranges of
524+ # passing tests, and one group per failed test. Re-runs aren't folded.
525+ self .github_groups = (not runtests .rerun
526+ and bool (os .environ .get ("GITHUB_STEP_SUMMARY" )))
527+ self .in_github_group = False
522528
523529 jobs = self .runtests .get_jobs ()
524530 if jobs is not None :
@@ -611,7 +617,16 @@ def _process_result(self, item: QueueOutput) -> TestResult:
611617 self .test_index += 1
612618 mp_result = item [1 ]
613619 result = mp_result .result
620+ failed = result .is_failed (self .runtests .fail_env_changed )
621+ if self .github_groups and failed :
622+ # Keep the failure annotation out of collapsed groups
623+ self .github_group (None )
614624 self .results .accumulate_result (result , self .runtests )
625+ if self .github_groups :
626+ if failed :
627+ self .github_group (decolor (str (result )))
628+ elif not self .in_github_group :
629+ self .github_group (f"Tests from #{ self .test_index } " )
615630 self .display_result (mp_result )
616631
617632 # Display worker stdout
@@ -624,9 +639,19 @@ def _process_result(self, item: QueueOutput) -> TestResult:
624639 stdout = mp_result .worker_stdout
625640 if stdout :
626641 print (stdout , flush = True )
642+ if self .github_groups and failed :
643+ self .github_group (None )
627644
628645 return result
629646
647+ def github_group (self , title : str | None ) -> None :
648+ """End the current GitHub Actions log group, start a new one if title."""
649+ if self .in_github_group :
650+ print ("::endgroup::" , flush = True )
651+ self .in_github_group = title is not None
652+ if title is not None :
653+ print (f"::group::{ title } " , flush = True )
654+
630655 def get_mem_usage (self ):
631656 usage = 0
632657 main_mem = get_process_memory_usage (os .getpid ())
@@ -659,6 +684,7 @@ def run(self) -> None:
659684 print ()
660685 self .results .interrupted = True
661686 finally :
687+ self .github_group (None )
662688 if self .timeout is not None :
663689 faulthandler .cancel_dump_traceback_later ()
664690
0 commit comments