Skip to content

Commit 18c9e11

Browse files
authoredMay 26, 2023
[lit] Port Support %if ... %else syntax for RUN lines from upstream (microsoft#5227)
* [lit] Port Support %if ... %else syntax for RUN lines from upstream Based on llvm/llvm-project@1041a96 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.
1 parent 4e0ef84 commit 18c9e11

File tree

10 files changed

+250
-0
lines changed

10 files changed

+250
-0
lines changed
 

‎docs/CommandGuide/lit.rst

+34
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,40 @@ suite they are in, and their relative path inside the test suite. For
199199
appropriately configured projects, this allows :program:`lit` to provide
200200
convenient and flexible support for out-of-tree builds.
201201

202+
Substitutions
203+
-------------
204+
Besides replacing LLVM tool names the following substitutions are performed in
205+
RUN lines:
206+
207+
``%s``
208+
File path to the test case's source. This is suitable for passing on the
209+
command line as the input to an LLVM tool.
210+
Example: ``/home/user/llvm/test/MC/ELF/foo_test.s``
211+
212+
``%S``
213+
Directory path to the test case's source.
214+
Example: ``/home/user/llvm/test/MC/ELF``
215+
216+
``%t``
217+
File path to a temporary file name that could be used for this test case.
218+
The file name won't conflict with other test cases. You can append to it
219+
if you need multiple temporaries. This is useful as the destination of
220+
some redirected output.
221+
Example: ``/home/user/llvm.build/test/MC/ELF/Output/foo_test.s.tmp``
222+
223+
``%T``
224+
Directory of ``%t``. Deprecated. Shouldn't be used, because it can be easily
225+
misused and cause race conditions between tests.
226+
Use ``rm -rf %t && mkdir %t`` instead if a temporary directory is necessary.
227+
Example: ``/home/user/llvm.build/test/MC/ELF/Output``
228+
229+
``%if feature %{<if branch>%} %else %{<else branch>%}``
230+
231+
Conditional substitution: if ``feature`` is available it expands to
232+
``<if branch>``, otherwise it expands to ``<else branch>``.
233+
``%else %{<else branch>%}`` is optional and treated like ``%else %{%}``
234+
if not present.
235+
202236
.. _test-status-results:
203237

204238
TEST STATUS RESULTS

‎utils/lit/lit/TestRunner.py

+100
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,13 @@ def parseIntegratedTestScript(test, normalize_slashes=False,
437437
('%/T', tmpDir.replace('\\', '/')),
438438
])
439439

440+
# re for %if
441+
re_cond_end = re.compile('%{')
442+
re_if = re.compile('(.*?)(?:%if)')
443+
re_nested_if = re.compile('(.*?)(?:%if|%})')
444+
re_else = re.compile('^\s*%else\s*(%{)?')
445+
446+
440447
# Collect the test lines from the script.
441448
script = []
442449
requires = []
@@ -475,11 +482,104 @@ def replace_line_number(match):
475482
raise ValueError("unknown script command type: %r" % (
476483
command_type,))
477484

