[clang] [llvm] [lit] Make builtin cat work with stdin (PR #158447)
Aiden Grossman via llvm-commits
llvm-commits at lists.llvm.org
Sat Sep 13 16:15:52 PDT 2025
https://github.com/boomanaiden154 updated https://github.com/llvm/llvm-project/pull/158447
>From 5bd8d4f925f3b5f82d85ef693861b6b1067d9f38 Mon Sep 17 00:00:00 2001
From: Aiden Grossman <aidengrossman at google.com>
Date: Sat, 13 Sep 2025 22:54: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
---
clang/test/Misc/dev-fd-fs.c | 1 -
llvm/utils/lit/lit/builtin_commands/cat.py | 3 +++
llvm/utils/lit/tests/Inputs/shtest-cat/cat.txt | 4 ++++
3 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/clang/test/Misc/dev-fd-fs.c b/clang/test/Misc/dev-fd-fs.c
index ea94d950b0716..b989ab8a439cf 100644
--- a/clang/test/Misc/dev-fd-fs.c
+++ b/clang/test/Misc/dev-fd-fs.c
@@ -1,6 +1,5 @@
// Check that we can operate on files from /dev/fd.
// REQUIRES: dev-fd-fs
-// REQUIRES: shell
// Check reading from named pipes. We cat the input here instead of redirecting
// it to ensure that /dev/fd/0 is a named pipe, not just a redirected file.
diff --git a/llvm/utils/lit/lit/builtin_commands/cat.py b/llvm/utils/lit/lit/builtin_commands/cat.py
index ddab555662045..2797e0cbb4154 100644
--- a/llvm/utils/lit/lit/builtin_commands/cat.py
+++ b/llvm/utils/lit/lit/builtin_commands/cat.py
@@ -49,6 +49,9 @@ def main(argv):
import os, msvcrt
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
+ if len(filenames) == 0:
+ sys.stdout.write(sys.stdin.read())
+ sys.exit(0)
for filename in filenames:
try:
contents = None
diff --git a/llvm/utils/lit/tests/Inputs/shtest-cat/cat.txt b/llvm/utils/lit/tests/Inputs/shtest-cat/cat.txt
index 4014b0fca1f24..c5b5d247c2f95 100644
--- a/llvm/utils/lit/tests/Inputs/shtest-cat/cat.txt
+++ b/llvm/utils/lit/tests/Inputs/shtest-cat/cat.txt
@@ -70,3 +70,7 @@
# NP-CAT-OUTPUT-NEXT:M-HM-IM-JM-KM-LM-MM-NM-OM-PM-QM-RM-SM-TM-UM-VM-WM-XM-YM-ZM-[
# NP-CAT-OUTPUT-NEXT:M-\M-]M-^M-_M-`M-aM-bM-cM-dM-eM-fM-gM-hM-iM-jM-kM-lM-mM-nM-o
# NP-CAT-OUTPUT-NEXT:M-pM-qM-rM-sM-tM-uM-vM-wM-xM-yM-zM-{M-|M-}M-~M-^?
+
+## Test that cat will pipe stdin to stdout if no other files are specified.
+# RUN: echo test | cat | FileCheck --check-prefix=CAT-STDIN %s
+# CAT-STDIN: test
>From 9edf5499e6c1f5b01bc66d5cad9f4f45b22f4c61 Mon Sep 17 00:00:00 2001
From: Aiden Grossman <aidengrossman at google.com>
Date: Sat, 13 Sep 2025 23:13:22 +0000
Subject: [PATCH 2/4] =?UTF-8?q?[=F0=9D=98=80=F0=9D=97=BD=F0=9D=97=BF]=20ch?=
=?UTF-8?q?anges=20introduced=20through=20rebase?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Created using spr 1.3.6
[skip ci]
---
clang/test/ClangScanDeps/pr61006.cppm | 3 ++-
clang/test/ClangScanDeps/resource_directory.c | 9 ++++-----
clang/test/Driver/env.c | 5 +++--
clang/test/Driver/program-path-priority.c | 16 +++++++--------
clang/test/Modules/relative-resource-dir.m | 6 +++---
llvm/docs/CommandGuide/lit.rst | 1 +
llvm/test/tools/llvm-cgdata/empty.test | 1 +
llvm/utils/lit/lit/TestRunner.py | 20 +++++++++++++++++++
.../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 ++++++++++++++++
13 files changed, 88 insertions(+), 19 deletions(-)
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/clang/test/ClangScanDeps/pr61006.cppm b/clang/test/ClangScanDeps/pr61006.cppm
index f75edd38c81ba..f10bc1e673987 100644
--- a/clang/test/ClangScanDeps/pr61006.cppm
+++ b/clang/test/ClangScanDeps/pr61006.cppm
@@ -6,7 +6,8 @@
// RUN: mkdir -p %t
// RUN: split-file %s %t
//
-// RUN: EXPECTED_RESOURCE_DIR=`%clang -print-resource-dir` && \
+// RUN: %clang -print-resource-dir | tr -d '\n' > %t/resource-dir
+// RUN: env EXPECTED_RESOURCE_DIR=%{readfile:%t/resource-dir} && \
// RUN: ln -s %clang++ %t/clang++ && \
// RUN: sed "s|EXPECTED_RESOURCE_DIR|$EXPECTED_RESOURCE_DIR|g; s|DIR|%/t|g" %t/P1689.json.in > %t/P1689.json && \
// RUN: clang-scan-deps -compilation-database %t/P1689.json -format=p1689 | FileCheck %t/a.cpp -DPREFIX=%/t && \
diff --git a/clang/test/ClangScanDeps/resource_directory.c b/clang/test/ClangScanDeps/resource_directory.c
index 55d5d90bbcdea..6183e8aefacfa 100644
--- a/clang/test/ClangScanDeps/resource_directory.c
+++ b/clang/test/ClangScanDeps/resource_directory.c
@@ -12,14 +12,14 @@
// then verify `%clang-scan-deps` arrives at the same path by calling the
// `Driver::GetResourcesPath` function.
//
-// RUN: EXPECTED_RESOURCE_DIR=`%clang -print-resource-dir`
+// RUN: %clang -print-resource-dir | tr -d '\n' > %t/resource-dir
// RUN: sed -e "s|CLANG|%clang|g" -e "s|DIR|%/t|g" \
// RUN: %S/Inputs/resource_directory/cdb.json.template > %t/cdb_path.json
//
// RUN: clang-scan-deps -compilation-database %t/cdb_path.json --format experimental-full \
// RUN: --resource-dir-recipe modify-compiler-path > %t/result_path.json
// RUN: cat %t/result_path.json | sed 's:\\\\\?:/:g' \
-// RUN: | FileCheck %s --check-prefix=CHECK-PATH -DEXPECTED_RESOURCE_DIR="$EXPECTED_RESOURCE_DIR"
+// RUN: | FileCheck %s --check-prefix=CHECK-PATH -DEXPECTED_RESOURCE_DIR="%{readfile:%t/resource-dir}"
// CHECK-PATH: "-resource-dir"
// CHECK-PATH-NEXT: "[[EXPECTED_RESOURCE_DIR]]"
@@ -31,9 +31,8 @@
// Here we hard-code the expected path into `%t/compiler` and then verify
// `%clang-scan-deps` arrives at the path by actually running the executable.
//
-// RUN: EXPECTED_RESOURCE_DIR="/custom/compiler/resources"
// RUN: echo "#!/bin/sh" > %t/compiler
-// RUN: echo "echo '$EXPECTED_RESOURCE_DIR'" >> %t/compiler
+// RUN: echo "echo '/custom/compiler/resources'" >> %t/compiler
// RUN: chmod +x %t/compiler
// RUN: sed -e "s|CLANG|%/t/compiler|g" -e "s|DIR|%/t|g" \
// RUN: %S/Inputs/resource_directory/cdb.json.template > %t/cdb_invocation.json
@@ -41,6 +40,6 @@
// RUN: clang-scan-deps -compilation-database %t/cdb_invocation.json --format experimental-full \
// RUN: --resource-dir-recipe invoke-compiler > %t/result_invocation.json
// RUN: cat %t/result_invocation.json | sed 's:\\\\\?:/:g' \
-// RUN: | FileCheck %s --check-prefix=CHECK-PATH -DEXPECTED_RESOURCE_DIR="$EXPECTED_RESOURCE_DIR"
+// RUN: | FileCheck %s --check-prefix=CHECK-PATH -DEXPECTED_RESOURCE_DIR="/custom/compiler/resources"
// CHECK-INVOCATION: "-resource-dir"
// CHECK-INVOCATION-NEXT: "[[EXPECTED_RESOURCE_DIR]]"
diff --git a/clang/test/Driver/env.c b/clang/test/Driver/env.c
index b3345ae09ffef..68ded45385702 100644
--- a/clang/test/Driver/env.c
+++ b/clang/test/Driver/env.c
@@ -1,13 +1,14 @@
// Some assertions in this test use Linux style (/) file paths.
// UNSUPPORTED: system-windows
+// RUN: bash -c env | grep LD_LIBRARY_PATH | tr -d '\n' > /tmp/ld_library_path
// The PATH variable is heavily used when trying to find a linker.
-// RUN: env -i LC_ALL=C LD_LIBRARY_PATH="$LD_LIBRARY_PATH" CLANG_NO_DEFAULT_CONFIG=1 \
+// RUN: env -i LC_ALL=C LD_LIBRARY_PATH="%{readfile:/tmp/ld_library_path}" CLANG_NO_DEFAULT_CONFIG=1 \
// RUN: %clang %s -### -o %t.o --target=i386-unknown-linux \
// RUN: --sysroot=%S/Inputs/basic_linux_tree \
// RUN: --rtlib=platform --unwindlib=platform -no-pie \
// RUN: 2>&1 | FileCheck --check-prefix=CHECK-LD-32 %s
//
-// RUN: env -i LC_ALL=C PATH="" LD_LIBRARY_PATH="$LD_LIBRARY_PATH" CLANG_NO_DEFAULT_CONFIG=1 \
+// RUN: env -i LC_ALL=C PATH="" LD_LIBRARY_PATH="%{readfile:/tmp/ld_library_path}" CLANG_NO_DEFAULT_CONFIG=1 \
// RUN: %clang %s -### -o %t.o --target=i386-unknown-linux \
// RUN: --sysroot=%S/Inputs/basic_linux_tree \
// RUN: --rtlib=platform --unwindlib=platform -no-pie \
diff --git a/clang/test/Driver/program-path-priority.c b/clang/test/Driver/program-path-priority.c
index b88c0f29f7f33..bb434482f90d4 100644
--- a/clang/test/Driver/program-path-priority.c
+++ b/clang/test/Driver/program-path-priority.c
@@ -87,8 +87,8 @@
/// <default-triple>-gcc has lowest priority so <triple>-gcc
/// on PATH beats default triple in program path
-// RUN: DEFAULT_TRIPLE=`%t/clang --version | grep "Target:" | cut -d ' ' -f2`
-// RUN: touch %t/$DEFAULT_TRIPLE-gcc && chmod +x %t/$DEFAULT_TRIPLE-gcc
+// RUN: %t/clang --version | grep "Target:" | cut -d ' ' -f2 > %t.default_triple
+// RUN: touch %t/%{readfile:%t.default_triple}-gcc && chmod +x %t/%{readfile:%t.default_triple}-gcc
// RUN: touch %t/%target_triple-gcc && chmod +x %t/%target_triple-gcc
// RUN: env "PATH=%t/env/" %t/clang -### -target notreal-none-elf %s 2>&1 | \
// RUN: FileCheck --check-prefix=DEFAULT_TRIPLE_GCC %s
@@ -101,7 +101,7 @@
// DEFAULT_TRIPLE_NO_NOTREAL: env/gcc"
// DEFAULT_TRIPLE_NO_NOTREAL-NOT: -gcc"
-/// Pick "gcc" as a fallback. Don't pick $DEFAULT_TRIPLE-gcc.
+/// Pick "gcc" as a fallback. Don't pick DEFAULT_TRIPLE-gcc.
// RUN: rm %t/env/gcc
// RUN: env "PATH=%t/env/" %t/clang -### -target notreal-none-elf %s 2>&1 | \
// RUN: FileCheck --check-prefix=DEFAULT_TRIPLE_NO_OTHERS %s
@@ -110,9 +110,9 @@
/// -B paths are searched separately so default triple will win
/// if put in one of those even if other paths have higher priority names
// RUN: mkdir -p %t/prefix
-/// One of these will fail when $DEFAULT_TRIPLE == %target_triple
-// RUN: test -f %t/$DEFAULT_TRIPLE-gcc && \
-// RUN: mv %t/$DEFAULT_TRIPLE-gcc %t/prefix || true
+/// One of these will fail when %{readfile:%t.default_triple} == %target_triple
+// RUN: test -f %t/%{readfile:%t.default_triple}-gcc && \
+// RUN: mv %t/%{readfile:%t.default_triple}-gcc %t/prefix || true
// RUN: test -f %t/%target_triple-gcc && \
// RUN: mv %t/%target_triple-gcc %t/prefix || true
// RUN: touch %t/notreal-none-elf-gcc && chmod +x %t/notreal-none-elf-gcc
@@ -123,8 +123,8 @@
// DEFAULT_TRIPLE_IN_PREFIX-NOT: notreal-none-elf-gcc"
/// Only if there is nothing in the prefix will we search other paths
-/// -f in case $DEFAULT_TRIPLE == %target_triple
-// RUN: rm -f %t/prefix/$DEFAULT_TRIPLE-gcc %t/prefix/%target_triple-gcc %t/prefix/gcc
+/// -f in case %{readfile:%t.default_triple} == %target_triple
+// RUN: rm -f %t/prefix/%{readfile:%t.default_triple}-gcc %t/prefix/%target_triple-gcc %t/prefix/gcc
// RUN: env "PATH=" %t/clang -### -canonical-prefixes --target=notreal-none-elf %s -B %t/prefix 2>&1 | \
// RUN: FileCheck --check-prefix=EMPTY_PREFIX_DIR1 %s
// EMPTY_PREFIX_DIR1: gcc"
diff --git a/clang/test/Modules/relative-resource-dir.m b/clang/test/Modules/relative-resource-dir.m
index 4f88150a2ba4c..96f2d8efc7860 100644
--- a/clang/test/Modules/relative-resource-dir.m
+++ b/clang/test/Modules/relative-resource-dir.m
@@ -1,9 +1,9 @@
// UNSUPPORTED: target={{.*}}-zos{{.*}}, target={{.*}}-aix{{.*}}
-// REQUIRES: shell
-// RUN: EXPECTED_RESOURCE_DIR=`%clang -print-resource-dir` && \
+// RUN: %clang -print-resource-dir | tr -d '\n' > %t.resource_dir
+// RUN: env EXPECTED_RESOURCE_DIR="%{readfile:%t.resource_dir}" && \
// RUN: mkdir -p %t && rm -rf %t/resource-dir && \
-// RUN: cp -R $EXPECTED_RESOURCE_DIR %t/resource-dir
+// RUN: cp -R %{readfile:%t.resource_dir} %t/resource-dir
// RUN: cd %t && %clang -cc1 -x objective-c -fmodules -fmodule-format=obj \
// RUN: -fimplicit-module-maps -fmodules-cache-path=%t.mcp \
// RUN: -fbuiltin-headers-in-system-modules \
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..5daa8887e4c80 100644
--- a/llvm/utils/lit/lit/TestRunner.py
+++ b/llvm/utils/lit/lit/TestRunner.py
@@ -720,6 +720,23 @@ 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():
# Prevent further recursion if the timeout has been hit
@@ -834,6 +851,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/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 572975066e843b76e51020bcf6abc7822d3dfb75 Mon Sep 17 00:00:00 2001
From: Aiden Grossman <aidengrossman at google.com>
Date: Sat, 13 Sep 2025 23:14:52 +0000
Subject: [PATCH 3/4] =?UTF-8?q?[=F0=9D=98=80=F0=9D=97=BD=F0=9D=97=BF]=20ch?=
=?UTF-8?q?anges=20introduced=20through=20rebase?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Created using spr 1.3.6
[skip ci]
---
clang/test/ClangScanDeps/pr61006.cppm | 3 ++-
clang/test/ClangScanDeps/resource_directory.c | 9 ++++-----
clang/test/Driver/env.c | 5 +++--
clang/test/Driver/program-path-priority.c | 16 +++++++--------
clang/test/Modules/relative-resource-dir.m | 6 +++---
llvm/docs/CommandGuide/lit.rst | 1 +
llvm/test/tools/llvm-cgdata/empty.test | 1 +
llvm/utils/lit/lit/TestRunner.py | 20 +++++++++++++++++++
.../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 ++++++++++++++++
13 files changed, 88 insertions(+), 19 deletions(-)
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/clang/test/ClangScanDeps/pr61006.cppm b/clang/test/ClangScanDeps/pr61006.cppm
index f75edd38c81ba..f10bc1e673987 100644
--- a/clang/test/ClangScanDeps/pr61006.cppm
+++ b/clang/test/ClangScanDeps/pr61006.cppm
@@ -6,7 +6,8 @@
// RUN: mkdir -p %t
// RUN: split-file %s %t
//
-// RUN: EXPECTED_RESOURCE_DIR=`%clang -print-resource-dir` && \
+// RUN: %clang -print-resource-dir | tr -d '\n' > %t/resource-dir
+// RUN: env EXPECTED_RESOURCE_DIR=%{readfile:%t/resource-dir} && \
// RUN: ln -s %clang++ %t/clang++ && \
// RUN: sed "s|EXPECTED_RESOURCE_DIR|$EXPECTED_RESOURCE_DIR|g; s|DIR|%/t|g" %t/P1689.json.in > %t/P1689.json && \
// RUN: clang-scan-deps -compilation-database %t/P1689.json -format=p1689 | FileCheck %t/a.cpp -DPREFIX=%/t && \
diff --git a/clang/test/ClangScanDeps/resource_directory.c b/clang/test/ClangScanDeps/resource_directory.c
index 55d5d90bbcdea..6183e8aefacfa 100644
--- a/clang/test/ClangScanDeps/resource_directory.c
+++ b/clang/test/ClangScanDeps/resource_directory.c
@@ -12,14 +12,14 @@
// then verify `%clang-scan-deps` arrives at the same path by calling the
// `Driver::GetResourcesPath` function.
//
-// RUN: EXPECTED_RESOURCE_DIR=`%clang -print-resource-dir`
+// RUN: %clang -print-resource-dir | tr -d '\n' > %t/resource-dir
// RUN: sed -e "s|CLANG|%clang|g" -e "s|DIR|%/t|g" \
// RUN: %S/Inputs/resource_directory/cdb.json.template > %t/cdb_path.json
//
// RUN: clang-scan-deps -compilation-database %t/cdb_path.json --format experimental-full \
// RUN: --resource-dir-recipe modify-compiler-path > %t/result_path.json
// RUN: cat %t/result_path.json | sed 's:\\\\\?:/:g' \
-// RUN: | FileCheck %s --check-prefix=CHECK-PATH -DEXPECTED_RESOURCE_DIR="$EXPECTED_RESOURCE_DIR"
+// RUN: | FileCheck %s --check-prefix=CHECK-PATH -DEXPECTED_RESOURCE_DIR="%{readfile:%t/resource-dir}"
// CHECK-PATH: "-resource-dir"
// CHECK-PATH-NEXT: "[[EXPECTED_RESOURCE_DIR]]"
@@ -31,9 +31,8 @@
// Here we hard-code the expected path into `%t/compiler` and then verify
// `%clang-scan-deps` arrives at the path by actually running the executable.
//
-// RUN: EXPECTED_RESOURCE_DIR="/custom/compiler/resources"
// RUN: echo "#!/bin/sh" > %t/compiler
-// RUN: echo "echo '$EXPECTED_RESOURCE_DIR'" >> %t/compiler
+// RUN: echo "echo '/custom/compiler/resources'" >> %t/compiler
// RUN: chmod +x %t/compiler
// RUN: sed -e "s|CLANG|%/t/compiler|g" -e "s|DIR|%/t|g" \
// RUN: %S/Inputs/resource_directory/cdb.json.template > %t/cdb_invocation.json
@@ -41,6 +40,6 @@
// RUN: clang-scan-deps -compilation-database %t/cdb_invocation.json --format experimental-full \
// RUN: --resource-dir-recipe invoke-compiler > %t/result_invocation.json
// RUN: cat %t/result_invocation.json | sed 's:\\\\\?:/:g' \
-// RUN: | FileCheck %s --check-prefix=CHECK-PATH -DEXPECTED_RESOURCE_DIR="$EXPECTED_RESOURCE_DIR"
+// RUN: | FileCheck %s --check-prefix=CHECK-PATH -DEXPECTED_RESOURCE_DIR="/custom/compiler/resources"
// CHECK-INVOCATION: "-resource-dir"
// CHECK-INVOCATION-NEXT: "[[EXPECTED_RESOURCE_DIR]]"
diff --git a/clang/test/Driver/env.c b/clang/test/Driver/env.c
index b3345ae09ffef..68ded45385702 100644
--- a/clang/test/Driver/env.c
+++ b/clang/test/Driver/env.c
@@ -1,13 +1,14 @@
// Some assertions in this test use Linux style (/) file paths.
// UNSUPPORTED: system-windows
+// RUN: bash -c env | grep LD_LIBRARY_PATH | tr -d '\n' > /tmp/ld_library_path
// The PATH variable is heavily used when trying to find a linker.
-// RUN: env -i LC_ALL=C LD_LIBRARY_PATH="$LD_LIBRARY_PATH" CLANG_NO_DEFAULT_CONFIG=1 \
+// RUN: env -i LC_ALL=C LD_LIBRARY_PATH="%{readfile:/tmp/ld_library_path}" CLANG_NO_DEFAULT_CONFIG=1 \
// RUN: %clang %s -### -o %t.o --target=i386-unknown-linux \
// RUN: --sysroot=%S/Inputs/basic_linux_tree \
// RUN: --rtlib=platform --unwindlib=platform -no-pie \
// RUN: 2>&1 | FileCheck --check-prefix=CHECK-LD-32 %s
//
-// RUN: env -i LC_ALL=C PATH="" LD_LIBRARY_PATH="$LD_LIBRARY_PATH" CLANG_NO_DEFAULT_CONFIG=1 \
+// RUN: env -i LC_ALL=C PATH="" LD_LIBRARY_PATH="%{readfile:/tmp/ld_library_path}" CLANG_NO_DEFAULT_CONFIG=1 \
// RUN: %clang %s -### -o %t.o --target=i386-unknown-linux \
// RUN: --sysroot=%S/Inputs/basic_linux_tree \
// RUN: --rtlib=platform --unwindlib=platform -no-pie \
diff --git a/clang/test/Driver/program-path-priority.c b/clang/test/Driver/program-path-priority.c
index b88c0f29f7f33..bb434482f90d4 100644
--- a/clang/test/Driver/program-path-priority.c
+++ b/clang/test/Driver/program-path-priority.c
@@ -87,8 +87,8 @@
/// <default-triple>-gcc has lowest priority so <triple>-gcc
/// on PATH beats default triple in program path
-// RUN: DEFAULT_TRIPLE=`%t/clang --version | grep "Target:" | cut -d ' ' -f2`
-// RUN: touch %t/$DEFAULT_TRIPLE-gcc && chmod +x %t/$DEFAULT_TRIPLE-gcc
+// RUN: %t/clang --version | grep "Target:" | cut -d ' ' -f2 > %t.default_triple
+// RUN: touch %t/%{readfile:%t.default_triple}-gcc && chmod +x %t/%{readfile:%t.default_triple}-gcc
// RUN: touch %t/%target_triple-gcc && chmod +x %t/%target_triple-gcc
// RUN: env "PATH=%t/env/" %t/clang -### -target notreal-none-elf %s 2>&1 | \
// RUN: FileCheck --check-prefix=DEFAULT_TRIPLE_GCC %s
@@ -101,7 +101,7 @@
// DEFAULT_TRIPLE_NO_NOTREAL: env/gcc"
// DEFAULT_TRIPLE_NO_NOTREAL-NOT: -gcc"
-/// Pick "gcc" as a fallback. Don't pick $DEFAULT_TRIPLE-gcc.
+/// Pick "gcc" as a fallback. Don't pick DEFAULT_TRIPLE-gcc.
// RUN: rm %t/env/gcc
// RUN: env "PATH=%t/env/" %t/clang -### -target notreal-none-elf %s 2>&1 | \
// RUN: FileCheck --check-prefix=DEFAULT_TRIPLE_NO_OTHERS %s
@@ -110,9 +110,9 @@
/// -B paths are searched separately so default triple will win
/// if put in one of those even if other paths have higher priority names
// RUN: mkdir -p %t/prefix
-/// One of these will fail when $DEFAULT_TRIPLE == %target_triple
-// RUN: test -f %t/$DEFAULT_TRIPLE-gcc && \
-// RUN: mv %t/$DEFAULT_TRIPLE-gcc %t/prefix || true
+/// One of these will fail when %{readfile:%t.default_triple} == %target_triple
+// RUN: test -f %t/%{readfile:%t.default_triple}-gcc && \
+// RUN: mv %t/%{readfile:%t.default_triple}-gcc %t/prefix || true
// RUN: test -f %t/%target_triple-gcc && \
// RUN: mv %t/%target_triple-gcc %t/prefix || true
// RUN: touch %t/notreal-none-elf-gcc && chmod +x %t/notreal-none-elf-gcc
@@ -123,8 +123,8 @@
// DEFAULT_TRIPLE_IN_PREFIX-NOT: notreal-none-elf-gcc"
/// Only if there is nothing in the prefix will we search other paths
-/// -f in case $DEFAULT_TRIPLE == %target_triple
-// RUN: rm -f %t/prefix/$DEFAULT_TRIPLE-gcc %t/prefix/%target_triple-gcc %t/prefix/gcc
+/// -f in case %{readfile:%t.default_triple} == %target_triple
+// RUN: rm -f %t/prefix/%{readfile:%t.default_triple}-gcc %t/prefix/%target_triple-gcc %t/prefix/gcc
// RUN: env "PATH=" %t/clang -### -canonical-prefixes --target=notreal-none-elf %s -B %t/prefix 2>&1 | \
// RUN: FileCheck --check-prefix=EMPTY_PREFIX_DIR1 %s
// EMPTY_PREFIX_DIR1: gcc"
diff --git a/clang/test/Modules/relative-resource-dir.m b/clang/test/Modules/relative-resource-dir.m
index 4f88150a2ba4c..96f2d8efc7860 100644
--- a/clang/test/Modules/relative-resource-dir.m
+++ b/clang/test/Modules/relative-resource-dir.m
@@ -1,9 +1,9 @@
// UNSUPPORTED: target={{.*}}-zos{{.*}}, target={{.*}}-aix{{.*}}
-// REQUIRES: shell
-// RUN: EXPECTED_RESOURCE_DIR=`%clang -print-resource-dir` && \
+// RUN: %clang -print-resource-dir | tr -d '\n' > %t.resource_dir
+// RUN: env EXPECTED_RESOURCE_DIR="%{readfile:%t.resource_dir}" && \
// RUN: mkdir -p %t && rm -rf %t/resource-dir && \
-// RUN: cp -R $EXPECTED_RESOURCE_DIR %t/resource-dir
+// RUN: cp -R %{readfile:%t.resource_dir} %t/resource-dir
// RUN: cd %t && %clang -cc1 -x objective-c -fmodules -fmodule-format=obj \
// RUN: -fimplicit-module-maps -fmodules-cache-path=%t.mcp \
// RUN: -fbuiltin-headers-in-system-modules \
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..5daa8887e4c80 100644
--- a/llvm/utils/lit/lit/TestRunner.py
+++ b/llvm/utils/lit/lit/TestRunner.py
@@ -720,6 +720,23 @@ 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():
# Prevent further recursion if the timeout has been hit
@@ -834,6 +851,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/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 e238732d75577598668a26f5b9ab63b75da6e04a Mon Sep 17 00:00:00 2001
From: Aiden Grossman <aidengrossman at google.com>
Date: Sat, 13 Sep 2025 23:15:38 +0000
Subject: [PATCH 4/4] =?UTF-8?q?[=F0=9D=98=80=F0=9D=97=BD=F0=9D=97=BF]=20ch?=
=?UTF-8?q?anges=20introduced=20through=20rebase?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Created using spr 1.3.6
[skip ci]
---
clang/test/ClangScanDeps/pr61006.cppm | 3 ++-
clang/test/ClangScanDeps/resource_directory.c | 9 ++++-----
clang/test/Driver/env.c | 5 +++--
clang/test/Driver/program-path-priority.c | 16 +++++++--------
clang/test/Modules/relative-resource-dir.m | 6 +++---
llvm/docs/CommandGuide/lit.rst | 1 +
llvm/test/tools/llvm-cgdata/empty.test | 1 +
llvm/utils/lit/lit/TestRunner.py | 20 +++++++++++++++++++
.../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 ++++++++++++++++
13 files changed, 88 insertions(+), 19 deletions(-)
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/clang/test/ClangScanDeps/pr61006.cppm b/clang/test/ClangScanDeps/pr61006.cppm
index f75edd38c81ba..f10bc1e673987 100644
--- a/clang/test/ClangScanDeps/pr61006.cppm
+++ b/clang/test/ClangScanDeps/pr61006.cppm
@@ -6,7 +6,8 @@
// RUN: mkdir -p %t
// RUN: split-file %s %t
//
-// RUN: EXPECTED_RESOURCE_DIR=`%clang -print-resource-dir` && \
+// RUN: %clang -print-resource-dir | tr -d '\n' > %t/resource-dir
+// RUN: env EXPECTED_RESOURCE_DIR=%{readfile:%t/resource-dir} && \
// RUN: ln -s %clang++ %t/clang++ && \
// RUN: sed "s|EXPECTED_RESOURCE_DIR|$EXPECTED_RESOURCE_DIR|g; s|DIR|%/t|g" %t/P1689.json.in > %t/P1689.json && \
// RUN: clang-scan-deps -compilation-database %t/P1689.json -format=p1689 | FileCheck %t/a.cpp -DPREFIX=%/t && \
diff --git a/clang/test/ClangScanDeps/resource_directory.c b/clang/test/ClangScanDeps/resource_directory.c
index 55d5d90bbcdea..6183e8aefacfa 100644
--- a/clang/test/ClangScanDeps/resource_directory.c
+++ b/clang/test/ClangScanDeps/resource_directory.c
@@ -12,14 +12,14 @@
// then verify `%clang-scan-deps` arrives at the same path by calling the
// `Driver::GetResourcesPath` function.
//
-// RUN: EXPECTED_RESOURCE_DIR=`%clang -print-resource-dir`
+// RUN: %clang -print-resource-dir | tr -d '\n' > %t/resource-dir
// RUN: sed -e "s|CLANG|%clang|g" -e "s|DIR|%/t|g" \
// RUN: %S/Inputs/resource_directory/cdb.json.template > %t/cdb_path.json
//
// RUN: clang-scan-deps -compilation-database %t/cdb_path.json --format experimental-full \
// RUN: --resource-dir-recipe modify-compiler-path > %t/result_path.json
// RUN: cat %t/result_path.json | sed 's:\\\\\?:/:g' \
-// RUN: | FileCheck %s --check-prefix=CHECK-PATH -DEXPECTED_RESOURCE_DIR="$EXPECTED_RESOURCE_DIR"
+// RUN: | FileCheck %s --check-prefix=CHECK-PATH -DEXPECTED_RESOURCE_DIR="%{readfile:%t/resource-dir}"
// CHECK-PATH: "-resource-dir"
// CHECK-PATH-NEXT: "[[EXPECTED_RESOURCE_DIR]]"
@@ -31,9 +31,8 @@
// Here we hard-code the expected path into `%t/compiler` and then verify
// `%clang-scan-deps` arrives at the path by actually running the executable.
//
-// RUN: EXPECTED_RESOURCE_DIR="/custom/compiler/resources"
// RUN: echo "#!/bin/sh" > %t/compiler
-// RUN: echo "echo '$EXPECTED_RESOURCE_DIR'" >> %t/compiler
+// RUN: echo "echo '/custom/compiler/resources'" >> %t/compiler
// RUN: chmod +x %t/compiler
// RUN: sed -e "s|CLANG|%/t/compiler|g" -e "s|DIR|%/t|g" \
// RUN: %S/Inputs/resource_directory/cdb.json.template > %t/cdb_invocation.json
@@ -41,6 +40,6 @@
// RUN: clang-scan-deps -compilation-database %t/cdb_invocation.json --format experimental-full \
// RUN: --resource-dir-recipe invoke-compiler > %t/result_invocation.json
// RUN: cat %t/result_invocation.json | sed 's:\\\\\?:/:g' \
-// RUN: | FileCheck %s --check-prefix=CHECK-PATH -DEXPECTED_RESOURCE_DIR="$EXPECTED_RESOURCE_DIR"
+// RUN: | FileCheck %s --check-prefix=CHECK-PATH -DEXPECTED_RESOURCE_DIR="/custom/compiler/resources"
// CHECK-INVOCATION: "-resource-dir"
// CHECK-INVOCATION-NEXT: "[[EXPECTED_RESOURCE_DIR]]"
diff --git a/clang/test/Driver/env.c b/clang/test/Driver/env.c
index b3345ae09ffef..68ded45385702 100644
--- a/clang/test/Driver/env.c
+++ b/clang/test/Driver/env.c
@@ -1,13 +1,14 @@
// Some assertions in this test use Linux style (/) file paths.
// UNSUPPORTED: system-windows
+// RUN: bash -c env | grep LD_LIBRARY_PATH | tr -d '\n' > /tmp/ld_library_path
// The PATH variable is heavily used when trying to find a linker.
-// RUN: env -i LC_ALL=C LD_LIBRARY_PATH="$LD_LIBRARY_PATH" CLANG_NO_DEFAULT_CONFIG=1 \
+// RUN: env -i LC_ALL=C LD_LIBRARY_PATH="%{readfile:/tmp/ld_library_path}" CLANG_NO_DEFAULT_CONFIG=1 \
// RUN: %clang %s -### -o %t.o --target=i386-unknown-linux \
// RUN: --sysroot=%S/Inputs/basic_linux_tree \
// RUN: --rtlib=platform --unwindlib=platform -no-pie \
// RUN: 2>&1 | FileCheck --check-prefix=CHECK-LD-32 %s
//
-// RUN: env -i LC_ALL=C PATH="" LD_LIBRARY_PATH="$LD_LIBRARY_PATH" CLANG_NO_DEFAULT_CONFIG=1 \
+// RUN: env -i LC_ALL=C PATH="" LD_LIBRARY_PATH="%{readfile:/tmp/ld_library_path}" CLANG_NO_DEFAULT_CONFIG=1 \
// RUN: %clang %s -### -o %t.o --target=i386-unknown-linux \
// RUN: --sysroot=%S/Inputs/basic_linux_tree \
// RUN: --rtlib=platform --unwindlib=platform -no-pie \
diff --git a/clang/test/Driver/program-path-priority.c b/clang/test/Driver/program-path-priority.c
index b88c0f29f7f33..bb434482f90d4 100644
--- a/clang/test/Driver/program-path-priority.c
+++ b/clang/test/Driver/program-path-priority.c
@@ -87,8 +87,8 @@
/// <default-triple>-gcc has lowest priority so <triple>-gcc
/// on PATH beats default triple in program path
-// RUN: DEFAULT_TRIPLE=`%t/clang --version | grep "Target:" | cut -d ' ' -f2`
-// RUN: touch %t/$DEFAULT_TRIPLE-gcc && chmod +x %t/$DEFAULT_TRIPLE-gcc
+// RUN: %t/clang --version | grep "Target:" | cut -d ' ' -f2 > %t.default_triple
+// RUN: touch %t/%{readfile:%t.default_triple}-gcc && chmod +x %t/%{readfile:%t.default_triple}-gcc
// RUN: touch %t/%target_triple-gcc && chmod +x %t/%target_triple-gcc
// RUN: env "PATH=%t/env/" %t/clang -### -target notreal-none-elf %s 2>&1 | \
// RUN: FileCheck --check-prefix=DEFAULT_TRIPLE_GCC %s
@@ -101,7 +101,7 @@
// DEFAULT_TRIPLE_NO_NOTREAL: env/gcc"
// DEFAULT_TRIPLE_NO_NOTREAL-NOT: -gcc"
-/// Pick "gcc" as a fallback. Don't pick $DEFAULT_TRIPLE-gcc.
+/// Pick "gcc" as a fallback. Don't pick DEFAULT_TRIPLE-gcc.
// RUN: rm %t/env/gcc
// RUN: env "PATH=%t/env/" %t/clang -### -target notreal-none-elf %s 2>&1 | \
// RUN: FileCheck --check-prefix=DEFAULT_TRIPLE_NO_OTHERS %s
@@ -110,9 +110,9 @@
/// -B paths are searched separately so default triple will win
/// if put in one of those even if other paths have higher priority names
// RUN: mkdir -p %t/prefix
-/// One of these will fail when $DEFAULT_TRIPLE == %target_triple
-// RUN: test -f %t/$DEFAULT_TRIPLE-gcc && \
-// RUN: mv %t/$DEFAULT_TRIPLE-gcc %t/prefix || true
+/// One of these will fail when %{readfile:%t.default_triple} == %target_triple
+// RUN: test -f %t/%{readfile:%t.default_triple}-gcc && \
+// RUN: mv %t/%{readfile:%t.default_triple}-gcc %t/prefix || true
// RUN: test -f %t/%target_triple-gcc && \
// RUN: mv %t/%target_triple-gcc %t/prefix || true
// RUN: touch %t/notreal-none-elf-gcc && chmod +x %t/notreal-none-elf-gcc
@@ -123,8 +123,8 @@
// DEFAULT_TRIPLE_IN_PREFIX-NOT: notreal-none-elf-gcc"
/// Only if there is nothing in the prefix will we search other paths
-/// -f in case $DEFAULT_TRIPLE == %target_triple
-// RUN: rm -f %t/prefix/$DEFAULT_TRIPLE-gcc %t/prefix/%target_triple-gcc %t/prefix/gcc
+/// -f in case %{readfile:%t.default_triple} == %target_triple
+// RUN: rm -f %t/prefix/%{readfile:%t.default_triple}-gcc %t/prefix/%target_triple-gcc %t/prefix/gcc
// RUN: env "PATH=" %t/clang -### -canonical-prefixes --target=notreal-none-elf %s -B %t/prefix 2>&1 | \
// RUN: FileCheck --check-prefix=EMPTY_PREFIX_DIR1 %s
// EMPTY_PREFIX_DIR1: gcc"
diff --git a/clang/test/Modules/relative-resource-dir.m b/clang/test/Modules/relative-resource-dir.m
index 4f88150a2ba4c..96f2d8efc7860 100644
--- a/clang/test/Modules/relative-resource-dir.m
+++ b/clang/test/Modules/relative-resource-dir.m
@@ -1,9 +1,9 @@
// UNSUPPORTED: target={{.*}}-zos{{.*}}, target={{.*}}-aix{{.*}}
-// REQUIRES: shell
-// RUN: EXPECTED_RESOURCE_DIR=`%clang -print-resource-dir` && \
+// RUN: %clang -print-resource-dir | tr -d '\n' > %t.resource_dir
+// RUN: env EXPECTED_RESOURCE_DIR="%{readfile:%t.resource_dir}" && \
// RUN: mkdir -p %t && rm -rf %t/resource-dir && \
-// RUN: cp -R $EXPECTED_RESOURCE_DIR %t/resource-dir
+// RUN: cp -R %{readfile:%t.resource_dir} %t/resource-dir
// RUN: cd %t && %clang -cc1 -x objective-c -fmodules -fmodule-format=obj \
// RUN: -fimplicit-module-maps -fmodules-cache-path=%t.mcp \
// RUN: -fbuiltin-headers-in-system-modules \
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..5daa8887e4c80 100644
--- a/llvm/utils/lit/lit/TestRunner.py
+++ b/llvm/utils/lit/lit/TestRunner.py
@@ -720,6 +720,23 @@ 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():
# Prevent further recursion if the timeout has been hit
@@ -834,6 +851,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/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}'
More information about the llvm-commits
mailing list