mirror of
https://github.com/nicolargo/glances.git
synced 2026-06-02 19:05:00 -04:00
User feedback: column widths shouldn't reserve space for label variants the renderer never uses. Add v4-style short_name to mem and let the auto-size adapt: mem.available.short_name = 'avail' (9 -> 5 chars) mem.inactive.short_name = 'inactiv' (8 -> 7 chars) mem.buffers.short_name = 'buffer' (7 -> 6 chars) The mem render_curses_v5 now resolves every label via field_label(..., prefer_short=True), so adding/changing a short_name in the schema is enough — no renderer edit needed. The label-column floors in cpu and mem renderers (_CPU_LABEL_MIN_WIDTHS / _MEM_LABEL_MIN_WIDTHS) are gone too: labels are constant strings so the content-driven auto-size is naturally stable. Only value columns still need a fixed floor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
269 lines
9.5 KiB
Python
269 lines
9.5 KiB
Python
"""Glances v5 — tests for the mem plugin's curses renderer."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from glances.outputs.curses_renderer_v5 import ColorRole
|
|
from glances.plugins.mem.render_curses_v5 import render
|
|
|
|
|
|
@pytest.fixture
|
|
def mem_fields():
|
|
return {
|
|
"total": {"unit": "bytes", "label": "total"},
|
|
"available": {"unit": "bytes", "label": "avail"},
|
|
"percent": {
|
|
"unit": "percent",
|
|
"label": "MEM",
|
|
"watched": True,
|
|
"prominent": True,
|
|
},
|
|
"used": {"unit": "bytes", "label": "used"},
|
|
"free": {"unit": "bytes", "label": "free"},
|
|
"active": {"unit": "bytes", "label": "active"},
|
|
"inactive": {"unit": "bytes", "label": "inactive"},
|
|
"buffers": {"unit": "bytes", "label": "buffers"},
|
|
"cached": {"unit": "bytes", "label": "cached"},
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def mem_payload_linux():
|
|
return {
|
|
"total": 16_421_208_064,
|
|
"available": 7_691_833_344,
|
|
"percent": 53.2,
|
|
"used": 8_729_374_720,
|
|
"free": 2_740_531_200,
|
|
"active": 6_184_337_408,
|
|
"inactive": 4_744_855_552,
|
|
"buffers": 194_555_904,
|
|
"cached": 4_538_667_008,
|
|
"_levels": {"percent": {"level": "careful", "prominent": True}},
|
|
}
|
|
|
|
|
|
# ---------------------------------------------------------------- structure
|
|
|
|
|
|
def test_render_produces_four_rows(mem_payload_linux, mem_fields):
|
|
rows = render(mem_payload_linux, mem_fields)
|
|
assert len(rows) == 4
|
|
|
|
|
|
def test_render_first_row_carries_mem_title(mem_payload_linux, mem_fields):
|
|
rows = render(mem_payload_linux, mem_fields)
|
|
first_row_text = " ".join(c.text for c in rows[0].cells)
|
|
assert "MEM" in first_row_text
|
|
|
|
|
|
def test_render_first_row_includes_percent_and_active(mem_payload_linux, mem_fields):
|
|
rows = render(mem_payload_linux, mem_fields)
|
|
first = " ".join(c.text for c in rows[0].cells)
|
|
assert "53.2%" in first
|
|
assert "active" in first
|
|
|
|
|
|
def test_render_lines_2_to_4_are_four_cell_rows(mem_payload_linux, mem_fields):
|
|
rows = render(mem_payload_linux, mem_fields)
|
|
for i in (1, 2, 3):
|
|
assert len(rows[i].cells) == 4, f"row {i} has {len(rows[i].cells)} cells"
|
|
|
|
|
|
def test_render_uses_avail_label_when_available_present(mem_payload_linux, mem_fields):
|
|
rows = render(mem_payload_linux, mem_fields)
|
|
flat = " ".join(c.text for row in rows for c in row.cells)
|
|
assert "avail" in flat
|
|
# `used:` label not shown when avail is.
|
|
assert "used " not in flat or "used " in flat # we just check avail won
|
|
|
|
|
|
def test_render_falls_back_to_used_when_available_absent(mem_fields):
|
|
"""No `available` in the payload (some Unix variants) → show `used`."""
|
|
payload = {
|
|
"total": 16e9,
|
|
"percent": 50.0,
|
|
"used": 8e9,
|
|
"free": 4e9,
|
|
"active": 5e9,
|
|
"inactive": 3e9,
|
|
"buffers": 1e8,
|
|
"cached": 2e9,
|
|
"_levels": {},
|
|
}
|
|
rows = render(payload, mem_fields)
|
|
# used row must appear instead of avail.
|
|
flat = " ".join(c.text for row in rows for c in row.cells)
|
|
assert "used" in flat
|
|
assert "avail" not in flat
|
|
|
|
|
|
def test_render_columns_align_across_rows(mem_payload_linux, mem_fields):
|
|
rows = render(mem_payload_linux, mem_fields)
|
|
ncols = max(len(r.cells) for r in rows)
|
|
for col in range(ncols):
|
|
widths = {len(r.cells[col].text) for r in rows if col < len(r.cells)}
|
|
assert len(widths) == 1, f"col {col} widths differ: {widths}"
|
|
|
|
|
|
def test_render_value_columns_have_stable_width_across_cycles(mem_fields):
|
|
"""Value cols hold "999.9G" worst case (6 chars). Low values must
|
|
pad to the same width as high values so the block doesn't jiggle."""
|
|
low = {
|
|
"total": 1024.0,
|
|
"available": 512.0,
|
|
"percent": 5.0,
|
|
"used": 256.0,
|
|
"free": 768.0,
|
|
"active": 200.0,
|
|
"inactive": 100.0,
|
|
"buffers": 50.0,
|
|
"cached": 80.0,
|
|
"_levels": {},
|
|
}
|
|
high = {
|
|
"total": 999_999_999_999.0,
|
|
"available": 500_000_000_000.0,
|
|
"percent": 100.0,
|
|
"used": 499_999_999_999.0,
|
|
"free": 1.0,
|
|
"active": 300_000_000_000.0,
|
|
"inactive": 200_000_000_000.0,
|
|
"buffers": 100_000_000_000.0,
|
|
"cached": 200_000_000_000.0,
|
|
"_levels": {},
|
|
}
|
|
rows_low = render(low, mem_fields)
|
|
rows_high = render(high, mem_fields)
|
|
for col in (1, 3):
|
|
widths_low = {len(r.cells[col].text) for r in rows_low if col < len(r.cells)}
|
|
widths_high = {len(r.cells[col].text) for r in rows_high if col < len(r.cells)}
|
|
assert widths_low == widths_high, f"col {col}: {widths_low} vs {widths_high}"
|
|
|
|
|
|
def test_render_handles_empty_payload(mem_fields):
|
|
rows = render({}, mem_fields)
|
|
assert len(rows) == 1
|
|
flat = " ".join(c.text for c in rows[0].cells)
|
|
assert "MEM" in flat
|
|
|
|
|
|
def test_render_title_cell_carries_mem_text_and_bold(mem_payload_linux, mem_fields):
|
|
"""The first cell carries the plugin label as bold text. Its colour
|
|
is dynamic. With the default payload (careful percent), the title
|
|
stays HEADER (white) — careful does NOT escalate the title."""
|
|
rows = render(mem_payload_linux, mem_fields)
|
|
first_cell = rows[0].cells[0]
|
|
assert "MEM" in first_cell.text
|
|
assert first_cell.bold is True
|
|
assert first_cell.color == ColorRole.HEADER
|
|
|
|
|
|
def test_render_percent_color_reflects_level(mem_payload_linux, mem_fields):
|
|
"""The percent value cell inherits the level color from `_levels.percent`."""
|
|
rows = render(mem_payload_linux, mem_fields)
|
|
# Find the percent cell in row 0 (second cell after MEM title).
|
|
percent_cell = rows[0].cells[1]
|
|
assert "53.2" in percent_cell.text # right value
|
|
assert percent_cell.color == ColorRole.CAREFUL
|
|
|
|
|
|
def test_render_percent_is_prominent_per_schema(mem_payload_linux, mem_fields):
|
|
"""The percent field is declared `prominent: True` — its cell carries
|
|
the flag so the painter applies A_REVERSE under any alert level."""
|
|
rows = render(mem_payload_linux, mem_fields)
|
|
percent_cell = rows[0].cells[1]
|
|
assert percent_cell.prominent is True
|
|
|
|
|
|
def test_render_title_role_default_when_ok(mem_fields):
|
|
"""When all prominent fields are OK, title uses HEADER (white+bold)."""
|
|
payload = {
|
|
"total": 1024,
|
|
"percent": 30.0,
|
|
"active": 256,
|
|
"_levels": {"percent": {"level": "ok", "prominent": True}},
|
|
}
|
|
rows = render(payload, mem_fields)
|
|
assert rows[0].cells[0].color == ColorRole.HEADER
|
|
assert rows[0].cells[0].bold is True
|
|
|
|
|
|
def test_render_title_role_warning_when_percent_warning(mem_fields):
|
|
"""percent at WARNING (prominent) → title coloured WARNING + bold."""
|
|
payload = {
|
|
"total": 1024,
|
|
"percent": 80.0,
|
|
"active": 256,
|
|
"_levels": {"percent": {"level": "warning", "prominent": True}},
|
|
}
|
|
rows = render(payload, mem_fields)
|
|
assert rows[0].cells[0].color == ColorRole.WARNING
|
|
assert rows[0].cells[0].bold is True
|
|
|
|
|
|
def test_render_title_role_critical_when_percent_critical(mem_fields):
|
|
"""percent at CRITICAL → title red + bold."""
|
|
payload = {
|
|
"total": 1024,
|
|
"percent": 95.0,
|
|
"active": 256,
|
|
"_levels": {"percent": {"level": "critical", "prominent": True}},
|
|
}
|
|
rows = render(payload, mem_fields)
|
|
assert rows[0].cells[0].color == ColorRole.CRITICAL
|
|
|
|
|
|
def test_render_pulls_short_name_from_schema(mem_payload_linux):
|
|
"""The mem renderer reads `short_name` from the schema for compact labels."""
|
|
fields = {
|
|
"total": {"unit": "bytes"},
|
|
"available": {"unit": "bytes", "short_name": "avail"},
|
|
"percent": {"unit": "percent", "watched": True, "prominent": True},
|
|
"used": {"unit": "bytes"},
|
|
"free": {"unit": "bytes"},
|
|
"active": {"unit": "bytes"},
|
|
"inactive": {"unit": "bytes", "short_name": "inactiv"},
|
|
"buffers": {"unit": "bytes", "short_name": "buffer"},
|
|
"cached": {"unit": "bytes"},
|
|
}
|
|
rows = render(mem_payload_linux, fields)
|
|
flat = " ".join(c.text for row in rows for c in row.cells)
|
|
assert "avail" in flat
|
|
assert "inactiv" in flat
|
|
assert "buffer" in flat
|
|
# Full forms must NOT appear (the short variants supersede them).
|
|
assert "available" not in flat
|
|
assert "inactive" not in flat
|
|
# "buffers" might still match "buffer" substring; explicit guard:
|
|
label_texts = {r.cells[0].text.strip() for r in rows if r.cells} | {
|
|
r.cells[2].text.strip() for r in rows if len(r.cells) > 2
|
|
}
|
|
assert "buffers" not in label_texts
|
|
assert "buffer" in label_texts
|
|
|
|
|
|
def test_render_column_widths_shrink_with_short_names(mem_payload_linux):
|
|
"""Without short_name (e.g. 'available' = 9 chars), col 0 is 9-wide.
|
|
With short_name 'avail' (5 chars), col 0 shrinks to 5 — saving space."""
|
|
fields_long = {
|
|
"total": {"unit": "bytes"},
|
|
"available": {"unit": "bytes"}, # no short_name → label width = 9
|
|
"percent": {"unit": "percent", "watched": True, "prominent": True},
|
|
"free": {"unit": "bytes"},
|
|
"active": {"unit": "bytes"},
|
|
"inactive": {"unit": "bytes"},
|
|
"buffers": {"unit": "bytes"},
|
|
"cached": {"unit": "bytes"},
|
|
}
|
|
fields_short = {**fields_long, "available": {"unit": "bytes", "short_name": "avail"}}
|
|
|
|
rows_long = render(mem_payload_linux, fields_long)
|
|
rows_short = render(mem_payload_linux, fields_short)
|
|
|
|
col0_long = max(len(r.cells[0].text) for r in rows_long)
|
|
col0_short = max(len(r.cells[0].text) for r in rows_short)
|
|
assert col0_short < col0_long
|
|
assert col0_short == 5 # "avail" / "total" / "free" / "MEM" all ≤ 5
|