erplibre/test/test_install_sh_confirm.py
Mathieu Benoit aa3f99e746 [IMP] install.sh: ask y/N before installing the tooling venv
On a system too old to read TODO, install.sh ran the install without asking,
and the install DELETES an existing .venv.erplibre. It now runs only on a yes
typed in a terminal: o, oui, y or yes, any case. Enter alone means no, and
with no terminal no question is asked and nothing is installed. « aucun
python3 » finally shows when the probe is silent: the || fallback never fired
behind a pipe.
Checked: 16 tests, each answer on a pseudo-terminal; install.sh run in an
isolated copy exits 1 on « n » and installs on « o ».

--- FR ---

[IMP] install.sh : demander o/N avant d'installer le venv d'outillage

Sur un système trop ancien pour lire TODO, install.sh lançait l'installation
sans rien demander, et celle-ci SUPPRIME un .venv.erplibre existant. Elle ne
part plus que sur un oui tapé au terminal : o, oui, y ou yes, toute casse.
Entrée seule vaut non, et sans terminal aucune question n'est posée ni
installation lancée. « aucun python3 » s'affiche enfin quand la sonde est
muette : le repli par || ne se déclenchait jamais derrière un tube.
Vérifié : 16 tests, dont chaque réponse sur un pseudo-terminal ; install.sh
lancé dans une copie isolée sort en 1 sur « n » et installe sur « o ».

Assisted-by: Claude Opus 5.5
2026-09-25 01:29:53 -04:00

83 lines
2.5 KiB
Python

#!/usr/bin/env python3
# © 2026 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
"""La question qu'install.sh pose avant l'installation du venv d'outillage.
L'installation peut SUPPRIMER un .venv.erplibre existant : elle ne part que
sur un oui explicite. La réponse se donne en français ou en anglais (o, oui,
y, yes, toute casse) ; Entrée seule vaut non. Sans terminal sur l'entrée,
personne ne peut consentir, et la réponse est non sans question posée.
"""
import os
import select
import subprocess
import unittest
from pathlib import Path
RACINE = Path(__file__).resolve().parent.parent
SCRIPT = (
"source <(sed -n '/^el_confirmer()/,/^}/p' install.sh); "
'el_confirmer "Lancer ?"'
)
def repondre_au_terminal(reponse):
"""Lance el_confirmer sur un pseudo-terminal, y tape la réponse.
Rend le code de sortie et ce que le terminal a affiché.
"""
maitre, esclave = os.openpty()
proc = subprocess.Popen(
["bash", "-c", SCRIPT],
cwd=RACINE,
stdin=esclave,
stdout=esclave,
stderr=esclave,
)
os.close(esclave)
os.write(maitre, (reponse + "\n").encode())
code = proc.wait(timeout=10)
lu = b""
while select.select([maitre], [], [], 0.2)[0]:
try:
morceau = os.read(maitre, 4096)
except OSError:
break
if not morceau:
break
lu += morceau
os.close(maitre)
return code, lu.decode(errors="replace")
class TestConfirmer(unittest.TestCase):
def test_oui_en_francais_et_en_anglais(self):
for reponse in ("o", "O", "oui", "Oui", "y", "Y", "yes", "YES"):
with self.subTest(reponse=reponse):
self.assertEqual(repondre_au_terminal(reponse)[0], 0)
def test_entree_seule_et_non_refusent(self):
for reponse in ("", "n", "non", "no", "ouais", "x"):
with self.subTest(reponse=reponse):
self.assertEqual(repondre_au_terminal(reponse)[0], 1)
def test_la_question_annonce_le_defaut(self):
self.assertIn("Lancer ? [o/N]", repondre_au_terminal("n")[1])
def test_sans_terminal_la_reponse_est_non(self):
proc = subprocess.run(
["bash", "-c", SCRIPT],
cwd=RACINE,
input="o\n",
capture_output=True,
text=True,
check=False,
)
self.assertEqual(proc.returncode, 1)
self.assertNotIn("[o/N]", proc.stdout)
if __name__ == "__main__":
unittest.main()