xref: /llvm-project/llvm/utils/lit/tests/unit/TestRunner.py (revision b71edfaa4ec3c998aadb35255ce2f60bba2940b0)
1# RUN: %{python} %s
2#
3# END.
4
5
6import os.path
7import platform
8import unittest
9
10import lit.discovery
11import lit.LitConfig
12import lit.Test as Test
13from lit.TestRunner import (
14    ParserKind,
15    IntegratedTestKeywordParser,
16    parseIntegratedTestScript,
17)
18
19
20class TestIntegratedTestKeywordParser(unittest.TestCase):
21    inputTestCase = None
22
23    @staticmethod
24    def load_keyword_parser_lit_tests():
25        """
26        Create and load the LIT test suite and test objects used by
27        TestIntegratedTestKeywordParser
28        """
29        # Create the global config object.
30        lit_config = lit.LitConfig.LitConfig(
31            progname="lit",
32            path=[],
33            quiet=False,
34            useValgrind=False,
35            valgrindLeakCheck=False,
36            valgrindArgs=[],
37            noExecute=False,
38            debug=False,
39            isWindows=(platform.system() == "Windows"),
40            order="smart",
41            params={},
42        )
43        TestIntegratedTestKeywordParser.litConfig = lit_config
44        # Perform test discovery.
45        test_path = os.path.dirname(os.path.dirname(__file__))
46        inputs = [os.path.join(test_path, "Inputs/testrunner-custom-parsers/")]
47        assert os.path.isdir(inputs[0])
48        tests = lit.discovery.find_tests_for_inputs(lit_config, inputs, False)
49        assert len(tests) == 1 and "there should only be one test"
50        TestIntegratedTestKeywordParser.inputTestCase = tests[0]
51
52    @staticmethod
53    def make_parsers():
54        def custom_parse(line_number, line, output):
55            if output is None:
56                output = []
57            output += [part for part in line.split(" ") if part.strip()]
58            return output
59
60        return [
61            IntegratedTestKeywordParser("MY_TAG.", ParserKind.TAG),
62            IntegratedTestKeywordParser("MY_DNE_TAG.", ParserKind.TAG),
63            IntegratedTestKeywordParser("MY_LIST:", ParserKind.LIST),
64            IntegratedTestKeywordParser("MY_BOOL:", ParserKind.BOOLEAN_EXPR),
65            IntegratedTestKeywordParser("MY_INT:", ParserKind.INTEGER),
66            IntegratedTestKeywordParser("MY_RUN:", ParserKind.COMMAND),
67            IntegratedTestKeywordParser("MY_CUSTOM:", ParserKind.CUSTOM, custom_parse),
68            IntegratedTestKeywordParser("MY_DEFINE:", ParserKind.DEFINE),
69            IntegratedTestKeywordParser("MY_REDEFINE:", ParserKind.REDEFINE),
70        ]
71
72    @staticmethod
73    def get_parser(parser_list, keyword):
74        for p in parser_list:
75            if p.keyword == keyword:
76                return p
77        assert False and "parser not found"
78
79    @staticmethod
80    def parse_test(parser_list, allow_result=False):
81        script = parseIntegratedTestScript(
82            TestIntegratedTestKeywordParser.inputTestCase,
83            additional_parsers=parser_list,
84            require_script=False,
85        )
86        if isinstance(script, lit.Test.Result):
87            assert allow_result
88        else:
89            assert isinstance(script, list)
90            assert len(script) == 0
91        return script
92
93    def test_tags(self):
94        parsers = self.make_parsers()
95        self.parse_test(parsers)
96        tag_parser = self.get_parser(parsers, "MY_TAG.")
97        dne_tag_parser = self.get_parser(parsers, "MY_DNE_TAG.")
98        self.assertTrue(tag_parser.getValue())
99        self.assertFalse(dne_tag_parser.getValue())
100
101    def test_lists(self):
102        parsers = self.make_parsers()
103        self.parse_test(parsers)
104        list_parser = self.get_parser(parsers, "MY_LIST:")
105        self.assertEqual(list_parser.getValue(), ["one", "two", "three", "four"])
106
107    def test_commands(self):
108        parsers = self.make_parsers()
109        self.parse_test(parsers)
110        cmd_parser = self.get_parser(parsers, "MY_RUN:")
111        value = cmd_parser.getValue()
112        self.assertEqual(len(value), 2)  # there are only two run lines
113        self.assertEqual(value[0].command.strip(), "%dbg(MY_RUN: at line 4)  baz")
114        self.assertEqual(value[1].command.strip(), "%dbg(MY_RUN: at line 7)  foo  bar")
115
116    def test_boolean(self):
117        parsers = self.make_parsers()
118        self.parse_test(parsers)
119        bool_parser = self.get_parser(parsers, "MY_BOOL:")
120        value = bool_parser.getValue()
121        self.assertEqual(len(value), 2)  # there are only two run lines
122        self.assertEqual(value[0].strip(), "a && (b)")
123        self.assertEqual(value[1].strip(), "d")
124
125    def test_integer(self):
126        parsers = self.make_parsers()
127        self.parse_test(parsers)
128        int_parser = self.get_parser(parsers, "MY_INT:")
129        value = int_parser.getValue()
130        self.assertEqual(len(value), 2)  # there are only two MY_INT: lines
131        self.assertEqual(type(value[0]), int)
132        self.assertEqual(value[0], 4)
133        self.assertEqual(type(value[1]), int)
134        self.assertEqual(value[1], 6)
135
136    def test_bad_parser_type(self):
137        parsers = self.make_parsers() + ["BAD_PARSER_TYPE"]
138        script = self.parse_test(parsers, allow_result=True)
139        self.assertTrue(isinstance(script, lit.Test.Result))
140        self.assertEqual(script.code, lit.Test.UNRESOLVED)
141        self.assertEqual(
142            "Additional parser must be an instance of " "IntegratedTestKeywordParser",
143            script.output,
144        )
145
146    def test_duplicate_keyword(self):
147        parsers = self.make_parsers() + [
148            IntegratedTestKeywordParser("KEY:", ParserKind.BOOLEAN_EXPR),
149            IntegratedTestKeywordParser("KEY:", ParserKind.BOOLEAN_EXPR),
150        ]
151        script = self.parse_test(parsers, allow_result=True)
152        self.assertTrue(isinstance(script, lit.Test.Result))
153        self.assertEqual(script.code, lit.Test.UNRESOLVED)
154        self.assertEqual("Parser for keyword 'KEY:' already exists", script.output)
155
156    def test_boolean_unterminated(self):
157        parsers = self.make_parsers() + [
158            IntegratedTestKeywordParser(
159                "MY_BOOL_UNTERMINATED:", ParserKind.BOOLEAN_EXPR
160            )
161        ]
162        script = self.parse_test(parsers, allow_result=True)
163        self.assertTrue(isinstance(script, lit.Test.Result))
164        self.assertEqual(script.code, lit.Test.UNRESOLVED)
165        self.assertEqual(
166            "Test has unterminated 'MY_BOOL_UNTERMINATED:' lines " "(with '\\')",
167            script.output,
168        )
169
170    def test_custom(self):
171        parsers = self.make_parsers()
172        self.parse_test(parsers)
173        custom_parser = self.get_parser(parsers, "MY_CUSTOM:")
174        value = custom_parser.getValue()
175        self.assertEqual(value, ["a", "b", "c"])
176
177    def test_defines(self):
178        parsers = self.make_parsers()
179        self.parse_test(parsers)
180        cmd_parser = self.get_parser(parsers, "MY_DEFINE:")
181        value = cmd_parser.getValue()
182        self.assertEqual(len(value), 1)  # there's only one MY_DEFINE directive
183        self.assertEqual(value[0].new_subst, True)
184        self.assertEqual(value[0].name, "%{name}")
185        self.assertEqual(value[0].value, "value one")
186
187    def test_redefines(self):
188        parsers = self.make_parsers()
189        self.parse_test(parsers)
190        cmd_parser = self.get_parser(parsers, "MY_REDEFINE:")
191        value = cmd_parser.getValue()
192        self.assertEqual(len(value), 1)  # there's only one MY_REDEFINE directive
193        self.assertEqual(value[0].new_subst, False)
194        self.assertEqual(value[0].name, "%{name}")
195        self.assertEqual(value[0].value, "value two")
196
197    def test_bad_keywords(self):
198        def custom_parse(line_number, line, output):
199            return output
200
201        try:
202            IntegratedTestKeywordParser("TAG_NO_SUFFIX", ParserKind.TAG),
203            self.fail("TAG_NO_SUFFIX failed to raise an exception")
204        except ValueError as e:
205            pass
206        except BaseException as e:
207            self.fail("TAG_NO_SUFFIX raised the wrong exception: %r" % e)
208
209        try:
210            IntegratedTestKeywordParser("TAG_WITH_COLON:", ParserKind.TAG),
211            self.fail("TAG_WITH_COLON: failed to raise an exception")
212        except ValueError as e:
213            pass
214        except BaseException as e:
215            self.fail("TAG_WITH_COLON: raised the wrong exception: %r" % e)
216
217        try:
218            IntegratedTestKeywordParser("LIST_WITH_DOT.", ParserKind.LIST),
219            self.fail("LIST_WITH_DOT. failed to raise an exception")
220        except ValueError as e:
221            pass
222        except BaseException as e:
223            self.fail("LIST_WITH_DOT. raised the wrong exception: %r" % e)
224
225        try:
226            IntegratedTestKeywordParser(
227                "CUSTOM_NO_SUFFIX", ParserKind.CUSTOM, custom_parse
228            ),
229            self.fail("CUSTOM_NO_SUFFIX failed to raise an exception")
230        except ValueError as e:
231            pass
232        except BaseException as e:
233            self.fail("CUSTOM_NO_SUFFIX raised the wrong exception: %r" % e)
234
235        # Both '.' and ':' are allowed for CUSTOM keywords.
236        try:
237            IntegratedTestKeywordParser(
238                "CUSTOM_WITH_DOT.", ParserKind.CUSTOM, custom_parse
239            ),
240        except BaseException as e:
241            self.fail("CUSTOM_WITH_DOT. raised an exception: %r" % e)
242        try:
243            IntegratedTestKeywordParser(
244                "CUSTOM_WITH_COLON:", ParserKind.CUSTOM, custom_parse
245            ),
246        except BaseException as e:
247            self.fail("CUSTOM_WITH_COLON: raised an exception: %r" % e)
248
249        try:
250            IntegratedTestKeywordParser("CUSTOM_NO_PARSER:", ParserKind.CUSTOM),
251            self.fail("CUSTOM_NO_PARSER: failed to raise an exception")
252        except ValueError as e:
253            pass
254        except BaseException as e:
255            self.fail("CUSTOM_NO_PARSER: raised the wrong exception: %r" % e)
256
257
258class TestApplySubtitutions(unittest.TestCase):
259    def test_simple(self):
260        script = ["echo %bar"]
261        substitutions = [("%bar", "hello")]
262        result = lit.TestRunner.applySubstitutions(script, substitutions)
263        self.assertEqual(result, ["echo hello"])
264
265    def test_multiple_substitutions(self):
266        script = ["echo %bar %baz"]
267        substitutions = [
268            ("%bar", "hello"),
269            ("%baz", "world"),
270            ("%useless", "shouldnt expand"),
271        ]
272        result = lit.TestRunner.applySubstitutions(script, substitutions)
273        self.assertEqual(result, ["echo hello world"])
274
275    def test_multiple_script_lines(self):
276        script = ["%cxx %compile_flags -c -o %t.o", "%cxx %link_flags %t.o -o %t.exe"]
277        substitutions = [
278            ("%cxx", "clang++"),
279            ("%compile_flags", "-std=c++11 -O3"),
280            ("%link_flags", "-lc++"),
281        ]
282        result = lit.TestRunner.applySubstitutions(script, substitutions)
283        self.assertEqual(
284            result,
285            ["clang++ -std=c++11 -O3 -c -o %t.o", "clang++ -lc++ %t.o -o %t.exe"],
286        )
287
288    def test_recursive_substitution_real(self):
289        script = ["%build %s"]
290        substitutions = [
291            ("%cxx", "clang++"),
292            ("%compile_flags", "-std=c++11 -O3"),
293            ("%link_flags", "-lc++"),
294            ("%build", "%cxx %compile_flags %link_flags %s -o %t.exe"),
295        ]
296        result = lit.TestRunner.applySubstitutions(
297            script, substitutions, recursion_limit=3
298        )
299        self.assertEqual(result, ["clang++ -std=c++11 -O3 -lc++ %s -o %t.exe %s"])
300
301    def test_recursive_substitution_limit(self):
302        script = ["%rec5"]
303        # Make sure the substitutions are not in an order where the global
304        # substitution would appear to be recursive just because they are
305        # processed in the right order.
306        substitutions = [
307            ("%rec1", "STOP"),
308            ("%rec2", "%rec1"),
309            ("%rec3", "%rec2"),
310            ("%rec4", "%rec3"),
311            ("%rec5", "%rec4"),
312        ]
313        for limit in [5, 6, 7]:
314            result = lit.TestRunner.applySubstitutions(
315                script, substitutions, recursion_limit=limit
316            )
317            self.assertEqual(result, ["STOP"])
318
319    def test_recursive_substitution_limit_exceeded(self):
320        script = ["%rec5"]
321        substitutions = [
322            ("%rec1", "STOP"),
323            ("%rec2", "%rec1"),
324            ("%rec3", "%rec2"),
325            ("%rec4", "%rec3"),
326            ("%rec5", "%rec4"),
327        ]
328        for limit in [0, 1, 2, 3, 4]:
329            try:
330                lit.TestRunner.applySubstitutions(
331                    script, substitutions, recursion_limit=limit
332                )
333                self.fail("applySubstitutions should have raised an exception")
334            except ValueError:
335                pass
336
337    def test_recursive_substitution_invalid_value(self):
338        script = ["%rec5"]
339        substitutions = [
340            ("%rec1", "STOP"),
341            ("%rec2", "%rec1"),
342            ("%rec3", "%rec2"),
343            ("%rec4", "%rec3"),
344            ("%rec5", "%rec4"),
345        ]
346        for limit in [-1, -2, -3, "foo"]:
347            try:
348                lit.TestRunner.applySubstitutions(
349                    script, substitutions, recursion_limit=limit
350                )
351                self.fail("applySubstitutions should have raised an exception")
352            except AssertionError:
353                pass
354
355
356if __name__ == "__main__":
357    TestIntegratedTestKeywordParser.load_keyword_parser_lit_tests()
358    unittest.main(verbosity=2)
359