Skip to content
Merged
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
8 changes: 4 additions & 4 deletions packages/testing/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ description = "Ethereum execution layer client test generation and runner framew
readme = "README.md"
requires-python = ">=3.11"
license = "MIT"
license-files = [ "LICENSE" ]
license-files = ["LICENSE"]
keywords = ["ethereum", "testing", "blockchain"]
classifiers = [
"Programming Language :: Python :: 3.11",
Expand Down Expand Up @@ -71,8 +71,8 @@ lint = [
"types-requests>=2.31,<2.33",
]
dev = [
{include-group = "test"},
{include-group = "lint"},
{ include-group = "test" },
{ include-group = "lint" },
]

[project.scripts]
Expand Down Expand Up @@ -106,7 +106,7 @@ where = ["src"]
exclude = ["*tests*"]

[tool.setuptools.package-data]
"execution_testing.forks" = ["forks/contracts/*.bin"]
"execution_testing.forks" = ["forks/eips/**/*.bin"]
"execution_testing.cli.pytest_commands.plugins.execute" = ["eth_config/networks.yml"]
"execution_testing.cli.eest.make" = ["templates/*.j2"]
"execution_testing.cli.pytest_commands" = ["pytest_ini_files/*.ini"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@

import click

from ...forks import get_development_forks
from .fill import FillCommand


def _last_development_fork() -> str | None:
"""Return the name of the last development fork, if any."""
dev_forks = get_development_forks()
return dev_forks[-1].name() if dev_forks else None


@click.command()
@click.option(
"--output",
Expand All @@ -22,13 +29,25 @@
multiple=True,
help="Generate checklist only for specific EIP(s)",
)
def checklist(output: str, eip: tuple[int, ...], **kwargs: Any) -> None:
@click.option(
"--until",
"-u",
type=str,
default=None,
help="Include upcoming forks up to and including this fork",
)
def checklist(
output: str, eip: tuple[int, ...], until: str | None, **kwargs: Any
) -> None:
"""
Generate EIP test checklists based on pytest.mark.eip_checklist markers.

This command scans test files for eip_checklist markers and generates
filled checklists showing which checklist items have been implemented.

By default, includes all development forks so that checklists for
upcoming EIPs are generated without needing --until.

Examples:
# Generate checklists for all EIPs
uv run checklist
Expand All @@ -39,6 +58,9 @@ def checklist(output: str, eip: tuple[int, ...], **kwargs: Any) -> None:
# Generate checklists for specific test path
uv run checklist tests/prague/eip7702*

# Limit to a specific fork
uv run checklist --until Prague

# Specify output directory
uv run checklist --output ./my-checklists

Expand All @@ -52,6 +74,13 @@ def checklist(output: str, eip: tuple[int, ...], **kwargs: Any) -> None:
for eip_num in eip:
args.extend(["--checklist-eip", str(eip_num)])

# Default --until to the last development fork so checklists for
# upcoming EIPs are generated without requiring the flag explicitly.
if until is None:
until = _last_development_fork()
if until:
args.extend(["--until", until])

command = FillCommand(
plugins=[
"execution_testing.cli.pytest_commands.plugins.filler.eip_checklist"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
ALL_FORKS,
ALL_FORKS_WITH_TRANSITIONS,
Fork,
ForkEIPSetAdapter,
ForkSetAdapter,
InvalidForkError,
TransitionFork,
Expand Down Expand Up @@ -712,6 +713,13 @@ class ValidityMarker(ABC):

mark: Mark | None

class ValidityMarkerCombinationError(Exception):
"""
Combination of two validity markers generates an empty fork range.
"""

pass

def __init_subclass__(
cls,
marker_name: str | None = None,
Expand Down Expand Up @@ -749,12 +757,21 @@ def process_fork_arguments(
self, *fork_args: str
) -> Set[Fork | TransitionFork]:
"""Process the fork arguments."""
fork_set = ForkSetAdapter.validate_python(fork_args)
if len(fork_set) != len(fork_args):
fork_eips_set = ForkEIPSetAdapter.validate_python(fork_args)
if len(fork_eips_set) != len(fork_args):
raise Exception(
f"Duplicate argument specified in '{self.marker_name}'"
)
return fork_set
forks_set: Set[Fork | TransitionFork] = set()
for fork_eip in fork_eips_set:
if fork_eip.is_transition_fork:
forks_set.add(fork_eip)
else:
if not fork_eip.is_eip():
forks_set.add(fork_eip)
else:
forks_set |= fork_eip.enabling_forks()
return forks_set

@staticmethod
def get_all_validity_markers(
Expand Down Expand Up @@ -855,7 +872,14 @@ def process(
)
if self.flag:
return forks - fork_set
return forks & fork_set
if not fork_set:
# Test is marked for an EIP that is not yet enabled in any
# fork.
return fork_set
resulting_set = forks & fork_set
if not resulting_set:
raise ValidityMarker.ValidityMarkerCombinationError()
return resulting_set

@abstractmethod
def _process_with_marker_args(
Expand Down Expand Up @@ -1125,17 +1149,39 @@ def pytest_generate_tests(metafunc: pytest.Metafunc) -> None:
test_fork_set = ValidityMarker.get_test_fork_set_from_metafunc(
metafunc
)
except Exception as e:
pytest.fail(f"Error generating tests for {test_name}: {e}")

if not test_fork_set:
except ValidityMarker.ValidityMarkerCombinationError:
markers = ValidityMarker.get_all_validity_markers(
metafunc.definition.iter_markers()
)
marker_names = [
f"@pytest.mark.{marker.marker_name}" for marker in markers
]
pytest.fail(
"The test function's "
f"'{test_name}' fork validity markers generate "
"an empty fork range. Please check the arguments to its "
f"markers: @pytest.mark.valid_from and "
f"@pytest.mark.valid_until."
f"markers: {', '.join(marker_names)}."
)
except Exception as e:
pytest.fail(f"Error generating tests for {test_name}: {e}")

pytest_params: List[Any]
if not test_fork_set:
if metafunc.config.getoption("verbose") >= 2:
pytest_params = [
pytest.param(
None,
marks=[
pytest.mark.skip(
reason=(
f"{test_name} is not enabled for any fork."
)
)
],
)
]
metafunc.parametrize("fork", pytest_params, scope="function")
return

# Get the intersection between the test's validity marker and the current
# filling parameters.
Expand All @@ -1146,7 +1192,6 @@ def pytest_generate_tests(metafunc: pytest.Metafunc) -> None:
if "fork" not in metafunc.fixturenames:
return

pytest_params: List[Any]
unsupported_forks: Set[Fork | TransitionFork] = (
metafunc.config.unsupported_forks # type: ignore
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ def test_case(state_test):
{"passed": 4, "failed": 0, "skipped": 0, "errors": 0},
id="valid_from",
),
pytest.param(
generate_test(
valid_from='"EIP3675"',
),
["--until=Prague"],
{"passed": 4, "failed": 0, "skipped": 0, "errors": 0},
id="valid_from_eip",
),
pytest.param(
generate_test(
valid_from='"Paris"',
Expand All @@ -54,6 +62,33 @@ def test_case(state_test):
{"passed": 3, "failed": 0, "skipped": 0, "errors": 0},
id="valid_from_until",
),
pytest.param(
generate_test(
valid_from='"EIP3675"',
valid_until='"EIP4844"',
),
[],
{"passed": 3, "failed": 0, "skipped": 0, "errors": 0},
id="valid_from_eip_until_eip",
),
pytest.param(
generate_test(
valid_from='"Paris"',
valid_until='"EIP4844"',
),
[],
{"passed": 3, "failed": 0, "skipped": 0, "errors": 0},
id="valid_from_fork_until_eip",
),
pytest.param(
generate_test(
valid_from='"EIP3675"',
valid_until='"Cancun"',
),
[],
{"passed": 3, "failed": 0, "skipped": 0, "errors": 0},
id="valid_from_eip_until_fork",
),
pytest.param(
generate_test(
valid_from='"Paris"',
Expand Down
2 changes: 2 additions & 0 deletions packages/testing/src/execution_testing/forks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
ALL_TRANSITION_FORKS,
Fork,
ForkAdapter,
ForkEIPSetAdapter,
ForkOrNoneAdapter,
ForkRangeDescriptor,
ForkSet,
Expand Down Expand Up @@ -79,6 +80,7 @@
"ALL_TRANSITION_FORKS",
"Fork",
"ForkAdapter",
"ForkEIPSetAdapter",
"ForkOrNoneAdapter",
"ForkSet",
"ForkSetAdapter",
Expand Down
Loading
Loading