diff --git a/build.sh b/build.sh index 7b5a8c50c..bafaa8895 100755 --- a/build.sh +++ b/build.sh @@ -28,6 +28,7 @@ Usage: build.sh [options...] --configure Run cmake stage (aka configure stage). --verbose | -v Run verbose build. --debug Build for debug version. + --debug-on-crash Enable interactive gdb debugging on a valkey-server crash for oss integration tests (single test mode only). --clean Clean the current build configuration (debug or release). --format Applies clang-format. (Run in dev container environment to ensure correct clang-format version) --run-tests Run all tests. Optionally, pass a test name to run: "--run-tests=". @@ -151,6 +152,13 @@ while [ $# -gt 0 ]; do print_usage exit 0 ;; + --debug-on-crash) + export DEBUG_ON_CRASH="yes" + BUILD_CONFIG="debug" # Force debug build for better debugging + shift || true + echo "Debug-on-crash enabled - will drop into gdb on valkey-server crash" + echo "Automatically enabling debug build for better debugging symbols" + ;; *) echo "Unknown argument: ${arg}" print_usage @@ -396,8 +404,21 @@ elif [[ "${INTEGRATION_TEST}" == "yes" ]]; then fi export TEST_PATTERN=${TEST_PATTERN} export INTEG_RETRIES=${INTEG_RETRIES} + params="" + if [[ "${BUILD_CONFIG}" == "debug" ]]; then + params="${params} --debug" + fi + if [[ "${SAN_BUILD}" == "address" ]]; then + params="${params} --asan" + fi + if [[ "${SAN_BUILD}" == "thread" ]]; then + params="${params} --tsan" + fi + if [[ "${DEBUG_ON_CRASH}" == "yes" ]]; then + params="${params} --debug-on-crash" + fi # Run will run ASan or normal tests based on the environment variable SAN_BUILD - ./run.sh + ./run.sh ${params} popd >/dev/null fi diff --git a/integration/run.sh b/integration/run.sh index a6dbee733..c6ecbfb7f 100755 --- a/integration/run.sh +++ b/integration/run.sh @@ -51,6 +51,11 @@ while [ $# -gt 0 ]; do print_usage exit 0 ;; + --debug-on-crash) + shift || true + export DEBUG_ON_CRASH="yes" + LOG_INFO "Debug-on-crash enabled" + ;; *) print_usage exit 1 diff --git a/integration/valkey_search_test_case.py b/integration/valkey_search_test_case.py index 99dc02391..ac8f3cdc5 100644 --- a/integration/valkey_search_test_case.py +++ b/integration/valkey_search_test_case.py @@ -14,6 +14,7 @@ import string import logging import shutil +import subprocess LOGS_DIR = "/tmp/valkey-test-framework-files" @@ -21,6 +22,36 @@ LOGS_DIR = os.environ["LOGS_DIR"] +class DebugValkeyServerHandle(ValkeyServerHandle): + """ValkeyServerHandle that wraps valkey-server with gdb for crash debugging""" + + def start(self, wait_for_ping=True, connect_client=True): + if self.server: + raise RuntimeError("Server already started") + + # Build server command + server_args = [self.valkey_path] + if self.conf_file: + server_args.append(self.conf_file) + for k, v in self.args.items(): + server_args.extend([f"--{k.replace('_', '-')}", str(v)]) + + # Wrap with gdb if debug-on-crash enabled + if os.environ.get("DEBUG_ON_CRASH") == "yes": + cmd = ["gdb", "--ex", "run", "--args"] + server_args + else: + cmd = server_args + + self.server = subprocess.Popen(cmd, cwd=self.cwd) + # Standard connection handling + if connect_client: + self.wait_for_ready_to_accept_connections() + if wait_for_ping: + self.connect() + + return self.client + + class Node: """This class represents a valkey server instance, regardless of its role""" @@ -148,6 +179,12 @@ def get_config_file_lines(self, testdir, port) -> List[str]: for example usage.""" raise NotImplementedError + def get_valkey_handle(self): + """Return debug-enabled valkey handle when debug-on-crash is enabled""" + if os.environ.get("DEBUG_ON_CRASH") == "yes": + return DebugValkeyServerHandle + return ValkeyServerHandle + def start_server( self, port: int,