Bläddra i källkod

[lit] Port Support %if ... %else syntax for RUN lines from upstream (#5227)

* [lit] Port Support %if ... %else syntax for RUN lines from upstream

Based on
https://github.com/llvm/llvm-project/commit/1041a9642ba035fd2685f925911d705e8edf5bb0

This syntax allows to modify RUN lines based on features
available. For example:

    RUN: ... | FileCheck %s --check-prefix=%if windows %{CHECK-W%} %else %{CHECK-NON-W%}
    CHECK-W: ...
    CHECK-NON-W: ...

This is merged to allow dxilver check apply on each RUN line.
Xiang Li 2 år sedan
förälder
incheckning
18c9e114f9

+ 34 - 0
docs/CommandGuide/lit.rst

@@ -199,6 +199,40 @@ suite they are in, and their relative path inside the test suite.  For
 appropriately configured projects, this allows :program:`lit` to provide
 convenient and flexible support for out-of-tree builds.
 
+Substitutions
+-------------
+Besides replacing LLVM tool names the following substitutions are performed in
+RUN lines:
+
+``%s``
+   File path to the test case's source. This is suitable for passing on the
+   command line as the input to an LLVM tool.
+   Example: ``/home/user/llvm/test/MC/ELF/foo_test.s``
+
+``%S``
+   Directory path to the test case's source.
+   Example: ``/home/user/llvm/test/MC/ELF``
+
+``%t``
+   File path to a temporary file name that could be used for this test case.
+   The file name won't conflict with other test cases. You can append to it
+   if you need multiple temporaries. This is useful as the destination of
+   some redirected output.
+   Example: ``/home/user/llvm.build/test/MC/ELF/Output/foo_test.s.tmp``
+   
+``%T``
+   Directory of ``%t``. Deprecated. Shouldn't be used, because it can be easily
+   misused and cause race conditions between tests.
+   Use ``rm -rf %t && mkdir %t`` instead if a temporary directory is necessary.
+   Example: ``/home/user/llvm.build/test/MC/ELF/Output``
+
+``%if feature %{<if branch>%} %else %{<else branch>%}``
+
+ Conditional substitution: if ``feature`` is available it expands to
+ ``<if branch>``, otherwise it expands to ``<else branch>``.
+ ``%else %{<else branch>%}`` is optional and treated like ``%else %{%}``
+ if not present.
+
 .. _test-status-results:
 
 TEST STATUS RESULTS

+ 100 - 0
utils/lit/lit/TestRunner.py

@@ -437,6 +437,13 @@ def parseIntegratedTestScript(test, normalize_slashes=False,
             ('%/T', tmpDir.replace('\\', '/')),
             ])
 
+    # re for %if
+    re_cond_end = re.compile('%{')
+    re_if = re.compile('(.*?)(?:%if)')
+    re_nested_if = re.compile('(.*?)(?:%if|%})')
+    re_else = re.compile('^\s*%else\s*(%{)?')
+
+
     # Collect the test lines from the script.
     script = []
     requires = []
