relenv 0.21.1__py3-none-any.whl → 0.22.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- relenv/__init__.py +14 -2
- relenv/__main__.py +12 -6
- relenv/_resources/xz/config.h +148 -0
- relenv/_resources/xz/readme.md +4 -0
- relenv/build/__init__.py +28 -30
- relenv/build/common/__init__.py +50 -0
- relenv/build/common/_sysconfigdata_template.py +72 -0
- relenv/build/common/builder.py +907 -0
- relenv/build/common/builders.py +163 -0
- relenv/build/common/download.py +324 -0
- relenv/build/common/install.py +609 -0
- relenv/build/common/ui.py +432 -0
- relenv/build/darwin.py +128 -14
- relenv/build/linux.py +296 -78
- relenv/build/windows.py +259 -44
- relenv/buildenv.py +48 -17
- relenv/check.py +10 -5
- relenv/common.py +499 -163
- relenv/create.py +147 -7
- relenv/fetch.py +16 -4
- relenv/manifest.py +15 -7
- relenv/python-versions.json +329 -0
- relenv/pyversions.py +817 -30
- relenv/relocate.py +101 -55
- relenv/runtime.py +452 -253
- relenv/toolchain.py +9 -3
- {relenv-0.21.1.dist-info → relenv-0.22.0.dist-info}/METADATA +1 -1
- relenv-0.22.0.dist-info/RECORD +48 -0
- tests/__init__.py +2 -0
- tests/_pytest_typing.py +45 -0
- tests/conftest.py +42 -36
- tests/test_build.py +426 -9
- tests/test_common.py +311 -48
- tests/test_create.py +149 -6
- tests/test_downloads.py +19 -15
- tests/test_fips_photon.py +6 -3
- tests/test_module_imports.py +44 -0
- tests/test_pyversions_runtime.py +177 -0
- tests/test_relocate.py +45 -39
- tests/test_relocate_module.py +257 -0
- tests/test_runtime.py +1802 -6
- tests/test_verify_build.py +500 -34
- relenv/build/common.py +0 -1609
- relenv-0.21.1.dist-info/RECORD +0 -35
- {relenv-0.21.1.dist-info → relenv-0.22.0.dist-info}/WHEEL +0 -0
- {relenv-0.21.1.dist-info → relenv-0.22.0.dist-info}/entry_points.txt +0 -0
- {relenv-0.21.1.dist-info → relenv-0.22.0.dist-info}/licenses/LICENSE.md +0 -0
- {relenv-0.21.1.dist-info → relenv-0.22.0.dist-info}/licenses/NOTICE +0 -0
- {relenv-0.21.1.dist-info → relenv-0.22.0.dist-info}/top_level.txt +0 -0
tests/test_create.py
CHANGED
|
@@ -12,12 +12,12 @@ from relenv.common import arches
|
|
|
12
12
|
from relenv.create import CreateException, chdir, create
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def test_chdir(tmp_path):
|
|
15
|
+
def test_chdir(tmp_path: pathlib.Path) -> None:
|
|
16
16
|
with chdir(str(tmp_path)):
|
|
17
17
|
assert pathlib.Path(os.getcwd()) == tmp_path
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def test_create(tmp_path):
|
|
20
|
+
def test_create(tmp_path: pathlib.Path) -> None:
|
|
21
21
|
to_be_archived = tmp_path / "to_be_archived"
|
|
22
22
|
to_be_archived.mkdir()
|
|
23
23
|
test_file = to_be_archived / "testfile"
|
|
@@ -34,21 +34,164 @@ def test_create(tmp_path):
|
|
|
34
34
|
assert (to_dir / to_be_archived.name / test_file.name) in to_dir.glob("**/*")
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def test_create_tar_doesnt_exist(tmp_path):
|
|
37
|
+
def test_create_tar_doesnt_exist(tmp_path: pathlib.Path) -> None:
|
|
38
38
|
tar_file = tmp_path / "fake_archive"
|
|
39
39
|
with patch("relenv.create.archived_build", return_value=tar_file):
|
|
40
40
|
with pytest.raises(CreateException):
|
|
41
41
|
create("foo", dest=tmp_path)
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
def test_create_directory_exists(tmp_path):
|
|
44
|
+
def test_create_directory_exists(tmp_path: pathlib.Path) -> None:
|
|
45
45
|
(tmp_path / "foo").mkdir()
|
|
46
46
|
with pytest.raises(CreateException):
|
|
47
47
|
create("foo", dest=tmp_path)
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
def test_create_arches_directory_exists(tmp_path):
|
|
51
|
-
mocked_arches = {key: [] for key in arches.keys()}
|
|
50
|
+
def test_create_arches_directory_exists(tmp_path: pathlib.Path) -> None:
|
|
51
|
+
mocked_arches: dict[str, list[str]] = {key: [] for key in arches.keys()}
|
|
52
52
|
with patch("relenv.create.arches", mocked_arches):
|
|
53
53
|
with pytest.raises(CreateException):
|
|
54
54
|
create("foo", dest=tmp_path)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_create_with_minor_version(tmp_path: pathlib.Path) -> None:
|
|
58
|
+
"""Test that minor version (e.g., '3.12') resolves to latest micro version."""
|
|
59
|
+
import argparse
|
|
60
|
+
import sys
|
|
61
|
+
|
|
62
|
+
from relenv.create import main
|
|
63
|
+
from relenv.pyversions import Version
|
|
64
|
+
|
|
65
|
+
# Mock python_versions to return some test versions
|
|
66
|
+
all_versions = {
|
|
67
|
+
Version("3.11.5"): "aaa111",
|
|
68
|
+
Version("3.12.5"): "abc123",
|
|
69
|
+
Version("3.12.6"): "def456",
|
|
70
|
+
Version("3.12.7"): "ghi789",
|
|
71
|
+
Version("3.13.1"): "zzz999",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
def mock_python_versions(minor: str | None = None) -> dict[Version, str]:
|
|
75
|
+
"""Mock that filters versions by minor version like the real function."""
|
|
76
|
+
if minor is None:
|
|
77
|
+
return all_versions
|
|
78
|
+
# Filter versions matching the minor version
|
|
79
|
+
mv = Version(minor)
|
|
80
|
+
return {
|
|
81
|
+
v: h
|
|
82
|
+
for v, h in all_versions.items()
|
|
83
|
+
if v.major == mv.major and v.minor == mv.minor
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Create a fake archive
|
|
87
|
+
to_be_archived = tmp_path / "to_be_archived"
|
|
88
|
+
to_be_archived.mkdir()
|
|
89
|
+
test_file = to_be_archived / "testfile"
|
|
90
|
+
test_file.touch()
|
|
91
|
+
tar_file = tmp_path / "fake_archive"
|
|
92
|
+
with tarfile.open(str(tar_file), "w:xz") as tar:
|
|
93
|
+
tar.add(str(to_be_archived), to_be_archived.name)
|
|
94
|
+
|
|
95
|
+
# Use appropriate architecture for the platform
|
|
96
|
+
test_arch = "amd64" if sys.platform == "win32" else "x86_64"
|
|
97
|
+
args = argparse.Namespace(name="test_env", arch=test_arch, python="3.12")
|
|
98
|
+
|
|
99
|
+
with chdir(str(tmp_path)):
|
|
100
|
+
with patch("relenv.create.python_versions", side_effect=mock_python_versions):
|
|
101
|
+
with patch("relenv.create.archived_build", return_value=tar_file):
|
|
102
|
+
with patch("relenv.create.build_arch", return_value=test_arch):
|
|
103
|
+
main(args)
|
|
104
|
+
|
|
105
|
+
to_dir = tmp_path / "test_env"
|
|
106
|
+
assert to_dir.exists()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_create_with_full_version(tmp_path: pathlib.Path) -> None:
|
|
110
|
+
"""Test that full version (e.g., '3.12.7') still works."""
|
|
111
|
+
import argparse
|
|
112
|
+
import sys
|
|
113
|
+
|
|
114
|
+
from relenv.create import main
|
|
115
|
+
from relenv.pyversions import Version
|
|
116
|
+
|
|
117
|
+
# Mock python_versions to return some test versions
|
|
118
|
+
all_versions = {
|
|
119
|
+
Version("3.11.5"): "aaa111",
|
|
120
|
+
Version("3.12.5"): "abc123",
|
|
121
|
+
Version("3.12.6"): "def456",
|
|
122
|
+
Version("3.12.7"): "ghi789",
|
|
123
|
+
Version("3.13.1"): "zzz999",
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
def mock_python_versions(minor: str | None = None) -> dict[Version, str]:
|
|
127
|
+
"""Mock that filters versions by minor version like the real function."""
|
|
128
|
+
if minor is None:
|
|
129
|
+
return all_versions
|
|
130
|
+
# Filter versions matching the minor version
|
|
131
|
+
mv = Version(minor)
|
|
132
|
+
return {
|
|
133
|
+
v: h
|
|
134
|
+
for v, h in all_versions.items()
|
|
135
|
+
if v.major == mv.major and v.minor == mv.minor
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# Create a fake archive
|
|
139
|
+
to_be_archived = tmp_path / "to_be_archived"
|
|
140
|
+
to_be_archived.mkdir()
|
|
141
|
+
test_file = to_be_archived / "testfile"
|
|
142
|
+
test_file.touch()
|
|
143
|
+
tar_file = tmp_path / "fake_archive"
|
|
144
|
+
with tarfile.open(str(tar_file), "w:xz") as tar:
|
|
145
|
+
tar.add(str(to_be_archived), to_be_archived.name)
|
|
146
|
+
|
|
147
|
+
# Use appropriate architecture for the platform
|
|
148
|
+
test_arch = "amd64" if sys.platform == "win32" else "x86_64"
|
|
149
|
+
args = argparse.Namespace(name="test_env", arch=test_arch, python="3.12.7")
|
|
150
|
+
|
|
151
|
+
with chdir(str(tmp_path)):
|
|
152
|
+
with patch("relenv.create.python_versions", side_effect=mock_python_versions):
|
|
153
|
+
with patch("relenv.create.archived_build", return_value=tar_file):
|
|
154
|
+
with patch("relenv.create.build_arch", return_value=test_arch):
|
|
155
|
+
main(args)
|
|
156
|
+
|
|
157
|
+
to_dir = tmp_path / "test_env"
|
|
158
|
+
assert to_dir.exists()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_create_with_unknown_minor_version(tmp_path: pathlib.Path) -> None:
|
|
162
|
+
"""Test that unknown minor version produces an error."""
|
|
163
|
+
import argparse
|
|
164
|
+
import sys
|
|
165
|
+
|
|
166
|
+
from relenv.create import main
|
|
167
|
+
from relenv.pyversions import Version
|
|
168
|
+
|
|
169
|
+
# Mock python_versions to return empty dict for unknown version
|
|
170
|
+
all_versions = {
|
|
171
|
+
Version("3.11.5"): "aaa111",
|
|
172
|
+
Version("3.12.5"): "abc123",
|
|
173
|
+
Version("3.12.6"): "def456",
|
|
174
|
+
Version("3.12.7"): "ghi789",
|
|
175
|
+
Version("3.13.1"): "zzz999",
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
# Use appropriate architecture for the platform
|
|
179
|
+
test_arch = "amd64" if sys.platform == "win32" else "x86_64"
|
|
180
|
+
args = argparse.Namespace(name="test_env", arch=test_arch, python="3.99")
|
|
181
|
+
|
|
182
|
+
def mock_python_versions(minor: str | None = None) -> dict[Version, str]:
|
|
183
|
+
"""Mock that filters versions by minor version like the real function."""
|
|
184
|
+
if minor is None:
|
|
185
|
+
return all_versions
|
|
186
|
+
# Filter versions matching the minor version
|
|
187
|
+
mv = Version(minor)
|
|
188
|
+
return {
|
|
189
|
+
v: h
|
|
190
|
+
for v, h in all_versions.items()
|
|
191
|
+
if v.major == mv.major and v.minor == mv.minor
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
with patch("relenv.create.python_versions", side_effect=mock_python_versions):
|
|
195
|
+
with patch("relenv.create.build_arch", return_value=test_arch):
|
|
196
|
+
with pytest.raises(SystemExit):
|
|
197
|
+
main(args)
|
tests/test_downloads.py
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
# Copyright 2022-2025 Broadcom.
|
|
2
|
-
# SPDX-License-Identifier: Apache-2
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import pathlib
|
|
4
4
|
import subprocess
|
|
5
5
|
import sys
|
|
6
|
+
|
|
7
|
+
# mypy: ignore-errors
|
|
6
8
|
from unittest.mock import patch
|
|
7
9
|
|
|
8
|
-
from relenv.build.common import Download
|
|
10
|
+
from relenv.build.common.download import Download
|
|
9
11
|
from relenv.common import RelenvException
|
|
10
12
|
|
|
11
13
|
|
|
12
|
-
def test_download_url():
|
|
14
|
+
def test_download_url() -> None:
|
|
13
15
|
download = Download(
|
|
14
16
|
"test", "https://test.com/{version}/test-{version}.tar.xz", version="1.0.0"
|
|
15
17
|
)
|
|
16
18
|
assert download.url == "https://test.com/1.0.0/test-1.0.0.tar.xz"
|
|
17
19
|
|
|
18
20
|
|
|
19
|
-
def test_download_url_change_version():
|
|
21
|
+
def test_download_url_change_version() -> None:
|
|
20
22
|
download = Download(
|
|
21
23
|
"test", "https://test.com/{version}/test-{version}.tar.xz", version="1.0.0"
|
|
22
24
|
)
|
|
@@ -24,7 +26,7 @@ def test_download_url_change_version():
|
|
|
24
26
|
assert download.url == "https://test.com/1.2.2/test-1.2.2.tar.xz"
|
|
25
27
|
|
|
26
28
|
|
|
27
|
-
def test_download_filepath():
|
|
29
|
+
def test_download_filepath() -> None:
|
|
28
30
|
download = Download(
|
|
29
31
|
"test",
|
|
30
32
|
"https://test.com/{version}/test-{version}.tar.xz",
|
|
@@ -38,7 +40,7 @@ def test_download_filepath():
|
|
|
38
40
|
assert str(download.filepath) == "/tmp/test-1.0.0.tar.xz"
|
|
39
41
|
|
|
40
42
|
|
|
41
|
-
def test_download_filepath_change_destination():
|
|
43
|
+
def test_download_filepath_change_destination() -> None:
|
|
42
44
|
download = Download(
|
|
43
45
|
"test",
|
|
44
46
|
"https://test.com/{version}/test-{version}.tar.xz",
|
|
@@ -53,7 +55,7 @@ def test_download_filepath_change_destination():
|
|
|
53
55
|
assert str(download.filepath) == "/tmp/foo/test-1.0.0.tar.xz"
|
|
54
56
|
|
|
55
57
|
|
|
56
|
-
def test_download_exists(tmp_path):
|
|
58
|
+
def test_download_exists(tmp_path: pathlib.Path) -> None:
|
|
57
59
|
download = Download(
|
|
58
60
|
"test",
|
|
59
61
|
"https://test.com/{version}/test-{version}.tar.xz",
|
|
@@ -65,25 +67,25 @@ def test_download_exists(tmp_path):
|
|
|
65
67
|
assert download.exists() is True
|
|
66
68
|
|
|
67
69
|
|
|
68
|
-
def test_validate_md5sum(tmp_path):
|
|
70
|
+
def test_validate_md5sum(tmp_path: pathlib.Path) -> None:
|
|
69
71
|
fake_md5 = "fakemd5"
|
|
70
|
-
with patch("relenv.build.common.verify_checksum") as run_mock:
|
|
72
|
+
with patch("relenv.build.common.download.verify_checksum") as run_mock:
|
|
71
73
|
assert Download.validate_checksum(str(tmp_path), fake_md5) is True
|
|
72
74
|
run_mock.assert_called_with(str(tmp_path), fake_md5)
|
|
73
75
|
|
|
74
76
|
|
|
75
|
-
def test_validate_md5sum_failed(tmp_path):
|
|
77
|
+
def test_validate_md5sum_failed(tmp_path: pathlib.Path) -> None:
|
|
76
78
|
fake_md5 = "fakemd5"
|
|
77
79
|
with patch(
|
|
78
|
-
"relenv.build.common.verify_checksum", side_effect=RelenvException
|
|
80
|
+
"relenv.build.common.download.verify_checksum", side_effect=RelenvException
|
|
79
81
|
) as run_mock:
|
|
80
82
|
assert Download.validate_checksum(str(tmp_path), fake_md5) is False
|
|
81
83
|
run_mock.assert_called_with(str(tmp_path), fake_md5)
|
|
82
84
|
|
|
83
85
|
|
|
84
|
-
def test_validate_signature(tmp_path):
|
|
86
|
+
def test_validate_signature(tmp_path: pathlib.Path) -> None:
|
|
85
87
|
sig = "fakesig"
|
|
86
|
-
with patch("relenv.build.common.runcmd") as run_mock:
|
|
88
|
+
with patch("relenv.build.common.download.runcmd") as run_mock:
|
|
87
89
|
assert Download.validate_signature(str(tmp_path), sig) is True
|
|
88
90
|
run_mock.assert_called_with(
|
|
89
91
|
["gpg", "--verify", sig, str(tmp_path)],
|
|
@@ -92,9 +94,11 @@ def test_validate_signature(tmp_path):
|
|
|
92
94
|
)
|
|
93
95
|
|
|
94
96
|
|
|
95
|
-
def test_validate_signature_failed(tmp_path):
|
|
97
|
+
def test_validate_signature_failed(tmp_path: pathlib.Path) -> None:
|
|
96
98
|
sig = "fakesig"
|
|
97
|
-
with patch(
|
|
99
|
+
with patch(
|
|
100
|
+
"relenv.build.common.download.runcmd", side_effect=RelenvException
|
|
101
|
+
) as run_mock:
|
|
98
102
|
assert Download.validate_signature(str(tmp_path), sig) is False
|
|
99
103
|
run_mock.assert_called_with(
|
|
100
104
|
["gpg", "--verify", sig, str(tmp_path)],
|
tests/test_fips_photon.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2022-2025 Broadcom.
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
#
|
|
4
4
|
import os
|
|
5
5
|
import pathlib
|
|
6
|
+
|
|
7
|
+
# mypy: ignore-errors
|
|
6
8
|
import subprocess
|
|
9
|
+
from typing import Any
|
|
7
10
|
|
|
8
11
|
import pytest
|
|
9
12
|
|
|
@@ -12,7 +15,7 @@ from tests.test_verify_build import _install_ppbt
|
|
|
12
15
|
from .conftest import get_build_version
|
|
13
16
|
|
|
14
17
|
|
|
15
|
-
def check_test_environment():
|
|
18
|
+
def check_test_environment() -> bool:
|
|
16
19
|
path = pathlib.Path("/etc/os-release")
|
|
17
20
|
if path.exists():
|
|
18
21
|
release = path.read_text()
|
|
@@ -28,7 +31,7 @@ pytestmark = [
|
|
|
28
31
|
]
|
|
29
32
|
|
|
30
33
|
|
|
31
|
-
def test_fips_mode(pyexec, build):
|
|
34
|
+
def test_fips_mode(pyexec: str, build: Any) -> None:
|
|
32
35
|
_install_ppbt(pyexec)
|
|
33
36
|
env = os.environ.copy()
|
|
34
37
|
proc = subprocess.run(
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Copyright 2022-2025 Broadcom.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import importlib
|
|
7
|
+
import pathlib
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Callable, List, Sequence, TypeVar, cast
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from _pytest.mark.structures import ParameterSet
|
|
14
|
+
|
|
15
|
+
F = TypeVar("F", bound=Callable[..., object])
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def typed_parametrize(*args: Any, **kwargs: Any) -> Callable[[F], F]:
|
|
19
|
+
"""Type-aware wrapper around pytest.mark.parametrize."""
|
|
20
|
+
decorator = pytest.mark.parametrize(*args, **kwargs)
|
|
21
|
+
return cast(Callable[[F], F], decorator)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _top_level_modules() -> Sequence["ParameterSet"]:
|
|
25
|
+
relenv_dir = pathlib.Path(__file__).resolve().parents[1] / "relenv"
|
|
26
|
+
params: List["ParameterSet"] = []
|
|
27
|
+
for path in sorted(relenv_dir.iterdir()):
|
|
28
|
+
if not path.is_file() or path.suffix != ".py":
|
|
29
|
+
continue
|
|
30
|
+
stem = path.stem
|
|
31
|
+
if stem == "__init__":
|
|
32
|
+
module_name = "relenv"
|
|
33
|
+
else:
|
|
34
|
+
module_name = f"relenv.{stem}"
|
|
35
|
+
params.append(pytest.param(module_name, id=module_name))
|
|
36
|
+
return params
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@typed_parametrize("module_name", _top_level_modules())
|
|
40
|
+
def test_import_top_level_module(module_name: str) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Ensure each top-level module in the relenv package can be imported.
|
|
43
|
+
"""
|
|
44
|
+
importlib.import_module(module_name)
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Copyright 2022-2025 Broadcom.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import hashlib
|
|
7
|
+
import pathlib
|
|
8
|
+
import subprocess
|
|
9
|
+
from typing import Any, Dict, Sequence
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
from relenv import pyversions
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_python_versions_returns_versions() -> None:
|
|
17
|
+
versions: Dict[pyversions.Version, str] = pyversions.python_versions()
|
|
18
|
+
assert versions, "python_versions() should return known versions"
|
|
19
|
+
first_version = next(iter(versions))
|
|
20
|
+
assert isinstance(first_version, pyversions.Version)
|
|
21
|
+
assert isinstance(versions[first_version], str)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_python_versions_filters_minor() -> None:
|
|
25
|
+
versions = pyversions.python_versions("3.11")
|
|
26
|
+
assert versions
|
|
27
|
+
assert all(version.major == 3 and version.minor == 11 for version in versions)
|
|
28
|
+
sorted_versions = sorted(versions)
|
|
29
|
+
assert sorted_versions[-1] in versions
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_release_urls_handles_old_versions() -> None:
|
|
33
|
+
tarball, signature = pyversions._release_urls(pyversions.Version("3.1.3"))
|
|
34
|
+
assert tarball.endswith(".tar.xz")
|
|
35
|
+
assert signature is not None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_release_urls_no_signature_before_23() -> None:
|
|
39
|
+
tarball, signature = pyversions._release_urls(pyversions.Version("2.2.3"))
|
|
40
|
+
assert tarball.endswith(".tar.xz")
|
|
41
|
+
assert signature is None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_ref_version_and_path_helpers() -> None:
|
|
45
|
+
html = '<a href="download/Python-3.11.9.tgz">Python 3.11.9</a>'
|
|
46
|
+
version = pyversions._ref_version(html)
|
|
47
|
+
assert str(version) == "3.11.9"
|
|
48
|
+
assert pyversions._ref_path(html) == "download/Python-3.11.9.tgz"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_digest(tmp_path: pathlib.Path) -> None:
|
|
52
|
+
file = tmp_path / "data.bin"
|
|
53
|
+
file.write_bytes(b"abc")
|
|
54
|
+
assert pyversions.digest(file) == hashlib.sha1(b"abc").hexdigest()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_get_keyid_parses_second_line() -> None:
|
|
58
|
+
proc = subprocess.CompletedProcess(
|
|
59
|
+
["gpg"],
|
|
60
|
+
1,
|
|
61
|
+
stdout=b"",
|
|
62
|
+
stderr=b"gpg: error\n[GNUPG:] INV_SGNR 0 CB1234\n",
|
|
63
|
+
)
|
|
64
|
+
assert pyversions._get_keyid(proc) == "CB1234"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_verify_signature_success(
|
|
68
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
|
|
69
|
+
) -> None:
|
|
70
|
+
called: Dict[str, list[str]] = {}
|
|
71
|
+
|
|
72
|
+
def fake_run(
|
|
73
|
+
cmd: Sequence[str], **kwargs: Any
|
|
74
|
+
) -> subprocess.CompletedProcess[bytes]:
|
|
75
|
+
called.setdefault("cmd", []).extend(cmd)
|
|
76
|
+
return subprocess.CompletedProcess(cmd, 0, stdout=b"", stderr=b"")
|
|
77
|
+
|
|
78
|
+
monkeypatch.setattr(pyversions.subprocess, "run", fake_run)
|
|
79
|
+
assert pyversions.verify_signature("archive.tgz", "archive.tgz.asc") is True
|
|
80
|
+
assert called["cmd"][0] == "gpg"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_verify_signature_failure_with_missing_key(
|
|
84
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
85
|
+
) -> None:
|
|
86
|
+
responses: list[str] = []
|
|
87
|
+
|
|
88
|
+
def fake_run(
|
|
89
|
+
cmd: Sequence[str], **kwargs: Any
|
|
90
|
+
) -> subprocess.CompletedProcess[bytes]:
|
|
91
|
+
if len(responses) == 0:
|
|
92
|
+
responses.append("first")
|
|
93
|
+
stderr = b"gpg: error\n[GNUPG:] INV_SGNR 0 ABCDEF12\nNo public key\n"
|
|
94
|
+
return subprocess.CompletedProcess(cmd, 1, stdout=b"", stderr=stderr)
|
|
95
|
+
return subprocess.CompletedProcess(cmd, 0, stdout=b"", stderr=b"")
|
|
96
|
+
|
|
97
|
+
monkeypatch.setattr(pyversions.subprocess, "run", fake_run)
|
|
98
|
+
monkeypatch.setattr(pyversions, "_receive_key", lambda keyid, server: True)
|
|
99
|
+
assert pyversions.verify_signature("archive.tgz", "archive.tgz.asc") is True
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_sha256_digest(tmp_path: pathlib.Path) -> None:
|
|
103
|
+
"""Test SHA-256 digest computation."""
|
|
104
|
+
file = tmp_path / "data.bin"
|
|
105
|
+
file.write_bytes(b"test data")
|
|
106
|
+
assert pyversions.sha256_digest(file) == hashlib.sha256(b"test data").hexdigest()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_detect_openssl_versions(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
110
|
+
"""Test OpenSSL version detection from GitHub releases."""
|
|
111
|
+
mock_html = """
|
|
112
|
+
<html>
|
|
113
|
+
<a href="/openssl/openssl/releases/tag/openssl-3.5.4">openssl-3.5.4</a>
|
|
114
|
+
<a href="/openssl/openssl/releases/tag/openssl-3.5.3">openssl-3.5.3</a>
|
|
115
|
+
<a href="/openssl/openssl/releases/tag/openssl-3.4.0">openssl-3.4.0</a>
|
|
116
|
+
</html>
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
def fake_fetch(url: str) -> str:
|
|
120
|
+
return mock_html
|
|
121
|
+
|
|
122
|
+
monkeypatch.setattr(pyversions, "fetch_url_content", fake_fetch)
|
|
123
|
+
versions = pyversions.detect_openssl_versions()
|
|
124
|
+
assert isinstance(versions, list)
|
|
125
|
+
assert "3.5.4" in versions
|
|
126
|
+
assert "3.5.3" in versions
|
|
127
|
+
assert "3.4.0" in versions
|
|
128
|
+
# Verify sorting (latest first)
|
|
129
|
+
assert versions[0] == "3.5.4"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_detect_sqlite_versions(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
133
|
+
"""Test SQLite version detection from sqlite.org."""
|
|
134
|
+
mock_html = """
|
|
135
|
+
<html>
|
|
136
|
+
<a href="2024/sqlite-autoconf-3500400.tar.gz">sqlite-autoconf-3500400.tar.gz</a>
|
|
137
|
+
<a href="2024/sqlite-autoconf-3500300.tar.gz">sqlite-autoconf-3500300.tar.gz</a>
|
|
138
|
+
</html>
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
def fake_fetch(url: str) -> str:
|
|
142
|
+
return mock_html
|
|
143
|
+
|
|
144
|
+
monkeypatch.setattr(pyversions, "fetch_url_content", fake_fetch)
|
|
145
|
+
versions = pyversions.detect_sqlite_versions()
|
|
146
|
+
assert isinstance(versions, list)
|
|
147
|
+
# Should return list of tuples (version, sqliteversion)
|
|
148
|
+
assert len(versions) > 0
|
|
149
|
+
assert isinstance(versions[0], tuple)
|
|
150
|
+
assert len(versions[0]) == 2
|
|
151
|
+
# Check that conversion worked
|
|
152
|
+
version, sqlite_ver = versions[0]
|
|
153
|
+
assert version == "3.50.4.0"
|
|
154
|
+
assert sqlite_ver == "3500400"
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_detect_xz_versions(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
158
|
+
"""Test XZ version detection from tukaani.org."""
|
|
159
|
+
mock_html = """
|
|
160
|
+
<html>
|
|
161
|
+
<a href="xz-5.8.1.tar.gz">xz-5.8.1.tar.gz</a>
|
|
162
|
+
<a href="xz-5.8.0.tar.gz">xz-5.8.0.tar.gz</a>
|
|
163
|
+
<a href="xz-5.6.3.tar.gz">xz-5.6.3.tar.gz</a>
|
|
164
|
+
</html>
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
def fake_fetch(url: str) -> str:
|
|
168
|
+
return mock_html
|
|
169
|
+
|
|
170
|
+
monkeypatch.setattr(pyversions, "fetch_url_content", fake_fetch)
|
|
171
|
+
versions = pyversions.detect_xz_versions()
|
|
172
|
+
assert isinstance(versions, list)
|
|
173
|
+
assert "5.8.1" in versions
|
|
174
|
+
assert "5.8.0" in versions
|
|
175
|
+
assert "5.6.3" in versions
|
|
176
|
+
# Verify sorting (latest first)
|
|
177
|
+
assert versions[0] == "5.8.1"
|