aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xarchsetup33
-rw-r--r--tests/installer-steps/test_check_disk_space.py94
2 files changed, 118 insertions, 9 deletions
diff --git a/archsetup b/archsetup
index cefd9f2..ab2deb0 100755
--- a/archsetup
+++ b/archsetup
@@ -480,20 +480,35 @@ nvidia_preflight_report() {
return 11
}
-preflight_checks() {
- echo "Running pre-flight checks..."
-
- # Check disk space (need at least 20GB free on root partition)
- available_kb=$(df / | awk 'NR==2 {print $4}')
- available_gb=$((available_kb / 1024 / 1024))
- if [ "$available_gb" -lt "$min_disk_space_gb" ]; then
+check_disk_space() {
+ # df -P forces POSIX single-line output; plain `df` wraps to two lines when
+ # the source device name is long (live ISO / device-mapper root), which
+ # made NR==2 read the device line and $4 come back empty. Compare in KB
+ # directly rather than truncating to GB first, which biased the gate
+ # against the user near the threshold.
+ local available_kb min_kb
+ available_kb=$(df -P / | awk 'NR==2 {print $4}')
+ # Treat empty or non-numeric output (df failed / malformed) as zero so the
+ # gate aborts loudly instead of crashing the arithmetic comparison.
+ case "$available_kb" in
+ ''|*[!0-9]*) available_kb=0 ;;
+ esac
+ min_kb=$((min_disk_space_gb * 1024 * 1024))
+ if [ "$available_kb" -lt "$min_kb" ]; then
echo "ERROR: Insufficient disk space"
echo " Required: ${min_disk_space_gb}GB"
- echo " Available: ${available_gb}GB"
+ echo " Available: $((available_kb / 1024 / 1024))GB"
echo " Free up disk space before running archsetup."
exit 1
fi
- echo " [OK] Disk space: ${available_gb}GB available"
+ echo " [OK] Disk space: $((available_kb / 1024 / 1024))GB available"
+}
+
+preflight_checks() {
+ echo "Running pre-flight checks..."
+
+ # Check disk space (need at least 20GB free on root partition)
+ check_disk_space
# Check network connectivity
if ! ping -c 1 -W 5 archlinux.org > /dev/null 2>&1; then
diff --git a/tests/installer-steps/test_check_disk_space.py b/tests/installer-steps/test_check_disk_space.py
new file mode 100644
index 0000000..deec6d9
--- /dev/null
+++ b/tests/installer-steps/test_check_disk_space.py
@@ -0,0 +1,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()