Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ bench-gas *args:
uv run fill \
--evm-bin="{{ evm_bin }}" \
--gas-benchmark-values 1 \
--generate-pre-alloc-groups \
--generate-all-formats \
--fork Osaka \
-m "not slow" \
-n auto --maxprocesses 10 --dist=loadgroup \
Expand Down
8 changes: 0 additions & 8 deletions docs/filling_tests/filling_tests_command_line.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,6 @@ This flag automatically performs a two-phase execution:
uv run fill --generate-all-formats --output=fixtures.tar.gz tests/shanghai/
```

!!! note "Alternative approach"
You can still use the legacy approach, but this will only generate the `BlockchainEngineXFixture` format:
```console
# Single command that automatically does 2-phase execution
# but only generates BlockchainEngineXFixture
uv run fill --generate-pre-alloc-groups tests/shanghai/
```

## Debugging the `t8n` Command

The `--evm-dump-dir` flag can be used to dump the inputs and outputs of every call made to the `t8n` command for debugging purposes, see [Debugging Transition Tools](./debugging_t8n_tools.md).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ Engine API payload structure identical to the one defined in [Blockchain Engine
## Usage Notes

- This format is generated when using:
- `--generate-pre-alloc-groups` flag (automatically triggers 2-phase execution, generates only `BlockchainEngineXFixture`)
- `--generate-pre-alloc-groups` followed by `--use-pre-alloc-groups` (two invocations: phase 1 populates the `pre_alloc` folder, phase 2 generates only `BlockchainEngineXFixture`)
- `--generate-all-formats` flag (automatically triggers 2-phase execution, generates all fixture formats)
- The `pre_alloc` folder is essential and must be distributed with the test fixtures
- Tests are grouped by identical (fork + environment + pre-allocation) combinations
Expand Down
109 changes: 68 additions & 41 deletions packages/testing/src/execution_testing/cli/pytest_commands/fill.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,33 +33,81 @@ def create_executions(
self, pytest_args: List[str]
) -> List[PytestExecution]:
"""
Create execution plan that supports two-phase pre-allocation group
Create execution plan supporting two-phase pre-allocation group
generation.

Returns single execution for normal filling, or two-phase execution
when --generate-pre-alloc-groups or --generate-all-formats is
specified.
Returns:
- Phase-1-only execution when `--generate-pre-alloc-groups` is
set without `--generate-all-formats`.
- Single-phase execution when `--use-pre-alloc-groups` is set,
regardless of `--generate-all-formats` (pre-alloc groups
already exist on disk from a previous run).
- Two-phase execution when `--generate-all-formats` is set.
- Normal single-phase execution otherwise.

"""
processed_args = self.process_arguments(pytest_args)
processed_args = self._add_default_ignores(processed_args)
self._validate_flag_combinations(processed_args)

# Check if we need two-phase execution
if self._should_use_two_phase_execution(processed_args):
return self._create_two_phase_executions(processed_args)
elif "--use-pre-alloc-groups" in processed_args:
# Only phase 2: using existing pre-allocation groups
if "--use-pre-alloc-groups" in processed_args:
# Pre-alloc groups already exist: single-phase fill only.
return self._create_single_phase_with_pre_alloc_groups(
processed_args
)
else:
# Normal single-phase execution
return [
PytestExecution(
config_file=self.config_path,
args=processed_args,
allowed_exit_codes=self.allowed_exit_codes,
)
]

has_phase_1_flag = "--generate-pre-alloc-groups" in processed_args
has_phase_2_flag = "--generate-all-formats" in processed_args

if has_phase_2_flag:
# --generate-all-formats always regenerates pre-alloc as phase 1.
return self._create_two_phase_executions(processed_args)
if has_phase_1_flag:
# Phase 1 only: generate pre-alloc groups without filling.
return [self._create_phase1_only_execution(processed_args)]

# Normal single-phase execution.
return [
PytestExecution(
config_file=self.config_path,
args=processed_args,
allowed_exit_codes=self.allowed_exit_codes,
)
]

def _validate_flag_combinations(self, args: List[str]) -> None:
"""
Reject contradictory flag combinations up front so phase 2 never
runs against a missing pre-alloc folder.
"""
if "--use-pre-alloc-groups" not in args:
return
if "--generate-pre-alloc-groups" in args:
raise click.UsageError(
"--use-pre-alloc-groups and --generate-pre-alloc-groups "
"are mutually exclusive: the first asserts the groups "
"already exist on disk, the second regenerates them."
)
if "--clean" in args:
raise click.UsageError(
"--use-pre-alloc-groups cannot be combined with --clean: "
"--clean wipes the output directory that holds the "
"pre-alloc groups."
)

def _create_phase1_only_execution(
self, args: List[str]
) -> PytestExecution:
"""Create a phase-1 execution that generates pre-allocation groups."""
return PytestExecution(
config_file=self.config_path,
args=self._create_phase1_args(args),
description="generating pre-allocation groups",
allowed_exit_codes=[
*self.allowed_exit_codes,
pytest.ExitCode.NO_TESTS_COLLECTED,
],
)

def _create_two_phase_executions(
self, args: List[str]
Expand All @@ -68,25 +116,11 @@ def _create_two_phase_executions(
Create two-phase execution: pre-allocation group generation + fixture
filling.
"""
# Phase 1: Pre-allocation group generation (clean and minimal output)
phase1_args = self._create_phase1_args(args)

# Phase 2: Main fixture generation (full user options)
phase2_args = self._create_phase2_args(args)

return [
self._create_phase1_only_execution(args),
PytestExecution(
config_file=self.config_path,
args=phase1_args,
description="generating pre-allocation groups",
allowed_exit_codes=[
*self.allowed_exit_codes,
pytest.ExitCode.NO_TESTS_COLLECTED,
],
),
PytestExecution(
config_file=self.config_path,
args=phase2_args,
args=self._create_phase2_args(args),
description="filling test fixtures",
),
]
Expand Down Expand Up @@ -195,13 +229,6 @@ def _add_use_pre_alloc_groups_flag(self, args: List[str]) -> List[str]:
"""Add --use-pre-alloc-groups flag to argument list."""
return args + ["--use-pre-alloc-groups"]

def _should_use_two_phase_execution(self, args: List[str]) -> bool:
"""Determine if two-phase execution is needed."""
return (
"--generate-pre-alloc-groups" in args
or "--generate-all-formats" in args
)

def _is_watch_mode(self, args: List[str]) -> bool:
"""Check if any watch flag is present in arguments."""
return any(flag in args for flag in ["--watch", "--watcherfall"])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

from unittest.mock import patch

import click
import pytest

from execution_testing.cli.pytest_commands.fill import (
FillCommand,
)
Expand Down Expand Up @@ -76,26 +79,79 @@ def test_generate_all_formats_removes_clean_from_phase2() -> None:
assert "--clean" not in phase2_args


def test_legacy_generate_pre_alloc_groups_still_works() -> None:
"""Test that the legacy --generate-pre-alloc-groups flag still works."""
def test_generate_pre_alloc_groups_alone_is_phase_1_only() -> None:
"""
Test that --generate-pre-alloc-groups without --generate-all-formats
runs phase 1 only, so CI can populate pre-alloc groups on a
dedicated runner without wasting time on phase 2.
"""
command = FillCommand()

with patch.object(command, "process_arguments", side_effect=lambda x: x):
pytest_args = ["--generate-pre-alloc-groups", "tests/somedir/"]
executions = command.create_executions(pytest_args)

assert len(executions) == 2
assert len(executions) == 1

# Phase 1: Should have --generate-pre-alloc-groups
phase1_args = executions[0].args
assert "--generate-pre-alloc-groups" in phase1_args
assert "--use-pre-alloc-groups" not in phase1_args
assert "--generate-all-formats" not in phase1_args

# Phase 2: Should have --use-pre-alloc-groups but NOT --generate-all-
# formats
phase2_args = executions[1].args
assert "--use-pre-alloc-groups" in phase2_args
assert "--generate-all-formats" not in phase2_args
assert "--generate-pre-alloc-groups" not in phase2_args

def test_use_pre_alloc_groups_forces_single_phase() -> None:
"""
Test that --use-pre-alloc-groups always runs a single phase, even
alongside --generate-all-formats (pre-alloc groups already exist on
disk from a previous run).
"""
command = FillCommand()

with patch.object(command, "process_arguments", side_effect=lambda x: x):
pytest_args = [
"--use-pre-alloc-groups",
"--generate-all-formats",
"tests/somedir/",
]
executions = command.create_executions(pytest_args)

assert len(executions) == 1
assert "--use-pre-alloc-groups" in executions[0].args
assert "--generate-all-formats" in executions[0].args


def test_use_and_generate_pre_alloc_groups_together_is_rejected() -> None:
"""
--use-pre-alloc-groups + --generate-pre-alloc-groups are contradictory:
the first asserts the groups exist, the second regenerates them.
"""
command = FillCommand()

with patch.object(command, "process_arguments", side_effect=lambda x: x):
pytest_args = [
"--use-pre-alloc-groups",
"--generate-pre-alloc-groups",
"tests/somedir/",
]
with pytest.raises(click.UsageError, match="mutually exclusive"):
command.create_executions(pytest_args)


def test_use_pre_alloc_groups_with_clean_is_rejected() -> None:
"""
--use-pre-alloc-groups + --clean is contradictory: --clean wipes the
output directory that holds the pre-alloc groups.
"""
command = FillCommand()

with patch.object(command, "process_arguments", side_effect=lambda x: x):
pytest_args = [
"--use-pre-alloc-groups",
"--clean",
"tests/somedir/",
]
with pytest.raises(click.UsageError, match="--clean"):
command.create_executions(pytest_args)


def test_single_phase_without_flags() -> None:
Expand Down
Loading