diff --git a/llvm/utils/lit/lit/Test.py b/llvm/utils/lit/lit/Test.py --- a/llvm/utils/lit/lit/Test.py +++ b/llvm/utils/lit/lit/Test.py @@ -1,3 +1,4 @@ +import itertools import os from xml.sax.saxutils import quoteattr from json import JSONEncoder @@ -161,7 +162,7 @@ addMicroResult(microResult) Attach a micro-test result to the test result, with the given name and - result. It is an error to attempt to attach a micro-test with the + result. It is an error to attempt to attach a micro-test with the same name multiple times. Each micro-test result must be an instance of the Result class. @@ -358,6 +359,26 @@ except ValueError as e: raise ValueError('Error in UNSUPPORTED list:\n%s' % str(e)) + def getUsedFeatures(self): + """ + getUsedFeatures() -> list of strings + + Returns a list of all features appearing in XFAIL, UNSUPPORTED and + REQUIRES annotations for this test. + """ + import lit.TestRunner + parsed = lit.TestRunner._parseKeywords(self.getSourcePath(), require_script=False) + unsupported = parsed['UNSUPPORTED:'] or [] + requires = parsed['REQUIRES:'] or [] + xfails = parsed['XFAIL:'] or [] + tokens = itertools.chain.from_iterable( + BooleanExpression.tokenize(expr) for expr in + itertools.chain(unsupported, requires, xfails) if expr != '*' + ) + SPECIALS = {'*', '&&', '||', '!', '(', ')', BooleanExpression.END} + tokens = set(tokens) - SPECIALS + return tokens + def isEarlyTest(self): """ isEarlyTest() -> bool @@ -376,7 +397,7 @@ safe_name = self.suite.name.replace(".","-") if safe_test_path: - class_name = safe_name + "." + "/".join(safe_test_path) + class_name = safe_name + "." + "/".join(safe_test_path) else: class_name = safe_name + "." + safe_name class_name = quoteattr(class_name) diff --git a/llvm/utils/lit/lit/TestRunner.py b/llvm/utils/lit/lit/TestRunner.py --- a/llvm/utils/lit/lit/TestRunner.py +++ b/llvm/utils/lit/lit/TestRunner.py @@ -1373,31 +1373,26 @@ BooleanExpression.evaluate(s, []) return output -def parseIntegratedTestScript(test, additional_parsers=[], - require_script=True): - """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test - script and extract the lines to 'RUN' as well as 'XFAIL', 'REQUIRES', - 'UNSUPPORTED' and 'ALLOW_RETRIES' information. - If additional parsers are specified then the test is also scanned for the - keywords they specify and all matches are passed to the custom parser. +def _parseKeywords(sourcepath, additional_parsers=[], + require_script=True): + """_parseKeywords - If 'require_script' is False an empty script - may be returned. This can be used for test formats where the actual script - is optional or ignored. - """ + Scan an LLVM/Clang style integrated test script and extract all the lines + pertaining to a special parser. This includes 'RUN', 'XFAIL', 'REQUIRES', + 'UNSUPPORTED' and 'ALLOW_RETRIES', as well as other specified custom + parsers. + Returns a dictionary mapping each custom parser to its value after + parsing the test. + """ # Install the built-in keyword parsers. script = [] builtin_parsers = [ - IntegratedTestKeywordParser('RUN:', ParserKind.COMMAND, - initial_value=script), - IntegratedTestKeywordParser('XFAIL:', ParserKind.BOOLEAN_EXPR, - initial_value=test.xfails), - IntegratedTestKeywordParser('REQUIRES:', ParserKind.BOOLEAN_EXPR, - initial_value=test.requires), - IntegratedTestKeywordParser('UNSUPPORTED:', ParserKind.BOOLEAN_EXPR, - initial_value=test.unsupported), + IntegratedTestKeywordParser('RUN:', ParserKind.COMMAND, initial_value=script), + IntegratedTestKeywordParser('XFAIL:', ParserKind.BOOLEAN_EXPR), + IntegratedTestKeywordParser('REQUIRES:', ParserKind.BOOLEAN_EXPR), + IntegratedTestKeywordParser('UNSUPPORTED:', ParserKind.BOOLEAN_EXPR), IntegratedTestKeywordParser('ALLOW_RETRIES:', ParserKind.INTEGER), IntegratedTestKeywordParser('END.', ParserKind.TAG) ] @@ -1414,7 +1409,6 @@ keyword_parsers[parser.keyword] = parser # Collect the test lines from the script. - sourcepath = test.getSourcePath() for line_number, command_type, ln in \ parseIntegratedTestScriptCommands(sourcepath, keyword_parsers.keys()): @@ -1441,6 +1435,37 @@ if value and value[-1][-1] == '\\': raise ValueError("Test has unterminated %s lines (with '\\')" % key) + # Make sure there's at most one ALLOW_RETRIES: line + allowed_retries = keyword_parsers['ALLOW_RETRIES:'].getValue() + if allowed_retries and len(allowed_retries) > 1: + return lit.Test.Result(Test.UNRESOLVED, + "Test has more than one ALLOW_RETRIES lines") + + return {p.keyword: p.getValue() for p in keyword_parsers.values()} + + +def parseIntegratedTestScript(test, additional_parsers=[], + require_script=True): + """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test + script and extract the lines to 'RUN' as well as 'XFAIL', 'REQUIRES', + 'UNSUPPORTED' and 'ALLOW_RETRIES' information into the given test. + + If additional parsers are specified then the test is also scanned for the + keywords they specify and all matches are passed to the custom parser. + + If 'require_script' is False an empty script + may be returned. This can be used for test formats where the actual script + is optional or ignored. + """ + # Parse the test sources and extract test properties + parsed = _parseKeywords(test.getSourcePath(), additional_parsers, require_script) + script = parsed['RUN:'] + test.xfails = parsed['XFAIL:'] or [] + test.requires = parsed['REQUIRES:'] or [] + test.unsupported = parsed['UNSUPPORTED:'] or [] + if parsed['ALLOW_RETRIES:']: + test.allowed_retries = parsed['ALLOW_RETRIES:'][0] + # Enforce REQUIRES: missing_required_features = test.getMissingRequiredFeatures() if missing_required_features: @@ -1458,14 +1483,6 @@ "Test does not support the following features " "and/or targets: %s" % msg) - # Handle ALLOW_RETRIES: - allowed_retries = keyword_parsers['ALLOW_RETRIES:'].getValue() - if allowed_retries: - if len(allowed_retries) > 1: - return lit.Test.Result(Test.UNRESOLVED, - "Test has more than one ALLOW_RETRIES lines") - test.allowed_retries = allowed_retries[0] - # Enforce limit_to_features. if not test.isWithinFeatureLimits(): msg = ', '.join(test.config.limit_to_features) diff --git a/llvm/utils/lit/lit/cl_arguments.py b/llvm/utils/lit/lit/cl_arguments.py --- a/llvm/utils/lit/lit/cl_arguments.py +++ b/llvm/utils/lit/lit/cl_arguments.py @@ -156,6 +156,9 @@ debug_group.add_argument("--show-tests", help="Show all discovered tests and exit", action="store_true") + debug_group.add_argument("--show-features", + help="Show all features used in the test suite (in XFAIL, UNSUPPORTED and REQUIRES) and exit", + action="store_true") # LIT is special: environment variables override command line arguments. env_args = shlex.split(os.environ.get("LIT_OPTS", "")) diff --git a/llvm/utils/lit/lit/main.py b/llvm/utils/lit/lit/main.py --- a/llvm/utils/lit/lit/main.py +++ b/llvm/utils/lit/lit/main.py @@ -4,6 +4,7 @@ See lit.pod for more information. """ +import itertools import os import platform import sys @@ -71,6 +72,11 @@ 'error.\n') sys.exit(2) + if opts.show_features: + features = set(itertools.chain.from_iterable(t.getUsedFeatures() for t in filtered_tests)) + print(' '.join(sorted(features))) + sys.exit(0) + determine_order(filtered_tests, opts.order) if opts.shard: diff --git a/llvm/utils/lit/tests/Inputs/show-features/lit.cfg b/llvm/utils/lit/tests/Inputs/show-features/lit.cfg new file mode 100644 --- /dev/null +++ b/llvm/utils/lit/tests/Inputs/show-features/lit.cfg @@ -0,0 +1,6 @@ +import lit.formats +config.name = 'show-features' +config.suffixes = ['.txt'] +config.test_format = lit.formats.ShTest() +config.test_source_root = None +config.test_exec_root = None diff --git a/llvm/utils/lit/tests/Inputs/show-features/mixed.txt b/llvm/utils/lit/tests/Inputs/show-features/mixed.txt new file mode 100644 --- /dev/null +++ b/llvm/utils/lit/tests/Inputs/show-features/mixed.txt @@ -0,0 +1,4 @@ + +// REQUIRES: my-require-feature-2 || my-require-feature-3 +// UNSUPPORTED: my-unsupported-feature-2, my-unsupported-feature-3 +// XFAIL: my-xfail-feature-2, my-xfail-feature-3 diff --git a/llvm/utils/lit/tests/Inputs/show-features/requires.txt b/llvm/utils/lit/tests/Inputs/show-features/requires.txt new file mode 100644 --- /dev/null +++ b/llvm/utils/lit/tests/Inputs/show-features/requires.txt @@ -0,0 +1,2 @@ + +// REQUIRES: my-require-feature-1 diff --git a/llvm/utils/lit/tests/Inputs/show-features/unsupported.txt b/llvm/utils/lit/tests/Inputs/show-features/unsupported.txt new file mode 100644 --- /dev/null +++ b/llvm/utils/lit/tests/Inputs/show-features/unsupported.txt @@ -0,0 +1,2 @@ + +// UNSUPPORTED: my-unsupported-feature-1 diff --git a/llvm/utils/lit/tests/Inputs/show-features/xfail.txt b/llvm/utils/lit/tests/Inputs/show-features/xfail.txt new file mode 100644 --- /dev/null +++ b/llvm/utils/lit/tests/Inputs/show-features/xfail.txt @@ -0,0 +1,2 @@ + +// XFAIL: my-xfail-feature-1 diff --git a/llvm/utils/lit/tests/show-features.py b/llvm/utils/lit/tests/show-features.py new file mode 100644 --- /dev/null +++ b/llvm/utils/lit/tests/show-features.py @@ -0,0 +1,6 @@ +# Check that --show-features works correctly. +# +# RUN: %{lit} %{inputs}/show-features --show-features | FileCheck %s +# CHECK: my-require-feature-1 my-require-feature-2 my-require-feature-3 +# CHECK: my-unsupported-feature-1 my-unsupported-feature-2 my-unsupported-feature-3 +# CHECK: my-xfail-feature-1 my-xfail-feature-2 my-xfail-feature-3