Skip to content

feat(pytest): add two commands that consume blockchain test fixtures #339

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
fb88ac3
refactor(fw): use click to wrap the pytest fill & tf entry points
danceratopz Oct 24, 2023
9d8ed97
feat(pytest): add a skeleton consume cli command & pytest plugin
danceratopz Oct 25, 2023
35b208c
feat(pytest): add initial implementation of the consume plugin
danceratopz Oct 25, 2023
e0ab7b8
fix(pytest): remove duplicated test function from plugin module
danceratopz Oct 26, 2023
c31c23d
Merge branch 'main' into feat/pytest/add-a-verify-fixtures-as-a-separ…
danceratopz Oct 26, 2023
0a22cbf
feat(pytest): run blocktest with --single-test option; add evm_dump_dir
danceratopz Oct 26, 2023
ee66a76
fix(pytest): update help groups for consume command
danceratopz Oct 26, 2023
cac6077
feat(pytest): detect blocktest and --single-test availability
danceratopz Oct 26, 2023
da07ba2
feat(pytest): re-add test_fixtures test; change --single-test to --run
danceratopz Nov 2, 2023
ce20ba0
chore(pytest): remove restrictive & unnecessary xdist config for cons…
danceratopz Nov 2, 2023
c95b5a7
Merge branch 'main' into feat/pytest/add-a-verify-fixtures-as-a-separ…
danceratopz Nov 17, 2023
a6b149c
feat(pytest): add a friendlier command alias for fill
danceratopz Nov 17, 2023
863c095
feat(fw): add a re-write of the hive consensus simulator
danceratopz Nov 25, 2023
74efd5e
chore: provide entry_points as a package
danceratopz Nov 25, 2023
ddc5a5b
Revert "chore: provide entry_points as a package"
danceratopz Nov 25, 2023
b7f899b
Merge branch 'main' into feat/pytest/add-a-verify-fixtures-as-a-separ…
danceratopz Nov 29, 2023
09a27f6
chore: change hive.py package source to marioevz main
danceratopz Nov 29, 2023
30e2242
fix: fix test result propagation to hive server; move to hive plugin
danceratopz Nov 29, 2023
e2daceb
chore: update whitelist for pytest keywords
danceratopz Nov 29, 2023
3766bf1
docs: fix consume rlp module docstring
danceratopz Nov 30, 2023
36d1aa3
feat: sepcify genesis & block rlp files BufferedReader objects
danceratopz Nov 30, 2023
d18b026
refactor(rlp): remove unnecessary network & client connection setup/t…
danceratopz Nov 30, 2023
9f04df9
refactor(fw): move json fixture loader helper to common.json
danceratopz Nov 30, 2023
520d161
docs: update changelog
danceratopz Nov 30, 2023
90d3a62
fix: manually specify nethermind genesis target filename
danceratopz Nov 30, 2023
50085b6
feat(pytest): enable piping of `fill`'s json to `consume rlp` (#26)
danceratopz Nov 30, 2023
70969c3
fix(cli): fix pipe behaviour; make stdin explicit via cli flag
danceratopz Dec 1, 2023
ca58d33
feat(pytest): add an entrypoint that fills & runs all consume commands
danceratopz Dec 1, 2023
06a1ff0
feat(pytest): exit pytest gracefully if hive server connection fails
danceratopz Dec 1, 2023
632e090
feat(pytest): allow all consumer tests to run in a single pytest session
danceratopz Dec 4, 2023
f8918c2
feat(pytest): add the consume engine command (#27)
spencer-tb Feb 5, 2024
2fe8e72
Merge branch 'main' into feat/pytest/add-a-verify-fixtures-as-a-separ…
danceratopz Feb 7, 2024
5f3a986
fix: check withdrawals is present in engine_new_payload (hotfix)
danceratopz Feb 7, 2024
2f46bca
fix: hotfix that manually maps merge fixture network to Paris fork
danceratopz Feb 7, 2024
77513ad
fix: hotfix to avoid block.expected_exception check; we should use In…
danceratopz Feb 7, 2024
a8a1f54
chore: Skip blockchain_hive and state tests directories.
spencer-tb Feb 8, 2024
5f69b17
chore: small logging tweak as we expect to skip invalid blockchain te…
spencer-tb Feb 8, 2024
7f6e7a2
chore: add missing pyjwt package dependency
danceratopz Feb 8, 2024
0b52de7
fix(consume): fix consume direct by escaping fixture names
danceratopz Feb 8, 2024
da5b901
fix(consume): remove pytest_hive as a dependency to consume direct
danceratopz Feb 8, 2024
92c900a
fix(pytest): fix consume when receiving fixtures on stdin
danceratopz Feb 8, 2024
d4874f8
chore(pytest): add comment explaining location of generate_test_cases
danceratopz Feb 9, 2024
4492eb0
Merge branch 'main' into feat/pytest/add-a-verify-fixtures-as-a-separ…
danceratopz Apr 4, 2024
6e42d55
Merge branch 'main' into feat/pytest/add-a-verify-fixtures-as-a-separ…
danceratopz Apr 15, 2024
cf8e4ff
Merge branch 'main' into feat/pytest/add-a-verify-fixtures-as-a-separ…
danceratopz Apr 16, 2024
d33b344
Merge branch 'main' into feat/pytest/add-a-verify-fixtures-as-a-separ…
danceratopz Apr 16, 2024
582d7ee
feat: ignore index.json & raise more precise exception types
danceratopz Apr 17, 2024
126827d
feat: add test case pydantic models and a test index generator
danceratopz Apr 17, 2024
3d3ee4b
refactor(fw): consume direct & rlp for pydantic models and test index
danceratopz Apr 17, 2024
bad5ca0
chore: fix up and add tests_consume/ to tox -e framework
danceratopz Apr 17, 2024
1e84f43
fix: fix consume rlp with fixtures on stdin
danceratopz Apr 17, 2024
405236d
fix: fix consume rlp errors from tox fix-up
danceratopz Apr 17, 2024
cb2ccc6
chore: minor clean-up of comments
danceratopz Apr 18, 2024
2a95ef1
fix(rlp): test consensus via fixture.last_block_hash
danceratopz Apr 20, 2024
9dc75e6
fix(rlp): add excessBlobGas to genesis
danceratopz Apr 20, 2024
0413654
Merge branch 'main' into feat/pytest/add-a-verify-fixtures-as-a-separ…
danceratopz Apr 23, 2024
ff57e27
chore: fix github actions on macos-latest by installing cairo
danceratopz Apr 23, 2024
26174d6
Revert "chore: fix github actions on macos-latest by installing cairo"
danceratopz Apr 23, 2024
cf17a4b
chore: fix github actions by using macos-12
danceratopz Apr 23, 2024
f33537b
Merge branch 'main' into feat/pytest/add-a-verify-fixtures-as-a-separ…
danceratopz May 2, 2024
fb1e343
chore: update whitelist
danceratopz May 5, 2024
59e3257
fix(rlp): strip 0x prefix to alloc addresses for nethermind
danceratopz May 5, 2024
c60a232
refactor(rlp): simplify genesis state creation; improve names of pyte…
danceratopz May 5, 2024
f12e6c7
chore(rlp): improve comments
danceratopz May 5, 2024
d9d5292
feat(types): add validation aliases to create headers from rpc data
danceratopz May 5, 2024
eacabd9
feat(rlp): compare expected & client genesis headers upon hash mismatch
danceratopz May 5, 2024
2b17ec6
chore(rlp): minor, clean-up comments
danceratopz May 5, 2024
c4e15db
feat(rlp): report & save the time taken for various test stages
danceratopz May 5, 2024
870c8a7
feat(rlp): better formatting of timings in terminal output
danceratopz May 6, 2024
68ce59e
Merge branch 'main' into feat/pytest/add-a-verify-fixtures-as-a-separ…
danceratopz May 7, 2024
c7ca98c
Revert "chore: fix github actions by using macos-12"
danceratopz May 7, 2024
3571458
chore: update whitelist
danceratopz May 7, 2024
b062a13
chore: improve docstrings
danceratopz May 7, 2024
42a2384
refactor(consume): move the consume plugins into one directory
danceratopz May 7, 2024
4835c09
fix(consume): don't write html report from fill if output is stdout
danceratopz May 7, 2024
f5d3e6f
refactor(consume): move pytest ini files to plugin directory
danceratopz May 7, 2024
7db2204
chore(fw): remove (currently) unused rpc modules to reduce pr scope
danceratopz May 7, 2024
9fb5b0c
refactor(consume): move common simulator fixture to its own plugin
danceratopz May 7, 2024
f8bf467
chore(consume): clean-up
danceratopz May 7, 2024
6ba1a21
fix(pytest): fix consume ini file names in test_help plugin
danceratopz May 7, 2024
7d0e706
feat(pytest): print fill's default output dir in --help
danceratopz May 7, 2024
5e1ef20
feat(consume): add default html reports - WIP
danceratopz May 7, 2024
5b26aa1
chore(cli): fix tox
danceratopz May 7, 2024
04c5f0c
fix(docs): changelog
marioevz May 7, 2024
889a28a
fix(fw): add `get_fork` method to fixture types
marioevz May 7, 2024
c61b1e6
fix(fw): fix fixture type
marioevz May 7, 2024
33da66a
fix(fw): misc
marioevz May 7, 2024
fac7927
fix(pytest): call consume/fill configure hook before pytest-html hook
danceratopz May 8, 2024
623f3fa
fix(rlp): fix pydantic v2 dict() warning
danceratopz May 8, 2024
229e6c1
chore(rlp): make comment more readable
danceratopz May 8, 2024
4a3642e
refactor(rlp): make pydantic model comparison more idiosyncratic
danceratopz May 8, 2024
18611cd
feat(consume): use --tb=short for pytest backtraces cf #542
danceratopz May 8, 2024
69771f1
Merge branch 'feat/pytest/add-a-verify-fixtures-as-a-separate-pytest-…
danceratopz May 8, 2024
a3836e9
feat(rlp): remove HIVE_SKIP_POW, add HIVE_NODETYPE to environment
danceratopz May 8, 2024
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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ site
venv-docs/
.pyspelling_en.dict

# cached fixture downloads (consume)
cached_downloads/
# pytest report
assets
*.html
5 changes: 5 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ Test fixtures for use by clients are available for each release on the [Github r

### 🛠️ Framework

- ✨ Adds two `consume` commands [#339](https://github.com/ethereum/execution-spec-tests/pull/339):

1. `consume direct` - Execute a test fixture directly against a client using a `blocktest`-like command (currently only geth supported).
2. `consume rlp` - Execute a test fixture in a hive simulator against a client that imports the test's genesis config and blocks as RLP upon startup. This is a re-write of the [ethereum/consensus](https://github.com/ethereum/hive/tree/master/simulators/ethereum/consensus) Golang simulator.

- ✨ Add Prague to forks ([#419](https://github.com/ethereum/execution-spec-tests/pull/419)).
- ✨ Improve handling of the argument passed to `solc --evm-version` when compiling Yul code ([#418](https://github.com/ethereum/execution-spec-tests/pull/418)).
- 🐞 Fix `fill -m yul_test` which failed to filter tests that are (dynamically) marked as a yul test ([#418](https://github.com/ethereum/execution-spec-tests/pull/418)).
Expand Down
10 changes: 9 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ package_dir =
python_requires = >=3.10

install_requires =
ethereum@git+https://github.com/ethereum/execution-specs.git
click>=8.1.0,<9
ethereum@git+https://github.com/ethereum/execution-specs
hive.py@git+https://github.com/danceratopz/hive.py@chore/setup.cfg/move-mypy-deps-to-lint-extras
setuptools
types-setuptools
PyJWT>=2.3.0,<3
tenacity>8.2.0,<9
bidict>=0.23,<1
requests>=2.31.0,<3
colorlog>=6.7.0,<7
Expand All @@ -47,12 +51,16 @@ ethereum_test_forks =
py.typed
evm_transition_tool =
py.typed
pytest_plugins =
py.typed

[options.entry_points]
console_scripts =
fill = cli.pytest_commands:fill
tf = cli.pytest_commands:tf
checkfixtures = cli.check_fixtures:check_fixtures
consume = cli.pytest_commands:consume
genindex = cli.gen_index:generate_fixtures_index_cli
gentest = cli.gentest:make_test
pyspelling_soft_fail = cli.tox_helpers:pyspelling
markdownlintcli2_soft_fail = cli.tox_helpers:markdownlint
Expand Down
218 changes: 218 additions & 0 deletions src/cli/gen_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
"""
Generate an index file of all the json fixtures in the specified directory.
"""
import datetime
import json
import os
from pathlib import Path
from typing import List

import click
import rich
from rich.progress import (
BarColumn,
Column,
Progress,
TaskProgressColumn,
TextColumn,
TimeElapsedColumn,
)

from ethereum_test_tools.common.base_types import HexNumber
from ethereum_test_tools.spec.consume.types import IndexFile, TestCaseIndexFile
from ethereum_test_tools.spec.file.types import Fixtures
from evm_transition_tool import FixtureFormats

from .hasher import HashableItem


def count_json_files_exclude_index(start_path: Path) -> int:
"""
Return the number of json files in the specified directory, excluding
index.json files and tests in "blockchain_tests_hive".
"""
json_file_count = sum(
1
for file in start_path.rglob("*.json")
if file.name != "index.json" and "blockchain_tests_hive" not in file.parts
)
return json_file_count


def infer_fixture_format_from_path(file: Path) -> FixtureFormats:
"""
Attempt to infer the fixture format from the file path.
"""
if "blockchain_tests_hive" in file.parts:
return FixtureFormats.BLOCKCHAIN_TEST_HIVE
if "blockchain_tests" in file.parts:
return FixtureFormats.BLOCKCHAIN_TEST
if "state_tests" in file.parts:
return FixtureFormats.STATE_TEST
return FixtureFormats.UNSET_TEST_FORMAT


@click.command(
help=(
"Generate an index file of all the json fixtures in the specified directory."
"The index file is saved as 'index.json' in the specified directory."
)
)
@click.option(
"--input",
"-i",
"input_dir",
type=click.Path(exists=True, file_okay=False, dir_okay=True, readable=True),
required=True,
help="The input directory",
)
@click.option(
"--disable-infer-format",
"-d",
"disable_infer_format",
is_flag=True,
default=False,
expose_value=True,
help="Don't try to guess the fixture format from the json file's path.",
)
@click.option(
"--quiet",
"-q",
"quiet_mode",
is_flag=True,
default=False,
expose_value=True,
help="Don't show the progress bar while processing fixture files.",
)
@click.option(
"--force",
"-f",
"force_flag",
is_flag=True,
default=False,
expose_value=True,
help="Force re-generation of the index file, even if it already exists.",
)
def generate_fixtures_index_cli(
input_dir: str, quiet_mode: bool, force_flag: bool, disable_infer_format: bool
):
"""
The CLI wrapper to an index of all the fixtures in the specified directory.
"""
generate_fixtures_index(
Path(input_dir),
quiet_mode=quiet_mode,
force_flag=force_flag,
disable_infer_format=disable_infer_format,
)


def generate_fixtures_index(
input_path: Path,
quiet_mode: bool = False,
force_flag: bool = False,
disable_infer_format: bool = False,
):
"""
Generate an index file (index.json) of all the fixtures in the specified
directory.
"""
total_files = 0
if not os.path.isdir(input_path): # caught by click if using via cli
raise FileNotFoundError(f"The directory {input_path} does not exist.")
if not quiet_mode:
total_files = count_json_files_exclude_index(input_path)

output_file = Path(f"{input_path}/index.json")
try:
root_hash = HashableItem.from_folder(folder_path=input_path).hash()
except (KeyError, TypeError):
root_hash = b"" # just regenerate a new index file

if not force_flag and output_file.exists():
index_data: IndexFile
try:
with open(output_file, "r") as f:
index_data = IndexFile(**json.load(f))
if index_data.root_hash and index_data.root_hash == HexNumber(root_hash):
if not quiet_mode:
rich.print(f"Index file [bold cyan]{output_file}[/] is up-to-date.")
return
except Exception as e:
rich.print(f"Ignoring exception {e}")
rich.print(f"...generating a new index file [bold cyan]{output_file}[/]")

filename_display_width = 25
with Progress(
TextColumn(
f"[bold cyan]{{task.fields[filename]:<{filename_display_width}}}[/]",
justify="left",
table_column=Column(ratio=1),
),
BarColumn(
complete_style="green3",
finished_style="bold green3",
table_column=Column(ratio=2),
),
TaskProgressColumn(),
TimeElapsedColumn(),
expand=False,
disable=quiet_mode,
) as progress:
task_id = progress.add_task("[cyan]Processing files...", total=total_files, filename="...")

test_cases: List[TestCaseIndexFile] = []
for file in input_path.rglob("*.json"):
if file.name == "index.json":
continue
if "blockchain_tests_hive" in file.parts:
continue

try:
fixture_format = None
if not disable_infer_format:
fixture_format = infer_fixture_format_from_path(file)
fixtures = Fixtures.from_file(file, fixture_format=fixture_format)
except Exception as e:
rich.print(f"[red]Error loading fixtures from {file}[/red]")
raise e

relative_file_path = Path(file).absolute().relative_to(Path(input_path).absolute())
for fixture_name, fixture in fixtures.items():
test_cases.append(
TestCaseIndexFile(
id=fixture_name,
json_path=relative_file_path,
fixture_hash=fixture.info.get("hash", None),
fork=fixture.get_fork(),
format=fixture.format,
)
)

display_filename = file.name
if len(display_filename) > filename_display_width:
display_filename = display_filename[: filename_display_width - 3] + "..."
else:
display_filename = display_filename.ljust(filename_display_width)

progress.update(task_id, advance=1, filename=display_filename)

progress.update(
task_id,
completed=total_files,
filename="Indexing complete 🦄".ljust(filename_display_width),
)

index = IndexFile(
test_cases=test_cases,
root_hash=root_hash,
created_at=datetime.datetime.now(),
test_count=len(test_cases),
)

with open(output_file, "w") as f:
f.write(index.model_dump_json(exclude_none=False, indent=2))


if __name__ == "__main__":
generate_fixtures_index_cli()
18 changes: 12 additions & 6 deletions src/cli/hasher.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,16 @@ def from_json_file(cls, *, file_path: Path, parents: List[str]) -> "HashableItem
with file_path.open("r") as f:
data = json.load(f)
for key, item in sorted(data.items()):
assert isinstance(item, dict), f"Expected dict, got {type(item)}"
assert "_info" in item, f"Expected _info in {key}"
assert "hash" in item["_info"], f"Expected hash in {key}"
assert isinstance(
item["_info"]["hash"], str
), f"Expected hash to be a string in {key}, got {type(item['_info']['hash'])}"
if not isinstance(item, dict):
raise TypeError(f"Expected dict, got {type(item)} for {key}")
if "_info" not in item:
raise KeyError(f"Expected '_info' in {key}")
if "hash" not in item["_info"]:
raise KeyError(f"Expected 'hash' in {key}")
if not isinstance(item["_info"]["hash"], str):
raise TypeError(
f"Expected hash to be a string in {key}, got {type(item['_info']['hash'])}"
)
item_hash_bytes = bytes.fromhex(item["_info"]["hash"][2:])
items[key] = cls(
type=HashableItemType.TEST,
Expand All @@ -96,6 +100,8 @@ def from_folder(cls, *, folder_path: Path, parents: List[str] = []) -> "Hashable
"""
items = {}
for file_path in sorted(folder_path.iterdir()):
if file_path.name == "index.json":
continue
if file_path.is_file() and file_path.suffix == ".json":
item = cls.from_json_file(
file_path=file_path, parents=parents + [folder_path.name]
Expand Down
Loading