[llvm] faf415a - [lit] Recursively expand substitutions
Louis Dionne via llvm-commits
llvm-commits at lists.llvm.org
Fri Mar 27 06:25:46 PDT 2020
Author: Louis Dionne
Date: 2020-03-27T09:25:26-04:00
New Revision: faf415a1dec1b7f500b6938152dcd6b5a0845e4a
URL: https://github.com/llvm/llvm-project/commit/faf415a1dec1b7f500b6938152dcd6b5a0845e4a
DIFF: https://github.com/llvm/llvm-project/commit/faf415a1dec1b7f500b6938152dcd6b5a0845e4a.diff
LOG: [lit] Recursively expand substitutions
This allows defining substitutions in terms of other substitutions. For
example, a %build substitution could be defined in terms of a %cxx
substitution as '%cxx %s -o %t.exe' and the script would be properly
expanded.
Differential Revision: https://reviews.llvm.org/D76178
Added:
llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/does-not-substitute-no-limit/lit.cfg
llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/does-not-substitute-no-limit/test.py
llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/does-not-substitute-within-limit/lit.cfg
llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/does-not-substitute-within-limit/test.py
llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/negative-integer/lit.cfg
llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/negative-integer/test.py
llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/not-an-integer/lit.cfg
llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/not-an-integer/test.py
llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/set-to-none/lit.cfg
llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/set-to-none/test.py
llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/substitutes-within-limit/lit.cfg
llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/substitutes-within-limit/test.py
llvm/utils/lit/tests/shtest-recursive-substitution.py
Modified:
llvm/docs/CommandGuide/lit.rst
llvm/utils/lit/lit/LitConfig.py
llvm/utils/lit/lit/TestRunner.py
llvm/utils/lit/tests/unit/TestRunner.py
Removed:
################################################################################
diff --git a/llvm/docs/CommandGuide/lit.rst b/llvm/docs/CommandGuide/lit.rst
index 63518fb20adc..ebb89d2f853d 100644
--- a/llvm/docs/CommandGuide/lit.rst
+++ b/llvm/docs/CommandGuide/lit.rst
@@ -410,11 +410,12 @@ be used to define subdirectories of optional tests, or to change other
configuration parameters --- for example, to change the test format, or the
suffixes which identify test files.
-PRE-DEFINED SUBSTITUTIONS
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+SUBSTITUTIONS
+~~~~~~~~~~~~~
-:program:`lit` provides various patterns that can be used with the RUN command.
-These are defined in TestRunner.py. The base set of substitutions are:
+:program:`lit` allows patterns to be substituted inside RUN commands. It also
+provides the following base set of substitutions, which are defined in
+TestRunner.py:
======================= ==============
Macro Substitution
@@ -453,6 +454,14 @@ Other substitutions are provided that are variations on this base set and
further substitution patterns can be defined by each test module. See the
modules :ref:`local-configuration-files`.
+By default, substitutions are expanded exactly once, so that if e.g. a
+substitution ``%build`` is defined in top of another substitution ``%cxx``,
+``%build`` will expand to ``%cxx`` textually, not to what ``%cxx`` expands to.
+However, if the ``recursiveExpansionLimit`` property of the ``LitConfig`` is
+set to a non-negative integer, substitutions will be expanded recursively until
+that limit is reached. It is an error if the limit is reached and expanding
+substitutions again would yield a
diff erent result.
+
More detailed information on substitutions can be found in the
:doc:`../TestingGuide`.
diff --git a/llvm/utils/lit/lit/LitConfig.py b/llvm/utils/lit/lit/LitConfig.py
index 58011b5986bf..9ec634388cc1 100644
--- a/llvm/utils/lit/lit/LitConfig.py
+++ b/llvm/utils/lit/lit/LitConfig.py
@@ -66,6 +66,19 @@ def __init__(self, progname, path, quiet,
self.maxIndividualTestTime = maxIndividualTestTime
self.parallelism_groups = parallelism_groups
self.echo_all_commands = echo_all_commands
+ self._recursiveExpansionLimit = None
+
+ @property
+ def recursiveExpansionLimit(self):
+ return self._recursiveExpansionLimit
+
+ @recursiveExpansionLimit.setter
+ def recursiveExpansionLimit(self, value):
+ if value is not None and not isinstance(value, int):
+ self.fatal('recursiveExpansionLimit must be either None or an integer (got <{}>)'.format(value))
+ if isinstance(value, int) and value < 0:
+ self.fatal('recursiveExpansionLimit must be a non-negative integer (got <{}>)'.format(value))
+ self._recursiveExpansionLimit = value
@property
def maxIndividualTestTime(self):
diff --git a/llvm/utils/lit/lit/TestRunner.py b/llvm/utils/lit/lit/TestRunner.py
index 12a686ff41b1..1632827a300d 100644
--- a/llvm/utils/lit/lit/TestRunner.py
+++ b/llvm/utils/lit/lit/TestRunner.py
@@ -1147,10 +1147,18 @@ def memoized(x):
def _caching_re_compile(r):
return re.compile(r)
-def applySubstitutions(script, substitutions):
- """Apply substitutions to the script. Allow full regular expression syntax.
+def applySubstitutions(script, substitutions, recursion_limit=None):
+ """
+ Apply substitutions to the script. Allow full regular expression syntax.
Replace each matching occurrence of regular expression pattern a with
- substitution b in line ln."""
+ substitution b in line ln.
+
+ If a substitution expands into another substitution, it is expanded
+ recursively until the line has no more expandable substitutions. If
+ the line can still can be substituted after being substituted
+ `recursion_limit` times, it is an error. If the `recursion_limit` is
+ `None` (the default), no recursive substitution is performed at all.
+ """
def processLine(ln):
# Apply substitutions
for a,b in substitutions:
@@ -1167,9 +1175,28 @@ def processLine(ln):
# Strip the trailing newline and any extra whitespace.
return ln.strip()
+
+ def processLineToFixedPoint(ln):
+ assert isinstance(recursion_limit, int) and recursion_limit >= 0
+ origLine = ln
+ steps = 0
+ processed = processLine(ln)
+ while processed != ln and steps < recursion_limit:
+ ln = processed
+ processed = processLine(ln)
+ steps += 1
+
+ if processed != ln:
+ raise ValueError("Recursive substitution of '%s' did not complete "
+ "in the provided recursion limit (%s)" % \
+ (origLine, recursion_limit))
+
+ return processed
+
# Note Python 3 map() gives an iterator rather than a list so explicitly
# convert to list before returning.
- return list(map(processLine, script))
+ process = processLine if recursion_limit is None else processLineToFixedPoint
+ return list(map(process, script))
class ParserKind(object):
@@ -1506,7 +1533,8 @@ def executeShTest(test, litConfig, useExternalSh,
substitutions = list(extra_substitutions)
substitutions += getDefaultSubstitutions(test, tmpDir, tmpBase,
normalize_slashes=useExternalSh)
- script = applySubstitutions(script, substitutions)
+ script = applySubstitutions(script, substitutions,
+ recursion_limit=litConfig.recursiveExpansionLimit)
# Re-run failed tests up to test.allowed_retries times.
attempts = test.allowed_retries + 1
diff --git a/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/does-not-substitute-no-limit/lit.cfg b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/does-not-substitute-no-limit/lit.cfg
new file mode 100644
index 000000000000..80c5f88c2ebd
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/does-not-substitute-no-limit/lit.cfg
@@ -0,0 +1,10 @@
+import lit.formats
+config.name = 'does-not-substitute-no-limit'
+config.suffixes = ['.py']
+config.test_format = lit.formats.ShTest()
+config.test_source_root = None
+config.test_exec_root = None
+
+config.substitutions = [("%rec1", "STOP"), ("%rec2", "%rec1"),
+ ("%rec3", "%rec2"), ("%rec4", "%rec3"),
+ ("%rec5", "%rec4")]
diff --git a/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/does-not-substitute-no-limit/test.py b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/does-not-substitute-no-limit/test.py
new file mode 100644
index 000000000000..eb115d6f73cd
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/does-not-substitute-no-limit/test.py
@@ -0,0 +1 @@
+# RUN: echo %rec5
diff --git a/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/does-not-substitute-within-limit/lit.cfg b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/does-not-substitute-within-limit/lit.cfg
new file mode 100644
index 000000000000..8fce89741b66
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/does-not-substitute-within-limit/lit.cfg
@@ -0,0 +1,12 @@
+import lit.formats
+config.name = 'does-not-substitute-within-limit'
+config.suffixes = ['.py']
+config.test_format = lit.formats.ShTest()
+config.test_source_root = None
+config.test_exec_root = None
+
+config.substitutions = [("%rec1", "STOP"), ("%rec2", "%rec1"),
+ ("%rec3", "%rec2"), ("%rec4", "%rec3"),
+ ("%rec5", "%rec4")]
+
+lit_config.recursiveExpansionLimit = 2
diff --git a/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/does-not-substitute-within-limit/test.py b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/does-not-substitute-within-limit/test.py
new file mode 100644
index 000000000000..eb115d6f73cd
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/does-not-substitute-within-limit/test.py
@@ -0,0 +1 @@
+# RUN: echo %rec5
diff --git a/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/negative-integer/lit.cfg b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/negative-integer/lit.cfg
new file mode 100644
index 000000000000..0037b34bb54f
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/negative-integer/lit.cfg
@@ -0,0 +1,8 @@
+import lit.formats
+config.name = 'negative-integer'
+config.suffixes = ['.py']
+config.test_format = lit.formats.ShTest()
+config.test_source_root = None
+config.test_exec_root = None
+
+lit_config.recursiveExpansionLimit = -4
diff --git a/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/negative-integer/test.py b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/negative-integer/test.py
new file mode 100644
index 000000000000..b80b60b7a279
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/negative-integer/test.py
@@ -0,0 +1 @@
+# RUN: true
diff --git a/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/not-an-integer/lit.cfg b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/not-an-integer/lit.cfg
new file mode 100644
index 000000000000..3eb36d8663dd
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/not-an-integer/lit.cfg
@@ -0,0 +1,8 @@
+import lit.formats
+config.name = 'not-an-integer'
+config.suffixes = ['.py']
+config.test_format = lit.formats.ShTest()
+config.test_source_root = None
+config.test_exec_root = None
+
+lit_config.recursiveExpansionLimit = "not-an-integer"
diff --git a/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/not-an-integer/test.py b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/not-an-integer/test.py
new file mode 100644
index 000000000000..b80b60b7a279
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/not-an-integer/test.py
@@ -0,0 +1 @@
+# RUN: true
diff --git a/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/set-to-none/lit.cfg b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/set-to-none/lit.cfg
new file mode 100644
index 000000000000..1dfc57a80944
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/set-to-none/lit.cfg
@@ -0,0 +1,8 @@
+import lit.formats
+config.name = 'set-to-none'
+config.suffixes = ['.py']
+config.test_format = lit.formats.ShTest()
+config.test_source_root = None
+config.test_exec_root = None
+
+lit_config.recursiveExpansionLimit = None
diff --git a/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/set-to-none/test.py b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/set-to-none/test.py
new file mode 100644
index 000000000000..b80b60b7a279
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/set-to-none/test.py
@@ -0,0 +1 @@
+# RUN: true
diff --git a/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/substitutes-within-limit/lit.cfg b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/substitutes-within-limit/lit.cfg
new file mode 100644
index 000000000000..aa66c0e856eb
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/substitutes-within-limit/lit.cfg
@@ -0,0 +1,12 @@
+import lit.formats
+config.name = 'substitutes-within-limit'
+config.suffixes = ['.py']
+config.test_format = lit.formats.ShTest()
+config.test_source_root = None
+config.test_exec_root = None
+
+config.substitutions = [("%rec1", "STOP"), ("%rec2", "%rec1"),
+ ("%rec3", "%rec2"), ("%rec4", "%rec3"),
+ ("%rec5", "%rec4")]
+
+lit_config.recursiveExpansionLimit = 5
diff --git a/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/substitutes-within-limit/test.py b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/substitutes-within-limit/test.py
new file mode 100644
index 000000000000..eb115d6f73cd
--- /dev/null
+++ b/llvm/utils/lit/tests/Inputs/shtest-recursive-substitution/substitutes-within-limit/test.py
@@ -0,0 +1 @@
+# RUN: echo %rec5
diff --git a/llvm/utils/lit/tests/shtest-recursive-substitution.py b/llvm/utils/lit/tests/shtest-recursive-substitution.py
new file mode 100644
index 000000000000..1b3d5f4c4451
--- /dev/null
+++ b/llvm/utils/lit/tests/shtest-recursive-substitution.py
@@ -0,0 +1,23 @@
+# Check that the config.recursiveExpansionLimit is picked up and will cause
+# lit substitutions to be expanded recursively.
+
+# RUN: %{lit} -j 1 %{inputs}/shtest-recursive-substitution/substitutes-within-limit --show-all | FileCheck --check-prefix=CHECK-TEST1 %s
+# CHECK-TEST1: PASS: substitutes-within-limit :: test.py
+# CHECK-TEST1: $ "echo" "STOP"
+
+# RUN: not %{lit} -j 1 %{inputs}/shtest-recursive-substitution/does-not-substitute-within-limit --show-all | FileCheck --check-prefix=CHECK-TEST2 %s
+# CHECK-TEST2: UNRESOLVED: does-not-substitute-within-limit :: test.py
+# CHECK-TEST2: ValueError: Recursive substitution of
+
+# RUN: %{lit} -j 1 %{inputs}/shtest-recursive-substitution/does-not-substitute-no-limit --show-all | FileCheck --check-prefix=CHECK-TEST3 %s
+# CHECK-TEST3: PASS: does-not-substitute-no-limit :: test.py
+# CHECK-TEST3: $ "echo" "%rec4"
+
+# RUN: not %{lit} -j 1 %{inputs}/shtest-recursive-substitution/not-an-integer --show-all 2>&1 | FileCheck --check-prefix=CHECK-TEST4 %s
+# CHECK-TEST4: recursiveExpansionLimit must be either None or an integer
+
+# RUN: not %{lit} -j 1 %{inputs}/shtest-recursive-substitution/negative-integer --show-all 2>&1 | FileCheck --check-prefix=CHECK-TEST5 %s
+# CHECK-TEST5: recursiveExpansionLimit must be a non-negative integer
+
+# RUN: %{lit} -j 1 %{inputs}/shtest-recursive-substitution/set-to-none --show-all | FileCheck --check-prefix=CHECK-TEST6 %s
+# CHECK-TEST6: PASS: set-to-none :: test.py
diff --git a/llvm/utils/lit/tests/unit/TestRunner.py b/llvm/utils/lit/tests/unit/TestRunner.py
index 4f33fce64885..5b5703c2e17d 100644
--- a/llvm/utils/lit/tests/unit/TestRunner.py
+++ b/llvm/utils/lit/tests/unit/TestRunner.py
@@ -199,6 +199,74 @@ def custom_parse(line_number, line, output):
except BaseException as e:
self.fail("CUSTOM_NO_PARSER: raised the wrong exception: %r" % e)
+class TestApplySubtitutions(unittest.TestCase):
+ def test_simple(self):
+ script = ["echo %bar"]
+ substitutions = [("%bar", "hello")]
+ result = lit.TestRunner.applySubstitutions(script, substitutions)
+ self.assertEqual(result, ["echo hello"])
+
+ def test_multiple_substitutions(self):
+ script = ["echo %bar %baz"]
+ substitutions = [("%bar", "hello"),
+ ("%baz", "world"),
+ ("%useless", "shouldnt expand")]
+ result = lit.TestRunner.applySubstitutions(script, substitutions)
+ self.assertEqual(result, ["echo hello world"])
+
+ def test_multiple_script_lines(self):
+ script = ["%cxx %compile_flags -c -o %t.o",
+ "%cxx %link_flags %t.o -o %t.exe"]
+ substitutions = [("%cxx", "clang++"),
+ ("%compile_flags", "-std=c++11 -O3"),
+ ("%link_flags", "-lc++")]
+ result = lit.TestRunner.applySubstitutions(script, substitutions)
+ self.assertEqual(result, ["clang++ -std=c++11 -O3 -c -o %t.o",
+ "clang++ -lc++ %t.o -o %t.exe"])
+
+ def test_recursive_substitution_real(self):
+ script = ["%build %s"]
+ substitutions = [("%cxx", "clang++"),
+ ("%compile_flags", "-std=c++11 -O3"),
+ ("%link_flags", "-lc++"),
+ ("%build", "%cxx %compile_flags %link_flags %s -o %t.exe")]
+ result = lit.TestRunner.applySubstitutions(script, substitutions, recursion_limit=3)
+ self.assertEqual(result, ["clang++ -std=c++11 -O3 -lc++ %s -o %t.exe %s"])
+
+ def test_recursive_substitution_limit(self):
+ script = ["%rec5"]
+ # Make sure the substitutions are not in an order where the global
+ # substitution would appear to be recursive just because they are
+ # processed in the right order.
+ substitutions = [("%rec1", "STOP"), ("%rec2", "%rec1"),
+ ("%rec3", "%rec2"), ("%rec4", "%rec3"), ("%rec5", "%rec4")]
+ for limit in [5, 6, 7]:
+ result = lit.TestRunner.applySubstitutions(script, substitutions, recursion_limit=limit)
+ self.assertEqual(result, ["STOP"])
+
+ def test_recursive_substitution_limit_exceeded(self):
+ script = ["%rec5"]
+ substitutions = [("%rec1", "STOP"), ("%rec2", "%rec1"),
+ ("%rec3", "%rec2"), ("%rec4", "%rec3"), ("%rec5", "%rec4")]
+ for limit in [0, 1, 2, 3, 4]:
+ try:
+ lit.TestRunner.applySubstitutions(script, substitutions, recursion_limit=limit)
+ self.fail("applySubstitutions should have raised an exception")
+ except ValueError:
+ pass
+
+ def test_recursive_substitution_invalid_value(self):
+ script = ["%rec5"]
+ substitutions = [("%rec1", "STOP"), ("%rec2", "%rec1"),
+ ("%rec3", "%rec2"), ("%rec4", "%rec3"), ("%rec5", "%rec4")]
+ for limit in [-1, -2, -3, "foo"]:
+ try:
+ lit.TestRunner.applySubstitutions(script, substitutions, recursion_limit=limit)
+ self.fail("applySubstitutions should have raised an exception")
+ except AssertionError:
+ pass
+
+
if __name__ == '__main__':
TestIntegratedTestKeywordParser.load_keyword_parser_lit_tests()
unittest.main(verbosity=2)
More information about the llvm-commits
mailing list