Skip to content

Conversation

boomanaiden154
Copy link
Contributor

This patch adds support for the new lit %{readfile:}
substitution to the external shell. The implementation currently just
appends some test commands to ensure the file exists and uses a subshell
with cat. This is intended to enable running tests using the
substitution in the external shell before we fully switch over to the
internal shell.

This code is designed to be temporary with us deleting it once
everything has migrated over to the internal shell and we are able to
remove the external shell code paths.

@llvmbot
Copy link
Member

llvmbot commented Sep 17, 2025

@llvm/pr-subscribers-testing-tools

Author: Aiden Grossman (boomanaiden154)

Changes

This patch adds support for the new lit %{readfile:<filename>}
substitution to the external shell. The implementation currently just
appends some test commands to ensure the file exists and uses a subshell
with cat. This is intended to enable running tests using the
substitution in the external shell before we fully switch over to the
internal shell.

This code is designed to be temporary with us deleting it once
everything has migrated over to the internal shell and we are able to
remove the external shell code paths.


Full diff: https://github.com/llvm/llvm-project/pull/159431.diff

4 Files Affected:

  • (modified) llvm/utils/lit/lit/TestRunner.py (+18)
  • (modified) llvm/utils/lit/tests/Inputs/shtest-readfile/lit.cfg (+13-1)
  • (added) llvm/utils/lit/tests/shtest-readfile-external.py (+21)
  • (modified) llvm/utils/lit/tests/shtest-readfile.py (+1-1)
diff --git a/llvm/utils/lit/lit/TestRunner.py b/llvm/utils/lit/lit/TestRunner.py
index 045472429b6e4..53eeb2f85b48b 100644
--- a/llvm/utils/lit/lit/TestRunner.py
+++ b/llvm/utils/lit/lit/TestRunner.py
@@ -2412,6 +2412,20 @@ def runOnce(
         status, output, attempts=i + 1, max_allowed_attempts=attempts
     )
 
