mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 20:07:04 +01:00
Verified by the 100%-coverage tests from the previous commit, written before this seam existed. _cpu_table/_dedupe_ram/_ram_table/_gpu_table move to _usage_report_tables.py with the two constants they own. _md_escape and _fmt_h move to _usage_report_format.py: both the tables module and the render module need them, so a third module is what avoids a cycle. Import sites name the owning module; no re-export barrel. usage_report.py needed no edit - it imports _fmt_h and _render_report from the render module, and both names still resolve there. Evidence the move was verbatim: an AST comparison of all 10 functions against HEAD reports 10 identical, 0 mismatches. That matters because I retyped rather than cut-and-pasted, and did briefly introduce a literal × where the original had × - ruff's RUF001 caught it. Coverage stays 100% line+branch on all three (15/55/54 stmts, 4/10/14 branches, 0 missed). Test count 43 before and after, summed across the five files it now lives in - the check that matters for a test split, since a file outside the runner's glob is silently never collected. Directory total 143 passed, unchanged. Ran the real program: a full report from today's atop + pmon logs, every section rendering through the new boundaries including the RAM sibling dedupe and its (+20 more) truncation. Over-cap 41 -> 39. No suppressions; a shared test-helper module was tried and abandoned because name-tests-test would have needed an exclusion. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
151 lines
4.7 KiB
Python
151 lines
4.7 KiB
Python
"""Tests for usage_report's host fingerprint: the /proc and nvidia-smi probes
|
|
behind _host_profile, and the Markdown bullets _fingerprint_section builds.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
from typing import TYPE_CHECKING
|
|
|
|
import _usage_report_render as render
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
def _fake_proc_files(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
contents: dict[str, str | OSError],
|
|
) -> None:
|
|
"""Route Path.open for /proc/cpuinfo and /proc/meminfo to canned text.
|
|
|
|
_host_profile only ever opens those two paths, so every caller supplies
|
|
both and there is no real-filesystem fallback to keep here.
|
|
"""
|
|
|
|
def fake_open(
|
|
self: Path,
|
|
*_args: object,
|
|
**_kwargs: object,
|
|
) -> io.StringIO:
|
|
value = contents[str(self)]
|
|
if isinstance(value, OSError):
|
|
raise value
|
|
return io.StringIO(value)
|
|
|
|
monkeypatch.setattr(render.Path, "open", fake_open)
|
|
|
|
|
|
def _stub_platform(monkeypatch: pytest.MonkeyPatch, *, cpus: int | None) -> None:
|
|
"""Pin hostname, kernel and CPU count so assertions are machine-agnostic."""
|
|
monkeypatch.setattr(render.platform, "node", lambda: "box")
|
|
monkeypatch.setattr(render.platform, "release", lambda: "6.1.0")
|
|
monkeypatch.setattr(render.os, "cpu_count", lambda: cpus)
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# _host_profile
|
|
# --------------------------------------------------------------------------- #
|
|
def test_host_profile_reads_cpu_model_and_memory(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""CPU model and total memory are lifted out of the /proc files."""
|
|
_stub_platform(monkeypatch, cpus=8)
|
|
monkeypatch.setattr(render, "_run", lambda _cmd: "")
|
|
_fake_proc_files(
|
|
monkeypatch,
|
|
{
|
|
"/proc/cpuinfo": "flags\t: x\nmodel name\t: Ryzen 9\nmodel name\t: other\n",
|
|
"/proc/meminfo": "SwapTotal: 1 kB\nMemTotal: 32768000 kB\n",
|
|
},
|
|
)
|
|
|
|
info = render._host_profile()
|
|
|
|
assert info["hostname"] == "box"
|
|
assert info["kernel"] == "6.1.0"
|
|
assert info["cpus_online"] == "8"
|
|
assert info["cpu_model"] == "Ryzen 9"
|
|
assert info["memory_total_gib"] == "31.2"
|
|
assert "gpu" not in info
|
|
|
|
|
|
def test_host_profile_includes_gpu_when_nvidia_smi_answers(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""Multi-line nvidia-smi output is flattened onto one line."""
|
|
_stub_platform(monkeypatch, cpus=1)
|
|
monkeypatch.setattr(render, "_run", lambda _cmd: "GPU0, 8 GiB\nGPU1, 8 GiB\n")
|
|
_fake_proc_files(monkeypatch, {"/proc/cpuinfo": "", "/proc/meminfo": ""})
|
|
|
|
assert render._host_profile()["gpu"] == "GPU0, 8 GiB; GPU1, 8 GiB"
|
|
|
|
|
|
def test_host_profile_survives_unreadable_proc_files(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""An OSError on either /proc file leaves that key absent, not raising."""
|
|
_stub_platform(monkeypatch, cpus=1)
|
|
monkeypatch.setattr(render, "_run", lambda _cmd: "")
|
|
_fake_proc_files(
|
|
monkeypatch,
|
|
{
|
|
"/proc/cpuinfo": OSError("nope"),
|
|
"/proc/meminfo": OSError("nope"),
|
|
},
|
|
)
|
|
|
|
info = render._host_profile()
|
|
|
|
assert "cpu_model" not in info
|
|
assert "memory_total_gib" not in info
|
|
|
|
|
|
def test_host_profile_survives_meminfo_without_digits(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""A MemTotal line with no parseable number is skipped, not fatal."""
|
|
_stub_platform(monkeypatch, cpus=1)
|
|
monkeypatch.setattr(render, "_run", lambda _cmd: "")
|
|
_fake_proc_files(
|
|
monkeypatch,
|
|
{"/proc/cpuinfo": "", "/proc/meminfo": "MemTotal: kB\n"},
|
|
)
|
|
|
|
assert "memory_total_gib" not in render._host_profile()
|
|
|
|
|
|
def test_host_profile_defaults_cpu_count_to_zero(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""os.cpu_count() returning None renders as '0' rather than 'None'."""
|
|
_stub_platform(monkeypatch, cpus=None)
|
|
monkeypatch.setattr(render, "_run", lambda _cmd: "")
|
|
_fake_proc_files(monkeypatch, {"/proc/cpuinfo": "", "/proc/meminfo": ""})
|
|
|
|
assert render._host_profile()["cpus_online"] == "0"
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# _fingerprint_section
|
|
# --------------------------------------------------------------------------- #
|
|
def test_fingerprint_section_renders_each_fact_as_a_bullet(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""Every host-profile key becomes one bold Markdown bullet."""
|
|
monkeypatch.setattr(
|
|
render,
|
|
"_host_profile",
|
|
lambda: {"hostname": "box", "kernel": "6"},
|
|
)
|
|
|
|
assert render._fingerprint_section() == [
|
|
"## Host",
|
|
"",
|
|
"- **hostname**: box",
|
|
"- **kernel**: 6",
|
|
"",
|
|
]
|