Skip to content

Commit cb6957d

Browse files
committed
feat(consume): add --enginex-fcu-frequency option
1 parent bac8d60 commit cb6957d

File tree

4 files changed

+206
-6
lines changed

4 files changed

+206
-6
lines changed

src/cli/pytest_commands/consume.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,36 @@ def engine() -> None:
116116
pass
117117

118118

119-
@consume_command(is_hive=True)
120-
def enginex() -> None:
119+
@consume.command(
120+
name="enginex",
121+
help="Client consumes Engine X Fixtures via the Engine API.",
122+
context_settings={"ignore_unknown_options": True},
123+
)
124+
@click.option(
125+
"--enginex-fcu-frequency",
126+
type=int,
127+
default=1,
128+
help=(
129+
"Control forkchoice update frequency for enginex simulator. "
130+
"0=disable FCUs, 1=FCU every test (default), N=FCU every Nth test per "
131+
"pre-allocation group."
132+
),
133+
)
134+
@common_pytest_options
135+
def enginex(enginex_fcu_frequency: int, pytest_args: List[str], **_kwargs) -> None:
121136
"""Client consumes Engine X Fixtures via the Engine API."""
122-
pass
137+
command_name = "enginex"
138+
command_paths = get_command_paths(command_name, is_hive=True)
139+
140+
# Validate the frequency parameter
141+
if enginex_fcu_frequency < 0:
142+
raise click.BadParameter("FCU frequency must be non-negative")
143+
144+
# Add the FCU frequency to pytest args as a custom config option
145+
pytest_args_with_fcu = [f"--enginex-fcu-frequency={enginex_fcu_frequency}"] + list(pytest_args)
146+
147+
consume_cmd = ConsumeCommand(command_paths, is_hive=True, command_name=command_name)
148+
consume_cmd.execute(pytest_args_with_fcu)
123149

124150

125151
@consume_command(is_hive=True)

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

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

2727

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

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

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

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,35 @@ def test_blockchain_via_engine(
3636
fixture: BlockchainEngineFixture | BlockchainEngineXFixture,
3737
genesis_header: FixtureHeader,
3838
strict_exception_matching: bool,
39+
fcu_frequency_tracker=None, # Optional for enginex simulator
40+
request=None, # For accessing test info
3941
):
4042
"""
4143
1. Check the client genesis block hash matches `genesis.block_hash`.
4244
2. Execute the test case fixture blocks against the client under test using the
4345
`engine_newPayloadVX` method from the Engine API.
44-
3. For valid payloads a forkchoice update is performed to finalize the chain.
46+
3. For valid payloads a forkchoice update is performed to finalize the chain
47+
(controlled by FCU frequency for enginex simulator).
4548
"""
49+
# Determine if we should perform forkchoice updates based on frequency tracker
50+
should_perform_fcus = True # Default behavior for engine simulator
51+
pre_hash = None
52+
53+
if fcu_frequency_tracker is not None and hasattr(fixture, "pre_hash"):
54+
# EngineX simulator with forkchoice update frequency control
55+
pre_hash = fixture.pre_hash
56+
should_perform_fcus = fcu_frequency_tracker.should_perform_fcu(pre_hash)
57+
58+
logger.info(
59+
f"Forkchoice update frequency check for pre-allocation group {pre_hash}: "
60+
f"perform_fcu={should_perform_fcus} "
61+
f"(frequency={fcu_frequency_tracker.fcu_frequency}, "
62+
f"test_count={fcu_frequency_tracker.get_test_count(pre_hash)})"
63+
)
64+
65+
# Always increment the test counter at the start for proper tracking
66+
if fcu_frequency_tracker is not None and pre_hash is not None:
67+
fcu_frequency_tracker.increment_test_count(pre_hash)
4668
# Send a initial forkchoice update
4769
with timing_data.time("Initial forkchoice update"):
4870
logger.info("Sending initial forkchoice update to genesis block...")
@@ -152,7 +174,7 @@ def test_blockchain_via_engine(
152174
f"Unexpected error code: {e.code}, expected: {payload.error_code}"
153175
) from e
154176

155-
if payload.valid():
177+
if payload.valid() and should_perform_fcus:
156178
with payload_timing.time(
157179
f"engine_forkchoiceUpdatedV{payload.forkchoice_updated_version}"
158180
):
@@ -173,4 +195,18 @@ def test_blockchain_via_engine(
173195
f"unexpected status: want {PayloadStatusEnum.VALID},"
174196
f" got {forkchoice_response.payload_status.status}"
175197
)
198+
elif payload.valid() and not should_perform_fcus:
199+
logger.info(
200+
f"Skipping forkchoice update for payload {i + 1} due to frequency setting "
201+
f"(pre-allocation group: {pre_hash})"
202+
)
176203
logger.info("All payloads processed successfully.")
204+
205+
# Log final FCU frequency statistics for enginex simulator
206+
if fcu_frequency_tracker is not None and pre_hash is not None:
207+
final_count = fcu_frequency_tracker.get_test_count(pre_hash)
208+
logger.info(
209+
f"Test completed for pre-allocation group {pre_hash}. "
210+
f"Total tests in group: {final_count}, "
211+
f"FCU frequency: {fcu_frequency_tracker.fcu_frequency}"
212+
)

0 commit comments

Comments
 (0)