Files
computer-playbook/tests/unit/python/scripts/meta/env/test_load.py
T
kevinveenbirkenbachandClaude Opus 5.5 1079f48feb fix(env): derive the image and container names from the distro under test
A compose deploy job labelled centos or arch built its stack image from
pkgmgr-debian and ran it as infinito_nexus_debian (#794). distros.sh
exports INFINITO_DISTRO per iteration and sources load.sh again, but the
loader returned early because INFINITO_ENV_LOADED and the .env mtime were
unchanged. A full reload would not have helped either: the debian values
were already exported, so the restore step kept them as caller intent.

load.sh now records the distro it loaded for. Another INFINITO_DISTRO
defeats the early return and drops INFINITO_CONTAINER,
INFINITO_PARENT_IMAGE and INFINITO_IMAGE, the three keys the env handlers
derive from the distro. When the requested distro also differs from the
one .env was generated for, the loader sources a set that
`python -m cli.meta.env --output` generates for it in a private temp
directory and leaves .env untouched, so a local multi-distro run does not
switch the checkout's distro. A caller value set for the loaded distro
still wins.

Verified: tests/unit/python/scripts/meta/env/test_load.py and the new
distros.sh case pass. Against main's loader both fail with the symptom of
the issue: INFINITO_DISTRO=centos next to infinito_nexus_debian and
pkgmgr-debian:stable, and distros.sh handing debian, arch and centos the
same debian parent. shellcheck is clean.

Consequence: CI jobs labelled arch, ubuntu, fedora or centos now build
and deploy that distro, so a failure that shows up there was masked
before.

feature/store-i18n carries 3f973eb137, which resolves the parent image
for scripts/image/push.sh on its own, and 05e4dbafcb, which inserts the
INFINITO_INSTANCE slot check into the block this commit restructures. The
merge keeps the slot check ahead of the distro check.

Closes #794

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 21:30:48 +02:00

132 lines
4.3 KiB
Python

"""The distro-derived keys follow the distro the caller asks for.
``scripts/tests/deploy/distros.sh`` exports ``INFINITO_DISTRO`` per iteration
and sources the loader again. The keys composed from the distro must then name
that distro, not the one ``.env`` was generated for, and ``.env`` itself must
stay as it was.
"""
from __future__ import annotations
import os
import shutil
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
from typing import ClassVar
from utils.cache.files import PROJECT_ROOT
LOADER_DIR = Path(PROJECT_ROOT) / "scripts" / "meta" / "env"
SHOWN = (
"INFINITO_DISTRO",
"INFINITO_CONTAINER",
"INFINITO_PARENT_IMAGE",
"INFINITO_IMAGE",
)
SHOW = "printf '%s\\n' " + " ".join(f'"{key}=${{{key}:-}}"' for key in SHOWN)
def clean_env(**overrides: str) -> dict[str, str]:
env = {
k: v
for k, v in os.environ.items()
if not k.startswith("INFINITO_")
and k not in ("BASH_ENV", "PYTHONPATH", "VENV", "PYTHON", "VIRTUAL_ENV")
}
env["PYTHONPATH"] = str(PROJECT_ROOT)
env.update(overrides)
return env
def bash(script: str, cwd: Path) -> dict[str, str]:
proc = subprocess.run(
["bash", "-c", f"{script}\n{SHOW}"],
env=clean_env(),
cwd=cwd,
capture_output=True,
text=True,
check=True,
)
return dict(line.split("=", 1) for line in proc.stdout.splitlines())
class TestDistroDerivedKeys(unittest.TestCase):
scratch: ClassVar[Path]
expected: ClassVar[dict[str, dict[str, str]]]
@classmethod
def setUpClass(cls) -> None:
cls.scratch = Path(tempfile.mkdtemp())
cls.expected = {}
for distro in ("debian", "centos"):
path = cls.scratch / f"{distro}.env"
subprocess.run(
[sys.executable, "-m", "cli.meta.env", "--output", str(path)],
env=clean_env(INFINITO_DISTRO=distro),
cwd=PROJECT_ROOT,
capture_output=True,
check=True,
)
cls.expected[distro] = bash(f'set -a; source "{path}"', cls.scratch)
@classmethod
def tearDownClass(cls) -> None:
shutil.rmtree(cls.scratch)
def run_loader(self, steps: str) -> dict[str, str]:
"""Source a copy of the loader in a checkout whose ``.env`` is debian's.
Args:
steps: bash run before the keys are shown; ``load`` sources the
loader.
Returns:
The keys as the steps leave them.
"""
checkout = Path(tempfile.mkdtemp())
self.addCleanup(shutil.rmtree, checkout)
(checkout / "scripts" / "meta" / "env").mkdir(parents=True)
for name in ("load.sh", "python.sh"):
shutil.copy(LOADER_DIR / name, checkout / "scripts" / "meta" / "env")
dotenv = checkout / ".env"
shutil.copy(self.scratch / "debian.env", dotenv)
before = dotenv.read_bytes()
shown = bash(f"load() {{ source scripts/meta/env/load.sh; }}\n{steps}", checkout)
self.assertEqual(dotenv.read_bytes(), before, ".env was rewritten")
return shown
def test_the_two_distros_derive_different_keys(self):
self.assertNotEqual(
self.expected["debian"]["INFINITO_PARENT_IMAGE"],
self.expected["centos"]["INFINITO_PARENT_IMAGE"],
)
def test_a_distro_exported_after_the_first_load_gets_its_own_keys(self):
shown = self.run_loader("load\nexport INFINITO_DISTRO=centos\nload")
self.assertEqual(shown, self.expected["centos"])
def test_a_distro_exported_before_the_first_load_gets_its_own_keys(self):
shown = self.run_loader("export INFINITO_DISTRO=centos\nload")
self.assertEqual(shown, self.expected["centos"])
def test_switching_back_restores_the_keys_of_the_dotenv(self):
shown = self.run_loader(
"load\nexport INFINITO_DISTRO=centos\nload\n"
"export INFINITO_DISTRO=debian\nload"
)
self.assertEqual(shown, self.expected["debian"])
def test_a_caller_value_for_the_loaded_distro_still_wins(self):
shown = self.run_loader(
"load\nexport INFINITO_PARENT_IMAGE=caller/image:pinned\n"
"touch -d '+1 minute' .env\nload"
)
self.assertEqual(shown["INFINITO_PARENT_IMAGE"], "caller/image:pinned")
if __name__ == "__main__":
unittest.main()