Skip to content

Commit d71dcc0

Browse files
feat(plugins,docs): EIP checklist generator plugin (#1679)
* feat(docs): Generate EIP Checklists * fix(docs): Correctly include checklist gen in mkdocs flow * fix(plugins/filler): remove unused import * fix(plugins): Tox, unit tests * fix(clis): Checklist command help * docs: Changelog * fix(docs): Outdated docs * fix(plugins): Collect-only parameter * feat(plugins/eip_checklist): Allow starts-with covered items, n/a, and externally covered items * Update docs page * fix correct path * docs: Move checklist guide to `writing_tests` * fix * docs: prefix commands with `uv run` * chore(clis): fix examples in `checklist` docstring * docs: apply correct indentation to fix ordered-list ennumeration --------- Co-authored-by: danceratopz <danceratopz@gmail.com>
1 parent e91aba1 commit d71dcc0

File tree

15 files changed

+872
-17
lines changed

15 files changed

+872
-17
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Users can select any of the artifacts depending on their testing needs for their
5353
- 🐞 Fix bug in ported-from plugin and coverage script that made PRs fail with modified tests that contained no ported tests ([#1661](https://github.com/ethereum/execution-spec-tests/pull/1661)).
5454
- 🔀 Refactor the `click`-based CLI interface used for pytest-based commands (`fill`, `execute`, `consume`) to make them more extensible ([#1654](https://github.com/ethereum/execution-spec-tests/pull/1654)).
5555
- 🔀 Split `src/ethereum_test_types/types.py` into several files to improve code organization ([#1665](https://github.com/ethereum/execution-spec-tests/pull/1665)).
56+
- ✨ Added automatic checklist generation for every EIP inside of the `tests` folder. The checklist is appended to each EIP in the documentation in the "Test Case Reference" section ([#1679](https://github.com/ethereum/execution-spec-tests/pull/1679)).
5657

5758
### 🧪 Test Cases
5859

docs/navigation.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
* [Exception Tests](writing_tests/exception_tests.md)
2222
* [Using and Extending Fork Methods](writing_tests/fork_methods.md)
2323
* [Referencing an EIP Spec Version](writing_tests/reference_specification.md)
24+
* [EIP Checklist Generation](writing_tests/eip_checklist.md)
2425
* [Testing Checklist Templates](writing_tests/checklist_templates/index.md)
2526
* [EIP Execution Layer Testing Checklist Template](writing_tests/checklist_templates/eip_testing_checklist_template.md)
2627
* [Post-mortems](writing_tests/post_mortems.md)

docs/scripts/gen_test_case_reference.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,12 @@
4343
"filterwarnings=ignore::pytest.PytestAssertRewriteWarning", # suppress warnings due to reload
4444
"-p",
4545
"pytest_plugins.filler.gen_test_doc.gen_test_doc",
46+
"-p",
47+
"pytest_plugins.filler.eip_checklist",
4648
"--gen-docs",
4749
f"--gen-docs-target-fork={TARGET_FORK}",
4850
f"--until={GENERATE_UNTIL_FORK}",
51+
"--checklist-doc-gen",
4952
"--skip-index",
5053
"-m",
5154
"not blockchain_test_engine",

docs/writing_tests/checklist_templates/eip_testing_checklist_template.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33

44
Depending on the changes introduced by an EIP, the following template is the minimum baseline to guarantee test coverage of the Execution Layer features.
55

6+
## Checklist Progress Tracker
7+
8+
| Total Checklist Items | Covered Checklist Items | Percentage |
9+
| --------------------- | ----------------------- | ---------- |
10+
| TOTAL_CHECKLIST_ITEMS | COVERED_CHECKLIST_ITEMS | PERCENTAGE |
11+
612
## General
713

814
#### Code coverage

docs/writing_tests/eip_checklist.md

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
# EIP Checklist Generation
2+
3+
The EIP checklist feature helps track test coverage for EIP implementations by automatically generating filled checklists based on test markers.
4+
5+
## Overview
6+
7+
When implementing tests for an EIP, you can mark specific tests as covering checklist items from the [EIP testing checklist template](../writing_tests/checklist_templates/eip_testing_checklist_template.md). The framework will then generate a filled checklist showing which items have been implemented.
8+
9+
## Using the `pytest.mark.eip_checklist` Marker
10+
11+
To mark a test as implementing a specific checklist item:
12+
13+
```python
14+
import pytest
15+
from ethereum_test_tools import StateTestFiller
16+
17+
@pytest.mark.eip_checklist("new_transaction_type/test/intrinsic_validity/gas_limit/exact")
18+
def test_exact_intrinsic_gas(state_test: StateTestFiller):
19+
"""Test transaction with exact intrinsic gas limit."""
20+
# Test implementation
21+
pass
22+
```
23+
24+
### Marker Parameters
25+
26+
- **First positional parameter** (required): The checklist item ID from the template
27+
- **`eip` keyword parameter** (optional): List of additional EIPs covered by the test
28+
29+
Example with multiple EIPs covered by the same test:
30+
31+
```python
32+
@pytest.mark.eip_checklist("new_transaction_type/test/signature/invalid/v/0", eip=[7702, 2930])
33+
def test_invalid_signature(state_test: StateTestFiller):
34+
"""Test invalid signature that affects multiple EIPs."""
35+
pass
36+
```
37+
38+
### Partial ID Matching
39+
40+
You can use partial IDs that will match all checklist items starting with that prefix:
41+
42+
```python
43+
# This will mark all items under "new_transaction_type/test/signature/invalid/" as covered
44+
@pytest.mark.eip_checklist("new_transaction_type/test/signature/invalid/")
45+
def test_all_invalid_signatures(state_test: StateTestFiller):
46+
"""Test covering all invalid signature scenarios."""
47+
pass
48+
```
49+
50+
## Generating Checklists
51+
52+
### Using the Dedicated `checklist` Command
53+
54+
To generate only checklists without filling fixtures:
55+
56+
```bash
57+
# Generate checklists for all EIPs
58+
uv run checklist
59+
60+
# Generate checklist for specific EIP
61+
uv run checklist --eip 7702
62+
63+
# Specify output directory
64+
uv run checklist --output ./my-checklists
65+
66+
# Multiple EIPs
67+
uv run checklist --eip 7702 --eip 2930
68+
```
69+
70+
### Automatic Generation in Documentation
71+
72+
When building the documentation with `mkdocs`, checklists are automatically generated for all EIPs that have tests with checklist markers. The checklists appear in the test documentation alongside the test modules.
73+
74+
## External Coverage and Not Applicable Items
75+
76+
### External Coverage
77+
78+
For checklist items that are covered by external tests, procedures, or tools (e.g., EELS coverage), create a file named `eip_checklist_external_coverage.txt` in the EIP test directory:
79+
80+
```text
81+
# tests/prague/eip7702_set_code_tx/eip_checklist_external_coverage.txt
82+
general/code_coverage/eels = Covered by EELS test suite
83+
general/code_coverage/second_client = Covered by Nethermind tests
84+
```
85+
86+
Format: `checklist_item_id = reason`
87+
88+
### Not Applicable Items
89+
90+
For checklist items that are not applicable to a specific EIP, create a file named `eip_checklist_not_applicable.txt` in the EIP test directory:
91+
92+
```text
93+
# tests/prague/eip7702_set_code_tx/eip_checklist_not_applicable.txt
94+
new_system_contract = EIP-7702 does not introduce a system contract
95+
new_precompile = EIP-7702 does not introduce a precompile
96+
```
97+
98+
Format: `checklist_item_id = reason`
99+
100+
Both files support partial ID matching, so you can mark entire sections as not applicable:
101+
102+
```text
103+
# Mark all system contract items as not applicable
104+
new_system_contract/ = EIP does not introduce system contracts
105+
```
106+
107+
## Output Format
108+
109+
The generated checklist will show:
110+
111+
- ✅ for completed items (either by tests or external coverage)
112+
- N/A for not applicable items
113+
- Test names that implement each item
114+
- External coverage reasons where applicable
115+
- A percentage of covered checklist items (excluding N/A items)
116+
- Color-coded completion status: 🟢 (100%), 🟡 (>50%), 🔴 (≤50%)
117+
118+
Example output snippet:
119+
120+
```markdown
121+
# EIP-7702 Test Checklist
122+
123+
## Checklist Progress Tracker
124+
125+
| Total Checklist Items | Covered Checklist Items | Percentage |
126+
| --------------------- | ----------------------- | ---------- |
127+
| 45 | 32 | 🟡 71.11% |
128+
129+
## General
130+
131+
#### Code coverage
132+
133+
| ID | Description | Status | Tests |
134+
| -- | ----------- | ------ | ----- |
135+
| `general/code_coverage/eels` | Run produced tests against EELS... | ✅ | Covered by EELS test suite |
136+
| `general/code_coverage/test_coverage` | Run coverage on the test code itself... | ✅ | `tests/prague/eip7702_set_code_tx/test_set_code_txs.py::test_set_code_txs` |
137+
138+
## New Transaction Type
139+
140+
| ID | Description | Status | Tests |
141+
| -- | ----------- | ------ | ----- |
142+
| `new_transaction_type/test/intrinsic_validity/gas_limit/exact` | Provide the exact intrinsic gas... | ✅ | `tests/prague/eip7702_set_code_tx/test_checklist_example.py::test_exact_intrinsic_gas` |
143+
| `new_transaction_type/test/intrinsic_validity/gas_limit/insufficient` | Provide the exact intrinsic gas minus one... | | |
144+
145+
## New System Contract
146+
147+
| ID | Description | Status | Tests |
148+
| -- | ----------- | ------ | ----- |
149+
| `new_system_contract/test/deployment/missing` | Verify block execution behavior... | N/A | EIP-7702 does not introduce a system contract |
150+
```
151+
152+
## Best Practices
153+
154+
1. **Start with the checklist**: Review the checklist template before writing tests to ensure comprehensive coverage
155+
2. **Use descriptive test names**: The test name will appear in the checklist, so make it clear what the test covers
156+
3. **Mark items as you go**: Add `eip_checklist` markers while writing tests, not as an afterthought
157+
4. **Document external coverage**: If items are covered by external tools/tests, document this in `eip_checklist_external_coverage.txt`
158+
5. **Be explicit about N/A items**: Document why items are not applicable in `eip_checklist_not_applicable.txt`
159+
6. **Use partial IDs wisely**: When a test covers multiple related items, use partial IDs to mark them all
160+
161+
## Workflow Example
162+
163+
1. **Create test directory structure**:
164+
165+
```bash
166+
tests/prague/eip9999_new_feature/
167+
├── __init__.py
168+
├── spec.py
169+
├── test_basic.py
170+
├── eip_checklist_external_coverage.txt
171+
└── eip_checklist_not_applicable.txt
172+
```
173+
174+
2. **Mark tests as you implement them**:
175+
176+
```python
177+
@pytest.mark.eip_checklist("new_opcode/test/gas_usage/normal")
178+
def test_opcode_gas_consumption(state_test: StateTestFiller):
179+
"""Test normal gas consumption of the new opcode."""
180+
pass
181+
```
182+
183+
3. **Document external coverage**:
184+
185+
```text
186+
# eip_checklist_external_coverage.txt
187+
general/code_coverage/eels = Covered by ethereum/execution-specs PR #1234
188+
```
189+
190+
4. **Mark non-applicable items**:
191+
192+
```text
193+
# eip_checklist_not_applicable.txt
194+
new_precompile/ = EIP-9999 introduces an opcode, not a precompile
195+
```
196+
197+
5. **Generate and review checklist**:
198+
199+
```bash
200+
checklist --eip 9999
201+
# Review the generated checklist for completeness
202+
```
203+
204+
## See Also
205+
206+
- [EIP Testing Checklist Template](./checklist_templates/eip_testing_checklist_template.md) - The full checklist template

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ checkfixtures = "cli.check_fixtures:check_fixtures"
9393
check_eip_versions = "cli.pytest_commands.check_eip_versions:check_eip_versions"
9494
consume = "cli.pytest_commands.consume:consume"
9595
protec = "cli.pytest_commands.consume:consume"
96+
checklist = "cli.pytest_commands.checklist:checklist"
9697
genindex = "cli.gen_index:generate_fixtures_index_cli"
9798
gentest = "cli.gentest:generate"
9899
eofwrap = "cli.eofwrap:eof_wrap"

src/cli/pytest_commands/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
fill --help
2020
# for example, or
2121
consume engine
22+
# or
23+
checklist --help
2224
```
2325
2426
They can also be executed (and debugged) directly in an interactive python

src/cli/pytest_commands/checklist.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"""CLI entry point for the `checklist` pytest-based command."""
2+
3+
from typing import List
4+
5+
import click
6+
7+
from .base import PytestCommand
8+
9+
10+
class ChecklistCommand(PytestCommand):
11+
"""Pytest command for generating EIP checklists."""
12+
13+
def __init__(self):
14+
"""Initialize checklist command with processors."""
15+
super().__init__(
16+
config_file="pytest.ini",
17+
)
18+
19+
def process_arguments(self, pytest_args: List[str]) -> List[str]:
20+
"""Process arguments, ensuring checklist generation is enabled."""
21+
processed_args = super().process_arguments(pytest_args)
22+
23+
# Add collect-only flag to avoid running tests
24+
processed_args.extend(["-p", "pytest_plugins.filler.eip_checklist"])
25+
26+
return processed_args
27+
28+
29+
@click.command()
30+
@click.option(
31+
"--output",
32+
"-o",
33+
type=click.Path(file_okay=False, dir_okay=True, writable=True),
34+
default="./checklists",
35+
help="Directory to output the generated checklists (default: ./checklists)",
36+
)
37+
@click.option(
38+
"--eip",
39+
"-e",
40+
type=int,
41+
multiple=True,
42+
help="Generate checklist only for specific EIP(s)",
43+
)
44+
def checklist(output: str, eip: tuple, **kwargs) -> None:
45+
"""
46+
Generate EIP test checklists based on pytest.mark.eip_checklist markers.
47+
48+
This command scans test files for eip_checklist markers and generates
49+
filled checklists showing which checklist items have been implemented.
50+
51+
Examples:
52+
# Generate checklists for all EIPs
53+
uv run checklist
54+
55+
# Generate checklist for specific EIP
56+
uv run checklist --eip 7702
57+
58+
# Generate checklists for specific test path
59+
uv run checklist tests/prague/eip7702*
60+
61+
# Specify output directory
62+
uv run checklist --output ./my-checklists
63+
64+
"""
65+
# Add output directory to pytest args
66+
args = ["--checklist-output", output]
67+
68+
# Add EIP filter if specified
69+
for eip_num in eip:
70+
args.extend(["--checklist-eip", str(eip_num)])
71+
72+
command = ChecklistCommand()
73+
command.execute(args)
74+
75+
76+
if __name__ == "__main__":
77+
checklist()

0 commit comments

Comments
 (0)