Files
scylladb/test/scylla_gdb/conftest.py
Jakub Smolar e978cc2a80 scylla_gdb: use persistent GDB - decrease test execution time
This commit replaces the previous approach of running pytest inside
GDB’s Python interpreter. Instead, tests are executed by driving a
persistent GDB process externally using pexpect.

- pexpect: Python library for controlling interactive programs
  (used here to send commands to GDB and capture its output)
- persistent GDB: keep one GDB session alive across multiple tests
  instead of starting a new process for each test

Tests can now be executed via `./test.py gdb` or with
`pytest test/scylla_gdb`. This improves performance and
makes failures easier to debug since pytest no longer runs
hidden inside GDB subprocesses.

Closes scylladb/scylladb#24804
2026-01-29 10:01:39 +02:00

93 lines
3.0 KiB
Python

# Copyright 2022-present ScyllaDB
#
# SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
"""Conftest for Scylla GDB tests"""
import logging
import os
import pexpect
import pytest
from test.pylib.suite.python import PythonTest
from test.pylib.util import LogPrefixAdapter
@pytest.fixture(scope="module")
async def scylla_server(testpy_test: PythonTest | None):
"""Return a running Scylla server instance from the active test cluster."""
logger_prefix = testpy_test.mode + "/" + testpy_test.uname
logger = LogPrefixAdapter(
logging.getLogger(logger_prefix), {"prefix": logger_prefix}
)
scylla_cluster = await testpy_test.suite.clusters.get(logger)
scylla_server = next(iter(scylla_cluster.running.values()))
yield scylla_server
await testpy_test.suite.clusters.put(scylla_cluster, is_dirty=True)
@pytest.fixture(scope="module")
def gdb_process(scylla_server, request):
"""Spawn an interactive GDB attached to the Scylla process.
Loads `scylla-gdb.py` and test helpers (`gdb_utils.py`) so tests can run GDB/Python helpers
against the live Scylla process.
"""
scylla_gdb_py = os.path.join(request.fspath.dirname, "..", "..", "scylla-gdb.py")
script_py = os.path.join(request.fspath.dirname, "gdb_utils.py")
cmd = (
f"gdb -q "
"--nx "
"-iex 'set confirm off' "
"-iex 'set pagination off' "
f"-se {scylla_server.exe} "
f"-p {scylla_server.cmd.pid} "
f"-ex set python print-stack full "
f"-x {scylla_gdb_py} "
f"-x {script_py}"
)
gdb_process = pexpect.spawn(cmd, maxread=10, searchwindowsize=10)
gdb_process.expect_exact("(gdb)")
yield gdb_process
gdb_process.terminate()
def execute_gdb_command(
gdb_process, scylla_command: str = None, full_command: str = None
):
"""
Execute a command in an interactive GDB session and return its output.
The command can be provided either as a Scylla GDB command (which will be
wrapped and executed via GDB's Python interface) or as a full raw GDB
command string.
The function waits for the GDB prompt to reappear, enforces a timeout,
and fails the test if the command does not complete or if GDB reports an
error.
Args:
gdb_process (pexpect.pty_spawn.spawn): An active GDB process spawned via pexpect
scylla_command (str, optional): A GDB Scylla command (from scylla-gdb.py) to execute.
full_command (str, optional): A raw GDB command string to execute.
"""
command = f"python gdb.execute('scylla {scylla_command}')"
if full_command:
command = full_command
gdb_process.sendline(command)
try:
gdb_process.expect_exact("(gdb)", timeout=180)
except pexpect.exceptions.TIMEOUT:
gdb_process.sendcontrol("c")
gdb_process.expect_exact("(gdb)", timeout=1)
pytest.fail("GDB command did not complete within the timeout period")
result = gdb_process.before.decode("utf-8")
assert "Error" not in result
return result