NumericalOS

tests/test_bootstrap_arch.py

back to source

import os
import subprocess
import tempfile
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 TestStaticCapability(unittest.TestCase):
    """numos_arch_has_static reports CAPABILITY, not availability.

    It is derived from whether the ArchTarget carries a cross-compilation
    triple. Whether a binary was actually published is a question only the
    manifest can answer - see numos_resolve_init and test_bootstrap_chain.
    Conflating the two made the bootstrap announce a static binary and then
    fetch the shell script.
    """

    def test_arch_with_a_cross_compilation_triple_reports_capable(self):
        code, _, _ = sh("numos_arch_has_static x86_64")
        self.assertEqual(code, 0)

    def test_arch_without_a_triple_reports_incapable(self):
        code, _, _ = sh("numos_arch_has_static loongarch64")
        self.assertNotEqual(code, 0)


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):
        # Comments are excluded: the guard is about what the shell executes,
        # and matching prose meant the word "local" could never appear in an
        # explanation. It did, in a comment about preferring the local copy
        # over the network, and this test failed on the sentence rather than
        # on any code.
        with open(os.path.join(ROOT, "boot", "bootstrap.sh")) as handle:
            code_lines = [
                line for line in handle.read().split("\n")
                if not line.lstrip().startswith("#")
            ]
        body = "\n".join(code_lines)
        for bashism in ("[[", "declare ", "local ", "${!", "function "):
            self.assertNotIn(bashism, body,
                             "bashism %r found in executable code" % bashism)

    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.pop("NUMOS_STATE", None)
        merged["NUMOS_FAKE_UNAME_M"] = "amd64"
        # Confine and stop the run right after arch resolution. The default
        # prefix is /opt/numericalos, which this test must not try to create,
        # and offline mode makes the halt deterministic instead of depending
        # on whether the machine has a network.
        merged["NUMOS_PREFIX"] = tempfile.mkdtemp()
        merged["NUMOS_OFFLINE"] = "1"

        # 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
        )

        # The assertion is about the arch table having been FOUND. If the
        # default NUMOS_LIB expansion were wrong, sourcing arch_table.sh would
        # fail and this line would never be printed. What the chain does after
        # that (halt, offline, no manifest) is another test's business.
        self.assertIn("numos: arch x86_64", proc.stdout,
                      "default NUMOS_LIB expansion did not locate arch_table.sh; "
                      "stdout=%r stderr=%r" % (proc.stdout, proc.stderr))


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