|
8 | 8 |
|
9 | 9 | import configparser
|
10 | 10 | import datetime
|
| 11 | +import json |
11 | 12 | import os
|
12 | 13 | import warnings
|
13 | 14 | from enum import Enum
|
|
18 | 19 | import xdist
|
19 | 20 | from _pytest.compat import NotSetType
|
20 | 21 | from _pytest.terminal import TerminalReporter
|
| 22 | +from filelock import FileLock |
21 | 23 | from pytest_metadata.plugin import metadata_key # type: ignore
|
22 | 24 |
|
23 | 25 | from cli.gen_index import generate_fixtures_index
|
|
36 | 38 | )
|
37 | 39 | from ethereum_test_forks import Fork, get_transition_fork_predecessor, get_transition_forks
|
38 | 40 | from ethereum_test_specs import BaseTest
|
| 41 | +from ethereum_test_specs.base import OpMode |
39 | 42 | from ethereum_test_tools.utility.versioning import (
|
40 | 43 | generate_github_url,
|
41 | 44 | get_current_commit_hash_or_tag,
|
@@ -280,6 +283,44 @@ def pytest_addoption(parser: pytest.Parser):
|
280 | 283 | ),
|
281 | 284 | )
|
282 | 285 |
|
| 286 | + optimize_gas_group = parser.getgroup( |
| 287 | + "optimize gas", |
| 288 | + "Arguments defining test gas optimization behavior.", |
| 289 | + ) |
| 290 | + optimize_gas_group.addoption( |
| 291 | + "--optimize-gas", |
| 292 | + action="store_true", |
| 293 | + dest="optimize_gas", |
| 294 | + default=False, |
| 295 | + help=( |
| 296 | + "Attempt to optimize the gas used in every transaction for the filled tests, " |
| 297 | + "then print the minimum amount of gas at which the test still produces a correct " |
| 298 | + "post state and the exact same trace." |
| 299 | + ), |
| 300 | + ) |
| 301 | + optimize_gas_group.addoption( |
| 302 | + "--optimize-gas-output", |
| 303 | + action="store", |
| 304 | + dest="optimize_gas_output", |
| 305 | + default=Path("optimize-gas-output.json"), |
| 306 | + type=Path, |
| 307 | + help=( |
| 308 | + "Path to the JSON file that is output to the gas optimization. " |
| 309 | + "Requires `--optimize-gas`." |
| 310 | + ), |
| 311 | + ) |
| 312 | + optimize_gas_group.addoption( |
| 313 | + "--optimize-gas-post-processing", |
| 314 | + action="store_true", |
| 315 | + dest="optimize_gas_post_processing", |
| 316 | + default=False, |
| 317 | + help=( |
| 318 | + "Post process the traces during gas optimization in order to Account for " |
| 319 | + "opcodes that put the current gas in the stack, in order to remove " |
| 320 | + "remaining-gas from the comparison." |
| 321 | + ), |
| 322 | + ) |
| 323 | + |
283 | 324 | debug_group = parser.getgroup("debug", "Arguments defining debug behavior")
|
284 | 325 | debug_group.addoption(
|
285 | 326 | "--evm-dump-dir",
|
@@ -359,16 +400,25 @@ def pytest_configure(config):
|
359 | 400 | ):
|
360 | 401 | config.option.htmlpath = config.fixture_output.directory / default_html_report_file_path()
|
361 | 402 |
|
| 403 | + config.gas_optimized_tests = {} |
| 404 | + if config.getoption("optimize_gas", False): |
| 405 | + if config.getoption("optimize_gas_post_processing"): |
| 406 | + config.op_mode = OpMode.OPTIMIZE_GAS_POST_PROCESSING |
| 407 | + else: |
| 408 | + config.op_mode = OpMode.OPTIMIZE_GAS |
| 409 | + |
| 410 | + config.collect_traces = config.getoption("evm_collect_traces") or config.getoption( |
| 411 | + "optimize_gas", False |
| 412 | + ) |
| 413 | + |
362 | 414 | # Instantiate the transition tool here to check that the binary path/trace option is valid.
|
363 | 415 | # This ensures we only raise an error once, if appropriate, instead of for every test.
|
364 | 416 | evm_bin = config.getoption("evm_bin")
|
365 | 417 | if evm_bin is None:
|
366 | 418 | assert TransitionTool.default_tool is not None, "No default transition tool found"
|
367 |
| - t8n = TransitionTool.default_tool(trace=config.getoption("evm_collect_traces")) |
| 419 | + t8n = TransitionTool.default_tool(trace=config.collect_traces) |
368 | 420 | else:
|
369 |
| - t8n = TransitionTool.from_binary_path( |
370 |
| - binary_path=evm_bin, trace=config.getoption("evm_collect_traces") |
371 |
| - ) |
| 421 | + t8n = TransitionTool.from_binary_path(binary_path=evm_bin, trace=config.collect_traces) |
372 | 422 | if (
|
373 | 423 | isinstance(config.getoption("numprocesses"), int)
|
374 | 424 | and config.getoption("numprocesses") > 0
|
@@ -584,9 +634,7 @@ def t8n(
|
584 | 634 | request: pytest.FixtureRequest, evm_bin: Path | None, t8n_server_url: str | None
|
585 | 635 | ) -> Generator[TransitionTool, None, None]:
|
586 | 636 | """Return configured transition tool."""
|
587 |
| - kwargs = { |
588 |
| - "trace": request.config.getoption("evm_collect_traces"), |
589 |
| - } |
| 637 | + kwargs = {"trace": request.config.collect_traces} # type: ignore[attr-defined] |
590 | 638 | if t8n_server_url is not None:
|
591 | 639 | kwargs["server_url"] = t8n_server_url
|
592 | 640 | if evm_bin is None:
|
@@ -645,7 +693,7 @@ def evm_fixture_verification(
|
645 | 693 | try:
|
646 | 694 | evm_fixture_verification = FixtureConsumerTool.from_binary_path(
|
647 | 695 | binary_path=Path(verify_fixtures_bin),
|
648 |
| - trace=request.config.getoption("evm_collect_traces"), |
| 696 | + trace=request.config.collect_traces, # type: ignore[attr-defined] |
649 | 697 | )
|
650 | 698 | except Exception:
|
651 | 699 | if reused_evm_bin:
|
@@ -958,12 +1006,22 @@ def __init__(self, *args, **kwargs):
|
958 | 1006 | )
|
959 | 1007 | group: PreAllocGroup = request.config.pre_alloc_groups[pre_alloc_hash] # type: ignore[annotation-unchecked]
|
960 | 1008 | self.pre = group.pre
|
961 |
| - |
962 |
| - fixture = self.generate( |
963 |
| - t8n=t8n, |
964 |
| - fork=fork, |
965 |
| - fixture_format=fixture_format, |
966 |
| - ) |
| 1009 | + try: |
| 1010 | + fixture = self.generate( |
| 1011 | + t8n=t8n, |
| 1012 | + fork=fork, |
| 1013 | + fixture_format=fixture_format, |
| 1014 | + ) |
| 1015 | + finally: |
| 1016 | + if ( |
| 1017 | + request.config.op_mode == OpMode.OPTIMIZE_GAS |
| 1018 | + or request.config.op_mode == OpMode.OPTIMIZE_GAS_POST_PROCESSING |
| 1019 | + ): |
| 1020 | + gas_optimized_tests = request.config.gas_optimized_tests |
| 1021 | + assert gas_optimized_tests is not None |
| 1022 | + # Force adding something to the list, even if it's None, |
| 1023 | + # to keep track of failed tests in the output file. |
| 1024 | + gas_optimized_tests[request.node.nodeid] = self._gas_optimization |
967 | 1025 |
|
968 | 1026 | # Post-process for Engine X format (add pre_hash and state diff)
|
969 | 1027 | if (
|
@@ -1180,6 +1238,16 @@ def pytest_sessionfinish(session: pytest.Session, exitstatus: int):
|
1180 | 1238 | session.config.pre_alloc_groups.to_folder(pre_alloc_groups_folder)
|
1181 | 1239 | return
|
1182 | 1240 |
|
| 1241 | + if session.config.getoption("optimize_gas", False): # type: ignore[attr-defined] |
| 1242 | + output_file = Path(session.config.getoption("optimize_gas_output")) |
| 1243 | + lock_file_path = output_file.with_suffix(".lock") |
| 1244 | + assert hasattr(session.config, "gas_optimized_tests") |
| 1245 | + gas_optimized_tests: Dict[str, int] = session.config.gas_optimized_tests |
| 1246 | + with FileLock(lock_file_path): |
| 1247 | + if output_file.exists(): |
| 1248 | + gas_optimized_tests = json.loads(output_file.read_text()) | gas_optimized_tests |
| 1249 | + output_file.write_text(json.dumps(gas_optimized_tests, indent=2, sort_keys=True)) |
| 1250 | + |
1183 | 1251 | if xdist.is_xdist_worker(session):
|
1184 | 1252 | return
|
1185 | 1253 |
|
|
0 commit comments