Skip to content

Commit 50a37e6

Browse files
committed
feat(consume): add --enginex-fcu-frequency option
1 parent d9bd4ee commit 50a37e6

File tree

3 files changed

+178
-4
lines changed

3 files changed

+178
-4
lines changed

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

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,29 @@
2626
)
2727

2828

29+
def pytest_addoption(parser):
30+
"""Add enginex-specific command line options."""
31+
enginex_group = parser.getgroup("enginex", "EngineX simulator options")
32+
enginex_group.addoption(
33+
"--enginex-fcu-frequency",
34+
action="store",
35+
type=int,
36+
default=0,
37+
help=(
38+
"Control forkchoice update frequency for enginex simulator. "
39+
"0=disable FCUs (default), 1=FCU every test, N=FCU every Nth test per "
40+
"pre-allocation group."
41+
),
42+
)
43+
44+
2945
def pytest_configure(config):
30-
"""Set the supported fixture formats for the engine simulator."""
46+
"""Set the supported fixture formats and store enginex configuration."""
3147
config._supported_fixture_formats = [BlockchainEngineXFixture.format_name]
3248

49+
# Store FCU frequency on config for access by fixtures
50+
config.enginex_fcu_frequency = config.getoption("--enginex-fcu-frequency", 1)
51+
3352

3453
@pytest.fixture(scope="module")
3554
def test_suite_name() -> str:
@@ -105,3 +124,23 @@ def engine_rpc(client: Client, client_exception_mapper: ExceptionMapper | None)
105124
},
106125
)
107126
return EngineRPC(f"http://{client.ip}:8551")
127+
128+
129+
@pytest.fixture(scope="session")
130+
def fcu_frequency_tracker(request):
131+
"""
132+
Session-scoped FCU frequency tracker for enginex simulator.
133+
134+
This fixture is imported from test_tracker module and configured
135+
with the --enginex-fcu-frequency command line option.
136+
"""
137+
# Import here to avoid circular imports
138+
from ..helpers.test_tracker import FCUFrequencyTracker
139+
140+
# Get FCU frequency from pytest config (set by command line argument)
141+
fcu_frequency = getattr(request.config, "enginex_fcu_frequency", 1)
142+
143+
tracker = FCUFrequencyTracker(fcu_frequency=fcu_frequency)
144+
logger.info(f"FCU frequency tracker initialized with frequency: {fcu_frequency}")
145+
146+
return tracker

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

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,102 @@ def pre_alloc_group_test_tracker(request) -> PreAllocGroupTestTracker:
175175

176176
logger.info("Pre-allocation group test tracker initialized")
177177
return tracker
178+
179+
180+
@dataclass
181+
class FCUFrequencyTracker:
182+
"""
183+
Tracks forkchoice update frequency per pre-allocation group.
184+
185+
This class enables controlling how often forkchoice updates are performed
186+
during test execution on a per-pre-allocation-group basis.
187+
"""
188+
189+
fcu_frequency: int
190+
"""Frequency of FCU operations (0=disabled, 1=every test, N=every Nth test)."""
191+
192+
group_test_counters: Dict[str, int] = field(default_factory=dict)
193+
"""Test counters per pre-allocation group (pre_hash -> count)."""
194+
195+
def should_perform_fcu(self, pre_hash: str) -> bool:
196+
"""
197+
Check if forkchoice update should be performed for this test.
198+
199+
Args:
200+
pre_hash: The pre-allocation group hash
201+
202+
Returns:
203+
True if FCU should be performed for this test
204+
205+
"""
206+
if self.fcu_frequency == 0:
207+
logger.debug(f"FCU disabled for pre-allocation group {pre_hash} (frequency=0)")
208+
return False
209+
210+
current_count = self.group_test_counters.get(pre_hash, 0)
211+
should_perform = (current_count % self.fcu_frequency) == 0
212+
213+
logger.debug(
214+
f"FCU decision for pre-allocation group {pre_hash}: "
215+
f"perform={should_perform} (test_count={current_count}, "
216+
f"frequency={self.fcu_frequency})"
217+
)
218+
219+
return should_perform
220+
221+
def increment_test_count(self, pre_hash: str) -> None:
222+
"""
223+
Increment test counter for pre-allocation group.
224+
225+
Args:
226+
pre_hash: The pre-allocation group hash
227+
228+
"""
229+
current_count = self.group_test_counters.get(pre_hash, 0)
230+
new_count = current_count + 1
231+
self.group_test_counters[pre_hash] = new_count
232+
233+
logger.debug(
234+
f"Incremented test count for pre-allocation group {pre_hash}: "
235+
f"{current_count} -> {new_count}"
236+
)
237+
238+
def get_test_count(self, pre_hash: str) -> int:
239+
"""
240+
Get current test count for pre-allocation group.
241+
242+
Args:
243+
pre_hash: The pre-allocation group hash
244+
245+
Returns:
246+
Current test count for the group
247+
248+
"""
249+
return self.group_test_counters.get(pre_hash, 0)
250+
251+
def get_all_test_counts(self) -> Dict[str, int]:
252+
"""
253+
Get test counts for all tracked pre-allocation groups.
254+
255+
Returns:
256+
Dict mapping pre_hash to test count
257+
258+
"""
259+
return dict(self.group_test_counters)
260+
261+
def reset_group(self, pre_hash: str) -> None:
262+
"""
263+
Reset test counter for a specific pre-allocation group.
264+
265+
Args:
266+
pre_hash: The pre-allocation group hash to reset
267+
268+
"""
269+
if pre_hash in self.group_test_counters:
270+
del self.group_test_counters[pre_hash]
271+
logger.debug(f"Reset test counter for pre-allocation group {pre_hash}")
272+
273+
def reset_all(self) -> None:
274+
"""Reset all test counters."""
275+
self.group_test_counters.clear()
276+
logger.debug("Reset all FCU frequency test counters")

