@@ -432,7 +432,7 @@ def hasopt(self, char: str) -> bool:
432432 char = {"xfailed" : "x" , "skipped" : "s" }.get (char , char )
433433 return char in self .reportchars
434434
435- def write_fspath_result (self , nodeid : str , res , ** markup : bool ) -> None :
435+ def write_fspath_result (self , nodeid : str , res : str , ** markup : bool ) -> None :
436436 fspath = self .config .rootpath / nodeid .split ("::" )[0 ]
437437 if self .currentfspath is None or fspath != self .currentfspath :
438438 if self .currentfspath is not None and self ._show_progress_info :
@@ -565,10 +565,11 @@ def pytest_deselected(self, items: Sequence[Item]) -> None:
565565 def pytest_runtest_logstart (
566566 self , nodeid : str , location : Tuple [str , Optional [int ], str ]
567567 ) -> None :
568+ fspath , lineno , domain = location
568569 # Ensure that the path is printed before the
569570 # 1st test of a module starts running.
570571 if self .showlongtestinfo :
571- line = self ._locationline (nodeid , * location )
572+ line = self ._locationline (nodeid , fspath , lineno , domain )
572573 self .write_ensure_prefix (line , "" )
573574 self .flush ()
574575 elif self .showfspath :
@@ -591,7 +592,6 @@ def pytest_runtest_logreport(self, report: TestReport) -> None:
591592 if not letter and not word :
592593 # Probably passed setup/teardown.
593594 return
594- running_xdist = hasattr (rep , "node" )
595595 if markup is None :
596596 was_xfail = hasattr (report , "wasxfail" )
597597 if rep .passed and not was_xfail :
@@ -604,11 +604,20 @@ def pytest_runtest_logreport(self, report: TestReport) -> None:
604604 markup = {"yellow" : True }
605605 else :
606606 markup = {}
607+ self ._progress_nodeids_reported .add (rep .nodeid )
607608 if self .config .get_verbosity (Config .VERBOSITY_TEST_CASES ) <= 0 :
608609 self ._tw .write (letter , ** markup )
610+ # When running in xdist, the logreport and logfinish of multiple
611+ # items are interspersed, e.g. `logreport`, `logreport`,
612+ # `logfinish`, `logfinish`. To avoid the "past edge" calculation
613+ # from getting confused and overflowing (#7166), do the past edge
614+ # printing here and not in logfinish, except for the 100% which
615+ # should only be printed after all teardowns are finished.
616+ if self ._show_progress_info and not self ._is_last_item :
617+ self ._write_progress_information_if_past_edge ()
609618 else :
610- self ._progress_nodeids_reported .add (rep .nodeid )
611619 line = self ._locationline (rep .nodeid , * rep .location )
620+ running_xdist = hasattr (rep , "node" )
612621 if not running_xdist :
613622 self .write_ensure_prefix (line , word , ** markup )
614623 if rep .skipped or hasattr (report , "wasxfail" ):
@@ -648,39 +657,29 @@ def _is_last_item(self) -> bool:
648657 assert self ._session is not None
649658 return len (self ._progress_nodeids_reported ) == self ._session .testscollected
650659
651- def pytest_runtest_logfinish (self , nodeid : str ) -> None :
652- assert self ._session
660+ @hookimpl (wrapper = True )
661+ def pytest_runtestloop (self ) -> Generator [None , object , object ]:
662+ result = yield
663+
664+ # Write the final/100% progress -- deferred until the loop is complete.
653665 if (
654666 self .config .get_verbosity (Config .VERBOSITY_TEST_CASES ) <= 0
655667 and self ._show_progress_info
668+ and self ._progress_nodeids_reported
656669 ):
657- if self ._show_progress_info == "count" :
658- num_tests = self ._session .testscollected
659- progress_length = len (f" [{ num_tests } /{ num_tests } ]" )
660- else :
661- progress_length = len (" [100%]" )
662-
663- self ._progress_nodeids_reported .add (nodeid )
670+ self ._write_progress_information_filling_space ()
664671
665- if self ._is_last_item :
666- self ._write_progress_information_filling_space ()
667- else :
668- main_color , _ = self ._get_main_color ()
669- w = self ._width_of_current_line
670- past_edge = w + progress_length + 1 >= self ._screen_width
671- if past_edge :
672- msg = self ._get_progress_information_message ()
673- self ._tw .write (msg + "\n " , ** {main_color : True })
672+ return result
674673
675674 def _get_progress_information_message (self ) -> str :
676675 assert self ._session
677676 collected = self ._session .testscollected
678677 if self ._show_progress_info == "count" :
679678 if collected :
680- progress = self ._progress_nodeids_reported
679+ progress = len ( self ._progress_nodeids_reported )
681680 counter_format = f"{{:{ len (str (collected ))} d}}"
682681 format_string = f" [{ counter_format } /{{}}]"
683- return format_string .format (len ( progress ) , collected )
682+ return format_string .format (progress , collected )
684683 return f" [ { collected } / { collected } ]"
685684 else :
686685 if collected :
@@ -689,6 +688,20 @@ def _get_progress_information_message(self) -> str:
689688 )
690689 return " [100%]"
691690
691+ def _write_progress_information_if_past_edge (self ) -> None :
692+ w = self ._width_of_current_line
693+ if self ._show_progress_info == "count" :
694+ assert self ._session
695+ num_tests = self ._session .testscollected
696+ progress_length = len (f" [{ num_tests } /{ num_tests } ]" )
697+ else :
698+ progress_length = len (" [100%]" )
699+ past_edge = w + progress_length + 1 >= self ._screen_width
700+ if past_edge :
701+ main_color , _ = self ._get_main_color ()
702+ msg = self ._get_progress_information_message ()
703+ self ._tw .write (msg + "\n " , ** {main_color : True })
704+
692705 def _write_progress_information_filling_space (self ) -> None :
693706 color , _ = self ._get_main_color ()
694707 msg = self ._get_progress_information_message ()
@@ -937,7 +950,7 @@ def mkrel(nodeid: str) -> str:
937950 line += "[" .join (values )
938951 return line
939952
940- # collect_fspath comes from testid which has a "/"-normalized path.
953+ # fspath comes from testid which has a "/"-normalized path.
941954 if fspath :
942955 res = mkrel (nodeid )
943956 if self .verbosity >= 2 and nodeid .split ("::" )[0 ] != fspath .replace (
0 commit comments