[FIX] install.sh : sonder la version en exécutant du code, non par -V
« python -V » répond avant de charger la bibliothèque standard. Un venv dont l'interpréteur ne la trouve pas, comme un checkout monté depuis une autre machine, rendait donc sa version et passait pour sain. install.sh lui passait alors TODO, qui mourait sur « No module named 'encodings' », et le garde « venv bâti ailleurs » ne s'affichait jamais. La sonde exécute maintenant « import sys » : un interpréteur qui ne démarre pas ne rend rien. Vérifié : un faux python sain à -V mais mort ailleurs fait échouer le test sur l'ancien code et le réussit avec la correction. --- EN --- "python -V" answers before loading the standard library. A venv whose interpreter cannot find it, such as a checkout mounted from another machine, still reported its version and passed as healthy. install.sh then handed TODO to it, which died on "No module named 'encodings'", and the "venv built elsewhere" guard never printed. The probe now runs "import sys": an interpreter that cannot start returns nothing. Checked: a fake python that answers -V but fails otherwise makes the test fail on the old code and pass with the fix. Assisted-by: Claude Opus 5.5
This commit is contained in:
parent
cb20191469
commit
c67ab16eb9
2 changed files with 76 additions and 7 deletions
15
install.sh
15
install.sh
|
|
@ -23,14 +23,15 @@ VOULUE="$(xargs < conf/python-erplibre-version 2> /dev/null)"
|
|||
ATTENDUE="${VOULUE%.*}"
|
||||
|
||||
# Majeure.mineure de l'exécutable : elle seule décide de la grammaire acceptée.
|
||||
# Rien n'est rendu quand la sortie n'a pas la forme « Python X.Y… » : un
|
||||
# interpréteur en panne écrit son diagnostic, et le premier mot venu passerait
|
||||
# sinon pour un numéro de version que « sort -V » jugerait assez récent.
|
||||
# La version est lue par du code exécuté, et non par « -V » : celui-ci répond
|
||||
# avant le chargement de la bibliothèque standard, si bien qu'un interpréteur
|
||||
# qui ne la trouve pas rend sa version puis meurt au premier vrai lancement.
|
||||
# Rien n'est rendu quand la sortie n'a pas la forme X.Y : un interpréteur en
|
||||
# panne écrit son diagnostic, et le premier mot venu passerait sinon pour un
|
||||
# numéro de version que « sort -V » jugerait assez récent.
|
||||
el_mineure() {
|
||||
"$1" -V 2>&1 | awk '$1 == "Python" {
|
||||
split($2, v, ".")
|
||||
if (v[1] ~ /^[0-9]+$/ && v[2] ~ /^[0-9]+$/) print v[1] "." v[2]
|
||||
}'
|
||||
"$1" -c 'import sys; print("%d.%d" % sys.version_info[:2])' 2> /dev/null \
|
||||
| awk '/^[0-9]+\.[0-9]+$/ { print; exit }'
|
||||
}
|
||||
|
||||
# Vrai si la version lue atteint au moins celle attendue, comparée en version
|
||||
|
|
|
|||
68
test/test_install_sh_probe.py
Normal file
68
test/test_install_sh_probe.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#!/usr/bin/env python3
|
||||
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
"""La sonde de version d'install.sh : lire la version en exécutant du code.
|
||||
|
||||
« python -V » répond avant le chargement de la bibliothèque standard. Un venv
|
||||
dont l'interpréteur ne trouve pas celle-ci — un checkout monté depuis une
|
||||
autre machine — rend donc sa version, passe pour sain, et meurt au lancement
|
||||
de TODO avec son pavé d'initialisation. La sonde doit le déclarer muet.
|
||||
"""
|
||||
|
||||
import os
|
||||
import stat
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
RACINE = Path(__file__).resolve().parent.parent
|
||||
|
||||
# Répond à -V comme un vrai interpréteur, échoue sur tout le reste : c'est la
|
||||
# conduite d'un python qui ne trouve pas sa bibliothèque standard.
|
||||
PYTHON_MORT = """#!/bin/sh
|
||||
if [ "$1" = "-V" ]; then echo "Python 3.12.10"; exit 0; fi
|
||||
echo "Fatal Python error: init_fs_encoding" >&2
|
||||
exit 1
|
||||
"""
|
||||
|
||||
|
||||
def el_mineure(executable):
|
||||
"""Rend la sortie de la fonction el_mineure d'install.sh sur l'exécutable."""
|
||||
script = (
|
||||
"source <(sed -n '/^el_mineure()/,/^}/p' install.sh); "
|
||||
'el_mineure "$1"'
|
||||
)
|
||||
return subprocess.run(
|
||||
["bash", "-c", script, "sonde", executable],
|
||||
cwd=RACINE,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
).stdout.strip()
|
||||
|
||||
|
||||
class TestSondeDeVersion(unittest.TestCase):
|
||||
def test_un_interpreteur_sain_rend_majeure_mineure(self):
|
||||
python = subprocess.run(
|
||||
["python3", "-c", "import sys; print('%d.%d' % sys.version_info[:2])"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
).stdout.strip()
|
||||
self.assertEqual(el_mineure("python3"), python)
|
||||
|
||||
def test_un_interpreteur_qui_ne_demarre_pas_ne_rend_rien(self):
|
||||
with tempfile.TemporaryDirectory() as dossier:
|
||||
faux = os.path.join(dossier, "python")
|
||||
Path(faux).write_text(PYTHON_MORT)
|
||||
os.chmod(faux, os.stat(faux).st_mode | stat.S_IEXEC)
|
||||
self.assertEqual(el_mineure(faux), "")
|
||||
|
||||
def test_un_interpreteur_absent_ne_rend_rien(self):
|
||||
self.assertEqual(el_mineure("/chemin/qui/n/existe/pas"), "")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Reference in a new issue