"""Test the disk-space pre-flight gate. Two bugs the extracted check_disk_space fixes: 1. `df /` wraps to two lines when the source device name is long (common on a live ISO / device-mapper root). The old `df / | awk 'NR==2 {print $4}'` then reads the device-name line, gets an empty $4, and wrongly aborts on a disk with plenty of space. `df -P /` forces POSIX single-line output. 2. The old code truncated KB -> GB with integer division before comparing, biasing the gate against the user near the threshold. The check now compares available KB against the minimum in KB directly. Method: sed-extract check_disk_space from the real `archsetup`, run it with a fake df (controlled output, and wrapped output for the plain-df regression) and assert the gate passes/aborts correctly. Run from repo root: python3 -m unittest tests.installer-steps.test_check_disk_space """ import os import subprocess import textwrap import unittest REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) ARCHSETUP = os.path.join(REPO_ROOT, "archsetup") # Fake df: single, correct POSIX line for `df -P`; a WRAPPED two-line body for # plain `df`. If check_disk_space ever reverts to plain `df`, the wrapped body # yields an empty Available field and the ample-space test fails -- that is the # regression guard for bug 1. FAKE_DF = textwrap.dedent("""\ df() {{ if [ "$1" = "-P" ]; then printf '%s\\n' "Filesystem 1024-blocks Used Available Capacity Mounted on" printf '%s\\n' "/dev/mapper/root {avail} 100 {avail} 1% /" else printf '%s\\n' "Filesystem 1024-blocks Used Available Capacity Mounted on" printf '%s\\n' "/dev/mapper/a-very-long-device-mapper-name-that-wraps" printf '%s\\n' " {avail} 100 {avail} 1% /" fi }} """) def run_check(available_kb, min_gb=20): """Run check_disk_space with fake df reporting available_kb free.""" script = textwrap.dedent(f"""\ min_disk_space_gb={min_gb} {FAKE_DF.format(avail=available_kb)} source <(sed -n '/^check_disk_space() {{/,/^}}/p' "{ARCHSETUP}") check_disk_space echo "REACHED-END" """) return subprocess.run( ["bash", "-c", script], capture_output=True, text=True, timeout=10, ) class CheckDiskSpace(unittest.TestCase): # min 20GB -> 20 * 1024 * 1024 = 20971520 KB MIN_KB = 20 * 1024 * 1024 def test_ample_space_passes(self): # ~95GB free; also the wrapped-df regression guard (plain df would # yield an empty Available and wrongly abort). r = run_check(99000000) self.assertIn("REACHED-END", r.stdout) self.assertIn("[OK] Disk space", r.stdout) def test_insufficient_space_aborts(self): r = run_check(5 * 1024 * 1024) # 5GB self.assertIn("Insufficient disk space", r.stdout) self.assertNotIn("REACHED-END", r.stdout) def test_boundary_exact_minimum_passes(self): r = run_check(self.MIN_KB) # exactly 20GB in KB self.assertIn("REACHED-END", r.stdout) def test_boundary_one_kb_under_minimum_aborts(self): r = run_check(self.MIN_KB - 1) self.assertIn("Insufficient disk space", r.stdout) self.assertNotIn("REACHED-END", r.stdout) def test_empty_df_output_aborts(self): # df field unparseable -> treat as zero, abort rather than crash. r = run_check("") self.assertIn("Insufficient disk space", r.stdout) self.assertNotIn("REACHED-END", r.stdout) if __name__ == "__main__": unittest.main()