Skip to content

Commit d9bd4ee

Browse files
committed
feat(consume): track tests by pre-alloc group for client cleanup
1 parent 25c2d13 commit d9bd4ee

File tree

4 files changed

+332
-7
lines changed

4 files changed

+332
-7
lines changed

src/pytest_plugins/consume/simulators/enginex/conftest.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,25 @@
44
Configures the hive back-end & EL clients for test execution with BlockchainEngineXFixtures.
55
"""
66

7+
import logging
8+
79
import pytest
810
from hive.client import Client
911

1012
from ethereum_test_exceptions import ExceptionMapper
1113
from ethereum_test_fixtures import BlockchainEngineXFixture
1214
from ethereum_test_rpc import EngineRPC
1315

16+
logger = logging.getLogger(__name__)
17+
1418
pytest_plugins = (
1519
"pytest_plugins.pytest_hive.pytest_hive",
1620
"pytest_plugins.consume.simulators.base",
1721
"pytest_plugins.consume.simulators.multi_test_client",
1822
"pytest_plugins.consume.simulators.test_case_description",
1923
"pytest_plugins.consume.simulators.timing_data",
2024
"pytest_plugins.consume.simulators.exceptions",
25+
"pytest_plugins.consume.simulators.helpers.test_tracker",
2126
)
2227

2328

@@ -41,6 +46,54 @@ def test_suite_description() -> str:
4146
)
4247

4348

49+
def pytest_collection_modifyitems(session, config, items):
50+
"""
51+
Build pre-allocation group test counts during collection phase.
52+
53+
This hook analyzes all collected test items to determine how many tests
54+
belong to each pre-allocation group, enabling automatic client cleanup
55+
when all tests in a group are complete.
56+
"""
57+
# Only process items for enginex simulator
58+
if not hasattr(config, "_supported_fixture_formats"):
59+
return
60+
61+
if BlockchainEngineXFixture.format_name not in config._supported_fixture_formats:
62+
return
63+
64+
# Get the test tracker from session if available
65+
test_tracker = getattr(session, "_pre_alloc_group_test_tracker", None)
66+
if test_tracker is None:
67+
# Tracker will be created later by the fixture, store counts on session for now
68+
group_counts = {}
69+
for item in items:
70+
if hasattr(item, "callspec") and "test_case" in item.callspec.params:
71+
test_case = item.callspec.params["test_case"]
72+
if hasattr(test_case, "pre_hash"):
73+
pre_hash = test_case.pre_hash
74+
group_counts[pre_hash] = group_counts.get(pre_hash, 0) + 1
75+
76+
# Store on session for later retrieval by test_tracker fixture
77+
session._pre_alloc_group_counts = group_counts
78+
logger.info(
79+
f"Collected {len(group_counts)} pre-allocation groups with tests: {dict(group_counts)}"
80+
)
81+
else:
82+
# Update tracker directly if it exists
83+
group_counts = {}
84+
for item in items:
85+
if hasattr(item, "callspec") and "test_case" in item.callspec.params:
86+
test_case = item.callspec.params["test_case"]
87+
if hasattr(test_case, "pre_hash"):
88+
pre_hash = test_case.pre_hash
89+
group_counts[pre_hash] = group_counts.get(pre_hash, 0) + 1
90+
91+
for pre_hash, count in group_counts.items():
92+
test_tracker.set_group_test_count(pre_hash, count)
93+
94+
logger.info(f"Updated test tracker with {len(group_counts)} pre-allocation groups")
95+
96+
4497
@pytest.fixture(scope="function")
4598
def engine_rpc(client: Client, client_exception_mapper: ExceptionMapper | None) -> EngineRPC:
4699
"""Initialize engine RPC client for the execution client under test."""

src/pytest_plugins/consume/simulators/helpers/client_wrapper.py

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from ethereum_test_forks import Fork
1616
from pytest_plugins.consume.simulators.helpers.ruleset import ruleset
1717

18+
from .test_tracker import PreAllocGroupTestTracker
19+
1820
logger = logging.getLogger(__name__)
1921

2022

@@ -248,12 +250,22 @@ def set_client(self, client: Client) -> None:
248250
)
249251

250252
def stop(self) -> None:
251-
"""Override to log with pre_hash information."""
253+
"""Override to log with pre_hash information and actually stop the client."""
252254
if self._is_started:
253255
logger.info(
254-
f"Marking multi-test client ({self.client_type.name}) for pre-allocation group "
255-
f"{self.pre_hash} as stopped after {self.test_count} tests"
256+
f"Stopping multi-test client ({self.client_type.name}) for pre-allocation group "
257+
f"{self.pre_hash} after {self.test_count} tests"
256258
)
259+
# Actually stop the Hive client
260+
if self.client is not None:
261+
try:
262+
self.client.stop()
263+
logger.debug(f"Hive client stopped for pre-allocation group {self.pre_hash}")
264+
except Exception as e:
265+
logger.error(
266+
f"Error stopping Hive client for pre-allocation group {self.pre_hash}: {e}"
267+
)
268+
257269
self.client = None
258270
self._is_started = False
259271

@@ -283,6 +295,7 @@ def __init__(self) -> None:
283295

284296
self.multi_test_clients: Dict[str, MultiTestClient] = {}
285297
self.pre_alloc_path: Optional[Path] = None
298+
self.test_tracker: Optional["PreAllocGroupTestTracker"] = None
286299
self._initialized = True
287300
logger.info("MultiTestClientManager initialized")
288301

@@ -297,6 +310,17 @@ def set_pre_alloc_path(self, path: Path) -> None:
297310
self.pre_alloc_path = path
298311
logger.debug(f"Pre-alloc path set to: {path}")
299312

313+
def set_test_tracker(self, test_tracker: "PreAllocGroupTestTracker") -> None:
314+
"""
315+
Set the test tracker for automatic client cleanup.
316+
317+
Args:
318+
test_tracker: The test tracker instance
319+
320+
"""
321+
self.test_tracker = test_tracker
322+
logger.debug("Test tracker set for automatic client cleanup")
323+
300324
def load_pre_alloc_group(self, pre_hash: str) -> PreAllocGroup:
301325
"""
302326
Load the pre-allocation group for a given preHash.
@@ -373,12 +397,15 @@ def get_or_create_multi_test_client(
373397

374398
return multi_test_client
375399

376-
def get_client_for_test(self, pre_hash: str) -> Optional[Client]:
400+
def get_client_for_test(
401+
self, pre_hash: str, test_id: Optional[str] = None
402+
) -> Optional[Client]:
377403
"""
378404
Get the actual client instance for a test with the given preHash.
379405
380406
Args:
381407
pre_hash: The hash identifying the pre-allocation group
408+
test_id: Optional test ID for completion tracking
382409
383410
Returns:
384411
The client instance if available, None otherwise
@@ -391,6 +418,58 @@ def get_client_for_test(self, pre_hash: str) -> Optional[Client]:
391418
return multi_test_client.client
392419
return None
393420

421+
def mark_test_completed(self, pre_hash: str, test_id: str) -> None:
422+
"""
423+
Mark a test as completed and trigger automatic client cleanup if appropriate.
424+
425+
Args:
426+
pre_hash: The hash identifying the pre-allocation group
427+
test_id: The unique test identifier
428+
429+
"""
430+
if self.test_tracker is None:
431+
logger.debug("No test tracker available, skipping completion tracking")
432+
return
433+
434+
# Mark test as completed in tracker
435+
is_group_complete = self.test_tracker.mark_test_completed(pre_hash, test_id)
436+
437+
if is_group_complete:
438+
# All tests in this pre-allocation group are complete
439+
self._auto_stop_client_if_complete(pre_hash)
440+
441+
def _auto_stop_client_if_complete(self, pre_hash: str) -> None:
442+
"""
443+
Automatically stop the client for a pre-allocation group if all tests are complete.
444+
445+
Args:
446+
pre_hash: The hash identifying the pre-allocation group
447+
448+
"""
449+
if pre_hash not in self.multi_test_clients:
450+
logger.debug(f"No client found for pre-allocation group {pre_hash}")
451+
return
452+
453+
multi_test_client = self.multi_test_clients[pre_hash]
454+
if not multi_test_client.is_running:
455+
logger.debug(f"Client for pre-allocation group {pre_hash} is already stopped")
456+
return
457+
458+
# Stop the client and remove from tracking
459+
logger.info(
460+
f"Auto-stopping client for pre-allocation group {pre_hash} - "
461+
f"all tests completed ({multi_test_client.test_count} tests executed)"
462+
)
463+
464+
try:
465+
multi_test_client.stop()
466+
except Exception as e:
467+
logger.error(f"Error auto-stopping client for pre-allocation group {pre_hash}: {e}")
468+
finally:
469+
# Remove from tracking to free memory
470+
del self.multi_test_clients[pre_hash]
471+
logger.debug(f"Removed completed client from tracking: {pre_hash}")
472+
394473
def stop_all_clients(self) -> None:
395474
"""Mark all multi-test clients as stopped."""
396475
logger.info(f"Marking all {len(self.multi_test_clients)} multi-test clients as stopped")
@@ -422,4 +501,5 @@ def reset(self) -> None:
422501
self.stop_all_clients()
423502
self.multi_test_clients.clear()
424503
self.pre_alloc_path = None
504+
self.test_tracker = None
425505
logger.info("MultiTestClientManager reset")

0 commit comments

Comments
 (0)