Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
* Fixed the startup loading screen crashing with ``UnicodeEncodeError`` on consoles that cannot encode
block or box-drawing characters, such as Windows output redirected to a file. The run summary drew a
greeting logo picked at random without checking the console encoding; logos the console cannot encode
are now left out.
27 changes: 22 additions & 5 deletions source/isaaclab/isaaclab/app/loading_screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,14 +338,15 @@ def __init__(self, num_stages: int, *, enabled: bool | None = None, logo: bool =
output. Defaults to None, which enables both when standard
output is a terminal.
logo: Whether :meth:`summary` shows a responsive greeting beside
the run summary. Defaults to True.
the run summary. Greetings the console cannot encode are left
out. Defaults to True.
"""
self._num_stages = num_stages
self._logos = random.choice(_LOGO_PAIRS) if logo else ()
self._remote_greetings: RemoteGreetings | None = None
self._enabled = _console_is_interactive() if enabled is None else enabled
self._console: IO[str] = sys.stdout
self._ascii_only = not _supports_box_drawing(self._console)
self._logos = _bundled_logos(self._console) if logo else ()
self._rich_console: Console | None = None
self._live: Live | None = None
self._display = self._Display(self)
Expand Down Expand Up @@ -645,10 +646,26 @@ def _console_is_interactive() -> bool:
return False


def _supports_box_drawing(stream: IO[str]) -> bool:
"""Return whether *stream* can encode box-drawing characters."""
def _can_encode(stream: IO[str], text: str) -> bool:
"""Return whether *stream* can encode *text*."""
try:
"".join(_BOX).encode(getattr(stream, "encoding", None) or "ascii")
text.encode(getattr(stream, "encoding", None) or "ascii")
except (LookupError, UnicodeEncodeError):
return False
return True


def _supports_box_drawing(stream: IO[str]) -> bool:
"""Return whether *stream* can encode box-drawing characters."""
return _can_encode(stream, "".join(_BOX))


def _bundled_logos(stream: IO[str]) -> tuple[str, ...]:
"""Draw a bundled greeting pair, keeping only the logos *stream* can encode.

A console that cannot encode block or box-drawing characters, such as a
Windows code page, would otherwise fail on the first summary that draws them.
"""
pairs = [tuple(logo for logo in pair if _can_encode(stream, logo)) for pair in _LOGO_PAIRS]
pairs = [pair for pair in pairs if pair]
return random.choice(pairs) if pairs else ()
27 changes: 26 additions & 1 deletion source/isaaclab/test/app/test_loading_screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import sys
import textwrap
import time
from io import StringIO
from io import BytesIO, StringIO, TextIOWrapper
from unittest.mock import Mock

import pytest
Expand Down Expand Up @@ -151,6 +151,31 @@ def test_hidden_greetings_do_not_start_remote_loading(monkeypatch: pytest.Monkey
screen.close()


@pytest.mark.kitless

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Warning · Test Quality — Mark whole file kitless via pytestmark

Every test in this file runs without Isaac Sim, and CI routes by file text, so a single-test mark pulls in the unmarked siblings while implying they do not qualify. Use a module-level pytestmark instead.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the kitless mark to a module-level pytestmark and removed the single-test mark, in 2214b28.

🤖 Addressed by Claude Code

@pytest.mark.parametrize(
("drawn", "columns"),
[(-1, 100), (0, 130)],
ids=["standard-width-art", "wide-width-art"],
)
def test_summary_prints_on_console_that_cannot_encode_greeting_art(
monkeypatch: pytest.MonkeyPatch, drawn: int, columns: int
):
# Output redirected to a file on Windows is encoded with the ANSI code page, which has no block or
# box-drawing characters, so a greeting drawn with them must give way to one the console can show.
console = TextIOWrapper(BytesIO(), encoding="cp1252")
monkeypatch.setattr(sys, "stdout", console)
monkeypatch.setenv("COLUMNS", str(columns))
monkeypatch.setattr(loading_screen.random, "choice", lambda options: options[drawn])
screen = loading_screen.LoadingScreen(1, enabled=False)

screen.summary("Run", {"Task": "Cartpole"})

console.flush()
shown = Text.from_ansi(console.buffer.getvalue().decode("cp1252")).plain
assert "Cartpole" in shown
assert "Welcome to Isaac Lab 3!" in shown


@pytest.mark.skipif(sys.platform == "win32", reason="Pseudo-terminals require POSIX.")
def test_interactive_input_shows_captured_context_before_waiting_for_answer():
import pty
Expand Down
Loading