NumericalOS

tests/test_bootstrap_arch.py

back to source

import os
import subprocess
import unittest

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BOOTSTRAP = os.path.join(ROOT, "boot", "bootstrap.sh").replace("\\", "/")


def sh(snippet, env=None):
    """Source bootstrap.sh with main suppressed, then run snippet."""
    full = 'NUMOS_SOURCE_ONLY=1 . "%s"; %s' % (BOOTSTRAP, snippet)
    merged = dict(os.environ)
    merged["NUMOS_SOURCE_ONLY"] = "1"
    merged["NUMOS_LIB"] = os.path.dirname(BOOTSTRAP) + "/lib"
    if env:
        merged.update(env)
    proc = subprocess.run(["bash", "-c", full], capture_output=True, text=True, env=merged)
    return proc.returncode, proc.stdout.strip(), proc.stderr.strip()


class TestArchResolution(unittest.TestCase):
    def test_amd64_resolves_to_x86_64(self):
        code, out, _ = sh("numos_detect_arch", {"NUMOS_FAKE_UNAME_M": "amd64"})
        self.assertEqual(code, 0)
        self.assertEqual(out, "x86_64")

    def test_arm64_resolves_to_aarch64(self):
        _, out, _ = sh("numos_detect_arch", {"NUMOS_FAKE_UNAME_M": "arm64"})
        self.assertEqual(out, "aarch64")

    def test_armv7l_resolves_to_arm(self):
        _, out, _ = sh("numos_detect_arch", {"NUMOS_FAKE_UNAME_M": "armv7l"})
        self.assertEqual(out, "arm")

    def test_riscv64_resolves_to_itself(self):
        _, out, _ = sh("numos_detect_arch", {"NUMOS_FAKE_UNAME_M": "riscv64"})
        self.assertEqual(out, "riscv64")

    def test_unknown_arch_halts_with_named_reason(self):
        code, _, err = sh("numos_detect_arch", {"NUMOS_FAKE_UNAME_M": "vax"})
        self.assertNotEqual(code, 0)
        self.assertIn("numos: HALT:", err)
        self.assertIn("vax", err)


class TestArtifactSelection(unittest.TestCase):
    def test_arch_with_static_build_selects_static(self):
        _, out, _ = sh("numos_select_artifact x86_64")
        self.assertEqual(out, "static numinit-x86_64")

    def test_arch_without_static_build_falls_back_to_busybox(self):
        _, out, _ = sh("numos_select_artifact loongarch64")
        self.assertEqual(out, "fallback busybox-static-loongarch64")


class TestSourcingDiscipline(unittest.TestCase):
    def test_sourcing_does_not_run_main(self):
        code, out, _ = sh("echo SOURCED_CLEAN")
        self.assertEqual(code, 0)
        self.assertIn("SOURCED_CLEAN", out)

    def test_script_has_no_bashisms(self):
        with open(os.path.join(ROOT, "boot", "bootstrap.sh")) as handle:
            body = handle.read()
        for bashism in ("[[", "declare ", "local ", "${!", "function "):
            self.assertNotIn(bashism, body)

    def test_default_numos_lib_expansion_via_dirname(self):
        """Test that the default NUMOS_LIB expansion (via dirname $0) works when executed directly.

        This exercises the line: NUMOS_LIB="${NUMOS_LIB:-$(dirname "$0")/lib}"
        When the script is executed directly, $0 is the script path, so dirname yields the boot directory.
        This test ensures the arch table is found via the default path, not via env override.
        """
        # Create environment without NUMOS_LIB (ensure we test the default expansion, not env override)
        merged = dict(os.environ)
        merged.pop("NUMOS_LIB", None)
        merged["NUMOS_FAKE_UNAME_M"] = "amd64"

        # Execute the script directly (not sourced) from repo root
        # When $0 is the full path to bootstrap.sh, dirname "$0" yields the boot directory
        proc = subprocess.run(
            ["bash", BOOTSTRAP],
            capture_output=True,
            text=True,
            env=merged,
            cwd=ROOT
        )

        # Script should succeed and report x86_64 as the detected arch
        self.assertEqual(proc.returncode, 0,
                        f"Script failed: {proc.stderr}")
        self.assertIn("x86_64", proc.stdout,
                     f"Expected 'x86_64' in output but got: {proc.stdout}")


if __name__ == "__main__":
    unittest.main()