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 9import os 10import pickle 11import pipes 12import platform 13import shutil 14import tempfile 15 16import libcxx.test.format 17import lit 18import lit.LitConfig 19import lit.Test 20import lit.TestRunner 21import lit.util 22 23 24class ConfigurationError(Exception): 25 pass 26 27 28class ConfigurationCompilationError(ConfigurationError): 29 pass 30 31 32class ConfigurationRuntimeError(ConfigurationError): 33 pass 34 35 36def _memoizeExpensiveOperation(extractCacheKey): 37 """ 38 Allows memoizing a very expensive operation. 39 40 We pickle the cache key to make sure we store an immutable representation 41 of it. If we stored an object and the object was referenced elsewhere, it 42 could be changed from under our feet, which would break the cache. 43 44 We also store the cache for a given function persistently across invocations 45 of Lit. This dramatically speeds up the configuration of the test suite when 46 invoking Lit repeatedly, which is important for developer workflow. However, 47 with the current implementation that does not synchronize updates to the 48 persistent cache, this also means that one should not call a memoized 49 operation from multiple threads. This should normally not be a problem 50 since Lit configuration is single-threaded. 51 """ 52 53 def decorator(function): 54 def f(config, *args, **kwargs): 55 cacheRoot = os.path.join(config.test_exec_root, "__config_cache__") 56 persistentCache = os.path.join(cacheRoot, function.__name__) 57 if not os.path.exists(cacheRoot): 58 os.makedirs(cacheRoot) 59 60 cache = {} 61 # Load a cache from a previous Lit invocation if there is one. 62 if os.path.exists(persistentCache): 63 with open(persistentCache, "rb") as cacheFile: 64 cache = pickle.load(cacheFile) 65 66 cacheKey = pickle.dumps(extractCacheKey(config, *args, **kwargs)) 67 if cacheKey not in cache: 68 cache[cacheKey] = function(config, *args, **kwargs) 69 # Update the persistent cache so it knows about the new key 70 # We write to a PID-suffixed file and rename the result to 71 # ensure that the cache is not corrupted when running the test 72 # suite with multiple shards. Since this file is in the same 73 # directory as the destination, os.replace() will be atomic. 74 unique_suffix = ".tmp." + str(os.getpid()) 75 with open(persistentCache + unique_suffix, "wb") as cacheFile: 76 pickle.dump(cache, cacheFile) 77 os.replace(persistentCache + unique_suffix, persistentCache) 78 return cache[cacheKey] 79 80 return f 81 82 return decorator 83 84 85def _executeWithFakeConfig(test, commands): 86 """ 87 Returns (stdout, stderr, exitCode, timeoutInfo, parsedCommands) 88 """ 89 litConfig = lit.LitConfig.LitConfig( 90 progname="lit", 91 path=[], 92 quiet=False, 93 useValgrind=False, 94 valgrindLeakCheck=False, 95 valgrindArgs=[], 96 noExecute=False, 97 debug=False, 98 isWindows=platform.system() == "Windows", 99 order="smart", 100 params={}, 101 ) 102 return libcxx.test.format._executeScriptInternal(test, litConfig, commands) 103 104 105def _makeConfigTest(config): 106 # Make sure the support directories exist, which is needed to create 107 # the temporary file %t below. 108 sourceRoot = os.path.join(config.test_exec_root, "__config_src__") 109 execRoot = os.path.join(config.test_exec_root, "__config_exec__") 110 for supportDir in (sourceRoot, execRoot): 111 if not os.path.exists(supportDir): 112 os.makedirs(supportDir) 113 114 # Create a dummy test suite and single dummy test inside it. As part of 115 # the Lit configuration, automatically do the equivalent of 'mkdir %T' 116 # and 'rm -r %T' to avoid cluttering the build directory. 117 suite = lit.Test.TestSuite("__config__", sourceRoot, execRoot, config) 118 tmp = tempfile.NamedTemporaryFile(dir=sourceRoot, delete=False, suffix=".cpp") 119 tmp.close() 120 pathInSuite = [os.path.relpath(tmp.name, sourceRoot)] 121 122 class TestWrapper(lit.Test.Test): 123 def __enter__(self): 124 testDir, _ = libcxx.test.format._getTempPaths(self) 125 os.makedirs(testDir) 126 return self 127 128 def __exit__(self, *args): 129 testDir, _ = libcxx.test.format._getTempPaths(self) 130 shutil.rmtree(testDir) 131 os.remove(tmp.name) 132 133 return TestWrapper(suite, pathInSuite, config) 134 135 136@_memoizeExpensiveOperation(lambda c, s, f=[]: (c.substitutions, c.environment, s, f)) 137def sourceBuilds(config, source, additionalFlags=[]): 138 """ 139 Return whether the program in the given string builds successfully. 140 141 This is done by compiling and linking a program that consists of the given 142 source with the %{cxx} substitution, and seeing whether that succeeds. If 143 any additional flags are passed, they are appended to the compiler invocation. 144 """ 145 with _makeConfigTest(config) as test: 146 with open(test.getSourcePath(), "w") as sourceFile: 147 sourceFile.write(source) 148 _, _, exitCode, _, _ = _executeWithFakeConfig( 149 test, ["%{{build}} {}".format(" ".join(additionalFlags))] 150 ) 151 return exitCode == 0 152 153 154@_memoizeExpensiveOperation( 155 lambda c, p, args=None: (c.substitutions, c.environment, p, args) 156) 157def programOutput(config, program, args=None): 158 """ 159 Compiles a program for the test target, run it on the test target and return 160 the output. 161 162 Note that execution of the program is done through the %{exec} substitution, 163 which means that the program may be run on a remote host depending on what 164 %{exec} does. 165 """ 166 if args is None: 167 args = [] 168 with _makeConfigTest(config) as test: 169 with open(test.getSourcePath(), "w") as source: 170 source.write(program) 171 _, err, exitCode, _, buildcmd = _executeWithFakeConfig(test, ["%{build}"]) 172 if exitCode != 0: 173 raise ConfigurationCompilationError( 174 "Failed to build program, cmd:\n{}\nstderr is:\n{}".format( 175 buildcmd, err 176 ) 177 ) 178 179 out, err, exitCode, _, runcmd = _executeWithFakeConfig( 180 test, ["%{{run}} {}".format(" ".join(args))] 181 ) 182 if exitCode != 0: 183 raise ConfigurationRuntimeError( 184 "Failed to run program, cmd:\n{}\nstderr is:\n{}".format(runcmd, err) 185 ) 186 187 return out 188 189 190@_memoizeExpensiveOperation( 191 lambda c, p, args=None: (c.substitutions, c.environment, p, args) 192) 193def programSucceeds(config, program, args=None): 194 """ 195 Compiles a program for the test target, run it on the test target and return 196 whether it completed successfully. 197 198 Note that execution of the program is done through the %{exec} substitution, 199 which means that the program may be run on a remote host depending on what 200 %{exec} does. 201 """ 202 try: 203 programOutput(config, program, args) 204 except ConfigurationRuntimeError: 205 return False 206 return True 207 208 209@_memoizeExpensiveOperation(lambda c, f: (c.substitutions, c.environment, f)) 210def tryCompileFlag(config, flag): 211 """ 212 Try using the given compiler flag and return the exit code along with stdout and stderr. 213 """ 214 # fmt: off 215 with _makeConfigTest(config) as test: 216 out, err, exitCode, timeoutInfo, _ = _executeWithFakeConfig(test, [ 217 "%{{cxx}} -xc++ {} -Werror -fsyntax-only %{{flags}} %{{compile_flags}} {}".format(os.devnull, flag) 218 ]) 219 return exitCode, out, err 220 # fmt: on 221 222 223def hasCompileFlag(config, flag): 224 """ 225 Return whether the compiler in the configuration supports a given compiler flag. 226 227 This is done by executing the %{cxx} substitution with the given flag and 228 checking whether that succeeds. 229 """ 230 (exitCode, _, _) = tryCompileFlag(config, flag) 231 return exitCode == 0 232 233 234@_memoizeExpensiveOperation(lambda c, s: (c.substitutions, c.environment, s)) 235def runScriptExitCode(config, script): 236 """ 237 Runs the given script as a Lit test, and returns the exit code of the execution. 238 239 The script must be a list of commands, each of which being something that 240 could appear on the right-hand-side of a `RUN:` keyword. 241 """ 242 with _makeConfigTest(config) as test: 243 _, _, exitCode, _, _ = _executeWithFakeConfig(test, script) 244 return exitCode 245 246 247@_memoizeExpensiveOperation(lambda c, s: (c.substitutions, c.environment, s)) 248def commandOutput(config, command): 249 """ 250 Runs the given script as a Lit test, and returns the output. 251 If the exit code isn't 0 an exception is raised. 252 253 The script must be a list of commands, each of which being something that 254 could appear on the right-hand-side of a `RUN:` keyword. 255 """ 256 with _makeConfigTest(config) as test: 257 out, err, exitCode, _, cmd = _executeWithFakeConfig(test, command) 258 if exitCode != 0: 259 raise ConfigurationRuntimeError( 260 "Failed to run command: {}\nstderr is:\n{}".format(cmd, err) 261 ) 262 return out 263 264 265@_memoizeExpensiveOperation(lambda c, l: (c.substitutions, c.environment, l)) 266def hasAnyLocale(config, locales): 267 """ 268 Return whether the runtime execution environment supports a given locale. 269 Different systems may use different names for a locale, so this function checks 270 whether any of the passed locale names is supported by setlocale() and returns 271 true if one of them works. 272 273 This is done by executing a program that tries to set the given locale using 274 %{exec} -- this means that the command may be executed on a remote host 275 depending on the %{exec} substitution. 276 """ 277 program = """ 278 #include <stddef.h> 279 #if defined(_LIBCPP_HAS_NO_LOCALIZATION) 280 int main(int, char**) { return 1; } 281 #else 282 #include <locale.h> 283 int main(int argc, char** argv) { 284 for (int i = 1; i < argc; i++) { 285 if (::setlocale(LC_ALL, argv[i]) != NULL) { 286 return 0; 287 } 288 } 289 return 1; 290 } 291 #endif 292 """ 293 return programSucceeds(config, program, args=[pipes.quote(l) for l in locales]) 294 295 296@_memoizeExpensiveOperation(lambda c, flags="": (c.substitutions, c.environment, flags)) 297def compilerMacros(config, flags=""): 298 """ 299 Return a dictionary of predefined compiler macros. 300 301 The keys are strings representing macros, and the values are strings 302 representing what each macro is defined to. 303 304 If the optional `flags` argument (a string) is provided, these flags will 305 be added to the compiler invocation when generating the macros. 306 """ 307 with _makeConfigTest(config) as test: 308 with open(test.getSourcePath(), "w") as sourceFile: 309 sourceFile.write( 310 """ 311 #if __has_include(<__config_site>) 312 # include <__config_site> 313 #endif 314 """ 315 ) 316 unparsedOutput, err, exitCode, _, cmd = _executeWithFakeConfig( 317 test, ["%{{cxx}} %s -dM -E %{{flags}} %{{compile_flags}} {}".format(flags)] 318 ) 319 if exitCode != 0: 320 raise ConfigurationCompilationError( 321 "Failed to retrieve compiler macros, compiler invocation is:\n{}\nstderr is:\n{}".format( 322 cmd, err 323 ) 324 ) 325 parsedMacros = dict() 326 defines = ( 327 l.strip() for l in unparsedOutput.split("\n") if l.startswith("#define ") 328 ) 329 for line in defines: 330 line = line[len("#define ") :] 331 macro, _, value = line.partition(" ") 332 parsedMacros[macro] = value 333 return parsedMacros 334 335 336def featureTestMacros(config, flags=""): 337 """ 338 Return a dictionary of feature test macros. 339 340 The keys are strings representing feature test macros, and the values are 341 integers representing the value of the macro. 342 """ 343 allMacros = compilerMacros(config, flags) 344 return { 345 m: int(v.rstrip("LlUu")) 346 for (m, v) in allMacros.items() 347 if m.startswith("__cpp_") 348 } 349 350 351def _getSubstitution(substitution, config): 352 for (orig, replacement) in config.substitutions: 353 if orig == substitution: 354 return replacement 355 raise ValueError('Substitution {} is not in the config.'.format(substitution)) 356 357def _appendToSubstitution(substitutions, key, value): 358 return [(k, v + " " + value) if k == key else (k, v) for (k, v) in substitutions] 359 360def _prependToSubstitution(substitutions, key, value): 361 return [(k, value + " " + v) if k == key else (k, v) for (k, v) in substitutions] 362 363def _ensureFlagIsSupported(config, flag): 364 (exitCode, out, err) = tryCompileFlag(config, flag) 365 assert ( 366 exitCode == 0 367 ), f"Trying to enable compiler flag {flag}, which is not supported. stdout was:\n{out}\n\nstderr was:\n{err}" 368 369 370class ConfigAction(object): 371 """ 372 This class represents an action that can be performed on a Lit TestingConfig 373 object. 374 375 Examples of such actions are adding or modifying substitutions, Lit features, 376 etc. This class only provides the interface of such actions, and it is meant 377 to be subclassed appropriately to create new actions. 378 """ 379 380 def applyTo(self, config): 381 """ 382 Applies the action to the given configuration. 383 384 This should modify the configuration object in place, and return nothing. 385 386 If applying the action to the configuration would yield an invalid 387 configuration, and it is possible to diagnose it here, this method 388 should produce an error. For example, it should be an error to modify 389 a substitution in a way that we know for sure is invalid (e.g. adding 390 a compiler flag when we know the compiler doesn't support it). Failure 391 to do so early may lead to difficult-to-diagnose issues down the road. 392 """ 393 pass 394 395 def pretty(self, config, litParams): 396 """ 397 Returns a short and human-readable string describing what this action does. 398 399 This is used for logging purposes when running the test suite, so it should 400 be kept concise. 401 """ 402 pass 403 404 405class AddFeature(ConfigAction): 406 """ 407 This action defines the given Lit feature when running the test suite. 408 409 The name of the feature can be a string or a callable, in which case it is 410 called with the configuration to produce the feature name (as a string). 411 """ 412 413 def __init__(self, name): 414 self._name = name 415 416 def _getName(self, config): 417 name = self._name(config) if callable(self._name) else self._name 418 if not isinstance(name, str): 419 raise ValueError( 420 "Lit feature did not resolve to a string (got {})".format(name) 421 ) 422 return name 423 424 def applyTo(self, config): 425 config.available_features.add(self._getName(config)) 426 427 def pretty(self, config, litParams): 428 return "add Lit feature {}".format(self._getName(config)) 429 430 431class AddFlag(ConfigAction): 432 """ 433 This action adds the given flag to the %{flags} substitution. 434 435 The flag can be a string or a callable, in which case it is called with the 436 configuration to produce the actual flag (as a string). 437 """ 438 439 def __init__(self, flag): 440 self._getFlag = lambda config: flag(config) if callable(flag) else flag 441 442 def applyTo(self, config): 443 flag = self._getFlag(config) 444 _ensureFlagIsSupported(config, flag) 445 config.substitutions = _appendToSubstitution( 446 config.substitutions, "%{flags}", flag 447 ) 448 449 def pretty(self, config, litParams): 450 return "add {} to %{{flags}}".format(self._getFlag(config)) 451 452class AddFlagIfSupported(ConfigAction): 453 """ 454 This action adds the given flag to the %{flags} substitution, only if 455 the compiler supports the flag. 456 457 The flag can be a string or a callable, in which case it is called with the 458 configuration to produce the actual flag (as a string). 459 """ 460 461 def __init__(self, flag): 462 self._getFlag = lambda config: flag(config) if callable(flag) else flag 463 464 def applyTo(self, config): 465 flag = self._getFlag(config) 466 if hasCompileFlag(config, flag): 467 config.substitutions = _appendToSubstitution( 468 config.substitutions, "%{flags}", flag 469 ) 470 471 def pretty(self, config, litParams): 472 return "add {} to %{{flags}}".format(self._getFlag(config)) 473 474 475class AddCompileFlag(ConfigAction): 476 """ 477 This action adds the given flag to the %{compile_flags} substitution. 478 479 The flag can be a string or a callable, in which case it is called with the 480 configuration to produce the actual flag (as a string). 481 """ 482 483 def __init__(self, flag): 484 self._getFlag = lambda config: flag(config) if callable(flag) else flag 485 486 def applyTo(self, config): 487 flag = self._getFlag(config) 488 _ensureFlagIsSupported(config, flag) 489 config.substitutions = _appendToSubstitution( 490 config.substitutions, "%{compile_flags}", flag 491 ) 492 493 def pretty(self, config, litParams): 494 return "add {} to %{{compile_flags}}".format(self._getFlag(config)) 495 496 497class AddLinkFlag(ConfigAction): 498 """ 499 This action appends the given flag to the %{link_flags} substitution. 500 501 The flag can be a string or a callable, in which case it is called with the 502 configuration to produce the actual flag (as a string). 503 """ 504 505 def __init__(self, flag): 506 self._getFlag = lambda config: flag(config) if callable(flag) else flag 507 508 def applyTo(self, config): 509 flag = self._getFlag(config) 510 _ensureFlagIsSupported(config, flag) 511 config.substitutions = _appendToSubstitution( 512 config.substitutions, "%{link_flags}", flag 513 ) 514 515 def pretty(self, config, litParams): 516 return "append {} to %{{link_flags}}".format(self._getFlag(config)) 517 518 519class PrependLinkFlag(ConfigAction): 520 """ 521 This action prepends the given flag to the %{link_flags} substitution. 522 523 The flag can be a string or a callable, in which case it is called with the 524 configuration to produce the actual flag (as a string). 525 """ 526 527 def __init__(self, flag): 528 self._getFlag = lambda config: flag(config) if callable(flag) else flag 529 530 def applyTo(self, config): 531 flag = self._getFlag(config) 532 _ensureFlagIsSupported(config, flag) 533 config.substitutions = _prependToSubstitution( 534 config.substitutions, "%{link_flags}", flag 535 ) 536 537 def pretty(self, config, litParams): 538 return "prepend {} to %{{link_flags}}".format(self._getFlag(config)) 539 540 541class AddOptionalWarningFlag(ConfigAction): 542 """ 543 This action adds the given warning flag to the %{compile_flags} substitution, 544 if it is supported by the compiler. 545 546 The flag can be a string or a callable, in which case it is called with the 547 configuration to produce the actual flag (as a string). 548 """ 549 550 def __init__(self, flag): 551 self._getFlag = lambda config: flag(config) if callable(flag) else flag 552 553 def applyTo(self, config): 554 flag = self._getFlag(config) 555 # Use -Werror to make sure we see an error about the flag being unsupported. 556 if hasCompileFlag(config, "-Werror " + flag): 557 config.substitutions = _appendToSubstitution( 558 config.substitutions, "%{compile_flags}", flag 559 ) 560 561 def pretty(self, config, litParams): 562 return "add {} to %{{compile_flags}}".format(self._getFlag(config)) 563 564 565class AddSubstitution(ConfigAction): 566 """ 567 This action adds the given substitution to the Lit configuration. 568 569 The substitution can be a string or a callable, in which case it is called 570 with the configuration to produce the actual substitution (as a string). 571 """ 572 573 def __init__(self, key, substitution): 574 self._key = key 575 self._getSub = ( 576 lambda config: substitution(config) 577 if callable(substitution) 578 else substitution 579 ) 580 581 def applyTo(self, config): 582 key = self._key 583 sub = self._getSub(config) 584 config.substitutions.append((key, sub)) 585 586 def pretty(self, config, litParams): 587 return "add substitution {} = {}".format(self._key, self._getSub(config)) 588 589 590class Feature(object): 591 """ 592 Represents a Lit available feature that is enabled whenever it is supported. 593 594 A feature like this informs the test suite about a capability of the compiler, 595 platform, etc. Unlike Parameters, it does not make sense to explicitly 596 control whether a Feature is enabled -- it should be enabled whenever it 597 is supported. 598 """ 599 600 def __init__(self, name, actions=None, when=lambda _: True): 601 """ 602 Create a Lit feature for consumption by a test suite. 603 604 - name 605 The name of the feature. This is what will end up in Lit's available 606 features if the feature is enabled. This can be either a string or a 607 callable, in which case it is passed the TestingConfig and should 608 generate a string representing the name of the feature. 609 610 - actions 611 An optional list of ConfigActions to apply when the feature is supported. 612 An AddFeature action is always created regardless of any actions supplied 613 here -- these actions are meant to perform more than setting a corresponding 614 Lit feature (e.g. adding compiler flags). If 'actions' is a callable, it 615 is called with the current configuration object to generate the actual 616 list of actions. 617 618 - when 619 A callable that gets passed a TestingConfig and should return a 620 boolean representing whether the feature is supported in that 621 configuration. For example, this can use `hasCompileFlag` to 622 check whether the compiler supports the flag that the feature 623 represents. If omitted, the feature will always be considered 624 supported. 625 """ 626 self._name = name 627 self._actions = [] if actions is None else actions 628 self._isSupported = when 629 630 def _getName(self, config): 631 name = self._name(config) if callable(self._name) else self._name 632 if not isinstance(name, str): 633 raise ValueError( 634 "Feature did not resolve to a name that's a string, got {}".format(name) 635 ) 636 return name 637 638 def getActions(self, config): 639 """ 640 Return the list of actions associated to this feature. 641 642 If the feature is not supported, an empty list is returned. 643 If the feature is supported, an `AddFeature` action is automatically added 644 to the returned list of actions, in addition to any actions provided on 645 construction. 646 """ 647 if not self._isSupported(config): 648 return [] 649 else: 650 actions = ( 651 self._actions(config) if callable(self._actions) else self._actions 652 ) 653 return [AddFeature(self._getName(config))] + actions 654 655 def pretty(self, config): 656 """ 657 Returns the Feature's name. 658 """ 659 return self._getName(config) 660 661 662def _str_to_bool(s): 663 """ 664 Convert a string value to a boolean. 665 666 True values are "y", "yes", "t", "true", "on" and "1", regardless of capitalization. 667 False values are "n", "no", "f", "false", "off" and "0", regardless of capitalization. 668 """ 669 trueVals = ["y", "yes", "t", "true", "on", "1"] 670 falseVals = ["n", "no", "f", "false", "off", "0"] 671 lower = s.lower() 672 if lower in trueVals: 673 return True 674 elif lower in falseVals: 675 return False 676 else: 677 raise ValueError("Got string '{}', which isn't a valid boolean".format(s)) 678 679 680def _parse_parameter(s, type): 681 if type is bool and isinstance(s, str): 682 return _str_to_bool(s) 683 elif type is list and isinstance(s, str): 684 return [x.strip() for x in s.split(",") if x.strip()] 685 return type(s) 686 687 688class Parameter(object): 689 """ 690 Represents a parameter of a Lit test suite. 691 692 Parameters are used to customize the behavior of test suites in a user 693 controllable way. There are two ways of setting the value of a Parameter. 694 The first one is to pass `--param <KEY>=<VALUE>` when running Lit (or 695 equivalently to set `litConfig.params[KEY] = VALUE` somewhere in the 696 Lit configuration files. This method will set the parameter globally for 697 all test suites being run. 698 699 The second method is to set `config.KEY = VALUE` somewhere in the Lit 700 configuration files, which sets the parameter only for the test suite(s) 701 that use that `config` object. 702 703 Parameters can have multiple possible values, and they can have a default 704 value when left unspecified. They can also have any number of ConfigActions 705 associated to them, in which case the actions will be performed on the 706 TestingConfig if the parameter is enabled. Depending on the actions 707 associated to a Parameter, it may be an error to enable the Parameter 708 if some actions are not supported in the given configuration. For example, 709 trying to set the compilation standard to C++23 when `-std=c++23` is not 710 supported by the compiler would be an error. 711 """ 712 713 def __init__(self, name, type, help, actions, choices=None, default=None): 714 """ 715 Create a Lit parameter to customize the behavior of a test suite. 716 717 - name 718 The name of the parameter that can be used to set it on the command-line. 719 On the command-line, the parameter can be set using `--param <name>=<value>` 720 when running Lit. This must be non-empty. 721 722 - choices 723 An optional non-empty set of possible values for this parameter. If provided, 724 this must be anything that can be iterated. It is an error if the parameter 725 is given a value that is not in that set, whether explicitly or through a 726 default value. 727 728 - type 729 A callable that can be used to parse the value of the parameter given 730 on the command-line. As a special case, using the type `bool` also 731 allows parsing strings with boolean-like contents, and the type `list` 732 will parse a string delimited by commas into a list of the substrings. 733 734 - help 735 A string explaining the parameter, for documentation purposes. 736 TODO: We should be able to surface those from the Lit command-line. 737 738 - actions 739 A callable that gets passed the parsed value of the parameter (either 740 the one passed on the command-line or the default one), and that returns 741 a list of ConfigAction to perform given the value of the parameter. 742 All the ConfigAction must be supported in the given configuration. 743 744 - default 745 An optional default value to use for the parameter when no value is 746 provided on the command-line. If the default value is a callable, it 747 is called with the TestingConfig and should return the default value 748 for the parameter. Whether the default value is computed or specified 749 directly, it must be in the 'choices' provided for that Parameter. 750 """ 751 self._name = name 752 if len(self._name) == 0: 753 raise ValueError("Parameter name must not be the empty string") 754 755 if choices is not None: 756 self._choices = list(choices) # should be finite 757 if len(self._choices) == 0: 758 raise ValueError( 759 "Parameter '{}' must be given at least one possible value".format( 760 self._name 761 ) 762 ) 763 else: 764 self._choices = None 765 766 self._parse = lambda x: _parse_parameter(x, type) 767 self._help = help 768 self._actions = actions 769 self._default = default 770 771 def _getValue(self, config, litParams): 772 """ 773 Return the value of the parameter given the configuration objects. 774 """ 775 param = getattr(config, self.name, None) 776 param = litParams.get(self.name, param) 777 if param is None and self._default is None: 778 raise ValueError( 779 "Parameter {} doesn't have a default value, but it was not specified in the Lit parameters or in the Lit config".format( 780 self.name 781 ) 782 ) 783 getDefault = ( 784 lambda: self._default(config) if callable(self._default) else self._default 785 ) 786 787 if param is not None: 788 (pretty, value) = (param, self._parse(param)) 789 else: 790 value = getDefault() 791 pretty = "{} (default)".format(value) 792 793 if self._choices and value not in self._choices: 794 raise ValueError( 795 "Got value '{}' for parameter '{}', which is not in the provided set of possible choices: {}".format( 796 value, self.name, self._choices 797 ) 798 ) 799 return (pretty, value) 800 801 @property 802 def name(self): 803 """ 804 Return the name of the parameter. 805 806 This is the name that can be used to set the parameter on the command-line 807 when running Lit. 808 """ 809 return self._name 810 811 def getActions(self, config, litParams): 812 """ 813 Return the list of actions associated to this value of the parameter. 814 """ 815 (_, parameterValue) = self._getValue(config, litParams) 816 return self._actions(parameterValue) 817 818 def pretty(self, config, litParams): 819 """ 820 Return a pretty representation of the parameter's name and value. 821 """ 822 (prettyParameterValue, _) = self._getValue(config, litParams) 823 return "{}={}".format(self.name, prettyParameterValue) 824