Skip to content
Draft
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
4 changes: 2 additions & 2 deletions .github/configs/feature.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ develop:

benchmark:
evm-type: benchmark
fill-params: --fork=Prague --gas-benchmark-values 1,10,30,45,60,100,150 -m "benchmark and not state_test" ./tests/benchmark
fill-params: --fork=Prague --gas-benchmark-values 1,10,30,45,60,100,150 -m "not state_test" ./tests/benchmark/compute

benchmark_develop:
evm-type: benchmark
fill-params: --fork=Osaka --gas-benchmark-values 1,10,30,60,100,150 -m "benchmark and not state_test" ./tests/benchmark
fill-params: --fork=Osaka --gas-benchmark-values 1,10,30,60,100,150 -m "not state_test" ./tests/benchmark/compute
feature_only: true

bal:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""The module contains the pytest hooks for the gas benchmark values."""

from pathlib import Path

import pytest

from execution_testing.test_types import Environment, EnvironmentDefaults
Expand Down Expand Up @@ -30,31 +32,68 @@ def pytest_addoption(parser: pytest.Parser) -> None:
)


def pytest_ignore_collect(
collection_path: Path, config: pytest.Config
) -> bool | None:
"""
Ignore tests/benchmark/ directory by default unless explicitly specified.

Returns True to ignore, False to collect, None for default behavior.
"""
# Only handle paths within tests/benchmark/
try:
collection_path.relative_to(config.rootpath / "tests" / "benchmark")
except ValueError:
return None

Copy link
Member

Choose a reason for hiding this comment

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

To address the failing documentation generation workflow, if fill is running in documentation mode (i.e., config.getoption("--gen-docs", default=False) is True), then we can simply return False here to not ignore benchmark tests and have them included in the documentation.

# Check if benchmark tests explicitly specified in command line arguments
benchmark_path = config.rootpath / "tests" / "benchmark"
Copy link
Member

Choose a reason for hiding this comment

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

We could define this above the try block above and use it in the try block, too.

for arg in config.args:
arg_path = Path(arg)
# Check absolute paths
if arg_path.is_absolute():
try:
arg_path.relative_to(benchmark_path)
# Explicitly specified, set op_mode and don't ignore
config.op_mode = OpMode.BENCHMARKING # type: ignore[attr-defined]
return False
except ValueError:
continue
# Check relative paths containing 'benchmark'
elif "benchmark" in arg:
Copy link
Member

Choose a reason for hiding this comment

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

This is a rather loose check, perhaps we could check for "benchmark/compute" or "benchmark/stateful". Of course, it'll break if we add another valid benchmark sub-folder here.

# Explicitly specified, set op_mode and don't ignore
config.op_mode = OpMode.BENCHMARKING # type: ignore[attr-defined]
return False
Comment on lines +44 to +66
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This step checks whether the benchmark path is explicitly specified, but i think this is not the best approach


# Not explicitly specified, ignore by default
return True


@pytest.hookimpl(tryfirst=True)
def pytest_configure(config: pytest.Config) -> None:
"""Configure the fill and execute mode to benchmarking."""
config.addinivalue_line(
"markers",
"repricing: Mark test as reference test for gas repricing analysis",
)
if config.getoption("gas_benchmark_value"):
config.op_mode = OpMode.BENCHMARKING # type: ignore[attr-defined]


def pytest_collection_modifyitems(
config: pytest.Config, items: list[pytest.Item]
) -> None:
"""Remove non-repricing tests when --fixed-opcode-count is specified."""
fixed_opcode_count = config.getoption("fixed_opcode_count")
if not fixed_opcode_count:
# If --fixed-opcode-count is not specified, don't filter anything
"""
Filter tests based on repricing marker kwargs when -m repricing is specified.

When a test has @pytest.mark.repricing(param=value), only the parameterized
variant matching those kwargs should be selected.
"""
# Check if -m repricing marker filter was specified
markexpr = config.getoption("markexpr", "")
if "repricing" not in markexpr:
return

filtered = []
for item in items:
if not item.get_closest_marker("benchmark"):
continue

repricing_marker = item.get_closest_marker("repricing")
if not repricing_marker:
continue
Expand Down Expand Up @@ -160,7 +199,10 @@ def genesis_environment(request: pytest.FixtureRequest) -> Environment: # noqa:
Return an Environment instance with appropriate gas limit based on test
type.
"""
if request.node.get_closest_marker("benchmark") is not None:
is_benchmark = (
getattr(request.config, "op_mode", False) == OpMode.BENCHMARKING
)
if is_benchmark:
return Environment(gas_limit=BENCHMARKING_MAX_GAS)
return Environment()

