Skip to content

Commit a2449bf

Browse files
authored
fix(ci): All slow unit tests, remove run_in_serial (#1870)
* fix(ci): Speed up slow unit tests by reusing t8n server * refactor(fill): add flag to more tests * fix(filler): flag
1 parent 8e8e64c commit a2449bf

File tree

11 files changed

+85
-47
lines changed

11 files changed

+85
-47
lines changed

CLAUDE.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ uv run ruff check --fix src tests .github/scripts
6767
uv run mypy src tests .github/scripts
6868

6969
# Framework unit tests
70-
uv run pytest -c pytest-framework.ini -n auto -m "not run_in_serial"
71-
uv run pytest -c pytest-framework.ini -m run_in_serial
70+
uv run pytest -c pytest-framework.ini -n auto
7271

7372
# Run specific checks (fast checks)
7473
uvx --with=tox-uv tox -e lint,typecheck,spellcheck

pytest-framework.ini

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ python_files=
55
test_*.py
66
testpaths =
77
src
8-
markers =
9-
run_in_serial
108
addopts =
119
-p pytester
1210
-p pytest_plugins.eels_resolver

src/cli/gentest/tests/test_cli.py

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
"""Tests for the gentest CLI command."""
22

3-
from tempfile import TemporaryDirectory
4-
53
import pytest
64
from click.testing import CliRunner
75

86
from cli.gentest.cli import generate
97
from cli.gentest.test_context_providers import StateTestProvider
10-
from cli.pytest_commands.fill import fill
118
from ethereum_test_base_types import Account
129
from ethereum_test_tools import Environment, Storage, Transaction
1310

@@ -92,14 +89,18 @@ def transaction_hash(tx_type: int) -> str: # noqa: D103
9289

9390

9491
@pytest.mark.parametrize("tx_type", list(transactions_by_type.keys()))
95-
def test_tx_type(tmp_path, monkeypatch, tx_type, transaction_hash):
92+
def test_tx_type(pytester, tmp_path, monkeypatch, tx_type, transaction_hash, default_t8n):
9693
"""Generates a test case for any transaction type."""
9794
## Arrange ##
9895
# This test is run in a CI environment, where connection to a node could be
9996
# unreliable. Therefore, we mock the RPC request to avoid any network issues.
10097
# This is done by patching the `get_context` method of the `StateTestProvider`.
10198
runner = CliRunner()
102-
output_file = str(tmp_path / f"gentest_type_{tx_type}.py")
99+
tmp_path_tests = tmp_path / "tests"
100+
tmp_path_tests.mkdir()
101+
tmp_path_output = tmp_path / "output"
102+
tmp_path_output.mkdir()
103+
generated_py_file = str(tmp_path_tests / f"gentest_type_{tx_type}.py")
103104

104105
tx = transactions_by_type[tx_type]
105106

@@ -109,17 +110,21 @@ def get_mock_context(self: StateTestProvider) -> dict:
109110
monkeypatch.setattr(StateTestProvider, "get_context", get_mock_context)
110111

111112
## Generate ##
112-
gentest_result = runner.invoke(generate, [transaction_hash, output_file])
113+
gentest_result = runner.invoke(generate, [transaction_hash, generated_py_file])
113114
assert gentest_result.exit_code == 0
114115

115116
## Fill ##
117+
with open(generated_py_file, "r") as f:
118+
pytester.makepyfile(f.read())
119+
pytester.copy_example(name="pytest.ini")
120+
116121
args = [
117-
"-c",
118-
"pytest.ini",
119-
"--skip-evm-dump",
120-
"--output",
121-
TemporaryDirectory().name,
122-
output_file,
122+
"-m",
123+
"state_test",
124+
"--fork",
125+
"Cancun",
126+
"--t8n-server-url",
127+
default_t8n.server_url,
123128
]
124-
fill_result = runner.invoke(fill, args)
125-
assert fill_result.exit_code == 0, f"Fill command failed:\n{fill_result.output}"
129+
result = pytester.runpytest("-v", *args)
130+
assert result.ret == pytest.ExitCode.OK, f"Fill command failed:\n{result}"

src/cli/tests/test_pytest_fill_command.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,25 @@ def test_fill_with_invalid_option(runner):
4444
assert "unrecognized arguments" in result.output
4545

4646

47-
@pytest.mark.run_in_serial
4847
class TestHtmlReportFlags:
4948
"""Test html report generation and output options."""
5049

5150
@pytest.fixture
52-
def fill_args(self):
51+
def fill_args(self, default_t8n):
5352
"""
5453
Provide default arguments for the `fill` command when testing html report
5554
generation.
5655
5756
Specifies a single existing example test case for faster fill execution,
5857
and to allow for tests to check for the fixture generation location.
5958
"""
60-
return ["-k", "test_dup and state_test-DUP16", "--fork", "Frontier"]
59+
return [
60+
"-k",
61+
"test_dup and state_test-DUP16",
62+
"--fork",
63+
"Frontier",
64+
f"--t8n-server-url={default_t8n.server_url}",
65+
]
6166

6267
@pytest.fixture()
6368
def default_html_report_file_path(self):

src/conftest.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,6 @@
77

88
from ethereum_clis import BesuTransitionTool, ExecutionSpecsTransitionTool, TransitionTool
99

10-
11-
def pytest_runtest_setup(item):
12-
"""Skip tests if running with pytest-xdist in parallel."""
13-
marker = item.get_closest_marker(name="run_in_serial")
14-
if marker is not None:
15-
if os.getenv("PYTEST_XDIST_WORKER_COUNT") not in [None, "1"]:
16-
pytest.skip("Skipping test because pytest-xdist is running with more than one worker.")
17-
18-
1910
DEFAULT_TRANSITION_TOOL_FOR_UNIT_TESTS = ExecutionSpecsTransitionTool
2011

2112
INSTALLED_TRANSITION_TOOLS = [
@@ -37,7 +28,9 @@ def installed_transition_tool_instances() -> Generator[
3728
instances: Dict[str, TransitionTool | Exception] = {}
3829
for transition_tool_class in INSTALLED_TRANSITION_TOOLS:
3930
try:
40-
instances[transition_tool_class.__name__] = transition_tool_class()
31+
transition_tool_instance = transition_tool_class()
32+
transition_tool_instance.start_server()
33+
instances[transition_tool_class.__name__] = transition_tool_instance
4134
except Exception as e:
4235
# Record the exception in order to provide context when failing the appropriate test
4336
instances[transition_tool_class.__name__] = e

src/ethereum_clis/clis/execution_specs.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,14 @@ class ExecutionSpecsTransitionTool(TransitionTool):
4646
detect_binary_pattern = re.compile(r"^ethereum-spec-evm-resolver\b")
4747
t8n_use_server: bool = True
4848
server_dir: Optional[TemporaryDirectory] = None
49+
server_url: str | None = None
4950

5051
def __init__(
5152
self,
5253
*,
5354
binary: Optional[Path] = None,
5455
trace: bool = False,
56+
server_url: str | None = None,
5557
):
5658
"""Initialize the Ethereum Specs EVM Resolver Transition Tool interface."""
5759
os.environ.setdefault("NO_PROXY", "*") # Disable proxy for local connections
@@ -71,6 +73,7 @@ def __init__(
7173
f"Unexpected exception calling ethereum-spec-evm-resolver: {e}."
7274
) from e
7375
self.help_string = result.stdout
76+
self.server_url = server_url
7477

7578
def start_server(self):
7679
"""

src/ethereum_clis/transition_tool.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class TransitionTool(EthereumCLI):
6262
cached_version: Optional[str] = None
6363
t8n_use_stream: bool = False
6464
t8n_use_server: bool = False
65-
server_url: str
65+
server_url: str | None = None
6666
process: Optional[subprocess.Popen] = None
6767

6868
@abstractmethod
@@ -555,7 +555,7 @@ def evaluate(
555555
can be overridden.
556556
"""
557557
if self.t8n_use_server:
558-
if not self.process:
558+
if not self.server_url:
559559
self.start_server()
560560
return self._evaluate_server(
561561
t8n_data=transition_tool_data,

src/pytest_plugins/filler/filler.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,17 @@ def pytest_addoption(parser: pytest.Parser):
128128
" Default: `ethereum-spec-evm-resolver`."
129129
),
130130
)
131+
evm_group.addoption(
132+
"--t8n-server-url",
133+
action="store",
134+
dest="t8n_server_url",
135+
type=str,
136+
default=None,
137+
help=(
138+
"[INTERNAL USE ONLY] URL of the t8n server to use. Used by framework tests/ci; not "
139+
"intended for regular CLI use."
140+
),
141+
)
131142
evm_group.addoption(
132143
"--traces",
133144
action="store_true",
@@ -557,18 +568,27 @@ def verify_fixtures_bin(request: pytest.FixtureRequest) -> Path | None:
557568
return request.config.getoption("verify_fixtures_bin")
558569

559570

571+
@pytest.fixture(autouse=True, scope="session")
572+
def t8n_server_url(request: pytest.FixtureRequest) -> str | None:
573+
"""Return configured t8n server url."""
574+
return request.config.getoption("t8n_server_url")
575+
576+
560577
@pytest.fixture(autouse=True, scope="session")
561578
def t8n(
562-
request: pytest.FixtureRequest, evm_bin: Path | None
579+
request: pytest.FixtureRequest, evm_bin: Path | None, t8n_server_url: str | None
563580
) -> Generator[TransitionTool, None, None]:
564581
"""Return configured transition tool."""
582+
kwargs = {
583+
"trace": request.config.getoption("evm_collect_traces"),
584+
}
585+
if t8n_server_url is not None:
586+
kwargs["server_url"] = t8n_server_url
565587
if evm_bin is None:
566588
assert TransitionTool.default_tool is not None, "No default transition tool found"
567-
t8n = TransitionTool.default_tool(trace=request.config.getoption("evm_collect_traces"))
589+
t8n = TransitionTool.default_tool(**kwargs)
568590
else:
569-
t8n = TransitionTool.from_binary_path(
570-
binary_path=evm_bin, trace=request.config.getoption("evm_collect_traces")
571-
)
591+
t8n = TransitionTool.from_binary_path(binary_path=evm_bin, **kwargs)
572592
if not t8n.exception_mapper.reliable:
573593
warnings.warn(
574594
f"The t8n tool that is currently being used to fill tests ({t8n.__class__.__name__}) "

src/pytest_plugins/filler/tests/test_filler.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ def test_shanghai_two(state_test, x):
7878
total_test_count = test_count_paris + test_count_shanghai
7979

8080

81-
@pytest.mark.run_in_serial
8281
@pytest.mark.parametrize(
8382
"args, expected_fixture_files, expected_fixture_counts",
8483
[
@@ -513,7 +512,11 @@ def test_shanghai_two(state_test, x):
513512
],
514513
)
515514
def test_fixture_output_based_on_command_line_args(
516-
testdir, args, expected_fixture_files, expected_fixture_counts
515+
testdir,
516+
args,
517+
expected_fixture_files,
518+
expected_fixture_counts,
519+
default_t8n,
517520
):
518521
"""
519522
Test:
@@ -548,6 +551,8 @@ def test_fixture_output_based_on_command_line_args(
548551
testdir.copy_example(name="pytest.ini")
549552
args.append("-v")
550553
args.append("--no-html")
554+
args.append("--t8n-server-url")
555+
args.append(default_t8n.server_url)
551556

552557
result = testdir.runpytest(*args)
553558
result.assert_outcomes(
@@ -600,7 +605,9 @@ def test_fixture_output_based_on_command_line_args(
600605

601606
assert ini_file is not None, f"No {expected_ini_file} file was found in {meta_dir}"
602607
config = configparser.ConfigParser()
603-
config.read(ini_file)
608+
ini_file_text = ini_file.read_text()
609+
ini_file_text = ini_file_text.replace(default_t8n.server_url, "t8n_server_path")
610+
config.read_string(ini_file_text)
604611

605612
if "--skip-index" not in args:
606613
assert index_file is not None, f"No {expected_index_file} file was found in {meta_dir}"
@@ -632,7 +639,6 @@ def test_max_gas_limit(state_test, pre, block_gas_limit):
632639
)
633640

634641

635-
@pytest.mark.run_in_serial
636642
@pytest.mark.parametrize(
637643
"args, expected_fixture_files, expected_fixture_counts, expected_gas_limit",
638644
[
@@ -661,7 +667,12 @@ def test_max_gas_limit(state_test, pre, block_gas_limit):
661667
],
662668
)
663669
def test_fill_variables(
664-
testdir, args, expected_fixture_files, expected_fixture_counts, expected_gas_limit
670+
testdir,
671+
args,
672+
expected_fixture_files,
673+
expected_fixture_counts,
674+
expected_gas_limit,
675+
default_t8n,
665676
):
666677
"""
667678
Test filling tests that depend on variables such as the max block gas limit.
@@ -679,6 +690,8 @@ def test_fill_variables(
679690
args.append("-m")
680691
args.append("state_test")
681692
args.append("--no-html")
693+
args.append("--t8n-server-url")
694+
args.append(default_t8n.server_url)
682695
result = testdir.runpytest(*args)
683696
result.assert_outcomes(
684697
passed=1,
@@ -730,7 +743,9 @@ def test_fill_variables(
730743

731744
assert ini_file is not None, f"No {expected_ini_file} file was found in {meta_dir}"
732745
config = configparser.ConfigParser()
733-
config.read(ini_file)
746+
ini_file_text = ini_file.read_text()
747+
ini_file_text = ini_file_text.replace(default_t8n.server_url, "t8n_server_path")
748+
config.read_string(ini_file_text)
734749

735750
if "--skip-index" not in args:
736751
assert index_file is not None, f"No {expected_index_file} file was found in {meta_dir}"

src/pytest_plugins/filler/tests/test_output_directory.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def fill_fork_until() -> str:
3030

3131

3232
@pytest.fixture
33-
def run_fill(test_path: Path, fill_fork_from: str, fill_fork_until: str):
33+
def run_fill(test_path: Path, fill_fork_from: str, fill_fork_until: str, default_t8n):
3434
"""Create a function to run the fill command with various output directory scenarios."""
3535

3636
def _run_fill(output_dir: Path, clean: bool = False, expect_failure: bool = False):
@@ -44,6 +44,7 @@ def _run_fill(output_dir: Path, clean: bool = False, expect_failure: bool = Fals
4444
f"--from={fill_fork_from}",
4545
f"--until={fill_fork_until}",
4646
f"--output={str(output_dir)}",
47+
f"--t8n-server-url={default_t8n.server_url}",
4748
str(test_path),
4849
]
4950

0 commit comments

Comments
 (0)