Files
glances/tests/test_plugin_percpu_render_curses_v5.py
nicolargo 35c4f20cfc fix(v5): percpu — column header names are plain text (no bold, no HEADER)
V4 parity: ``display_cpu_stats_per_line`` emits each column name via
``curse_add_line(msg)`` with no decoration. Only the leading ``CPU``
title cell carries HEADER + bold.

Regression test verifies that every column-header cell after the title
is ``ColorRole.DEFAULT`` and ``bold=False``.
2026-05-15 10:11:11 +02:00

239 lines
8.7 KiB
Python

"""Glances v5 — tests for the percpu plugin's curses renderer."""
from __future__ import annotations
import pytest
from glances.outputs.curses_renderer_v5 import ColorRole
from glances.plugins.percpu.render_curses_v5 import render
@pytest.fixture
def percpu_fields():
return {
"cpu_number": {"unit": "number", "primary_key": True},
"total": {"unit": "percent"},
"user": {"unit": "percent"},
"system": {"unit": "percent"},
"idle": {"unit": "percent"},
"iowait": {"unit": "percent"},
"irq": {"unit": "percent"},
"nice": {"unit": "percent"},
"steal": {"unit": "percent"},
"guest": {"unit": "percent"},
}
def _core(n: int, **overrides):
base = {
"cpu_number": n,
"total": 20.0,
"user": 10.0,
"system": 5.0,
"iowait": 0.5,
"idle": 80.0,
"irq": 0.0,
"nice": 0.0,
"steal": 0.0,
"guest": 0.0,
}
base.update(overrides)
return base
@pytest.fixture
def percpu_payload_4cores():
return {
"data": [
_core(0, total=21.7, user=12.5, system=3.2, iowait=0.5, idle=83.8),
_core(1, total=11.9, user=8.1, system=2.0, iowait=0.1, idle=89.8),
_core(2, total=21.5, user=15.0, system=4.5, iowait=1.2, idle=79.3),
_core(3, total=8.1, user=6.3, system=1.8, iowait=0.0, idle=91.9),
],
"_levels": {},
}
# ---------------------------------------------------------------- structure
def test_render_first_row_is_header(percpu_payload_4cores, percpu_fields, monkeypatch):
monkeypatch.setattr("sys.platform", "linux")
rows = render(percpu_payload_4cores, percpu_fields)
flat = " ".join(c.text for c in rows[0].cells)
assert "CPU" in flat
assert "total" in flat
assert "user" in flat
assert "system" in flat
def test_render_header_includes_linux_columns(percpu_payload_4cores, percpu_fields, monkeypatch):
monkeypatch.setattr("sys.platform", "linux")
rows = render(percpu_payload_4cores, percpu_fields)
flat = " ".join(c.text for c in rows[0].cells)
# Linux: user, system, iowait, idle, irq, nice, steal, guest
for col in ("iowait", "idle", "irq", "nice", "steal", "guest"):
assert col in flat, f"missing column {col}"
def test_render_one_row_per_cpu_plus_header(percpu_payload_4cores, percpu_fields, monkeypatch):
monkeypatch.setattr("sys.platform", "linux")
rows = render(percpu_payload_4cores, percpu_fields)
# 1 header + 4 cores = 5 rows (no overflow when len <= max_cpu_display).
assert len(rows) == 5
def test_render_cpu_labels_for_single_digit_ids(percpu_payload_4cores, percpu_fields, monkeypatch):
monkeypatch.setattr("sys.platform", "linux")
rows = render(percpu_payload_4cores, percpu_fields)
labels = [r.cells[0].text.strip() for r in rows[1:]]
# cores are sorted by total desc, but each row label should be CPU<n>.
assert all(lbl.startswith("CPU") for lbl in labels)
assert {"CPU0", "CPU1", "CPU2", "CPU3"} == set(labels)
def test_render_data_rows_sorted_by_total_desc(percpu_payload_4cores, percpu_fields, monkeypatch):
monkeypatch.setattr("sys.platform", "linux")
rows = render(percpu_payload_4cores, percpu_fields)
# Top-N by total: CPU2 (21.5), CPU0 (21.7) → highest first.
# Wait: CPU0=21.7, CPU2=21.5 → CPU0 first.
labels = [r.cells[0].text.strip() for r in rows[1:]]
assert labels[0] == "CPU0"
assert labels[-1] == "CPU3" # 8.1, lowest
def test_render_each_data_row_has_label_plus_one_cell_per_column(percpu_payload_4cores, percpu_fields, monkeypatch):
monkeypatch.setattr("sys.platform", "linux")
rows = render(percpu_payload_4cores, percpu_fields)
# Header on Linux: title + total + 8 stats = 10 cells.
header_ncells = len(rows[0].cells)
for r in rows[1:]:
assert len(r.cells) == header_ncells
def test_render_value_cells_use_percent_format(percpu_payload_4cores, percpu_fields, monkeypatch):
monkeypatch.setattr("sys.platform", "linux")
rows = render(percpu_payload_4cores, percpu_fields)
flat = " ".join(c.text for row in rows[1:] for c in row.cells)
# 12.5% formatted as " 12.5%" must appear for CPU0.user.
assert "12.5%" in flat
def test_render_columns_align_across_rows(percpu_payload_4cores, percpu_fields, monkeypatch):
monkeypatch.setattr("sys.platform", "linux")
rows = render(percpu_payload_4cores, percpu_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}"
# ---------------------------------------------------------------- overflow
def test_render_more_than_max_cpu_display_adds_overflow_row(percpu_fields, monkeypatch):
"""6 cores → 4 displayed + 1 CPU* overflow row (default max_cpu_display=4)."""
monkeypatch.setattr("sys.platform", "linux")
payload = {
"data": [_core(i, total=float(i * 10)) for i in range(6)],
"_levels": {},
}
rows = render(payload, percpu_fields)
# 1 header + 4 displayed + 1 overflow = 6 rows.
assert len(rows) == 6
labels = [r.cells[0].text.strip() for r in rows[1:]]
assert labels[-1] == "CPU*"
def test_render_no_overflow_row_when_exact_max(percpu_payload_4cores, percpu_fields, monkeypatch):
monkeypatch.setattr("sys.platform", "linux")
rows = render(percpu_payload_4cores, percpu_fields)
labels = [r.cells[0].text.strip() for r in rows[1:]]
assert "CPU*" not in labels
# ---------------------------------------------------------------- OS variation
def test_render_macos_headers(percpu_fields, monkeypatch):
monkeypatch.setattr("sys.platform", "darwin")
payload = {"data": [_core(0)], "_levels": {}}
rows = render(payload, percpu_fields)
flat = " ".join(c.text for c in rows[0].cells)
assert "user" in flat
assert "system" in flat
assert "idle" in flat
assert "nice" in flat
# iowait is Linux-only
assert "iowait" not in flat
def test_render_windows_headers(percpu_fields, monkeypatch):
monkeypatch.setattr("sys.platform", "win32")
payload = {"data": [_core(0)], "_levels": {}}
rows = render(payload, percpu_fields)
flat = " ".join(c.text for c in rows[0].cells)
assert "dpc" in flat
assert "interrupt" in flat
# iowait/idle exclusive to other OSes shouldn't appear here.
assert "iowait" not in flat
# ---------------------------------------------------------------- edge cases
def test_render_empty_payload(percpu_fields, monkeypatch):
monkeypatch.setattr("sys.platform", "linux")
rows = render({}, percpu_fields)
# Header only.
assert len(rows) == 1
def test_render_empty_data_list(percpu_fields, monkeypatch):
monkeypatch.setattr("sys.platform", "linux")
rows = render({"data": [], "_levels": {}}, percpu_fields)
assert len(rows) == 1
def test_render_double_digit_cpu_label(percpu_fields, monkeypatch):
"""cpu_number=10 → right-aligned numeric label (no `CPU` prefix per v4)."""
monkeypatch.setattr("sys.platform", "linux")
payload = {"data": [_core(10)], "_levels": {}}
rows = render(payload, percpu_fields)
label_text = rows[1].cells[0].text
# v4: `f'{cpu_id:4}'` → " 10" (4-char right-aligned).
assert "10" in label_text
assert "CPU10" not in label_text
def test_render_handles_missing_value(percpu_fields, monkeypatch):
"""A missing stat field falls back to a `?` placeholder, not a crash."""
monkeypatch.setattr("sys.platform", "linux")
payload = {"data": [{"cpu_number": 0, "total": 10.0}], "_levels": {}}
rows = render(payload, percpu_fields)
flat = " ".join(c.text for row in rows[1:] for c in row.cells)
assert "?" in flat
# ---------------------------------------------------------------- title
def test_render_title_cell_is_header_bold(percpu_payload_4cores, percpu_fields, monkeypatch):
"""Percpu carries no _levels (per model_v5 docstring) → title stays HEADER."""
monkeypatch.setattr("sys.platform", "linux")
rows = render(percpu_payload_4cores, percpu_fields)
title = rows[0].cells[0]
assert "CPU" in title.text
assert title.color == ColorRole.HEADER
assert title.bold is True
def test_render_column_names_are_neither_bold_nor_header(percpu_payload_4cores, percpu_fields, monkeypatch):
"""v4 parity: column names (`total`, `user`, …) are plain text — no
HEADER color, no bold (only the leading `CPU` title gets decoration)."""
monkeypatch.setattr("sys.platform", "linux")
rows = render(percpu_payload_4cores, percpu_fields)
for cell in rows[0].cells[1:]:
assert cell.bold is False, f"column header {cell.text!r} is bold"
assert cell.color == ColorRole.DEFAULT, f"column header {cell.text!r} has color {cell.color}"