Index: test/libcxx/compiler.py =================================================================== --- test/libcxx/compiler.py +++ test/libcxx/compiler.py @@ -1,9 +1,10 @@ import lit.util - +import libcxx.util class CXXCompiler(object): - def __init__(self, path, flags=[], compile_flags=[], link_flags=[], use_ccache=False): + def __init__(self, path, flags=[], compile_flags=[], link_flags=[], + use_ccache=False): self.path = path self.flags = list(flags) self.compile_flags = list(compile_flags) @@ -91,6 +92,29 @@ out, err, rc = lit.util.executeCommand(cmd, env=env, cwd=cwd) return cmd, out, err, rc + def _compileLinkTwoSteps(self, infile, object_file, out=None, flags=[], + env=None, cwd=None): + cmd, output, err, rc = self.compile(infile, object_file, + flags=flags, env=env, cwd=cwd) + if rc != 0: + return cmd, output, err, rc + return self.link(object_file, out=out, flags=flags, env=env, + cwd=cwd) + + def compileLinkTwoSteps(self, infile, out=None, object_file=None, flags=[], + env=None, cwd=None): + if not isinstance(infile, str): + raise TypeError('This function only accepts a single input file') + # Create, use and delete a temporary object file if none is given. + if object_file is None: + with libcxx.util.guardedTemporaryFile(suffix='.o') as object_file: + return self._compileLinkTwoSteps(infile, object_file, out, + flags, env, cwd) + # Othewise compile using the given object file. + else: + return self._compileLinkTwoSteps(infile, object_file, out, flags, + env, cwd) + def dumpMacros(self, infiles=None, flags=[], env=None, cwd=None): if infiles is None: infiles = '/dev/null' Index: test/libcxx/double_include.sh.cpp =================================================================== --- /dev/null +++ test/libcxx/double_include.sh.cpp @@ -0,0 +1,111 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// 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. +// +//===----------------------------------------------------------------------===// + +// Test that we can include each header in two TU's and link them together. + +// RUN: %cxx -c %s -o %t.first.o %flags %compile_flags +// RUN: %cxx -c %s -o %t.second.o -DWITH_MAIN %flags %compile_flags +// RUN: %cxx -o %t.exe %t.first.o %t.second.o %flags %link_flags +// RUN: %run + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef _LIBCPP_HAS_NO_THREADS +#include +#include +#include +#include +#include +#endif + +#if defined(WITH_MAIN) +int main() {} +#endif Index: test/libcxx/test/config.py =================================================================== --- test/libcxx/test/config.py +++ test/libcxx/test/config.py @@ -12,6 +12,32 @@ from libcxx.compiler import CXXCompiler +def loadSiteConfig(lit_config, config, param_name, env_name): + # We haven't loaded the site specific configuration (the user is + # probably trying to run on a test file directly, and either the site + # configuration hasn't been created by the build system, or we are in an + # out-of-tree build situation). + site_cfg = lit_config.params.get(param_name, + os.environ.get(env_name)) + if not site_cfg: + lit_config.warning('No site specific configuration file found!' + ' Running the tests in the default configuration.') + elif not os.path.isfile(site_cfg): + lit_config.fatal( + "Specified site configuration file does not exist: '%s'" % + site_cfg) + else: + lit_config.note('using site specific configuration at %s' % site_cfg) + ld_fn = lit_config.load_config + # Null out the load_config function so that lit.site.cfg doesn't + # recursively load a config even if it tries. + # TODO: This is one hell of a hack. Fix it. + def prevent_reload_fn(*args, **kwargs): + pass + lit_config.load_config = prevent_reload_fn + ld_fn(config, site_cfg) + lit_config.load_config = ld_fn + class Configuration(object): # pylint: disable=redefined-outer-name def __init__(self, lit_config, config): @@ -25,6 +51,7 @@ self.use_system_cxx_lib = False self.use_clang_verify = False self.long_tests = None + self.execute_external = False if platform.system() not in ('Darwin', 'FreeBSD', 'Linux'): self.lit_config.fatal("unrecognized system") @@ -56,11 +83,13 @@ self.configure_cxx_library_root() self.configure_use_system_cxx_lib() self.configure_use_clang_verify() + self.configure_execute_external() self.configure_ccache() self.configure_env() self.configure_compile_flags() self.configure_link_flags() self.configure_sanitizer() + self.configure_substitutions() self.configure_features() def print_config_info(self): @@ -78,6 +107,7 @@ return LibcxxTestFormat( self.cxx, self.use_clang_verify, + self.execute_external, exec_env=self.env) def configure_cxx(self): @@ -139,6 +169,21 @@ self.lit_config.note( "inferred use_clang_verify as: %r" % self.use_clang_verify) + def configure_execute_external(self): + # Choose between lit's internal shell pipeline runner and a real shell. + # If LIT_USE_INTERNAL_SHELL is in the environment, we use that as the + # default value. Otherwise we default to internal on Windows and + # external elsewhere, as bash on Windows is usually very slow. + use_lit_shell_default = os.environ.get('LIT_USE_INTERNAL_SHELL') + if use_lit_shell_default is not None: + use_lit_shell_default = (use_lit_shell_default != '0') + else: + use_lit_shell_default = sys.platform in ['win32'] + # Check for the command line parameter using the default value if it is + # not present. + use_lit_shell = self.get_lit_bool('use_lit_shell', use_lit_shell_default) + self.execute_external = not use_lit_shell + def configure_ccache(self): use_ccache = self.get_lit_bool('use_ccache', False) if use_ccache: @@ -441,6 +486,45 @@ self.lit_config.fatal('unsupported value for ' 'use_sanitizer: {0}'.format(san)) + def configure_substitutions(self): + sub = self.config.substitutions + # Configure compiler substitions + sub.append( ('%cxx', self.cxx.path) ) + # Configure flags substitutions + flags_str = ' '.join(self.cxx.flags) + compile_flags_str = ' '.join(self.cxx.compile_flags) + link_flags_str = ' '.join(self.cxx.link_flags) + all_flags = '%s %s %s' % (flags_str, compile_flags_str, link_flags_str) + sub.append( ('%flags' , flags_str ) ) + sub.append( ('%compile_flags', compile_flags_str) ) + sub.append( ('%link_flags' , link_flags_str ) ) + sub.append( ('%all_flags' , all_flags ) ) + # Add compile and link shortcuts + compile_str = (self.cxx.path + ' -o %t.o %s -c ' + flags_str + + compile_flags_str) + link_str = (self.cxx.path + ' -o %t.exe %t.o ' + flags_str + + link_flags_str) + assert type(link_str) is str + build_str = self.cxx.path + ' -o %t.exe %s ' + all_flags + sub.append( ('%compile', compile_str) ) + sub.append( ('%link' , link_str ) ) + sub.append( ('%build' , build_str ) ) + # Configure exec prefix substitutions. + exec_env_str = 'env ' if len(self.env) != 0 else '' + for k,v in self.env.items(): + exec_env_str += ' %s=%s' % (k, v) + # Configure run env substitution. + exec_str = '' + if self.lit_config.useValgrind: + exec_str = ' '.join(self.lit_config.valgrindArgs) + exec_env_str + sub.append( ('%exec', exec_str) ) + # Configure run shortcut + sub.append( ('%run', exec_str + ' %t.exe') ) + # Configure not program substitions + python_exe = sys.executable + not_py = os.path.join(self.libcxx_src_root, 'utils', 'not', 'not.py') + sub.append( ('not', python_exe + ' ' + not_py) ) + def configure_triple(self): # Get or infer the target triple. self.config.target_triple = self.get_lit_conf('target_triple') Index: test/libcxx/test/format.py =================================================================== --- test/libcxx/test/format.py +++ test/libcxx/test/format.py @@ -1,12 +1,14 @@ import errno import os -import tempfile import time -import lit.formats # pylint: disable=import-error +import lit.Test # pylint: disable=import-error +import lit.TestRunner # pylint: disable=import-error +import lit.util # pylint: disable=import-error +import libcxx.util -class LibcxxTestFormat(lit.formats.FileBasedTest): +class LibcxxTestFormat(object): """ Custom test format handler for use with the test format use by libc++. @@ -14,13 +16,31 @@ 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, exec_env): + def __init__(self, cxx, use_verify_for_fail, execute_external, exec_env): self.cxx = cxx self.use_verify_for_fail = use_verify_for_fail + self.execute_external = execute_external self.exec_env = dict(exec_env) + # 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: @@ -31,95 +51,36 @@ time.sleep(0.1) def _execute(self, test, lit_config): - # Extract test metadata from the test file. - requires = [] - unsupported = [] - use_verify = False - with open(test.getSourcePath()) as f: - for ln in f: - if 'XFAIL:' in ln: - items = ln[ln.index('XFAIL:') + 6:].split(',') - test.xfails.extend([s.strip() for s in items]) - elif 'REQUIRES:' in ln: - items = ln[ln.index('REQUIRES:') + 9:].split(',') - requires.extend([s.strip() for s in items]) - elif 'UNSUPPORTED:' in ln: - items = ln[ln.index('UNSUPPORTED:') + 12:].split(',') - unsupported.extend([s.strip() for s in items]) - elif 'USE_VERIFY' in ln and self.use_verify_for_fail: - use_verify = True - elif not ln.strip().startswith("//") and ln.strip(): - # Stop at the first non-empty line that is not a C++ - # comment. - break - - # Check that we have the required features. - # - # FIXME: For now, this is cribbed from lit.TestRunner, to avoid - # introducing a dependency there. What we more ideally would like to do - # is lift the "requires" handling to be a core lit framework feature. - missing_required_features = [ - f for f in requires - if f not in test.config.available_features - ] - if missing_required_features: - return (lit.Test.UNSUPPORTED, - "Test requires the following features: %s" % ( - ', '.join(missing_required_features),)) - - unsupported_features = [f for f in unsupported - if f in test.config.available_features] - if unsupported_features: - return (lit.Test.UNSUPPORTED, - "Test is unsupported with the following features: %s" % ( - ', '.join(unsupported_features),)) - - # Evaluate the test. - return self._evaluate_test(test, use_verify, lit_config) - - def _make_report(self, cmd, out, err, rc): # pylint: disable=no-self-use - report = "Command: %s\n" % cmd - report += "Exit Code: %d\n" % rc - if out: - report += "Standard Output:\n--\n%s--\n" % out - if err: - report += "Standard Error:\n--\n%s--\n" % err - report += '\n' - return cmd, report, rc - - def _compile(self, output_path, source_path, use_verify=False): - extra_flags = [] - if use_verify: - extra_flags += ['-Xclang', '-verify'] - return self.cxx.compile(source_path, out=output_path, flags=extra_flags) - - def _link(self, exec_path, object_path): - return self.cxx.link(object_path, out=exec_path) - - def _compile_and_link(self, exec_path, source_path): - object_file = tempfile.NamedTemporaryFile(suffix=".o", delete=False) - object_path = object_file.name - object_file.close() - try: - cmd, out, err, rc = self.cxx.compile(source_path, out=object_path) - if rc != 0: - return cmd, out, err, rc - return self.cxx.link(object_path, out=exec_path) - finally: - try: - os.remove(object_path) - except OSError: - pass - - def _build(self, exec_path, source_path, compile_only=False, - use_verify=False): - if compile_only: - cmd, out, err, rc = self._compile(exec_path, source_path, - use_verify) + name = test.path_in_suite[-1] + is_sh_test = name.endswith('.sh.cpp') + is_pass_test = name.endswith('.pass.cpp') + is_fail_test = name.endswith('.fail.cpp') + + res = lit.TestRunner.parseIntegratedTestScript(test, + require_script=is_sh_test) + # Check if a result for the test was returned. If so return that result. + if isinstance(res, lit.Test.Result): + return res + if lit_config.noExecute: + return lit.Test.Result(lit.Test.PASS) + # res is not an instance of lit.test.Result. Expand res into its parts. + script, tmpBase, execDir = res + # 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) + + # Dispatch the test based on its suffix. + if is_sh_test: + return lit.TestRunner._runShTest(test, lit_config, + self.execute_external, script, + tmpBase, execDir) + elif is_fail_test: + return self._evaluate_fail_test(test, tmpBase, execDir, lit_config) + elif is_pass_test: + return self._evaluate_pass_test(test, tmpBase, execDir, lit_config) else: - assert not use_verify - cmd, out, err, rc = self._compile_and_link(exec_path, source_path) - return self._make_report(cmd, out, err, rc) + # No other test type is supported + assert False def _clean(self, exec_path): # pylint: disable=no-self-use try: @@ -127,58 +88,59 @@ except OSError: pass - def _run(self, exec_path, lit_config, in_dir=None): - cmd = [] - if self.exec_env: - cmd.append('env') - cmd.extend('%s=%s' % (name, value) - for name, value in self.exec_env.items()) - cmd.append(exec_path) - if lit_config.useValgrind: - cmd = lit_config.valgrindArgs + cmd - out, err, rc = lit.util.executeCommand(cmd, cwd=in_dir) - return self._make_report(cmd, out, err, rc) - - def _evaluate_test(self, test, use_verify, lit_config): - name = test.path_in_suite[-1] + def _evaluate_pass_test(self, test, tmpBase, execDir, lit_config): source_path = test.getSourcePath() - source_dir = os.path.dirname(source_path) + exec_path = tmpBase + '.exe' + object_path = tmpBase + '.o' + # Create the output directory if it does not already exist. + lit.util.mkdir_p(os.path.dirname(tmpBase)) + try: + # Compile the test + cmd, out, err, rc = self.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 + cmd = [] + if self.exec_env: + cmd.append('env') + cmd.append('%s=%s' % (k,v) for k,v in self.exec_env.items()) + if lit_config.useValgrind: + cmd = lit_config.valgrindArgs + cmd + cmd.append(exec_path) + out, err, rc = lit.util.executeCommand(cmd, + cwd=os.path.dirname(source_path)) + if rc != 0: + 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 + return lit.Test.PASS, '' + finally: + # Note that cleanup of exec_file happens in `_clean()`. If you + # override this, cleanup is your reponsibility. + self._clean(object_path) + self._clean(exec_path) - # Check what kind of test this is. - assert name.endswith('.pass.cpp') or name.endswith('.fail.cpp') - expected_compile_fail = name.endswith('.fail.cpp') - # If this is a compile (failure) test, build it and check for failure. - if expected_compile_fail: - cmd, report, rc = self._build('/dev/null', source_path, - compile_only=True, - use_verify=use_verify) - expected_rc = 0 if use_verify else 1 - if rc == expected_rc: - return lit.Test.PASS, "" - else: - return (lit.Test.FAIL, - report + 'Expected compilation to fail!\n') + def _evaluate_fail_test(self, test, tmpBase, execDir, lit_config): + source_path = test.getSourcePath() + with open(source_path, 'r') as f: + contents = f.read() + use_verify = 'USE_VERIFY' in contents and self.use_verify_for_fail + extra_flags = [] + if use_verify: + extra_flags += ['-Xclang', '-verify'] + cmd, out, err, rc = self.cxx.compile(source_path, out='/dev/null', + flags=extra_flags) + expected_rc = 0 if use_verify else 1 + if rc == expected_rc: + return lit.Test.PASS, '' else: - exec_file = tempfile.NamedTemporaryFile(suffix="exe", delete=False) - exec_path = exec_file.name - exec_file.close() - - try: - cmd, report, rc = self._build(exec_path, source_path) - compile_cmd = cmd - if rc != 0: - report += "Compilation failed unexpectedly!" - return lit.Test.FAIL, report - - cmd, report, rc = self._run(exec_path, lit_config, - source_dir) - if rc != 0: - report = "Compiled With: %s\n%s" % (compile_cmd, report) - report += "Compiled test failed unexpectedly!" - return lit.Test.FAIL, report - finally: - # Note that cleanup of exec_file happens in `_clean()`. If you - # override this, cleanup is your reponsibility. - self._clean(exec_path) - return lit.Test.PASS, "" + report = libcxx.util.makeReport(cmd, out, err, rc) + return (lit.Test.FAIL, + report + 'Expected compilation to fail!\n') Index: test/libcxx/util.py =================================================================== --- /dev/null +++ test/libcxx/util.py @@ -0,0 +1,25 @@ +from contextlib import contextmanager +import os +import tempfile + +import lit.util # pylint: disable=import-error + +@contextmanager +def guardedTemporaryFile(suffix='', prefix='', dir=None): + handle, name = tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir) + os.close(handle) + yield name + try: + os.remove(name) + except OSError: + pass + +def makeReport(cmd, out, err, rc): + report = "Command: %s\n" % cmd + report += "Exit Code: %d\n" % rc + if out: + report += "Standard Output:\n--\n%s--\n" % out + if err: + report += "Standard Error:\n--\n%s--\n" % err + report += '\n' + return report Index: test/lit.cfg =================================================================== --- test/lit.cfg +++ test/lit.cfg @@ -15,37 +15,27 @@ config.name = 'libc++' # suffixes: A list of file extensions to treat as test files. -config.suffixes = ['.cpp'] +config.suffixes = ['.pass.cpp', '.fail.cpp', '.sh.cpp'] # test_source_root: The root path where tests are located. config.test_source_root = os.path.dirname(__file__) # Infer the test_exec_root from the libcxx_object root. -libcxx_obj_root = getattr(config, 'libcxx_obj_root', None) -if libcxx_obj_root is not None: - config.test_exec_root = os.path.join(libcxx_obj_root, 'test') +obj_root = getattr(config, 'libcxx_obj_root', None) # Check that the test exec root is known. -if config.test_exec_root is None: - # Otherwise, we haven't loaded the site specific configuration (the user is - # probably trying to run on a test file directly, and either the site - # configuration hasn't been created by the build system, or we are in an - # out-of-tree build situation). - site_cfg = lit_config.params.get('libcxx_site_config', - os.environ.get('LIBCXX_SITE_CONFIG')) - if not site_cfg: - lit_config.warning('No site specific configuration file found!' - ' Running the tests in the default configuration.') - # TODO: Set test_exec_root to a temporary directory where output files - # can be placed. This is needed for ShTest. - elif not os.path.isfile(site_cfg): - lit_config.fatal( - "Specified site configuration file does not exist: '%s'" % - site_cfg) - else: - lit_config.note('using site specific configuration at %s' % site_cfg) - lit_config.load_config(config, site_cfg) - raise SystemExit() +if obj_root is None: + import libcxx.test.config + libcxx.test.config.loadSiteConfig(lit_config, config, 'libcxx_site_config', + 'LIBCXX_SITE_CONFIG') + obj_root = getattr(config, 'libcxx_obj_root', None) + if obj_root is None: + import tempfile + obj_root = tempfile.mkdtemp(prefix='libcxx-testsuite-') + lit_config.warning('Creating temporary directory for object root: %s' % + obj_root) + +config.test_exec_root = os.path.join(obj_root, 'test') cfg_variant = getattr(config, 'configuration_variant', 'libcxx') if cfg_variant: Index: utils/not/not.py =================================================================== --- /dev/null +++ utils/not/not.py @@ -0,0 +1,86 @@ +import os +import platform +import signal +import subprocess +import sys + +# not.py is a utility for inverting the return code of commands. It acts similar +# to llvm/utils/not. +# ex: python /path/to/not.py echo hello +# echo $? // (prints 1) + + +# NOTE: this is stolen from llvm/utils/lit/lit/util.py because python probably +# won't be able to find the lit import. +def which(command, paths = None): + """which(command, [paths]) - Look up the given command in the paths string + (or the PATH environment variable, if unspecified).""" + + if paths is None: + paths = os.environ.get('PATH','') + + # Check for absolute match first. + if os.path.isfile(command): + return command + + # Would be nice if Python had a lib function for this. + if not paths: + paths = os.defpath + + # Get suffixes to search. + # On Cygwin, 'PATHEXT' may exist but it should not be used. + if os.pathsep == ';': + pathext = os.environ.get('PATHEXT', '').split(';') + else: + pathext = [''] + + # Search the paths... + for path in paths.split(os.pathsep): + for ext in pathext: + p = os.path.join(path, command + ext) + if os.path.exists(p): + return p + + return None + +# Close extra file handles on UNIX (on Windows this cannot be done while +# also redirecting input). +kUseCloseFDs = not (platform.system() == 'Windows') +def executeCommand(command, cwd=None, env=None): + p = subprocess.Popen(command, cwd=cwd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=env, close_fds=kUseCloseFDs) + out,err = p.communicate() + exitCode = p.wait() + + # Detect Ctrl-C in subprocess. + if exitCode == -signal.SIGINT: + raise KeyboardInterrupt + + return out, err, exitCode + +def main(): + argv = sys.argv + del argv[0] + if len(argv) > 0 and argv[0] == '--crash': + del argv[0] + expectCrash = True + else: + expectCrash = False + if len(argv) == 0: + return 1 + prog = which(argv[0]) + if prog is None: + sys.stderr.write('Failed to find program %s' % argv[0]) + return 1 + out, err, rc = executeCommand(argv) + if rc == 0 and not expectCrash: + return 1 + else: + return 0 + +if __name__ == '__main__': + ret = main() + exit(ret) Index: www/lit_usage.html =================================================================== --- www/lit_usage.html +++ www/lit_usage.html @@ -132,6 +132,16 @@

+

use_lit_shell=<bool>

+
+Enable or disable the use of LIT's internal shell in ShTests. If the enviroment +variable LIT_USE_INTERNAL_SHELL is present then that is used as the +default value. Otherwise the default value is True on Windows and +False on every other platform. +
+

+ +

no_default_flags=<bool>

Default: False