Skip to content

Commit bac8d60

Browse files
committed
feat(consume): track tests by pre-alloc group for client cleanup
1 parent 2e8238d commit bac8d60

File tree

4 files changed

+334
-8
lines changed

4 files changed

+334
-8
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,19 +4,24 @@
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.consume.simulators.base",
1620
"pytest_plugins.consume.simulators.multi_test_client",
1721
"pytest_plugins.consume.simulators.test_case_description",
1822
"pytest_plugins.consume.simulators.timing_data",
1923
"pytest_plugins.consume.simulators.exceptions",
24+
"pytest_plugins.consume.simulators.helpers.test_tracker",
2025
)
2126

2227

@@ -40,6 +45,54 @@ def test_suite_description() -> str:
4045
)
4146

4247

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

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

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import logging
66
from abc import ABC, abstractmethod
77
from pathlib import Path
8-
from typing import Dict, Optional, cast
8+
from typing import TYPE_CHECKING, Dict, Optional, cast
99

1010
from hive.client import Client, ClientType
1111

@@ -15,6 +15,9 @@
1515
from ethereum_test_forks import Fork
1616
from pytest_plugins.consume.simulators.helpers.ruleset import ruleset
1717

18+
if TYPE_CHECKING:
19+
from .test_tracker import PreAllocGroupTestTracker
20+
1821
logger = logging.getLogger(__name__)
1922

2023

@@ -248,12 +251,22 @@ def set_client(self, client: Client) -> None:
248251
)
249252

250253
def stop(self) -> None:
251-
"""Override to log with pre_hash information."""
254+
"""Override to log with pre_hash information and actually stop the client."""
252255
if self._is_started:
253256
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"
257+
f"Stopping multi-test client ({self.client_type.name}) for pre-allocation group "
258+
f"{self.pre_hash} after {self.test_count} tests"
256259
)
260+
# Actually stop the Hive client
261+
if self.client is not None:
262+
try:
263+
self.client.stop()
264+
logger.debug(f"Hive client stopped for pre-allocation group {self.pre_hash}")
265+
except Exception as e:
266+
logger.error(
267+
f"Error stopping Hive client for pre-allocation group {self.pre_hash}: {e}"
268+
)
269+
257270
self.client = None
258271
self._is_started = False
259272

@@ -283,6 +296,7 @@ def __init__(self) -> None:
283296

284297
self.multi_test_clients: Dict[str, MultiTestClient] = {}
285298
self.pre_alloc_path: Optional[Path] = None
299+
self.test_tracker: Optional["PreAllocGroupTestTracker"] = None
286300
self._initialized = True
287301
logger.info("MultiTestClientManager initialized")
288302

@@ -297,6 +311,17 @@ def set_pre_alloc_path(self, path: Path) -> None:
297311
self.pre_alloc_path = path
298312
logger.debug(f"Pre-alloc path set to: {path}")
299313

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

374399
return multi_test_client
375400

376-
def get_client_for_test(self, pre_hash: str) -> Optional[Client]:
401+
def get_client_for_test(
402+
self, pre_hash: str, test_id: Optional[str] = None
403+
) -> Optional[Client]:
377404
"""
378405
Get the actual client instance for a test with the given preHash.
379406
380407
Args:
381408
pre_hash: The hash identifying the pre-allocation group
409+
test_id: Optional test ID for completion tracking
382410
383411
Returns:
384412
The client instance if available, None otherwise
@@ -391,6 +419,58 @@ def get_client_for_test(self, pre_hash: str) -> Optional[Client]:
391419
return multi_test_client.client
392420
return None
393421

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

0 commit comments

Comments
 (0)