[FIX] analyse: make the module checker executable, and guard it

It shipped without the execute bit while carrying a shebang and a main,
so it only ran when prefixed with python3 — and nothing said so before
the attempt. The new test ties the two together in BOTH directions: a
library module marked executable invites a run it cannot serve.

--- FR ---

Livré sans le bit d'exécution alors qu'il porte un shebang et un main :
il ne se lançait qu'en le préfixant de python3, et rien ne le signalait
avant l'essai. Le test neuf lie les deux dans LES DEUX SENS — un module
de bibliothèque marqué exécutable invite à un lancement impossible.

Assisted-by: Claude Opus 5
This commit is contained in:
Mathieu Benoit 2026-08-21 04:01:47 -04:00
parent 97d2ebec65
commit 50366673c2
2 changed files with 26 additions and 0 deletions

0
script/analyse/check_module_package.py Normal file → Executable file
View file

View file

@ -566,5 +566,31 @@ class TestTheWiring(unittest.TestCase):
self.assertEqual(entrees, branches)
class TestThePermissions(unittest.TestCase):
"""Shebang et exécutable vont ensemble, dans les deux sens.
Un outil livré sans le bit d'exécution ne se lance qu'en le préfixant
de `python3`, et rien ne le signale avant l'essai. Un module de
bibliothèque marqué exécutable invite à le lancer alors qu'il n'a pas
de `main` — l'inverse est tout aussi trompeur.
"""
DOSSIER = os.path.normpath(
os.path.join(os.path.dirname(__file__), "..", "script", "analyse")
)
def test_shebang_and_executable_bit_agree(self):
import glob
import stat
for chemin in sorted(glob.glob(os.path.join(self.DOSSIER, "*.py"))):
with io.open(chemin, encoding="utf-8") as handle:
shebang = handle.readline().startswith("#!")
executable = bool(
stat.S_IMODE(os.stat(chemin).st_mode) & stat.S_IXUSR
)
self.assertEqual(shebang, executable, os.path.basename(chemin))
if __name__ == "__main__":
unittest.main()