mirror of
https://github.com/nicolargo/glances.git
synced 2026-06-02 19:05:00 -04:00
Three small UX adjustments raised on the live TUI:
1. ``fs.percent`` schema: explicit ``prominent: False``. Colours the
Used cell at alert level but no longer reverse-videos it (the
framework defaults missing ``prominent`` to True). Consistent with
sin/sout in memswap.
2. ``fs.percent`` default thresholds raised to 70/80/90. Filesystems
often sit at 60-70% on healthy hosts and the old 50/70/90 ladder
produced noisy "careful" warnings. ``mem`` keeps the stricter
50/70/90.
3. Alert block UI clarifies active-vs-resolved state:
- Header was ``ALERTS (N)`` — now ``ALERT (X ongoing / Y total)``.
``ongoing`` = unique (plugin, key, field) tuples whose
most-recent event has a non-ok level. ``total`` = full history
length.
- Each event row ending in a non-ok level for the **latest** event
of its tuple gets a visible ``(ongoing)`` suffix on the level
cell:
``careful (ongoing)``
``careful → warning (ongoing)``
Older non-ok events superseded by a later resolution lose the
marker — they are no longer ongoing. Resolutions
(``warning → ok``) never carry the marker.
7 new tests:
- ``test_percent_is_watched_but_not_prominent`` (fs schema)
- ``test_percent_default_thresholds_are_70_80_90`` (fs schema)
- 1 fs runtime test updated to the new ladder + non-prominent
- 4 renderer tests covering header counts, ongoing marker, resolution
exemption, latest-per-tuple semantics.
Full v5 suite: 734 passed, lint clean.
171 lines
5.7 KiB
Python
171 lines
5.7 KiB
Python
"""Glances v5 — tests for the fs plugin's curses renderer."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from glances.outputs.curses_renderer_v5 import ColorRole
|
|
from glances.plugins.fs.render_curses_v5 import render
|
|
|
|
|
|
@pytest.fixture
|
|
def fs_fields():
|
|
return {
|
|
"mnt_point": {"unit": "string", "primary_key": True},
|
|
"device_name": {"unit": "string"},
|
|
"fs_type": {"unit": "string", "internal": True},
|
|
"options": {"unit": "string", "internal": True},
|
|
"size": {"unit": "bytes"},
|
|
"used": {"unit": "bytes"},
|
|
"free": {"unit": "bytes"},
|
|
"percent": {"unit": "percent", "watched": True, "prominent": True},
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def fs_payload():
|
|
return {
|
|
"data": [
|
|
{
|
|
"mnt_point": "/",
|
|
"device_name": "/dev/sda1",
|
|
"fs_type": "ext4",
|
|
"options": "rw,relatime",
|
|
"size": 500 * 1024**3,
|
|
"used": 125 * 1024**3,
|
|
"free": 375 * 1024**3,
|
|
"percent": 25.0,
|
|
},
|
|
{
|
|
"mnt_point": "/home",
|
|
"device_name": "/dev/sda2",
|
|
"fs_type": "ext4",
|
|
"options": "rw,relatime",
|
|
"size": 1024**4,
|
|
"used": 512 * 1024**3,
|
|
"free": 512 * 1024**3,
|
|
"percent": 50.0,
|
|
},
|
|
],
|
|
"_levels": {
|
|
"/": {"percent": {"level": "ok", "prominent": True}},
|
|
"/home": {"percent": {"level": "careful", "prominent": True}},
|
|
},
|
|
}
|
|
|
|
|
|
# ---------------------------------------------------------------- structure
|
|
|
|
|
|
def test_render_first_row_is_filesys_header(fs_payload, fs_fields):
|
|
rows = render(fs_payload, fs_fields)
|
|
text = " ".join(c.text for c in rows[0].cells)
|
|
assert "FILE SYS" in text
|
|
assert "Used" in text
|
|
assert "Total" in text
|
|
|
|
|
|
def test_render_one_row_per_filesystem(fs_payload, fs_fields):
|
|
rows = render(fs_payload, fs_fields)
|
|
# 1 header + 2 filesystems
|
|
assert len(rows) == 3
|
|
|
|
|
|
def test_render_filesystems_sorted_by_mnt_point(fs_payload, fs_fields):
|
|
rows = render(fs_payload, fs_fields)
|
|
mnts = [r.cells[0].text.strip() for r in rows[1:]]
|
|
assert mnts == sorted(mnts)
|
|
|
|
|
|
def test_render_each_data_row_has_three_cells(fs_payload, fs_fields):
|
|
rows = render(fs_payload, fs_fields)
|
|
for r in rows[1:]:
|
|
assert len(r.cells) == 3
|
|
|
|
|
|
def test_render_used_and_total_in_bytes(fs_payload, fs_fields):
|
|
rows = render(fs_payload, fs_fields)
|
|
flat = " ".join(c.text for row in rows[1:] for c in row.cells)
|
|
# 125 GiB → "125.0G", 500 GiB → "500.0G"
|
|
assert "125.0G" in flat
|
|
assert "500.0G" in flat
|
|
|
|
|
|
def test_render_columns_align_across_rows(fs_payload, fs_fields):
|
|
rows = render(fs_payload, fs_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_block_width_fits_sidebar_cap(fs_payload, fs_fields):
|
|
"""Row width (cells + 1-char gaps) must stay ≤ 34 (left sidebar cap)."""
|
|
rows = render(fs_payload, fs_fields)
|
|
for r in rows:
|
|
natural_w = sum(len(c.text) for c in r.cells) + max(0, len(r.cells) - 1)
|
|
assert natural_w <= 34, f"row width {natural_w} exceeds sidebar cap 34"
|
|
|
|
|
|
def test_render_handles_empty_data(fs_fields):
|
|
rows = render({"data": [], "_levels": {}}, fs_fields)
|
|
assert len(rows) == 1
|
|
|
|
|
|
def test_render_handles_empty_payload(fs_fields):
|
|
rows = render({}, fs_fields)
|
|
assert len(rows) == 1
|
|
flat = " ".join(c.text for c in rows[0].cells)
|
|
assert "FILE SYS" in flat
|
|
|
|
|
|
# ---------------------------------------------------------------- truncation
|
|
|
|
|
|
def test_render_long_mnt_point_truncated_with_underscore(fs_fields):
|
|
"""Long mountpoints are tail-truncated with a leading ``_`` (v4 parity)."""
|
|
long_mnt = "/very/long/mountpoint/path/that/exceeds/limits"
|
|
payload = {
|
|
"data": [{"mnt_point": long_mnt, "size": 1024**3, "used": 0, "free": 1024**3, "percent": 0.0}],
|
|
"_levels": {long_mnt: {}},
|
|
}
|
|
rows = render(payload, fs_fields)
|
|
mnt_text = rows[1].cells[0].text
|
|
assert mnt_text.startswith("_")
|
|
# Width matches the value-row mnt column.
|
|
assert len(mnt_text) == len(rows[0].cells[0].text)
|
|
|
|
|
|
# ---------------------------------------------------------------- color
|
|
|
|
|
|
def test_render_used_cell_inherits_percent_level(fs_fields):
|
|
"""Per-fs ``_levels.<mnt>.percent`` drives the Used cell color (v4 parity:
|
|
v4 decorates the ``used`` cell with the percent threshold). The cell is
|
|
coloured but NOT prominent — see fs.percent schema (``prominent: False``)."""
|
|
payload = {
|
|
"data": [{"mnt_point": "/", "size": 100, "used": 95, "free": 5, "percent": 95.0}],
|
|
"_levels": {"/": {"percent": {"level": "critical", "prominent": False}}},
|
|
}
|
|
rows = render(payload, fs_fields)
|
|
used_cell = rows[1].cells[1]
|
|
assert used_cell.color == ColorRole.CRITICAL
|
|
assert used_cell.prominent is False
|
|
|
|
|
|
def test_render_title_role_header_when_no_prominent_alert(fs_payload, fs_fields):
|
|
rows = render(fs_payload, fs_fields)
|
|
title = rows[0].cells[0]
|
|
assert title.color == ColorRole.HEADER
|
|
assert title.bold is True
|
|
|
|
|
|
def test_render_title_role_escalates_on_critical(fs_fields):
|
|
payload = {
|
|
"data": [{"mnt_point": "/", "size": 100, "used": 95, "free": 5, "percent": 95.0}],
|
|
"_levels": {"/": {"percent": {"level": "critical", "prominent": True}}},
|
|
}
|
|
rows = render(payload, fs_fields)
|
|
assert rows[0].cells[0].color == ColorRole.CRITICAL
|
|
assert rows[0].cells[0].bold is True
|