# Copyright Kevin Deldycke <[email protected]> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
from __future__ import annotations
import os
import shutil
import pytest
from click_extra import env_summary, strip_ansi
from meta_package_manager import logo
from meta_package_manager.logo import (
GUTTER,
LOGO,
LOGO_LINES,
LOGO_WIDTH,
TONES,
VERSION_SCREEN,
build_rows,
render_logo,
)
PROG, VERSION = "mpm", "1.2.3"
BLANK = " "
MIRROR = str.maketrans({"+": ":", ":": "+"})
"""Reflecting the mark swaps its two vertical planes, left-facing for right."""
[docs]
def version_screen(prog_name: str, version: str) -> str | None:
"""The screen `--version` draws, without the styles of the theme."""
return VERSION_SCREEN.render(prog_name, version, {})
[docs]
def screen_lines(*args, **kwargs) -> list[str]:
"""The version screen body, stripped of ANSI and split into lines.
Drops the leading blank the screen opens on, so every assertion below indexes
the mark itself.
"""
screen = version_screen(*args, **kwargs)
assert screen is not None
lines = strip_ansi(screen).split("\n")
assert lines[0] == ""
return lines[1:]
[docs]
def test_logo_is_rectangular_and_pairable():
"""Rows must be equal-length and even in count, or the half-blocks desync."""
assert len(LOGO) % 2 == 0
assert len({len(row) for row in LOGO}) == 1
[docs]
def test_declared_dimensions_match_the_logo():
assert LOGO_WIDTH == len(LOGO[0])
assert LOGO_LINES == len(LOGO) // 2
[docs]
def test_logo_width_is_even():
"""An odd width gives the axis a column that would face both ways at once."""
assert LOGO_WIDTH % 2 == 0
[docs]
def test_logo_is_symmetric():
"""Mirroring swaps the two vertical planes and leaves the up-facing ones."""
for row in LOGO:
assert row == row[::-1].translate(MIRROR)
[docs]
def test_logo_uses_declared_tones_only():
"""A tone with no palette entry would raise a `KeyError` mid-render."""
used = {char for row in LOGO for char in row} - {BLANK}
assert used <= set(TONES)
[docs]
def test_logo_carries_structure():
"""The mark must shade in more than one tone.
Its structure lives entirely in the shading, so a single-tone map would render
as a featureless silhouette, the failure the color gate exists to avoid.
"""
assert len({char for row in LOGO for char in row} - {BLANK}) > 1
[docs]
def test_render_logo_halves_the_rows():
"""Two sub-pixel rows collapse into one terminal line."""
assert len(render_logo()) == LOGO_LINES
[docs]
def test_render_logo_lines_are_logo_wide():
"""Every line must be the same visible width, or the metadata column shears."""
assert {len(strip_ansi(line)) for line in render_logo()} == {LOGO_WIDTH}
[docs]
def test_render_logo_silhouette_is_symmetric():
"""Symmetry has to survive the render, not just hold in the source map.
Only the silhouette is asserted, because *shading* is handed and should be: the
two vertical planes catch different light, so mirroring the mark swaps them, and
that difference is spent entirely on the colors.
"""
for line in render_logo():
inked = [char != BLANK for char in strip_ansi(line)]
assert inked == inked[::-1]
[docs]
def test_render_logo_mirrors_character_for_character():
"""Half-blocks say only how much of a cell is covered, which mirrors."""
for line in render_logo():
plain = strip_ansi(line)
assert plain == plain[::-1]
[docs]
def test_render_logo_leaves_transparent_runs_unstyled():
"""Transparent cells stay bare spaces, never wrapped in a no-op reset."""
for line in render_logo():
assert "\x1b[0m\x1b[0m" not in line
[docs]
def test_version_screen_opens_on_a_blank_line():
"""Separates the mark from whatever noisier command preceded it."""
screen = version_screen(PROG, VERSION)
assert screen is not None
assert screen.startswith("\n")
assert not screen.startswith("\n\n")
[docs]
def test_version_screen_reports_the_version():
"""The first metadata row is the plain-rendering version line, verbatim."""
assert f"{PROG}, version {VERSION}" in "\n".join(screen_lines(PROG, VERSION))
[docs]
def test_version_screen_is_as_tall_as_the_logo():
assert len(screen_lines(PROG, VERSION)) == LOGO_LINES
[docs]
def test_version_screen_has_no_trailing_whitespace():
for line in screen_lines(PROG, VERSION):
assert line == line.rstrip()
[docs]
@pytest.mark.parametrize(
("columns", "renders"),
(
("200", True),
("80", True),
("40", False),
("1", False),
),
)
def test_version_screen_needs_room(monkeypatch, columns, renders):
"""A terminal too narrow for the two columns declines instead of wrapping."""
monkeypatch.setenv("COLUMNS", columns)
monkeypatch.setenv("LINES", "40")
assert (version_screen(PROG, VERSION) is not None) is renders
[docs]
def test_build_rows_are_empty_outside_a_binary():
"""A source install bakes nothing, so the two rows never reach the screen."""
assert build_rows() == ()
[docs]
def bake(monkeypatch) -> None:
"""Fill the placeholders `click-extra prebake all` writes before a binary build.
The longest values any target produces, so the width assertion below is the
worst case rather than whatever this machine happens to be.
"""
monkeypatch.setattr(logo, "__build_time__", "2026-09-08T10:24:16Z")
monkeypatch.setattr(logo, "__build_os__", "Windows")
monkeypatch.setattr(logo, "__build_target__", "manylinux_2_28_x86_64")
monkeypatch.setattr(logo, "__build_target_arch__", "ARM64 (AArch64)")
[docs]
def test_build_rows_reach_the_screen_once_baked(monkeypatch):
bake(monkeypatch)
assert build_rows() == (
("Built", "2026-09-08T10:24:16Z"),
("Target", "manylinux_2_28_x86_64"),
)
body = "\n".join(screen_lines(PROG, VERSION))
assert "2026-09-08T10:24:16Z" in body
assert "manylinux_2_28_x86_64" in body
[docs]
def test_build_rows_cost_the_screen_no_width(monkeypatch):
"""A binary must still get its screen on a stock 80-column terminal.
The build metadata is stacked into two rows precisely so it never widens the
layout past the `Managers` row. Joined onto one line it would, and the screen
would then silently degrade to the plain message on every released binary,
which is the one build nobody runs from a checkout to notice.
"""
monkeypatch.setenv("COLUMNS", "80")
monkeypatch.setenv("LINES", "40")
bare = version_screen(PROG, VERSION)
bake(monkeypatch)
baked = version_screen(PROG, VERSION)
assert bare is not None
assert baked is not None
assert max(map(len, strip_ansi(baked).split("\n"))) == max(
map(len, strip_ansi(bare).split("\n"))
)
[docs]
def test_version_screen_width_never_exceeds_the_terminal():
"""The threshold is measured, not guessed: no line may overflow the width."""
width = shutil.get_terminal_size().columns
for line in screen_lines(PROG, VERSION):
assert len(line) <= width
[docs]
def test_env_summary_facts_reach_the_screen():
"""Both halves of the plain summary are restated as their own metadata rows."""
python_part, platform_part = env_summary().split(", ", 1)
screen = "\n".join(screen_lines(PROG, VERSION))
assert python_part.removeprefix("Python ") in screen
assert platform_part in screen
[docs]
def test_version_screen_survives_a_missing_terminal(monkeypatch):
"""No `COLUMNS` and no terminal still lands on `shutil`'s 80-column default.
That is the shape of a redirected run with colors forced on, which must still
get the screen it asked for.
"""
def no_terminal(*args):
raise OSError
monkeypatch.delenv("COLUMNS", raising=False)
monkeypatch.delenv("LINES", raising=False)
monkeypatch.setattr(os, "get_terminal_size", no_terminal)
assert version_screen(PROG, VERSION) is not None