Skip to content

Commit 19057d7

Browse files
committed
Merge commit 'b272eb28511f071abed44936c19db610fe451cef' into split-fill
2 parents 395c88e + b272eb2 commit 19057d7

File tree

265 files changed

+15516
-4441
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

265 files changed

+15516
-4441
lines changed

packages/tests/src/cli/check_fixtures.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,15 @@ def check_json(json_file_path: Path):
2727
"""
2828
Check all fixtures in the specified json file:
2929
1. Load the json file into a pydantic model. This checks there are no
30-
Validation errors when loading fixtures into EEST models.
30+
Validation errors when loading fixtures into EEST models.
3131
2. Serialize the loaded pydantic model to "json" (actually python data
32-
structures, ready to written as json).
32+
structures, ready to written as json).
3333
3. Load the serialized data back into a pydantic model (to get an updated
34-
hash) from step 2.
34+
hash) from step 2.
3535
4. Compare hashes:
3636
a. Compare the newly calculated hashes from step 2. and 3. and
37-
b. If present, compare info["hash"] with the calculated hash from step 2.
37+
b. If present, compare info["hash"] with the calculated hash from
38+
step 2.
3839
"""
3940
fixtures: Fixtures = Fixtures.model_validate_json(json_file_path.read_text())
4041
fixtures_json = to_json(fixtures)
@@ -86,7 +87,9 @@ def check_json(json_file_path: Path):
8687
help="Stop and raise any exceptions encountered while checking fixtures.",
8788
)
8889
def check_fixtures(input_str: str, quiet_mode: bool, stop_on_error: bool):
89-
"""Perform some checks on the fixtures contained in the specified directory."""
90+
"""
91+
Perform some checks on the fixtures contained in the specified directory.
92+
"""
9093
input_path = Path(input_str)
9194
success = True
9295
file_count = 0

