nas-burnin/tests/test_badblocks_cmd.py
Brandon Walter d38807f957
Some checks are pending
Security scan / pip-audit (push) Waiting to run
Security scan / bandit (push) Waiting to run
Security scan / gitleaks (push) Waiting to run
Security scan / mypy (push) Waiting to run
test: cover Spearfoot tunables in badblocks command
Extracts the badblocks shell-command construction into
_build_badblocks_cmd(devname) so it can be unit-tested without
spinning up an asyncssh connection. Behavior unchanged.

Three tests guard:
1. Defaults match disk-burnin.sh recommendation (-b 4096 -c 64 -p 1)
2. Operator-set tunables actually propagate to the command
3. The PID-capture wrapper (sh -c 'echo PID:\$\$; exec ...') stays
   intact — without it, cancel cannot kill the remote process
   because asyncssh's signal channel is silently ignored by sshd.
2026-05-03 21:24:10 -07:00

77 lines
2.9 KiB
Python

"""Verifies the Spearfoot tunables (block_size, block_buffer, passes)
actually thread through to the badblocks command line.
These three settings are exposed in Settings → Burn-in. Without a test,
nothing catches if a future refactor drops one of the flags or reads
from the wrong attribute. The defaults match the Spearfoot disk-burnin.sh
community script; non-defaults can roughly halve runtime on multi-TB
drives at the cost of more RAM.
Run inside the container image so app deps are present.
"""
from __future__ import annotations
import unittest
from app.burnin.stages import _build_badblocks_cmd
from app.config import settings
class TestBadblocksCmd(unittest.TestCase):
def setUp(self):
# Snapshot defaults so each test can mutate freely without
# polluting siblings or the running process.
self._snap = (
settings.surface_validate_block_size,
settings.surface_validate_block_buffer,
settings.surface_validate_passes,
)
def tearDown(self):
(
settings.surface_validate_block_size,
settings.surface_validate_block_buffer,
settings.surface_validate_passes,
) = self._snap
def test_defaults_match_spearfoot(self):
"""Out of the box: -b 4096 -c 64 -p 1 — matches the
disk-burnin.sh community script's recommendation for HDDs."""
cmd = _build_badblocks_cmd("sda")
self.assertIn("-b 4096", cmd)
self.assertIn("-c 64", cmd)
self.assertIn("-p 1", cmd)
self.assertIn("/dev/sda", cmd)
# Destructive write+verify mode must always be present — anything
# else (read-only, non-destructive) defeats the purpose of burn-in.
self.assertIn("-wsv", cmd)
def test_tunables_propagate_to_cmd(self):
"""Operator-set values (e.g. for paranoid 3-pass burn-in on a
suspect drive, or 8 KiB blocks for faster scan on a 24 TB HDD)
must end up in the shell command."""
settings.surface_validate_block_size = 8192
settings.surface_validate_block_buffer = 128
settings.surface_validate_passes = 3
cmd = _build_badblocks_cmd("sdb")
self.assertIn("-b 8192", cmd)
self.assertIn("-c 128", cmd)
self.assertIn("-p 3", cmd)
self.assertNotIn("-b 4096", cmd) # no leak from defaults
self.assertNotIn("-c 64", cmd)
self.assertIn("/dev/sdb", cmd)
def test_pid_capture_wrapper_intact(self):
"""The `sh -c 'echo PID:$$; exec ...'` wrapper is what makes
out-of-band kill -9 work over a fresh SSH session — asyncssh's
signal channel is silently ignored by sshd. If a future refactor
drops the wrapper, a cancel won't actually stop the test."""
cmd = _build_badblocks_cmd("sda")
self.assertTrue(cmd.startswith("sh -c 'echo PID:$$; exec badblocks"))
self.assertTrue(cmd.endswith("'"))
if __name__ == "__main__":
unittest.main()