@@ -198,7 +198,7 @@ def flush(self, decrement_depth: bool = True):
198198 self ._stack .clear ()
199199
200200
201- class BufferedTracePrinter :
201+ class TracePrinter :
202202 def __init__ (
203203 self ,
204204 print_passed : bool ,
@@ -218,6 +218,81 @@ def __init__(
218218 self .colors = colors
219219 self .width = width
220220 self .print = print_callback
221+
222+ def log_message_to_console (
223+ self , in_test : bool , message : str , stream : Literal ["stdout" , "stderr" ]
224+ ):
225+ self .print (f"Logged from test { stream } : { message .rstrip ()} " )
226+
227+ def _format_banner (self , status_text : str , status_color , name : str ) -> str :
228+ if self .colors and status_color :
229+ status_text = status_color (status_text )
230+ status_line = f"{ status_text } : { name } "
231+ underline_length = min (self .width , ANSI .len (status_line ))
232+ underline = "═" * underline_length
233+ return f"{ status_line } \n { underline } "
234+
235+ def _format_keyword_header (self , name : str , attributes : dict ) -> str :
236+ kwtype = attributes ["type" ]
237+ args = attributes ["args" ]
238+ if kwtype != "KEYWORD" :
239+ name = f"{ kwtype } { name } " if name else kwtype
240+ if args or kwtype in {"KEYWORD" , "SETUP" , "TEARDOWN" }:
241+ argstr = "(" + ", " .join (repr (a ) for a in args ) + ")"
242+ else :
243+ argstr = ""
244+ return f"▶ { name } { argstr } "
245+
246+ def _format_keyword_status (self , status : str , elapsed_time_ms : int ) -> str :
247+ elapsed = TestTimings .format_time (elapsed_time_ms / 1000 )
248+ if status == "PASS" :
249+ status_text = "✓ PASS"
250+ if self .colors :
251+ status_text = ANSI .Fore .BRIGHT_GREEN (status_text )
252+ return f" { status_text } { elapsed } "
253+ elif status == "SKIP" :
254+ status_text = "→ SKIP"
255+ if self .colors :
256+ status_text = ANSI .Fore .YELLOW (status_text )
257+ return f" { status_text } { elapsed } "
258+ elif status == "FAIL" :
259+ status_text = "✗ FAIL"
260+ if self .colors :
261+ status_text = ANSI .Fore .BRIGHT_RED (status_text )
262+ return f" { status_text } { elapsed } "
263+ elif status == "NOT RUN" :
264+ status_text = "⊘ NOT RUN"
265+ if self .colors :
266+ status_text = ANSI .Fore .BRIGHT_BLACK (status_text )
267+ return f" { status_text } { elapsed } "
268+ else :
269+ return f" ? { status } { elapsed } "
270+
271+ def _format_log_message (self , level : str , text : str , indent : str = "" ) -> list [str ]:
272+ level_initial = level [0 ].upper ()
273+ text_lines = text .splitlines ()
274+ lines = []
275+ lines .append (f"{ indent } { level_initial } { text_lines [0 ]} " )
276+ for text_line in text_lines [1 :]:
277+ lines .append (f"{ indent } { text_line } " )
278+
279+ if self .colors :
280+ if level == "ERROR" or level == "FAIL" :
281+ lines = [ANSI .Fore .BRIGHT_RED (line ) for line in lines ]
282+ elif level == "WARN" :
283+ lines = [ANSI .Fore .BRIGHT_YELLOW (line ) for line in lines ]
284+ elif level == "SKIP" :
285+ lines = [ANSI .Fore .YELLOW (line ) for line in lines ]
286+ elif level == "INFO" :
287+ lines = [ANSI .Fore .BRIGHT_BLACK (line ) for line in lines ]
288+ elif level == "DEBUG" or level == "TRACE" :
289+ lines = [ANSI .Fore .WHITE (line ) for line in lines ]
290+ return lines
291+
292+
293+ class BufferedTracePrinter (TracePrinter ):
294+ def __init__ (self , * args , ** kwargs ):
295+ super ().__init__ (* args , ** kwargs )
221296 self .test_trace_stack = TraceStack ("<prerun>" )
222297 self .suite_trace_stack = TraceStack ("<prerun>" )
223298
@@ -246,16 +321,12 @@ def end_suite(self, name, attributes):
246321 status_text += " WITH WARNINGS"
247322 status_color = ANSI .Fore .BRIGHT_YELLOW
248323 if should_print :
249- if self .colors and status_color :
250- status_text = status_color (status_text )
251- suite_name = attributes ["longname" ]
252- status_line = f"{ status_text } : { suite_name } "
253- underline_length = min (self .width , ANSI .len (status_line ))
254- underline = "═" * underline_length
324+ banner = self ._format_banner (
325+ status_text , status_color , attributes ["longname" ]
326+ )
255327 if not trace :
256328 trace = attributes ["message" ] + "\n "
257- trace = f"{ status_line } \n { underline } \n { trace } "
258- self .print (trace )
329+ self .print (f"{ banner } \n { trace } " )
259330
260331 def start_test (self , name , attributes ):
261332 test_name = attributes ["longname" ]
@@ -286,28 +357,16 @@ def end_test(self, name, attributes):
286357 status_text += " WITH WARNINGS"
287358 status_color = ANSI .Fore .BRIGHT_YELLOW
288359 if should_print :
289- if self .colors and status_color :
290- status_text = status_color (status_text )
291- test_name = attributes ["longname" ]
292- status_line = f"{ status_text } : { test_name } "
293- underline_length = min (self .width , ANSI .len (status_line ))
294- underline = "═" * underline_length
360+ banner = self ._format_banner (
361+ status_text , status_color , attributes ["longname" ]
362+ )
295363 if not trace :
296364 trace = attributes ["message" ] + "\n "
297- trace = f"{ status_line } \n { underline } \n { trace } "
298- self .print (trace )
365+ self .print (f"{ banner } \n { trace } " )
299366
300367 def start_keyword (self , in_test : bool , name , attributes ):
301368 stack = self .test_trace_stack if in_test else self .suite_trace_stack
302- kwtype = attributes ["type" ]
303- args = attributes ["args" ]
304- if kwtype != "KEYWORD" :
305- name = f"{ kwtype } { name } " if name else kwtype
306- if args or kwtype in {"KEYWORD" , "SETUP" , "TEARDOWN" }:
307- argstr = "(" + ", " .join (repr (a ) for a in args ) + ")"
308- else :
309- argstr = ""
310- trace_line = f"▶ { name } { argstr } "
369+ trace_line = self ._format_keyword_header (name , attributes )
311370 stack .push_keyword (trace_line )
312371
313372 def end_keyword (self , in_test : bool , name , attributes ):
@@ -322,28 +381,7 @@ def end_keyword(self, in_test: bool, name, attributes):
322381 # so the hierarchy appears in the trace.
323382 stack .flush ()
324383
325- elapsed_time_ms = attributes ["elapsedtime" ]
326- elapsed = TestTimings .format_time (elapsed_time_ms / 1000 )
327-
328- keyword_trace = " "
329- if status == "PASS" :
330- status_text = "✓ PASS"
331- if self .colors :
332- status_text = ANSI .Fore .BRIGHT_GREEN (status_text )
333- keyword_trace += f"{ status_text } { elapsed } "
334- elif status == "SKIP" :
335- status_text = "→ SKIP"
336- if self .colors :
337- status_text = ANSI .Fore .YELLOW (status_text )
338- keyword_trace += f"{ status_text } { elapsed } "
339- elif status == "FAIL" :
340- status_text = "✗ FAIL"
341- if self .colors :
342- status_text = ANSI .Fore .BRIGHT_RED (status_text )
343- keyword_trace += f"{ status_text } { elapsed } "
344- else :
345- keyword_trace += f"? { status } { elapsed } "
346-
384+ keyword_trace = self ._format_keyword_status (status , attributes ["elapsedtime" ])
347385 stack .append_trace (keyword_trace )
348386
349387 def log_message (self , in_test : bool , attributes ):
@@ -354,98 +392,40 @@ def log_message(self, in_test: bool, attributes):
354392 stack = self .test_trace_stack if in_test else self .suite_trace_stack
355393 stack .flush (decrement_depth = False )
356394
357- level_initial = level [0 ].upper ()
358- text_lines = text .splitlines ()
359- lines = []
360- # First line gets level initial
361- lines .append (f"{ level_initial } { text_lines [0 ]} " )
362- # Remaining lines align without repeating the level
363- for text_line in text_lines [1 :]:
364- lines .append (f" { text_line } " )
365-
366- if self .colors :
367- if level == "ERROR" :
368- lines = [ANSI .Fore .BRIGHT_RED (line ) for line in lines ]
369- elif level == "FAIL" :
370- lines = [ANSI .Fore .BRIGHT_RED (line ) for line in lines ]
371- elif level == "WARN" :
372- lines = [ANSI .Fore .BRIGHT_YELLOW (line ) for line in lines ]
373- elif level == "SKIP" :
374- lines = [ANSI .Fore .YELLOW (line ) for line in lines ]
375- elif level == "INFO" :
376- lines = [ANSI .Fore .BRIGHT_BLACK (line ) for line in lines ]
377- elif level == "DEBUG" or level == "TRACE" :
378- lines = [ANSI .Fore .WHITE (line ) for line in lines ]
379-
380395 if level == "ERROR" :
381396 stack .has_errors = True
382397 elif level == "WARN" :
383398 stack .has_warnings = True
384399 elif level == "FAIL" :
385400 stack .has_failures = True
386401
402+ lines = self ._format_log_message (level , text )
387403 stack .append_trace ("\n " .join (lines ))
388404
389- def log_message_to_console (
390- self , in_test : bool , message : str , stream : Literal ["stdout" , "stderr" ]
391- ):
392- self .print (f"Logged from test { stream } : { message .rstrip ()} " )
393-
394405
395- class LiveTracePrinter :
396- def __init__ (
397- self ,
398- print_passed : bool ,
399- print_skipped : bool ,
400- print_warned : bool ,
401- print_errored : bool ,
402- print_failed : bool ,
403- colors : bool ,
404- width : int ,
405- print_callback ,
406- ):
407- self .print_passed = print_passed
408- self .print_skipped = print_skipped
409- self .print_warned = print_warned
410- self .print_errored = print_errored
411- self .print_failed = print_failed
412- self .colors = colors
413- self .width = width
414- self .print = print_callback
406+ class LiveTracePrinter (TracePrinter ):
407+ def __init__ (self , * args , ** kwargs ):
408+ super ().__init__ (* args , ** kwargs )
415409 self .indent = 0
416410
417411 def start_suite (self , name , attributes ):
418412 suite_name = attributes ["longname" ]
419- status_line = f"SUITE: { suite_name } "
420- underline_length = min (self .width , ANSI .len (status_line ))
421- underline = "═" * underline_length
422- trace = f"{ status_line } \n { underline } "
423- self .print (trace )
413+ banner = self ._format_banner ("SUITE" , None , suite_name )
414+ self .print (banner )
424415
425416 def end_suite (self , name , attributes ):
426417 pass
427418
428419 def start_test (self , name , attributes ):
429420 test_name = attributes ["longname" ]
430- status_line = f"TEST: { test_name } "
431- underline_length = min (self .width , ANSI .len (status_line ))
432- underline = "═" * underline_length
433- trace = f"{ status_line } \n { underline } "
434- self .print (trace )
421+ banner = self ._format_banner ("TEST" , None , test_name )
422+ self .print (banner )
435423
436424 def end_test (self , name , attributes ):
437425 self .print ("" )
438426
439427 def start_keyword (self , in_test : bool , name , attributes ):
440- kwtype = attributes ["type" ]
441- args = attributes ["args" ]
442- if kwtype != "KEYWORD" :
443- name = f"{ kwtype } { name } " if name else kwtype
444- if args or kwtype in {"KEYWORD" , "SETUP" , "TEARDOWN" }:
445- argstr = "(" + ", " .join (repr (a ) for a in args ) + ")"
446- else :
447- argstr = ""
448- trace_line = f"▶ { name } { argstr } "
428+ trace_line = self ._format_keyword_header (name , attributes )
449429 self .print (" " * self .indent * 2 + trace_line )
450430 self .indent += 1
451431
@@ -454,70 +434,17 @@ def end_keyword(self, in_test: bool, name, attributes):
454434
455435 status = attributes ["status" ]
456436
457- elapsed_time_ms = attributes ["elapsedtime" ]
458- elapsed = TestTimings .format_time (elapsed_time_ms / 1000 )
459-
460- keyword_trace = " "
461- if status == "PASS" :
462- status_text = "✓ PASS"
463- if self .colors :
464- status_text = ANSI .Fore .BRIGHT_GREEN (status_text )
465- keyword_trace += f"{ status_text } { elapsed } "
466- elif status == "SKIP" :
467- status_text = "→ SKIP"
468- if self .colors :
469- status_text = ANSI .Fore .YELLOW (status_text )
470- keyword_trace += f"{ status_text } { elapsed } "
471- elif status == "FAIL" :
472- status_text = "✗ FAIL"
473- if self .colors :
474- status_text = ANSI .Fore .BRIGHT_RED (status_text )
475- keyword_trace += f"{ status_text } { elapsed } "
476- elif status == "NOT RUN" :
477- status_text = "⊘ NOT RUN"
478- if self .colors :
479- status_text = ANSI .Fore .BRIGHT_BLACK (status_text )
480- keyword_trace += f"{ status_text } { elapsed } "
481- else :
482- keyword_trace += f"? { status } { elapsed } "
483-
437+ keyword_trace = self ._format_keyword_status (status , attributes ["elapsedtime" ])
484438 self .print (" " * self .indent * 2 + keyword_trace )
485439
486440 def log_message (self , in_test : bool , attributes ):
487441 level = attributes ["level" ]
488442 text = attributes ["message" ]
489443
490444 indent = " " * self .indent * 2
491- level_initial = level [0 ].upper ()
492- text_lines = text .splitlines ()
493- lines = []
494- # First line gets level initial
495- lines .append (f"{ indent } { level_initial } { text_lines [0 ]} " )
496- # Remaining lines align without repeating the level
497- for text_line in text_lines [1 :]:
498- lines .append (f"{ indent } { text_line } " )
499-
500- if self .colors :
501- if level == "ERROR" :
502- lines = [ANSI .Fore .BRIGHT_RED (line ) for line in lines ]
503- elif level == "FAIL" :
504- lines = [ANSI .Fore .BRIGHT_RED (line ) for line in lines ]
505- elif level == "WARN" :
506- lines = [ANSI .Fore .BRIGHT_YELLOW (line ) for line in lines ]
507- elif level == "SKIP" :
508- lines = [ANSI .Fore .YELLOW (line ) for line in lines ]
509- elif level == "INFO" :
510- lines = [ANSI .Fore .BRIGHT_BLACK (line ) for line in lines ]
511- elif level == "DEBUG" or level == "TRACE" :
512- lines = [ANSI .Fore .WHITE (line ) for line in lines ]
513-
445+ lines = self ._format_log_message (level , text , indent )
514446 self .print ("\n " .join (lines ))
515447
516- def log_message_to_console (
517- self , in_test : bool , message : str , stream : Literal ["stdout" , "stderr" ]
518- ):
519- self .print (f"Logged from test { stream } : { message .rstrip ()} " )
520-
521448
522449class TestStatistics :
523450 def __init__ (self ):
0 commit comments