Skip to content

Commit a131180

Browse files
winsvegamarioevz
andauthored
feat(unit-tests): verify transition tool support (#1263)
* unit test to check transition tool support * feat(clis): Implement `is_installed` method for `EthereumCLI` * fix(clis): Geth non-optional binary * fix(clis): Tests: Make t8n support unit tests run on installed t8n tools only * fix(clis): type fix * fix(clis): unit test non-sorted forks * feat(clis): Add unit test to CI-only checks * build evms for unit tests framework CI * support evmone action on macOS * add Prague resolution for eels in CI * refactor(fw): Generalize `installed_t8n` fixture --------- Co-authored-by: Mario Vega <[email protected]>
1 parent 66ca184 commit a131180

File tree

9 files changed

+363
-22
lines changed

9 files changed

+363
-22
lines changed

.github/actions/build-evm-client/evmone/action.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,15 @@ runs:
2727
uses: jwlawson/actions-setup-cmake@802fa1a2c4e212495c05bf94dba2704a92a472be
2828
with:
2929
cmake-version: '3.x'
30-
- name: "Install GMP"
30+
- name: "Install GMP Linux"
31+
if: runner.os == 'Linux'
3132
shell: bash
3233
run: sudo apt-get -q update && sudo apt-get -qy install libgmp-dev
34+
- name: Install GMP macOS
35+
if: runner.os == 'macOS'
36+
shell: bash
37+
run: |
38+
brew update && brew install gmp
3339
- name: Build evmone binary
3440
shell: bash
3541
run: |

.github/configs/eels_resolutions.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,9 @@
2828
},
2929
"Cancun": {
3030
"path": "../../execution-specs/src/ethereum/cancun"
31+
},
32+
"Prague": {
33+
"git_url": "https://github.com/ethereum/execution-specs.git",
34+
"branch": "devnets/prague/6"
3135
}
3236
}

.github/workflows/tox_verify.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ jobs:
113113
cache-dependency-glob: "uv.lock"
114114
version: ${{ vars.UV_VERSION }}
115115
python-version: ${{ matrix.python }}
116+
- name: Build EVMONE EVM
117+
uses: ./.github/actions/build-evm-client/evmone
118+
with:
119+
targets: "evmone-t8n"
120+
- name: Build GETH EVM
121+
uses: ./.github/actions/build-evm-client/geth
116122
- name: Run tox - run framework unit tests with pytest
117123
env:
118124
EELS_RESOLUTIONS_FILE: ${{ github.workspace }}/.github/configs/eels_resolutions.json

src/conftest.py

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
"""Local pytest configuration for framework tests."""
1+
"""Local pytest configuration used on multiple framework tests."""
22

33
import os
4-
from typing import Generator
4+
from typing import Dict, Generator
55

66
import pytest
77

8-
from ethereum_clis import ExecutionSpecsTransitionTool, TransitionTool
8+
from ethereum_clis import BesuTransitionTool, ExecutionSpecsTransitionTool, TransitionTool
99

1010

1111
def pytest_runtest_setup(item):
@@ -16,21 +16,82 @@ def pytest_runtest_setup(item):
1616
pytest.skip("Skipping test because pytest-xdist is running with more than one worker.")
1717

1818

19-
DEFAULT_T8N_FOR_UNIT_TESTS = ExecutionSpecsTransitionTool
19+
DEFAULT_TRANSITION_TOOL_FOR_UNIT_TESTS = ExecutionSpecsTransitionTool
20+
21+
INSTALLED_TRANSITION_TOOLS = [
22+
transition_tool
23+
for transition_tool in TransitionTool.registered_tools
24+
if (
25+
transition_tool.is_installed()
26+
# Currently, Besu has the same `default_binary` as Geth, so we can't use `is_installed`.
27+
and transition_tool != BesuTransitionTool
28+
)
29+
]
2030

2131

