Skip to content

Commit 5b9b7de

Browse files
pasmudCommandCodeBotpre-commit-ci[bot]gaborbernat
authored
Reset the embed update log when it cannot be read (#3376)
Co-authored-by: CommandCodeBot <noreply@commandcode.ai> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Bernat Gabor <gaborjbernat@gmail.com>
1 parent 9fd48c8 commit 5b9b7de

3 files changed

Lines changed: 51 additions & 4 deletions

File tree

‎docs/changelog/3376.bugfix.rst‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fall back to the bundled seed wheel and remove the embed update log when the log in the app data folder holds JSON of
2+
the wrong shape, instead of failing to create the environment - by :user:`pasmud`.

‎src/virtualenv/seed/wheels/periodic_update.py‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import os
99
import ssl
1010
import sys
11+
from contextlib import suppress
1112
from datetime import datetime, timedelta, timezone
1213
from itertools import groupby
1314
from pathlib import Path
@@ -28,7 +29,7 @@
2829
if TYPE_CHECKING:
2930
from collections.abc import Generator
3031

31-
from virtualenv.app_data.base import AppData
32+
from virtualenv.app_data.base import AppData, ContentStore
3233

3334
LOGGER = logging.getLogger(__name__)
3435
GRACE_PERIOD_CI = timedelta(hours=1) # prevent version switch in the middle of a CI run
@@ -192,8 +193,16 @@ def from_dict(cls, dictionary: dict[str, object] | None) -> UpdateLog:
192193

193194
@classmethod
194195
def from_app_data(cls, app_data: AppData, distribution: str, for_py_version: str) -> UpdateLog:
195-
raw_json = app_data.embed_update_log(distribution, for_py_version).read()
196-
return cls.from_dict(raw_json)
196+
store: Final[ContentStore] = app_data.embed_update_log(distribution, for_py_version)
197+
try:
198+
return cls.from_dict(store.read())
199+
except (AttributeError, KeyError, TypeError, ValueError):
200+
# another virtualenv version or an interrupted run can leave valid JSON of the wrong shape in the shared
201+
# app data; drop it as read() drops invalid JSON, since an empty log falls back to the bundled wheel
202+
LOGGER.warning("removing malformed embed update log of %s", distribution)
203+
with suppress(OSError): # another process may remove or rewrite the file at the same time
204+
store.remove()
205+
return cls(None, None, [], None)
197206

198207
def to_dict(self) -> dict[str, object]:
199208
return {

‎tests/unit/seed/wheels/test_periodic_update.py‎

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import hashlib
44
import json
5+
import logging
56
import os
67
import subprocess
78
import sys
@@ -12,7 +13,7 @@
1213
from itertools import zip_longest
1314
from pathlib import Path
1415
from textwrap import dedent
15-
from typing import TYPE_CHECKING
16+
from typing import TYPE_CHECKING, Final
1617
from urllib.error import URLError
1718

1819
import pytest
@@ -42,6 +43,8 @@
4243

4344
from pytest_mock import MockerFixture
4445

46+
from virtualenv.app_data.via_disk_folder import EmbedDistributionUpdateStoreDisk
47+
4548

4649
@pytest.fixture(autouse=True)
4750
def _clear_pypi_info_cache() -> Generator[None, None, None]:
@@ -251,6 +254,39 @@ def test_periodic_update_skip(u_log, mocker, for_py_version, session_app_data, t
251254
assert result is None
252255

253256

257+
@pytest.mark.parametrize(
258+
"stored",
259+
[
260+
pytest.param([1, 2, 3], id="not-a-mapping"),
261+
pytest.param("oops", id="string"),
262+
pytest.param({"versions": "abc"}, id="versions-not-a-list"),
263+
pytest.param({"versions": ["abc"]}, id="version-not-a-mapping"),
264+
pytest.param({"completed": 5, "started": None, "versions": []}, id="completed-not-a-datetime"),
265+
],
266+
)
267+
def test_periodic_update_drops_malformed_log(
268+
stored: list[int] | str | dict[str, str | int | list[str] | None],
269+
tmp_path: Path,
270+
for_py_version: str,
271+
caplog: pytest.LogCaptureFixture,
272+
) -> None:
273+
caplog.set_level(logging.WARNING)
274+
app_data: Final[AppDataDiskFolder] = AppDataDiskFolder(str(tmp_path))
275+
log: Final[EmbedDistributionUpdateStoreDisk] = app_data.embed_update_log("setuptools", for_py_version)
276+
log.write(stored)
277+
wheel: Final[Wheel | None] = get_embed_wheel("setuptools", for_py_version)
278+
279+
result: Final[Wheel | None] = periodic_update(
280+
"setuptools", None, for_py_version, wheel, [], app_data, False, os.environ
281+
)
282+
283+
assert (result, log.exists(), caplog.messages) == (
284+
wheel,
285+
False,
286+
["removing malformed embed update log of setuptools"],
287+
)
288+
289+
254290
_UPDATE_YES = {
255291
"never_started": UpdateLog(started=None, completed=None, versions=[], periodic=False),
256292
"started_1_hour": UpdateLog(

0 commit comments

Comments
 (0)