From 50366673c21a94b76f7a65e32c045547fcbe11f7 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Fri, 21 Aug 2026 04:01:47 -0400 Subject: [PATCH] [FIX] analyse: make the module checker executable, and guard it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- script/analyse/check_module_package.py | 0 test/test_check_module_package.py | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+) mode change 100644 => 100755 script/analyse/check_module_package.py diff --git a/script/analyse/check_module_package.py b/script/analyse/check_module_package.py old mode 100644 new mode 100755 diff --git a/test/test_check_module_package.py b/test/test_check_module_package.py index beb2a59..d3b8db0 100644 --- a/test/test_check_module_package.py +++ b/test/test_check_module_package.py @@ -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()