packages/tests/src/cli/compare_fixtures.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""
22
Compare two fixture folders and remove duplicates based on fixture hashes.
33
4-
This tool reads the .meta/index.json files from two fixture directories and identifies
5-
fixtures with identical hashes on a test case basis, then removes the duplicates from
6-
both of the folders. Used within the coverage workflow.
4+
This tool reads the .meta/index.json files from two fixture directories and
5+
identifies fixtures with identical hashes on a test case basis, then removes
6+
the duplicates from both of the folders. Used within the coverage workflow.
77
"""
88

99
import json
@@ -95,8 +95,8 @@ def batch_remove_fixtures_from_files(removals_by_file):
9595

9696
def rewrite_index(folder: Path, index: IndexFile, dry_run: bool):
9797
"""
98-
Rewrite the index to the correct index file, or if the test count was reduced to zero,
99-
the entire directory is deleted.
98+
Rewrite the index to the correct index file, or if the test count was
99+
reduced to zero, the entire directory is deleted.
100100
"""
101101
if len(index.test_cases) > 0:
102102
# Just rewrite the index
@@ -130,7 +130,9 @@ def main(
130130
dry_run: bool,
131131
abort_on_empty_patch: bool,
132132
):
133-
"""Compare two fixture folders and remove duplicates based on fixture hashes."""
133+
"""
134+
Compare two fixture folders and remove duplicates based on fixture hashes.
135+
"""
134136
try:
135137
# Load indices
136138
base_index = load_index(base)
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
#!/usr/bin/env python
2+
"""
3+
Compare opcode counts between two folders of JSON fixtures.
4+
5+
This script crawls two folders for JSON files, parses them using the Fixtures
6+
model, and compares the opcode_count field from the info section between
7+
fixtures with the same name.
8+
"""
9+
10+
import sys
11+
from pathlib import Path
12+
from typing import Dict, List, Optional
13+
14+
import click
15+
16+
from ethereum_clis.cli_types import OpcodeCount
17+
from ethereum_test_fixtures.file import Fixtures
18+
19+
20+
def find_json_files(directory: Path) -> List[Path]:
21+
"""Find all JSON files in a directory, excluding index.json files."""
22+
json_files = []
23+
if directory.is_dir():
24+
for file_path in directory.rglob("*.json"):
25+
if file_path.name != "index.json":
26+
json_files.append(file_path)
27+
return json_files
28+
29+
30+
def load_fixtures_from_file(
31+
file_path: Path, remove_from_fixture_names: List[str]
32+
) -> Optional[Fixtures]:
33+
"""Load fixtures from a JSON file using the Fixtures model."""
34+
try:
35+
fixtures = Fixtures.model_validate_json(file_path.read_text())
36+
renames = []
37+
for k in fixtures.root:
38+
new_name = None
39+
for s in remove_from_fixture_names:
40+
if s in k:
41+
if new_name is None:
42+
new_name = k.replace(s, "")
43+
else:
44+
new_name = new_name.replace(s, "")
45+
if new_name is not None:
46+
renames.append((k, new_name))
47+
for old_name, new_name in renames:
48+
fixtures.root[new_name] = fixtures.root.pop(old_name)
49+
return fixtures
50+
except Exception as e:
51+
print(f"Error loading {file_path}: {e}", file=sys.stderr)
52+
return None
53+
54+
55+
def extract_opcode_counts_from_fixtures(fixtures: Fixtures) -> Dict[str, OpcodeCount]:
56+
"""Extract opcode_count from info field for each fixture."""
57+
opcode_counts = {}
58+
for fixture_name, fixture in fixtures.items():
59+
if hasattr(fixture, "info") and fixture.info and "opcode_count" in fixture.info:
60+
try:
61+
opcode_count = OpcodeCount.model_validate(fixture.info["opcode_count"])
62+
opcode_counts[fixture_name] = opcode_count
63+
except Exception as e:
64+
print(f"Error parsing opcode_count for {fixture_name}: {e}", file=sys.stderr)
65+
return opcode_counts
66+
67+
68+
def load_all_opcode_counts(
69+
directory: Path, remove_from_fixture_names: List[str]
70+
) -> Dict[str, OpcodeCount]:
71+
"""Load all opcode counts from all JSON files in a directory."""
72+
all_opcode_counts = {}
73+
json_files = find_json_files(directory)
74+
75+
for json_file in json_files:
76+
fixtures = load_fixtures_from_file(
77+
json_file, remove_from_fixture_names=remove_from_fixture_names
78+
)
79+
if fixtures:
80+
file_opcode_counts = extract_opcode_counts_from_fixtures(fixtures)
81+
# Use fixture name as key, if there are conflicts, choose the last
82+
all_opcode_counts.update(file_opcode_counts)
83+
84+
return all_opcode_counts
85+
86+
87+
def compare_opcode_counts(count1: OpcodeCount, count2: OpcodeCount) -> Dict[str, int]:
88+
"""Compare two opcode counts and return the differences."""
89+
differences = {}
90+
91+
# Get all unique opcodes from both counts
92+
all_opcodes = set(count1.root.keys()) | set(count2.root.keys())
93+
94+
for opcode in all_opcodes:
95+
val1 = count1.root.get(opcode, 0)
96+
val2 = count2.root.get(opcode, 0)
97+
diff = val2 - val1
98+
if diff != 0:
99+
differences[str(opcode)] = diff
100+
101+
return differences
102+
103+
104+
@click.command()
105+
@click.argument("base", type=click.Path(exists=True, file_okay=False, path_type=Path))
106+
@click.argument("patch", type=click.Path(exists=True, file_okay=False, path_type=Path))
107+
@click.option(
108+
"--show-common",
109+
is_flag=True,
110+
help="Print fixtures that contain identical opcode counts.",
111+
)
112+
@click.option(
113+
"--show-missing",
114+
is_flag=True,
115+
help="Print fixtures only found in one of the folders.",
116+
)
117+
@click.option(
118+
"--remove-from-fixture-names",
119+
"-r",
120+
multiple=True,
121+
help="String to be removed from the fixture name, in case the fixture names have changed, "
122+
"in order to make the comparison easier. "
123+
"Can be specified multiple times.",
124+
)
125+
def main(
126+
base: Path,
127+
patch: Path,
128+
show_common: bool,
129+
show_missing: bool,
130+
remove_from_fixture_names: List[str],
131+
):
132+
"""Crawl two folders, compare and print the opcode count diffs."""
133+
print(f"Loading opcode counts from {base}...")
134+
opcode_counts1 = load_all_opcode_counts(base, remove_from_fixture_names)
135+
print(f"Found {len(opcode_counts1)} fixtures with opcode counts")
136+
137+
print(f"Loading opcode counts from {patch}...")
138+
opcode_counts2 = load_all_opcode_counts(patch, remove_from_fixture_names)
139+
print(f"Found {len(opcode_counts2)} fixtures with opcode counts")
140+
141+
# Find common fixture names
142+
common_names = set(opcode_counts1.keys()) & set(opcode_counts2.keys())
143+
only_in_1 = set(opcode_counts1.keys()) - set(opcode_counts2.keys())
144+
only_in_2 = set(opcode_counts2.keys()) - set(opcode_counts1.keys())
145+
146+
print("\nSummary:")
147+
print(f" Common fixtures: {len(common_names)}")
148+
print(f" Only in {base.name}: {len(only_in_1)}")
149+
print(f" Only in {patch.name}: {len(only_in_2)}")
150+
151+
# Show missing fixtures if requested
152+
if show_missing:
153+
if only_in_1:
154+
print(f"\nFixtures only in {base.name}:")
155+
for name in sorted(only_in_1):
156+
print(f" {name}")
157+
158+
if only_in_2:
159+
print(f"\nFixtures only in {patch.name}:")
160+
for name in sorted(only_in_2):
161+
print(f" {name}")
162+
163+
# Compare common fixtures
164+
differences_found = False
165+
common_with_same_counts = 0
166+
167+
for fixture_name in sorted(common_names):
168+
count1 = opcode_counts1[fixture_name]
169+
count2 = opcode_counts2[fixture_name]
170+
171+
differences = compare_opcode_counts(count1, count2)
172+
173+
if differences:
174+
differences_found = True
175+
print(f"\n{fixture_name}:")
176+
for opcode, diff in sorted(differences.items()):
177+
if diff > 0:
178+
print(f" +{diff} {opcode}")
179+
else:
180+
print(f" {diff} {opcode}")
181+
elif show_common:
182+
print(f"\n{fixture_name}: No differences")
183+
common_with_same_counts += 1
184+
185+
if not differences_found:
186+
print("\nNo differences found in opcode counts between common fixtures!")
187+
elif show_common:
188+
print(f"\n{common_with_same_counts} fixtures have identical opcode counts")
189+
190+
191+
if __name__ == "__main__":
192+
main()

packages/tests/src/cli/eest/commands/clean.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,23 @@
1919
def clean(all_files: bool, dry_run: bool, verbose: bool):
2020
"""
2121
Remove all generated files and directories from the repository.
22-
If `--all` is specified, the virtual environment and .tox directory will also be removed.
22+
23+
If `--all` is specified, the virtual environment and .tox directory will
24+
also be removed.
2325
2426
Args:
25-
all_files (bool): Remove the virtual environment and .tox directory as well.
27+
all_files (bool): Remove the virtual environment and .tox directory
28+
as well.
2629
2730
dry_run (bool): Simulate the cleanup without removing files.
2831
2932
verbose (bool): Show verbose output.
3033
31-
Note: The virtual environment and .tox directory are not removed by default.
34+
Note: The virtual environment and .tox directory are not removed by
35+
default.
3236
33-
Example: Cleaning all generated files and directories and show the deleted items.
37+
Example: Cleaning all generated files and directories and show the deleted
38+
items.
3439
3540
uv run eest clean --all -v
3641

