Index: lldb/CMakeLists.txt =================================================================== --- lldb/CMakeLists.txt +++ lldb/CMakeLists.txt @@ -94,6 +94,7 @@ add_subdirectory(unittests) add_subdirectory(utils/lit-cpuid) add_subdirectory(utils/lldb-dotest) + add_subdirectory(utils/lldb-repro) endif() if (LLDB_ENABLE_PYTHON) Index: lldb/test/Shell/Reproducer/lit.local.cfg =================================================================== --- lldb/test/Shell/Reproducer/lit.local.cfg +++ lldb/test/Shell/Reproducer/lit.local.cfg @@ -2,5 +2,11 @@ if 'LLVM_DISABLE_CRASH_REPORT' in config.environment: del config.environment['LLVM_DISABLE_CRASH_REPORT'] +# Unset the always capture environment override. if 'LLDB_CAPTURE_REPRODUCER' in config.environment: del config.environment['LLDB_CAPTURE_REPRODUCER'] + +# Running the reproducer tests under the lldb-repro utility doesn't make sense. +if 'LLDB_REPRO_UTIL_CAPTURE' in config.environment or \ + 'LLDB_REPRO_UTIL_REPLAY' in config.environment: + config.unsupported = True Index: lldb/test/Shell/helper/toolchain.py =================================================================== --- lldb/test/Shell/helper/toolchain.py +++ lldb/test/Shell/helper/toolchain.py @@ -9,6 +9,11 @@ from lit.llvm.subst import FindTool from lit.llvm.subst import ToolSubst + +def _lldb_init(config): + return os.path.join(config.test_exec_root, 'Shell', 'lit-lldb-init') + + def use_lldb_substitutions(config): # Set up substitutions for primary tools. These tools must come from config.lldb_tools_dir # which is basically the build output directory. We do not want to find these in path or @@ -29,7 +34,7 @@ if config.llvm_libs_dir: build_script_args.append('--libs-dir={0}'.format(config.llvm_libs_dir)) - lldb_init = os.path.join(config.test_exec_root, 'Shell', 'lit-lldb-init') + lldb_init = _lldb_init(config) primary_tools = [ ToolSubst('%lldb', @@ -135,3 +140,15 @@ 'llvm-objcopy', 'lli'] additional_tool_dirs += [config.lldb_tools_dir, config.llvm_tools_dir] llvm_config.add_tool_substitutions(support_tools, additional_tool_dirs) + +def use_lldb_repro_substitutions(config): + lldb_init = _lldb_init(config) + substitutions = [ + ToolSubst('%lldb', + command=FindTool('lldb-repro'), + extra_args=['--no-lldbinit', '-S', lldb_init]), + ToolSubst('%lldb-init', + command=FindTool('lldb-repro'), + extra_args=['-S', lldb_init]), + ] + llvm_config.add_tool_substitutions(substitutions) Index: lldb/test/Shell/lit.cfg.py =================================================================== --- lldb/test/Shell/lit.cfg.py +++ lldb/test/Shell/lit.cfg.py @@ -38,16 +38,27 @@ # test_exec_root: The root path where tests should be run. config.test_exec_root = os.path.join(config.lldb_obj_root, 'test') -# Propagate LLDB_CAPTURE_REPRODUCER +# Propagate reproducer environment vars. if 'LLDB_CAPTURE_REPRODUCER' in os.environ: config.environment['LLDB_CAPTURE_REPRODUCER'] = os.environ[ 'LLDB_CAPTURE_REPRODUCER'] +# Support running the test suite under the lldb-repro wrapper. This makes it +# possible to capture a test suite run and then rerun all the test from the +# just captured reproducer. +run_under_lldb_repro = lit_config.params.get('lldb-run-with-repro', '').lower() +if run_under_lldb_repro == "capture": + config.environment['LLDB_REPRO_UTIL_CAPTURE'] = '1' + toolchain.use_lldb_repro_substitutions(config) +elif run_under_lldb_repro == "replay": + lit_config.note("Running Shell test from reproducer replay") + config.environment['LLDB_REPRO_UTIL_REPLAY'] = '1' + toolchain.use_lldb_repro_substitutions(config) + llvm_config.use_default_substitutions() toolchain.use_lldb_substitutions(config) toolchain.use_support_substitutions(config) - if re.match(r'^arm(hf.*-linux)|(.*-linux-gnuabihf)', config.target_triple): config.available_features.add("armhf-linux") Index: lldb/utils/lldb-repro/CMakeLists.txt =================================================================== --- /dev/null +++ lldb/utils/lldb-repro/CMakeLists.txt @@ -0,0 +1,4 @@ +add_custom_target(lldb-repro) +add_dependencies(lldb-repro lldb-test-deps) +set_target_properties(lldb-repro PROPERTIES FOLDER "lldb utils") +configure_file(lldb-repro.py ${LLVM_RUNTIME_OUTPUT_INTDIR}/lldb-repro COPYONLY) Index: lldb/utils/lldb-repro/lldb-repro.py =================================================================== --- /dev/null +++ lldb/utils/lldb-repro/lldb-repro.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +import sys +import os +import tempfile +import subprocess + + +def main(): + # Compute a hash based on the input arguments and the current working + # directory. + args = ' '.join(sys.argv[1:]) + cwd = os.getcwd() + input_hash = str(hash((cwd, args))) + + # Use the hash to "uniquely" identify a reproducer path. + reproducer_path = os.path.join(tempfile.gettempdir(), input_hash) + + # Create a new lldb invocation with capture or replay enabled. + lldb = os.path.join(os.path.dirname(sys.argv[0]), 'lldb') + new_args = [lldb] + if 'LLDB_REPRO_UTIL_REPLAY' in os.environ: + new_args.extend(['--replay', reproducer_path]) + elif 'LLDB_REPRO_UTIL_CAPTURE' in os.environ: + new_args.extend([ + '--capture', '--capture-path', reproducer_path, + '--reproducer-auto-generate' + ]) + new_args.extend(sys.argv[1:]) + else: + print("error: enable capture or replay by setting the environment " + "variables LLDB_REPRO_UTIL_CAPTURE and LLDB_REPRO_UTIL_REPLAY " + "respectively") + return 1 + + return subprocess.call(new_args) + + +# lldb-repro is a utility to transparently capture and replay debugger sessions +# through the command line driver. Its used to test the reproducers by running +# the test suite twice. +# +# During the first run, with LLDB_REPRO_UTIL_CAPTURE set, it captures a +# reproducer for every lldb invocation and saves it to a well-know location +# derived from the arguments and current working directory. +# +# During the second run, with LLDB_REPRO_UTIL_CAPTURE set, the test suite is +# run again but this time every invocation of lldb replays the previously +# recorded session. +if __name__ == '__main__': + exit(main())