Skip to content

Commit 971214c

Browse files
feat(fill): enable shared pre-allocation groups and add BlockchainEngineReorgFixture (#1706)
* feat(fill): enable interface for two-phase shared alloc filling * feat(fixtures,types): add shared pre-allocation data models - Add `SharedPreStateGroup` and `SharedPreState` models. - Support grouping tests by (fork, environment) hash. - Enable serialization for phase 1/2 coordination. * feat(fixtures): add `BlockchainEngineReorgFixture` and common base class - Add `BlockchainEngineFixtureCommon` base class for shared functionality. - Add `BlockchainEngineReorgFixture` with `post_state_diff` field. - Remove unnecessary backwards compatibility validator from common class. - Update `test_collect_only` framework test for new fixture. * feat(fixtures): export new fixture classes and shared models - Export `BlockchainEngineReorgFixture` and `BlockchainEngineFixtureCommon`. - Export `SharedPreState` and `SharedPreStateGroup` models. - Enable imports for shared pre-allocation functionality. * feat(specs): add shared pre-allocation support to `BaseTest` - Add `update_shared_pre_state()` and `compute_shared_pre_alloc_hash()`. - Add `get_genesis_environment()` for polymorphic environment access. - Support both `StateTest` and `BlockchainTest` formats. * feat(fill): implement shared pre-allocation (`fill` pytest hooks) - Add `pytest_sessionstart` and `pytest_sessionfinish` for phase coordination. - Add `calculate_post_state_diff()` for memory-efficient storage. - Add custom terminal summary with group statistics. - Load/save shared pre-allocation state. * feat(fixtures): enable global address allocation and shared pre-alloc path - Add `shared_prealloc_path` property to `FixtureOutput`. - Extend global address iterators to all test formats. - Make the fixture scope of address iterators dynamic: session-scoped for shared pre-alloc generation, function-scoped for regular pre-alloc. - Update and improve `pre_alloc` tests. * feat(specs): add `BlockchainEngineReorgFixture` generation support - Support generating reorg fixtures from blockchain tests. - Enable shared pre-allocation integration with blockchain specs. * feat(fixtures): add `pre_hash` field to test case index - Support pre-allocation hash tracking in index files. - Enable fixture consumption with shared pre-allocation metadata. * chore(fill): only generate engine_reorg fixtures w/shared-alloc flags * feat(fill): add a new pytest mark for custom prealloc group control * chore(tests): apply `prealloc_group` to system contract & requests tests * fix(tests): prevent iterator state persistence in beacon root tests during two-phase execution The beacon root tests were failing when run with two-phase shared pre-allocation because they used `itertools.count()` objects directly in pytest parametrization. These iterator objects maintain internal state that persists across test phases when run in the same Python process. Changes: - Replace direct `count()` objects with factory functions that return fresh iterators. - Update `beacon_roots` fixture to create new iterator instances on each invocation. - Modify test parametrization to use `timestamps_factory` instead of `timestamps`. This bug only manifested in two-phase execution because: 1. In regular single-phase filling, each test gets its own fresh Python process. 2. In two-phase execution within one process, the same parametrized iterator. objects are reused, causing timestamps to continue from where phase 1 left off. 3. The second phase would see timestamps like 21000 instead of starting at 1000. The fix ensures each test invocation gets fresh iterators with reset state, preventing cross-phase contamination while maintaining the same test behavior. * chore(cli): add `groupstats` script to analyze shared pre-allocation groups Add a new CLI tool that provides comprehensive analysis of shared pre-allocation groups generated by the test framework. The tool helps identify optimization opportunities for client teams by analyzing group distributions, test coverage, and identifying problematic test functions that create multiple size-1 groups. Key features: - Display overall statistics (groups, tests, accounts) - Show distribution by fork with average tests per group - Analyze group size frequency distribution - Calculate test coverage impact by group size - List test modules by execution complexity (group count) - Identify split test functions with Groups/Fork ratio analysis - Progressive disclosure with -v and -vv verbosity levels The Groups/Fork ratio metric distinguishes between necessary multi-fork coverage and problematic parameter-heavy tests, enabling targeted optimization strategies for CI workflows. * docs(consume): add `BlockchainEngineReorgFixture` docs * refactor(all): standardize shared pre-allocation naming conventions - CLI flags: `--generate-shared-alloc` → `--generate-shared-pre` - CLI flags: `--use-shared-alloc` → `--use-shared-pre` - Property: `shared_prealloc_path` → `shared_pre_alloc_path` - Pytest marker: `prealloc_group` → `pre_alloc_group` - Entry point: `show_pre_alloc_groups` → `groupstats` * docs: update changelog * fix(fixtures/filler): Pydantic fixes * feat(filler): pre_alloc.json is now a folder * fix(cli): groupstats * fix: tox * feat: Use deterministic addresses in pre-alloc * fix: xdist * feat: genesis header in pre_alloc files * fix: tox * fix: move pre --------- Co-authored-by: Mario Vega <[email protected]>
1 parent dc8dc53 commit 971214c

Some content is hidden

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

44 files changed

+2144
-162
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Users can select any of the artifacts depending on their testing needs for their
3131

3232
- ✨ Add the `ported_from` test marker to track Python test cases that were converted from static fillers in [ethereum/tests](https://github.com/ethereum/tests) repository [#1590](https://github.com/ethereum/execution-spec-tests/pull/1590).
3333
- ✨ Add a new pytest plugin, `ported_tests`, that lists the static fillers and PRs from `ported_from` markers for use in the coverage Github Workflow [#1634](https://github.com/ethereum/execution-spec-tests/pull/1634).
34+
- ✨ Enable two-phase filling of fixtures with shared pre-allocation groups and add a `BlockchainEngineReorgFixture` format [#1606](https://github.com/ethereum/execution-spec-tests/pull/1706).
3435
- 🔀 Refactor: Encapsulate `fill`'s fixture output options (`--output`, `--flat-output`, `--single-fixture-per-file`) into a `FixtureOutput` class ([#1471](https://github.com/ethereum/execution-spec-tests/pull/1471),[#1612](https://github.com/ethereum/execution-spec-tests/pull/1612)).
3536
- ✨ Don't warn about a "high Transaction gas_limit" for `zkevm` tests ([#1598](https://github.com/ethereum/execution-spec-tests/pull/1598)).
3637
- 🐞 `fill` no longer writes generated fixtures into an existing, non-empty output directory; it must now be empty or `--clean` must be used to delete it first ([#1608](https://github.com/ethereum/execution-spec-tests/pull/1608)).

docs/navigation.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
* [State Tests](running_tests/test_formats/state_test.md)
4242
* [Blockchain Tests](running_tests/test_formats/blockchain_test.md)
4343
* [Blockchain Engine Tests](running_tests/test_formats/blockchain_test_engine.md)
44+
* [Blockchain Engine Reorg Tests](running_tests/test_formats/blockchain_test_engine_reorg.md)
4445
* [EOF Tests](running_tests/test_formats/eof_test.md)
4546
* [Transaction Tests](running_tests/test_formats/transaction_test.md)
4647
* [Common Types](running_tests/test_formats/common_types.md)
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Blockchain Engine Reorg Tests <!-- markdownlint-disable MD051 (MD051=link-fragments "Link fragments should be valid") -->
2+
3+
The Blockchain Engine Reorg Test fixture format tests are included in the fixtures subdirectory `blockchain_tests_engine_reorg`, and use Engine API directives with optimized shared pre-allocation for improved execution performance.
4+
5+
These are produced by the `StateTest` and `BlockchainTest` test specs when using the `--generate-shared-pre` and `--use-shared-pre` flags.
6+
7+
## Description
8+
9+
The Blockchain Engine Reorg Test fixture format is an optimized variant of the [Blockchain Engine Test](./blockchain_test_engine.md) format designed for large-scale test execution with performance optimizations.
10+
11+
It uses the Engine API to test block validation and consensus rules while leveraging **shared pre-allocation state** to significantly reduce test execution time and resource usage. Tests are grouped by their initial state (fork + environment + pre-allocation) and share common genesis states through blockchain reorganization.
12+
13+
The key optimization is that **clients need only be started once per group** instead of once per test (as in the original engine fixture format), dramatically improving execution performance for large test suites.
14+
15+
Instead of including large pre-allocation state in each test fixture, this format references a shared pre-allocation folder (`pre_alloc`) which includes all different pre-allocation combinations used for any test fixture group.
16+
17+
A single JSON fixture file is composed of a JSON object where each key-value pair is a different [`ReorgFixture`](#reorgfixture) test object, with the key string representing the test name.
18+
19+
The JSON file path plus the test name are used as the unique test identifier.
20+
21+
## Shared Pre-Allocation File
22+
23+
The `blockchain_tests_engine_reorg` directory contains a special directory `pre_alloc` that stores shared pre-allocation state file used by all tests in this format, one per pre-allocation group with the name of the pre-alloc hash. This folder is essential for test execution and must be present alongside the test fixtures.
24+
25+
### Pre-Allocation File Structure
26+
27+
Each file in the `pre_alloc` folder corresponds to a pre-allocation hash to shared state groups:
28+
29+
```json
30+
{
31+
"test_count": 88,
32+
"pre_account_count": 174,
33+
"testIds": ["test1", "test2", ...],
34+
"network": "Prague",
35+
"environment": { ... },
36+
"pre": { ... }
37+
}
38+
```
39+
40+
#### SharedPreStateGroup Fields
41+
42+
- **`test_count`**: Number of tests sharing this pre-allocation group
43+
- **`pre_account_count`**: Number of accounts in the shared pre-allocation state
44+
- **`testIds`**: Array of test identifiers that use this shared state
45+
- **`network`**: Fork name (e.g., "Prague", "Cancun")
46+
- **`environment`**: Complete [`Environment`](./common_types.md#environment) object with execution context
47+
- **`pre`**: Shared [`Alloc`](./common_types.md#alloc-mappingaddressaccount) object containing initial account states
48+
49+
## Consumption
50+
51+
For each [`ReorgFixture`](#reorgfixture) test object in the JSON fixture file, perform the following steps:
52+
53+
1. **Load Shared Pre-Allocation**:
54+
- Read the appropriate file from the `pre_alloc` folder in the same directory
55+
- Locate the shared state group using [`preHash`](#-prehash-string)
56+
- Extract the `pre` allocation and `environment` from the shared group
57+
58+
2. **Initialize Client**:
59+
- Use [`network`](#-network-fork) to configure the execution fork schedule
60+
- Use the shared `pre` allocation as the starting state
61+
- Use the shared `environment` as the execution context
62+
- Use [`genesisBlockHeader`](#-genesisblockheader-fixtureheader) as the genesis block header
63+
64+
3. **Execute Engine API Sequence**:
65+
- For each [`FixtureEngineNewPayload`](#fixtureenginenewpayload) in [`engineNewPayloads`](#-enginenewpayloads-listfixtureenginenewpayload):
66+
1. Deliver the payload using `engine_newPayloadVX`
67+
2. Validate the response according to the payload's expected status
68+
- If [`syncPayload`](#-syncpayload-optionalfixtureenginenewpayload) is present, execute it for chain synchronization
69+
70+
4. **Verify Final State**:
71+
- Compare the final chain head against [`lastblockhash`](#-lastblockhash-hash)
72+
- If [`postStateDiff`](#-poststatediff-optionalalloc) is present:
73+
- Apply the state differences to the shared pre-allocation
74+
- Verify the resulting state matches the client's final state
75+
- If `post` field were present (not typical), verify it directly
76+
77+
## Structures
78+
79+
### `ReorgFixture`
80+
81+
#### - `network`: [`Fork`](./common_types.md#fork)
82+
83+
##### TO BE DEPRECATED
84+
85+
Fork configuration for the test.
86+
87+
This field is going to be replaced by the value contained in `config.network`.
88+
89+
#### - `preHash`: `string`
90+
91+
Hash identifier referencing a shared pre-allocation group in the `pre_alloc` folder. This hash uniquely identifies the combination of fork, environment, and pre-allocation state shared by multiple tests.
92+
93+
#### - `genesisBlockHeader`: [`FixtureHeader`](./blockchain_test.md#fixtureheader)
94+
95+
Genesis block header. The state root in this header must match the state root calculated from the shared pre-allocation referenced by [`preHash`](#-prehash-string).
96+
97+
#### - `engineNewPayloads`: [`List`](./common_types.md#list)`[`[`FixtureEngineNewPayload`](#fixtureenginenewpayload)`]`
98+
99+
List of `engine_newPayloadVX` directives to be processed after the genesis block. These define the sequence of blocks to be executed via the Engine API.
100+
101+
#### - `syncPayload`: [`Optional`](./common_types.md#optional)`[`[`FixtureEngineNewPayload`](#fixtureenginenewpayload)`]`
102+
103+
Optional synchronization payload used for blockchain reorganization scenarios. When present, this payload is typically used to sync the chain to a specific state before or after the main payload sequence.
104+
105+
#### - `lastblockhash`: [`Hash`](./common_types.md#hash)
106+
107+
Hash of the last valid block after all payloads have been processed, or the genesis block hash if all payloads are invalid.
108+
109+
#### - `postStateDiff`: [`Optional`](./common_types.md#optional)`[`[`Alloc`](./common_types.md#alloc-mappingaddressaccount)`]`
110+
111+
State differences from the shared pre-allocation state after test execution. This optimization stores only the accounts that changed, were created, or were deleted during test execution, rather than the complete final state.
112+
113+
To reconstruct the final state:
114+
115+
1. Start with the shared pre-allocation from the `pre_alloc` folder
116+
2. Apply the changes in `postStateDiff`:
117+
- **Modified accounts**: Replace existing accounts with new values
118+
- **New accounts**: Add accounts not present in pre-allocation
119+
- **Deleted accounts**: Remove accounts (represented as `null` values)
120+
121+
#### - `config`: [`FixtureConfig`](#fixtureconfig)
122+
123+
Chain configuration object to be applied to the client running the blockchain engine reorg test.
124+
125+
### `FixtureConfig`
126+
127+
#### - `network`: [`Fork`](./common_types.md#fork)
128+
129+
Fork configuration for the test. It is guaranteed that this field contains the same value as the root field `network`.
130+
131+
#### - `blobSchedule`: [`BlobSchedule`](./common_types.md#blobschedule-mappingforkforkblobschedule)
132+
133+
Optional; present from Cancun on. Maps forks to their blob schedule configurations as defined by [EIP-7840](https://eips.ethereum.org/EIPS/eip-7840).
134+
135+
### `FixtureEngineNewPayload`
136+
137+
Engine API payload structure identical to the one defined in [Blockchain Engine Tests](./blockchain_test_engine.md#fixtureenginenewpayload). Includes execution payload, versioned hashes, parent beacon block root, validation errors, version, and error codes.
138+
139+
## Usage Notes
140+
141+
- This format is only generated when using `--generate-shared-pre` and `--use-shared-pre` flags
142+
- The `pre_alloc` folder is essential and must be distributed with the test fixtures
143+
- Tests are grouped by identical (fork + environment + pre-allocation) combinations
144+
- The format is optimized for Engine API testing (post-Paris forks)
145+
- Reorganization scenarios are supported through the `forkChoiceUpdate` mechanism

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ evm_bytes = "cli.evm_bytes:evm_bytes"
101101
hasher = "cli.hasher:main"
102102
eest = "cli.eest.cli:eest"
103103
fillerconvert = "cli.fillerconvert.fillerconvert:main"
104+
groupstats = "cli.show_pre_alloc_group_stats:main"
104105

105106
[tool.setuptools.packages.find]
106107
where = ["src"]

pytest-execute.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ markers =
77
slow
88
pre_alloc_modify
99
ported_from
10+
pre_alloc_group: Control shared pre-allocation grouping (use "separate" for isolated group or custom string for named groups)
1011
addopts =
1112
-p pytest_plugins.concurrency
1213
-p pytest_plugins.execute.sender

pytest.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ markers =
77
slow
88
pre_alloc_modify
99
ported_from
10+
pre_alloc_group: Control shared pre-allocation grouping (use "separate" for isolated group or custom string for named groups)
1011
addopts =
1112
-p pytest_plugins.concurrency
1213
-p pytest_plugins.filler.pre_alloc

src/cli/gen_index.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,19 @@
2323

2424
from .hasher import HashableItem
2525

26+
# Files and directories to exclude from index generation
27+
INDEX_EXCLUDED_FILES = frozenset({"index.json"})
28+
INDEX_EXCLUDED_PATH_PARTS = frozenset({".meta", "pre_alloc"})
29+
2630

2731
def count_json_files_exclude_index(start_path: Path) -> int:
28-
"""
29-
Return the number of json files in the specified directory, excluding
30-
index.json files and tests in "blockchain_tests_engine".
31-
"""
32-
json_file_count = sum(1 for file in start_path.rglob("*.json") if file.name != "index.json")
32+
"""Return the number of fixture json files in the specified directory."""
33+
json_file_count = sum(
34+
1
35+
for file in start_path.rglob("*.json")
36+
if file.name not in INDEX_EXCLUDED_FILES
37+
and not any(part in INDEX_EXCLUDED_PATH_PARTS for part in file.parts)
38+
)
3339
return json_file_count
3440

3541

@@ -144,7 +150,9 @@ def generate_fixtures_index(
144150
fixture_formats = set()
145151
test_cases: List[TestCaseIndexFile] = []
146152
for file in input_path.rglob("*.json"):
147-
if file.name == "index.json" or ".meta" in file.parts:
153+
if file.name in INDEX_EXCLUDED_FILES or any(
154+
part in INDEX_EXCLUDED_PATH_PARTS for part in file.parts
155+
):
148156
continue
149157

150158
try:
@@ -165,6 +173,7 @@ def generate_fixtures_index(
165173
or f"0x{fixture.info.get('generatedTestHash')}",
166174
fork=fixture_fork,
167175
format=fixture.__class__,
176+
pre_hash=getattr(fixture, "pre_hash", None),
168177
)
169178
)
170179
if fixture_fork:

src/cli/pytest_commands/base.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,11 @@ def run_multiple(self, executions: List[PytestExecution]) -> int:
6363
"""
6464
for i, execution in enumerate(executions):
6565
if execution.description and len(executions) > 1:
66-
self.console.print(
67-
f"Phase {i + 1}/{len(executions)}: [italic]{execution.description}[/italic]"
66+
phase_text = (
67+
f"[bold blue]phase {i + 1}/{len(executions)}: "
68+
f"{execution.description}[/bold blue]"
6869
)
70+
self.console.rule(phase_text, style="bold blue")
6971

7072
result = self.run_single(execution.config_file, execution.args)
7173
if result != 0:

src/cli/pytest_commands/fill.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,129 @@ def __init__(self):
2121
],
2222
)
2323

24+
def create_executions(self, pytest_args: List[str]) -> List[PytestExecution]:
25+
"""
26+
Create execution plan that supports two-phase shared pre-state generation.
27+
28+
Returns single execution for normal filling, or two-phase execution
29+
when --gen-shared-pre is specified.
30+
"""
31+
processed_args = self.process_arguments(pytest_args)
32+
33+
# Check if we need two-phase execution
34+
if "--generate-shared-pre" in processed_args:
35+
return self._create_two_phase_executions(processed_args)
36+
elif "--use-shared-pre" in processed_args:
37+
# Only phase 2: using existing shared pre-allocation state
38+
return self._create_single_phase_with_shared_alloc(processed_args)
39+
else:
40+
# Normal single-phase execution
41+
return [
42+
PytestExecution(
43+
config_file=self.config_file,
44+
args=processed_args,
45+
)
46+
]
47+
48+
def _create_two_phase_executions(self, args: List[str]) -> List[PytestExecution]:
49+
"""Create two-phase execution: shared allocation generation + fixture filling."""
50+
# Phase 1: Shared allocation generation (clean and minimal output)
51+
phase1_args = self._create_phase1_args(args)
52+
53+
# Phase 2: Main fixture generation (full user options)
54+
phase2_args = self._create_phase2_args(args)
55+
56+
return [
57+
PytestExecution(
58+
config_file=self.config_file,
59+
args=phase1_args,
60+
description="generating shared pre-allocation state",
61+
),
62+
PytestExecution(
63+
config_file=self.config_file,
64+
args=phase2_args,
65+
description="filling test fixtures",
66+
),
67+
]
68+
69+
def _create_single_phase_with_shared_alloc(self, args: List[str]) -> List[PytestExecution]:
70+
"""Create single execution using existing shared pre-allocation state."""
71+
return [
72+
PytestExecution(
73+
config_file=self.config_file,
74+
args=args,
75+
)
76+
]
77+
78+
def _create_phase1_args(self, args: List[str]) -> List[str]:
79+
"""Create arguments for phase 1 (shared allocation generation)."""
80+
# Start with all args, then remove what we don't want for phase 1
81+
filtered_args = self._remove_unwanted_phase1_args(args)
82+
83+
# Add required phase 1 flags (with quiet output by default)
84+
phase1_args = [
85+
"--generate-shared-pre",
86+
"-qq", # Quiet pytest output by default (user -v/-vv/-vvv can override)
87+
] + filtered_args
88+
89+
return phase1_args
90+
91+
def _create_phase2_args(self, args: List[str]) -> List[str]:
92+
"""Create arguments for phase 2 (fixture filling)."""
93+
# Remove --generate-shared-pre and --clean, then add --use-shared-pre
94+
phase2_args = self._remove_generate_shared_pre_flag(args)
95+
phase2_args = self._remove_clean_flag(phase2_args)
96+
phase2_args = self._add_use_shared_pre_flag(phase2_args)
97+
return phase2_args
98+
99+
def _remove_unwanted_phase1_args(self, args: List[str]) -> List[str]:
100+
"""Remove arguments we don't want in phase 1 (pre-state generation)."""
101+
unwanted_flags = {
102+
# Output format flags
103+
"--html",
104+
# Report flags (we'll add our own -qq)
105+
"-q",
106+
"--quiet",
107+
"-qq",
108+
"--tb",
109+
# Shared allocation flags (we'll add our own)
110+
"--generate-shared-pre",
111+
"--use-shared-pre",
112+
}
113+
114+
filtered_args = []
115+
i = 0
116+
while i < len(args):
117+
arg = args[i]
118+
119+
# Skip unwanted flags
120+
if arg in unwanted_flags:
121+
# Skip flag and its value if it takes one
122+
if arg in ["--html", "--tb", "-n"] and i + 1 < len(args):
123+
i += 2 # Skip flag and value
124+
else:
125+
i += 1 # Skip just the flag
126+
# Skip unwanted flags with = format
127+
elif any(arg.startswith(f"{flag}=") for flag in unwanted_flags):
128+
i += 1
129+
else:
130+
filtered_args.append(arg)
131+
i += 1
132+
133+
return filtered_args
134+
135+
def _remove_generate_shared_pre_flag(self, args: List[str]) -> List[str]:
136+
"""Remove --generate-shared-pre flag from argument list."""
137+
return [arg for arg in args if arg != "--generate-shared-pre"]
138+
139+
def _remove_clean_flag(self, args: List[str]) -> List[str]:
140+
"""Remove --clean flag from argument list."""
141+
return [arg for arg in args if arg != "--clean"]
142+
143+
def _add_use_shared_pre_flag(self, args: List[str]) -> List[str]:
144+
"""Add --use-shared-pre flag to argument list."""
145+
return args + ["--use-shared-pre"]
146+
24147

25148
class PhilCommand(FillCommand):
26149
"""Friendly fill command with emoji reporting."""
@@ -74,3 +197,8 @@ def phil(pytest_args: List[str], **kwargs) -> None:
74197
"""Friendly alias for the fill command."""
75198
command = PhilCommand()
76199
command.execute(list(pytest_args))
200+
201+
202+
if __name__ == "__main__":
203+
# to allow debugging in vscode: in launch config, set "module": "cli.pytest_commands.fill"
204+
fill(prog_name="fill")

0 commit comments

Comments
 (0)