src/pytest_plugins/consume/simulators/simulator_logic/test_via_engine.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,36 @@ def test_blockchain_via_engine(
3939
fixture: BlockchainEngineFixture | BlockchainEngineXFixture,
4040
genesis_header: FixtureHeader,
4141
strict_exception_matching: bool,
42+
fcu_frequency_tracker=None, # Optional for enginex simulator
43+
request=None, # For accessing test info
4244
):
4345
"""
4446
1. Check the client genesis block hash matches `genesis.block_hash`.
4547
2. Execute the test case fixture blocks against the client under test using the
4648
`engine_newPayloadVX` method from the Engine API.
47-
3. For valid payloads a forkchoice update is performed to finalize the chain.
49+
3. For valid payloads a forkchoice update is performed to finalize the chain
50+
(controlled by FCU frequency for enginex simulator).
4851
"""
49-
# Send an initial forkchoice update
52+
# Determine if we should perform forkchoice updates based on frequency tracker
53+
should_perform_fcus = True # Default behavior for engine simulator
54+
pre_hash = None
55+
56+
if fcu_frequency_tracker is not None and hasattr(fixture, "pre_hash"):
57+
# EngineX simulator with forkchoice update frequency control
58+
pre_hash = fixture.pre_hash
59+
should_perform_fcus = fcu_frequency_tracker.should_perform_fcu(pre_hash)
60+
61+
logger.info(
62+
f"Forkchoice update frequency check for pre-allocation group {pre_hash}: "
63+
f"perform_fcu={should_perform_fcus} "
64+
f"(frequency={fcu_frequency_tracker.fcu_frequency}, "
65+
f"test_count={fcu_frequency_tracker.get_test_count(pre_hash)})"
66+
)
67+
68+
# Always increment the test counter at the start for proper tracking
69+
if fcu_frequency_tracker is not None and pre_hash is not None:
70+
fcu_frequency_tracker.increment_test_count(pre_hash)
71+
# Send a initial forkchoice update
5072
with timing_data.time("Initial forkchoice update"):
5173
logger.info("Sending initial forkchoice update to genesis block...")
5274
delay = 0.5
@@ -155,7 +177,7 @@ def test_blockchain_via_engine(
155177
f"Unexpected error code: {e.code}, expected: {payload.error_code}"
156178
) from e
157179

158-
if payload.valid():
180+
if payload.valid() and should_perform_fcus:
159181
with payload_timing.time(
160182
f"engine_forkchoiceUpdatedV{payload.forkchoice_updated_version}"
161183
):
@@ -176,4 +198,18 @@ def test_blockchain_via_engine(
176198
f"unexpected status: want {PayloadStatusEnum.VALID},"
177199
f" got {forkchoice_response.payload_status.status}"
178200
)
201+
elif payload.valid() and not should_perform_fcus:
202+
logger.info(
203+
f"Skipping forkchoice update for payload {i + 1} due to frequency setting "
204+
f"(pre-allocation group: {pre_hash})"
205+
)
179206
logger.info("All payloads processed successfully.")
207+
208+
# Log final FCU frequency statistics for enginex simulator
209+
if fcu_frequency_tracker is not None and pre_hash is not None:
210+
final_count = fcu_frequency_tracker.get_test_count(pre_hash)
211+
logger.info(
212+
f"Test completed for pre-allocation group {pre_hash}. "
213+
f"Total tests in group: {final_count}, "
214+
f"FCU frequency: {fcu_frequency_tracker.fcu_frequency}"
215+
)

0 commit comments

Comments
 (0)