485+
486+
def substituteIfElse(ln):
487+
# early exit to avoid wasting time on lines without
488+
# conditional substitutions
489+
if ln.find('%if ') == -1:
490+
return ln
491+
492+
def tryParseIfCond(ln):
493+
# space is important to not conflict with other (possible)
494+
# substitutions
495+
if not ln.startswith('%if '):
496+
return None, ln
497+
ln = ln[4:]
498+
499+
# stop at '%{'
500+
match = re_cond_end.search(ln)
501+
if not match:
502+
raise ValueError("'%{' is missing for %if substitution")
503+
cond = ln[:match.start()]
504+
505+
# eat '%{' as well
506+
ln = ln[match.end():]
507+
return cond, ln
508+
509+
def tryParseElse(ln):
510+
match = re_else.search(ln)
511+
if not match:
512+
return False, ln
513+
if not match.group(1):
514+
raise ValueError("'%{' is missing for %else substitution")
515+
return True, ln[match.end():]
516+
517+
def tryParseEnd(ln):
518+
if ln.startswith('%}'):
519+
return True, ln[2:]
520+
return False, ln
521+
522+
def parseText(ln, isNested):
523+
# parse everything until %if, or %} if we're parsing a
524+
# nested expression.
525+
re_pat = re_nested_if if isNested else re_if
526+
match = re_pat.search(ln)
527+
if not match:
528+
# there is no terminating pattern, so treat the whole
529+
# line as text
530+
return ln, ''
531+
text_end = match.end(1)
532+
return ln[:text_end], ln[text_end:]
533+
534+
def parseRecursive(ln, isNested):
535+
result = ''
536+
while len(ln):
537+
if isNested:
538+
found_end, _ = tryParseEnd(ln)
539+
if found_end:
540+
break
541+
542+
# %if cond %{ branch_if %} %else %{ branch_else %}
543+
cond, ln = tryParseIfCond(ln)
544+
if cond:
545+
branch_if, ln = parseRecursive(ln, isNested=True)
546+
found_end, ln = tryParseEnd(ln)
547+
if not found_end:
548+
raise ValueError("'%}' is missing for %if substitution")
549+
550+
branch_else = ''
551+
found_else, ln = tryParseElse(ln)
552+
if found_else:
553+
branch_else, ln = parseRecursive(ln, isNested=True)
554+
found_end, ln = tryParseEnd(ln)
555+
if not found_end:
556+
raise ValueError("'%}' is missing for %else substitution")
557+
558+
cond = cond.strip()
559+
560+
if cond in test.config.available_features:
561+
result += branch_if
562+
else:
563+
result += branch_else
564+
continue
565+
566+
# The rest is handled as plain text.
567+
text, ln = parseText(ln, isNested)
568+
result += text
569+
570+
return result, ln
571+
572+
result, ln = parseRecursive(ln, isNested=False)
573+
assert len(ln) == 0
574+
return result
575+
478576
# Apply substitutions to the script. Allow full regular
479577
# expression syntax. Replace each matching occurrence of regular
480578
# expression pattern a with substitution b in line ln.
481579
def processLine(ln):
482580
# Apply substitutions
581+
ln = substituteIfElse(ln)
582+
483583
for a,b in substitutions:
484584
if kIsWindows:
485585
b = b.replace("\\","\\\\")

‎utils/lit/lit/main.py

+7
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ def update(self, test):
6262
print(test.result.output)
6363
print("*" * 20)
6464

