fix(spec-tools,testing): move evm_tools into the testing package - #3307
fix(spec-tools,testing): move evm_tools into the testing package#3307gurukamath wants to merge 4 commits into
Conversation
69b0300 to
356f1c3
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## forks/amsterdam #3307 +/- ##
================================================
Coverage 93.49% 93.50%
================================================
Files 624 624
Lines 37056 37070 +14
Branches 3394 3394
================================================
+ Hits 34647 34661 +14
Misses 1653 1653
Partials 756 756
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
One small thing (the diff view hides renamed file). The Evm docstring in evm_trace/protocols.py cross references the old module path, and now that the file lives in the testing package it is rendered by mkdocstrings rather than docc, here
A larger thing, can we remove b11r? I remember there was talk of security using it for fuzzing but I think they should or infact do use geth's now :)
| --basetemp="{{ output_dir }}/spec-tools/tmp" \ | ||
| --ignore=tests/evm_tools/test_count_opcodes.py \ | ||
| "$@" \ | ||
| tests/evm_tools |
There was a problem hiding this comment.
Should we move tests/evm_tools somewhere else in the repo? I think we should try to aim for src/ only including the spec and tests/ only including the spec/bench tests
Definitely follow ups!
There was a problem hiding this comment.
Yes. The folder name is a bit misleading. Happy to fix in a follow up
There was a problem hiding this comment.
A quick win for clarity, potentially in this PR: rename tests/evm_tools to tests/spec_tools, matching the just recipe name (spec-tools). After the move the directory only holds spec-tools tests anyway (test_lint.py, test_new_fork.py, test_docc_shards.py).
The rename touches three places: the recipe at Justfile:219, the fill default ignores at packages/testing/src/execution_testing/cli/pytest_commands/fill.py:143, and the three t8n_build paths in the packaging job. If the t8n_build inputs move to the testing package (see the comment above on the workflow), the last one disappears and this becomes a git mv plus two lines.
Fixed the docstrings. Nice catch!!
We could do this but I'd defer to a follow-up PR |
There was a problem hiding this comment.
Thanks @gurukamath, the move looks clean and (bar 2. below) complete!!!
Details are in the inline comments. Two larger changes that could/should go in this PR:
- Move the new packaging job into a
just test-packagingrecipe (see the comment on the job). - Let
loaders/andutils.pyfollowevm_toolsinto the testing package (see the comment ontransaction_loader.py). This PR already breaks their import path, so one break beats two.
Follow-up: opened #3326 for the unrelated but never-runnable test_count_opcodes.py that was spotted during review.
| # list, so additions are covered automatically. The exceptions: | ||
| # `docc` plugins only load once the `doc` group installs docc, | ||
| # and importing a `__main__` would run it. | ||
| - name: Import-check the spec wheel alone |
There was a problem hiding this comment.
My friend and I tested this step against the pre-fix wheels: built the forks/amsterdam spec wheel, installed it alone into a clean venv, and ran this exact walk-import script. It passes (exit 0) on the broken code.
The reason: the old execution_testing imports in evm_tools were all function-scoped (t8n/cli.py:214, :266, :314-322), deliberately, to avoid an import cycle. So every module imports cleanly, and #3236 only fired at runtime inside build_t8n_from_cli_options. That is exactly the traceback in the issue. A module-level import walk cannot see this form.
The step is still worth keeping. It catches future module-level leaks (now the likely form, since the cycle that motivated function-scoping is gone) and general wheel breakage.
To ensure the invariant "./src/ never imports execution_testing" does not get broken in the future, we could add an ethereum-spec-lint rule that rejects any such import, top-level or function-scoped. An AST check sees both forms, and it runs locally via just static. That might be a bit heavy though, so a quick alternative is ! grep -rn "execution_testing" src/, which passes as of this branch.
| --input.alloc=tests/evm_tools/t8n_build/alloc.json \ | ||
| --input.env=tests/evm_tools/t8n_build/env.json \ | ||
| --input.txs=tests/evm_tools/t8n_build/txs.json \ |
There was a problem hiding this comment.
Now that t8n lives in the testing package, I think these new fixtures should be there too?
Suggested home: packages/testing/src/execution_testing/evm_tools/tests/fixtures/t8n_build/. This follows the existing convention in the package (client_clis/tests/fixtures/, specs/tests/fixtures/, tools/tests/test_filling/fixtures/).
These three lines are the only references repo-wide. The exclude = ["*tests*"] rule keeps the files out of the wheel, and this job reads them from the checkout, so only the paths here change.
It would also leave tests/evm_tools/ holding nothing but spec-tools tests, which makes the tests/spec_tools/ rename follow-up a pure git mv.
(One caution: not the top-level execution_testing/fixtures/, which is the fixture-format code subpackage.)
There was a problem hiding this comment.
Justification about the move/rename of ./tests/evm_tools/ to ./tests/spec_tools/ here #3307 (comment)
| # imports ``.specs`` which transitively imports ``client_clis``, | ||
| # which imports ``ExecutionSpecsTransitionTool`` — top-level imports | ||
| # from ``execution_testing`` would cycle back into spec-tools. | ||
| from execution_testing.base_types.composite_types import BlobSchedule |
There was a problem hiding this comment.
The import-cycle note that lived here was deleted, but two comments in this file still point at it: lines 212 and 264 both read # Function-scoped: see import-cycle note in build_t8n_from_cli_options.
They now reference nothing, and these bare function-scoped imports invite a future hoist-to-module-level cleanup.
I would either restore a short note here (updated for the new home) or repoint the two comments. result.py:24-28 kept and updated its equivalent note in this PR and reads well as the model.
| --basetemp="{{ output_dir }}/spec-tools/tmp" \ | ||
| --ignore=tests/evm_tools/test_count_opcodes.py \ | ||
| "$@" \ | ||
| tests/evm_tools |
There was a problem hiding this comment.
A quick win for clarity, potentially in this PR: rename tests/evm_tools to tests/spec_tools, matching the just recipe name (spec-tools). After the move the directory only holds spec-tools tests anyway (test_lint.py, test_new_fork.py, test_docc_shards.py).
The rename touches three places: the recipe at Justfile:219, the fill default ignores at packages/testing/src/execution_testing/cli/pytest_commands/fill.py:143, and the three t8n_build paths in the packaging job. If the t8n_build inputs move to the testing package (see the comment above on the workflow), the last one disappears and this becomes a git mv plus two lines.
| ethereum-spec-new-fork = "ethereum_spec_tools.new_fork.cli:main" | ||
| ethereum-spec-patch = "ethereum_spec_tools.patch_tool:main" | ||
| ethereum-spec-evm = "ethereum_spec_tools.evm_tools:main" | ||
| whitelist = "ethereum_spec_tools.whitelist:main" |
There was a problem hiding this comment.
This entry point is broken in a standalone wheel install: ethereum_spec_tools.whitelist is not in the [tool.setuptools] packages list, so it never ships and the script fails with ModuleNotFoundError. This is the same class of packaging bug as #3236.
What do you think about removing this line. It is a repo-bound dev tool (it edits the checkout's whitelist.txt) and its generic whitelist name lands in every consumer venv's bin/. The only consumer is Justfile:67, which can become uv run python -m ethereum_spec_tools.whitelist "$@". just whitelist <word> stays the same.
| | -------| ---------- | --------------- | | ||
| | [ethereum/evmone](https://github.com/ethereum/evmone) | `evmone t8n` | Yes | | ||
| | [ethereum/execution-specs](https://github.com/ethereum/execution-specs) | [`ethereum-spec-evm t8n`](https://github.com/ethereum/execution-specs/tree/a48e0b381d5225a6c3de2d06cd9ee7ae0b6ca9bb/src/ethereum_spec_tools/evm_tools/t8n) | Yes | | ||
| | [ethereum/execution-specs](https://github.com/ethereum/execution-specs) | [`ethereum-spec-evm t8n`](https://github.com/ethereum/execution-specs/tree/forks/amsterdam/packages/testing/src/execution_testing/evm_tools/t8n) | Yes | |
There was a problem hiding this comment.
Can we permalink/SHA pin this? This points at tree/forks/amsterdam/..., which will break at the next fork rollover when the default branch moves on.
There was a problem hiding this comment.
Now that t8n lives in the testing package, I think these new fixtures should be there too? See
https://github.com/ethereum/execution-specs/pull/3307/changes#r3725869393
| @@ -0,0 +1,37 @@ | |||
| # The `ethereum-execution-testing` Package | |||
|
|
|||
| Test generation and execution framework for the [Ethereum Execution Layer Specifications (EELS)](https://github.com/ethereum/execution-specs), derived from [ethereum/execution-spec-tests](https://github.com/ethereum/execution-spec-tests). | |||
There was a problem hiding this comment.
I don't think we need a reference to the archived EEST repo here.
| Test generation and execution framework for the [Ethereum Execution Layer Specifications (EELS)](https://github.com/ethereum/execution-specs), derived from [ethereum/execution-spec-tests](https://github.com/ethereum/execution-spec-tests). | |
| Test generation and execution framework for the [Ethereum Execution Layer Specifications (EELS)](https://github.com/ethereum/execution-specs). |
| EOF | ||
| uvx --from actionlint-py actionlint | ||
|
|
||
| packaging: |
There was a problem hiding this comment.
Thanks a lot for adding these additional checks, Guru!
Can we plz also add easy local verification of these steps by adding these checks to the Justfile instead? In particular, workflow-yaml-inline scripts make it hard to verify locally.
Every other test job here is a one-liner delegating to a just recipe; this is the only one with inline logic, including ~18 lines of Python in a heredoc that no linter or type checker ever sees. Moving the whole job into a just test-packaging recipe would match the other nine jobs, make the checks runnable locally before pushing (#3236 is exactly the class of bug you want to catch at the desk), and give the embedded Python a lintable home in .github/scripts/, which already has a tested-scripts convention via just test-ci-scripts.
Suggested shape:
# --- Packaging ---
# Build every workspace wheel into .just/dist
[group('packaging')]
build-wheels:
uv build --wheel --all-packages --out-dir "{{ output_dir }}/dist"
# Smoke-test the built wheels: clean-venv install, real t8n run, spec-wheel-alone import check
[group('packaging')]
test-packaging: build-wheels
# 1. install both wheels by explicit path into a fresh venv
# 2. run the Frontier t8n transition, assert result.json
# 3. install the spec wheel alone, run .github/scripts/import_check.pyThe packaging group is forward looking: a future publish-wheels or a version-lockstep check would land in the same group, each independently runnable and clustered together in just --list. Note the doc comment above each recipe must be a single line; just shows only the line immediately above the recipe in the listing.
The job then reduces to checkout, setup-uv, run: just test-packaging (plus needs: static, see the other comment). One tradeoff: the four named steps collapse into one, so per-step timing and failure attribution moves from the UI into the log. The step comments here (why --all-packages, why explicit wheel paths, why a real transition instead of --help) are good and should move into the recipe with the logic.
| hex_to_uint, | ||
| ) | ||
| from ethereum_spec_tools.evm_tools.utils import parse_hex_or_int | ||
| from ethereum_spec_tools.utils import parse_hex_or_int |
There was a problem hiding this comment.
Suggestion (anchored here because the rest of the loader move is a pure rename in the diff): let loaders/ and utils.py follow evm_tools all the way into the testing package, in this PR, rather than stopping at ethereum_spec_tools top level.
Justification: After the move they have zero consumers left in the spec package: every user of loaders/ and utils.py is in packages/testing (11 import sites) or tests/json_loader (one import, Load in helpers/load_blockchain_tests.py). The only spec-internal use is this file importing utils.parse_hex_or_int, and it moves with them. forks.py is different and should stay, it is real spec API.
Why now? The import path already breaks in this PR either way: on base these lived at ethereum_spec_tools.evm_tools.loaders and evm_tools/utils.py, so any external consumer has to update regardless. Landing them at execution_testing.evm_tools.loaders and evm_tools/utils.py now means one break instead of two.
What the full move buys, beyond the cleaner boundary:
- 13 of the 16 new vulture whitelist entries go away outright (
Load, the sevenForkLoad.has_*, the fiveutilshelpers leavesrc/); the remaining three are covered by extending the deadcode scan (other comment). - The
"ethereum_spec_tools.loaders"line added to the setuptools packages list can be dropped again. - The new fork-selection branch in
tests/json_loader/helpers/select_tests.py:79-84can be reverted; it exists only because these files stayed behind. - The PR's story simplifies from "move
evm_tools, but split out its loaders and utils" to "moveevm_toolswholesale".
Cost is the same mechanical class as the rest of the PR: five files moved plus roughly a dozen import-site rewrites.
There was a problem hiding this comment.
The layout in the three states, for the visual argument.
- Base (
forks/amsterdam), everything under one roof:
src/ethereum_spec_tools/
├── __init__.py, forks.py, patch_tool.py, sync.py, py.typed
└── evm_tools/
├── __init__.py, __main__.py, daemon.py
├── utils.py ◄ inside evm_tools
├── b11r/ (__init__.py, b11r_types.py)
├── loaders/ (__init__.py, fixture_loader.py, ◄ inside evm_tools
│ fork_loader.py, transaction_loader.py)
├── statetest/ (__init__.py)
└── t8n/ (__init__.py, block_environment.py, cli.py,
result.py, evm_trace/ ×5)
- This PR as it stands,
evm_toolsmoves butloaders/andutils.pyare split out and promoted to spec-tools top level:
src/ethereum_spec_tools/
├── __init__.py, forks.py, patch_tool.py, sync.py, py.typed
├── loaders/ (__init__.py, fixture_loader.py, ◄ promoted, stayed behind
│ fork_loader.py, transaction_loader.py)
└── utils.py ◄ promoted, stayed behind
packages/testing/src/execution_testing/
└── evm_tools/
├── __init__.py, __main__.py, daemon.py
├── b11r/ (__init__.py, b11r_types.py)
├── statetest/ (__init__.py)
├── t8n/ (__init__.py, block_environment.py, cli.py,
│ result.py, evm_trace/ ×5)
└── tests/ (×3, moved from tests/evm_tools/)
- With the suggested move,
evm_toolsmoves wholesale and keeps its base-internal structure at the new address:
src/ethereum_spec_tools/
├── __init__.py
├── forks.py ◄ stays: real spec API
└── patch_tool.py, sync.py, py.typed
packages/testing/src/execution_testing/
└── evm_tools/
├── __init__.py, __main__.py, daemon.py
├── utils.py ◄ back where it was
├── b11r/ (__init__.py, b11r_types.py)
├── loaders/ (__init__.py, fixture_loader.py, ◄ back where it was
│ fork_loader.py, transaction_loader.py)
├── statetest/ (__init__.py)
├── t8n/ (__init__.py, block_environment.py, cli.py,
│ result.py, evm_trace/ ×5)
└── tests/ (×3)
Why the picture matters: state 3 is not a new reorganization on top of the PR; it is the PR's own move applied uniformly. evm_tools in state 3 has the identical internal shape it had on base (utils.py and loaders/ in their old positions relative to t8n/, b11r/, statetest/); only the package prefix changed. State 2 invents a layout that never existed before, loaders/ and utils.py at spec-tools top level. The leftover src/ethereum_spec_tools/ in state 3 is pure spec-serving tooling: fork enumeration (forks.py), lint, new-fork scaffolding, docs, sync.
Description
pip install-ing this repository produces a brokenethereum-spec-evm: the t8nand statetest subcommands import
execution_testing(since #2924), which is auv workspace member — not a dependency of
ethereum-execution— so standaloneinstalls fail with
ModuleNotFoundErroron first use.This PR fixes the dependency inversion by moving
evm_tools(t8n, b11r,statetest, daemon) into the
ethereum-execution-testingpackage, so allimports point one way: testing → spec. The
ethereum-spec-evmentry pointmoves with it;
loaders/andutils.pystay inethereum_spec_tools.into a clean venv, and runs a real Frontier transition — the class of
regression that shipped No module named 'execution_testing' #3236 is now caught before merge.
packages/testing/README.md(also fixes its danglingreadme =metadata) with verified standalone install recipes, plus updatedrepo README, packaging docs, and an
evm_toolsreference page.Note for consumers: the CLI is no longer installable from the repo root alone —
install both packages from the same clone, e.g.
pip install ./execution-specs ./execution-specs/packages/testing.Related Issues or PRs
Fixes #3236.
Checklist
just static<type>(<area>): <title>, where<type>and<area>come from an appropriateC-<type>, respectivelyA-<area>, label. The title should match the target squash commit message.Cute Animal Picture