[LIT] Use forward slashes in substitutions when LLVM_WINDOWS_PREFER_FORWARD_SLASH is set (#179865)

When building with `-DLLVM_WINDOWS_PREFER_FORWARD_SLASH=ON`, tools like
lld output paths with forward slashes on Windows. However, lit's default
substitutions (`%t`, `%p`) typically use backslashes on Windows, causing
FileCheck failures in tests that strictly match path separators.

This patch propagates the `LLVM_WINDOWS_PREFER_FORWARD_SLASH` build flag
to llvm-lit via `builtin_parameters`. It also updates lit's TestRunner
to respect the 'use_normalized_slashes' parameter. When enabled, lit
normalizes paths in substitutions to use forward slashes, ensuring that
test expectations align with the tool output.

With this fix, the number of failed tests with
`-DLLVM_WINDOWS_PREFER_FORWARD_SLASH=ON` changes as follow:

- The total number of failed tests: 303 -> 168
- Break down:
  - `Builtins-i386-windows` tests: 99 -> 0
  - `Clang` tests: 28 -> 5
  - `Clang Tools` tests: 7 -> 1
  - `LLVM` tests: 6 -> 4
  - `lld` tests: 2 -> 0
- Other failed tests:
  - `glang`: 1
  - `Clang-Unit`: 5
  - `Clangd`: 3
  - `Clangd Unit Tests`: 150
  - `LLVM-Unit`: 1
This commit is contained in:
Junji Watanabe 2026-03-03 14:45:06 +09:00 committed by GitHub
parent 75b0cf39b2
commit 4ea39c43e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 66 additions and 13 deletions

View File

@ -216,6 +216,8 @@ class LitConfig(object):
# Step out of _write_message, and then out of wrapper.
f = f.f_back.f_back
file = os.path.abspath(inspect.getsourcefile(f))
if lit.util.pythonize_bool(self.params.get("use_normalized_slashes")):
file = file.replace("\\", "/")
line = inspect.getlineno(f)
sys.stderr.write(
"%s: %s:%d: %s: %s\n" % (self.progname, file, line, kind, message)

View File

@ -89,12 +89,13 @@ class ShellEnvironment(object):
we maintain a dir stack for pushd/popd.
"""
def __init__(self, cwd, env, umask=-1, ulimit=None):
def __init__(self, cwd, env, umask=-1, ulimit=None, normalize_slashes=False):
self.cwd = cwd
self.env = dict(env)
self.umask = umask
self.dirStack = []
self.ulimit = ulimit if ulimit else {}
self.normalize_slashes = normalize_slashes
def change_dir(self, newdir):
if os.path.isabs(newdir):
@ -723,7 +724,7 @@ def processRedirects(cmd, stdin_source, cmd_shenv, opened_files):
return std_fds
def _expandLateSubstitutions(cmd, arguments, cwd):
def _expandLateSubstitutions(cmd, arguments, cwd, normalize_slashes=False):
for i, arg in enumerate(arguments):
if not isinstance(arg, str):
continue
@ -732,6 +733,8 @@ def _expandLateSubstitutions(cmd, arguments, cwd):
filePath = match.group(1)
if not os.path.isabs(filePath):
filePath = os.path.join(cwd, filePath)
if normalize_slashes:
filePath = filePath.replace("\\", "/")
try:
with open(filePath) as fileHandle:
return fileHandle.read()
@ -815,7 +818,9 @@ def _executeShCmd(cmd, shenv, results, timeoutHelper):
not_crash = False
# Expand all late substitutions.
args = _expandLateSubstitutions(j, args, cmd_shenv.cwd)
args = _expandLateSubstitutions(
j, args, cmd_shenv.cwd, cmd_shenv.normalize_slashes
)
while True:
if args[0] == "env":
@ -826,7 +831,12 @@ def _executeShCmd(cmd, shenv, results, timeoutHelper):
# env FOO=1 llc < %s | env BAR=2 llvm-mc | FileCheck %s
# env FOO=1 %{another_env_plus_cmd} | FileCheck %s
if cmd_shenv is shenv:
cmd_shenv = ShellEnvironment(shenv.cwd, shenv.env, shenv.umask)
cmd_shenv = ShellEnvironment(
shenv.cwd,
shenv.env,
shenv.umask,
normalize_slashes=shenv.normalize_slashes,
)
args = updateEnv(cmd_shenv, args)
if not args:
# Return the environment variables if no argument is provided.
@ -1225,7 +1235,10 @@ def executeScriptInternal(
results = []
timeoutInfo = None
shenv = ShellEnvironment(cwd, test.config.environment)
normalize_slashes = litConfig.params.get("use_normalized_slashes", False)
shenv = ShellEnvironment(
cwd, test.config.environment, normalize_slashes=normalize_slashes
)
shenv.env["LIT_CURRENT_TESTCASE"] = test.getFullName()
exitCode, timeoutInfo = executeShCmd(
@ -1524,11 +1537,25 @@ def getDefaultSubstitutions(test, tmpDir, tmpBase, normalize_slashes=False):
substitutions.append(("%{s:basename}", sourceBaseName))
substitutions.append(("%{t:stem}", tmpBaseName))
fs_sep = os.path.sep
if normalize_slashes:
fs_sep = "/"
substitutions.extend(
[
("%{fs-src-root}", pathlib.Path(sourcedir).anchor),
("%{fs-tmp-root}", pathlib.Path(tmpBase).anchor),
("%{fs-sep}", os.path.sep),
(
"%{fs-src-root}",
pathlib.Path(sourcedir).anchor.replace("\\", "/")
if normalize_slashes
else pathlib.Path(sourcedir).anchor,
),
(
"%{fs-tmp-root}",
pathlib.Path(tmpBase).anchor.replace("\\", "/")
if normalize_slashes
else pathlib.Path(tmpBase).anchor,
),
("%{fs-sep}", fs_sep),
]
)
@ -2465,7 +2492,11 @@ def executeShTest(
tmpDir, tmpBase = getTempPaths(test)
substitutions = list(extra_substitutions)
substitutions += getDefaultSubstitutions(
test, tmpDir, tmpBase, normalize_slashes=useExternalSh
test,
tmpDir,
tmpBase,
normalize_slashes=useExternalSh
or litConfig.params.get("use_normalized_slashes", False),
)
conditions = {feature: True for feature in test.config.available_features}
script = applySubstitutions(

View File

@ -57,6 +57,10 @@ class LLVMConfig(object):
self.lit_config.diagnostic_level_enabled("note")
and lit_path_displayed is False
):
if lit.util.pythonize_bool(
self.lit_config.params.get("use_normalized_slashes")
):
path = path.replace("\\", "/")
self.lit_config.note("using lit tools: {}".format(path))
lit_path_displayed = True
@ -557,6 +561,10 @@ class LLVMConfig(object):
if tool:
tool = os.path.normpath(tool)
if lit.util.pythonize_bool(
self.lit_config.params.get("use_normalized_slashes")
):
tool = tool.replace("\\", "/")
if not quiet:
self.lit_config.note("using {}: {}".format(name, tool))
return tool

View File

@ -64,14 +64,21 @@ for attribute in ("llvm_tools_dir", "lit_tools_dir"):
# within %{inputs}'s test suites. Thus, %{lit} clears environment variables
# that can affect FileCheck's output. It also includes "--order=lexical -j1"
# to ensure predictable test order, as it is often required for FileCheck
# matches.
# matches. It also specifies "-D use_normalized_slashes={0,1}" to be aligned
# with the slashes in the substitutions (e.g, %t, %p) for Windows since it
# switches between forward slashes and backslashes based on
# LLVM_WINDOWS_PREFER_FORWARD_SLASH build config.
config.substitutions.append(("%{inputs}", "Inputs"))
config.substitutions.append(("%{lit}", "%{lit-no-order-opt} --order=lexical"))
use_normalized_slashes = "0"
if lit.util.pythonize_bool(lit_config.params.get("use_normalized_slashes")):
use_normalized_slashes = "1"
config.substitutions.append(
(
"%{lit-no-order-opt}",
"{env} %{{python}} {lit} -j1".format(
env="env -u FILECHECK_OPTS", lit=os.path.join(lit_path, "lit.py")
"{env} %{{python}} {lit} -j1 -D use_normalized_slashes={use_normalized_slashes}".format(
env="env -u FILECHECK_OPTS", lit=os.path.join(lit_path, "lit.py"),
use_normalized_slashes=use_normalized_slashes,
),
)
)

View File

@ -3,7 +3,7 @@
# TODO(boomanaiden154): This sometimes fails, possibly due to buffers not being flushed.
# ALLOW_RETRIES: 2
# RUN: env LIT_USE_INTERNAL_SHELL=1 not %{lit} -v %{inputs}/shtest-readfile | FileCheck -match-full-lines -DTEMP_PATH=%S%{fs-sep}Inputs%{fs-sep}shtest-readfile%{fs-sep}Output %s
# RUN: env LIT_USE_INTERNAL_SHELL=1 not %{lit} -v %{inputs}/shtest-readfile | FileCheck -match-full-lines -DTEMP_PATH=%/S/Inputs/shtest-readfile/Output %s
# CHECK: -- Testing: 5 tests{{.*}}

View File

@ -25,6 +25,11 @@ builtin_parameters['config_map'] = config_map
llvm_source_root = path(r'@LLVM_SOURCE_DIR@')
sys.path.insert(0, os.path.join(llvm_source_root, 'utils', 'lit'))
import lit.util
if lit.util.pythonize_bool('@LLVM_WINDOWS_PREFER_FORWARD_SLASH@'):
builtin_parameters['use_normalized_slashes'] = True
if __name__=='__main__':
from lit.main import main
main(builtin_parameters)