Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions src/valkey_test_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -728,3 +728,83 @@ def waitForReplicaOffsetToSyncUp(self, primary, replica):
pinfo.get_primary_repl_offset(),
timeout=TEST_MAX_WAIT_TIME_SECONDS,
)


class ReuseServerTestCase(ValkeyTestCase):
"""Test case that reuses a single server across all tests in the class.

Instead of spawning a fresh server per test, one server is started on the
first create_server() call and reused for all subsequent tests. FLUSHALL +
CONFIG RESETSTAT run between tests to reset state.

Usage — just change your base class:

class MyModuleTestCase(ReuseServerTestCase):
... # keep your existing setup_test exactly as-is

That's it. self.server, self.client, create_server() all work as before.
"""

def create_server(
self,
testdir=None,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any benefit to not require testdir?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a fallback: if testdir isn't passed, it defaults to self.testdir (same approach as server_path). This way None never reaches the parent, and modules that already pass testdir=self.testdir still work the same.

bind_ip=None,
port=None,
server_path=None,
args="",
skip_teardown=False,
conf_file=None,
external_server=False,
wait_for_ping=True,
connect_client=True,
):
if hasattr(self.__class__, '_shared_server') and self.__class__._shared_server:
return self.__class__._shared_server, self.__class__._shared_client

if testdir is None:
testdir = self.testdir
if server_path is None:
server_path = self.server_path

server, client = super().create_server(
testdir=testdir,
bind_ip=bind_ip,
port=port,
server_path=server_path,
args=args,
skip_teardown=skip_teardown,
conf_file=conf_file,
external_server=external_server,
wait_for_ping=wait_for_ping,
connect_client=connect_client,
)
self.__class__._shared_server = server
self.__class__._shared_client = client
self.__class__._initial_config = client.config_get("*")
return server, client

def teardown(self):
if hasattr(self.__class__, '_shared_server') and self.__class__._shared_server:
client = self.__class__._shared_client
try:
client.flushall()
client.execute_command("CONFIG", "RESETSTAT")
if hasattr(self.__class__, '_initial_config'):
current = client.config_get("*")
for key, val in self.__class__._initial_config.items():
if current.get(key) != val:
try:
client.config_set(key, val)
except Exception:
pass
except Exception:
self.__class__._shared_server = None
self.__class__._shared_client = None

@pytest.fixture(autouse=True, scope="class")
def class_teardown(self, request):
yield
if hasattr(self.__class__, '_shared_server') and self.__class__._shared_server:
self.__class__._shared_server.exit()
self.__class__._shared_server = None
self.__class__._shared_client = None
43 changes: 43 additions & 0 deletions tests/test_reuse_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""
Demonstrates ReuseServerTestCase usage.

All tests in this class share ONE server. Between each test, FLUSHALL + CONFIG
RESETSTAT run automatically to give each test a clean slate without the cost of
restarting the server.
"""

import pytest
from conftest import resource_port_tracker
from valkey_test_case import ReuseServerTestCase


class TestReuseServer(ReuseServerTestCase):
"""Verifies that server reuse works and tests are isolated."""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lack of setup_test to call create_server() after redesigning the ReuseServerTestCase in the 2nd commit

@Fniakate8 Fniakate8 Jul 30, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're correct. After the redesign of the server reuse, the server is only created when create_server() is first called. I'll add a setup_test fixture taht calls create_server().


@pytest.fixture(autouse=True)
def setup_test(self, setup):
self.server, self.client = self.create_server(testdir=self.testdir)

def test_write_and_read(self):
"""Basic write/read on the shared server."""
self.client.set("greeting", "hello")
assert self.client.get("greeting") == b"hello"

def test_isolation_from_previous(self):
"""Proves FLUSHALL cleaned up the previous test's data."""
result = self.client.get("greeting")
assert result is None, "Key from previous test should not exist"

def test_server_still_alive(self):
"""Proves the server survived across tests (no restart)."""
assert self.client.ping() is True

def test_multiple_keys(self):
"""Write multiple keys, verify they all exist within this test."""
for i in range(10):
self.client.set(f"key:{i}", f"value:{i}")
assert self.client.dbsize() == 10

def test_previous_keys_gone(self):
"""Proves the 10 keys from the previous test were flushed."""
assert self.client.dbsize() == 0