aboutsummaryrefslogtreecommitdiff
path: root/tests/installer-steps/test_check_disk_space.py
blob: deec6d9b5ad9cf93c5c9a6ca623990e04b600a1f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
"""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()