From 43be44996c6531348142979c8110e7faf2b3d3d6 Mon Sep 17 00:00:00 2001 From: Rodrigo Gomez Date: Mon, 9 Nov 2026 11:13:17 -0500 Subject: [PATCH] Fix paths with spaces in lit check updater --- scripts/update_lit_checks.py | 33 +++++++++++++++----- test/unit/test_update_lit_checks_paths.py | 38 +++++++++++++++++++++++ 2 files changed, 63 insertions(+), 8 deletions(-) create mode 100755 test/unit/test_update_lit_checks_paths.py diff --git a/scripts/update_lit_checks.py b/scripts/update_lit_checks.py index 0e6801e663c..be81c277e16 100755 --- a/scripts/update_lit_checks.py +++ b/scripts/update_lit_checks.py @@ -23,6 +23,7 @@ import glob import os import re +import shlex import subprocess import sys import tempfile @@ -109,15 +110,31 @@ def find_run_lines(test, lines): def run_command(args, test, tmp, command): env = dict(os.environ) env['PATH'] = args.binaryen_bin + os.pathsep + env['PATH'] - command = command.replace('%s', test) - command = command.replace('%S', os.path.dirname(test)) - command = command.replace('%t', tmp) - match = re.match(r'^(.*)\s*foreach\s+(\S+)\s+(\S+)\s+(.*)$', command) + def replace_paths(text): + return text.replace('%s', test).replace('%S', os.path.dirname(test)).replace('%t', tmp) + + def quote_path(path): + return subprocess.list2cmdline([path]) if os.name == 'nt' else shlex.quote(path) + + def replace_shell_paths(text): + def replace(match): + token = match.group() + if not any(path in token for path in ('%s', '%S', '%t')): + return token + if token[0] in {'"', "'"}: + token = token[1:-1] + return quote_path(replace_paths(token)) + + return re.sub(r'''"[^"]*"|'[^']*'|%[sSt]''', replace, text) + + # Parse foreach arguments before expanding paths, which can contain spaces. + argument = r'''(?:"[^"]*"|'[^']*'|\S+)''' + match = re.match(r'^(.*)\s*foreach\s+(' + argument + r')\s+(' + argument + r')\s+(.*)$', command) if match: prefix = match.group(1) - infile = match.group(2) - tempfile = match.group(3) + infile = replace_paths(match.group(2).strip('\'"')) + tempfile = replace_paths(match.group(3).strip('\'"')) cmd_rest = match.group(4) outputs = [] @@ -125,14 +142,14 @@ def run_command(args, test, tmp, command): tempname = tempfile + '.' + str(i) with open(tempname, 'w') as temp: print(module, file=temp) - new_command = prefix + ' ' + cmd_rest + ' ' + tempname + new_command = replace_shell_paths(prefix + ' ' + cmd_rest) + ' ' + quote_path(tempname) out = subprocess.check_output(new_command, shell=True, env=env).decode('utf-8') outputs.append(out) return f"\n{INTERNAL_SEPARATOR}\n".join(outputs) else: assert 'foreach' not in command, 'bad foreach matching: ' + command - return subprocess.check_output(command, shell=True, env=env).decode('utf-8') + return subprocess.check_output(replace_shell_paths(command), shell=True, env=env).decode('utf-8') def find_end(module, start): diff --git a/test/unit/test_update_lit_checks_paths.py b/test/unit/test_update_lit_checks_paths.py new file mode 100755 index 00000000000..a3b081792c9 --- /dev/null +++ b/test/unit/test_update_lit_checks_paths.py @@ -0,0 +1,38 @@ +import os +import subprocess +import sys +import tempfile +from pathlib import Path + +from scripts.test import shared + +from . import utils + + +class UpdateLitChecksPathsTest(utils.BinaryenTestCase): + def test_paths_with_spaces(self): + with tempfile.TemporaryDirectory(prefix='update lit checks ') as temp: + root = Path(temp) + # Exercise %s, %S, and %t, with both quoted and unquoted RUN lines. + for run in ( + 'wasm-opt %s -S -o -', + 'wasm-opt "%s" -S -o -', + 'wasm-opt "%S/input file.wat" -S -o %t.wat && wasm-opt %t.wat -S -o -', + 'foreach %s %t wasm-opt -S -o -', + 'foreach "%s" "%t" wasm-opt -S -o -', + ): + with self.subTest(run=run): + test = root / 'input file.wat' + test.write_text(';; RUN: ' + run + ' | filecheck %s\n' + '(module\n' + ' (func $answer (result i32) (i32.const 42))\n' + ')\n') + env = dict(os.environ, TMPDIR=temp, TEMP=temp, TMP=temp) + command = [sys.executable, + os.path.join(shared.options.binaryen_root, 'scripts', 'update_lit_checks.py'), + '--binaryen-bin', shared.options.binaryen_bin, + '--force', '--dry-run', str(test)] + result = subprocess.run(command, env=env, capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn(';; CHECK:', result.stdout) + self.assertIn('(i32.const 42)', result.stdout)