testsAndMisc/linux_configuration/tests/test_usage_report_atop_build.py
Krzysztof kuhy Rudnicki b5dbe1e67a
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
usage_report: split _usage_report_parsing.py 425 -> 243 + 206
Lifts the atop subprocess machinery into _usage_report_atop.py: running
atop, streaming its parseable output, building/caching the atop_agg C
helper, and reading its TSV. What stays is record parsing, epoch windows
and per-day merging.

Verified by the 100%-coverage tests from the previous commit. An AST
comparison of all 16 functions against HEAD reports 16 identical, 0
mismatches.

_usage_report_render.py imported _run from the parsing module; it now
imports from the module that owns it. That is the same re-export barrel
the last split shipped and had to fix in a follow-up - caught in the same
commit this time. usage_report.py needed no edit at all.

Both modules hold 100% line+branch (94/28 and 130/34, 0 missed). 179
tests before and after; the five new test files collect 8+6+4+14+5 = 37,
which is last commit's 36 plus the one re-homed out of
test_usage_report_modes.py, where it referenced a constant that had
moved out from under it.

The two test files added last commit were themselves over cap (373, 272)
so they are split five ways here - the DropZone lesson from earlier
today, where a lint fix silently added an over-cap file.

Ran the program: a real report from today's logs, through the native
atop_agg path, which is precisely the code this commit moved.

Over-cap 39 -> 38.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 12:12:11 +02:00

135 lines
4.6 KiB
Python

"""Tests for usage_report's native atop_agg helper: when the cached binary is
reused, when it is rebuilt, and every way the build can fail into the
pure-Python fallback.
"""
from __future__ import annotations
import subprocess
from typing import TYPE_CHECKING
import _usage_report_atop as parsing
if TYPE_CHECKING:
from pathlib import Path
import pytest
def test_atop_agg_binary_uses_fresh_cache(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
"""A cached binary newer than the source is returned without rebuilding."""
src_dir = tmp_path / "src"
src_dir.mkdir()
src_c = src_dir / "atop_agg.c"
src_c.write_text("int main(void){return 0;}", encoding="utf-8")
cache = tmp_path / "cached"
cache.write_bytes(b"binary")
monkeypatch.setattr(parsing, "_ATOP_AGG_SRC_DIR", src_dir)
monkeypatch.setattr(parsing, "_ATOP_AGG_CACHE_BIN", cache)
def fail(*_a: object, **_k: object) -> None:
msg = "should not rebuild when the cache is fresh"
raise AssertionError(msg)
monkeypatch.setattr(parsing.subprocess, "run", fail)
assert parsing._atop_agg_binary() == cache
def test_atop_agg_binary_none_without_compiler(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
"""With a stale cache and no `cc`, the caller falls back to Python."""
src_dir = tmp_path / "src"
src_dir.mkdir()
(src_dir / "atop_agg.c").write_text("x", encoding="utf-8")
monkeypatch.setattr(parsing, "_ATOP_AGG_SRC_DIR", src_dir)
monkeypatch.setattr(parsing, "_ATOP_AGG_CACHE_BIN", tmp_path / "absent")
monkeypatch.setattr(parsing.shutil, "which", lambda _name: None)
assert parsing._atop_agg_binary() is None
def test_atop_agg_binary_none_when_build_fails(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
"""A failing `make` falls back rather than propagating the error."""
src_dir = tmp_path / "src"
src_dir.mkdir()
(src_dir / "atop_agg.c").write_text("x", encoding="utf-8")
monkeypatch.setattr(parsing, "_ATOP_AGG_SRC_DIR", src_dir)
monkeypatch.setattr(parsing, "_ATOP_AGG_CACHE_BIN", tmp_path / "cache" / "bin")
monkeypatch.setattr(parsing.shutil, "which", lambda _name: "/usr/bin/cc")
def boom(*_a: object, **_k: object) -> None:
raise subprocess.CalledProcessError(1, "make")
monkeypatch.setattr(parsing.subprocess, "run", boom)
assert parsing._atop_agg_binary() is None
def test_atop_agg_binary_none_when_make_produces_nothing(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
"""A `make` that exits 0 without writing the binary still falls back."""
src_dir = tmp_path / "src"
src_dir.mkdir()
(src_dir / "atop_agg.c").write_text("x", encoding="utf-8")
monkeypatch.setattr(parsing, "_ATOP_AGG_SRC_DIR", src_dir)
monkeypatch.setattr(parsing, "_ATOP_AGG_CACHE_BIN", tmp_path / "cache" / "bin")
monkeypatch.setattr(parsing.shutil, "which", lambda _name: "/usr/bin/cc")
monkeypatch.setattr(
parsing.subprocess,
"run",
lambda *_a, **_k: subprocess.CompletedProcess([], 0),
)
assert parsing._atop_agg_binary() is None
def test_atop_agg_binary_builds_and_caches(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
"""A successful build is copied into the cache and made executable."""
src_dir = tmp_path / "src"
src_dir.mkdir()
(src_dir / "atop_agg.c").write_text("x", encoding="utf-8")
cache = tmp_path / "cache" / "atop_agg"
monkeypatch.setattr(parsing, "_ATOP_AGG_SRC_DIR", src_dir)
monkeypatch.setattr(parsing, "_ATOP_AGG_CACHE_BIN", cache)
monkeypatch.setattr(parsing.shutil, "which", lambda _name: "/usr/bin/cc")
def fake_make(*_a: object, **_k: object) -> subprocess.CompletedProcess[bytes]:
(src_dir / "atop_agg").write_bytes(b"ELF")
return subprocess.CompletedProcess([], 0)
monkeypatch.setattr(parsing.subprocess, "run", fake_make)
result = parsing._atop_agg_binary()
assert result == cache
assert cache.read_bytes() == b"ELF"
assert cache.stat().st_mode & 0o777 == parsing._ATOP_AGG_BIN_MODE
def test_atop_agg_binary_missing_source_falls_back(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""A deleted C source tree yields None (Python fallback) even when a cached
binary exists - never trust an orphaned, unverifiable build."""
monkeypatch.setattr(parsing, "_ATOP_AGG_SRC_DIR", tmp_path / "gone")
cache = tmp_path / "atop_agg"
cache.write_text("stale binary", encoding="utf-8")
monkeypatch.setattr(parsing, "_ATOP_AGG_CACHE_BIN", cache)
assert parsing._atop_agg_binary() is None