@@ -475,11 +482,104 @@ def parseIntegratedTestScript(test, normalize_slashes=False,
             raise ValueError("unknown script command type: %r" % (
                     command_type,))
 
+
+    def substituteIfElse(ln):
+        # early exit to avoid wasting time on lines without
+        # conditional substitutions
+        if ln.find('%if ') == -1:
+            return ln
+
+        def tryParseIfCond(ln):
+            # space is important to not conflict with other (possible)
+            # substitutions
+            if not ln.startswith('%if '):
+                return None, ln
+            ln = ln[4:]
+
+            # stop at '%{'
+            match = re_cond_end.search(ln)
+            if not match:
+                raise ValueError("'%{' is missing for %if substitution")
+            cond = ln[:match.start()]
+
+            # eat '%{' as well
+            ln = ln[match.end():]
+            return cond, ln
+
+        def tryParseElse(ln):
+            match = re_else.search(ln)
+            if not match:
+                return False, ln
+            if not match.group(1):
+                raise ValueError("'%{' is missing for %else substitution")
+            return True, ln[match.end():]
+
+        def tryParseEnd(ln):
+            if ln.startswith('%}'):
+                return True, ln[2:]
+            return False, ln
+
+        def parseText(ln, isNested):
+            # parse everything until %if, or %} if we're parsing a
+            # nested expression.
+            re_pat = re_nested_if if isNested else re_if
+            match = re_pat.search(ln)
+            if not match:
+                # there is no terminating pattern, so treat the whole
+                # line as text
+                return ln, ''
+            text_end = match.end(1)
+            return ln[:text_end], ln[text_end:]
+
+        def parseRecursive(ln, isNested):
+            result = ''
+            while len(ln):
+                if isNested:
+                    found_end, _ = tryParseEnd(ln)
+                    if found_end:
+                        break
+
+                # %if cond %{ branch_if %} %else %{ branch_else %}
+                cond, ln = tryParseIfCond(ln)
+                if cond:
+                    branch_if, ln = parseRecursive(ln, isNested=True)
+                    found_end, ln = tryParseEnd(ln)
+                    if not found_end:
+                        raise ValueError("'%}' is missing for %if substitution")
+
+                    branch_else = ''
+                    found_else, ln = tryParseElse(ln)
+                    if found_else:
+                        branch_else, ln = parseRecursive(ln, isNested=True)
+                        found_end, ln = tryParseEnd(ln)
+                        if not found_end:
+                            raise ValueError("'%}' is missing for %else substitution")
+
+                    cond = cond.strip()
+
+                    if cond in test.config.available_features:
+                        result += branch_if
+                    else:
+                        result += branch_else
+                    continue
+
+                # The rest is handled as plain text.
+                text, ln = parseText(ln, isNested)
+                result += text
+
+            return result, ln
+
+        result, ln = parseRecursive(ln, isNested=False)
+        assert len(ln) == 0
+        return result
+
     # 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.
     def processLine(ln):
         # Apply substitutions
+        ln = substituteIfElse(ln)
+
         for a,b in substitutions:
             if kIsWindows:
                 b = b.replace("\\","\\\\")

+ 7 - 0
utils/lit/lit/main.py

@@ -62,6 +62,9 @@ class TestingProgressDisplay(object):
             print(test.result.output)
             print("*" * 20)
 
+        if self.opts.showAllOutput:
+            print(test.result.output)
+
         # Report test metrics, if present.
         if test.result.metrics:
             print("%s TEST '%s' RESULTS %s" % ('*'*10, test.getFullName(),
@@ -163,6 +166,10 @@ def main(builtinParameters = {}):
     group.add_option("-v", "--verbose", dest="showOutput",
                      help="Show all test output",
                      action="store_true", default=False)
+    group.add_option("-a", "--show-all",
+            dest="showAllOutput",
+            help="Display all commandlines and output",
+            action="store_true", default=False)
     group.add_option("-o", "--output", dest="output_path",
                      help="Write test results to the provided path",
                      action="store", type=str, metavar="PATH")

+ 8 - 0
utils/lit/tests/Inputs/shtest-if-else/lit.cfg

@@ -0,0 +1,8 @@
+import lit.formats
+config.name = 'shtest-if-else'
+config.test_format = lit.formats.ShTest()
+config.test_source_root = None
+config.test_exec_root = None
+config.suffixes = ['.txt']
+config.available_features.add('feature')
+config.substitutions.append(('%{sub}', 'ok'))

+ 3 - 0
utils/lit/tests/Inputs/shtest-if-else/test-neg1.txt

@@ -0,0 +1,3 @@
+# CHECK: ValueError: '%{' is missing for %if substitution
+#
+# RUN: %if feature echo "test-1"

+ 3 - 0
utils/lit/tests/Inputs/shtest-if-else/test-neg2.txt

@@ -0,0 +1,3 @@
+# CHECK: ValueError: '%}' is missing for %if substitution
+#
+# RUN: %if feature %{ echo

+ 3 - 0
utils/lit/tests/Inputs/shtest-if-else/test-neg3.txt

@@ -0,0 +1,3 @@
+# CHECK: ValueError: '%{' is missing for %else substitution
+#
+# RUN: %if feature %{ echo %} %else fail

+ 3 - 0
utils/lit/tests/Inputs/shtest-if-else/test-neg4.txt

@@ -0,0 +1,3 @@
+# CHECK: ValueError: '%}' is missing for %else substitution
+#
+# RUN: %if feature %{ echo %} %else %{ fail

+ 75 - 0
utils/lit/tests/Inputs/shtest-if-else/test.txt

@@ -0,0 +1,75 @@
+# CHECK: -- Testing:{{.*}}
+# CHECK-NEXT: PASS: shtest-if-else :: test.txt (1 of 1)
+# CHECK-NEXT: Script:
+# CHECK-NEXT: --
+
+# RUN: %if feature %{ echo "feature" %} %else %{ echo "missing feature" %}
+# CHECK-NEXT: echo "feature"
+
+#
+# RUN: %if nofeature %{ echo "found unicorn" %} %else %{ echo "nofeature" %}
+# CHECK-NEXT: "nofeature"
+# CHECK-NOT: found unicorn
+
+# Spaces inside curly braces are not ignored
+#
+# RUN: echo test-%if feature %{ 3 %} %else %{ echo "fail" %}-test
+# RUN: echo test-%if feature %{ 4 4 %} %else %{ echo "fail" %}-test
+# RUN: echo test-%if nofeature %{ echo "fail" %} %else %{ 5 5 %}-test
+# CHECK-NEXT: echo test- 3 -test
+# CHECK-NOT: echo "fail"
+# CHECK-NEXT: echo test- 4 4 -test
+# CHECK-NOT: echo "fail"
+# CHECK-NEXT: echo test- 5 5 -test
+# CHECK-NOT: echo "fail"
+
+# Escape line breaks for multi-line expressions
+#
+# RUN: %if feature  \
+# RUN:   %{ echo     \
+# RUN:     "test-5" \
+# RUN:   %} %else %{ echo "fail" %}
+# CHECK-NEXT: echo "test-5"
+
+# RUN: %if nofeature       \
+# RUN:   %{ echo "fail" %}   \
+# RUN: %else               \
+# RUN:   %{ echo "test-6" %}
+# CHECK-NEXT: echo "test-6"
+
+# RUN: echo "test%if feature %{%} %else %{%}-7"
+# CHECK-NEXT: echo "test-7"
+
+
+# Nested expressions are supported:
+#
+# RUN: echo %if feature %{ %if feature %{ %if nofeature %{"fail"%} %else %{"test-9"%} %} %}
+# CHECK-NEXT: echo "test-9"
+
+# Spaces between %if and %else are ignored. If there is no %else -
+# space after %if %{...%} is not ignored.
+#
+# RUN: echo XX %if feature %{YY%} ZZ
+# RUN: echo AA %if feature %{BB%} %else %{CC%} DD
+# RUN: echo AA %if nofeature %{BB%} %else %{CC%} DD
+# CHECK-NEXT: echo XX YY ZZ
+# CHECK-NEXT: echo AA BB DD
+# CHECK-NEXT: echo AA CC DD
+
+# '{' and '}' can be used without escaping
+#
+# RUN: %if feature %{echo {}%}
+# CHECK-NEXT: echo {}
+
+# Spaces are not required
+#
+# RUN: echo %if feature%{"ok"%}%else%{"fail"%}
+# CHECK-NEXT: echo "ok"
+
+# Substitutions with braces are handled correctly
+#
+# RUN: echo %{sub} %if feature%{test-%{sub}%}%else%{"fail"%}
+# CHECK-NEXT: echo ok test-ok
+
+# CHECK-NEXT: --
+# CHECK-NEXT: Exit Code: 0

+ 14 - 0
utils/lit/tests/shtest-if-else.py

@@ -0,0 +1,14 @@
+# RUN: %{lit} -v --show-all %{inputs}/shtest-if-else/test.txt \
+# RUN:    | FileCheck %{inputs}/shtest-if-else/test.txt
+
+# RUN: not %{lit} -v  %{inputs}/shtest-if-else/test-neg1.txt 2>&1 \
+# RUN:    | FileCheck %{inputs}/shtest-if-else/test-neg1.txt
+
+# RUN: not %{lit} -v  %{inputs}/shtest-if-else/test-neg2.txt 2>&1 \
+# RUN:    | FileCheck %{inputs}/shtest-if-else/test-neg2.txt
+
+# RUN: not %{lit} -v  %{inputs}/shtest-if-else/test-neg3.txt 2>&1 \
+# RUN:    | FileCheck %{inputs}/shtest-if-else/test-neg3.txt
+
+# RUN: not %{lit} -v  %{inputs}/shtest-if-else/test-neg4.txt 2>&1 \
+# RUN:    | FileCheck %{inputs}/shtest-if-else/test-neg4.txt