Skip to content

Commit f912a4d

Browse files
committed
Refactor result printers to reduce duplication
1 parent 4f4e0e5 commit f912a4d

File tree

1 file changed

+97
-170
lines changed

1 file changed

+97
-170
lines changed

robot_trace/RobotTrace.py

Lines changed: 97 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -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

522449
class TestStatistics:
523450
def __init__(self):

0 commit comments

Comments
 (0)