Files
glances/tests/test_plugin_mem_v5.py
2026-05-04 18:38:09 +02:00

166 lines
5.3 KiB
Python
Executable File

#!/usr/bin/env python
#
# Glances - An eye on your system
#
# SPDX-FileCopyrightText: 2026 Nicolas Hennion <nicolas@nicolargo.com>
#
# SPDX-License-Identifier: LGPL-3.0-only
#
"""Glances v5 — unit tests for the `mem` plugin."""
from __future__ import annotations
from collections import namedtuple
from unittest.mock import patch
import pytest
from glances.config_v5 import GlancesConfigV5
from glances.plugins.mem.model_v5 import PluginModel
from glances.stats_store_v5 import StatsStoreV5
# psutil.virtual_memory() returns a namedtuple — replicate its shape.
VMTuple = namedtuple(
"svmem",
["total", "available", "percent", "used", "free", "active", "inactive", "buffers", "cached", "shared", "slab"],
)
def _make_vm(percent: float = 50.0) -> VMTuple:
return VMTuple(
total=16_000_000_000,
available=8_000_000_000,
percent=percent,
used=8_000_000_000,
free=4_000_000_000,
active=3_000_000_000,
inactive=1_000_000_000,
buffers=500_000_000,
cached=2_000_000_000,
shared=100_000_000,
slab=200_000_000,
)
# ---------------------------------------------------------- fixtures
@pytest.fixture
def store() -> StatsStoreV5:
return StatsStoreV5()
@pytest.fixture
def config(tmp_path, monkeypatch) -> GlancesConfigV5:
monkeypatch.setattr(GlancesConfigV5, "SYSTEM_CONFIG_PATH", tmp_path / "etc" / "glances.conf")
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / "xdg"))
return GlancesConfigV5()
def _config_with(tmp_path, monkeypatch, body: str) -> GlancesConfigV5:
monkeypatch.setattr(GlancesConfigV5, "SYSTEM_CONFIG_PATH", tmp_path / "etc" / "glances.conf")
xdg = tmp_path / "xdg"
cfg_dir = xdg / "glances"
cfg_dir.mkdir(parents=True)
(cfg_dir / "glances.conf").write_text(body)
monkeypatch.setenv("XDG_CONFIG_HOME", str(xdg))
return GlancesConfigV5()
# ---------------------------------------------------------- contract
def test_plugin_identity(store, config):
plugin = PluginModel(store, config)
assert plugin.plugin_name == "mem"
assert plugin.IS_COLLECTION is False
def test_plugin_declares_percent_as_watched(store, config):
plugin = PluginModel(store, config)
schema = plugin._fields["percent"]
assert schema["watched"] is True
assert schema["watch_direction"] == "high"
assert schema["prominent"] is True
assert "default_thresholds" in schema
# ---------------------------------------------------------- update pipeline
async def test_update_writes_psutil_fields_to_store(store, config):
plugin = PluginModel(store, config)
with patch("glances.plugins.mem.model_v5.psutil.virtual_memory", return_value=_make_vm(50.0)):
await plugin.update()
payload = store.get("mem")
assert payload is not None
assert payload["percent"] == 50.0
assert payload["total"] == 16_000_000_000
assert payload["available"] == 8_000_000_000
async def test_update_drops_undeclared_fields(store, config):
"""If psutil exposes a field not in fields_description it is filtered."""
Vm = namedtuple("svmem", ["total", "percent", "available", "used", "free", "future_field"])
fake = Vm(total=1, percent=10.0, available=1, used=0, free=1, future_field="surprise")
plugin = PluginModel(store, config)
with patch("glances.plugins.mem.model_v5.psutil.virtual_memory", return_value=fake):
await plugin.update()
payload = store.get("mem")
assert "future_field" not in payload
# ---------------------------------------------------------- _levels
@pytest.mark.parametrize(
"percent, expected_level",
[
(10.0, "ok"),
(50.0, "careful"),
(70.0, "warning"),
(90.0, "critical"),
(95.0, "critical"),
],
)
async def test_default_thresholds_drive_percent_level(store, config, percent, expected_level):
plugin = PluginModel(store, config)
with patch("glances.plugins.mem.model_v5.psutil.virtual_memory", return_value=_make_vm(percent)):
await plugin.update()
entry = store.get("mem")["_levels"]["percent"]
assert entry == {"level": expected_level, "prominent": True}
async def test_user_config_overrides_default_threshold(tmp_path, monkeypatch, store):
config = _config_with(tmp_path, monkeypatch, "[mem]\ncritical=95\n")
plugin = PluginModel(store, config)
# 91 was critical with defaults (90), but should now be warning.
with patch("glances.plugins.mem.model_v5.psutil.virtual_memory", return_value=_make_vm(91.0)):
await plugin.update()
assert store.get("mem")["_levels"]["percent"]["level"] == "warning"
async def test_only_percent_field_is_levelled(store, config):
plugin = PluginModel(store, config)
with patch("glances.plugins.mem.model_v5.psutil.virtual_memory", return_value=_make_vm(50.0)):
await plugin.update()
levels = store.get("mem")["_levels"]
assert list(levels.keys()) == ["percent"]
# ---------------------------------------------------------- export
async def test_get_export_strips_internals(store, config):
plugin = PluginModel(store, config)
with patch("glances.plugins.mem.model_v5.psutil.virtual_memory", return_value=_make_vm(50.0)):
await plugin.update()
exported = plugin.get_export()
assert "_levels" not in exported
assert "time_since_update" not in exported # exportable: False on metadata
assert exported["percent"] == 50.0