Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(fw): augment & use Fixtures model in fixture collection #508

Merged
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
2 changes: 1 addition & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Test fixtures for use by clients are available for each release on the [Github r
- 🐞 Fix incorrect `!=` operator for `FixedSizeBytes` ([#477](https://github.com/ethereum/execution-spec-tests/pull/477)).
- ✨ Add Macro enum that represents byte sequence of Op instructions ([#457](https://github.com/ethereum/execution-spec-tests/pull/457))
- ✨ Number of parameters used to call opcodes (to generate bytecode) is now checked ([#492](https://github.com/ethereum/execution-spec-tests/pull/492)).
- ✨ Libraries have been refactored to use `pydantic` for type checking in most test types ([#486](https://github.com/ethereum/execution-spec-tests/pull/486), [#501](https://github.com/ethereum/execution-spec-tests/pull/501)).
- ✨ Libraries have been refactored to use `pydantic` for type checking in most test types ([#486](https://github.com/ethereum/execution-spec-tests/pull/486), [#501](https://github.com/ethereum/execution-spec-tests/pull/501), [#508](https://github.com/ethereum/execution-spec-tests/pull/508)).

### 🔧 EVM Tools

Expand Down
13 changes: 1 addition & 12 deletions src/ethereum_test_tools/spec/base/base_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from itertools import count
from os import path
from pathlib import Path
from typing import Any, Callable, ClassVar, Dict, Generator, Iterator, List, Optional, TextIO
from typing import Any, Callable, ClassVar, Dict, Generator, Iterator, List, Optional

from pydantic import BaseModel, Field

Expand Down Expand Up @@ -111,17 +111,6 @@ def fill_info(
if ref_spec is not None:
ref_spec.write_info(self.info)

@classmethod
def collect_into_file(cls, fd: TextIO, fixtures: Dict[str, "BaseFixture"]):
"""
For all formats, we simply join the json fixtures into a single file.
"""
json_fixtures: Dict[str, Dict[str, Any]] = {}
for name, fixture in fixtures.items():
assert isinstance(fixture, cls), f"Invalid fixture type: {type(fixture)}"
json_fixtures[name] = fixture.json_dict_with_info()
json.dump(json_fixtures, fd, indent=4)


class BaseTest(BaseModel):
"""
Expand Down
18 changes: 18 additions & 0 deletions src/ethereum_test_tools/spec/file/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"blockchain_test_hive", "blockchain_test", "state_test", "unset_test_format"
]

FixtureModel = BlockchainFixture | BlockchainHiveFixture | StateFixture


class BaseFixturesRootModel(RootModel):
"""
Expand All @@ -32,6 +34,9 @@ class BaseFixturesRootModel(RootModel):

root: Dict[str, Any]

def __setitem__(self, key: str, value: Any): # noqa: D105
self.root[key] = value

def __getitem__(self, item): # noqa: D105
return self.root[item]

Expand All @@ -53,6 +58,19 @@ def values(self): # noqa: D102
def items(self): # noqa: D102
return self.root.items()

def collect_into_file(self, file_path: Path):
"""
For all formats, we join the fixtures as json into a single file.

Note: We don't use pydantic model_dump_json() on the Fixtures object as we
add the hash to the info field on per-fixture basis.
"""
json_fixtures: Dict[str, Dict[str, Any]] = {}
for name, fixture in self.items():
json_fixtures[name] = fixture.json_dict_with_info()
with open(file_path, "w") as f:
json.dump(json_fixtures, f, indent=4)

@classmethod
def from_file(
cls,
Expand Down
41 changes: 15 additions & 26 deletions src/ethereum_test_tools/spec/fixture_collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from evm_transition_tool import FixtureFormats, TransitionTool

from .base.base_test import BaseFixture
from .file.types import Fixtures


def strip_test_prefix(name: str) -> str:
Expand Down Expand Up @@ -103,8 +104,7 @@ class FixtureCollector:
base_dump_dir: Optional[Path] = None

# Internal state
all_fixtures: Dict[Path, Dict[str, BaseFixture]] = field(default_factory=dict)
json_path_to_fixture_type: Dict[Path, FixtureFormats] = field(default_factory=dict)
all_fixtures: Dict[Path, Fixtures] = field(default_factory=dict)
json_path_to_test_item: Dict[Path, TestInfo] = field(default_factory=dict)

def get_fixture_basename(self, info: TestInfo) -> Path:
Expand Down Expand Up @@ -138,17 +138,8 @@ def add_fixture(self, info: TestInfo, fixture: BaseFixture) -> None:
/ fixture.format.output_base_dir_name
/ fixture_basename.with_suffix(fixture.format.output_file_extension)
)
if fixture_path not in self.all_fixtures: # relevant when we group by test function
self.all_fixtures[fixture_path] = {}
if fixture_path in self.json_path_to_fixture_type:
if self.json_path_to_fixture_type[fixture_path] != fixture.format:
raise Exception(
f"Fixture {fixture_path} has two different types: "
f"{self.json_path_to_fixture_type[fixture_path]} "
f"and {fixture.format}"
)
else:
self.json_path_to_fixture_type[fixture_path] = fixture.format
if fixture_path not in self.all_fixtures.keys(): # relevant when we group by test function
self.all_fixtures[fixture_path] = Fixtures(root={})
self.json_path_to_test_item[fixture_path] = info

self.all_fixtures[fixture_path][info.id] = fixture
Expand All @@ -160,24 +151,22 @@ def dump_fixtures(self) -> None:
os.makedirs(self.output_dir, exist_ok=True)
for fixture_path, fixtures in self.all_fixtures.items():
os.makedirs(fixture_path.parent, exist_ok=True)

# Get the first fixture to dump to get its type
fixture = next(iter(fixtures.values()))
# Call class method to dump all the fixtures
with open(fixture_path, "w") as fd:
fixture.collect_into_file(fd, fixtures)
if len({fixture.format for fixture in fixtures.values()}) != 1:
raise TypeError("All fixtures in a single file must have the same format.")
fixtures.collect_into_file(fixture_path)

def verify_fixture_files(self, evm_fixture_verification: TransitionTool) -> None:
"""
Runs `evm [state|block]test` on each fixture.
"""
for fixture_path, fixture_format in self.json_path_to_fixture_type.items():
if FixtureFormats.is_verifiable(fixture_format):
info = self.json_path_to_test_item[fixture_path]
verify_fixtures_dump_dir = self._get_verify_fixtures_dump_dir(info)
evm_fixture_verification.verify_fixture(
fixture_format, fixture_path, verify_fixtures_dump_dir
)
for fixture_path, name_fixture_dict in self.all_fixtures.items():
for fixture_name, fixture in name_fixture_dict.items():
if FixtureFormats.is_verifiable(fixture.format):
info = self.json_path_to_test_item[fixture_path]
verify_fixtures_dump_dir = self._get_verify_fixtures_dump_dir(info)
evm_fixture_verification.verify_fixture(
fixture.format, fixture_path, verify_fixtures_dump_dir
)

def _get_verify_fixtures_dump_dir(
self,
Expand Down
Loading