65+
if self.opts.showAllOutput:
66+
print(test.result.output)
67+
6568
# Report test metrics, if present.
6669
if test.result.metrics:
6770
print("%s TEST '%s' RESULTS %s" % ('*'*10, test.getFullName(),
@@ -163,6 +166,10 @@ def main(builtinParameters = {}):
163166
group.add_option("-v", "--verbose", dest="showOutput",
164167
help="Show all test output",
165168
action="store_true", default=False)
169+
group.add_option("-a", "--show-all",
170+
dest="showAllOutput",
171+
help="Display all commandlines and output",
172+
action="store_true", default=False)
166173
group.add_option("-o", "--output", dest="output_path",
167174
help="Write test results to the provided path",
168175
action="store", type=str, metavar="PATH")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import lit.formats
2+
config.name = 'shtest-if-else'
3+
config.test_format = lit.formats.ShTest()
4+
config.test_source_root = None
5+
config.test_exec_root = None
6+
config.suffixes = ['.txt']
7+
config.available_features.add('feature')
8+
config.substitutions.append(('%{sub}', 'ok'))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# CHECK: ValueError: '%{' is missing for %if substitution
2+
#
3+
# RUN: %if feature echo "test-1"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# CHECK: ValueError: '%}' is missing for %if substitution
2+
#
3+
# RUN: %if feature %{ echo
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# CHECK: ValueError: '%{' is missing for %else substitution
2+
#
3+
# RUN: %if feature %{ echo %} %else fail
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# CHECK: ValueError: '%}' is missing for %else substitution
2+
#
3+
# RUN: %if feature %{ echo %} %else %{ fail
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# CHECK: -- Testing:{{.*}}
2+
# CHECK-NEXT: PASS: shtest-if-else :: test.txt (1 of 1)
3+
# CHECK-NEXT: Script:
4+
# CHECK-NEXT: --
5+
6+
# RUN: %if feature %{ echo "feature" %} %else %{ echo "missing feature" %}
7+
# CHECK-NEXT: echo "feature"
8+
9+
#
10+
# RUN: %if nofeature %{ echo "found unicorn" %} %else %{ echo "nofeature" %}
11+
# CHECK-NEXT: "nofeature"
12+
# CHECK-NOT: found unicorn
13+
14+
# Spaces inside curly braces are not ignored
15+
#
16+
# RUN: echo test-%if feature %{ 3 %} %else %{ echo "fail" %}-test
17+
# RUN: echo test-%if feature %{ 4 4 %} %else %{ echo "fail" %}-test
18+
# RUN: echo test-%if nofeature %{ echo "fail" %} %else %{ 5 5 %}-test
19+
# CHECK-NEXT: echo test- 3 -test
20+
# CHECK-NOT: echo "fail"
21+
# CHECK-NEXT: echo test- 4 4 -test
22+
# CHECK-NOT: echo "fail"
23+
# CHECK-NEXT: echo test- 5 5 -test
24+
# CHECK-NOT: echo "fail"
25+
26+
# Escape line breaks for multi-line expressions
27+
#
28+
# RUN: %if feature \
29+
# RUN: %{ echo \
30+
# RUN: "test-5" \
31+
# RUN: %} %else %{ echo "fail" %}
32+
# CHECK-NEXT: echo "test-5"
33+
34+
# RUN: %if nofeature \
35+
# RUN: %{ echo "fail" %} \
36+
# RUN: %else \
37+
# RUN: %{ echo "test-6" %}
38+
# CHECK-NEXT: echo "test-6"
39+
40+
# RUN: echo "test%if feature %{%} %else %{%}-7"
41+
# CHECK-NEXT: echo "test-7"
42+
43+
44+
# Nested expressions are supported:
45+
#
46+
# RUN: echo %if feature %{ %if feature %{ %if nofeature %{"fail"%} %else %{"test-9"%} %} %}
47+
# CHECK-NEXT: echo "test-9"
48+
49+
# Spaces between %if and %else are ignored. If there is no %else -
50+
# space after %if %{...%} is not ignored.
51+
#
52+
# RUN: echo XX %if feature %{YY%} ZZ
53+
# RUN: echo AA %if feature %{BB%} %else %{CC%} DD
54+
# RUN: echo AA %if nofeature %{BB%} %else %{CC%} DD
55+
# CHECK-NEXT: echo XX YY ZZ
56+
# CHECK-NEXT: echo AA BB DD
57+
# CHECK-NEXT: echo AA CC DD
58+
59+
# '{' and '}' can be used without escaping
60+
#
61+
# RUN: %if feature %{echo {}%}
62+
# CHECK-NEXT: echo {}
63+
64+
# Spaces are not required
65+
#
66+
# RUN: echo %if feature%{"ok"%}%else%{"fail"%}
67+
# CHECK-NEXT: echo "ok"
68+
69+
# Substitutions with braces are handled correctly
70+
#
71+
# RUN: echo %{sub} %if feature%{test-%{sub}%}%else%{"fail"%}
72+
# CHECK-NEXT: echo ok test-ok
73+
74+
# CHECK-NEXT: --
75+
# CHECK-NEXT: Exit Code: 0

‎utils/lit/tests/shtest-if-else.py

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# RUN: %{lit} -v --show-all %{inputs}/shtest-if-else/test.txt \
2+
# RUN: | FileCheck %{inputs}/shtest-if-else/test.txt
3+
4+
# RUN: not %{lit} -v %{inputs}/shtest-if-else/test-neg1.txt 2>&1 \
5+
# RUN: | FileCheck %{inputs}/shtest-if-else/test-neg1.txt
6+
7+
# RUN: not %{lit} -v %{inputs}/shtest-if-else/test-neg2.txt 2>&1 \
8+
# RUN: | FileCheck %{inputs}/shtest-if-else/test-neg2.txt
9+
10+
# RUN: not %{lit} -v %{inputs}/shtest-if-else/test-neg3.txt 2>&1 \
11+
# RUN: | FileCheck %{inputs}/shtest-if-else/test-neg3.txt
12+
13+
# RUN: not %{lit} -v %{inputs}/shtest-if-else/test-neg4.txt 2>&1 \
14+
# RUN: | FileCheck %{inputs}/shtest-if-else/test-neg4.txt

0 commit comments

Comments
 (0)
Please sign in to comment.