diff --git a/lldb/bindings/headers.swig b/lldb/bindings/headers.swig --- a/lldb/bindings/headers.swig +++ b/lldb/bindings/headers.swig @@ -47,6 +47,7 @@ #include "lldb/API/SBProcessInfo.h" #include "lldb/API/SBQueue.h" #include "lldb/API/SBQueueItem.h" +#include "lldb/API/SBReproducer.h" #include "lldb/API/SBSection.h" #include "lldb/API/SBSourceManager.h" #include "lldb/API/SBStream.h" diff --git a/lldb/bindings/interface/SBReproducer.i b/lldb/bindings/interface/SBReproducer.i new file mode 100644 --- /dev/null +++ b/lldb/bindings/interface/SBReproducer.i @@ -0,0 +1,17 @@ +//===-- SWIG Interface for SBReproducer--------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +namespace lldb { +class SBReproducer +{ + public: + static const char *Capture(const char *path); + static const char *PassiveReplay(const char *path); + static bool SetAutoGenerate(bool b); +}; +} diff --git a/lldb/bindings/interfaces.swig b/lldb/bindings/interfaces.swig --- a/lldb/bindings/interfaces.swig +++ b/lldb/bindings/interfaces.swig @@ -54,6 +54,7 @@ %include "./interface/SBProcessInfo.i" %include "./interface/SBQueue.i" %include "./interface/SBQueueItem.i" +%include "./interface/SBReproducer.i" %include "./interface/SBSection.i" %include "./interface/SBSourceManager.i" %include "./interface/SBStream.i" diff --git a/lldb/include/lldb/API/SBReproducer.h b/lldb/include/lldb/API/SBReproducer.h --- a/lldb/include/lldb/API/SBReproducer.h +++ b/lldb/include/lldb/API/SBReproducer.h @@ -22,6 +22,7 @@ static const char *Capture(const char *path); static const char *Replay(const char *path); static const char *Replay(const char *path, bool skip_version_check); + static const char *PassiveReplay(const char *path); static const char *GetPath(); static bool SetAutoGenerate(bool b); static bool Generate(); diff --git a/lldb/packages/Python/lldbsuite/test/configuration.py b/lldb/packages/Python/lldbsuite/test/configuration.py --- a/lldb/packages/Python/lldbsuite/test/configuration.py +++ b/lldb/packages/Python/lldbsuite/test/configuration.py @@ -87,7 +87,6 @@ # Set this flag if there is any session info dumped during the test run. sdir_has_content = False - # svn_info stores the output from 'svn info lldb.base.dir'. svn_info = '' @@ -124,6 +123,10 @@ results_formatter_options = None test_result = None +# Reproducers +capture_path = None +replay_path = None + # Test rerun configuration vars rerun_all_issues = False diff --git a/lldb/packages/Python/lldbsuite/test/decorators.py b/lldb/packages/Python/lldbsuite/test/decorators.py --- a/lldb/packages/Python/lldbsuite/test/decorators.py +++ b/lldb/packages/Python/lldbsuite/test/decorators.py @@ -854,3 +854,11 @@ return "ASAN unsupported" return None return skipTestIfFn(is_asan)(func) + +def skipIfReproducer(func): + """Skip this test if the environment is set up to run LLDB with reproducers.""" + def is_reproducer(): + if configuration.capture_path or configuration.replay_path: + return "reproducers unsupported" + return None + return skipTestIfFn(is_reproducer)(func) diff --git a/lldb/packages/Python/lldbsuite/test/dotest.py b/lldb/packages/Python/lldbsuite/test/dotest.py --- a/lldb/packages/Python/lldbsuite/test/dotest.py +++ b/lldb/packages/Python/lldbsuite/test/dotest.py @@ -429,6 +429,17 @@ configuration.results_formatter_name = ( "lldbsuite.test_event.formatter.results_formatter.ResultsFormatter") + # Reproducer arguments + if args.capture_path and args.replay_path: + logging.error('Cannot specify both a capture and a replay path.') + sys.exit(-1) + + if args.capture_path: + configuration.capture_path = args.capture_path + + if args.replay_path: + configuration.replay_path = args.replay_path + # rerun-related arguments configuration.rerun_all_issues = args.rerun_all_issues @@ -955,8 +966,19 @@ setupSysPath() import lldbconfig + if configuration.capture_path or configuration.replay_path: + lldbconfig.INITIALIZE = False import lldb + if configuration.capture_path: + lldb.SBReproducer.Capture(configuration.capture_path) + lldb.SBReproducer.SetAutoGenerate(True) + elif configuration.replay_path: + lldb.SBReproducer.PassiveReplay(configuration.replay_path) + + if not lldbconfig.INITIALIZE: + lldb.SBDebugger.Initialize() + # Use host platform by default. lldb.selected_platform = lldb.SBPlatform.GetHostPlatform() diff --git a/lldb/packages/Python/lldbsuite/test/dotest_args.py b/lldb/packages/Python/lldbsuite/test/dotest_args.py --- a/lldb/packages/Python/lldbsuite/test/dotest_args.py +++ b/lldb/packages/Python/lldbsuite/test/dotest_args.py @@ -196,6 +196,17 @@ metavar='platform-working-dir', help='The directory to use on the remote platform.') + # Reproducer options + group = parser.add_argument_group('Reproducer options') + group.add_argument( + '--capture-path', + metavar='reproducer path', + help='The reproducer capture path') + group.add_argument( + '--replay-path', + metavar='reproducer path', + help='The reproducer replay path') + # Test-suite behaviour group = parser.add_argument_group('Runtime behaviour options') X('-d', 'Suspend the process after launch to wait indefinitely for a debugger to attach') diff --git a/lldb/source/API/SBReproducer.cpp b/lldb/source/API/SBReproducer.cpp --- a/lldb/source/API/SBReproducer.cpp +++ b/lldb/source/API/SBReproducer.cpp @@ -124,6 +124,15 @@ return nullptr; } +const char *SBReproducer::PassiveReplay(const char *path) { + static std::string error; + if (auto e = Reproducer::Initialize(ReproducerMode::Replay, FileSpec(path))) { + error = llvm::toString(std::move(e)); + return error.c_str(); + } + return nullptr; +} + const char *SBReproducer::Replay(const char *path) { return SBReproducer::Replay(path, false); } diff --git a/lldb/test/API/functionalities/reproducers/attach/TestReproducerAttach.py b/lldb/test/API/functionalities/reproducers/attach/TestReproducerAttach.py --- a/lldb/test/API/functionalities/reproducers/attach/TestReproducerAttach.py +++ b/lldb/test/API/functionalities/reproducers/attach/TestReproducerAttach.py @@ -20,6 +20,7 @@ @skipIfWindows @skipIfRemote @skipIfiOSSimulator + @skipIfReproducer def test_reproducer_attach(self): """Test thread creation after process attach.""" exe = '%s_%d' % (self.testMethodName, os.getpid()) diff --git a/lldb/test/API/lit.cfg.py b/lldb/test/API/lit.cfg.py --- a/lldb/test/API/lit.cfg.py +++ b/lldb/test/API/lit.cfg.py @@ -60,6 +60,17 @@ 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. +lldb_repro_mode = lit_config.params.get('lldb-run-with-repro', None) +if lldb_repro_mode: + lit_config.note("Running API tests in {} mode.".format(lldb_repro_mode)) + if lldb_repro_mode == 'capture': + config.available_features.add('lldb-repro-capture') + elif lldb_repro_mode == 'replay': + config.available_features.add('lldb-repro-replay') + # Clean the module caches in the test build directory. This is necessary in an # incremental build whenever clang changes underneath, so doing it once per # lit.py invocation is close enough. diff --git a/lldb/test/API/lldbtest.py b/lldb/test/API/lldbtest.py --- a/lldb/test/API/lldbtest.py +++ b/lldb/test/API/lldbtest.py @@ -1,6 +1,6 @@ from __future__ import absolute_import import os - +import tempfile import subprocess import sys @@ -67,14 +67,15 @@ # python exe as the first parameter of the command. cmd = [sys.executable] + self.dotest_cmd + [testPath, '-p', testFile] + builddir = getBuildDir(cmd) + mkdir_p(builddir) + # The macOS system integrity protection (SIP) doesn't allow injecting # libraries into system binaries, but this can be worked around by # copying the binary into a different location. if 'DYLD_INSERT_LIBRARIES' in test.config.environment and \ (sys.executable.startswith('/System/') or \ sys.executable.startswith('/usr/bin/')): - builddir = getBuildDir(cmd) - mkdir_p(builddir) copied_python = os.path.join(builddir, 'copied-system-python') if not os.path.isfile(copied_python): import shutil, subprocess @@ -86,6 +87,16 @@ shutil.copy(python, copied_python) cmd[0] = copied_python + if 'lldb-repro-capture' in test.config.available_features or \ + 'lldb-repro-replay' in test.config.available_features: + reproducer_root = os.path.join(builddir, 'reproducers') + mkdir_p(reproducer_root) + reproducer_path = os.path.join(reproducer_root, testFile) + if 'lldb-repro-capture' in test.config.available_features: + cmd.extend(['--capture-path', reproducer_path]) + else: + cmd.extend(['--replay-path', reproducer_path]) + timeoutInfo = None try: out, err, exitCode = lit.util.executeCommand(