packages/tests/src/cli/eest/make/cli.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
"""
2-
The `make` CLI streamlines the process of scaffolding tasks, such as generating new test files,
3-
enabling developers to concentrate on the core aspects of specification testing.
2+
The `make` CLI streamlines the process of scaffolding tasks, such as generating
3+
new test files, enabling developers to concentrate on the core aspects of
4+
specification testing.
45
56
6-
The module calls the appropriate function for the subcommand. If an invalid subcommand
7-
is chosen, it throws an error and shows a list of valid subcommands. If no subcommand
8-
is present, it shows a list of valid subcommands to choose from.
7+
8+
The module calls the appropriate function for the subcommand. If an invalid
9+
subcommand is chosen, it throws an error and shows a list of valid subcommands.
10+
If no subcommand is present, it shows a list of valid subcommands to choose
11+
from.
912
"""
1013

1114
import click

packages/tests/src/cli/eest/make/commands/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"""
2-
Holds subcommands for the make command. New subcommands must be created as
3-
modules and exported from this package, then registered under the make command in
4-
`cli.py`.
2+
Holds subcommands for the make command.
3+
4+
New subcommands must be created as modules and exported from this package,
5+
then registered under the make command in `cli.py`.
56
"""
67

78
from .env import create_default_env

packages/tests/src/cli/eest/make/commands/test.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"""
22
Provides a CLI command to scaffold a test file.
33
4-
The `test` command guides the user through a series of prompts to generate a test file
5-
based on the selected test type, fork, EIP number, and EIP name. The generated test file
6-
is saved in the appropriate directory with a rendered template using Jinja2.
4+
The `test` command guides the user through a series of prompts to generate a
5+
test file based on the selected test type, fork, EIP number, and EIP name. The
6+
generated test file is saved in the appropriate directory with a rendered
7+
template using Jinja2.
78
"""
89

