Files
MeshTerm/tests/test_winterminal.py
T
Jean-Pierre MartineauandClaude Opus 5 2790159d04
Installers / windows-x64 (push) Canceled after 0s
Installers / macos-x64 (push) Canceled after 0s
Installers / macos-arm64 (push) Canceled after 0s
Installers / linux-x64 (push) Canceled after 0s
Installers / linux-arm64 (push) Canceled after 0s
GitHub release / build (push) Canceled after 0s
GitHub release / create the release (push) Canceled after 0s
Ask for a window MeshTerm fits in, rather than taking whatever is open
JP saw the PicoCalc's wordmark on a desktop session. The splash picks the
widest mark that fits, the desktop one is 71 columns, and below that it swaps
to the 53 drawn for the handheld -- so the app was announcing itself as the
handheld and truncating every menu description onto the pager underneath.

The window was 64 columns. reopen() passed no window argument, so wt.exe put
the session in a *tab* of whatever window happened to be open and it inherited
that size; with no window open it would instead have taken the reader's global
launch size, which is a setting about their shell and nothing to do with this
app. Either way the app could land somewhere too small for it, and the failure
was quiet and cosmetic rather than anything that announced itself.

It now asks for a window of its own at the platform's stated minimum plus a
margin -- 80x30 on the desktop, derived from readable_cols/readable_rows so it
follows them. Windows Terminal clamps to what the display can show, so a small
screen degrades exactly as it did before.

The tab is titled MeshTerm too. It was showing the executable's full path,
which on a downloaded build is a line of Downloads folder. The icon is not ours
to set -- Windows Terminal takes that from a profile and a bare command line is
not one.

Release 0.2.8.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 20:28:54 -04:00

177 lines
7.6 KiB
Python

