Skip to content

Commit 136e15f

Browse files
feat(fill): improve performance when xdist is enabled (#1118)
Co-authored-by: danceratopz <[email protected]>
1 parent fd62bd3 commit 136e15f

File tree

9 files changed

+96
-89
lines changed

9 files changed

+96
-89
lines changed

docs/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ Release tarball changes:
8383
- ✨ Modify `valid_at_transition_to` marker to add keyword arguments `subsequent_transitions` and `until` to fill a test using multiple transition forks ([#1081](https://github.com/ethereum/execution-spec-tests/pull/1081)).
8484
- 🐞 fix(consume): use `"HIVE_CHECK_LIVE_PORT"` to signal hive to wait for port 8551 (Engine API port) instead of the 8545 port when running `consume engine` ([#1095](https://github.com/ethereum/execution-spec-tests/pull/1095)).
8585
-`state_test`, `blockchain_test` and `blockchain_test_engine` fixtures now contain the `blobSchedule` from [EIP-7840](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7840.md), only for tests filled for Cancun and Prague forks ([#1040](https://github.com/ethereum/execution-spec-tests/pull/1040)).
86+
- 🔀 Change `--dist` flag to the default value, `load`, for better parallelism handling during test filling ([#1118](https://github.com/ethereum/execution-spec-tests/pull/1118))
8687

8788
### 🔧 EVM Tools
8889

docs/gen_test_case_reference.py

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"--gen-docs",
3333
f"--gen-docs-target-fork={TARGET_FORK}",
3434
f"--until={GENERATE_UNTIL_FORK}",
35+
"--skip-index",
3536
"-m",
3637
"(not blockchain_test_engine) and (not eip_version_check)",
3738
"-s",

pytest.ini

-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ addopts =
1818
-p pytest_plugins.help.help
1919
-m "not eip_version_check"
2020
--tb short
21-
--dist loadscope
2221
--ignore tests/cancun/eip4844_blobs/point_evaluation_vectors/
2322
# these customizations require the pytest-custom-report plugin
2423
report_passed_verbose = FILLED

src/cli/pytest_commands/fill.py

+2-6
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,7 @@ def handle_fill_command_flags(fill_args: List[str]) -> List[str]:
4444
def fill(pytest_args: List[str], **kwargs) -> None:
4545
"""Entry point for the fill command."""
4646
result = pytest.main(
47-
handle_fill_command_flags(
48-
["--index", *pytest_args],
49-
),
47+
handle_fill_command_flags(list(pytest_args)),
5048
)
5149
sys.exit(result)
5250

@@ -59,9 +57,7 @@ def fill(pytest_args: List[str], **kwargs) -> None:
5957
@common_click_options
6058
def phil(pytest_args: List[str], **kwargs) -> None:
6159
"""Friendly alias for the fill command."""
62-
args = handle_fill_command_flags(
63-
["--index", *pytest_args],
64-
)
60+
args = handle_fill_command_flags(list(pytest_args))
6561
result = pytest.main(
6662
args
6763
+ [

src/ethereum_test_fixtures/file.py

+11-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from pathlib import Path
55
from typing import Annotated, Any, Dict, Optional
66

7+
from filelock import FileLock
78
from pydantic import Discriminator, Tag
89

910
from ethereum_test_base_types import EthereumTestRootModel
@@ -65,10 +66,16 @@ def collect_into_file(self, file_path: Path):
6566
add the hash to the info field on per-fixture basis.
6667
"""
6768
json_fixtures: Dict[str, Dict[str, Any]] = {}
68-
for name, fixture in self.items():
69-
json_fixtures[name] = fixture.json_dict_with_info()
70-
with open(file_path, "w") as f:
71-
json.dump(json_fixtures, f, indent=4)
69+
lock_file_path = file_path.with_suffix(".lock")
70+
with FileLock(lock_file_path):
71+
if file_path.exists():
72+
with open(file_path, "r") as f:
73+
json_fixtures = json.load(f)
74+
for name, fixture in self.items():
75+
json_fixtures[name] = fixture.json_dict_with_info()
76+
77+
with open(file_path, "w") as f:
78+
json.dump(dict(sorted(json_fixtures.items())), f, indent=4)
7279

7380
@classmethod
7481
def from_file(

src/pytest_plugins/filler/filler.py

+43-62
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
from typing import Any, Dict, Generator, List, Type
1616

1717
import pytest
18+
import xdist
1819
from _pytest.terminal import TerminalReporter
19-
from filelock import FileLock
2020
from pytest_metadata.plugin import metadata_key # type: ignore
2121

2222
from cli.gen_index import generate_fixtures_index
@@ -164,11 +164,11 @@ def pytest_addoption(parser: pytest.Parser):
164164
help="Specify a build name for the fixtures.ini file, e.g., 'stable'.",
165165
)
166166
test_group.addoption(
167-
"--index",
168-
action="store_true",
167+
"--skip-index",
168+
action="store_false",
169169
dest="generate_index",
170-
default=False,
171-
help="Generate an index file for all produced fixtures.",
170+
default=True,
171+
help="Skip generating an index file for all produced fixtures.",
172172
)
173173

174174
debug_group = parser.getgroup("debug", "Arguments defining debug behavior")
@@ -532,27 +532,6 @@ def create_properties_file(
532532
config.write(f)
533533

534534

535-
@pytest.fixture(scope="session", autouse=True)
536-
def create_tarball(
537-
request: pytest.FixtureRequest, output_dir: Path, is_output_tarball: bool
538-
) -> Generator[None, None, None]:
539-
"""
540-
Create a tarball of json files the output directory if the configured
541-
output ends with '.tar.gz'.
542-
543-
Only include .json and .ini files in the archive.
544-
"""
545-
yield
546-
if is_output_tarball:
547-
source_dir = output_dir
548-
tarball_filename = request.config.getoption("output")
549-
with tarfile.open(tarball_filename, "w:gz") as tar:
550-
for file in source_dir.rglob("*"):
551-
if file.suffix in {".json", ".ini"}:
552-
arcname = Path("fixtures") / file.relative_to(source_dir)
553-
tar.add(file, arcname=arcname)
554-
555-
556535
@pytest.fixture(scope="function")
557536
def dump_dir_parameter_level(
558537
request: pytest.FixtureRequest, base_dump_dir: Path | None, filler_path: Path
@@ -590,11 +569,6 @@ def get_fixture_collection_scope(fixture_name, config):
590569
return "module"
591570

592571

593-
@pytest.fixture(scope="session")
594-
def generate_index(request) -> bool: # noqa: D103
595-
return request.config.option.generate_index
596-
597-
598572
@pytest.fixture(scope=get_fixture_collection_scope)
599573
def fixture_collector(
600574
request: pytest.FixtureRequest,
@@ -603,29 +577,11 @@ def fixture_collector(
603577
filler_path: Path,
604578
base_dump_dir: Path | None,
605579
output_dir: Path,
606-
session_temp_folder: Path | None,
607-
generate_index: bool,
608580
) -> Generator[FixtureCollector, None, None]:
609581
"""
610582
Return configured fixture collector instance used for all tests
611583
in one test module.
612584
"""
613-
if session_temp_folder is not None:
614-
fixture_collector_count_file_name = "fixture_collector_count"
615-
fixture_collector_count_file = session_temp_folder / fixture_collector_count_file_name
616-
fixture_collector_count_file_lock = (
617-
session_temp_folder / f"{fixture_collector_count_file_name}.lock"
618-
)
619-
with FileLock(fixture_collector_count_file_lock):
620-
if fixture_collector_count_file.exists():
621-
with open(fixture_collector_count_file, "r") as f:
622-
fixture_collector_count = int(f.read())
623-
else:
624-
fixture_collector_count = 0
625-
fixture_collector_count += 1
626-
with open(fixture_collector_count_file, "w") as f:
627-
f.write(str(fixture_collector_count))
628-
629585
fixture_collector = FixtureCollector(
630586
output_dir=output_dir,
631587
flat_output=request.config.getoption("flat_output"),
@@ -638,19 +594,6 @@ def fixture_collector(
638594
if do_fixture_verification:
639595
fixture_collector.verify_fixture_files(evm_fixture_verification)
640596

641-
fixture_collector_count = 0
642-
if session_temp_folder is not None:
643-
with FileLock(fixture_collector_count_file_lock):
644-
with open(fixture_collector_count_file, "r") as f:
645-
fixture_collector_count = int(f.read())
646-
fixture_collector_count -= 1
647-
with open(fixture_collector_count_file, "w") as f:
648-
f.write(str(fixture_collector_count))
649-
if generate_index and fixture_collector_count == 0:
650-
generate_fixtures_index(
651-
output_dir, quiet_mode=True, force_flag=False, disable_infer_format=False
652-
)
653-
654597

655598
@pytest.fixture(autouse=True, scope="session")
656599
def filler_path(request: pytest.FixtureRequest) -> Path:
@@ -809,3 +752,41 @@ def pytest_collection_modifyitems(config: pytest.Config, items: List[pytest.Item
809752
item.add_marker(mark)
810753
if "yul" in item.fixturenames: # type: ignore
811754
item.add_marker(pytest.mark.yul_test)
755+
756+
757+
def pytest_sessionfinish(session: pytest.Session, exitstatus: int):
758+
"""
759+
Perform session finish tasks.
760+
761+
- Remove any lock files that may have been created.
762+
- Generate index file for all produced fixtures.
763+
- Create tarball of the output directory if the output is a tarball.
764+
"""
765+
if xdist.is_xdist_worker(session):
766+
return
767+
768+
output: Path = session.config.getoption("output")
769+
if is_output_stdout(output):
770+
return
771+
772+
output_dir = strip_output_tarball_suffix(output)
773+
# Remove any lock files that may have been created.
774+
for file in output_dir.rglob("*.lock"):
775+
file.unlink()
776+
777+
# Generate index file for all produced fixtures.
778+
if session.config.getoption("generate_index"):
779+
generate_fixtures_index(
780+
output_dir, quiet_mode=True, force_flag=False, disable_infer_format=False
781+
)
782+
783+
# Create tarball of the output directory if the output is a tarball.
784+
is_output_tarball = output.suffix == ".gz" and output.with_suffix("").suffix == ".tar"
785+
if is_output_tarball:
786+
source_dir = output_dir
787+
tarball_filename = output
788+
with tarfile.open(tarball_filename, "w:gz") as tar:
789+
for file in source_dir.rglob("*"):
790+
if file.suffix in {".json", ".ini"}:
791+
arcname = Path("fixtures") / file.relative_to(source_dir)
792+
tar.add(file, arcname=arcname)

src/pytest_plugins/filler/tests/test_filler.py

+32-16
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def test_shanghai_two(state_test, x):
7979

8080
@pytest.mark.run_in_serial
8181
@pytest.mark.parametrize(
82-
"args, expected_fixture_files, expected_fixture_counts, expected_index",
82+
"args, expected_fixture_files, expected_fixture_counts",
8383
[
8484
pytest.param(
8585
[],
@@ -102,11 +102,33 @@ def test_shanghai_two(state_test, x):
102102
Path("fixtures/state_tests/shanghai/module_shanghai/shanghai_two.json"),
103103
],
104104
[2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 6, 6],
105-
False,
106105
id="default-args",
107106
),
108107
pytest.param(
109-
["--index", "--build-name", "test_build"],
108+
["--skip-index"],
109+
[
110+
Path("fixtures/blockchain_tests/paris/module_paris/paris_one.json"),
111+
Path("fixtures/blockchain_tests_engine/paris/module_paris/paris_one.json"),
112+
Path("fixtures/state_tests/paris/module_paris/paris_one.json"),
113+
Path("fixtures/blockchain_tests/paris/module_paris/paris_two.json"),
114+
Path("fixtures/blockchain_tests_engine/paris/module_paris/paris_two.json"),
115+
Path("fixtures/state_tests/paris/module_paris/paris_two.json"),
116+
Path("fixtures/blockchain_tests/shanghai/module_shanghai/shanghai_one.json"),
117+
Path(
118+
"fixtures/blockchain_tests_engine/shanghai/module_shanghai/shanghai_one.json"
119+
),
120+
Path("fixtures/state_tests/shanghai/module_shanghai/shanghai_one.json"),
121+
Path("fixtures/blockchain_tests/shanghai/module_shanghai/shanghai_two.json"),
122+
Path(
123+
"fixtures/blockchain_tests_engine/shanghai/module_shanghai/shanghai_two.json"
124+
),
125+
Path("fixtures/state_tests/shanghai/module_shanghai/shanghai_two.json"),
126+
],
127+
[2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 6, 6],
128+
id="skip-index",
129+
),
130+
pytest.param(
131+
["--build-name", "test_build"],
110132
[
111133
Path("fixtures/blockchain_tests/paris/module_paris/paris_one.json"),
112134
Path("fixtures/blockchain_tests_engine/paris/module_paris/paris_one.json"),
@@ -126,11 +148,10 @@ def test_shanghai_two(state_test, x):
126148
Path("fixtures/state_tests/shanghai/module_shanghai/shanghai_two.json"),
127149
],
128150
[2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 6, 6],
129-
True,
130151
id="build-name-in-fixtures-ini-file",
131152
),
132153
pytest.param(
133-
["--flat-output", "--index"],
154+
["--flat-output"],
134155
[
135156
Path("fixtures/blockchain_tests/paris_one.json"),
136157
Path("fixtures/blockchain_tests_engine/paris_one.json"),
@@ -146,11 +167,10 @@ def test_shanghai_two(state_test, x):
146167
Path("fixtures/state_tests/shanghai_two.json"),
147168
],
148169
[2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 6, 6],
149-
True,
150170
id="flat-output",
151171
),
152172
pytest.param(
153-
["--flat-output", "--index", "--output", "other_fixtures"],
173+
["--flat-output", "--output", "other_fixtures"],
154174
[
155175
Path("other_fixtures/blockchain_tests/paris_one.json"),
156176
Path("other_fixtures/blockchain_tests_engine/paris_one.json"),
@@ -166,11 +186,10 @@ def test_shanghai_two(state_test, x):
166186
Path("other_fixtures/state_tests/shanghai_two.json"),
167187
],
168188
[2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 6, 6],
169-
True,
170189
id="flat-output_custom-output-dir",
171190
),
172191
pytest.param(
173-
["--single-fixture-per-file", "--index"],
192+
["--single-fixture-per-file"],
174193
[
175194
Path(
176195
"fixtures/blockchain_tests/paris/module_paris/paris_one__fork_Paris_blockchain_test.json"
@@ -282,11 +301,10 @@ def test_shanghai_two(state_test, x):
282301
),
283302
],
284303
[1] * 36,
285-
True,
286304
id="single-fixture-per-file",
287305
),
288306
pytest.param(
289-
["--single-fixture-per-file", "--index", "--output", "other_fixtures"],
307+
["--single-fixture-per-file", "--output", "other_fixtures"],
290308
[
291309
Path(
292310
"other_fixtures/blockchain_tests/paris/module_paris/paris_one__fork_Paris_blockchain_test.json"
@@ -398,11 +416,10 @@ def test_shanghai_two(state_test, x):
398416
),
399417
],
400418
[1] * 36,
401-
True,
402419
id="single-fixture-per-file_custom_output_dir",
403420
),
404421
pytest.param(
405-
["--flat-output", "--index", "--single-fixture-per-file"],
422+
["--flat-output", "--single-fixture-per-file"],
406423
[
407424
Path("fixtures/blockchain_tests/paris_one__fork_Paris_blockchain_test.json"),
408425
Path("fixtures/state_tests/paris_one__fork_Paris_state_test.json"),
@@ -478,13 +495,12 @@ def test_shanghai_two(state_test, x):
478495
),
479496
],
480497
[1] * 36,
481-
True,
482498
id="flat-single-per-file_flat-output",
483499
),
484500
],
485501
)
486502
def test_fixture_output_based_on_command_line_args(
487-
testdir, args, expected_fixture_files, expected_fixture_counts, expected_index
503+
testdir, args, expected_fixture_files, expected_fixture_counts
488504
):
489505
"""
490506
Test:
@@ -571,7 +587,7 @@ def test_fixture_output_based_on_command_line_args(
571587
config = configparser.ConfigParser()
572588
config.read(ini_file)
573589

574-
if expected_index:
590+
if "--skip-index" not in args:
575591
assert index_file is not None, f"No {expected_index_file} file was found in {meta_dir}"
576592

577593
properties = {key: value for key, value in config.items("fixtures")}

stubs/xdist/__init__.pyi

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .methods import is_xdist_worker
2+
3+
__all__ = ("is_xdist_worker",)

stubs/xdist/methods.pyi

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import pytest
2+
3+
def is_xdist_worker(session: pytest.Session) -> bool: ...

0 commit comments

Comments
 (0)