Files
MeshTerm/tests/test_platforms.py
T
Jean-Pierre MartineauandClaude Opus 5.5 11b5274b31 Docstrings and comments of the tests are in ASD-STE100
Each test docstring states the behaviour that the test proves, in the
simple present. No assertion message, test id, or other string literal
changed. Four statements now agree with the code: the Trace highlight
stays on the last row, a menu count, and two others.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 21:36:50 -04:00

239 lines
9.3 KiB
Python

# SPDX-License-Identifier: Apache-2.0
"""Tests for the platform seam.
The tests cover the resolution order, the singleton for the active platform, and the
checks of the two platform specs. The PicoCalc is strictly the narrower and plainer
flavour.
"""
from __future__ import annotations
import dataclasses
from pathlib import Path
import pytest
import meshterm.platforms as platforms_mod
from meshterm.platforms import (
PICOCALC_LYRA,
REGULAR,
get_platform,
resolve,
set_platform,
without_emoji,
)
def test_regular_is_todays_behaviour() -> None:
"""REGULAR is the desktop terminal, with exactly the behaviour that it has without this seam."""
assert REGULAR.name == "regular"
assert REGULAR.width_reclaim is True
assert REGULAR.emoji is True
assert REGULAR.truecolor is True
assert REGULAR.frame_border is True
assert REGULAR.readable_cols == 72
def test_picocalc_is_the_narrower_plainer_flavour() -> None:
"""PICOCALC_LYRA never claims a capability that the console does not have."""
assert PICOCALC_LYRA.name == "picocalc-lyra"
assert PICOCALC_LYRA.readable_cols < REGULAR.readable_cols
assert PICOCALC_LYRA.width_reclaim is False # a phantom column would tear the exact-width frame
assert PICOCALC_LYRA.emoji is False
assert PICOCALC_LYRA.truecolor is False
def test_get_platform_defaults_to_regular() -> None:
"""Each test starts on REGULAR (refer to the autouse fixture in conftest.py)."""
assert get_platform() is REGULAR
def test_set_platform_is_visible_through_get_platform() -> None:
"""``set_platform`` has an effect at once.
Each caller that reads through ``get_platform()`` sees it.
"""
set_platform(PICOCALC_LYRA)
assert get_platform() is PICOCALC_LYRA
set_platform(REGULAR)
assert get_platform() is REGULAR
def test_set_platform_reaches_a_dotted_import_too() -> None:
"""A consumer that imports the module, and not the name, sees the change at once.
This is the exact trap that the module docstring warns about. ``from meshterm.platforms
import PLATFORM`` at the top level of a module does not see the change. Access through
the module name (``platforms_mod.PLATFORM``) does see it. Each real binding point in
this code base reads through :func:`get_platform`, and it also sees it.
"""
set_platform(PICOCALC_LYRA)
assert platforms_mod.PLATFORM is PICOCALC_LYRA
def test_resolve_defaults_to_regular_off_device(monkeypatch: pytest.MonkeyPatch) -> None:
"""With no flag, no environment variable, and no device tree, the result is REGULAR.
The source is 'default'. This is the case of each desktop and CI machine.
"""
monkeypatch.delenv("MESHTERM_PLATFORM", raising=False)
monkeypatch.setattr(platforms_mod, "_DEVICE_TREE_MODEL_PATH", Path("/nonexistent/model"))
r = resolve()
assert r.platform is REGULAR
assert r.source == "default"
assert r.flag is None and r.env is None and r.detected_model is None
def test_resolve_flag_wins_over_everything(monkeypatch: pytest.MonkeyPatch) -> None:
"""An explicit --platform overrides both the environment variable and the auto-detection."""
monkeypatch.setenv("MESHTERM_PLATFORM", "picocalc-lyra")
monkeypatch.setattr(platforms_mod, "_DEVICE_TREE_MODEL_PATH", Path("/nonexistent/model"))
r = resolve("regular")
assert r.platform is REGULAR
assert r.source == "--platform flag"
assert r.env == "picocalc-lyra" # the result records it, but it did not decide the outcome
def test_resolve_env_wins_when_no_flag(monkeypatch: pytest.MonkeyPatch) -> None:
"""MESHTERM_PLATFORM decides when the user did not pass --platform."""
monkeypatch.setenv("MESHTERM_PLATFORM", "picocalc-lyra")
monkeypatch.setattr(platforms_mod, "_DEVICE_TREE_MODEL_PATH", Path("/nonexistent/model"))
r = resolve(None)
assert r.platform is PICOCALC_LYRA
assert r.source == "MESHTERM_PLATFORM env var"
def test_resolve_matching_is_case_insensitive_and_trims_space() -> None:
"""A flag value or an environment value matches in any case, and with extra whitespace."""
assert resolve(" PicoCalc-Lyra ").platform is PICOCALC_LYRA
assert resolve("REGULAR").platform is REGULAR
@pytest.mark.parametrize("old", ["picocalc", "cardputer"])
def test_the_old_device_names_are_refused_with_the_new_ones_listed(old: str) -> None:
"""The code refuses the old handheld names, and it lists the new names.
``picocalc`` and ``cardputer`` were renamed for the variant of the handheld, with no
alias. M5Stack makes three Cardputers, and the PicoCalc takes several cores. Thus the
bare names were ambiguous. A launcher that still passes one of them fails with a clear
error that names the real choices. It does not go back to the desktop layout with no
message.
"""
with pytest.raises(ValueError) as exc:
resolve(old)
assert "picocalc-lyra" in str(exc.value) and "cardputer-zero" in str(exc.value)
def test_resolve_unknown_flag_raises_with_the_choices(monkeypatch: pytest.MonkeyPatch) -> None:
"""An unknown --platform gives a clear ValueError that lists the choices.
A typing error in --platform must not go back to REGULAR with no message.
"""
monkeypatch.delenv("MESHTERM_PLATFORM", raising=False)
with pytest.raises(ValueError) as exc:
resolve("laptop")
message = str(exc.value)
assert "laptop" in message and "--platform" in message
assert "picocalc-lyra" in message and "regular" in message
def test_resolve_unknown_env_raises_only_when_it_would_decide(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""A bad environment variable is an error only when nothing else (the flag) overrides it."""
monkeypatch.setenv("MESHTERM_PLATFORM", "laptop")
# A valid flag wins completely. The result records the bad variable, and never checks it.
assert resolve("regular").platform is REGULAR
with pytest.raises(ValueError) as exc:
resolve(None)
assert "MESHTERM_PLATFORM" in str(exc.value)
def test_resolve_auto_detects_the_lyra_model(monkeypatch: pytest.MonkeyPatch) -> None:
"""The auto-detection finds the Lyra model.
The test uses the exact string from the handheld (P0, 2026-08-01), with the space and
the NUL at the end.
"""
monkeypatch.delenv("MESHTERM_PLATFORM", raising=False)
class _FakePath:
def read_bytes(self) -> bytes:
return b"Luckfox Lyra \x00"
monkeypatch.setattr(platforms_mod, "_DEVICE_TREE_MODEL_PATH", _FakePath())
r = resolve()
assert r.platform is PICOCALC_LYRA
assert r.source == "auto-detect"
assert r.detected_model == "Luckfox Lyra" # the code removes the space and the NUL at the end
def test_resolve_auto_detect_is_conservative(monkeypatch: pytest.MonkeyPatch) -> None:
"""The auto-detection is careful.
A device tree that names other hardware never gives a guess of picocalc-lyra.
"""
monkeypatch.delenv("MESHTERM_PLATFORM", raising=False)
class _FakePath:
def read_bytes(self) -> bytes:
return b"Raspberry Pi 4 Model B\x00"
monkeypatch.setattr(platforms_mod, "_DEVICE_TREE_MODEL_PATH", _FakePath())
r = resolve()
assert r.platform is REGULAR
assert r.detected_model == "Raspberry Pi 4 Model B"
# -- the emoji-less variant ------------------------------------------------------------
def test_without_emoji_moves_one_flag_and_nothing_else() -> None:
"""``without_emoji`` changes one flag and nothing else.
A terminal that cannot draw emoji is not a different flavour. The classic Windows
console has the width, the colour depth, the borders, and the keyboard of the desktop.
Only the icons must change. Each other field must stay, and ``font`` is the most
important one. If the code folded to the font of a handheld here, it would remove
accents from names that the console renders correctly.
"""
plain = without_emoji(REGULAR)
assert plain.emoji is False
assert plain.font == ""
assert plain.truecolor is True
assert plain.readable_cols == REGULAR.readable_cols
assert plain.footer_fkeys is REGULAR.footer_fkeys
changed = {
f.name
for f in dataclasses.fields(REGULAR)
if getattr(plain, f.name) != getattr(REGULAR, f.name)
}
assert changed == {"emoji"}
def test_without_emoji_leaves_an_already_compact_platform_alone() -> None:
"""``without_emoji`` leaves a platform that is already compact alone.
A platform whose icons are already compact comes back unchanged. It is the same
object. A copy would compare equal, but it would break each ``is`` check that the suite
makes against PICOCALC_LYRA.
"""
assert without_emoji(PICOCALC_LYRA) is PICOCALC_LYRA
def test_the_emoji_less_variant_still_drives_the_theme() -> None:
"""The platform without emoji still controls the theme.
The derived platform is a real platform. When code binds it, the code sends icons
through the table. This is the purpose of the design. The flags of the seam are
independent. Thus when one flag is cleared, the change reaches the glyph funnel, and
the fold of the console font does not come with it.
"""
from meshterm.ui.theme import fold_text, glyph
set_platform(without_emoji(REGULAR))
assert glyph("📡") == "☼" # the advert icon, in the compact form
assert fold_text("café Montréal") == "café Montréal" # no fold
assert get_platform().readable_cols == 72