33
33
34
34
REPORT_COLLECTING_RESOLUTION = 0.5
35
35
36
+ KNOWN_TYPES = (
37
+ "failed" ,
38
+ "passed" ,
39
+ "skipped" ,
40
+ "deselected" ,
41
+ "xfailed" ,
42
+ "xpassed" ,
43
+ "warnings" ,
44
+ "error" ,
45
+ )
46
+
36
47
_REPORTCHARS_DEFAULT = "fE"
37
48
38
49
@@ -254,6 +265,8 @@ def __init__(self, config: Config, file=None) -> None:
254
265
self ._showfspath = None
255
266
256
267
self .stats = {} # type: Dict[str, List[Any]]
268
+ self ._main_color = None # type: Optional[str]
269
+ self ._known_types = None # type: Optional[List]
257
270
self .startdir = config .invocation_dir
258
271
if file is None :
259
272
file = sys .stdout
@@ -372,6 +385,12 @@ def section(self, title, sep="=", **kw):
372
385
def line (self , msg , ** kw ):
373
386
self ._tw .line (msg , ** kw )
374
387
388
+ def _add_stats (self , category : str , items : List ) -> None :
389
+ set_main_color = category not in self .stats
390
+ self .stats .setdefault (category , []).extend (items [:])
391
+ if set_main_color :
392
+ self ._set_main_color ()
393
+
375
394
def pytest_internalerror (self , excrepr ):
376
395
for line in str (excrepr ).split ("\n " ):
377
396
self .write_line ("INTERNALERROR> " + line )
@@ -381,15 +400,14 @@ def pytest_warning_captured(self, warning_message, item):
381
400
# from _pytest.nodes import get_fslocation_from_item
382
401
from _pytest .warnings import warning_record_to_str
383
402
384
- warnings = self .stats .setdefault ("warnings" , [])
385
403
fslocation = warning_message .filename , warning_message .lineno
386
404
message = warning_record_to_str (warning_message )
387
405
388
406
nodeid = item .nodeid if item is not None else ""
389
407
warning_report = WarningReport (
390
408
fslocation = fslocation , message = message , nodeid = nodeid
391
409
)
392
- warnings . append ( warning_report )
410
+ self . _add_stats ( "warnings" , [ warning_report ] )
393
411
394
412
def pytest_plugin_registered (self , plugin ):
395
413
if self .config .option .traceconfig :
@@ -400,7 +418,7 @@ def pytest_plugin_registered(self, plugin):
400
418
self .write_line (msg )
401
419
402
420
def pytest_deselected (self , items ):
403
- self .stats . setdefault ("deselected" , []). extend ( items )
421
+ self ._add_stats ("deselected" , items )
404
422
405
423
def pytest_runtest_logstart (self , nodeid , location ):
406
424
# ensure that the path is printed before the
@@ -421,7 +439,7 @@ def pytest_runtest_logreport(self, report: TestReport) -> None:
421
439
word , markup = word
422
440
else :
423
441
markup = None
424
- self .stats . setdefault (category , []). append ( rep )
442
+ self ._add_stats (category , [rep ] )
425
443
if not letter and not word :
426
444
# probably passed setup/teardown
427
445
return
@@ -463,6 +481,10 @@ def pytest_runtest_logreport(self, report: TestReport) -> None:
463
481
self ._tw .write (" " + line )
464
482
self .currentfspath = - 2
465
483
484
+ @property
485
+ def _is_last_item (self ):
486
+ return len (self ._progress_nodeids_reported ) == self ._session .testscollected
487
+
466
488
def pytest_runtest_logfinish (self , nodeid ):
467
489
assert self ._session
468
490
if self .verbosity <= 0 and self ._show_progress_info :
@@ -472,15 +494,12 @@ def pytest_runtest_logfinish(self, nodeid):
472
494
else :
473
495
progress_length = len (" [100%]" )
474
496
475
- main_color , _ = _get_main_color (self .stats )
476
-
477
497
self ._progress_nodeids_reported .add (nodeid )
478
- is_last_item = (
479
- len (self ._progress_nodeids_reported ) == self ._session .testscollected
480
- )
481
- if is_last_item :
482
- self ._write_progress_information_filling_space (color = main_color )
498
+
499
+ if self ._is_last_item :
500
+ self ._write_progress_information_filling_space ()
483
501
else :
502
+ main_color , _ = self ._get_main_color ()
484
503
w = self ._width_of_current_line
485
504
past_edge = w + progress_length + 1 >= self ._screen_width
486
505
if past_edge :
@@ -504,9 +523,8 @@ def _get_progress_information_message(self) -> str:
504
523
)
505
524
return " [100%]"
506
525
507
- def _write_progress_information_filling_space (self , color = None ):
508
- if not color :
509
- color , _ = _get_main_color (self .stats )
526
+ def _write_progress_information_filling_space (self ):
527
+ color , _ = self ._get_main_color ()
510
528
msg = self ._get_progress_information_message ()
511
529
w = self ._width_of_current_line
512
530
fill = self ._tw .fullwidth - w - 1
@@ -531,9 +549,9 @@ def pytest_collection(self) -> None:
531
549
532
550
def pytest_collectreport (self , report : CollectReport ) -> None :
533
551
if report .failed :
534
- self .stats . setdefault ("error" , []). append ( report )
552
+ self ._add_stats ("error" , [report ] )
535
553
elif report .skipped :
536
- self .stats . setdefault ("skipped" , []). append ( report )
554
+ self ._add_stats ("skipped" , [report ] )
537
555
items = [x for x in report .result if isinstance (x , pytest .Item )]
538
556
self ._numcollected += len (items )
539
557
if self .isatty :
@@ -916,7 +934,7 @@ def summary_stats(self):
916
934
return
917
935
918
936
session_duration = time .time () - self ._sessionstarttime
919
- (parts , main_color ) = build_summary_stats_line ( self .stats )
937
+ (parts , main_color ) = self .build_summary_stats_line ( )
920
938
line_parts = []
921
939
922
940
display_sep = self .verbosity >= 0
@@ -1017,6 +1035,53 @@ def show_skipped(lines: List[str]) -> None:
1017
1035
for line in lines :
1018
1036
self .write_line (line )
1019
1037
1038
+ def _get_main_color (self ) -> Tuple [str , List [str ]]:
1039
+ if self ._main_color is None or self ._known_types is None or self ._is_last_item :
1040
+ self ._set_main_color ()
1041
+ assert self ._main_color
1042
+ assert self ._known_types
1043
+ return self ._main_color , self ._known_types
1044
+
1045
+ def _determine_main_color (self , unknown_type_seen : bool ) -> str :
1046
+ stats = self .stats
1047
+ if "failed" in stats or "error" in stats :
1048
+ main_color = "red"
1049
+ elif "warnings" in stats or "xpassed" in stats or unknown_type_seen :
1050
+ main_color = "yellow"
1051
+ elif "passed" in stats or not self ._is_last_item :
1052
+ main_color = "green"
1053
+ else :
1054
+ main_color = "yellow"
1055
+ return main_color
1056
+
1057
+ def _set_main_color (self ) -> None :
1058
+ unknown_types = [] # type: List[str]
1059
+ for found_type in self .stats .keys ():
1060
+ if found_type : # setup/teardown reports have an empty key, ignore them
1061
+ if found_type not in KNOWN_TYPES and found_type not in unknown_types :
1062
+ unknown_types .append (found_type )
1063
+ self ._known_types = list (KNOWN_TYPES ) + unknown_types
1064
+ self ._main_color = self ._determine_main_color (bool (unknown_types ))
1065
+
1066
+ def build_summary_stats_line (self ) -> Tuple [List [Tuple [str , Dict [str , bool ]]], str ]:
1067
+ main_color , known_types = self ._get_main_color ()
1068
+
1069
+ parts = []
1070
+ for key in known_types :
1071
+ reports = self .stats .get (key , None )
1072
+ if reports :
1073
+ count = sum (
1074
+ 1 for rep in reports if getattr (rep , "count_towards_summary" , True )
1075
+ )
1076
+ color = _color_for_type .get (key , _color_for_type_default )
1077
+ markup = {color : True , "bold" : color == main_color }
1078
+ parts .append (("%d %s" % _make_plural (count , key ), markup ))
1079
+
1080
+ if not parts :
1081
+ parts = [("no tests ran" , {_color_for_type_default : True })]
1082
+
1083
+ return parts , main_color
1084
+
1020
1085
1021
1086
def _get_pos (config , rep ):
1022
1087
nodeid = config .cwd_relative_nodeid (rep .nodeid )
@@ -1105,50 +1170,6 @@ def _make_plural(count, noun):
1105
1170
return count , noun + "s" if count != 1 else noun
1106
1171
1107
1172
1108
- def _get_main_color (stats ) -> Tuple [str , List [str ]]:
1109
- known_types = (
1110
- "failed passed skipped deselected xfailed xpassed warnings error" .split ()
1111
- )
1112
- unknown_type_seen = False
1113
- for found_type in stats .keys ():
1114
- if found_type not in known_types :
1115
- if found_type : # setup/teardown reports have an empty key, ignore them
1116
- known_types .append (found_type )
1117
- unknown_type_seen = True
1118
-
1119
- # main color
1120
- if "failed" in stats or "error" in stats :
1121
- main_color = "red"
1122
- elif "warnings" in stats or "xpassed" in stats or unknown_type_seen :
1123
- main_color = "yellow"
1124
- elif "passed" in stats :
1125
- main_color = "green"
1126
- else :
1127
- main_color = "yellow"
1128
-
1129
- return main_color , known_types
1130
-
1131
-
1132
- def build_summary_stats_line (stats ):
1133
- main_color , known_types = _get_main_color (stats )
1134
-
1135
- parts = []
1136
- for key in known_types :
1137
- reports = stats .get (key , None )
1138
- if reports :
1139
- count = sum (
1140
- 1 for rep in reports if getattr (rep , "count_towards_summary" , True )
1141
- )
1142
- color = _color_for_type .get (key , _color_for_type_default )
1143
- markup = {color : True , "bold" : color == main_color }
1144
- parts .append (("%d %s" % _make_plural (count , key ), markup ))
1145
-
1146
- if not parts :
1147
- parts = [("no tests ran" , {_color_for_type_default : True })]
1148
-
1149
- return parts , main_color
1150
-
1151
-
1152
1173
def _plugin_nameversions (plugininfo ) -> List [str ]:
1153
1174
values = [] # type: List[str]
1154
1175
for plugin , dist in plugininfo :
0 commit comments