Files
MeshTerm/tests/test_winterminal.py
T
Jean-Pierre MartineauandClaude Opus 5.5 efc02ddced The code text uses the glossary words in more places
A final pass over the docstrings and comments: "the own X of Y" is now
"Y's own X" (84 places), a navigation entry is a screen on the stack, our
node is our node, a paint is a paint, the quoted UX labels are in code
font, and a list of punctuation glyphs is in code font.

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

198 lines
8.6 KiB
Python

# SPDX-License-Identifier: Apache-2.0
"""Tests for the reopen of the session in Windows Terminal.
No test here starts a terminal. The tests check the command that the code would run. This
is the part that can be wrong with no message. A mistake in it makes 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:
"""The reopen is only for Windows.
The terminal of each other platform already draws the whole app. There is nothing to
correct.
"""
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:
"""A session that was reopened never offers to reopen again.
This is the loop guard. Windows Terminal does not qualify in any case, and the guard is
the backup. A relaunch that can chain is not only a cosmetic bug. Each relaunch starts
a window and exits, so the user would see windows open without end.
"""
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:
"""A frozen build relaunches itself.
The executable of the installer gets the arguments exactly as the user gave them.
"""
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:
"""A source install relaunches through its interpreter, with ``-m meshterm``.
The code does not use the console script. The path of the script is a wrapper, and its
location depends on the install method (venv, pipx, user site). The code already knows
the interpreter that runs it. The use of the interpreter also makes sure that the new
window gets the same environment as this window.
"""
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:
"""The code never passes the program name on as an argument.
``argv[0]`` is the name of this process. It is not for the next process. If the code
passed it on, it would reach MeshTerm as an extra positional argument, and Typer would
reject it. This would happen in a 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 own bookkeeping of the bundle never reaches the new process.
The frozen build found this bug, and the source build could not find it. A PyInstaller
executable of one file unpacks itself and runs itself again. The two halves talk
through ``_PYI*`` variables. When the code passed these variables on, the reopened
MeshTerm thought that it was the second half of a start that it never made. It checked
its parent and found Windows Terminal. Then it stopped 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 user's own settings stay
assert environment[winterminal.REOPENED_ENV] == "1"
def test_a_source_run_keeps_the_environment_it_was_given(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""A source run keeps the environment that it got.
The code removes nothing when there is no bundle, so a development run passes the whole
environment on. ``LD_LIBRARY_PATH`` is the variable to watch. When the build is not
frozen, the value belongs to the user. If the code removed it, the new process would
load its libraries in a different way, and there would be no reason for this.
"""
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 code puts back the loader path that the bootloader replaced.
The bootloader points the loader at its own unpacked copy, and it keeps the original
value in another variable. The new process unpacks a bundle of its own. Thus it needs
the value that the user started with. If there was no value, it gets no value.
"""
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 window that the code asks for fits the startup wordmark.
The size request exists because a window that is too small fails with no message. The
startup wordmark is 71 columns wide. In a narrower window, the splash changes with no
message to the narrow wordmark that is drawn for the PicoCalc. This is how the problem
was found: a desktop session showed the mark of the handheld. The request must be wider
than 71 columns, and not only wider than the minimum of the platform.
"""
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:
"""The session gets its own window at that size.
Both parts are necessary, and each was a separate way to get a window that is too
small. Without ``-w new``, the session becomes a tab in a window that is already open,
and it has the size of that window. A measurement on a real machine gave 64 columns.
This is the reason that the wrong mark appeared. Without ``--size``, a new window gets
the global start size of the user. That is a setting for the shell of the user, and not
for 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 the options of wt from the options of MeshTerm. If it does
# not, `--mock` becomes an option of wt.
assert "--" in argv and argv.index("--") > argv.index("--size")