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