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