2232
@pytest.fixture(scope="session")
23-
def default_t8n_instance() -> Generator[TransitionTool, None, None]:
24-
"""Fixture to provide a default t8n instance."""
25-
instance = ExecutionSpecsTransitionTool()
26-
instance.start_server()
27-
yield instance
28-
instance.shutdown()
33+
def installed_transition_tool_instances() -> Generator[
34+
Dict[str, TransitionTool | Exception], None, None
35+
]:
36+
"""Return all instantiated transition tools."""
37+
instances: Dict[str, TransitionTool | Exception] = {}
38+
for transition_tool_class in INSTALLED_TRANSITION_TOOLS:
39+
try:
40+
instances[transition_tool_class.__name__] = transition_tool_class()
41+
except Exception as e:
42+
# Record the exception in order to provide context when failing the appropriate test
43+
instances[transition_tool_class.__name__] = e
44+
yield instances
45+
for instance in instances.values():
46+
if isinstance(instance, TransitionTool):
47+
instance.shutdown()
48+
49+
50+
@pytest.fixture(
51+
params=INSTALLED_TRANSITION_TOOLS,
52+
ids=[transition_tool_class.__name__ for transition_tool_class in INSTALLED_TRANSITION_TOOLS],
53+
)
54+
def installed_t8n(
55+
request: pytest.FixtureRequest,
56+
installed_transition_tool_instances: Dict[str, TransitionTool | Exception],
57+
) -> TransitionTool:
58+
"""
59+
Return an instantiated transition tool.
60+
61+
Tests using this fixture will be automatically parameterized with all
62+
installed transition tools.
63+
"""
64+
transition_tool_class = request.param
65+
assert issubclass(transition_tool_class, TransitionTool)
66+
assert transition_tool_class.__name__ in installed_transition_tool_instances, (
67+
f"{transition_tool_class.__name__} not instantiated"
68+
)
69+
instance_or_error = installed_transition_tool_instances[transition_tool_class.__name__]
70+
if isinstance(instance_or_error, Exception):
71+
raise Exception(
72+
f"Failed to instantiate {transition_tool_class.__name__}"
73+
) from instance_or_error
74+
return instance_or_error
2975

3076

3177
@pytest.fixture
3278
def default_t8n(
33-
default_t8n_instance: TransitionTool,
79+
installed_transition_tool_instances: Dict[str, TransitionTool | Exception],
3480
) -> TransitionTool:
3581
"""Fixture to provide a default t8n instance."""
36-
return default_t8n_instance
82+
instance = installed_transition_tool_instances.get(
83+
DEFAULT_TRANSITION_TOOL_FOR_UNIT_TESTS.__name__
84+
)
85+
if instance is None:
86+
raise Exception(f"Failed to instantiate {DEFAULT_TRANSITION_TOOL_FOR_UNIT_TESTS.__name__}")
87+
if isinstance(instance, Exception):
88+
raise Exception(
89+
f"Failed to instantiate {DEFAULT_TRANSITION_TOOL_FOR_UNIT_TESTS.__name__}"
90+
) from instance
91+
return instance
92+
93+
94+
@pytest.fixture(scope="session")
95+
def running_in_ci() -> bool:
96+
"""Return whether the test is running in a CI environment."""
97+
return "CI" in os.environ

src/ethereum_clis/clis/geth.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,13 @@ class GethEvm(EthereumCLI):
104104

105105
def __init__(
106106
self,
107-
binary: Path,
107+
binary: Optional[Path] = None,
108108
trace: bool = False,
109-
exception_mapper: ExceptionMapper | None = None,
110109
):
111110
"""Initialize the GethEvm class."""
112-
self.binary = binary
111+
self.binary = binary if binary else self.default_binary
113112
self.trace = trace
114-
self.exception_mapper = exception_mapper if exception_mapper else GethExceptionMapper()
113+
self._info_metadata: Optional[Dict[str, Any]] = {}
115114

116115
def _run_command(self, command: List[str]) -> subprocess.CompletedProcess:
117116
try:
@@ -167,12 +166,18 @@ class GethTransitionTool(GethEvm, TransitionTool):
167166
trace: bool
168167
t8n_use_stream = True
169168

170-
def __init__(self, *, binary: Path, trace: bool = False):
169+
def __init__(
170+
self,
171+
*,
172+
exception_mapper: Optional[ExceptionMapper] = None,
173+
binary: Optional[Path] = None,
174+
trace: bool = False,
175+
):
171176
"""Initialize the GethTransitionTool class."""
177+
if not exception_mapper:
178+
exception_mapper = GethExceptionMapper()
172179
GethEvm.__init__(self, binary=binary, trace=trace)
173-
TransitionTool.__init__(
174-
self, exception_mapper=self.exception_mapper, binary=binary, trace=trace
175-
)
180+
TransitionTool.__init__(self, binary=binary, exception_mapper=exception_mapper)
176181
help_command = [str(self.binary), str(self.subcommand), "--help"]
177182
result = self._run_command(help_command)
178183
self.help_string = result.stdout

src/ethereum_clis/ethereum_cli.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,18 @@ def detect_binary(cls, binary_output: str) -> bool:
126126

127127
return cls.detect_binary_pattern.match(binary_output) is not None
128128

129+
@classmethod
130+
def is_installed(cls, binary_path: Optional[Path] = None) -> bool:
131+
"""Return whether the tool is installed in the current system."""
132+
if binary_path is None:
133+
binary_path = cls.default_binary
134+
else:
135+
resolved_path = Path(os.path.expanduser(binary_path)).resolve()
136+
if resolved_path.exists():
137+
binary_path = resolved_path
138+
binary = shutil.which(binary_path)
139+
return binary is not None
140+
129141
def version(self) -> str:
130142
"""Return the name and version of the CLI as reported by the CLI's version flag."""
131143
if self.cached_version is None:

0 commit comments

Comments
 (0)