erplibre/test/test_qemu_arch_yay.py
Mathieu Benoit 29b12c8ca1 [ADD] qemu arch : yay et bash-completion sur l'invité, guide au MOTD
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
2026-09-02 08:04:05 -04:00

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