"""Tests for reopening the session in Windows Terminal.
Nothing here starts a terminal. What is pinned is the command that *would* be run — which
is the part that can be silently wrong, because a mistake in it produces a window that
opens, fails, and closes faster than anyone can read it.
"""
from __future__ import annotations
import sys
import pytest
from meshterm.core import winterminal
def test_it_is_only_ever_windows(monkeypatch: pytest.MonkeyPatch) -> None:
"""Every other platform's terminal already draws the whole app; there is nothing to fix."""
monkeypatch.setattr(sys, "platform", "linux")
assert winterminal.available() is None
assert winterminal.reopen() is False
def test_a_reopened_session_never_offers_to_reopen_again(monkeypatch: pytest.MonkeyPatch) -> None:
"""The loop guard. Windows Terminal would not qualify anyway, and this is the backstop.
A relaunch that could chain is not a cosmetic bug: each one spawns a window and exits,
so the reader would watch windows open forever.
"""
monkeypatch.setattr(sys, "platform", "win32")
monkeypatch.setenv(winterminal.REOPENED_ENV, "1")
assert winterminal.available() is None
assert winterminal.reopen() is False
def test_a_frozen_build_relaunches_itself(monkeypatch: pytest.MonkeyPatch) -> None:
"""The installer's own executable takes the arguments exactly as they were given."""
monkeypatch.setattr(sys, "frozen", True, raising=False)
monkeypatch.setattr(sys, "executable", r"C:\Downloads\meshterm.exe")
monkeypatch.setattr(sys, "argv", ["meshterm", "--mock", "--profile", "home"])
assert winterminal.own_command() == [
r"C:\Downloads\meshterm.exe",
"--mock",
"--profile",
"home",
]
def test_a_source_install_relaunches_through_its_interpreter(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""``-m meshterm``, not the console script.
The script's path is a wrapper whose location varies by install method (venv, pipx,
user site), while the interpreter running us is a fact we already hold. Using the
interpreter also guarantees the new window gets the same environment as this one.
"""
monkeypatch.delattr(sys, "frozen", raising=False)
monkeypatch.setattr(sys, "executable", r"C:\venv\Scripts\python.exe")
monkeypatch.setattr(sys, "argv", ["meshterm", "--mock"])
assert winterminal.own_command() == [r"C:\venv\Scripts\python.exe", "-m", "meshterm", "--mock"]
def test_the_program_name_is_never_passed_on_as_an_argument(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""``argv[0]`` is how this process was named, not something to hand to the next one.
Passing it through would reach MeshTerm as a stray positional argument and be rejected
by Typer — in a brand new window that then closes.
"""
monkeypatch.setattr(sys, "frozen", True, raising=False)
monkeypatch.setattr(sys, "executable", r"C:\meshterm.exe")
monkeypatch.setattr(sys, "argv", [r"C:\some\other\name.exe"])
assert winterminal.own_command() == [r"C:\meshterm.exe"]
def test_the_bundles_own_bookkeeping_never_reaches_the_new_process(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""The bug the frozen build found, and the only one the source build could not.
A PyInstaller one-file executable unpacks itself and re-runs itself, the two halves
talking through ``_PYI*`` variables. Passing those on made the reopened MeshTerm think
it was the second half of a launch it never made; it checked its parent, found Windows
Terminal, and killed itself with a bootloader error in a window that had just opened.
"""
monkeypatch.setattr(sys, "frozen", True, raising=False)
monkeypatch.setenv("_PYI_ARCHIVE_FILE", r"C:\Downloads\meshterm.exe")
monkeypatch.setenv("_PYI_PARENT_PROCESS_LEVEL", "0")
monkeypatch.setenv("_MEIPASS2", r"C:\Temp\_MEI123")
monkeypatch.setenv("MESHTERM_HOME", r"C:\mine")
environment = winterminal.child_environment()
assert not [key for key in environment if key.startswith("_PYI")]
assert "_MEIPASS2" not in environment
assert environment["MESHTERM_HOME"] == r"C:\mine" # the reader's own settings survive
assert environment[winterminal.REOPENED_ENV] == "1"
def test_a_source_run_keeps_the_environment_it_was_given(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Nothing is stripped when there is no bundle, so a dev run is passed through whole.
``LD_LIBRARY_PATH`` is the one to watch: unfrozen it is the reader's, and dropping it
would change how the new process loads its libraries for no reason at all.
"""
monkeypatch.delattr(sys, "frozen", raising=False)
monkeypatch.setenv("LD_LIBRARY_PATH", "/opt/mine/lib")
assert winterminal.child_environment()["LD_LIBRARY_PATH"] == "/opt/mine/lib"
def test_the_loader_path_the_bootloader_replaced_is_put_back(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""The bootloader points the loader at its own unpacked copy and parks the original.
The new process unpacks a bundle of its own, so it wants the value the reader started
with — and where there was none, none.
"""
monkeypatch.setattr(sys, "frozen", True, raising=False)
monkeypatch.setenv("LD_LIBRARY_PATH", "/tmp/_MEI999")
monkeypatch.setenv("LD_LIBRARY_PATH_ORIG", "/opt/mine/lib")
monkeypatch.setenv("DYLD_LIBRARY_PATH", "/tmp/_MEI999")
monkeypatch.delenv("DYLD_LIBRARY_PATH_ORIG", raising=False)
environment = winterminal.child_environment()
assert environment["LD_LIBRARY_PATH"] == "/opt/mine/lib"
assert "LD_LIBRARY_PATH_ORIG" not in environment
assert "DYLD_LIBRARY_PATH" not in environment
def test_the_window_asked_for_fits_the_startup_wordmark() -> None:
"""The size request exists because a too-small window fails quietly, not loudly.
The startup wordmark is 71 columns; below that the splash silently swaps to the narrow
one drawn for the PicoCalc, which is how this was found — a desktop session showing the
handheld's mark. The request has to clear that, not merely the platform's minimum.
"""
from meshterm.platforms import REGULAR, set_platform
from meshterm.ui.logo import load_logo, logo_width
set_platform(REGULAR)
cols, rows = (int(part) for part in winterminal._wanted_size().split(","))
assert logo_width(load_logo(cols)) == 71, "the window asked for would get the narrow mark"
assert cols >= REGULAR.readable_cols
assert rows >= REGULAR.readable_rows
def test_the_session_gets_its_own_window_at_that_size(monkeypatch: pytest.MonkeyPatch) -> None:
"""Both halves matter, and each was a separate way to arrive somewhere too small.
Without ``-w new`` the session lands as a tab in whatever window is already open and
inherits its size — measured at 64 columns on a real machine, which is why the wrong
mark appeared. Without ``--size`` a new window takes the reader's global launch size,
a setting about their shell rather than about this app.
"""
started: list[list[str]] = []
monkeypatch.setattr(sys, "platform", "win32")
monkeypatch.delenv(winterminal.REOPENED_ENV, raising=False)
monkeypatch.setattr(winterminal.shutil, "which", lambda _: r"C:\wt.exe")
monkeypatch.setattr(
winterminal.subprocess, "Popen", lambda argv, **_: started.append(argv) or None
)
assert winterminal.reopen() is True
argv = started[0]
assert argv[:3] == [r"C:\wt.exe", "-w", "new"]
assert "--size" in argv
assert argv[argv.index("--size") + 1] == winterminal._wanted_size()
# `--` must still separate wt's options from MeshTerm's, or `--mock` becomes wt's.
assert "--" in argv and argv.index("--") > argv.index("--size")