910
import os
@@ -38,10 +39,11 @@ def test():
3839
"""
3940
Generate a new test file for an EIP.
4041
41-
This function guides the user through a series of prompts to generate a test file
42-
for Ethereum execution specifications. The user is prompted to select the type of test,
43-
the fork to use, and to provide the EIP number and name. Based on the inputs, a test file
44-
is created in the appropriate directory with a rendered template.
42+
This function guides the user through a series of prompts to generate a
43+
test file for Ethereum execution specifications. The user is prompted to
44+
select the type of test, the fork to use, and to provide the EIP number and
45+
name. Based on the inputs, a test file is created in the appropriate
46+
directory with a rendered template.
4547
4648
Example:
4749
uv run eest make test

packages/tests/src/cli/eest/make/templates/blockchain_test.py.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ from ethereum_test_tools import (
1414
Environment,
1515
Transaction,
1616
)
17-
from ethereum_test_tools.vm.opcode import Opcodes as Op
17+
from ethereum_test_vm import Opcodes as Op
1818

1919
REFERENCE_SPEC_GIT_PATH = "DUMMY/eip-DUMMY.md"
2020
REFERENCE_SPEC_VERSION = "DUMMY_VERSION"

packages/tests/src/cli/eest/make/templates/state_test.py.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import pytest
77

88
from ethereum_test_forks import Fork
99
from ethereum_test_tools import Account, Alloc, Environment, StateTestFiller, Transaction
10-
from ethereum_test_tools.vm.opcode import Opcodes as Op
10+
from ethereum_test_vm import Opcodes as Op
1111

1212
REFERENCE_SPEC_GIT_PATH = "DUMMY/eip-DUMMY.md"
1313
REFERENCE_SPEC_VERSION = "DUMMY_VERSION"

packages/tests/src/cli/eest/quotes.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,7 @@ def box_quote(quote):
5151

5252

5353
def get_quote():
54-
"""Return random inspirational quote related to system design formatted in a box."""
54+
"""
55+
Return random inspirational quote formatted in a box.
56+
"""
5557
return box_quote(random.choice(make_something_great))

0 commit comments

Comments
 (0)