Skip to content

Commit 23a354e

Browse files
authored
fix(consume): fix fixture downloads for non-release urls and numerical release specs (#1437)
* fix(consume): fix downloading non-release url tarballs * fix(consume): fix downloading via release spec with numerical version * docs: update changelog * fix typo
1 parent 62d2563 commit 23a354e

File tree

3 files changed

+55
-15
lines changed

3 files changed

+55
-15
lines changed

docs/CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ Test fixtures for use by clients are available for each release on the [Github r
1212

1313
#### `fill`
1414

15-
- 🐞 Fix `DeprecationWarning: Pickle, copy, and deepcopy support will be removed from itertools in Python 3.14.` by avoiding use `itertools` object in the spec `BaseTest` pydantic model ([#1414](https://github.com/ethereum/execution-spec-tests/pull/1414)).
1615
- ✨ The `static_filler` plug-in now has support for static state tests (from [GeneralStateTests](https://github.com/ethereum/tests/tree/develop/src/GeneralStateTestsFiller)) ([#1362](https://github.com/ethereum/execution-spec-tests/pull/1362)).
16+
- 🐞 Fix `DeprecationWarning: Pickle, copy, and deepcopy support will be removed from itertools in Python 3.14.` by avoiding use `itertools` object in the spec `BaseTest` pydantic model ([#1414](https://github.com/ethereum/execution-spec-tests/pull/1414)).
17+
18+
#### `consume`
19+
20+
- 🐞 Fix fixture tarball downloading with regular, non-Github release URLS and with numerical versions in regular release specs, e.g., `stable@v4.2.0` ([#1437](https://github.com/ethereum/execution-spec-tests/pull/1437)).
1721

1822
### 📋 Misc
1923

src/pytest_plugins/consume/consume.py

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from ethereum_test_forks import get_forks, get_relative_fork_markers, get_transition_forks
2121
from ethereum_test_tools.utility.versioning import get_current_commit_hash_or_tag
2222

23-
from .releases import ReleaseTag, get_release_page_url, get_release_url
23+
from .releases import ReleaseTag, get_release_page_url, get_release_url, is_release_url, is_url
2424

2525
CACHED_DOWNLOADS_DIRECTORY = (
2626
Path(platformdirs.user_cache_dir("ethereum-execution-spec-tests")) / "cached_downloads"
@@ -50,10 +50,16 @@ def __init__(self, url: str, base_directory: Path): # noqa: D107
5050
self.url = url
5151
self.base_directory = base_directory
5252
self.parsed_url = urlparse(url)
53-
self.org_repo = self.extract_github_repo()
54-
self.version = Path(self.parsed_url.path).parts[-2]
5553
self.archive_name = self.strip_archive_extension(Path(self.parsed_url.path).name)
56-
self.extract_to = base_directory / self.org_repo / self.version / self.archive_name
54+
55+
@property
56+
def extract_to(self) -> Path:
57+
"""Path to the directory where the archive will be extracted."""
58+
if is_release_url(self.url):
59+
version = Path(self.parsed_url.path).parts[-2]
60+
self.org_repo = self.extract_github_repo()
61+
return self.base_directory / self.org_repo / version / self.archive_name
62+
return self.base_directory / "other" / self.archive_name
5763

5864
def download_and_extract(self) -> Tuple[bool, Path]:
5965
"""Download the URL and extract it locally if it hasn't already been downloaded."""
@@ -91,7 +97,7 @@ def detect_extracted_directory(self) -> Path:
9197
"""
9298
Detect a single top-level dir within the extracted archive, otherwise return extract_to.
9399
""" # noqa: D200
94-
extracted_dirs = [d for d in self.extract_to.iterdir() if d.is_dir()]
100+
extracted_dirs = [d for d in self.extract_to.iterdir() if d.is_dir() and d.name != ".meta"]
95101
return extracted_dirs[0] if len(extracted_dirs) == 1 else self.extract_to
96102

97103

@@ -112,15 +118,17 @@ def from_input(cls, input_source: str) -> "FixturesSource":
112118
"""Determine the fixture source type and return an instance."""
113119
if input_source == "stdin":
114120
return cls(input_option=input_source, path=Path(), is_local=False, is_stdin=True)
121+
if is_release_url(input_source):
122+
return cls.from_release_url(input_source)
115123
if is_url(input_source):
116124
return cls.from_url(input_source)
117125
if ReleaseTag.is_release_string(input_source):
118126
return cls.from_release_spec(input_source)
119127
return cls.validate_local_path(Path(input_source))
120128

121129
@classmethod
122-
def from_url(cls, url: str) -> "FixturesSource":
123-
"""Create a fixture source from a direct URL."""
130+
def from_release_url(cls, url: str) -> "FixturesSource":
131+
"""Create a fixture source from a supported github repo release URL."""
124132
release_page = get_release_page_url(url)
125133
downloader = FixtureDownloader(url, CACHED_DOWNLOADS_DIRECTORY)
126134
was_cached, path = downloader.download_and_extract()
@@ -133,6 +141,20 @@ def from_url(cls, url: str) -> "FixturesSource":
133141
was_cached=was_cached,
134142
)
135143

144+
@classmethod
145+
def from_url(cls, url: str) -> "FixturesSource":
146+
"""Create a fixture source from a direct URL."""
147+
downloader = FixtureDownloader(url, CACHED_DOWNLOADS_DIRECTORY)
148+
was_cached, path = downloader.download_and_extract()
149+
return cls(
150+
input_option=url,
151+
path=path,
152+
url=url,
153+
release_page="",
154+
is_local=False,
155+
was_cached=was_cached,
156+
)
157+
136158
@classmethod
137159
def from_release_spec(cls, spec: str) -> "FixturesSource":
138160
"""Create a fixture source from a release spec (e.g., develop@latest)."""
@@ -159,12 +181,6 @@ def validate_local_path(path: Path) -> "FixturesSource":
159181
return FixturesSource(input_option=str(path), path=path)
160182

161183

162-
def is_url(string: str) -> bool:
163-
"""Check if a string is a remote URL."""
164-
result = urlparse(string)
165-
return all([result.scheme, result.netloc])
166-
167-
168184
class SimLimitBehavior:
169185
"""Represents options derived from the `--sim.limit` argument."""
170186

src/pytest_plugins/consume/releases.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from datetime import datetime
88
from pathlib import Path
99
from typing import List
10+
from urllib.parse import urlparse
1011

1112
import platformdirs
1213
import requests
@@ -72,7 +73,11 @@ def __eq__(self, value) -> bool:
7273
"""
7374
assert isinstance(value, str), f"Expected a string, but got: {value}"
7475
if self.version is not None:
75-
return value == f"{self.tag_name}@{self.version}"
76+
# normal release, e.g., stable@v4.0.0
77+
normal_release_match = value == self.version
78+
# pre release, e.g., pectra-devnet-6@v1.0.0
79+
pre_release_match = value == f"{self.tag_name}@{self.version}"
80+
return normal_release_match or pre_release_match
7681
return value.startswith(self.tag_name)
7782

7883
@property
@@ -140,6 +145,21 @@ def is_docker_or_ci() -> bool:
140145
return "GITHUB_ACTIONS" in os.environ or Path("/.dockerenv").exists()
141146

142147

148+
def is_url(string: str) -> bool:
149+
"""Check if a string is a remote URL."""
150+
result = urlparse(string)
151+
return all([result.scheme, result.netloc])
152+
153+
154+
def is_release_url(input_str: str) -> bool:
155+
"""Check if the release string is a URL."""
156+
if not is_url(input_str):
157+
return False
158+
repo_pattern = "|".join(re.escape(repo) for repo in SUPPORTED_REPOS)
159+
regex_pattern = rf"https://github\.com/({repo_pattern})/releases/download/"
160+
return re.match(regex_pattern, input_str) is not None
161+
162+
143163
def parse_release_information(release_information: List) -> List[ReleaseInformation]:
144164
"""Parse the release information from the Github API."""
145165
return Releases.model_validate(release_information).root # type: ignore

0 commit comments

Comments
 (0)