Skip to content

Commit

Permalink
feat(plugins): Join --release into --input, implement hive inform…
Browse files Browse the repository at this point in the history
…ation
  • Loading branch information
marioevz committed Jan 3, 2025
1 parent a851c3d commit d874956
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 94 deletions.
81 changes: 33 additions & 48 deletions src/pytest_plugins/consume/consume.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@
from ethereum_test_fixtures.consume import TestCases
from ethereum_test_tools.utility.versioning import get_current_commit_hash_or_tag

from .releases import get_release_url
from .releases import ReleaseTag, get_release_url

CACHED_DOWNLOADS_DIRECTORY = (
Path(platformdirs.user_cache_dir("ethereum-execution-spec-tests")) / "cached_downloads"
)

JsonSource = Union[Path, Literal["stdin"]]
FixturesSource = Union[Path, Literal["stdin"]]


def default_input_directory() -> str:
Expand Down Expand Up @@ -76,28 +76,16 @@ def pytest_addoption(parser): # noqa: D103
consume_group.addoption(
"--input",
action="store",
dest="fixture_source",
dest="fixtures_source",
default=None,
help=(
"Specify the JSON test fixtures source. Can be a local directory, a URL pointing to a "
" fixtures.tar.gz archive, or one of the special keywords: 'stdin', "
"'latest-stable', 'latest-develop'. "
" fixtures.tar.gz archive, a release name and version in the form of `[email protected]` "
"(`stable` and `develop` are valid release names, and `latest` is a valid version), "
"or the special keyword 'stdin'. "
f"Defaults to the following local directory: '{default_input_directory()}'."
),
)
consume_group.addoption(
"--release",
action="store",
dest="fixture_release",
default=None,
help=(
"Specify the name and, optionally, the version of the release to use as JSON test"
" fixtures source."
"If a specific version is not provided (e.g. [email protected]), the latest release"
" will be used."
"Release names `stable` and `develop` are supported, as well as devnet-named releases."
),
)
consume_group.addoption(
"--fork",
action="store",
Expand Down Expand Up @@ -144,52 +132,49 @@ def pytest_configure(config): # noqa: D103
called before the pytest-html plugin's pytest_configure to ensure that
it uses the modified `htmlpath` option.
"""
input_source = config.getoption("fixture_source")
release_source = config.getoption("fixture_release")
cached_downloads_directory = Path(config.getoption("fixture_cache_folder"))
fixtures_source = config.getoption("fixtures_source")
config.fixture_source_flags = ["--input", fixtures_source]

if input_source is not None and input_source == "stdin":
config.fixture_source_flags = ["--input=stdin"]
if fixtures_source is None:
config.fixture_source_flags = []
fixtures_source = default_input_directory()
elif fixtures_source == "stdin":
config.test_cases = TestCases.from_stream(sys.stdin)
config.input_source = "stdin"
config.fixtures_real_source = "stdin"
config.fixtures_source = "stdin"
return
elif ReleaseTag.is_release_string(fixtures_source):
fixtures_source = get_release_url(fixtures_source)

if release_source is not None:
config.fixture_source_flags = ["--release", release_source]
input_source = get_release_url(release_source)
elif input_source is not None:
config.fixture_source_flags = ["--input", input_source]
else:
config.fixture_source_flags = []
input_source = default_input_directory()

if is_url(input_source):
config.fixtures_real_source = fixtures_source
if is_url(fixtures_source):
cached_downloads_directory = Path(config.getoption("fixture_cache_folder"))
cached_downloads_directory.mkdir(parents=True, exist_ok=True)
input_source = download_and_extract(input_source, cached_downloads_directory)
fixtures_source = download_and_extract(fixtures_source, cached_downloads_directory)

input_source = Path(input_source)
config.input_source = input_source
if not input_source.exists():
pytest.exit(f"Specified fixture directory '{input_source}' does not exist.")
if not any(input_source.glob("**/*.json")):
fixtures_source = Path(fixtures_source)
config.fixtures_source = fixtures_source
if not fixtures_source.exists():
pytest.exit(f"Specified fixture directory '{fixtures_source}' does not exist.")
if not any(fixtures_source.glob("**/*.json")):
pytest.exit(
f"Specified fixture directory '{input_source}' does not contain any JSON files."
f"Specified fixture directory '{fixtures_source}' does not contain any JSON files."
)

index_file = input_source / ".meta" / "index.json"
index_file = fixtures_source / ".meta" / "index.json"
index_file.parent.mkdir(parents=True, exist_ok=True)
if not index_file.exists():
rich.print(f"Generating index file [bold cyan]{index_file}[/]...")
generate_fixtures_index(
input_source, quiet_mode=False, force_flag=False, disable_infer_format=False
fixtures_source, quiet_mode=False, force_flag=False, disable_infer_format=False
)
config.test_cases = TestCases.from_index_file(index_file)

if config.option.collectonly:
return
if not config.getoption("disable_html") and config.getoption("htmlpath") is None:
# generate an html report by default, unless explicitly disabled
config.option.htmlpath = os.path.join(input_source, default_html_report_file_path())
config.option.htmlpath = os.path.join(fixtures_source, default_html_report_file_path())


def pytest_html_report_title(report):
Expand All @@ -199,8 +184,8 @@ def pytest_html_report_title(report):

def pytest_report_header(config): # noqa: D103
consume_version = f"consume commit: {get_current_commit_hash_or_tag()}"
input_source = f"fixtures: {config.input_source}"
return [consume_version, input_source]
fixtures_real_source = f"fixtures: {config.fixtures_real_source}"
return [consume_version, fixtures_real_source]


@pytest.fixture(scope="session")
Expand All @@ -210,8 +195,8 @@ def fixture_source_flags(request) -> List[str]:


@pytest.fixture(scope="session")
def fixture_source(request) -> JsonSource: # noqa: D103
return request.config.input_source
def fixtures_source(request) -> FixturesSource: # noqa: D103
return request.config.fixtures_source


def pytest_generate_tests(metafunc):
Expand Down
8 changes: 5 additions & 3 deletions src/pytest_plugins/consume/direct/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
from ethereum_test_fixtures.consume import TestCaseIndexFile, TestCaseStream
from ethereum_test_fixtures.file import Fixtures

from ..consume import FixturesSource


def pytest_addoption(parser): # noqa: D103
consume_group = parser.getgroup(
Expand Down Expand Up @@ -96,13 +98,13 @@ def test_dump_dir(


@pytest.fixture
def fixture_path(test_case: TestCaseIndexFile | TestCaseStream, fixture_source):
def fixture_path(test_case: TestCaseIndexFile | TestCaseStream, fixtures_source: FixturesSource):
"""
Path to the current JSON fixture file.
If the fixture source is stdin, the fixture is written to a temporary json file.
"""
if fixture_source == "stdin":
if fixtures_source == "stdin":
assert isinstance(test_case, TestCaseStream)
temp_dir = tempfile.TemporaryDirectory()
fixture_path = Path(temp_dir.name) / f"{test_case.id.replace('/','_')}.json"
Expand All @@ -113,7 +115,7 @@ def fixture_path(test_case: TestCaseIndexFile | TestCaseStream, fixture_source):
temp_dir.cleanup()
else:
assert isinstance(test_case, TestCaseIndexFile)
yield fixture_source / test_case.json_path
yield fixtures_source / test_case.json_path


@pytest.fixture(scope="function")
Expand Down
25 changes: 15 additions & 10 deletions src/pytest_plugins/consume/hive_simulators/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from ethereum_test_fixtures.consume import TestCaseIndexFile, TestCaseStream
from ethereum_test_rpc import EthRPC
from pytest_plugins.consume.hive_simulators.ruleset import ruleset # TODO: generate dynamically
from pytest_plugins.pytest_hive.hive_info import ClientInfo

from .timing import TimingData

Expand All @@ -38,37 +39,41 @@ def eth_rpc(client: Client) -> EthRPC:
return EthRPC(f"http://{client.ip}:8545")


@pytest.fixture(scope="session")
def hive_client_config_file() -> str | None:
@pytest.fixture(scope="function")
def hive_client_config_file_parameter(
client_type: ClientType, client_file: List[ClientInfo]
) -> List[str]:
"""Return the hive client config file that is currently being used to configure tests."""
# TODO: We need a hive endpoint from which we can fetch this information
return "configs/prague.yaml"
for client in client_file:
if client_type.name.startswith(client.client):
return ["--client-file", f"<('{client.model_dump_json()}')"]
return []


@pytest.fixture(scope="function")
def hive_consume_command(
test_suite_name: str,
client_type: ClientType,
test_case: TestCaseIndexFile | TestCaseStream,
hive_client_config_file: str | None,
hive_client_config_file_parameter: List[str],
) -> List[str]:
"""Command to run the test within hive."""
command = ["./hive", "--sim", f"ethereum/{test_suite_name}"]
if hive_client_config_file:
command += ["--client-file", hive_client_config_file]
if hive_client_config_file_parameter:
command += hive_client_config_file_parameter
command += ["--client", client_type.name, "--sim.limit", f'"{test_case.id}"']
return command


@pytest.fixture(scope="function")
def hive_dev_command(
client_type: ClientType,
hive_client_config_file: str | None,
hive_client_config_file_parameter: List[str],
) -> List[str]:
"""Return the command used to instantiate hive alongside the `consume` command."""
hive_dev = ["./hive", "--dev"]
if hive_client_config_file:
hive_dev += ["--client-file", hive_client_config_file]
if hive_client_config_file_parameter:
hive_dev += hive_client_config_file_parameter
hive_dev += ["--client", client_type.name]
return hive_dev

Expand Down
11 changes: 6 additions & 5 deletions src/pytest_plugins/consume/hive_simulators/engine/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"""

import io
from pathlib import Path
from typing import Mapping

import pytest
Expand All @@ -15,7 +14,7 @@
from ethereum_test_fixtures.consume import TestCaseIndexFile, TestCaseStream
from ethereum_test_fixtures.file import BlockchainEngineFixtures
from ethereum_test_rpc import EngineRPC
from pytest_plugins.consume.consume import JsonSource
from pytest_plugins.consume.consume import FixturesSource

TestCase = TestCaseIndexFile | TestCaseStream

Expand All @@ -39,15 +38,17 @@ def test_suite_description() -> str:


@pytest.fixture(scope="function")
def blockchain_fixture(fixture_source: JsonSource, test_case: TestCase) -> BlockchainEngineFixture:
def blockchain_fixture(
fixtures_source: FixturesSource, test_case: TestCase
) -> BlockchainEngineFixture:
"""
Create the blockchain engine fixture pydantic model for the current test case.
The fixture is either already available within the test case (if consume
is taking input on stdin) or loaded from the fixture json file if taking
input from disk (fixture directory with index file).
"""
if fixture_source == "stdin":
if fixtures_source == "stdin":
assert isinstance(test_case, TestCaseStream), "Expected a stream test case"
assert isinstance(
test_case.fixture, BlockchainEngineFixture
Expand All @@ -57,7 +58,7 @@ def blockchain_fixture(fixture_source: JsonSource, test_case: TestCase) -> Block
assert isinstance(test_case, TestCaseIndexFile), "Expected an index file test case"
# TODO: Optimize, json files will be loaded multiple times. This pytest fixture
# is executed per test case, and a fixture json will contain multiple test cases.
fixtures = BlockchainEngineFixtures.from_file(Path(fixture_source) / test_case.json_path)
fixtures = BlockchainEngineFixtures.from_file(fixtures_source / test_case.json_path)
fixture = fixtures[test_case.id]
return fixture

Expand Down
9 changes: 4 additions & 5 deletions src/pytest_plugins/consume/hive_simulators/rlp/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Pytest fixtures and classes for the `consume rlp` hive simulator."""

import io
from pathlib import Path
from typing import List, Mapping, cast

import pytest
Expand All @@ -10,7 +9,7 @@
from ethereum_test_fixtures import BlockchainFixture
from ethereum_test_fixtures.consume import TestCaseIndexFile, TestCaseStream
from ethereum_test_fixtures.file import BlockchainFixtures
from pytest_plugins.consume.consume import JsonSource
from pytest_plugins.consume.consume import FixturesSource

TestCase = TestCaseIndexFile | TestCaseStream

Expand All @@ -28,15 +27,15 @@ def test_suite_description() -> str:


@pytest.fixture(scope="function")
def blockchain_fixture(fixture_source: JsonSource, test_case: TestCase) -> BlockchainFixture:
def blockchain_fixture(fixtures_source: FixturesSource, test_case: TestCase) -> BlockchainFixture:
"""
Create the blockchain fixture pydantic model for the current test case.
The fixture is either already available within the test case (if consume
is taking input on stdin) or loaded from the fixture json file if taking
input from disk (fixture directory with index file).
"""
if fixture_source == "stdin":
if fixtures_source == "stdin":
assert isinstance(test_case, TestCaseStream), "Expected a stream test case"
assert isinstance(
test_case.fixture, BlockchainFixture
Expand All @@ -46,7 +45,7 @@ def blockchain_fixture(fixture_source: JsonSource, test_case: TestCase) -> Block
assert isinstance(test_case, TestCaseIndexFile), "Expected an index file test case"
# TODO: Optimize, json files will be loaded multiple times. This pytest fixture
# is executed per test case, and a fixture json will contain multiple test cases.
fixtures = BlockchainFixtures.from_file(Path(fixture_source) / test_case.json_path)
fixtures = BlockchainFixtures.from_file(fixtures_source / test_case.json_path)
fixture = fixtures[test_case.id]
return fixture

Expand Down
Loading

0 comments on commit d874956

Please sign in to comment.