diff --git a/docs/CommandGuide/lit.rst b/docs/CommandGuide/lit.rst index e820eef2f..f3a7d2b53 100644 --- a/docs/CommandGuide/lit.rst +++ b/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 %{%} %else %{%}`` + + Conditional substitution: if ``feature`` is available it expands to + ````, otherwise it expands to ````. + ``%else %{%}`` is optional and treated like ``%else %{%}`` + if not present. + .. _test-status-results: TEST STATUS RESULTS diff --git a/utils/lit/lit/TestRunner.py b/utils/lit/lit/TestRunner.py index 70382b472..d5c3370ae 100644 --- a/utils/lit/lit/TestRunner.py +++ b/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("\\","\\\\") diff --git a/utils/lit/lit/main.py b/utils/lit/lit/main.py index e3722674f..1e66fb80d 100755 --- a/utils/lit/lit/main.py +++ b/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") diff --git a/utils/lit/tests/Inputs/shtest-if-else/lit.cfg b/utils/lit/tests/Inputs/shtest-if-else/lit.cfg new file mode 100644 index 000000000..b2243df51 --- /dev/null +++ b/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')) diff --git a/utils/lit/tests/Inputs/shtest-if-else/test-neg1.txt b/utils/lit/tests/Inputs/shtest-if-else/test-neg1.txt new file mode 100644 index 000000000..ce7485269 --- /dev/null +++ b/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" diff --git a/utils/lit/tests/Inputs/shtest-if-else/test-neg2.txt b/utils/lit/tests/Inputs/shtest-if-else/test-neg2.txt new file mode 100644 index 000000000..ae7ad887a --- /dev/null +++ b/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 diff --git a/utils/lit/tests/Inputs/shtest-if-else/test-neg3.txt b/utils/lit/tests/Inputs/shtest-if-else/test-neg3.txt new file mode 100644 index 000000000..ed6594c23 --- /dev/null +++ b/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 diff --git a/utils/lit/tests/Inputs/shtest-if-else/test-neg4.txt b/utils/lit/tests/Inputs/shtest-if-else/test-neg4.txt new file mode 100644 index 000000000..0ee85f2df --- /dev/null +++ b/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 diff --git a/utils/lit/tests/Inputs/shtest-if-else/test.txt b/utils/lit/tests/Inputs/shtest-if-else/test.txt new file mode 100644 index 000000000..e5405709b --- /dev/null +++ b/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 diff --git a/utils/lit/tests/shtest-if-else.py b/utils/lit/tests/shtest-if-else.py new file mode 100644 index 000000000..bac958615 --- /dev/null +++ b/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