+def _expandLateSubstitutionsExternal(commandLine):
+    filePaths = []
+    def _replaceReadFile(match):
+        filePath = match.group(1)
+        filePaths.append(filePath)
+        return "$(cat %s)" % filePath
+
+    commandLine = re.sub(r"%{readfile:([^}]*)}", _replaceReadFile, commandLine)
+    # Add test commands before the command to check if the file exists as
+    # cat inside a subshell will never return a non-zero exit code outside
+    # of the subshell.
+    for filePath in filePaths:
+        commandLine = "%s && test -e %s" % (commandLine, filePath)
+    return commandLine
 
 def executeShTest(
     test, litConfig, useExternalSh, extra_substitutions=[], preamble_commands=[]
@@ -2443,4 +2457,8 @@ def executeShTest(
         recursion_limit=test.config.recursiveExpansionLimit,
     )
 
+    if useExternalSh:
+        for index, command in enumerate(script):
+            script[index] = _expandLateSubstitutionsExternal(command)
+
     return _runShTest(test, litConfig, useExternalSh, script, tmpBase)
diff --git a/llvm/utils/lit/tests/Inputs/shtest-readfile/lit.cfg b/llvm/utils/lit/tests/Inputs/shtest-readfile/lit.cfg
index cf453e1ea786f..ee496674fdb62 100644
--- a/llvm/utils/lit/tests/Inputs/shtest-readfile/lit.cfg
+++ b/llvm/utils/lit/tests/Inputs/shtest-readfile/lit.cfg
@@ -1,7 +1,19 @@
+import os
+
 import lit.formats
+import lit.util
 
 config.name = "shtest-readfile"
 config.suffixes = [".txt"]
-config.test_format = lit.formats.ShTest(execute_external=False)
+lit_shell_env = os.environ.get("LIT_USE_INTERNAL_SHELL")
+use_lit_shell = lit.util.pythonize_bool(lit_shell_env)
+config.test_format = lit.formats.ShTest(execute_external=not use_lit_shell)
 config.test_source_root = None
 config.test_exec_root = None
+
+# If we are testing with the external shell, remove the fake-externals from
+# PATH so that we use mkdir in the tests.
+if not use_lit_shell:
+    path_parts = config.environment["PATH"].split(os.path.pathsep)
+    path_parts = [path_part for path_part in path_parts if "fake-externals" not in path_part]
+    config.environment["PATH"] = os.path.pathsep.join(path_parts)
diff --git a/llvm/utils/lit/tests/shtest-readfile-external.py b/llvm/utils/lit/tests/shtest-readfile-external.py
new file mode 100644
index 0000000000000..5825ad674ba05
--- /dev/null
+++ b/llvm/utils/lit/tests/shtest-readfile-external.py
@@ -0,0 +1,21 @@
+## Tests the readfile substitution.
+
+# RUN: env LIT_USE_INTERNAL_SHELL=0 not %{lit} -a -v %{inputs}/shtest-readfile | FileCheck -match-full-lines -DTEMP_PATH=%S/Inputs/shtest-readfile/Output %s
+
+# CHECK: -- Testing: 4 tests{{.*}}
+
+# CHECK-LABEL: FAIL: shtest-readfile :: absolute-paths.txt ({{[^)]*}})
+# CHECK: echo $(cat [[TEMP_PATH]]/absolute-paths.txt.tmp) && test -e /home/gha/llvm-project/build/utils/lit/tests/Inputs/shtest-readfile/Output/absolute-paths.txt.tmp {{.*}}
+# CHECK: + echo hello
+
+# CHECK-LABEL: FAIL: shtest-readfile :: file-does-not-exist.txt ({{[^)]*}})
+# CHECK: echo $(cat /file/does/not/exist) && test -e /file/does/not/exist {{.*}}
+# CHECK: cat: /file/does/not/exist: No such file or directory
+
+# CHECK-LABEL: FAIL: shtest-readfile :: relative-paths.txt ({{[^)]*}})
+# CHECK: echo $(cat rel_path_test_folder/test_file) && test -e rel_path_test_folder/test_file {{.*}}
+# CHECK: + echo hello
+
+# CHECK-LABEL: FAIL: shtest-readfile :: two-same-line.txt ({{[^)]*}})
+# CHECK: echo $(cat /home/gha/llvm-project/build/utils/lit/tests/Inputs/shtest-readfile/Output/two-same-line.txt.tmp.1) $(cat /home/gha/llvm-project/build/utils/lit/tests/Inputs/shtest-readfile/Output/two-same-line.txt.tmp.2) && test -e /home/gha/llvm-project/build/utils/lit/tests/Inputs/shtest-readfile/Output/two-same-line.txt.tmp.1 && test -e /home/gha/llvm-project/build/utils/lit/tests/Inputs/shtest-readfile/Output/two-same-line.txt.tmp.2 {{.*}}
+# CHECK: + echo hello bye
diff --git a/llvm/utils/lit/tests/shtest-readfile.py b/llvm/utils/lit/tests/shtest-readfile.py
index c25a643b4eff8..2200576d05394 100644
--- a/llvm/utils/lit/tests/shtest-readfile.py
+++ b/llvm/utils/lit/tests/shtest-readfile.py
@@ -1,6 +1,6 @@
 ## Tests the readfile substitution.
 
-# RUN: not %{lit} -a -v %{inputs}/shtest-readfile | FileCheck -match-full-lines -DTEMP_PATH=%S/Inputs/shtest-readfile/Output %s
+# RUN: env LIT_USE_INTERNAL_SHELL=1 not %{lit} -a -v %{inputs}/shtest-readfile | FileCheck -match-full-lines -DTEMP_PATH=%S/Inputs/shtest-readfile/Output %s
 
 # CHECK: -- Testing: 4 tests{{.*}}
 

Copy link

github-actions bot commented Sep 17, 2025

✅ With the latest revision this PR passed the Python code formatter.

Created using spr 1.3.6

[skip ci]
Created using spr 1.3.6
@boomanaiden154 boomanaiden154 changed the base branch from users/boomanaiden154/main.lit-add-support-for-readfile-to-external-shell to main September 19, 2025 15:50
@boomanaiden154 boomanaiden154 merged commit cfabbf0 into main Sep 19, 2025
14 of 15 checks passed
@boomanaiden154 boomanaiden154 deleted the users/boomanaiden154/lit-add-support-for-readfile-to-external-shell branch September 19, 2025 22:04
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request Sep 19, 2025
This patch adds support for the new lit %{readfile:<filename>}
substitution to the external shell. The implementation currently just
appends some test commands to ensure the file exists and uses a subshell
with cat. This is intended to enable running tests using the
substitution in the external shell before we fully switch over to the
internal shell.

This code is designed to be temporary with us deleting it once
everything has migrated over to the internal shell and we are able to
remove the external shell code paths.

Reviewers: petrhosek, cmtice, pogo59, ilovepi, arichardson

Reviewed By: cmtice

Pull Request: llvm/llvm-project#159431
schwitanski pushed a commit to RWTH-HPC/llvm-lit-mirror that referenced this pull request Sep 20, 2025
This patch adds support for the new lit %{readfile:<filename>}
substitution to the external shell. The implementation currently just
appends some test commands to ensure the file exists and uses a subshell
with cat. This is intended to enable running tests using the
substitution in the external shell before we fully switch over to the
internal shell.

This code is designed to be temporary with us deleting it once
everything has migrated over to the internal shell and we are able to
remove the external shell code paths.

Reviewers: petrhosek, cmtice, pogo59, ilovepi, arichardson

Reviewed By: cmtice

Pull Request: llvm/llvm-project#159431
@mikaelholmen
Copy link
Collaborator

Hi,

Is it only me that see utils/lit/tests/shtest-readfile-external.py failing randomly every now and then?
I don't know what's happening but there seem to be something flaky about this test because in downstream build bots we sometimes see it failing and then passing again on the same commit.

I just failed for me like

FAIL: lit :: shtest-readfile-external.py (89176 of 103680)
******************** TEST 'lit :: shtest-readfile-external.py' FAILED ********************
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 4
env LIT_USE_INTERNAL_SHELL=0 not env -u FILECHECK_OPTS "/app/vbuild/RHEL8-x86_64/python/3.8.0/bin/python3.8" /repo/llvm/utils/lit/lit.py -j1 --order=lexical -a -v Inputs/shtest-readfile | FileCheck -match-full-lines -DTEMP_PATH=/repo/llvm/build-all-builtins/utils/lit/tests/Inputs/shtest-readfile/Output /repo/llvm/build-all-builtins/utils/lit/tests/shtest-readfile-external.py
# executed command: env LIT_USE_INTERNAL_SHELL=0 not env -u FILECHECK_OPTS /app/vbuild/RHEL8-x86_64/python/3.8.0/bin/python3.8 /repo/llvm/utils/lit/lit.py -j1 --order=lexical -a -v Inputs/shtest-readfile
# note: command had no output on stdout or stderr
# executed command: FileCheck -match-full-lines -DTEMP_PATH=/repo/llvm/build-all-builtins/utils/lit/tests/Inputs/shtest-readfile/Output /repo/llvm/build-all-builtins/utils/lit/tests/shtest-readfile-external.py
# .---command stderr------------
# | /repo/llvm/build-all-builtins/utils/lit/tests/shtest-readfile-external.py:22:10: error: CHECK: expected string not found in input
# | # CHECK: + echo hello bye
# |          ^
# | <stdin>:88:526: note: scanning from here
# | echo $(cat /repo/llvm/build-all-builtins/utils/lit/tests/Inputs/shtest-readfile/Output/two-same-line.txt.tmp.1) $(cat /repo/llvm/build-all-builtins/utils/lit/tests/Inputs/shtest-readfile/Output/two-same-line.txt.tmp.2) && test -e /repo/llvm/build-all-builtins/utils/lit/tests/Inputs/shtest-readfile/Output/two-same-line.txt.tmp.1 && test -e /repo/llvm/build-all-builtins/utils/lit/tests/Inputs/shtest-readfile/Output/two-same-line.txt.tmp.2 # RUN: at line 5
# |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              ^
# | <stdin>:91:1: note: possible intended match here
# | + echo hello
# | ^
# |
# | Input file: <stdin>
# | Check file: /repo/llvm/build-all-builtins/utils/lit/tests/shtest-readfile-external.py
# |
# | -dump-input=help explains the following input dump.
# |
# | Input was:
# | <<<<<<
# |             .
# |             .
# |             .
# |            83: --
# |            84: echo -n "hello" > /repo/llvm/build-all-builtins/utils/lit/tests/Inputs/shtest-readfile/Output/two-same-line.txt.tmp.1 # RUN: at line 3
# |            85: + echo -n hello
# |            86: echo -n "bye" > /repo/llvm/build-all-builtins/utils/lit/tests/Inputs/shtest-readfile/Output/two-same-line.txt.tmp.2 # RUN: at line 4
# |            87: + echo -n bye
# |            88: echo $(cat /repo/llvm/build-all-builtins/utils/lit/tests/Inputs/shtest-readfile/Output/two-same-line.txt.tmp.1) $(cat /repo/llvm/build-all-builtins/utils/lit/tests/Inputs/shtest-readfile/Output/two-same-line.txt.tmp.2) && test -e /repo/llvm/build-all-builtins/utils/lit/tests/Inputs/shtest-readfile/Output/two-same-line.txt.tmp.1 && test -e /repo/llvm/build-all-builtins/utils/lit/tests/Inputs/shtest-readfile/Output/two-same-line.txt.tmp.2 # RUN: at line 5
# | check:22'0                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  X error: no match found
# |            89: ++ cat /repo/llvm/build-all-builtins/utils/lit/tests/Inputs/shtest-readfile/Output/two-same-line.txt.tmp.1
# | check:22'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |            90: ++ cat /repo/llvm/build-all-builtins/utils/lit/tests/Inputs/shtest-readfile/Output/two-same-line.txt.tmp.2
# | check:22'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |            91: + echo hello
# | check:22'0     ~~~~~~~~~~~~~
# | check:22'1     ?             possible intended match
# |            92: + test -e /repo/llvm/build-all-builtins/utils/lit/tests/Inputs/shtest-readfile/Output/two-same-line.txt.tmp.1
# | check:22'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |            93: + test -e /repo/llvm/build-all-builtins/utils/lit/tests/Inputs/shtest-readfile/Output/two-same-line.txt.tmp.2
# | check:22'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |            94: not echo return # RUN: at line 8
# | check:22'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |            95: + not echo return
# | check:22'0     ~~~~~~~~~~~~~~~~~~
# |            96:
# | check:22'0     ~
# |             .
# |             .
# |             .
# | >>>>>>
# `-----------------------------
# error: command failed with exit status: 1

--

********************
********************
Failed Tests (1):
  lit :: shtest-readfile-external.py

@jh7370
Copy link
Collaborator

jh7370 commented Sep 24, 2025

That looks suspiciously like a buffered output not being flushed properly somewhere, perhaps being flushed in some racy manner somewhere within the system implementation. I don't really have a concrete suggestion as to where though, nor can I be certain that is the issue.

@boomanaiden154
Copy link
Contributor Author

Is it only me that see utils/lit/tests/shtest-readfile-external.py failing randomly every now and then?
I don't know what's happening but there seem to be something flaky about this test because in downstream build bots we sometimes see it failing and then passing again on the same commit.

Yeah, I've seen this too in our builders. I'm of the same opinion as James that it seems like some buffering issue. I haven't had time to investigate. I'll hopefully get to it later today.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants