Une image cloud Arch est nue : ni bash-completion, ni accès à l'AUR. Les deux arrivent avec l'amorçage, sur la seule branche pacman. yay-bin plutôt que yay, dont le paquet source compile Go pour le même outil ; la construction reste sous l'utilisateur de la VM, makepkg refusant root. Le « || true » qui ferme le bloc porte : le groupe est le dernier membre de sa liste « || », donc set -e s'y applique et un sudo en échec emporterait l'installation entière. Le guide de connexion n'annonce yay que si une installation a eu lieu. Vérifié : 10 tests, dont « bash -n » sur la commande distante entière et la survie du bloc sous set -e avec un PATH vide. --- EN --- An Arch cloud image is bare: no bash-completion, no AUR access. Both come with the bootstrap, on the pacman branch alone. yay-bin rather than yay, whose source package compiles Go for the same tool; the build stays under the VM user, makepkg refusing root. The « || true » closing the block carries weight: the group is the last member of its « || » list, so set -e applies inside it and one failing sudo would take the whole install down. The login guide announces yay only when an install ran. Checked: 10 tests, among them « bash -n » over the whole remote command and the block surviving set -e with an empty PATH. Assisted-by: Claude Opus 5
154 lines
5.4 KiB
Python
154 lines
5.4 KiB
Python
#!/usr/bin/env python3
|
|
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
"""L'invité Arch : yay et bash-completion, puis leur trace dans l'accueil.
|
|
|
|
Une image cloud Arch est nue : ni bash-completion, ni accès à l'AUR. Les deux
|
|
s'ajoutent à l'amorçage d'installation, sur la seule branche pacman.
|
|
|
|
Ce que ces tests gardent :
|
|
|
|
- makepkg REFUSE de construire en root : la construction ne passe jamais par
|
|
sudo, et un « sudo makepkg » repasserait le test au rouge.
|
|
- yay est un bonus : sous « set -e », son échec ne doit pas emporter une
|
|
installation par ailleurs complète.
|
|
- L'accueil de session n'annonce yay que sur une VM qui l'aura vraiment —
|
|
la règle déjà tenue par le bloc ERPLibre, appliquée au même signal.
|
|
"""
|
|
|
|
import importlib.util
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
sys.argv = ["todo.py"]
|
|
from script.todo.todo import TODO # noqa: E402
|
|
|
|
RACINE = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def _deploy_qemu():
|
|
"""deploy_qemu.py chargé comme module, comme le fait todo.py."""
|
|
path = RACINE / "script/qemu/deploy_qemu.py"
|
|
spec = importlib.util.spec_from_file_location("deploy_qemu", path)
|
|
mod = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(mod)
|
|
return mod
|
|
|
|
|
|
DQ = _deploy_qemu()
|
|
|
|
|
|
class ArchGuestBootstrap(unittest.TestCase):
|
|
def setUp(self):
|
|
self.todo = TODO.__new__(TODO)
|
|
self.cmd = self.todo._qemu_erplibre_remote_cmd(
|
|
"develop", None, False, False, "", "deb", ()
|
|
)
|
|
|
|
def test_bash_completion_rides_the_pacman_branch(self):
|
|
self.assertIn("bash-completion", self.cmd)
|
|
# Sur la branche pacman et nulle part ailleurs : les images Debian et
|
|
# Fedora l'embarquent déjà, l'ajouter là serait du bruit.
|
|
pacman = self.cmd.index("command -v pacman")
|
|
zypper = self.cmd.index("command -v zypper", pacman)
|
|
self.assertIn("bash-completion", self.cmd[pacman:zypper])
|
|
|
|
def test_yay_is_installed_from_the_prebuilt_package(self):
|
|
"""yay-bin plutôt que yay : le paquet source compile Go, et le
|
|
compilateur avec, pour le même outil."""
|
|
self.assertIn("aur.archlinux.org/yay-bin.git", self.cmd)
|
|
|
|
def test_makepkg_never_runs_under_sudo(self):
|
|
"""makepkg sort en erreur sous root : « running makepkg as root is
|
|
not allowed ». La construction reste sous l'utilisateur de la VM."""
|
|
yay = self.todo._qemu_yay_install_cmd()
|
|
self.assertIn("makepkg -si --noconfirm", yay)
|
|
self.assertNotIn("sudo makepkg", yay)
|
|
|
|
def test_a_failing_aur_does_not_break_the_chain(self):
|
|
"""Sous « set -e », un groupe qui échoue arrête tout. Le bloc doit
|
|
rendre 0 même sans réseau, sans quoi une VM par ailleurs installée
|
|
serait comptée en échec."""
|
|
yay = self.todo._qemu_yay_install_cmd()
|
|
script = "set -e\nPATH=/nonexistent\n" + yay + "\necho SURVECU"
|
|
with tempfile.NamedTemporaryFile(
|
|
"w", suffix=".sh", delete=False
|
|
) as fh:
|
|
fh.write(script)
|
|
chemin = fh.name
|
|
res = subprocess.run(
|
|
["bash", chemin], capture_output=True, text=True, timeout=60
|
|
)
|
|
self.assertEqual(res.returncode, 0, res.stderr)
|
|
self.assertIn("SURVECU", res.stdout)
|
|
|
|
def test_the_whole_remote_command_is_valid_shell(self):
|
|
"""Une erreur de syntaxe ne se verrait qu'une fois la VM déployée."""
|
|
res = subprocess.run(
|
|
["bash", "-n"],
|
|
input=self.cmd,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=60,
|
|
)
|
|
self.assertEqual(res.returncode, 0, res.stderr)
|
|
|
|
|
|
class AurInTheMotd(unittest.TestCase):
|
|
def test_arch_with_an_install_announces_yay(self):
|
|
motd = DQ.build_motd(
|
|
"arch", "latest", "amd64", "fr", "~/git/erplibre", "install", "vi"
|
|
)
|
|
self.assertIn("AUR — yay", motd)
|
|
self.assertIn("yay -Syu", motd)
|
|
|
|
def test_arch_without_an_install_announces_nothing(self):
|
|
motd = DQ.build_motd("arch", "latest", "amd64", "fr")
|
|
self.assertNotIn("yay", motd)
|
|
|
|
def test_another_distro_never_gets_the_block(self):
|
|
for distro, version in (
|
|
("ubuntu", "24.04"),
|
|
("debian", "13"),
|
|
("fedora", "43"),
|
|
("opensuse", "tumbleweed"),
|
|
):
|
|
with self.subTest(distro=distro):
|
|
motd = DQ.build_motd(
|
|
distro,
|
|
version,
|
|
"amd64",
|
|
"fr",
|
|
"~/git/erplibre",
|
|
"install",
|
|
"vi",
|
|
)
|
|
self.assertNotIn("yay", motd)
|
|
|
|
def test_the_block_is_translated(self):
|
|
motd = DQ.build_motd(
|
|
"arch", "latest", "amd64", "en", "~/git/erplibre", "install", "vi"
|
|
)
|
|
self.assertIn("search the AUR", motd)
|
|
self.assertNotIn("chercher dans l'AUR", motd)
|
|
|
|
def test_the_block_stays_under_a_standard_terminal(self):
|
|
for lang in ("fr", "en"):
|
|
motd = DQ.build_motd(
|
|
"arch",
|
|
"latest",
|
|
"amd64",
|
|
lang,
|
|
"~/git/erplibre",
|
|
"install",
|
|
"vi",
|
|
)
|
|
for line in motd.splitlines():
|
|
self.assertLessEqual(len(line), 80, line)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|