| #===----------------------------------------------------------------------===## |
| # |
| # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| # See https://llvm.org/LICENSE.txt for license information. |
| # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| # |
| #===----------------------------------------------------------------------===## |
| |
| # Note: We prepend arguments with 'x' to avoid thinking there are too few |
| # arguments in case an argument is an empty string. |
| # RUN: %{python} %s x%S \ |
| # RUN: x%T \ |
| # RUN: x%{escaped_exec} \ |
| # RUN: x%{escaped_cxx} \ |
| # RUN: x%{escaped_flags} \ |
| # RUN: x%{escaped_compile_flags} \ |
| # RUN: x%{escaped_link_flags} |
| # END. |
| |
| import base64 |
| import copy |
| import os |
| import platform |
| import subprocess |
| import sys |
| import unittest |
| from os.path import dirname |
| |
| # Allow importing 'lit' and the 'libcxx' module. Make sure we put the lit |
| # path first so we don't find any system-installed version. |
| monorepoRoot = dirname(dirname(dirname(dirname(dirname(dirname(__file__)))))) |
| sys.path = [os.path.join(monorepoRoot, 'libcxx', 'utils'), |
| os.path.join(monorepoRoot, 'llvm', 'utils', 'lit')] + sys.path |
| import libcxx.test.dsl as dsl |
| import lit.LitConfig |
| import lit.util |
| |
| # Steal some parameters from the config running this test so that we can |
| # bootstrap our own TestingConfig. |
| args = list(map(lambda s: s[1:], sys.argv[1:8])) # Remove the leading 'x' |
| SOURCE_ROOT, EXEC_PATH, EXEC, CXX, FLAGS, COMPILE_FLAGS, LINK_FLAGS = args |
| sys.argv[1:8] = [] |
| |
| class SetupConfigs(unittest.TestCase): |
| """ |
| Base class for the tests below -- it creates a fake TestingConfig. |
| """ |
| def setUp(self): |
| """ |
| Create a fake TestingConfig that can be populated however we wish for |
| the purpose of running unit tests below. We pre-populate it with the |
| minimum required substitutions. |
| """ |
| self.litConfig = lit.LitConfig.LitConfig( |
| progname='lit', |
| path=[], |
| quiet=False, |
| useValgrind=False, |
| valgrindLeakCheck=False, |
| valgrindArgs=[], |
| noExecute=False, |
| debug=False, |
| isWindows=platform.system() == 'Windows', |
| params={}) |
| |
| self.config = lit.TestingConfig.TestingConfig.fromdefaults(self.litConfig) |
| self.config.test_source_root = SOURCE_ROOT |
| self.config.test_exec_root = EXEC_PATH |
| self.config.recursiveExpansionLimit = 10 |
| base64Decode = lambda s: lit.util.to_string(base64.b64decode(s)) |
| self.config.substitutions = [ |
| ('%{cxx}', base64Decode(CXX)), |
| ('%{flags}', base64Decode(FLAGS)), |
| ('%{compile_flags}', base64Decode(COMPILE_FLAGS)), |
| ('%{link_flags}', base64Decode(LINK_FLAGS)), |
| ('%{exec}', base64Decode(EXEC)) |
| ] |
| |
| def getSubstitution(self, substitution): |
| """ |
| Return a given substitution from the TestingConfig. It is an error if |
| there is no such substitution. |
| """ |
| found = [x for (s, x) in self.config.substitutions if s == substitution] |
| assert len(found) == 1 |
| return found[0] |
| |
| |
| def findIndex(list, pred): |
| """Finds the index of the first element satisfying 'pred' in a list, or |
| 'len(list)' if there is no such element.""" |
| index = 0 |
| for x in list: |
| if pred(x): |
| break |
| else: |
| index += 1 |
| return index |
| |
| |
| class TestHasCompileFlag(SetupConfigs): |
| """ |
| Tests for libcxx.test.dsl.hasCompileFlag |
| """ |
| def test_no_flag_should_work(self): |
| self.assertTrue(dsl.hasCompileFlag(self.config, '')) |
| |
| def test_flag_exists(self): |
| self.assertTrue(dsl.hasCompileFlag(self.config, '-O1')) |
| |
| def test_nonexistent_flag(self): |
| self.assertFalse(dsl.hasCompileFlag(self.config, '-this_is_not_a_flag_any_compiler_has')) |
| |
| def test_multiple_flags(self): |
| self.assertTrue(dsl.hasCompileFlag(self.config, '-O1 -Dhello')) |
| |
| |
| class TestSourceBuilds(SetupConfigs): |
| """ |
| Tests for libcxx.test.dsl.sourceBuilds |
| """ |
| def test_valid_program_builds(self): |
| source = """int main(int, char**) { return 0; }""" |
| self.assertTrue(dsl.sourceBuilds(self.config, source)) |
| |
| def test_compilation_error_fails(self): |
| source = """int main(int, char**) { this does not compile }""" |
| self.assertFalse(dsl.sourceBuilds(self.config, source)) |
| |
| def test_link_error_fails(self): |
| source = """extern void this_isnt_defined_anywhere(); |
| int main(int, char**) { this_isnt_defined_anywhere(); return 0; }""" |
| self.assertFalse(dsl.sourceBuilds(self.config, source)) |
| |
| |
| class TestProgramOutput(SetupConfigs): |
| """ |
| Tests for libcxx.test.dsl.programOutput |
| """ |
| def test_valid_program_returns_output(self): |
| source = """ |
| #include <cstdio> |
| int main(int, char**) { std::printf("FOOBAR"); return 0; } |
| """ |
| self.assertEqual(dsl.programOutput(self.config, source), "FOOBAR") |
| |
| def test_valid_program_returns_output_newline_handling(self): |
| source = """ |
| #include <cstdio> |
| int main(int, char**) { std::printf("FOOBAR\\n"); return 0; } |
| """ |
| self.assertEqual(dsl.programOutput(self.config, source), "FOOBAR\n") |
| |
| def test_valid_program_returns_no_output(self): |
| source = """ |
| int main(int, char**) { return 0; } |
| """ |
| self.assertEqual(dsl.programOutput(self.config, source), "") |
| |
| def test_invalid_program_returns_None_1(self): |
| # The program compiles, but exits with an error |
| source = """ |
| int main(int, char**) { return 1; } |
| """ |
| self.assertEqual(dsl.programOutput(self.config, source), None) |
| |
| def test_invalid_program_returns_None_2(self): |
| # The program doesn't compile |
| source = """ |
| int main(int, char**) { this doesnt compile } |
| """ |
| self.assertEqual(dsl.programOutput(self.config, source), None) |
| |
| def test_pass_arguments_to_program(self): |
| source = """ |
| #include <cassert> |
| #include <string> |
| int main(int argc, char** argv) { |
| assert(argc == 3); |
| assert(argv[1] == std::string("first-argument")); |
| assert(argv[2] == std::string("second-argument")); |
| return 0; |
| } |
| """ |
| args = ["first-argument", "second-argument"] |
| self.assertEqual(dsl.programOutput(self.config, source, args=args), "") |
| |
| def test_caching_is_not_too_aggressive(self): |
| # Run a program, then change the substitutions and run it again. |
| # Make sure the program is run the second time and the right result |
| # is given, to ensure we're not incorrectly caching the result of the |
| # first program run. |
| source = """ |
| #include <cstdio> |
| int main(int, char**) { |
| std::printf("MACRO=%u\\n", MACRO); |
| } |
| """ |
| compileFlagsIndex = findIndex(self.config.substitutions, lambda x: x[0] == '%{compile_flags}') |
| compileFlags = self.config.substitutions[compileFlagsIndex][1] |
| |
| self.config.substitutions[compileFlagsIndex] = ('%{compile_flags}', compileFlags + ' -DMACRO=1') |
| output1 = dsl.programOutput(self.config, source) |
| self.assertEqual(output1, "MACRO=1\n") |
| |
| self.config.substitutions[compileFlagsIndex] = ('%{compile_flags}', compileFlags + ' -DMACRO=2') |
| output2 = dsl.programOutput(self.config, source) |
| self.assertEqual(output2, "MACRO=2\n") |
| |
| |
| class TestHasLocale(SetupConfigs): |
| """ |
| Tests for libcxx.test.dsl.hasLocale |
| """ |
| def test_doesnt_explode(self): |
| # It's really hard to test that a system has a given locale, so at least |
| # make sure we don't explode when we try to check it. |
| try: |
| dsl.hasAnyLocale(self.config, ['en_US.UTF-8']) |
| except subprocess.CalledProcessError: |
| self.fail("checking for hasLocale should not explode") |
| |
| def test_nonexistent_locale(self): |
| self.assertFalse(dsl.hasAnyLocale(self.config, ['for_sure_this_is_not_an_existing_locale'])) |
| |
| |
| class TestCompilerMacros(SetupConfigs): |
| """ |
| Tests for libcxx.test.dsl.compilerMacros |
| """ |
| def test_basic(self): |
| macros = dsl.compilerMacros(self.config) |
| self.assertIsInstance(macros, dict) |
| self.assertGreater(len(macros), 0) |
| for (k, v) in macros.items(): |
| self.assertIsInstance(k, str) |
| self.assertIsInstance(v, str) |
| |
| def test_no_flag(self): |
| macros = dsl.compilerMacros(self.config) |
| self.assertIn('__cplusplus', macros.keys()) |
| |
| def test_empty_flag(self): |
| macros = dsl.compilerMacros(self.config, '') |
| self.assertIn('__cplusplus', macros.keys()) |
| |
| def test_with_flag(self): |
| macros = dsl.compilerMacros(self.config, '-DFOO=3') |
| self.assertIn('__cplusplus', macros.keys()) |
| self.assertEqual(macros['FOO'], '3') |
| |
| def test_with_flags(self): |
| macros = dsl.compilerMacros(self.config, '-DFOO=3 -DBAR=hello') |
| self.assertIn('__cplusplus', macros.keys()) |
| self.assertEqual(macros['FOO'], '3') |
| self.assertEqual(macros['BAR'], 'hello') |
| |
| |
| class TestFeatureTestMacros(SetupConfigs): |
| """ |
| Tests for libcxx.test.dsl.featureTestMacros |
| """ |
| def test_basic(self): |
| macros = dsl.featureTestMacros(self.config) |
| self.assertIsInstance(macros, dict) |
| self.assertGreater(len(macros), 0) |
| for (k, v) in macros.items(): |
| self.assertIsInstance(k, str) |
| self.assertIsInstance(v, int) |
| |
| |
| class TestFeature(SetupConfigs): |
| """ |
| Tests for libcxx.test.dsl.Feature |
| """ |
| def test_trivial(self): |
| feature = dsl.Feature(name='name') |
| origSubstitutions = copy.deepcopy(self.config.substitutions) |
| actions = feature.getActions(self.config) |
| self.assertTrue(len(actions) == 1) |
| for a in actions: |
| a.applyTo(self.config) |
| self.assertEqual(origSubstitutions, self.config.substitutions) |
| self.assertIn('name', self.config.available_features) |
| |
| def test_name_can_be_a_callable(self): |
| feature = dsl.Feature(name=lambda cfg: 'name') |
| for a in feature.getActions(self.config): |
| a.applyTo(self.config) |
| self.assertIn('name', self.config.available_features) |
| |
| def test_name_is_not_a_string_1(self): |
| feature = dsl.Feature(name=None) |
| self.assertRaises(ValueError, lambda: feature.getActions(self.config)) |
| self.assertRaises(ValueError, lambda: feature.pretty(self.config)) |
| |
| def test_name_is_not_a_string_2(self): |
| feature = dsl.Feature(name=lambda cfg: None) |
| self.assertRaises(ValueError, lambda: feature.getActions(self.config)) |
| self.assertRaises(ValueError, lambda: feature.pretty(self.config)) |
| |
| def test_adding_action(self): |
| feature = dsl.Feature(name='name', actions=[dsl.AddCompileFlag('-std=c++03')]) |
| origLinkFlags = copy.deepcopy(self.getSubstitution('%{link_flags}')) |
| for a in feature.getActions(self.config): |
| a.applyTo(self.config) |
| self.assertIn('name', self.config.available_features) |
| self.assertIn('-std=c++03', self.getSubstitution('%{compile_flags}')) |
| self.assertEqual(origLinkFlags, self.getSubstitution('%{link_flags}')) |
| |
| def test_actions_can_be_a_callable(self): |
| feature = dsl.Feature(name='name', |
| actions=lambda cfg: ( |
| self.assertIs(self.config, cfg), |
| [dsl.AddCompileFlag('-std=c++03')] |
| )[1]) |
| for a in feature.getActions(self.config): |
| a.applyTo(self.config) |
| self.assertIn('-std=c++03', self.getSubstitution('%{compile_flags}')) |
| |
| def test_unsupported_feature(self): |
| feature = dsl.Feature(name='name', when=lambda _: False) |
| self.assertEqual(feature.getActions(self.config), []) |
| |
| def test_is_supported_gets_passed_the_config(self): |
| feature = dsl.Feature(name='name', when=lambda cfg: (self.assertIs(self.config, cfg), True)[1]) |
| self.assertEqual(len(feature.getActions(self.config)), 1) |
| |
| |
| def _throw(): |
| raise ValueError() |
| |
| class TestParameter(SetupConfigs): |
| """ |
| Tests for libcxx.test.dsl.Parameter |
| """ |
| def test_empty_name_should_blow_up(self): |
| self.assertRaises(ValueError, lambda: dsl.Parameter(name='', choices=['c++03'], type=str, help='', actions=lambda _: [])) |
| |
| def test_empty_choices_should_blow_up(self): |
| self.assertRaises(ValueError, lambda: dsl.Parameter(name='std', choices=[], type=str, help='', actions=lambda _: [])) |
| |
| def test_name_is_set_correctly(self): |
| param = dsl.Parameter(name='std', choices=['c++03'], type=str, help='', actions=lambda _: []) |
| self.assertEqual(param.name, 'std') |
| |
| def test_no_value_provided_and_no_default_value(self): |
| param = dsl.Parameter(name='std', choices=['c++03'], type=str, help='', actions=lambda _: []) |
| self.assertRaises(ValueError, lambda: param.getActions(self.config, self.litConfig.params)) |
| |
| def test_no_value_provided_and_default_value(self): |
| param = dsl.Parameter(name='std', choices=['c++03'], type=str, help='', default='c++03', |
| actions=lambda std: [dsl.AddFeature(std)]) |
| for a in param.getActions(self.config, self.litConfig.params): |
| a.applyTo(self.config) |
| self.assertIn('c++03', self.config.available_features) |
| |
| def test_value_provided_on_command_line_and_no_default_value(self): |
| self.litConfig.params['std'] = 'c++03' |
| param = dsl.Parameter(name='std', choices=['c++03'], type=str, help='', |
| actions=lambda std: [dsl.AddFeature(std)]) |
| for a in param.getActions(self.config, self.litConfig.params): |
| a.applyTo(self.config) |
| self.assertIn('c++03', self.config.available_features) |
| |
| def test_value_provided_on_command_line_and_default_value(self): |
| """The value provided on the command line should override the default value""" |
| self.litConfig.params['std'] = 'c++11' |
| param = dsl.Parameter(name='std', choices=['c++03', 'c++11'], type=str, default='c++03', help='', |
| actions=lambda std: [dsl.AddFeature(std)]) |
| for a in param.getActions(self.config, self.litConfig.params): |
| a.applyTo(self.config) |
| self.assertIn('c++11', self.config.available_features) |
| self.assertNotIn('c++03', self.config.available_features) |
| |
| def test_value_provided_in_config_and_default_value(self): |
| """The value provided in the config should override the default value""" |
| self.config.std ='c++11' |
| param = dsl.Parameter(name='std', choices=['c++03', 'c++11'], type=str, default='c++03', help='', |
| actions=lambda std: [dsl.AddFeature(std)]) |
| for a in param.getActions(self.config, self.litConfig.params): |
| a.applyTo(self.config) |
| self.assertIn('c++11', self.config.available_features) |
| self.assertNotIn('c++03', self.config.available_features) |
| |
| def test_value_provided_in_config_and_on_command_line(self): |
| """The value on the command line should override the one in the config""" |
| self.config.std = 'c++11' |
| self.litConfig.params['std'] = 'c++03' |
| param = dsl.Parameter(name='std', choices=['c++03', 'c++11'], type=str, help='', |
| actions=lambda std: [dsl.AddFeature(std)]) |
| for a in param.getActions(self.config, self.litConfig.params): |
| a.applyTo(self.config) |
| self.assertIn('c++03', self.config.available_features) |
| self.assertNotIn('c++11', self.config.available_features) |
| |
| def test_no_actions(self): |
| self.litConfig.params['std'] = 'c++03' |
| param = dsl.Parameter(name='std', choices=['c++03'], type=str, help='', |
| actions=lambda _: []) |
| actions = param.getActions(self.config, self.litConfig.params) |
| self.assertEqual(actions, []) |
| |
| def test_boolean_value_parsed_from_trueish_string_parameter(self): |
| self.litConfig.params['enable_exceptions'] = "True" |
| param = dsl.Parameter(name='enable_exceptions', choices=[True, False], type=bool, help='', |
| actions=lambda exceptions: [] if exceptions else _throw()) |
| self.assertEqual(param.getActions(self.config, self.litConfig.params), []) |
| |
| def test_boolean_value_from_true_boolean_parameter(self): |
| self.litConfig.params['enable_exceptions'] = True |
| param = dsl.Parameter(name='enable_exceptions', choices=[True, False], type=bool, help='', |
| actions=lambda exceptions: [] if exceptions else _throw()) |
| self.assertEqual(param.getActions(self.config, self.litConfig.params), []) |
| |
| def test_boolean_value_parsed_from_falseish_string_parameter(self): |
| self.litConfig.params['enable_exceptions'] = "False" |
| param = dsl.Parameter(name='enable_exceptions', choices=[True, False], type=bool, help='', |
| actions=lambda exceptions: [] if exceptions else [dsl.AddFeature("-fno-exceptions")]) |
| for a in param.getActions(self.config, self.litConfig.params): |
| a.applyTo(self.config) |
| self.assertIn('-fno-exceptions', self.config.available_features) |
| |
| def test_boolean_value_from_false_boolean_parameter(self): |
| self.litConfig.params['enable_exceptions'] = False |
| param = dsl.Parameter(name='enable_exceptions', choices=[True, False], type=bool, help='', |
| actions=lambda exceptions: [] if exceptions else [dsl.AddFeature("-fno-exceptions")]) |
| for a in param.getActions(self.config, self.litConfig.params): |
| a.applyTo(self.config) |
| self.assertIn('-fno-exceptions', self.config.available_features) |
| |
| |
| if __name__ == '__main__': |
| unittest.main(verbosity=2) |