diff --git a/docs/filling_tests/filling_tests_dev_fork.md b/docs/filling_tests/filling_tests_dev_fork.md index ff9a7de538..74b0f7b147 100644 --- a/docs/filling_tests/filling_tests_dev_fork.md +++ b/docs/filling_tests/filling_tests_dev_fork.md @@ -42,7 +42,7 @@ just binary-trie-fork [paths] A bare invocation fills only the scoped `tests/binary_tree` suite; `just binary-trie-fork tests` instead reinterprets the entire `tests/` tree against `BinaryTree`. -When porting existing tests: `valid_from(...)` markers select `BinaryTree` (it subclasses `Amsterdam`), but `valid_at(...)` silently does not, so a handful of `valid_at`-pinned tests are invisible to the port with no warning at collection time. +When porting existing tests: `valid_from(...)` markers select `BinaryTree` (it subclasses `Amsterdam`), and `valid_at(...)` markers naming `Amsterdam` — or an EIP that resolves to it — select it too, because the fork is declared with `inherits_exact_fork_validity=True` (it keeps Amsterdam's execution semantics). Tests pinned to earlier forks (e.g. `valid_at("Prague")`) remain excluded, as do `valid_at_transition_to(...)` tests: no transition fork to `BinaryTree` exists. ## Further Help diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/forks.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/forks.py index df04e8d4a1..4018dc1911 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/forks.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/forks.py @@ -1092,6 +1092,34 @@ def _process_with_marker_args( return resulting_set +def expand_forks_with_inherited_validity( + forks: Set[Fork | TransitionFork], +) -> Set[Fork | TransitionFork]: + """ + Add every fork that declares `inherits_exact_fork_validity` and + whose parent is in `forks` (transitively, so a chain of such forks + is followed). + + This lets a development fork that keeps its parent's semantics, + such as `BinaryTree` over `Amsterdam`, run tests pinned to the + parent with `valid_at`, without naming the development fork in + every marker. + """ + expanded = set(forks) + changed = True + while changed: + changed = False + for fork in ALL_FORKS: + if fork in expanded: + continue + if not fork.inherits_exact_fork_validity(): + continue + if fork.parent() in expanded: + expanded.add(fork) + changed = True + return expanded + + class ValidAt(ValidityMarker): """ Marker to specify each fork individually for which the test is valid. @@ -1111,13 +1139,20 @@ def test_something_only_valid_at_london_and_cancun( In this example, the test will only be filled for the London and Cancun forks. + + A development fork declared with `inherits_exact_fork_validity=True` + is additionally selected wherever its parent fork is: `BinaryTree` + keeps Amsterdam's execution semantics, so `valid_at("Amsterdam")` + (or an EIP name resolving to Amsterdam) also selects `BinaryTree`. """ def _process_with_marker_args( self, *fork_args: str ) -> Set[Fork | TransitionFork]: """Process the fork arguments.""" - return self.process_fork_arguments(*fork_args) + return expand_forks_with_inherited_validity( + self.process_fork_arguments(*fork_args) + ) class ValidAtTransitionTo( diff --git a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_markers.py b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_markers.py index 696d5770d3..dcbad13bb7 100644 --- a/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_markers.py +++ b/packages/testing/src/execution_testing/cli/pytest_commands/plugins/forks/tests/test_markers.py @@ -80,6 +80,46 @@ def test_case(state_test): {"passed": 3, "failed": 0, "skipped": 0, "errors": 0}, id="valid_from_until", ), + pytest.param( + generate_test( + valid_at='"London", "Cancun"', + ), + [], + {"passed": 2, "failed": 0, "skipped": 0, "errors": 0}, + id="valid_at", + ), + pytest.param( + generate_test( + valid_at='"Amsterdam"', + ), + ["--fork=BinaryTree"], + # BinaryTree is declared with + # `inherits_exact_fork_validity=True`, so a test pinned to + # its parent Amsterdam also fills for it. + {"passed": 1, "failed": 0, "skipped": 0, "errors": 0}, + id="valid_at_dev_fork_inherits_validity", + ), + pytest.param( + generate_test( + valid_at='"EIP7981"', + ), + ["--fork=BinaryTree"], + # The EIP name resolves to Amsterdam, which BinaryTree + # inherits exact-fork validity from. + {"passed": 1, "failed": 0, "skipped": 0, "errors": 0}, + id="valid_at_eip_dev_fork_inherits_validity", + ), + pytest.param( + generate_test( + valid_at='"Cancun"', + ), + ["--fork=BinaryTree"], + # Inheritance follows the parent link only: BinaryTree's + # parent is Amsterdam, so a test pinned to an older fork + # stays excluded. + {"passed": 0, "failed": 0, "skipped": 0, "errors": 0}, + id="valid_at_dev_fork_older_fork_excluded", + ), pytest.param( generate_test( valid_from='"EIP3675"', diff --git a/packages/testing/src/execution_testing/forks/base_fork.py b/packages/testing/src/execution_testing/forks/base_fork.py index 08305f5eef..8c3e50949d 100644 --- a/packages/testing/src/execution_testing/forks/base_fork.py +++ b/packages/testing/src/execution_testing/forks/base_fork.py @@ -343,6 +343,7 @@ class BaseFork(ForkOpcodeInterface, metaclass=BaseForkMeta): _fork_by_timestamp: ClassVar[bool] = False _blob_constants: ClassVar[Dict[str, int]] = {} _deployed: ClassVar[bool] = True + _inherits_exact_fork_validity: ClassVar[bool] = False _enabled_eips: ClassVar[Set[int]] = set() _enabling_forks: ClassVar[Set[Type["BaseFork"]]] = set() @@ -362,6 +363,7 @@ def __init_subclass__( ruleset_name: Optional[str] = None, fork_by_timestamp: Optional[bool] = None, deployed: Optional[bool] = None, + inherits_exact_fork_validity: bool = False, update_blob_constants: Optional[Dict[str, int]] = None, engine_new_payload_version_bump: Optional[bool] = None, engine_forkchoice_updated_version_bump: Optional[bool] = None, @@ -376,6 +378,7 @@ def __init_subclass__( cls._solc_name = solc_name cls._ignore = ignore cls._bpo_fork = bpo_fork + cls._inherits_exact_fork_validity = inherits_exact_fork_validity cls._ruleset_name = ruleset_name cls._children = set() if fork_by_timestamp is None: @@ -1324,6 +1327,18 @@ def ignore(cls) -> bool: """Return whether the fork should be ignored during test generation.""" return cls._ignore + @classmethod + def inherits_exact_fork_validity(cls) -> bool: + """ + Return whether markers that name this fork's parent exactly + (`valid_at`) also select this fork. + + Declared per fork with the `inherits_exact_fork_validity` class + argument; meant for development forks that keep their parent's + semantics, so tests pinned to the parent remain meaningful. + """ + return cls._inherits_exact_fork_validity + @classmethod def bpo_fork(cls) -> bool: """Return whether the fork is a BPO fork.""" diff --git a/packages/testing/src/execution_testing/forks/forks/forks.py b/packages/testing/src/execution_testing/forks/forks/forks.py index 40929cafd7..4deede3f56 100644 --- a/packages/testing/src/execution_testing/forks/forks/forks.py +++ b/packages/testing/src/execution_testing/forks/forks/forks.py @@ -1654,12 +1654,17 @@ class Amsterdam( class BinaryTree( Amsterdam, deployed=False, + inherits_exact_fork_validity=True, ): """ Experimental EIP-8297 fork: state is committed through the Partitioned Binary Tree instead of the Merkle Patricia Trie. For testing purposes only. The commitment scheme is selected by the spec fork's own imports; the transition tool reflects it. + + The fork keeps Amsterdam's execution semantics, so it inherits + exact-fork validity: tests pinned to Amsterdam with `valid_at` + also run here. """ pass