[FIX] nixos : le service ERPLibre déclaré, démarré quand Odoo est là
Trois obstacles enchaînés, aucun visible sans une vraie machine. /etc est généré depuis le store : le « tee » d'une unité y rend « Read-only file system », et l'installation échouait à sa DERNIÈRE étape, après que le clone, le venv et un démarrage d'Odoo avaient réussi. ExecStart=/bin/bash rendait 203/EXEC, /bin étant un montage FUSE que systemd ne voit pas ; et le PATH d'une unité n'a pas bash, que réclament les shebangs de run.sh. Déclarée, l'unité est démarrée par la reconstruction — qui a lieu pendant « make install_os », quand la source d'Odoo n'arrive qu'après : une condition la fait sauter plutôt qu'échouer 21 fois de suite. Mesuré : service actif, 8069 servi par lui, login en 303. --- EN --- Three chained obstacles, none visible without a real machine. /etc is generated from the store: « tee » of a unit there returns « Read-only file system », and the install failed at its LAST step, after the clone, the venv and an Odoo start had all succeeded. ExecStart=/bin/bash returned 203/EXEC, /bin being a FUSE mount systemd does not see; and a unit's PATH carries no bash, which run.sh's shebangs ask for. Declared, the unit is started by the rebuild — which happens during « make install_os », while Odoo's source lands later: a condition makes it skip rather than fail 21 times over. Measured: service active, 8069 served by it, login in 303. Assisted-by: Claude Opus 5
This commit is contained in:
parent
2f08ec0890
commit
64a8e6400f
6 changed files with 279 additions and 5 deletions
|
|
@ -177,4 +177,67 @@
|
||||||
LIBRARY_PATH = "/run/current-system/sw/lib";
|
LIBRARY_PATH = "/run/current-system/sw/lib";
|
||||||
PKG_CONFIG_PATH = "/run/current-system/sw/lib/pkgconfig";
|
PKG_CONFIG_PATH = "/run/current-system/sw/lib/pkgconfig";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Le service ERPLibre, DÉCLARÉ et non écrit.
|
||||||
|
#
|
||||||
|
# Sur toute autre distribution l'installation dépose l'unité par
|
||||||
|
# « tee /etc/systemd/system/erplibre.service ». Ici /etc est généré depuis
|
||||||
|
# le store et monté en lecture seule : le tee échoue sur « Read-only file
|
||||||
|
# system », et l'installation entière rend 1 à sa dernière étape, après que
|
||||||
|
# tout le reste a réussi.
|
||||||
|
#
|
||||||
|
# « wantedBy » est l'équivalent déclaratif de « systemctl enable » :
|
||||||
|
# l'activation par lien symbolique écrirait elle aussi dans /etc.
|
||||||
|
#
|
||||||
|
# L'interpréteur vient du STORE et non de /bin. /bin et /usr/bin sont ici
|
||||||
|
# un montage FUSE d'envfs, et systemd résout l'exécutable d'ExecStart
|
||||||
|
# lui-même, hors de portée de ce montage : « /bin/bash » y rend
|
||||||
|
# « 203/EXEC, Unable to locate executable ». Avec Restart=always, l'unité
|
||||||
|
# boucle alors indéfiniment. La même raison vaut pour /usr/bin/env, donc
|
||||||
|
# le shebang de run.sh ne suffirait pas davantage.
|
||||||
|
#
|
||||||
|
# La première reconstruction déclare le service AVANT qu'ERPLibre ne soit
|
||||||
|
# installé, et son démarrage échoue alors — c'est attendu, et c'est
|
||||||
|
# exactement le cas que « nixos-rebuild rend 4 » recouvre. L'installation
|
||||||
|
# le relance une fois le dépôt en place.
|
||||||
|
systemd.services.erplibre = {
|
||||||
|
description = "ERPLibre";
|
||||||
|
requires = [ "postgresql.service" ];
|
||||||
|
after = [ "network.target" "network-online.target" "postgresql.service" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
# Une unité systemd ne reçoit PAS le PATH d'une session : le sien ne
|
||||||
|
# porte que coreutils, findutils, grep, sed et systemd.
|
||||||
|
#
|
||||||
|
# bash — run.sh lance odoo_bin.sh et lib_db_select.sh, dont le shebang est
|
||||||
|
# « #!/usr/bin/env bash ». env est là, bash non : « env: 'bash': No such
|
||||||
|
# file or directory », et run.sh s'arrête avant Odoo.
|
||||||
|
#
|
||||||
|
# python3 — la sonde de réveil tourne AVANT que odoo_bin.sh n'active le
|
||||||
|
# venv, donc avec le python du système. Son échec est silencieux
|
||||||
|
# (« 2>/dev/null ») : sans elle, la première page ouverte attendrait le
|
||||||
|
# chargement du registre sans que rien ne le dise.
|
||||||
|
path = with pkgs; [ bash python312 ];
|
||||||
|
# L'unité est déclarée par le module, donc démarrée par la
|
||||||
|
# reconstruction — qui a lieu PENDANT « make install_os », alors que la
|
||||||
|
# source d'Odoo n'arrive qu'à « make install_odoo_18 ». Sans condition,
|
||||||
|
# run.sh échoue sur un odoo-bin absent et « Restart = always » le rejoue
|
||||||
|
# toutes les cinq secondes jusqu'à ce que l'installation le pose — vingt
|
||||||
|
# et un échecs mesurés sur une pose ordinaire, et un « nixos-rebuild »
|
||||||
|
# qui rend 4 parce qu'une unité n'a pas démarré.
|
||||||
|
#
|
||||||
|
# Une CONDITION, et non une dépendance : systemd saute l'unité en le
|
||||||
|
# disant une fois, sans la marquer en échec, et la démarre d'elle-même
|
||||||
|
# au prochain déclenchement une fois le fichier là. Le motif évite de
|
||||||
|
# figer la version d'Odoo ici, où elle vieillirait en silence.
|
||||||
|
unitConfig.ConditionPathExistsGlob = "@EL_DIR@/odoo*/odoo/odoo-bin";
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
User = "@EL_USER@";
|
||||||
|
WorkingDirectory = "@EL_DIR@";
|
||||||
|
ExecStart = "${pkgs.bash}/bin/bash @EL_DIR@/run.sh";
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = 5;
|
||||||
|
StandardOutput = "journal+console";
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -312,10 +312,21 @@ def verifier(cible, journal, jump=""):
|
||||||
"""
|
"""
|
||||||
resultats = {}
|
resultats = {}
|
||||||
|
|
||||||
|
# Par NOM EXACT, jamais par motif. envfs est un système de fichiers qui
|
||||||
|
# RÉSOUT à la demande et n'ÉNUMÈRE pas : « ls /usr/bin » y rend un
|
||||||
|
# répertoire vide alors que « /usr/bin/python3.12 » s'ouvre. Un glob passe
|
||||||
|
# par readdir, donc ne rend rien, et le contrôle déclarait absent ce qui
|
||||||
|
# est là — il éprouvait envfs de la seule façon dont envfs ne peut pas
|
||||||
|
# répondre.
|
||||||
|
#
|
||||||
|
# La version vient du dépôt plutôt que d'être écrite ici : c'est elle que
|
||||||
|
# l'installation a suivie, et une constante vieillirait à côté.
|
||||||
chemins = sonder(
|
chemins = sonder(
|
||||||
cible,
|
cible,
|
||||||
"for f in /bin/bash /usr/bin/env; do [ -e $f ] || exit 1; done;"
|
"for f in /bin/bash /usr/bin/env; do [ -e $f ] || exit 1; done;"
|
||||||
" ls /usr/bin/python3.* >/dev/null 2>&1 || exit 1; echo ok",
|
" v=$(cut -d. -f1,2 git/erplibre/.python-odoo-version 2>/dev/null);"
|
||||||
|
' [ -n "$v" ] || v=3;'
|
||||||
|
' [ -e "/usr/bin/python$v" ] || exit 1; echo ok',
|
||||||
jump,
|
jump,
|
||||||
)
|
)
|
||||||
resultats["envfs"] = chemins == "ok"
|
resultats["envfs"] = chemins == "ok"
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,11 @@ set -e
|
||||||
. ./env_var.sh
|
. ./env_var.sh
|
||||||
|
|
||||||
EL_USER=${USER}
|
EL_USER=${USER}
|
||||||
|
# Le répertoire du service, pris du checkout QUI POSE le module : « make
|
||||||
|
# install_os » tourne à la racine du dépôt, et c'est ce dépôt-là que le
|
||||||
|
# service doit lancer. Deviner « /home/$USER/git/erplibre » se tromperait sur
|
||||||
|
# une installation de production, qui vit sous /opt.
|
||||||
|
EL_DIR=${EL_DIR:-${PWD}}
|
||||||
MODULE_SRC="conf/nixos/erplibre.nix"
|
MODULE_SRC="conf/nixos/erplibre.nix"
|
||||||
MODULE_DST="/etc/nixos/erplibre.nix"
|
MODULE_DST="/etc/nixos/erplibre.nix"
|
||||||
CONFIG="/etc/nixos/configuration.nix"
|
CONFIG="/etc/nixos/configuration.nix"
|
||||||
|
|
@ -34,8 +39,9 @@ fi
|
||||||
echo -e "\n---- Module ERPLibre pour NixOS ----"
|
echo -e "\n---- Module ERPLibre pour NixOS ----"
|
||||||
# Le nom du compte est substitué comme le user-data cloud-init l'est : le
|
# Le nom du compte est substitué comme le user-data cloud-init l'est : le
|
||||||
# module déclare un rôle PostgreSQL, et un rôle porte un nom.
|
# module déclare un rôle PostgreSQL, et un rôle porte un nom.
|
||||||
sed "s/@EL_USER@/${EL_USER}/g" "${MODULE_SRC}" | sudo tee "${MODULE_DST}" > /dev/null
|
sed -e "s/@EL_USER@/${EL_USER}/g" -e "s#@EL_DIR@#${EL_DIR}#g" \
|
||||||
echo " posé : ${MODULE_DST} (compte ${EL_USER})"
|
"${MODULE_SRC}" | sudo tee "${MODULE_DST}" > /dev/null
|
||||||
|
echo " posé : ${MODULE_DST} (compte ${EL_USER}, dépôt ${EL_DIR})"
|
||||||
|
|
||||||
if [ ! -f "${CONFIG}" ]; then
|
if [ ! -f "${CONFIG}" ]; then
|
||||||
echo "Configuration introuvable : ${CONFIG}"
|
echo "Configuration introuvable : ${CONFIG}"
|
||||||
|
|
|
||||||
|
|
@ -205,8 +205,28 @@ class QemuInstallMixin:
|
||||||
"sudo sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' "
|
"sudo sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' "
|
||||||
"/etc/selinux/config 2>/dev/null || true; fi; "
|
"/etc/selinux/config 2>/dev/null || true; fi; "
|
||||||
)
|
)
|
||||||
|
# NixOS est reconnu DANS la VM, et non d'après ce que l'hôte croit
|
||||||
|
# savoir : la même commande sert au déploiement, au test long et à un
|
||||||
|
# « --hote » qu'on n'a pas créé. /etc y est généré depuis le store et
|
||||||
|
# monté en lecture seule — le tee plus bas échoue sur « Read-only file
|
||||||
|
# system », et l'installation rend 1 à sa dernière étape après que
|
||||||
|
# tout le reste a réussi. L'unité vient du module déclaratif posé par
|
||||||
|
# « make install_os » ; il ne reste qu'à la relancer, une fois le
|
||||||
|
# dépôt en place.
|
||||||
|
nixos = (
|
||||||
|
"if [ -f /etc/os-release ] && grep -q '^ID=nixos' "
|
||||||
|
"/etc/os-release; then "
|
||||||
|
"if systemctl cat erplibre.service >/dev/null 2>&1; then "
|
||||||
|
"sudo systemctl restart erplibre.service; "
|
||||||
|
"else "
|
||||||
|
'echo "erplibre.service non déclaré : '
|
||||||
|
'« make install_os » pose le module NixOS qui le porte." >&2; '
|
||||||
|
"exit 1; fi; "
|
||||||
|
"else "
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
f'SVC_USER=$(whoami); SVC_GROUP=$(id -gn); SVC_DIR="{svc_dir}"; '
|
nixos
|
||||||
|
+ f'SVC_USER=$(whoami); SVC_GROUP=$(id -gn); SVC_DIR="{svc_dir}"; '
|
||||||
+ pre
|
+ pre
|
||||||
+ selinux_shell
|
+ selinux_shell
|
||||||
+ "sudo tee /etc/systemd/system/erplibre.service >/dev/null <<UNIT\n"
|
+ "sudo tee /etc/systemd/system/erplibre.service >/dev/null <<UNIT\n"
|
||||||
|
|
@ -230,7 +250,8 @@ class QemuInstallMixin:
|
||||||
"WantedBy=multi-user.target\n"
|
"WantedBy=multi-user.target\n"
|
||||||
"UNIT\n"
|
"UNIT\n"
|
||||||
"sudo systemctl daemon-reload; "
|
"sudo systemctl daemon-reload; "
|
||||||
"sudo systemctl enable --now erplibre.service"
|
"sudo systemctl enable --now erplibre.service; "
|
||||||
|
"fi"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Bureaux disponibles, par gestionnaire de paquets. Une seule source pour
|
# Bureaux disponibles, par gestionnaire de paquets. Une seule source pour
|
||||||
|
|
|
||||||
|
|
@ -277,5 +277,151 @@ class LHoteSansGestionnaire(unittest.TestCase):
|
||||||
self.assertIn("conseil_sans_gestionnaire", src[i : i + 400])
|
self.assertIn("conseil_sans_gestionnaire", src[i : i + 400])
|
||||||
|
|
||||||
|
|
||||||
|
class LeServiceEstDeclare(unittest.TestCase):
|
||||||
|
"""Sur NixOS, /etc est généré depuis le store et monté en lecture seule.
|
||||||
|
|
||||||
|
L'installation dépose l'unité par « tee /etc/systemd/system/
|
||||||
|
erplibre.service » sur toute autre distribution ; ici le tee échoue sur
|
||||||
|
« Read-only file system », et l'installation entière rend 1 à sa dernière
|
||||||
|
étape, après que le dépôt, le venv, les modules compilés et un démarrage
|
||||||
|
d'Odoo ont tous réussi. L'unité vient donc de la configuration.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.src = MODULE.read_text(encoding="utf-8")
|
||||||
|
self.script = SCRIPT.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
def test_the_unit_is_declared_in_the_module(self):
|
||||||
|
self.assertIn("systemd.services.erplibre", self.src)
|
||||||
|
self.assertIn('Type = "simple";', self.src)
|
||||||
|
self.assertIn('User = "@EL_USER@";', self.src)
|
||||||
|
|
||||||
|
def test_it_starts_at_boot_without_a_symlink(self):
|
||||||
|
"""« systemctl enable » activerait par un lien dans /etc, que le
|
||||||
|
système refuse d'écrire. « wantedBy » en est l'équivalent déclaratif,
|
||||||
|
et c'est la seule forme d'activation qui tienne ici.
|
||||||
|
|
||||||
|
L'absence de « systemctl enable » se vérifie sur la COMMANDE du menu,
|
||||||
|
plus bas : un module Nix n'exécute rien, et l'y chercher interdirait
|
||||||
|
au commentaire de nommer ce qu'il explique."""
|
||||||
|
self.assertIn('wantedBy = [ "multi-user.target" ];', self.src)
|
||||||
|
|
||||||
|
def test_the_interpreter_comes_from_the_store(self):
|
||||||
|
"""/bin et /usr/bin sont un montage FUSE d'envfs, et systemd résout
|
||||||
|
l'exécutable d'ExecStart lui-même, hors de portée de ce montage :
|
||||||
|
« /bin/bash » y rend « 203/EXEC ». Avec Restart=always, l'unité boucle
|
||||||
|
indéfiniment."""
|
||||||
|
self.assertIn('ExecStart = "${pkgs.bash}/bin/bash', self.src)
|
||||||
|
self.assertNotIn('ExecStart = "/bin/bash', self.src)
|
||||||
|
self.assertNotIn('ExecStart = "/usr/bin/env', self.src)
|
||||||
|
|
||||||
|
def test_the_unit_carries_the_path_its_scripts_need(self):
|
||||||
|
"""Une unité ne reçoit pas le PATH d'une session. run.sh lance des
|
||||||
|
scripts dont le shebang est « env bash » : env est dans le PATH par
|
||||||
|
défaut, bash non, et run.sh s'arrête avant Odoo."""
|
||||||
|
self.assertIn("path = with pkgs; [ bash python312 ];", self.src)
|
||||||
|
|
||||||
|
def test_the_repository_is_not_guessed(self):
|
||||||
|
"""Le service lance le dépôt QUI A POSÉ le module. Écrire
|
||||||
|
« /home/$USER/git/erplibre » se tromperait sur une installation de
|
||||||
|
production, qui vit sous /opt."""
|
||||||
|
self.assertIn('WorkingDirectory = "@EL_DIR@";', self.src)
|
||||||
|
self.assertIn("EL_DIR=${EL_DIR:-${PWD}}", self.script)
|
||||||
|
|
||||||
|
def test_both_placeholders_are_substituted(self):
|
||||||
|
"""Un marqueur non substitué partirait tel quel dans /etc/nixos et
|
||||||
|
ferait échouer l'évaluation du module."""
|
||||||
|
for marqueur in ("@EL_USER@", "@EL_DIR@"):
|
||||||
|
with self.subTest(marqueur=marqueur):
|
||||||
|
self.assertIn(marqueur, self.src)
|
||||||
|
self.assertIn(marqueur, self.script)
|
||||||
|
|
||||||
|
|
||||||
|
class LeMenuNEcritPasDansEtcSurNixos(unittest.TestCase):
|
||||||
|
def _cmd(self, prod=False):
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.argv = ["todo.py"]
|
||||||
|
sys.path.insert(0, str(RACINE))
|
||||||
|
from script.todo.todo import TODO
|
||||||
|
|
||||||
|
return TODO.__new__(TODO)._qemu_odoo_service_cmd(prod=prod)
|
||||||
|
|
||||||
|
def test_nixos_is_recognised_before_the_write(self):
|
||||||
|
"""Reconnu DANS la VM : la même commande sert au déploiement, au test
|
||||||
|
long et à un « --hote » qu'on n'a pas créé."""
|
||||||
|
for prod in (False, True):
|
||||||
|
with self.subTest(prod=prod):
|
||||||
|
cmd = self._cmd(prod)
|
||||||
|
self.assertLess(
|
||||||
|
cmd.index("ID=nixos"),
|
||||||
|
cmd.index("tee /etc/systemd/system"),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_the_declarative_branch_only_restarts(self):
|
||||||
|
"""L'unité existe déjà — le module l'a déclarée. « enable » écrirait
|
||||||
|
un lien dans /etc, que le système refuse."""
|
||||||
|
cmd = self._cmd()
|
||||||
|
nix = cmd[cmd.index("ID=nixos") : cmd.index("tee /etc/systemd/system")]
|
||||||
|
self.assertIn("systemctl restart erplibre.service", nix)
|
||||||
|
self.assertNotIn("systemctl enable", nix)
|
||||||
|
|
||||||
|
def test_a_missing_unit_is_named_and_fails(self):
|
||||||
|
"""Sans le module, « restart » rendrait une erreur de systemd sans
|
||||||
|
dire ce qui manque ni où le prendre."""
|
||||||
|
cmd = self._cmd()
|
||||||
|
self.assertIn("systemctl cat erplibre.service", cmd)
|
||||||
|
self.assertIn("make install_os", cmd)
|
||||||
|
|
||||||
|
|
||||||
|
class LeServiceAttendQuOdooSoitLa(unittest.TestCase):
|
||||||
|
"""L'unité est déclarée par le module, donc démarrée par la
|
||||||
|
reconstruction — laquelle a lieu PENDANT « make install_os ».
|
||||||
|
|
||||||
|
La source d'Odoo, elle, n'arrive qu'à « make install_odoo_18 ». Entre les
|
||||||
|
deux, run.sh échoue sur un odoo-bin absent et « Restart = always » le
|
||||||
|
rejoue toutes les cinq secondes : vingt et un échecs mesurés sur une pose
|
||||||
|
ordinaire, et un « nixos-rebuild » qui rend 4 au lieu de 0 parce qu'une
|
||||||
|
unité n'a pas démarré. Le journal d'une machine neuve s'ouvre alors sur
|
||||||
|
une avalanche qui n'accuse rien de réel.
|
||||||
|
|
||||||
|
Une CONDITION plutôt qu'une dépendance : mesuré sur une VM, systemd saute
|
||||||
|
l'unité en le disant une fois — « was skipped because of an unmet
|
||||||
|
condition check » — sans la marquer en échec, puis la démarre d'elle-même
|
||||||
|
une fois le fichier là.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.src = MODULE.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
def test_the_unit_waits_for_what_run_sh_executes(self):
|
||||||
|
self.assertIn(
|
||||||
|
'unitConfig.ConditionPathExistsGlob = "@EL_DIR@/odoo*/odoo/'
|
||||||
|
'odoo-bin";',
|
||||||
|
self.src,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_the_odoo_version_is_not_frozen_in_the_module(self):
|
||||||
|
"""Une version écrite ici vieillirait en silence : l'unité cesserait
|
||||||
|
de démarrer le jour où le dépôt passe à la suivante."""
|
||||||
|
condition = [l for l in self.src.splitlines() if "ConditionPath" in l]
|
||||||
|
self.assertEqual(len(condition), 1)
|
||||||
|
self.assertNotIn("odoo18", condition[0])
|
||||||
|
|
||||||
|
def test_it_is_a_condition_and_not_a_dependency(self):
|
||||||
|
"""« requires » sur un chemin n'existe pas, et « after » ne
|
||||||
|
garantirait rien : seule une condition SAUTE au lieu d'échouer."""
|
||||||
|
self.assertNotIn('odoo-bin" ]', self.src)
|
||||||
|
self.assertIn("ConditionPathExistsGlob", self.src)
|
||||||
|
|
||||||
|
def test_the_marker_is_substituted_everywhere(self):
|
||||||
|
"""Le chemin du dépôt apparaît désormais deux fois dans le module ;
|
||||||
|
un sed sans « g » n'en remplacerait qu'une, et la condition porterait
|
||||||
|
un « @EL_DIR@ » littéral que rien ne satisfait jamais."""
|
||||||
|
script = SCRIPT.read_text(encoding="utf-8")
|
||||||
|
self.assertIn("s#@EL_DIR@#${EL_DIR}#g", script)
|
||||||
|
self.assertGreater(self.src.count("@EL_DIR@"), 1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -367,5 +367,32 @@ class LaCreationDeLaVmEstPrivilegiee(unittest.TestCase):
|
||||||
self.assertIn('([] if self.dry_run else ["sudo"]) + [', src)
|
self.assertIn('([] if self.dry_run else ["sudo"]) + [', src)
|
||||||
|
|
||||||
|
|
||||||
|
class LaSondeEnvfsNommeSesChemins(unittest.TestCase):
|
||||||
|
"""envfs RÉSOUT à la demande et n'ÉNUMÈRE pas.
|
||||||
|
|
||||||
|
« ls /usr/bin » y rend un répertoire vide alors que
|
||||||
|
« /usr/bin/python3.12 » s'ouvre : un glob passe par readdir, donc ne rend
|
||||||
|
rien. Le contrôle déclarait absent ce qui est là — il éprouvait envfs de
|
||||||
|
la seule façon dont envfs ne peut pas répondre.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_no_glob_is_used_against_that_filesystem(self):
|
||||||
|
self.assertNotIn("ls /usr/bin/python3.*", SRC)
|
||||||
|
self.assertNotIn("/usr/bin/python3.*", SRC)
|
||||||
|
|
||||||
|
def test_the_path_is_named_exactly(self):
|
||||||
|
self.assertIn('[ -e "/usr/bin/python$v" ]', SRC)
|
||||||
|
|
||||||
|
def test_the_version_comes_from_the_repository(self):
|
||||||
|
"""Une version écrite ici vieillirait à côté de celle que
|
||||||
|
l'installation a suivie."""
|
||||||
|
self.assertIn(".python-odoo-version", SRC)
|
||||||
|
|
||||||
|
def test_a_missing_version_file_still_probes_something(self):
|
||||||
|
"""Sans le fichier, « /usr/bin/python » tout court : mieux vaut une
|
||||||
|
épreuve plus faible qu'un chemin vide, qui passerait toujours."""
|
||||||
|
self.assertIn('[ -n "$v" ] || v=3', SRC)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue