@@ -247,6 +247,8 @@ def __init__(self, config: Config, file=None) -> None:
247
247
self ._showfspath = None
248
248
249
249
self .stats = {} # type: Dict[str, List[Any]]
250
+ self ._main_color = None # type: Optional[str]
251
+ self ._known_types = None # type: Optional[List]
250
252
self .startdir = config .invocation_dir
251
253
if file is None :
252
254
file = sys .stdout
@@ -365,6 +367,12 @@ def section(self, title, sep="=", **kw):
365
367
def line (self , msg , ** kw ):
366
368
self ._tw .line (msg , ** kw )
367
369
370
+ def _add_stats (self , category : str , items : List ) -> None :
371
+ set_main_color = category not in self .stats
372
+ self .stats .setdefault (category , []).extend (items [:])
373
+ if set_main_color :
374
+ self ._set_main_color ()
375
+
368
376
def pytest_internalerror (self , excrepr ):
369
377
for line in str (excrepr ).split ("\n " ):
370
378
self .write_line ("INTERNALERROR> " + line )
@@ -374,15 +382,14 @@ def pytest_warning_captured(self, warning_message, item):
374
382
# from _pytest.nodes import get_fslocation_from_item
375
383
from _pytest .warnings import warning_record_to_str
376
384
377
- warnings = self .stats .setdefault ("warnings" , [])
378
385
fslocation = warning_message .filename , warning_message .lineno
379
386
message = warning_record_to_str (warning_message )
380
387
381
388
nodeid = item .nodeid if item is not None else ""
382
389
warning_report = WarningReport (
383
390
fslocation = fslocation , message = message , nodeid = nodeid
384
391
)
385
- warnings . append ( warning_report )
392
+ self . _add_stats ( "warnings" , [ warning_report ] )
386
393
387
394
def pytest_plugin_registered (self , plugin ):
388
395
if self .config .option .traceconfig :
@@ -393,7 +400,7 @@ def pytest_plugin_registered(self, plugin):
393
400
self .write_line (msg )
394
401
395
402
def pytest_deselected (self , items ):
396
- self .stats . setdefault ("deselected" , []). extend ( items )
403
+ self ._add_stats ("deselected" , items )
397
404
398
405
def pytest_runtest_logstart (self , nodeid , location ):
399
406
# ensure that the path is printed before the
@@ -414,7 +421,7 @@ def pytest_runtest_logreport(self, report: TestReport) -> None:
414
421
word , markup = word
415
422
else :
416
423
markup = None
417
- self .stats . setdefault (category , []). append ( rep )
424
+ self ._add_stats (category , [rep ] )
418
425
if not letter and not word :
419
426
# probably passed setup/teardown
420
427
return
@@ -456,6 +463,10 @@ def pytest_runtest_logreport(self, report: TestReport) -> None:
456
463
self ._tw .write (" " + line )
457
464
self .currentfspath = - 2
458
465
466
+ @property
467
+ def _is_last_item (self ):
468
+ return len (self ._progress_nodeids_reported ) == self ._session .testscollected
469
+
459
470
def pytest_runtest_logfinish (self , nodeid ):
460
471
assert self ._session
461
472
if self .verbosity <= 0 and self ._show_progress_info :
@@ -465,15 +476,12 @@ def pytest_runtest_logfinish(self, nodeid):
465
476
else :
466
477
progress_length = len (" [100%]" )
467
478
468
- main_color , _ = _get_main_color (self .stats )
469
-
470
479
self ._progress_nodeids_reported .add (nodeid )
471
- is_last_item = (
472
- len (self ._progress_nodeids_reported ) == self ._session .testscollected
473
- )
474
- if is_last_item :
475
- self ._write_progress_information_filling_space (color = main_color )
480
+
481
+ if self ._is_last_item :
482
+ self ._write_progress_information_filling_space ()
476
483
else :
484
+ main_color , _ = self ._get_main_color ()
477
485
w = self ._width_of_current_line
478
486
past_edge = w + progress_length + 1 >= self ._screen_width
479
487
if past_edge :
@@ -497,9 +505,8 @@ def _get_progress_information_message(self) -> str:
497
505
)
498
506
return " [100%]"
499
507
500
- def _write_progress_information_filling_space (self , color = None ):
501
- if not color :
502
- color , _ = _get_main_color (self .stats )
508
+ def _write_progress_information_filling_space (self ):
509
+ color , _ = self ._get_main_color ()
503
510
msg = self ._get_progress_information_message ()
504
511
w = self ._width_of_current_line
505
512
fill = self ._tw .fullwidth - w - 1
@@ -524,9 +531,9 @@ def pytest_collection(self):
524
531
525
532
def pytest_collectreport (self , report : CollectReport ) -> None :
526
533
if report .failed :
527
- self .stats . setdefault ("error" , []). append ( report )
534
+ self ._add_stats ("error" , [report ] )
528
535
elif report .skipped :
529
- self .stats . setdefault ("skipped" , []). append ( report )
536
+ self ._add_stats ("skipped" , [report ] )
530
537
items = [x for x in report .result if isinstance (x , pytest .Item )]
531
538
self ._numcollected += len (items )
532
539
if self .isatty :
@@ -909,7 +916,7 @@ def summary_stats(self):
909
916
return
910
917
911
918
session_duration = time .time () - self ._sessionstarttime
912
- (parts , main_color ) = build_summary_stats_line ( self .stats )
919
+ (parts , main_color ) = self .build_summary_stats_line ( )
913
920
line_parts = []
914
921
915
922
display_sep = self .verbosity >= 0
@@ -1012,6 +1019,56 @@ def show_skipped(lines: List[str]) -> None:
1012
1019
for line in lines :
1013
1020
self .write_line (line )
1014
1021
1022
+ def _get_main_color (self ) -> Tuple [str , List [str ]]:
1023
+ if self ._main_color is None or self ._known_types is None or self ._is_last_item :
1024
+ self ._set_main_color ()
1025
+ assert self ._main_color
1026
+ assert self ._known_types
1027
+ return self ._main_color , self ._known_types
1028
+
1029
+ def _set_main_color (self ) -> Tuple [str , List [str ]]:
1030
+ stats = self .stats
1031
+ known_types = (
1032
+ "failed passed skipped deselected xfailed xpassed warnings error" .split ()
1033
+ )
1034
+ unknown_type_seen = False
1035
+ for found_type in stats .keys ():
1036
+ if found_type not in known_types :
1037
+ if found_type : # setup/teardown reports have an empty key, ignore them
1038
+ known_types .append (found_type )
1039
+ unknown_type_seen = True
1040
+
1041
+ # main color
1042
+ if "failed" in stats or "error" in stats :
1043
+ main_color = "red"
1044
+ elif "warnings" in stats or "xpassed" in stats or unknown_type_seen :
1045
+ main_color = "yellow"
1046
+ elif "passed" in stats or not self ._is_last_item :
1047
+ main_color = "green"
1048
+ else :
1049
+ main_color = "yellow"
1050
+ self ._main_color , self ._known_types = main_color , known_types
1051
+ return main_color , known_types
1052
+
1053
+ def build_summary_stats_line (self ) -> Tuple [List [Tuple [str , Dict [str , bool ]]], str ]:
1054
+ main_color , known_types = self ._get_main_color ()
1055
+
1056
+ parts = []
1057
+ for key in known_types :
1058
+ reports = self .stats .get (key , None )
1059
+ if reports :
1060
+ count = sum (
1061
+ 1 for rep in reports if getattr (rep , "count_towards_summary" , True )
1062
+ )
1063
+ color = _color_for_type .get (key , _color_for_type_default )
1064
+ markup = {color : True , "bold" : color == main_color }
1065
+ parts .append (("%d %s" % _make_plural (count , key ), markup ))
1066
+
1067
+ if not parts :
1068
+ parts = [("no tests ran" , {_color_for_type_default : True })]
1069
+
1070
+ return parts , main_color
1071
+
1015
1072
1016
1073
def _get_pos (config , rep ):
1017
1074
nodeid = config .cwd_relative_nodeid (rep .nodeid )
@@ -1100,50 +1157,6 @@ def _make_plural(count, noun):
1100
1157
return count , noun + "s" if count != 1 else noun
1101
1158
1102
1159
1103
- def _get_main_color (stats ) -> Tuple [str , List [str ]]:
1104
- known_types = (
1105
- "failed passed skipped deselected xfailed xpassed warnings error" .split ()
1106
- )
1107
- unknown_type_seen = False
1108
- for found_type in stats .keys ():
1109
- if found_type not in known_types :
1110
- if found_type : # setup/teardown reports have an empty key, ignore them
1111
- known_types .append (found_type )
1112
- unknown_type_seen = True
1113
-
1114
- # main color
1115
- if "failed" in stats or "error" in stats :
1116
- main_color = "red"
1117
- elif "warnings" in stats or unknown_type_seen :
1118
- main_color = "yellow"
1119
- elif "passed" in stats :
1120
- main_color = "green"
1121
- else :
1122
- main_color = "yellow"
1123
-
1124
- return main_color , known_types
1125
-
1126
-
1127
- def build_summary_stats_line (stats ):
1128
- main_color , known_types = _get_main_color (stats )
1129
-
1130
- parts = []
1131
- for key in known_types :
1132
- reports = stats .get (key , None )
1133
- if reports :
1134
- count = sum (
1135
- 1 for rep in reports if getattr (rep , "count_towards_summary" , True )
1136
- )
1137
- color = _color_for_type .get (key , _color_for_type_default )
1138
- markup = {color : True , "bold" : color == main_color }
1139
- parts .append (("%d %s" % _make_plural (count , key ), markup ))
1140
-
1141
- if not parts :
1142
- parts = [("no tests ran" , {_color_for_type_default : True })]
1143
-
1144
- return parts , main_color
1145
-
1146
-
1147
1160
def _plugin_nameversions (plugininfo ) -> List [str ]:
1148
1161
values = [] # type: List[str]
1149
1162
for plugin , dist in plugininfo :
0 commit comments