Expand All @@ -171,6 +213,9 @@ def env(request: pytest.FixtureRequest) -> Environment: # noqa: D103
Return an Environment instance with appropriate gas limit based on test
type.
"""
if request.node.get_closest_marker("benchmark") is not None:
is_benchmark = (
getattr(request.config, "op_mode", False) == OpMode.BENCHMARKING
)
if is_benchmark:
return Environment(gas_limit=BENCHMARKING_MAX_GAS)
return Environment()
5 changes: 5 additions & 0 deletions packages/testing/src/execution_testing/specs/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,11 @@ def model_post_init(self, __context: Any, /) -> None:

blocks: List[Block] = self.setup_blocks

if self.fixed_opcode_count is not None and self.code_generator is None:
pytest.skip(
"Cannot run fixed opcode count tests without a code generator"
)

if self.code_generator is not None:
# Inject fixed_opcode_count into the code generator if provided
self.code_generator.fixed_opcode_count = self.fixed_opcode_count
Expand Down
56 changes: 0 additions & 56 deletions tests/benchmark/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,62 +32,6 @@ def pytest_generate_tests(metafunc: Any) -> None:
metafunc.definition.add_marker(benchmark_marker)


def pytest_collection_modifyitems(config: Any, items: Any) -> None:
"""Add the `benchmark` marker to all tests under `./tests/benchmark`."""
benchmark_dir = Path(__file__).parent
benchmark_marker = pytest.mark.benchmark
gen_docs = config.getoption("--gen-docs", default=False)

if gen_docs:
for item in items:
if (
benchmark_dir in Path(item.fspath).parents
and not item.get_closest_marker("benchmark")
and not item.get_closest_marker("stateful")
):
item.add_marker(benchmark_marker)
return
Comment on lines -39 to -49
Copy link
Member

Choose a reason for hiding this comment

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

This code is pretty ugly, but this PR will have to add some "gen_docs" logic so that ./tests/benchmark is collected if we're generating documentation, so that the docs contain all documentation.


marker_expr = config.getoption("-m", default="")
run_benchmarks = (
marker_expr
and "benchmark" in marker_expr
and "not benchmark" not in marker_expr
)
run_stateful_tests = (
marker_expr
and "stateful" in marker_expr
and "not stateful" not in marker_expr
)

items_for_removal = []
for i, item in enumerate(items):
is_in_benchmark_dir = benchmark_dir in Path(item.fspath).parents
has_stateful_marker = item.get_closest_marker("stateful")
is_benchmark_test = (
is_in_benchmark_dir and not has_stateful_marker
) or item.get_closest_marker("benchmark")

if is_benchmark_test:
if is_in_benchmark_dir and not item.get_closest_marker(
"benchmark"
):
item.add_marker(benchmark_marker)
if not run_benchmarks:
items_for_removal.append(i)
elif run_benchmarks:
items_for_removal.append(i)
elif (
is_in_benchmark_dir
and has_stateful_marker
and not run_stateful_tests
):
items_for_removal.append(i)

for i in reversed(items_for_removal):
items.pop(i)


@pytest.fixture
def tx_gas_limit(fork: Fork, gas_benchmark_value: int) -> int:
"""Return the transaction gas limit cap."""
Expand Down
47 changes: 0 additions & 47 deletions tests/benchmark/stateful/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,50 +25,3 @@ def pytest_generate_tests(metafunc: Any) -> None:
metafunc.definition.add_marker(
pytest.mark.valid_from(DEFAULT_BENCHMARK_FORK)
)


def pytest_collection_modifyitems(config: Any, items: Any) -> None:
"""Manage stateful test markers and filtering."""
state_dir = Path(__file__).parent
gen_docs = config.getoption("--gen-docs", default=False)

if gen_docs:
_add_stateful_markers_for_docs(items, state_dir)
return

marker_expr = config.getoption("-m", default="")

items_to_remove = []

for i, item in enumerate(items):
item_path = Path(item.fspath)
is_in_state_dir = state_dir in item_path.parents

# Add stateful marker to tests in state directory that don't have it
if is_in_state_dir and not item.get_closest_marker("stateful"):
item.add_marker(pytest.mark.stateful)

has_stateful_marker = item.get_closest_marker("stateful")

run_stateful = (
marker_expr
and ("stateful" in marker_expr)
and ("not stateful" not in marker_expr)
)

# When not running stateful tests, remove all stateful tests
if not run_stateful and has_stateful_marker:
items_to_remove.append(i)

for i in reversed(items_to_remove):
items.pop(i)


def _add_stateful_markers_for_docs(items: Any, state_dir: Any) -> None:
"""Add stateful markers for documentation generation."""
for item in items:
item_path = Path(item.fspath)
if state_dir in item_path.parents and not item.get_closest_marker(
"stateful"
):
item.add_marker(pytest.mark.stateful)
8 changes: 4 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ commands =
description = Fill the tests using EELS (with Python)
commands =
fill \
-m "not slow and not zkevm and not benchmark" \
-m "not slow" \
-n auto --maxprocesses 10 --dist=loadgroup \
--basetemp="{temp_dir}/pytest" \
--log-to "{toxworkdir}/logs" \
Expand All @@ -102,7 +102,7 @@ commands =
--tb=no \
--show-capture=no \
--disable-warnings \
-m "not slow and not zkevm and not benchmark" \
-m "not slow" \
-n auto --maxprocesses 7 --dist=loadgroup \
--basetemp="{temp_dir}/pytest" \
--log-to "{toxworkdir}/logs" \
Expand All @@ -117,13 +117,13 @@ commands =
--generate-all-formats \
--gas-benchmark-values 1 \
--evm-bin=evmone-t8n \
-m "benchmark and not state_test" \
-m "not state_test" \
-n auto --maxprocesses 10 --dist=loadgroup \
--basetemp="{temp_dir}/pytest" \
--log-to "{toxworkdir}/logs" \
--clean \
--fork Prague \
tests/benchmark
tests/benchmark/compute

[testenv:optimized]
description = Run unit tests for optimized state and ethash
Expand Down
Loading