mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-11-26 14:46:56 +00:00
- Replace jvm_filters with unified memory_filters (JVM + Redis helpers) - Add redis_maxmemory_mb filter and unit tests - Introduce sys-ctl-cln-docker role (systemd-based Docker prune + anon volumes) - Refactor disk space health check to Python script and wire SIZE_PERCENT_CLEANUP_DISC_SPACE - Adjust schedules and services for Docker cleanup and disk space health See discussion: https://chatgpt.com/share/6925c1c5-ee38-800f-84b6-da29ccfa7537
124 lines
5.0 KiB
Python
124 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
|
import io
|
|
import os
|
|
import sys
|
|
import unittest
|
|
import pathlib
|
|
import contextlib
|
|
import importlib.util
|
|
from types import SimpleNamespace
|
|
from unittest import mock
|
|
|
|
|
|
def load_target_module():
|
|
"""
|
|
Load the target script (roles/sys-ctl-hlth-disc-space/files/script.py)
|
|
via its file path so that dashes in the directory name are not an issue.
|
|
"""
|
|
# tests/unit/roles/sys-ctl-hlth-disc-space/files/script.py
|
|
test_file_path = pathlib.Path(__file__).resolve()
|
|
repo_root = test_file_path.parents[4] # go up: files -> ... -> unit -> tests -> <root>
|
|
|
|
script_path = repo_root / "roles" / "sys-ctl-hlth-disc-space" / "files" / "script.py"
|
|
if not script_path.is_file():
|
|
raise FileNotFoundError(f"Target script not found at: {script_path}")
|
|
|
|
spec = importlib.util.spec_from_file_location("disk_space_script", script_path)
|
|
module = importlib.util.module_from_spec(spec)
|
|
assert spec.loader is not None
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
# Load the module once for all tests
|
|
SCRIPT_MODULE = load_target_module()
|
|
|
|
|
|
class TestDiskSpaceScript(unittest.TestCase):
|
|
def test_get_disk_usage_percentages_parses_output(self):
|
|
"""
|
|
Ensure get_disk_usage_percentages parses 'df --output=pcent' correctly
|
|
and returns integer percentages without the '%' sign.
|
|
"""
|
|
# Fake df output, including header line and various spacings
|
|
fake_df_output = "Use%\n 10%\n 50%\n100%\n"
|
|
|
|
with mock.patch.object(
|
|
SCRIPT_MODULE.subprocess,
|
|
"run",
|
|
return_value=SimpleNamespace(stdout=fake_df_output, returncode=0),
|
|
):
|
|
result = SCRIPT_MODULE.get_disk_usage_percentages()
|
|
|
|
self.assertEqual(result, [10, 50, 100])
|
|
|
|
def test_main_exits_zero_when_below_threshold(self):
|
|
"""
|
|
If all filesystems are below or equal the threshold,
|
|
main() should exit with status code 0.
|
|
"""
|
|
# First call: 'df' (printing only) -> we don't care about stdout here
|
|
df_print_cp = SimpleNamespace(stdout="Filesystem ...\n", returncode=0)
|
|
# Second call: 'df --output=pcent'
|
|
df_pcent_cp = SimpleNamespace(stdout="Use%\n 10%\n 50%\n 80%\n", returncode=0)
|
|
|
|
def fake_run(args, capture_output=False, text=False, check=False):
|
|
# Decide which fake result to return based on the arguments
|
|
if args == ["df", "--output=pcent"]:
|
|
return df_pcent_cp
|
|
elif args == ["df"]:
|
|
return df_print_cp
|
|
else:
|
|
raise AssertionError(f"Unexpected subprocess.run args: {args}")
|
|
|
|
with mock.patch.object(SCRIPT_MODULE.subprocess, "run", side_effect=fake_run):
|
|
with mock.patch.object(sys, "argv", ["script.py", "80"]):
|
|
with mock.patch.object(SCRIPT_MODULE.sys, "exit", side_effect=SystemExit) as mock_exit:
|
|
# Capture stdout to avoid clutter in test output
|
|
with contextlib.redirect_stdout(io.StringIO()):
|
|
with self.assertRaises(SystemExit):
|
|
SCRIPT_MODULE.main()
|
|
|
|
# Expect no filesystem above 80% -> exit code 0
|
|
mock_exit.assert_called_once_with(0)
|
|
|
|
def test_main_exits_with_error_count_and_prints_warnings(self):
|
|
"""
|
|
If some filesystems exceed the threshold, main() should:
|
|
- Print a warning for each filesystem that exceeds it
|
|
- Exit with a status code equal to the number of such filesystems
|
|
"""
|
|
df_print_cp = SimpleNamespace(stdout="Filesystem ...\n", returncode=0)
|
|
# Two filesystems above threshold (90%, 95%), one below (60%)
|
|
df_pcent_cp = SimpleNamespace(stdout="Use%\n 60%\n 90%\n 95%\n", returncode=0)
|
|
|
|
def fake_run(args, capture_output=False, text=False, check=False):
|
|
if args == ["df", "--output=pcent"]:
|
|
return df_pcent_cp
|
|
elif args == ["df"]:
|
|
return df_print_cp
|
|
else:
|
|
raise AssertionError(f"Unexpected subprocess.run args: {args}")
|
|
|
|
with mock.patch.object(SCRIPT_MODULE.subprocess, "run", side_effect=fake_run):
|
|
with mock.patch.object(sys, "argv", ["script.py", "80"]):
|
|
with mock.patch.object(SCRIPT_MODULE.sys, "exit", side_effect=SystemExit) as mock_exit:
|
|
buffer = io.StringIO()
|
|
with contextlib.redirect_stdout(buffer):
|
|
with self.assertRaises(SystemExit):
|
|
SCRIPT_MODULE.main()
|
|
|
|
# Expect exit code 2 (two filesystems over 80%)
|
|
mock_exit.assert_called_once_with(2)
|
|
|
|
output = buffer.getvalue()
|
|
self.assertIn("Checking disk space usage...", output)
|
|
self.assertIn("WARNING: 90% exceeds the limit of 80%.", output)
|
|
self.assertIn("WARNING: 95% exceeds the limit of 80%.", output)
|
|
# Ensure the "below threshold" value does not produce a warning
|
|
self.assertNotIn("60% exceeds the limit of 80%.", output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|