• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#===----------------------------------------------------------------------===##
2#
3# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4# See https://llvm.org/LICENSE.txt for license information.
5# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6#
7#===----------------------------------------------------------------------===##
8
9# Note: We prepend arguments with 'x' to avoid thinking there are too few
10#       arguments in case an argument is an empty string.
11# RUN: %{python} %s x%S x%T x%{substitutions}
12
13import base64
14import copy
15import os
16import pickle
17import platform
18import subprocess
19import sys
20import unittest
21from os.path import dirname
22
23# Allow importing 'lit' and the 'libcxx' module. Make sure we put the lit
24# path first so we don't find any system-installed version.
25monorepoRoot = dirname(dirname(dirname(dirname(dirname(dirname(__file__))))))
26sys.path = [os.path.join(monorepoRoot, 'libcxx', 'utils'),
27            os.path.join(monorepoRoot, 'llvm', 'utils', 'lit')] + sys.path
28import libcxx.test.dsl as dsl
29import lit.LitConfig
30import lit.util
31
32# Steal some parameters from the config running this test so that we can
33# bootstrap our own TestingConfig.
34args = list(map(lambda s: s[1:], sys.argv[1:8])) # Remove the leading 'x'
35SOURCE_ROOT, EXEC_PATH, SUBSTITUTIONS = args
36sys.argv[1:8] = []
37
38# Decode the substitutions.
39SUBSTITUTIONS = pickle.loads(base64.b64decode(SUBSTITUTIONS))
40for s, sub in SUBSTITUTIONS:
41    print("Substitution '{}' is '{}'".format(s, sub))
42
43class SetupConfigs(unittest.TestCase):
44    """
45    Base class for the tests below -- it creates a fake TestingConfig.
46    """
47    def setUp(self):
48        """
49        Create a fake TestingConfig that can be populated however we wish for
50        the purpose of running unit tests below. We pre-populate it with the
51        minimum required substitutions.
52        """
53        self.litConfig = lit.LitConfig.LitConfig(
54            progname='lit',
55            path=[],
56            quiet=False,
57            useValgrind=False,
58            valgrindLeakCheck=False,
59            valgrindArgs=[],
60            noExecute=False,
61            debug=False,
62            isWindows=platform.system() == 'Windows',
63            order='smart',
64            params={})
65
66        self.config = lit.TestingConfig.TestingConfig.fromdefaults(self.litConfig)
67        self.config.environment = dict(os.environ)
68        self.config.test_source_root = SOURCE_ROOT
69        self.config.test_exec_root = EXEC_PATH
70        self.config.recursiveExpansionLimit = 10
71        self.config.substitutions = copy.deepcopy(SUBSTITUTIONS)
72
73    def getSubstitution(self, substitution):
74        """
75        Return a given substitution from the TestingConfig. It is an error if
76        there is no such substitution.
77        """
78        found = [x for (s, x) in self.config.substitutions if s == substitution]
79        assert len(found) == 1
80        return found[0]
81
82
83def findIndex(list, pred):
84    """Finds the index of the first element satisfying 'pred' in a list, or
85       'len(list)' if there is no such element."""
86    index = 0
87    for x in list:
88        if pred(x):
89            break
90        else:
91            index += 1
92    return index
93
94
95class TestHasCompileFlag(SetupConfigs):
96    """
97    Tests for libcxx.test.dsl.hasCompileFlag
98    """
99    def test_no_flag_should_work(self):
100        self.assertTrue(dsl.hasCompileFlag(self.config, ''))
101
102    def test_flag_exists(self):
103        self.assertTrue(dsl.hasCompileFlag(self.config, '-O1'))
104
105    def test_nonexistent_flag(self):
106        self.assertFalse(dsl.hasCompileFlag(self.config, '-this_is_not_a_flag_any_compiler_has'))
107
108    def test_multiple_flags(self):
109        self.assertTrue(dsl.hasCompileFlag(self.config, '-O1 -Dhello'))
110
111
112class TestSourceBuilds(SetupConfigs):
113    """
114    Tests for libcxx.test.dsl.sourceBuilds
115    """
116    def test_valid_program_builds(self):
117        source = """int main(int, char**) { return 0; }"""
118        self.assertTrue(dsl.sourceBuilds(self.config, source))
119
120    def test_compilation_error_fails(self):
121        source = """int main(int, char**) { this does not compile }"""
122        self.assertFalse(dsl.sourceBuilds(self.config, source))
123
124    def test_link_error_fails(self):
125        source = """extern void this_isnt_defined_anywhere();
126                    int main(int, char**) { this_isnt_defined_anywhere(); return 0; }"""
127        self.assertFalse(dsl.sourceBuilds(self.config, source))
128
129
130class TestProgramOutput(SetupConfigs):
131    """
132    Tests for libcxx.test.dsl.programOutput
133    """
134    def test_valid_program_returns_output(self):
135        source = """
136        #include <cstdio>
137        int main(int, char**) { std::printf("FOOBAR"); return 0; }
138        """
139        self.assertEqual(dsl.programOutput(self.config, source), "FOOBAR")
140
141    def test_valid_program_returns_output_newline_handling(self):
142        source = """
143        #include <cstdio>
144        int main(int, char**) { std::printf("FOOBAR\\n"); return 0; }
145        """
146        self.assertEqual(dsl.programOutput(self.config, source), "FOOBAR\n")
147
148    def test_valid_program_returns_no_output(self):
149        source = """
150        int main(int, char**) { return 0; }
151        """
152        self.assertEqual(dsl.programOutput(self.config, source), "")
153
154    def test_program_that_fails_to_run_raises_runtime_error(self):
155        # The program compiles, but exits with an error
156        source = """
157        int main(int, char**) { return 1; }
158        """
159        self.assertRaises(dsl.ConfigurationRuntimeError, lambda: dsl.programOutput(self.config, source))
160
161    def test_program_that_fails_to_compile_raises_compilation_error(self):
162        # The program doesn't compile
163        source = """
164        int main(int, char**) { this doesnt compile }
165        """
166        self.assertRaises(dsl.ConfigurationCompilationError, lambda: dsl.programOutput(self.config, source))
167
168    def test_pass_arguments_to_program(self):
169        source = """
170        #include <cassert>
171        #include <string>
172        int main(int argc, char** argv) {
173            assert(argc == 3);
174            assert(argv[1] == std::string("first-argument"));
175            assert(argv[2] == std::string("second-argument"));
176            return 0;
177        }
178        """
179        args = ["first-argument", "second-argument"]
180        self.assertEqual(dsl.programOutput(self.config, source, args=args), "")
181
182    def test_caching_is_not_too_aggressive(self):
183        # Run a program, then change the substitutions and run it again.
184        # Make sure the program is run the second time and the right result
185        # is given, to ensure we're not incorrectly caching the result of the
186        # first program run.
187        source = """
188        #include <cstdio>
189        int main(int, char**) {
190            std::printf("MACRO=%u\\n", MACRO);
191            return 0;
192        }
193        """
194        compileFlagsIndex = findIndex(self.config.substitutions, lambda x: x[0] == '%{compile_flags}')
195        compileFlags = self.config.substitutions[compileFlagsIndex][1]
196
197        self.config.substitutions[compileFlagsIndex] = ('%{compile_flags}',  compileFlags + ' -DMACRO=1')
198        output1 = dsl.programOutput(self.config, source)
199        self.assertEqual(output1, "MACRO=1\n")
200
201        self.config.substitutions[compileFlagsIndex] = ('%{compile_flags}',  compileFlags + ' -DMACRO=2')
202        output2 = dsl.programOutput(self.config, source)
203        self.assertEqual(output2, "MACRO=2\n")
204
205    def test_program_stderr_is_not_conflated_with_stdout(self):
206        # Run a program that produces stdout output and stderr output too, making
207        # sure the stderr output does not pollute the stdout output.
208        source = """
209        #include <cstdio>
210        int main(int, char**) {
211            std::fprintf(stdout, "STDOUT-OUTPUT");
212            std::fprintf(stderr, "STDERR-OUTPUT");
213            return 0;
214        }
215        """
216        self.assertEqual(dsl.programOutput(self.config, source), "STDOUT-OUTPUT")
217
218
219class TestProgramSucceeds(SetupConfigs):
220    """
221    Tests for libcxx.test.dsl.programSucceeds
222    """
223    def test_success(self):
224        source = """
225        int main(int, char**) { return 0; }
226        """
227        self.assertTrue(dsl.programSucceeds(self.config, source))
228
229    def test_failure(self):
230        source = """
231        int main(int, char**) { return 1; }
232        """
233        self.assertFalse(dsl.programSucceeds(self.config, source))
234
235    def test_compile_failure(self):
236        source = """
237        this does not compile
238        """
239        self.assertRaises(dsl.ConfigurationCompilationError, lambda: dsl.programSucceeds(self.config, source))
240
241class TestHasLocale(SetupConfigs):
242    """
243    Tests for libcxx.test.dsl.hasLocale
244    """
245    def test_doesnt_explode(self):
246        # It's really hard to test that a system has a given locale, so at least
247        # make sure we don't explode when we try to check it.
248        try:
249            dsl.hasAnyLocale(self.config, ['en_US.UTF-8'])
250        except subprocess.CalledProcessError:
251            self.fail("checking for hasLocale should not explode")
252
253    def test_nonexistent_locale(self):
254        self.assertFalse(dsl.hasAnyLocale(self.config, ['forsurethisisnotanexistinglocale']))
255
256    def test_localization_program_doesnt_compile(self):
257        compilerIndex = findIndex(self.config.substitutions, lambda x: x[0] == '%{cxx}')
258        self.config.substitutions[compilerIndex] = ('%{cxx}', 'this-is-certainly-not-a-valid-compiler!!')
259        self.assertRaises(dsl.ConfigurationCompilationError, lambda: dsl.hasAnyLocale(self.config, ['en_US.UTF-8']))
260
261
262class TestCompilerMacros(SetupConfigs):
263    """
264    Tests for libcxx.test.dsl.compilerMacros
265    """
266    def test_basic(self):
267        macros = dsl.compilerMacros(self.config)
268        self.assertIsInstance(macros, dict)
269        self.assertGreater(len(macros), 0)
270        for (k, v) in macros.items():
271            self.assertIsInstance(k, str)
272            self.assertIsInstance(v, str)
273
274    def test_no_flag(self):
275        macros = dsl.compilerMacros(self.config)
276        self.assertIn('__cplusplus', macros.keys())
277
278    def test_empty_flag(self):
279        macros = dsl.compilerMacros(self.config, '')
280        self.assertIn('__cplusplus', macros.keys())
281
282    def test_with_flag(self):
283        macros = dsl.compilerMacros(self.config, '-DFOO=3')
284        self.assertIn('__cplusplus', macros.keys())
285        self.assertEqual(macros['FOO'], '3')
286
287    def test_with_flags(self):
288        macros = dsl.compilerMacros(self.config, '-DFOO=3 -DBAR=hello')
289        self.assertIn('__cplusplus', macros.keys())
290        self.assertEqual(macros['FOO'], '3')
291        self.assertEqual(macros['BAR'], 'hello')
292
293
294class TestFeatureTestMacros(SetupConfigs):
295    """
296    Tests for libcxx.test.dsl.featureTestMacros
297    """
298    def test_basic(self):
299        macros = dsl.featureTestMacros(self.config)
300        self.assertIsInstance(macros, dict)
301        self.assertGreater(len(macros), 0)
302        for (k, v) in macros.items():
303            self.assertIsInstance(k, str)
304            self.assertIsInstance(v, int)
305
306
307class TestFeature(SetupConfigs):
308    """
309    Tests for libcxx.test.dsl.Feature
310    """
311    def test_trivial(self):
312        feature = dsl.Feature(name='name')
313        origSubstitutions = copy.deepcopy(self.config.substitutions)
314        actions = feature.getActions(self.config)
315        self.assertTrue(len(actions) == 1)
316        for a in actions:
317            a.applyTo(self.config)
318        self.assertEqual(origSubstitutions, self.config.substitutions)
319        self.assertIn('name', self.config.available_features)
320
321    def test_name_can_be_a_callable(self):
322        feature = dsl.Feature(name=lambda cfg: 'name')
323        for a in feature.getActions(self.config):
324            a.applyTo(self.config)
325        self.assertIn('name', self.config.available_features)
326
327    def test_name_is_not_a_string_1(self):
328        feature = dsl.Feature(name=None)
329        self.assertRaises(ValueError, lambda: feature.getActions(self.config))
330        self.assertRaises(ValueError, lambda: feature.pretty(self.config))
331
332    def test_name_is_not_a_string_2(self):
333        feature = dsl.Feature(name=lambda cfg: None)
334        self.assertRaises(ValueError, lambda: feature.getActions(self.config))
335        self.assertRaises(ValueError, lambda: feature.pretty(self.config))
336
337    def test_adding_action(self):
338        feature = dsl.Feature(name='name', actions=[dsl.AddCompileFlag('-std=c++03')])
339        origLinkFlags = copy.deepcopy(self.getSubstitution('%{link_flags}'))
340        for a in feature.getActions(self.config):
341            a.applyTo(self.config)
342        self.assertIn('name', self.config.available_features)
343        self.assertIn('-std=c++03', self.getSubstitution('%{compile_flags}'))
344        self.assertEqual(origLinkFlags, self.getSubstitution('%{link_flags}'))
345
346    def test_actions_can_be_a_callable(self):
347        feature = dsl.Feature(name='name',
348                              actions=lambda cfg: (
349                                self.assertIs(self.config, cfg),
350                                [dsl.AddCompileFlag('-std=c++03')]
351                              )[1])
352        for a in feature.getActions(self.config):
353            a.applyTo(self.config)
354        self.assertIn('-std=c++03', self.getSubstitution('%{compile_flags}'))
355
356    def test_unsupported_feature(self):
357        feature = dsl.Feature(name='name', when=lambda _: False)
358        self.assertEqual(feature.getActions(self.config), [])
359
360    def test_is_supported_gets_passed_the_config(self):
361        feature = dsl.Feature(name='name', when=lambda cfg: (self.assertIs(self.config, cfg), True)[1])
362        self.assertEqual(len(feature.getActions(self.config)), 1)
363
364
365def _throw():
366    raise ValueError()
367
368class TestParameter(SetupConfigs):
369    """
370    Tests for libcxx.test.dsl.Parameter
371    """
372    def test_empty_name_should_blow_up(self):
373        self.assertRaises(ValueError, lambda: dsl.Parameter(name='', choices=['c++03'], type=str, help='', actions=lambda _: []))
374
375    def test_empty_choices_should_blow_up(self):
376        self.assertRaises(ValueError, lambda: dsl.Parameter(name='std', choices=[], type=str, help='', actions=lambda _: []))
377
378    def test_no_choices_is_ok(self):
379        param = dsl.Parameter(name='triple', type=str, help='', actions=lambda _: [])
380        self.assertEqual(param.name, 'triple')
381
382    def test_name_is_set_correctly(self):
383        param = dsl.Parameter(name='std', choices=['c++03'], type=str, help='', actions=lambda _: [])
384        self.assertEqual(param.name, 'std')
385
386    def test_no_value_provided_and_no_default_value(self):
387        param = dsl.Parameter(name='std', choices=['c++03'], type=str, help='', actions=lambda _: [])
388        self.assertRaises(ValueError, lambda: param.getActions(self.config, self.litConfig.params))
389
390    def test_no_value_provided_and_default_value(self):
391        param = dsl.Parameter(name='std', choices=['c++03'], type=str, help='', default='c++03',
392                              actions=lambda std: [dsl.AddFeature(std)])
393        for a in param.getActions(self.config, self.litConfig.params):
394            a.applyTo(self.config)
395        self.assertIn('c++03', self.config.available_features)
396
397    def test_value_provided_on_command_line_and_no_default_value(self):
398        self.litConfig.params['std'] = 'c++03'
399        param = dsl.Parameter(name='std', choices=['c++03'], type=str, help='',
400                              actions=lambda std: [dsl.AddFeature(std)])
401        for a in param.getActions(self.config, self.litConfig.params):
402            a.applyTo(self.config)
403        self.assertIn('c++03', self.config.available_features)
404
405    def test_value_provided_on_command_line_and_default_value(self):
406        """The value provided on the command line should override the default value"""
407        self.litConfig.params['std'] = 'c++11'
408        param = dsl.Parameter(name='std', choices=['c++03', 'c++11'], type=str, default='c++03', help='',
409                              actions=lambda std: [dsl.AddFeature(std)])
410        for a in param.getActions(self.config, self.litConfig.params):
411            a.applyTo(self.config)
412        self.assertIn('c++11', self.config.available_features)
413        self.assertNotIn('c++03', self.config.available_features)
414
415    def test_value_provided_in_config_and_default_value(self):
416        """The value provided in the config should override the default value"""
417        self.config.std ='c++11'
418        param = dsl.Parameter(name='std', choices=['c++03', 'c++11'], type=str, default='c++03', help='',
419                              actions=lambda std: [dsl.AddFeature(std)])
420        for a in param.getActions(self.config, self.litConfig.params):
421            a.applyTo(self.config)
422        self.assertIn('c++11', self.config.available_features)
423        self.assertNotIn('c++03', self.config.available_features)
424
425    def test_value_provided_in_config_and_on_command_line(self):
426        """The value on the command line should override the one in the config"""
427        self.config.std = 'c++11'
428        self.litConfig.params['std'] = 'c++03'
429        param = dsl.Parameter(name='std', choices=['c++03', 'c++11'], type=str, help='',
430                              actions=lambda std: [dsl.AddFeature(std)])
431        for a in param.getActions(self.config, self.litConfig.params):
432            a.applyTo(self.config)
433        self.assertIn('c++03', self.config.available_features)
434        self.assertNotIn('c++11', self.config.available_features)
435
436    def test_no_actions(self):
437        self.litConfig.params['std'] = 'c++03'
438        param = dsl.Parameter(name='std', choices=['c++03'], type=str, help='',
439                              actions=lambda _: [])
440        actions = param.getActions(self.config, self.litConfig.params)
441        self.assertEqual(actions, [])
442
443    def test_boolean_value_parsed_from_trueish_string_parameter(self):
444        self.litConfig.params['enable_exceptions'] = "True"
445        param = dsl.Parameter(name='enable_exceptions', choices=[True, False], type=bool, help='',
446                              actions=lambda exceptions: [] if exceptions else _throw())
447        self.assertEqual(param.getActions(self.config, self.litConfig.params), [])
448
449    def test_boolean_value_from_true_boolean_parameter(self):
450        self.litConfig.params['enable_exceptions'] = True
451        param = dsl.Parameter(name='enable_exceptions', choices=[True, False], type=bool, help='',
452                              actions=lambda exceptions: [] if exceptions else _throw())
453        self.assertEqual(param.getActions(self.config, self.litConfig.params), [])
454
455    def test_boolean_value_parsed_from_falseish_string_parameter(self):
456        self.litConfig.params['enable_exceptions'] = "False"
457        param = dsl.Parameter(name='enable_exceptions', choices=[True, False], type=bool, help='',
458                              actions=lambda exceptions: [] if exceptions else [dsl.AddFeature("-fno-exceptions")])
459        for a in param.getActions(self.config, self.litConfig.params):
460            a.applyTo(self.config)
461        self.assertIn('-fno-exceptions', self.config.available_features)
462
463    def test_boolean_value_from_false_boolean_parameter(self):
464        self.litConfig.params['enable_exceptions'] = False
465        param = dsl.Parameter(name='enable_exceptions', choices=[True, False], type=bool, help='',
466                              actions=lambda exceptions: [] if exceptions else [dsl.AddFeature("-fno-exceptions")])
467        for a in param.getActions(self.config, self.litConfig.params):
468            a.applyTo(self.config)
469        self.assertIn('-fno-exceptions', self.config.available_features)
470
471    def test_list_parsed_from_comma_delimited_string_empty(self):
472        self.litConfig.params['additional_features'] = ""
473        param = dsl.Parameter(name='additional_features', type=list, help='', actions=lambda f: f)
474        self.assertEqual(param.getActions(self.config, self.litConfig.params), [])
475
476    def test_list_parsed_from_comma_delimited_string_1(self):
477        self.litConfig.params['additional_features'] = "feature1"
478        param = dsl.Parameter(name='additional_features', type=list, help='', actions=lambda f: f)
479        self.assertEqual(param.getActions(self.config, self.litConfig.params), ['feature1'])
480
481    def test_list_parsed_from_comma_delimited_string_2(self):
482        self.litConfig.params['additional_features'] = "feature1,feature2"
483        param = dsl.Parameter(name='additional_features', type=list, help='', actions=lambda f: f)
484        self.assertEqual(param.getActions(self.config, self.litConfig.params), ['feature1', 'feature2'])
485
486    def test_list_parsed_from_comma_delimited_string_3(self):
487        self.litConfig.params['additional_features'] = "feature1,feature2, feature3"
488        param = dsl.Parameter(name='additional_features', type=list, help='', actions=lambda f: f)
489        self.assertEqual(param.getActions(self.config, self.litConfig.params), ['feature1', 'feature2', 'feature3'])
490
491
492if __name__ == '__main__':
493    unittest.main(verbosity=2)
494