[llvm] [lit] Add readfile substitution (PR #158441)

Aiden Grossman via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 17 09:18:41 PDT 2025


https://github.com/boomanaiden154 updated https://github.com/llvm/llvm-project/pull/158441

>From 98b433083ec1f9dab8343191ac1c2451cac3373b Mon Sep 17 00:00:00 2001
From: Aiden Grossman <aidengrossman at google.com>
Date: Sat, 13 Sep 2025 21:44:58 +0000
Subject: [PATCH 1/4] =?UTF-8?q?[=F0=9D=98=80=F0=9D=97=BD=F0=9D=97=BF]=20in?=
 =?UTF-8?q?itial=20version?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Created using spr 1.3.6
---
 llvm/docs/CommandGuide/lit.rst                  |  1 +
 llvm/test/tools/llvm-cgdata/empty.test          |  1 +
 llvm/utils/lit/lit/TestRunner.py                | 17 +++++++++++++++++
 .../Inputs/shtest-readfile/.lit_test_times.txt  |  1 +
 .../Output/absolute-paths.txt.tmp               |  1 +
 .../Inputs/shtest-readfile/absolute-paths.txt   |  6 ++++++
 .../lit/tests/Inputs/shtest-readfile/lit.cfg    |  8 ++++++++
 .../Inputs/shtest-readfile/relative-paths.txt   |  7 +++++++
 .../Inputs/shtest-readfile/two-same-line.txt    |  8 ++++++++
 llvm/utils/lit/tests/shtest-readfile.py         | 17 +++++++++++++++++
 10 files changed, 67 insertions(+)
 create mode 100644 llvm/utils/lit/tests/Inputs/shtest-readfile/.lit_test_times.txt
 create mode 100644 llvm/utils/lit/tests/Inputs/shtest-readfile/Output/absolute-paths.txt.tmp
 create mode 100644 llvm/utils/lit/tests/Inputs/shtest-readfile/absolute-paths.txt
 create mode 100644 llvm/utils/lit/tests/Inputs/shtest-readfile/lit.cfg
 create mode 100644 llvm/utils/lit/tests/Inputs/shtest-readfile/relative-paths.txt
 create mode 100644 llvm/utils/lit/tests/Inputs/shtest-readfile/two-same-line.txt
 create mode 100644 llvm/utils/lit/tests/shtest-readfile.py

diff --git a/llvm/docs/CommandGuide/lit.rst b/llvm/docs/CommandGuide/lit.rst
index 15c249d8e6d31..359e0c3e81d0e 100644
--- a/llvm/docs/CommandGuide/lit.rst
+++ b/llvm/docs/CommandGuide/lit.rst
@@ -664,6 +664,7 @@ TestRunner.py:
                          Otherwise, %t but with a single leading ``/`` removed.
  %:T                     On Windows, %/T but a ``:`` is removed if its the second character.
                          Otherwise, %T but with a single leading ``/`` removed.
+ %{readfile:<filename>}  Reads the file specified.
  ======================= ==============
 
 Other substitutions are provided that are variations on this base set and
diff --git a/llvm/test/tools/llvm-cgdata/empty.test b/llvm/test/tools/llvm-cgdata/empty.test
index 52d0dfb87623f..7e42db5ed8512 100644
--- a/llvm/test/tools/llvm-cgdata/empty.test
+++ b/llvm/test/tools/llvm-cgdata/empty.test
@@ -35,3 +35,4 @@ RUN: printf '\000\000\000\000' >> %t_header.cgdata
 RUN: printf '\040\000\000\000\000\000\000\000' >> %t_header.cgdata
 RUN: printf '\040\000\000\000\000\000\000\000' >> %t_header.cgdata
 RUN: diff %t_header.cgdata %t_emptyheader.cgdata
+RUN: echo %{readfile:/tmp/test} > /tmp/test
diff --git a/llvm/utils/lit/lit/TestRunner.py b/llvm/utils/lit/lit/TestRunner.py
index 90c2c6479b004..8d565e8bad53a 100644
--- a/llvm/utils/lit/lit/TestRunner.py
+++ b/llvm/utils/lit/lit/TestRunner.py
@@ -719,6 +719,20 @@ def processRedirects(cmd, stdin_source, cmd_shenv, opened_files):
 
     return std_fds
 
+def _expandLateSubstitutions(arguments, cwd):
+    for i, arg in enumerate(arguments):
+        if not isinstance(arg, str):
+            continue
+        def _replaceReadFile(match):
+            filePath = match.group(1)
+            if not os.path.isabs(filePath):
+                filePath = os.path.join(cwd, filePath)
+            with open(filePath) as fileHandle:
+                return fileHandle.read()
+        
+        arguments[i] = re.sub(r"%{readfile:([^}]*)}", _replaceReadFile, arg)
+    
+    return arguments
 
 def _executeShCmd(cmd, shenv, results, timeoutHelper):
     if timeoutHelper.timeoutReached():
@@ -834,6 +848,9 @@ def _executeShCmd(cmd, shenv, results, timeoutHelper):
         # Ensure args[0] is hashable.
         args[0] = expand_glob(args[0], cmd_shenv.cwd)[0]
 
+        # Expand all late substitutions
+        args = _expandLateSubstitutions(args, cmd_shenv.cwd)
+
         inproc_builtin = inproc_builtins.get(args[0], None)
         if inproc_builtin and (args[0] != "echo" or len(cmd.commands) == 1):
             # env calling an in-process builtin is useless, so we take the safe
diff --git a/llvm/utils/lit/tests/Inputs/shtest-readfile/.lit_test_times.txt b/llvm/utils/lit/tests/Inputs/shtest-readfile/.lit_test_times.txt
new file mode 100644
index 0000000000000..9802a6bcd406a
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/shtest-readfile/.lit_test_times.txt
@@ -0,0 +1 @@
+-4.514933e-03 absolute-paths.txt
diff --git a/llvm/utils/lit/tests/Inputs/shtest-readfile/Output/absolute-paths.txt.tmp b/llvm/utils/lit/tests/Inputs/shtest-readfile/Output/absolute-paths.txt.tmp
new file mode 100644
index 0000000000000..b6fc4c620b67d
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/shtest-readfile/Output/absolute-paths.txt.tmp
@@ -0,0 +1 @@
+hello
\ No newline at end of file
diff --git a/llvm/utils/lit/tests/Inputs/shtest-readfile/absolute-paths.txt b/llvm/utils/lit/tests/Inputs/shtest-readfile/absolute-paths.txt
new file mode 100644
index 0000000000000..4246064cf7bfc
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/shtest-readfile/absolute-paths.txt
@@ -0,0 +1,6 @@
+## Tests that readfile works with absolute paths
+# RUN: echo -n "hello" > %t
+# RUN: echo %{readfile:%t}
+
+## Fail the test so we can assert on the output.
+# RUN: not echo return
diff --git a/llvm/utils/lit/tests/Inputs/shtest-readfile/lit.cfg b/llvm/utils/lit/tests/Inputs/shtest-readfile/lit.cfg
new file mode 100644
index 0000000000000..25651f2cd4832
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/shtest-readfile/lit.cfg
@@ -0,0 +1,8 @@
+import lit.formats
+
+config.name = "shtest-readfile"
+config.suffixes = [".txt"]
+config.test_format = lit.formats.ShTest(execute_external=False)
+config.test_source_root = None
+config.test_exec_root = None
+config.substitutions.append(("%{python}", '"%s"' % (sys.executable)))
diff --git a/llvm/utils/lit/tests/Inputs/shtest-readfile/relative-paths.txt b/llvm/utils/lit/tests/Inputs/shtest-readfile/relative-paths.txt
new file mode 100644
index 0000000000000..3d203d411379d
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/shtest-readfile/relative-paths.txt
@@ -0,0 +1,7 @@
+## Tests that readfile works with relative paths
+# RUN: mkdir -p rel_path_test_folder
+# RUN: echo -n "hello" > rel_path_test_folder/test_file
+# RUN: echo %{readfile:rel_path_test_folder/test_file}
+
+## Fail the test so we can assert on the output.
+# RUN: not echo return
diff --git a/llvm/utils/lit/tests/Inputs/shtest-readfile/two-same-line.txt b/llvm/utils/lit/tests/Inputs/shtest-readfile/two-same-line.txt
new file mode 100644
index 0000000000000..6855d27d66d35
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/shtest-readfile/two-same-line.txt
@@ -0,0 +1,8 @@
+## Tests that readfile works with two substitutions on the same line to ensure the
+## regular expressions are setup correctly.
+# RUN: echo -n "hello" > %t.1
+# RUN: echo -n "bye" > %t.2
+# RUN: echo %{readfile:%t.1} %{readfile:%t.2}
+
+## Fail the test so we can assert on the output.
+# RUN: not echo return
diff --git a/llvm/utils/lit/tests/shtest-readfile.py b/llvm/utils/lit/tests/shtest-readfile.py
new file mode 100644
index 0000000000000..8ba8e53b32966
--- /dev/null
+++ b/llvm/utils/lit/tests/shtest-readfile.py
@@ -0,0 +1,17 @@
+## Tests the readfile substitution
+
+# RUN: not %{lit} -a -v %{inputs}/shtest-readfile | FileCheck -match-full-lines %s
+
+# CHECK: -- Testing: 3 tests{{.*}}
+
+# CHECK-LABEL: FAIL: shtest-readfile :: absolute-paths.txt ({{[^)]*}})
+# CHECK: echo hello
+# CHECK: # executed command: echo '%{readfile:{{.*}}}'
+
+# CHECK-LABEL: FAIL: shtest-readfile :: relative-paths.txt ({{[^)]*}})
+# CHECK: echo hello
+# CHECK: # executed command: echo '%{readfile:rel_path_test_folder/test_file}'
+
+# CHECK-LABEL: FAIL: shtest-readfile :: two-same-line.txt ({{[^)]*}})
+# CHECK: echo hello bye
+# CHECK: # executed command: echo '%{readfile:{{.*}}.1}' '%{readfile:{{.*}}.2}'

>From 7901d972ce08bf10617f9e07c90d2f83746dc52e Mon Sep 17 00:00:00 2001
From: Aiden Grossman <aidengrossman at google.com>
Date: Sat, 13 Sep 2025 21:51:22 +0000
Subject: [PATCH 2/4] formatting

Created using spr 1.3.6
---
 llvm/utils/lit/lit/TestRunner.py                            | 6 ++++--
 .../lit/tests/Inputs/shtest-readfile/.lit_test_times.txt    | 1 -
 .../Inputs/shtest-readfile/Output/absolute-paths.txt.tmp    | 1 -
 3 files changed, 4 insertions(+), 4 deletions(-)
 delete mode 100644 llvm/utils/lit/tests/Inputs/shtest-readfile/.lit_test_times.txt
 delete mode 100644 llvm/utils/lit/tests/Inputs/shtest-readfile/Output/absolute-paths.txt.tmp

diff --git a/llvm/utils/lit/lit/TestRunner.py b/llvm/utils/lit/lit/TestRunner.py
index 8d565e8bad53a..0268c4801aa07 100644
--- a/llvm/utils/lit/lit/TestRunner.py
+++ b/llvm/utils/lit/lit/TestRunner.py
@@ -719,19 +719,21 @@ def processRedirects(cmd, stdin_source, cmd_shenv, opened_files):
 
     return std_fds
 
+
 def _expandLateSubstitutions(arguments, cwd):
     for i, arg in enumerate(arguments):
         if not isinstance(arg, str):
             continue
+
         def _replaceReadFile(match):
             filePath = match.group(1)
             if not os.path.isabs(filePath):
                 filePath = os.path.join(cwd, filePath)
             with open(filePath) as fileHandle:
                 return fileHandle.read()
-        
+
         arguments[i] = re.sub(r"%{readfile:([^}]*)}", _replaceReadFile, arg)
-    
+
     return arguments
 
 def _executeShCmd(cmd, shenv, results, timeoutHelper):
diff --git a/llvm/utils/lit/tests/Inputs/shtest-readfile/.lit_test_times.txt b/llvm/utils/lit/tests/Inputs/shtest-readfile/.lit_test_times.txt
deleted file mode 100644
index 9802a6bcd406a..0000000000000
--- a/llvm/utils/lit/tests/Inputs/shtest-readfile/.lit_test_times.txt
+++ /dev/null
@@ -1 +0,0 @@
--4.514933e-03 absolute-paths.txt
diff --git a/llvm/utils/lit/tests/Inputs/shtest-readfile/Output/absolute-paths.txt.tmp b/llvm/utils/lit/tests/Inputs/shtest-readfile/Output/absolute-paths.txt.tmp
deleted file mode 100644
index b6fc4c620b67d..0000000000000
--- a/llvm/utils/lit/tests/Inputs/shtest-readfile/Output/absolute-paths.txt.tmp
+++ /dev/null
@@ -1 +0,0 @@
-hello
\ No newline at end of file

>From 82bf25b01ade95c5a44d22677d19eda10f7257b8 Mon Sep 17 00:00:00 2001
From: Aiden Grossman <aidengrossman at google.com>
Date: Sat, 13 Sep 2025 21:57:05 +0000
Subject: [PATCH 3/4] formatting

Created using spr 1.3.6
---
 llvm/utils/lit/lit/TestRunner.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/llvm/utils/lit/lit/TestRunner.py b/llvm/utils/lit/lit/TestRunner.py
index 0268c4801aa07..5daa8887e4c80 100644
--- a/llvm/utils/lit/lit/TestRunner.py
+++ b/llvm/utils/lit/lit/TestRunner.py
@@ -736,6 +736,7 @@ def _replaceReadFile(match):
 
     return arguments
 
+
 def _executeShCmd(cmd, shenv, results, timeoutHelper):
     if timeoutHelper.timeoutReached():
         # Prevent further recursion if the timeout has been hit

>From 1599e056b4f11f1b61a050b5907b0bbfc3e66d9a Mon Sep 17 00:00:00 2001
From: Aiden Grossman <aidengrossman at google.com>
Date: Wed, 17 Sep 2025 16:18:30 +0000
Subject: [PATCH 4/4] feedback

Created using spr 1.3.6
---
 llvm/utils/lit/lit/TestRunner.py                     | 12 ++++++++----
 .../Inputs/shtest-readfile/file-does-not-exist.txt   |  4 ++++
 llvm/utils/lit/tests/shtest-readfile.py              |  6 +++++-
 3 files changed, 17 insertions(+), 5 deletions(-)
 create mode 100644 llvm/utils/lit/tests/Inputs/shtest-readfile/file-does-not-exist.txt

diff --git a/llvm/utils/lit/lit/TestRunner.py b/llvm/utils/lit/lit/TestRunner.py
index 19462431e7deb..daa051dcfc4b1 100644
--- a/llvm/utils/lit/lit/TestRunner.py
+++ b/llvm/utils/lit/lit/TestRunner.py
@@ -720,7 +720,7 @@ def processRedirects(cmd, stdin_source, cmd_shenv, opened_files):
     return std_fds
 
 
-def _expandLateSubstitutions(arguments, cwd):
+def _expandLateSubstitutions(cmd, arguments, cwd):
     for i, arg in enumerate(arguments):
         if not isinstance(arg, str):
             continue
@@ -729,8 +729,12 @@ def _replaceReadFile(match):
             filePath = match.group(1)
             if not os.path.isabs(filePath):
                 filePath = os.path.join(cwd, filePath)
-            with open(filePath) as fileHandle:
-                return fileHandle.read()
+            try:
+                with open(filePath) as fileHandle:
+                    return fileHandle.read()
+            except FileNotFoundError as error:
+                print(error)
+                raise InternalShellError(cmd, "File does not exist: %s" % filePath)
 
         arguments[i] = re.sub(r"%{readfile:([^}]*)}", _replaceReadFile, arg)
 
@@ -852,7 +856,7 @@ def _executeShCmd(cmd, shenv, results, timeoutHelper):
         args[0] = expand_glob(args[0], cmd_shenv.cwd)[0]
 
         # Expand all late substitutions.
-        args = _expandLateSubstitutions(args, cmd_shenv.cwd)
+        args = _expandLateSubstitutions(j, args, cmd_shenv.cwd)
 
         inproc_builtin = inproc_builtins.get(args[0], None)
         if inproc_builtin and (args[0] != "echo" or len(cmd.commands) == 1):
diff --git a/llvm/utils/lit/tests/Inputs/shtest-readfile/file-does-not-exist.txt b/llvm/utils/lit/tests/Inputs/shtest-readfile/file-does-not-exist.txt
new file mode 100644
index 0000000000000..1151b75f2fa00
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/shtest-readfile/file-does-not-exist.txt
@@ -0,0 +1,4 @@
+## Test that readfile reports information appropriately when the file specified
+## does not exist.
+
+# RUN: echo %{readfile:/file/does/not/exist}
diff --git a/llvm/utils/lit/tests/shtest-readfile.py b/llvm/utils/lit/tests/shtest-readfile.py
index 245ea371bc14d..c25a643b4eff8 100644
--- a/llvm/utils/lit/tests/shtest-readfile.py
+++ b/llvm/utils/lit/tests/shtest-readfile.py
@@ -2,12 +2,16 @@
 
 # RUN: not %{lit} -a -v %{inputs}/shtest-readfile | FileCheck -match-full-lines -DTEMP_PATH=%S/Inputs/shtest-readfile/Output %s
 
-# CHECK: -- Testing: 3 tests{{.*}}
+# CHECK: -- Testing: 4 tests{{.*}}
 
 # CHECK-LABEL: FAIL: shtest-readfile :: absolute-paths.txt ({{[^)]*}})
 # CHECK: echo hello
 # CHECK: # executed command: echo '%{readfile:[[TEMP_PATH]]/absolute-paths.txt.tmp}'
 
+# CHECK-LABEL: FAIL: shtest-readfile :: file-does-not-exist.txt ({{[^)]*}})
+# CHECK: # executed command: @echo 'echo %{readfile:/file/does/not/exist}'
+# CHECK: # | File does not exist: /file/does/not/exist
+
 # CHECK-LABEL: FAIL: shtest-readfile :: relative-paths.txt ({{[^)]*}})
 # CHECK: echo hello
 # CHECK: # executed command: echo '%{readfile:rel_path_test_folder/test_file}'



More information about the llvm-commits mailing list