noaa-goes-visualization/tests/test_vfs.py

216 lines
7.7 KiB
Python
Raw Permalink Normal View History

import pytest
"""Tests for reclaiming the virtiofs mount's file handles.
These guard a mechanism that exists only because of an environment defect: the
mount's daemon holds a host descriptor per inode the guest looks up, and runs out.
The properties that matter are that reclaim is *actually verified* rather than
assumed, and that the expensive fallback is used only when it must be.
"""
from suvi import vfs
def test_drop_caches_reports_failure_without_root(monkeypatch):
"""Must return False rather than raise, so the fallback can run."""
def refuse(*args, **kwargs):
raise PermissionError(13, "Permission denied")
monkeypatch.setattr("builtins.open", refuse)
assert vfs.drop_caches() is False
def test_reclaimable_kb_reads_the_slab():
assert vfs.reclaimable_kb() > 0 # real /proc/meminfo
def test_reclaimable_kb_survives_a_broken_meminfo(monkeypatch):
monkeypatch.setattr("builtins.open", lambda *a, **k: (_ for _ in ()).throw(OSError()))
assert vfs.reclaimable_kb() == 0
def test_release_prefers_drop_caches(monkeypatch):
"""The fallback allocates tens of GiB; it must not run when drop_caches works."""
monkeypatch.setattr(vfs, "drop_caches", lambda: True)
def explode(*args, **kwargs):
raise AssertionError("allocated memory despite drop_caches succeeding")
monkeypatch.setattr(vfs, "bytearray", explode, raising=False)
assert vfs.release_handles() is True
def test_fallback_reports_failure_when_nothing_is_reclaimed(monkeypatch):
monkeypatch.setattr(vfs, "drop_caches", lambda: False)
monkeypatch.setattr(vfs, "reclaimable_kb", lambda: 1000)
assert vfs.release_handles(budget_gib=1, floor_kb=0) is False
def test_release_gives_up_when_the_slab_cannot_be_read(monkeypatch):
monkeypatch.setattr(vfs, "drop_caches", lambda: False)
monkeypatch.setattr(vfs, "reclaimable_kb", lambda: 0)
assert vfs.release_handles() is False
# --------------------------------------------------------------- memory bounding
def test_available_gib_reads_meminfo():
assert vfs.available_gib() > 0
def test_pressure_stops_at_the_reserve_not_a_fraction(monkeypatch):
"""Capping at a fraction of available memory reclaims nothing.
The kernel only frees slab under genuine pressure, so leaving 40% headroom
means it never triggers: an earlier version allocated 14 GiB of a 23 GiB
allowance and freed zero, costing the machine the memory for no benefit.
Pressure must run down to a fixed reserve instead.
"""
monkeypatch.setattr(vfs, "drop_caches", lambda: False)
monkeypatch.setattr(vfs, "reclaimable_kb", lambda: 1000) # never yields
available = [20.0]
monkeypatch.setattr(vfs, "available_gib", lambda: available[0])
allocated = []
real = bytearray
def fake(n):
allocated.append(n)
available[0] -= 1.0 # each GiB claimed reduces what is available
return real(1)
monkeypatch.setattr(vfs, "bytearray", fake, raising=False)
assert vfs.release_handles(reserve_gib=2.0, floor_kb=0) is False
# Pushed from 20 GiB down to the 2 GiB reserve, not stopped at 60%.
assert 17 <= len(allocated) <= 19, f"allocated {len(allocated)} GiB"
def test_pressure_stops_as_soon_as_the_slab_yields(monkeypatch):
monkeypatch.setattr(vfs, "drop_caches", lambda: False)
readings = iter([1000, 1000, 1000, 400])
monkeypatch.setattr(vfs, "reclaimable_kb", lambda: next(readings))
monkeypatch.setattr(vfs, "available_gib", lambda: 20.0)
allocated = []
real = bytearray
monkeypatch.setattr(
vfs, "bytearray", lambda n: allocated.append(n) or real(1), raising=False
)
assert vfs.release_handles(floor_kb=0) is True
assert len(allocated) == 2, "kept allocating after the kernel gave ground"
# ------------------------------------------------------------------- Reliever
def test_reliever_fires_on_the_interval(monkeypatch):
calls = []
monkeypatch.setattr(vfs, "release_handles", lambda *a, **k: calls.append(1) or True)
reliever = vfs.Reliever(interval=10)
for _ in range(9):
reliever.tick()
assert calls == []
reliever.tick()
assert len(calls) == 1
def test_reliever_keeps_firing_across_a_long_run(monkeypatch):
calls = []
monkeypatch.setattr(vfs, "release_handles", lambda *a, **k: calls.append(1) or True)
reliever = vfs.Reliever(interval=10)
for _ in range(100):
reliever.tick()
assert len(calls) == 10, "stopped reclaiming partway through a long traversal"
assert reliever.releases == 10
def test_reliever_counts_batched_items(monkeypatch):
"""Callers processing six bands per timestamp tick by six, not one."""
calls = []
monkeypatch.setattr(vfs, "release_handles", lambda *a, **k: calls.append(1) or True)
reliever = vfs.Reliever(interval=10)
reliever.tick(6)
reliever.tick(6)
assert len(calls) == 1
assert reliever.seen == 12
def test_reliever_can_be_disabled(monkeypatch):
def explode(*a, **k):
raise AssertionError("reclaimed while disabled")
monkeypatch.setattr(vfs, "release_handles", explode)
reliever = vfs.Reliever(interval=1, enabled=False)
for _ in range(50):
reliever.tick()
assert reliever.releases == 0
def test_reliever_reclaims_when_the_run_ends(monkeypatch):
"""Reclaiming only during a run leaves the machine loaded once it finishes.
The inodes a traversal cached stay pinned -- and with them the host's file
handles -- until something else forces reclaim. That is why the system was
still saturated with nothing of ours running.
"""
calls = []
monkeypatch.setattr(vfs, "release_handles", lambda *a, **k: calls.append(1) or True)
reliever = vfs.Reliever(interval=1000, on_exit=False)
reliever.tick(5)
assert calls == [], "should not have reclaimed mid-run yet"
reliever.finish()
assert len(calls) == 1
def test_finish_is_idempotent(monkeypatch):
calls = []
monkeypatch.setattr(vfs, "release_handles", lambda *a, **k: calls.append(1) or True)
reliever = vfs.Reliever(interval=1000, on_exit=False)
reliever.tick()
reliever.finish()
reliever.finish()
reliever.finish()
assert len(calls) == 1
def test_finish_does_nothing_if_no_work_happened(monkeypatch):
def explode(*a, **k):
raise AssertionError("reclaimed despite processing nothing")
monkeypatch.setattr(vfs, "release_handles", explode)
vfs.Reliever(on_exit=False).finish()
def test_reliever_works_as_a_context_manager(monkeypatch):
calls = []
monkeypatch.setattr(vfs, "release_handles", lambda *a, **k: calls.append(1) or True)
with vfs.Reliever(interval=1000, on_exit=False) as reliever:
reliever.tick(3)
assert calls == []
assert len(calls) == 1
def test_context_manager_reclaims_even_when_the_body_raises(monkeypatch):
calls = []
monkeypatch.setattr(vfs, "release_handles", lambda *a, **k: calls.append(1) or True)
with pytest.raises(ValueError):
with vfs.Reliever(interval=1000, on_exit=False) as reliever:
reliever.tick()
raise ValueError("boom")
assert len(calls) == 1, "a failed job must still hand its handles back"
def test_no_pressure_is_applied_when_the_slab_is_already_small(monkeypatch):
"""Below the floor the slab is live processes' working set, not archive inodes.
Squeezing the machine to shave a few hundred megabytes off it costs far more
than it gains -- and during a long job this runs every RELIEF_INTERVAL items.
"""
monkeypatch.setattr(vfs, "drop_caches", lambda: False)
monkeypatch.setattr(vfs, "reclaimable_kb", lambda: 500 * 1024) # 0.5 GiB
def explode(*a, **k):
raise AssertionError("applied memory pressure below the floor")
monkeypatch.setattr(vfs, "bytearray", explode, raising=False)
assert vfs.release_handles() is True