[libcxx-commits] [libcxx] [libcxx][test] Add support for LLVM-style split tests (PR #188283)
Tom Eccles via libcxx-commits
libcxx-commits at lists.llvm.org
Thu Mar 26 08:49:07 PDT 2026
https://github.com/tblah updated https://github.com/llvm/llvm-project/pull/188283
>From 0173673b94485f249b627023c83cbf26cb50d65d Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Tue, 24 Mar 2026 16:14:53 +0000
Subject: [PATCH 1/4] [libcxx][test] Add support for LLVM-style split tests
This patch adds support for a new kind of test (whose names have
`.split.` in them). An example of such test is as follows:
```
// RUN: use %{temp}/main.cpp
// RUN: use %{temp}/inputs.txt
//--- main.cpp
int main() { return 0; }
//--- input.txt
some inputs
```
This patch creates the subfiles in the directory pointed to by the `%{temp}` substitution. Then it runs the original test file just like a regular shell LIT test.
The LLVM `split-file` utility would do this for us already, but it's not available as a Python package yet. So in the meantime we piggyback off of the hand-rolled `_splitFile` that's used for the `.gen.` tests.
This is useful for the upcoming LLDB data-formatter tests (see https://github.com/llvm/llvm-project/pull/187677).
---
libcxx/test/selftest/split/test.split.cpp | 24 ++++++++++++
libcxx/test/selftest/split/test.split.sh | 24 ++++++++++++
libcxx/utils/libcxx/test/format.py | 45 ++++++++++++++++++++++-
3 files changed, 91 insertions(+), 2 deletions(-)
create mode 100644 libcxx/test/selftest/split/test.split.cpp
create mode 100644 libcxx/test/selftest/split/test.split.sh
diff --git a/libcxx/test/selftest/split/test.split.cpp b/libcxx/test/selftest/split/test.split.cpp
new file mode 100644
index 0000000000000..38e638569e3d6
--- /dev/null
+++ b/libcxx/test/selftest/split/test.split.cpp
@@ -0,0 +1,24 @@
+// Pre-delimiter comment.
+
+// RUN: grep 'int main' %{temp}/main.cpp
+// RUN: grep 'return 0' %{temp}/main.cpp
+// RUN: not grep -c 'Pre-delimiter' %{temp}/main.cpp
+// RUN: not grep -c 'foo' %{temp}/main.cpp
+// RUN: not grep -c '//---' %{temp}/main.cpp
+
+// RUN: grep foo %{temp}/input.txt
+// RUN: grep bar %{temp}/input.txt
+// RUN: not grep -c 'Pre-delimiter' %{temp}/input.txt
+// RUN: not grep -c 'int main' %{temp}/input.txt
+// RUN: not grep -c '//---' %{temp}/input.txt
+
+//--- main.cpp
+
+int main() {
+ return 0;
+}
+
+//--- input.txt
+
+foo
+bar
diff --git a/libcxx/test/selftest/split/test.split.sh b/libcxx/test/selftest/split/test.split.sh
new file mode 100644
index 0000000000000..a72e3aadfc501
--- /dev/null
+++ b/libcxx/test/selftest/split/test.split.sh
@@ -0,0 +1,24 @@
+# Pre-delimiter comment.
+
+# RUN: grep 'int main' %{temp}/main.cpp
+# RUN: grep 'return 0' %{temp}/main.cpp
+# RUN: not grep -c 'Pre-delimiter' %{temp}/main.cpp
+# RUN: not grep -c 'foo' %{temp}/main.cpp
+# RUN: not grep -c '//---' %{temp}/main.cpp
+
+# RUN: grep foo %{temp}/input.txt
+# RUN: grep bar %{temp}/input.txt
+# RUN: not grep -c 'Pre-delimiter' %{temp}/input.txt
+# RUN: not grep -c 'int main' %{temp}/input.txt
+# RUN: not grep -c '//---' %{temp}/input.txt
+
+#--- main.cpp
+
+int main() {
+ return 0;
+}
+
+#--- input.txt
+
+foo
+bar
diff --git a/libcxx/utils/libcxx/test/format.py b/libcxx/utils/libcxx/test/format.py
index 49cbe8a8db618..49186d2832757 100644
--- a/libcxx/utils/libcxx/test/format.py
+++ b/libcxx/utils/libcxx/test/format.py
@@ -281,6 +281,7 @@ def getTestsForPath(self, testSuite, pathInSuite, litConfig, localConfig):
"[.]sh[.][^.]+$",
"[.]gen[.][^.]+$",
"[.]verify[.]cpp$",
+ "[.]split[.][^.]+$",
]
sourcePath = testSuite.getSourcePath(pathInSuite)
@@ -365,6 +366,8 @@ def execute(self, test, litConfig):
return self._executeShTest(test, litConfig, steps)
elif re.search('[.]gen[.][^.]+$', filename): # This only happens when a generator test is not supported
return self._executeShTest(test, litConfig, [])
+ elif re.search('[.]split[.][^.]+$', filename):
+ return self._executeSplitTest(test, litConfig)
else:
return lit.Test.Result(
lit.Test.UNRESOLVED, "Unknown test suffix for '{}'".format(filename)
@@ -418,6 +421,36 @@ def _generateGenTest(self, testSuite, pathInSuite, litConfig, localConfig):
f.write(content)
yield lit.Test.Test(testSuite, (generatedFile,), localConfig)
+ # Split tests have following structure:
+ #
+ # // RUN: use %{temp}/main.cpp
+ # // RUN: use %{temp}/inputs.txt
+ #
+ # //--- main.cpp
+ # int main() { return 0; }
+ #
+ # //--- input.txt
+ # some inputs
+ #
+ # This function takes such test and creates the subfiles in the directory
+ # pointed to by the %{temp} substitution. Then it runs the original test file
+ # just like a regular shell LIT test.
+ def _executeSplitTest(self, test, litConfig):
+ with open(test.getSourcePath(), 'r') as f:
+ content_to_split = f.read()
+ for subfile, content in self._splitFile(content_to_split):
+ # Write split content into respective subfile in the temporary
+ # directory (pointed to by the substitution %t substitution).
+ tempDir, _ = _getTempPaths(test)
+ subfile_path = os.path.join(tempDir, subfile)
+ os.makedirs(os.path.dirname(subfile_path), exist_ok=True)
+ with open(subfile_path, 'w') as sf:
+ sf.write(content)
+
+ # Just as for regular .sh tests, the steps are already in the script.
+ steps = []
+ return self._executeShTest(test, litConfig, steps)
+
def _splitFile(self, input):
DELIM = r'^(//|#)---(.+)'
lines = input.splitlines()
@@ -430,7 +463,15 @@ def _splitFile(self, input):
yield (currentFile, '\n'.join(thisFileContent))
currentFile = match.group(2).strip()
thisFileContent = []
- assert currentFile is not None, f"Some input to split-file doesn't belong to any file, input was:\n{input}"
- thisFileContent.append(line)
+
+ # Anything before the first match line is disregarded.
+ # E.g., .split. tests put all the RUN lines before any
+ # split delimiters.
+ if currentFile is None:
+ continue
+
+ # Don't put the delimiter itself into the split content.
+ if not match:
+ thisFileContent.append(line)
if currentFile is not None:
yield (currentFile, '\n'.join(thisFileContent))
>From b515b157216106fdabdc6542d487bb51ee230e8e Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Wed, 25 Mar 2026 10:11:29 +0000
Subject: [PATCH 2/4] fixup! format
---
libcxx/test/selftest/split/test.split.cpp | 24 -----------------
libcxx/utils/libcxx/test/format.py | 32 +++++++++++------------
2 files changed, 16 insertions(+), 40 deletions(-)
delete mode 100644 libcxx/test/selftest/split/test.split.cpp
diff --git a/libcxx/test/selftest/split/test.split.cpp b/libcxx/test/selftest/split/test.split.cpp
deleted file mode 100644
index 38e638569e3d6..0000000000000
--- a/libcxx/test/selftest/split/test.split.cpp
+++ /dev/null
@@ -1,24 +0,0 @@
-// Pre-delimiter comment.
-
-// RUN: grep 'int main' %{temp}/main.cpp
-// RUN: grep 'return 0' %{temp}/main.cpp
-// RUN: not grep -c 'Pre-delimiter' %{temp}/main.cpp
-// RUN: not grep -c 'foo' %{temp}/main.cpp
-// RUN: not grep -c '//---' %{temp}/main.cpp
-
-// RUN: grep foo %{temp}/input.txt
-// RUN: grep bar %{temp}/input.txt
-// RUN: not grep -c 'Pre-delimiter' %{temp}/input.txt
-// RUN: not grep -c 'int main' %{temp}/input.txt
-// RUN: not grep -c '//---' %{temp}/input.txt
-
-//--- main.cpp
-
-int main() {
- return 0;
-}
-
-//--- input.txt
-
-foo
-bar
diff --git a/libcxx/utils/libcxx/test/format.py b/libcxx/utils/libcxx/test/format.py
index 49186d2832757..f3197aaec0dc9 100644
--- a/libcxx/utils/libcxx/test/format.py
+++ b/libcxx/utils/libcxx/test/format.py
@@ -281,7 +281,7 @@ def getTestsForPath(self, testSuite, pathInSuite, litConfig, localConfig):
"[.]sh[.][^.]+$",
"[.]gen[.][^.]+$",
"[.]verify[.]cpp$",
- "[.]split[.][^.]+$",
+ "[.]split[.]sh$",
]
sourcePath = testSuite.getSourcePath(pathInSuite)
@@ -366,7 +366,7 @@ def execute(self, test, litConfig):
return self._executeShTest(test, litConfig, steps)
elif re.search('[.]gen[.][^.]+$', filename): # This only happens when a generator test is not supported
return self._executeShTest(test, litConfig, [])
- elif re.search('[.]split[.][^.]+$', filename):
+ elif re.search("[.]split[.]sh$", filename):
return self._executeSplitTest(test, litConfig)
else:
return lit.Test.Result(
@@ -436,20 +436,20 @@ def _generateGenTest(self, testSuite, pathInSuite, litConfig, localConfig):
# pointed to by the %{temp} substitution. Then it runs the original test file
# just like a regular shell LIT test.
def _executeSplitTest(self, test, litConfig):
- with open(test.getSourcePath(), 'r') as f:
- content_to_split = f.read()
- for subfile, content in self._splitFile(content_to_split):
- # Write split content into respective subfile in the temporary
- # directory (pointed to by the substitution %t substitution).
- tempDir, _ = _getTempPaths(test)
- subfile_path = os.path.join(tempDir, subfile)
- os.makedirs(os.path.dirname(subfile_path), exist_ok=True)
- with open(subfile_path, 'w') as sf:
- sf.write(content)
-
- # Just as for regular .sh tests, the steps are already in the script.
- steps = []
- return self._executeShTest(test, litConfig, steps)
+ with open(test.getSourcePath(), "r") as f:
+ content_to_split = f.read()
+ for subfile, content in self._splitFile(content_to_split):
+ # Write split content into respective subfile in the temporary
+ # directory (pointed to by the substitution %t substitution).
+ tempDir, _ = _getTempPaths(test)
+ subfile_path = os.path.join(tempDir, subfile)
+ os.makedirs(os.path.dirname(subfile_path), exist_ok=True)
+ with open(subfile_path, "w") as sf:
+ sf.write(content)
+
+ # Just as for regular .sh tests, the steps are already in the script.
+ steps = []
+ return self._executeShTest(test, litConfig, steps)
def _splitFile(self, input):
DELIM = r'^(//|#)---(.+)'
>From 0485aa75bc5b0b16ea15e50d968bbbbee9fab07d Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Wed, 25 Mar 2026 10:12:16 +0000
Subject: [PATCH 3/4] fixup! fix comment
---
libcxx/utils/libcxx/test/format.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/libcxx/utils/libcxx/test/format.py b/libcxx/utils/libcxx/test/format.py
index f3197aaec0dc9..18d76abe23a98 100644
--- a/libcxx/utils/libcxx/test/format.py
+++ b/libcxx/utils/libcxx/test/format.py
@@ -423,13 +423,13 @@ def _generateGenTest(self, testSuite, pathInSuite, litConfig, localConfig):
# Split tests have following structure:
#
- # // RUN: use %{temp}/main.cpp
- # // RUN: use %{temp}/inputs.txt
+ # # RUN: use %{temp}/main.cpp
+ # # RUN: use %{temp}/inputs.txt
#
- # //--- main.cpp
+ # #--- main.cpp
# int main() { return 0; }
#
- # //--- input.txt
+ # #--- input.txt
# some inputs
#
# This function takes such test and creates the subfiles in the directory
>From c486f68087e8de8c8af140b1be855bd8a851c40a Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Wed, 25 Mar 2026 10:12:52 +0000
Subject: [PATCH 4/4] fixup! remove redundant comment
---
libcxx/utils/libcxx/test/format.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/libcxx/utils/libcxx/test/format.py b/libcxx/utils/libcxx/test/format.py
index 18d76abe23a98..19175c9fe35ea 100644
--- a/libcxx/utils/libcxx/test/format.py
+++ b/libcxx/utils/libcxx/test/format.py
@@ -439,8 +439,6 @@ def _executeSplitTest(self, test, litConfig):
with open(test.getSourcePath(), "r") as f:
content_to_split = f.read()
for subfile, content in self._splitFile(content_to_split):
- # Write split content into respective subfile in the temporary
- # directory (pointed to by the substitution %t substitution).
tempDir, _ = _getTempPaths(test)
subfile_path = os.path.join(tempDir, subfile)
os.makedirs(os.path.dirname(subfile_path), exist_ok=True)
More information about the libcxx-commits
mailing list