Testing Live Display Implementation in Textualize/Rich
This test suite validates the Live display functionality in the Rich library, focusing on dynamic console output updates and terminal rendering. It covers various display modes, overflow handling, and console redirection scenarios.
Test Coverage Overview
Implementation Analysis
Technical Details
Best Practices Demonstrated
textualize/rich
tests/test_live.py
# encoding=utf-8
import time
from typing import Optional
# import pytest
from rich.console import Console
from rich.live import Live
from rich.text import Text
def create_capture_console(
*, width: int = 60, height: int = 80, force_terminal: Optional[bool] = True
) -> Console:
return Console(
width=width,
height=height,
force_terminal=force_terminal,
legacy_windows=False,
color_system=None, # use no color system to reduce complexity of output,
_environ={},
)
def test_live_state() -> None:
with Live("") as live:
assert live._started
live.start()
assert live.renderable == ""
assert live._started
live.stop()
assert not live._started
assert not live._started
def test_growing_display() -> None:
console = create_capture_console()
console.begin_capture()
with Live(console=console, auto_refresh=False) as live:
display = ""
for step in range(10):
display += f"Step {step}
"
live.update(display, refresh=True)
output = console.end_capture()
print(repr(output))
assert (
output
== "\x1b[?25lStep 0
\r\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
Step 8
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
Step 8
Step 9
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
Step 8
Step 9
\x1b[?25h"
)
def test_growing_display_transient() -> None:
console = create_capture_console()
console.begin_capture()
with Live(console=console, auto_refresh=False, transient=True) as live:
display = ""
for step in range(10):
display += f"Step {step}
"
live.update(display, refresh=True)
output = console.end_capture()
assert (
output
== "\x1b[?25lStep 0
\r\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
Step 8
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
Step 8
Step 9
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
Step 8
Step 9
\x1b[?25h\r\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K"
)
def test_growing_display_overflow_ellipsis() -> None:
console = create_capture_console(height=5)
console.begin_capture()
with Live(
console=console, auto_refresh=False, vertical_overflow="ellipsis"
) as live:
display = ""
for step in range(10):
display += f"Step {step}
"
live.update(display, refresh=True)
output = console.end_capture()
assert (
output
== "\x1b[?25lStep 0
\r\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
... \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
... \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
... \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
... \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
... \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
... \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
Step 8
Step 9
\x1b[?25h"
)
def test_growing_display_overflow_crop() -> None:
console = create_capture_console(height=5)
console.begin_capture()
with Live(console=console, auto_refresh=False, vertical_overflow="crop") as live:
display = ""
for step in range(10):
display += f"Step {step}
"
live.update(display, refresh=True)
output = console.end_capture()
assert (
output
== "\x1b[?25lStep 0
\r\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
Step 8
Step 9
\x1b[?25h"
)
def test_growing_display_overflow_visible() -> None:
console = create_capture_console(height=5)
console.begin_capture()
with Live(console=console, auto_refresh=False, vertical_overflow="visible") as live:
display = ""
for step in range(10):
display += f"Step {step}
"
live.update(display, refresh=True)
output = console.end_capture()
assert (
output
== "\x1b[?25lStep 0
\r\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
Step 8
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
Step 8
Step 9
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
Step 8
Step 9
\x1b[?25h"
)
def test_growing_display_autorefresh() -> None:
"""Test generating a table but using auto-refresh from threading"""
console = create_capture_console(height=5)
console.begin_capture()
with Live(console=console, auto_refresh=True, vertical_overflow="visible") as live:
display = ""
for step in range(10):
display += f"Step {step}
"
live.update(display)
time.sleep(0.2)
# no way to truly test w/ multithreading, just make sure it doesn't crash
def test_growing_display_console_redirect() -> None:
console = create_capture_console()
console.begin_capture()
with Live(console=console, auto_refresh=False) as live:
display = ""
for step in range(10):
console.print(f"Running step {step}")
display += f"Step {step}
"
live.update(display, refresh=True)
output = console.end_capture()
assert (
output
== "\x1b[?25lRunning step 0
\r\x1b[2KStep 0
\r\x1b[2K\x1b[1A\x1b[2KRunning step 1
Step 0
\r\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 2
Step 0
Step 1
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 3
Step 0
Step 1
Step 2
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 4
Step 0
Step 1
Step 2
Step 3
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 5
Step 0
Step 1
Step 2
Step 3
Step 4
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 6
Step 0
Step 1
Step 2
Step 3
Step 4
Step 5
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 7
Step 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 8
Step 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
Step 8
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 9
Step 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
Step 8
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
Step 8
Step 9
\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
Step 8
Step 9
\x1b[?25h"
)
def test_growing_display_file_console() -> None:
console = create_capture_console(force_terminal=False)
console.begin_capture()
with Live(console=console, auto_refresh=False) as live:
display = ""
for step in range(10):
display += f"Step {step}
"
live.update(display, refresh=True)
output = console.end_capture()
assert (
output
== "Step 0
Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
Step 8
Step 9
"
)
def test_live_screen() -> None:
console = create_capture_console(width=20, height=5)
console.begin_capture()
with Live(Text("foo"), screen=True, console=console, auto_refresh=False) as live:
live.refresh()
result = console.end_capture()
print(repr(result))
expected = "\x1b[?1049h\x1b[H\x1b[?25l\x1b[Hfoo
\x1b[Hfoo
\x1b[?25h\x1b[?1049l"
assert result == expected