summaryrefslogtreecommitdiff
path: root/utils/libcxx/test/format.py
diff options
context:
space:
mode:
Diffstat (limited to 'utils/libcxx/test/format.py')
-rw-r--r--utils/libcxx/test/format.py238
1 files changed, 238 insertions, 0 deletions
diff --git a/utils/libcxx/test/format.py b/utils/libcxx/test/format.py
new file mode 100644
index 000000000000..c3bc97187ad4
--- /dev/null
+++ b/utils/libcxx/test/format.py
@@ -0,0 +1,238 @@
+#===----------------------------------------------------------------------===##
+#
+# The LLVM Compiler Infrastructure
+#
+# This file is dual licensed under the MIT and the University of Illinois Open
+# Source Licenses. See LICENSE.TXT for details.
+#
+#===----------------------------------------------------------------------===##
+
+import copy
+import errno
+import os
+import time
+import random
+
+import lit.Test # pylint: disable=import-error
+import lit.TestRunner # pylint: disable=import-error
+from lit.TestRunner import ParserKind, IntegratedTestKeywordParser \
+ # pylint: disable=import-error
+
+from libcxx.test.executor import LocalExecutor as LocalExecutor
+import libcxx.util
+
+
+class LibcxxTestFormat(object):
+ """
+ Custom test format handler for use with the test format use by libc++.
+
+ Tests fall into two categories:
+ FOO.pass.cpp - Executable test which should compile, run, and exit with
+ code 0.
+ FOO.fail.cpp - Negative test case which is expected to fail compilation.
+ FOO.sh.cpp - A test that uses LIT's ShTest format.
+ """
+
+ def __init__(self, cxx, use_verify_for_fail, execute_external,
+ executor, exec_env):
+ self.cxx = copy.deepcopy(cxx)
+ self.use_verify_for_fail = use_verify_for_fail
+ self.execute_external = execute_external
+ self.executor = executor
+ self.exec_env = dict(exec_env)
+
+ @staticmethod
+ def _make_custom_parsers():
+ return [
+ IntegratedTestKeywordParser('FLAKY_TEST.', ParserKind.TAG,
+ initial_value=False),
+ IntegratedTestKeywordParser('MODULES_DEFINES:', ParserKind.LIST,
+ initial_value=[])
+ ]
+
+ @staticmethod
+ def _get_parser(key, parsers):
+ for p in parsers:
+ if p.keyword == key:
+ return p
+ assert False and "parser not found"
+
+ # TODO: Move this into lit's FileBasedTest
+ def getTestsInDirectory(self, testSuite, path_in_suite,
+ litConfig, localConfig):
+ source_path = testSuite.getSourcePath(path_in_suite)
+ for filename in os.listdir(source_path):
+ # Ignore dot files and excluded tests.
+ if filename.startswith('.') or filename in localConfig.excludes:
+ continue
+
+ filepath = os.path.join(source_path, filename)
+ if not os.path.isdir(filepath):
+ if any([filename.endswith(ext)
+ for ext in localConfig.suffixes]):
+ yield lit.Test.Test(testSuite, path_in_suite + (filename,),
+ localConfig)
+
+ def execute(self, test, lit_config):
+ while True:
+ try:
+ return self._execute(test, lit_config)
+ except OSError as oe:
+ if oe.errno != errno.ETXTBSY:
+ raise
+ time.sleep(0.1)
+
+ def _execute(self, test, lit_config):
+ name = test.path_in_suite[-1]
+ name_root, name_ext = os.path.splitext(name)
+ is_libcxx_test = test.path_in_suite[0] == 'libcxx'
+ is_sh_test = name_root.endswith('.sh')
+ is_pass_test = name.endswith('.pass.cpp')
+ is_fail_test = name.endswith('.fail.cpp')
+ assert is_sh_test or name_ext == '.cpp', 'non-cpp file must be sh test'
+
+ if test.config.unsupported:
+ return (lit.Test.UNSUPPORTED,
+ "A lit.local.cfg marked this unsupported")
+
+ parsers = self._make_custom_parsers()
+ script = lit.TestRunner.parseIntegratedTestScript(
+ test, additional_parsers=parsers, require_script=is_sh_test)
+ # Check if a result for the test was returned. If so return that
+ # result.
+ if isinstance(script, lit.Test.Result):
+ return script
+ if lit_config.noExecute:
+ return lit.Test.Result(lit.Test.PASS)
+
+ # Check that we don't have run lines on tests that don't support them.
+ if not is_sh_test and len(script) != 0:
+ lit_config.fatal('Unsupported RUN line found in test %s' % name)
+
+ tmpDir, tmpBase = lit.TestRunner.getTempPaths(test)
+ substitutions = lit.TestRunner.getDefaultSubstitutions(test, tmpDir,
+ tmpBase)
+ script = lit.TestRunner.applySubstitutions(script, substitutions)
+
+ test_cxx = copy.deepcopy(self.cxx)
+ if is_fail_test:
+ test_cxx.useCCache(False)
+ test_cxx.useWarnings(False)
+ extra_modules_defines = self._get_parser('MODULES_DEFINES:',
+ parsers).getValue()
+ if '-fmodules' in test.config.available_features:
+ test_cxx.compile_flags += [('-D%s' % mdef.strip()) for
+ mdef in extra_modules_defines]
+ test_cxx.addWarningFlagIfSupported('-Wno-macro-redefined')
+ # FIXME: libc++ debug tests #define _LIBCPP_ASSERT to override it
+ # If we see this we need to build the test against uniquely built
+ # modules.
+ if is_libcxx_test:
+ with open(test.getSourcePath(), 'r') as f:
+ contents = f.read()
+ if '#define _LIBCPP_ASSERT' in contents:
+ test_cxx.useModules(False)
+
+ # Dispatch the test based on its suffix.
+ if is_sh_test:
+ if not isinstance(self.executor, LocalExecutor):
+ # We can't run ShTest tests with a executor yet.
+ # For now, bail on trying to run them
+ return lit.Test.UNSUPPORTED, 'ShTest format not yet supported'
+ test.config.enviroment = dict(self.exec_env)
+ return lit.TestRunner._runShTest(test, lit_config,
+ self.execute_external, script,
+ tmpBase)
+ elif is_fail_test:
+ return self._evaluate_fail_test(test, test_cxx, parsers)
+ elif is_pass_test:
+ return self._evaluate_pass_test(test, tmpBase, lit_config,
+ test_cxx, parsers)
+ else:
+ # No other test type is supported
+ assert False
+
+ def _clean(self, exec_path): # pylint: disable=no-self-use
+ libcxx.util.cleanFile(exec_path)
+
+ def _evaluate_pass_test(self, test, tmpBase, lit_config,
+ test_cxx, parsers):
+ execDir = os.path.dirname(test.getExecPath())
+ source_path = test.getSourcePath()
+ exec_path = tmpBase + '.exe'
+ object_path = tmpBase + '.o'
+ # Create the output directory if it does not already exist.
+ libcxx.util.mkdir_p(os.path.dirname(tmpBase))
+ try:
+ # Compile the test
+ cmd, out, err, rc = test_cxx.compileLinkTwoSteps(
+ source_path, out=exec_path, object_file=object_path,
+ cwd=execDir)
+ compile_cmd = cmd
+ if rc != 0:
+ report = libcxx.util.makeReport(cmd, out, err, rc)
+ report += "Compilation failed unexpectedly!"
+ return lit.Test.FAIL, report
+ # Run the test
+ local_cwd = os.path.dirname(source_path)
+ env = None
+ if self.exec_env:
+ env = self.exec_env
+ # TODO: Only list actually needed files in file_deps.
+ # Right now we just mark all of the .dat files in the same
+ # directory as dependencies, but it's likely less than that. We
+ # should add a `// FILE-DEP: foo.dat` to each test to track this.
+ data_files = [os.path.join(local_cwd, f)
+ for f in os.listdir(local_cwd) if f.endswith('.dat')]
+ is_flaky = self._get_parser('FLAKY_TEST.', parsers).getValue()
+ max_retry = 3 if is_flaky else 1
+ for retry_count in range(max_retry):
+ cmd, out, err, rc = self.executor.run(exec_path, [exec_path],
+ local_cwd, data_files,
+ env)
+ if rc == 0:
+ res = lit.Test.PASS if retry_count == 0 else lit.Test.FLAKYPASS
+ return res, ''
+ elif rc != 0 and retry_count + 1 == max_retry:
+ report = libcxx.util.makeReport(cmd, out, err, rc)
+ report = "Compiled With: %s\n%s" % (compile_cmd, report)
+ report += "Compiled test failed unexpectedly!"
+ return lit.Test.FAIL, report
+
+ assert False # Unreachable
+ finally:
+ # Note that cleanup of exec_file happens in `_clean()`. If you
+ # override this, cleanup is your reponsibility.
+ libcxx.util.cleanFile(object_path)
+ self._clean(exec_path)
+
+ def _evaluate_fail_test(self, test, test_cxx, parsers):
+ source_path = test.getSourcePath()
+ # FIXME: lift this detection into LLVM/LIT.
+ with open(source_path, 'r') as f:
+ contents = f.read()
+ verify_tags = ['expected-note', 'expected-remark', 'expected-warning',
+ 'expected-error', 'expected-no-diagnostics']
+ use_verify = self.use_verify_for_fail and \
+ any([tag in contents for tag in verify_tags])
+ # FIXME(EricWF): GCC 5 does not evaluate static assertions that
+ # are dependant on a template parameter when '-fsyntax-only' is passed.
+ # This is fixed in GCC 6. However for now we only pass "-fsyntax-only"
+ # when using Clang.
+ if test_cxx.type != 'gcc':
+ test_cxx.flags += ['-fsyntax-only']
+ if use_verify:
+ test_cxx.useVerify()
+ test_cxx.useWarnings()
+ if '-Wuser-defined-warnings' in test_cxx.warning_flags:
+ test_cxx.warning_flags += ['-Wno-error=user-defined-warnings']
+
+ cmd, out, err, rc = test_cxx.compile(source_path, out=os.devnull)
+ expected_rc = 0 if use_verify else 1
+ if rc == expected_rc:
+ return lit.Test.PASS, ''
+ else:
+ report = libcxx.util.makeReport(cmd, out, err, rc)
+ report_msg = ('Expected compilation to fail!' if not use_verify else
+ 'Expected compilation using verify to pass!')
+ return lit.Test.FAIL, report + report_msg + '\n'