From 29024806e59864c2361c67003791685393fe1e8e Mon Sep 17 00:00:00 2001 From: seg6 Date: Mon, 10 Nov 2025 16:14:30 +0100 Subject: [PATCH] tests: minimum python version validation for user facing scripts Adds [`Vermin`](https://github.com/netromdk/vermin) to make sure our user facing Python scripts conform to the proper minimum Python version requirements. The tool parses Python code into an AST and checks it against a database of rules covering Python 2.0-3.13. Testing Python version compatibility is tricky because most issues only show up at runtime. Type annotations like `str | None` (Python 3.10+) compile just fine (under pyc) and they don't throw an exception until you actually call the relevant function. This makes it hard to catch compatibility bugs without running the code (through all possible execution paths) on every Python version. Signed-off-by: seg6 Closes #12044 --- .github/actions/install-sphinx/action.yml | 2 ++ share/tools/create_manpage_completions.py | 3 ++ .../python-user-facing-tools-compat.fish | 28 +++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 tests/checks/python-user-facing-tools-compat.fish diff --git a/.github/actions/install-sphinx/action.yml b/.github/actions/install-sphinx/action.yml index 9c022bfe4..20512b4c5 100644 --- a/.github/actions/install-sphinx/action.yml +++ b/.github/actions/install-sphinx/action.yml @@ -10,6 +10,8 @@ runs: run: | set -x sudo pip install uv --break-system-packages + command -v uv + command -v uvx # Check that pyproject.toml and the lock file are in sync. # TODO Use "uv" to install Python as well. : 'Note that --no-managed-python below would be implied but be explicit' diff --git a/share/tools/create_manpage_completions.py b/share/tools/create_manpage_completions.py index 04513bda0..6400c83c0 100755 --- a/share/tools/create_manpage_completions.py +++ b/share/tools/create_manpage_completions.py @@ -295,6 +295,7 @@ class Deroffer: g_re_word = re.compile(r"[a-zA-Z_]+") # equivalent to the word() method g_re_number = re.compile(r"[+-]?\d+") # equivalent to the number() method g_re_esc_char = re.compile( + # novermin r"""([a-zA-Z_]) | # Word ([+-]?\d) | # Number \\ # Backslash (for escape seq) @@ -309,6 +310,7 @@ class Deroffer: g_re_newline_collapse = re.compile(r"\n{3,}") g_re_font = re.compile( + # novermin r"""\\f( # Starts with backslash f (\(\S{2}) | # Open paren, then two printable chars (\[\S*?\]) | # Open bracket, zero or more printable characters, then close bracket @@ -2090,6 +2092,7 @@ def get_paths_from_man_locations(): with open("/etc/man.conf", "r") as file: data = file.read() for key in ["MANPATH", "_default"]: + # novermin for match in re.findall(r"^%s\s+(.*)$" % key, data, re.I | re.M): parent_paths.append(match) except FileNotFoundError: diff --git a/tests/checks/python-user-facing-tools-compat.fish b/tests/checks/python-user-facing-tools-compat.fish new file mode 100644 index 000000000..db944102d --- /dev/null +++ b/tests/checks/python-user-facing-tools-compat.fish @@ -0,0 +1,28 @@ +#RUN: %fish %s +#REQUIRES: command -v uvx + +set -l webconfig (status dirname)/../../share/tools/web_config/webconfig.py +set -l create_manpage (status dirname)/../../share/tools/create_manpage_completions.py +set -l min_version 3.5 + +# use vermin to detect minimum Python version violations +# features enabled: +# - union-types: catch `str | None` syntax (3.10+) +# - fstring-self-doc: catch f'{var=}' syntax (3.8+) +set -l output (uvx vermin \ + --no-tips \ + --violations \ + --feature union-types \ + --feature fstring-self-doc \ + --target=$min_version- \ + $webconfig \ + $create_manpage \ + 2>&1 | string collect) +set -l exit_code $pipestatus[1] + +echo $exit_code +# CHECK: 0 + +if test $exit_code -ne 0 + echo $output >&2 +end