From 9f8749bcc58cc72376dfab7032357f9766b1d155 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Fillion-Robin Date: Tue, 26 Aug 2025 00:03:07 -0400 Subject: [PATCH 1/7] chore: Simplify test runner by introducing "run()" function This change refactors the test runner script by adding a `run()` function for executing compiler commands, capturing their output, and returning the results. --- run-tests.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/run-tests.py b/run-tests.py index 5017f4c1..ad1d6e88 100644 --- a/run-tests.py +++ b/run-tests.py @@ -78,6 +78,19 @@ def cleanup(out): 'pr57580.c', ] + +def run(compiler_executable, compiler_args): + """Execute a compiler command and capture its output.""" + compiler_cmd = [compiler_executable] + compiler_cmd.extend(compiler_args) + p = subprocess.Popen(compiler_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + comm = p.communicate() + exit_code = p.returncode + output = cleanup(comm[0]) + error = comm[0].decode('utf-8').strip() + return (exit_code, output, error) + + numberOfSkipped = 0 numberOfFailed = 0 numberOfFixed = 0 @@ -89,26 +102,12 @@ def cleanup(out): numberOfSkipped = numberOfSkipped + 1 continue - clang_cmd = ['clang'] - clang_cmd.extend(cmd.split(' ')) - p = subprocess.Popen(clang_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - comm = p.communicate() - clang_output = cleanup(comm[0]) + clang_output = run('clang', cmd.split(' '))[1] - gcc_cmd = ['gcc'] - gcc_cmd.extend(cmd.split(' ')) - p = subprocess.Popen(gcc_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - comm = p.communicate() - gcc_output = cleanup(comm[0]) + gcc_output = run('gcc', cmd.split(' '))[1] - simplecpp_cmd = ['./simplecpp'] # -E is not supported and we bail out on unknown options - simplecpp_cmd.extend(cmd.replace('-E ', '', 1).split(' ')) - p = subprocess.Popen(simplecpp_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - comm = p.communicate() - simplecpp_ec = p.returncode - simplecpp_output = cleanup(comm[0]) - simplecpp_err = comm[0].decode('utf-8').strip() + simplecpp_ec, simplecpp_output, simplecpp_err = run('./simplecpp', cmd.replace('-E ', '', 1).split(' ')) if simplecpp_output != clang_output and simplecpp_output != gcc_output: filename = cmd[cmd.rfind('/')+1:] From f2d07490a9ecec6eddbfe1549edccc521dc9422b Mon Sep 17 00:00:00 2001 From: Jean-Christophe Fillion-Robin Date: Tue, 26 Aug 2025 00:42:46 -0400 Subject: [PATCH 2/7] tests: Update test runner to fail early if prerequisites are missing This change updates the test runner script to check for the presence of required compilers (clang, gcc) before running tests. --- run-tests.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/run-tests.py b/run-tests.py index ad1d6e88..3e079e68 100644 --- a/run-tests.py +++ b/run-tests.py @@ -1,6 +1,7 @@ import glob import os +import shutil import subprocess import sys @@ -13,6 +14,19 @@ def cleanup(out): ret = ret + s return ret + +# Check for required compilers and exit if any are missing +CLANG_EXE = shutil.which('clang') +if not CLANG_EXE: + sys.exit('Failed to run tests: clang compiler not found') + +GCC_EXE = shutil.which('gcc') +if not GCC_EXE: + sys.exit('Failed to run tests: gcc compiler not found') + +SIMPLECPP_EXE = './simplecpp' + + commands = [] for f in sorted(glob.glob(os.path.expanduser('testsuite/clang-preprocessor-tests/*.c*'))): @@ -102,12 +116,12 @@ def run(compiler_executable, compiler_args): numberOfSkipped = numberOfSkipped + 1 continue - clang_output = run('clang', cmd.split(' '))[1] + clang_output = run(CLANG_EXE, cmd.split(' '))[1] - gcc_output = run('gcc', cmd.split(' '))[1] + gcc_output = run(GCC_EXE, cmd.split(' '))[1] # -E is not supported and we bail out on unknown options - simplecpp_ec, simplecpp_output, simplecpp_err = run('./simplecpp', cmd.replace('-E ', '', 1).split(' ')) + simplecpp_ec, simplecpp_output, simplecpp_err = run(SIMPLECPP_EXE, cmd.replace('-E ', '', 1).split(' ')) if simplecpp_output != clang_output and simplecpp_output != gcc_output: filename = cmd[cmd.rfind('/')+1:] From 5fecffcf596cd25915980429b257ddf962ecd40e Mon Sep 17 00:00:00 2001 From: Jean-Christophe Fillion-Robin Date: Tue, 26 Aug 2025 03:22:00 -0400 Subject: [PATCH 3/7] chore: Update test runner script to explicitly set output of run() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Suggested-by: Oliver Stöneberg --- run-tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/run-tests.py b/run-tests.py index 3e079e68..89224f97 100644 --- a/run-tests.py +++ b/run-tests.py @@ -116,9 +116,9 @@ def run(compiler_executable, compiler_args): numberOfSkipped = numberOfSkipped + 1 continue - clang_output = run(CLANG_EXE, cmd.split(' '))[1] + _, clang_output, _ = run(CLANG_EXE, cmd.split(' ')) - gcc_output = run(GCC_EXE, cmd.split(' '))[1] + _, gcc_output, _ = run(GCC_EXE, cmd.split(' ')) # -E is not supported and we bail out on unknown options simplecpp_ec, simplecpp_output, simplecpp_err = run(SIMPLECPP_EXE, cmd.replace('-E ', '', 1).split(' ')) From 645062b906ee8e3444a5e1bfbc0addb973c38ae1 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Fillion-Robin Date: Tue, 26 Aug 2025 03:43:04 -0400 Subject: [PATCH 4/7] refactor: modernize run-tests helpers with typing and safer subprocess handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Suggested-by: Oliver Stöneberg --- run-tests.py | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/run-tests.py b/run-tests.py index 89224f97..f2028385 100644 --- a/run-tests.py +++ b/run-tests.py @@ -1,3 +1,4 @@ +from __future__ import annotations import glob import os @@ -5,14 +6,14 @@ import subprocess import sys -def cleanup(out): - ret = '' - for s in out.decode('utf-8').split('\n'): - if len(s) > 1 and s[0] == '#': + +def cleanup(out: str) -> str: + parts = [] + for line in out.decode('utf-8').splitlines(): + if len(line) > 1 and line[0] == '#': continue - s = "".join(s.split()) - ret = ret + s - return ret + parts.append("".join(line.split())) + return "".join(parts) # Check for required compilers and exit if any are missing @@ -93,15 +94,23 @@ def cleanup(out): ] -def run(compiler_executable, compiler_args): - """Execute a compiler command and capture its output.""" +def run(compiler_executable: str, compiler_args: list[str]) -> tuple[int, str, str]: + """Execute a compiler command and capture its exit code, stdout, and stderr.""" compiler_cmd = [compiler_executable] compiler_cmd.extend(compiler_args) - p = subprocess.Popen(compiler_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - comm = p.communicate() - exit_code = p.returncode - output = cleanup(comm[0]) - error = comm[0].decode('utf-8').strip() + + try: + with subprocess.Popen(compiler_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as process: + stdout, stderr = process.communicate() + exit_code = process.returncode + except FileNotFoundError as e: + # Compiler not found + return (127, "", f"{e}") + except Exception as e: + return (1, "", f"{e}") + + output = cleanup(stdout) # bytes -> str via cleanup + error = (stderr or b"").decode("utf-8", errors="replace").strip() return (exit_code, output, error) From 8d9228d0b42e2154e2e7271397e8f9dc19394479 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Fillion-Robin Date: Tue, 26 Aug 2025 04:15:58 -0400 Subject: [PATCH 5/7] chore: simplify run-tests by using text mode for subprocess --- run-tests.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/run-tests.py b/run-tests.py index f2028385..292e78b1 100644 --- a/run-tests.py +++ b/run-tests.py @@ -9,7 +9,7 @@ def cleanup(out: str) -> str: parts = [] - for line in out.decode('utf-8').splitlines(): + for line in out.splitlines(): if len(line) > 1 and line[0] == '#': continue parts.append("".join(line.split())) @@ -100,7 +100,7 @@ def run(compiler_executable: str, compiler_args: list[str]) -> tuple[int, str, s compiler_cmd.extend(compiler_args) try: - with subprocess.Popen(compiler_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as process: + with subprocess.Popen(compiler_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8") as process: stdout, stderr = process.communicate() exit_code = process.returncode except FileNotFoundError as e: @@ -109,8 +109,8 @@ def run(compiler_executable: str, compiler_args: list[str]) -> tuple[int, str, s except Exception as e: return (1, "", f"{e}") - output = cleanup(stdout) # bytes -> str via cleanup - error = (stderr or b"").decode("utf-8", errors="replace").strip() + output = cleanup(stdout) + error = (stderr or "").strip() return (exit_code, output, error) From 2a310b3d79de61e69aff6e5a3b5ac412a33695c9 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Fillion-Robin Date: Tue, 26 Aug 2025 04:43:40 -0400 Subject: [PATCH 6/7] chore: Remove misleading exit codes from run-tests.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Suggested-by: Oliver Stöneberg --- run-tests.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/run-tests.py b/run-tests.py index 292e78b1..ab91a755 100644 --- a/run-tests.py +++ b/run-tests.py @@ -99,15 +99,9 @@ def run(compiler_executable: str, compiler_args: list[str]) -> tuple[int, str, s compiler_cmd = [compiler_executable] compiler_cmd.extend(compiler_args) - try: - with subprocess.Popen(compiler_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8") as process: - stdout, stderr = process.communicate() - exit_code = process.returncode - except FileNotFoundError as e: - # Compiler not found - return (127, "", f"{e}") - except Exception as e: - return (1, "", f"{e}") + with subprocess.Popen(compiler_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8") as process: + stdout, stderr = process.communicate() + exit_code = process.returncode output = cleanup(stdout) error = (stderr or "").strip() From 89e6b317c02fda42e2524326ffa062a950eab646 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Fillion-Robin Date: Thu, 28 Aug 2025 10:46:49 -0400 Subject: [PATCH 7/7] chore: Drop unnecessary `__future__.annotations` import in test runner `from __future__ import annotations` is useful in importable packages to enable postponed evaluation of annotations. However, in a standalone script like `run-tests.py`, it adds no value. --- run-tests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/run-tests.py b/run-tests.py index ab91a755..8810bf2b 100644 --- a/run-tests.py +++ b/run-tests.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import glob import os import shutil