Skip to content

Commit f4361ad

Browse files
authored
More test coverage for release.py and sbom.py (#187)
1 parent 8dd1514 commit f4361ad

File tree

7 files changed

+190
-15
lines changed

7 files changed

+190
-15
lines changed

.coveragerc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ exclude_also =
66
# Don't complain if non-runnable code isn't run:
77
if __name__ == .__main__.
88
def main
9+
def get_arg_parser

release.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -363,13 +363,16 @@ def get_arg_parser() -> optparse.OptionParser:
363363

364364

365365
def constant_replace(
366-
fn: str, updated_constants: str, comment_start: str = "/*", comment_end: str = "*/"
366+
filename: str,
367+
updated_constants: str,
368+
comment_start: str = "/*",
369+
comment_end: str = "*/",
367370
) -> None:
368371
"""Inserts in between --start constant-- and --end constant-- in a file"""
369372
start_tag = comment_start + "--start constants--" + comment_end
370373
end_tag = comment_start + "--end constants--" + comment_end
371-
with open(fn, encoding="ascii") as infile, open(
372-
fn + ".new", "w", encoding="ascii"
374+
with open(filename, encoding="ascii") as infile, open(
375+
filename + ".new", "w", encoding="ascii"
373376
) as outfile:
374377
found_constants = False
375378
waiting_for_end = False
@@ -387,12 +390,14 @@ def constant_replace(
387390
else:
388391
outfile.write(line)
389392
if not found_constants:
390-
error(f"Constant section delimiters not found: {fn}")
391-
os.rename(fn + ".new", fn)
393+
error(f"Constant section delimiters not found: {filename}")
394+
os.rename(filename + ".new", filename)
392395

393396

394-
def tweak_patchlevel(tag: Tag, done: bool = False) -> None:
395-
print("Updating Include/patchlevel.h...", end=" ")
397+
def tweak_patchlevel(
398+
tag: Tag, filename: str = "Include/patchlevel.h", done: bool = False
399+
) -> None:
400+
print(f"Updating {filename}...", end=" ")
396401
template = '''
397402
#define PY_MAJOR_VERSION\t{tag.major}
398403
#define PY_MINOR_VERSION\t{tag.minor}
@@ -414,7 +419,7 @@ def tweak_patchlevel(tag: Tag, done: bool = False) -> None:
414419
)
415420
if tag.as_tuple() >= (3, 7, 0, "a", 3):
416421
new_constants = new_constants.expandtabs()
417-
constant_replace("Include/patchlevel.h", new_constants)
422+
constant_replace(filename, new_constants)
418423
print("done")
419424

420425

@@ -440,22 +445,22 @@ def bump(tag: Tag) -> None:
440445
".github/ISSUE_TEMPLATE/crash.yml",
441446
]
442447
print("\nManual editing time...")
443-
for fn in other_files:
444-
if os.path.exists(fn):
445-
print(f"Edit {fn}")
446-
manual_edit(fn)
448+
for filename in other_files:
449+
if os.path.exists(filename):
450+
print(f"Edit {filename}")
451+
manual_edit(filename)
447452
else:
448-
print(f"Skipping {fn}")
453+
print(f"Skipping {filename}")
449454

450455
print("Bumped revision")
451456
if extra_work:
452457
print("configure.ac has changed; re-run autotools!")
453458
print("Please commit and use --tag")
454459

455460

456-
def manual_edit(fn: str) -> None:
461+
def manual_edit(filename: str) -> None:
457462
editor = os.environ["EDITOR"].split()
458-
run_cmd([*editor, fn])
463+
run_cmd([*editor, filename])
459464

460465

461466
@contextmanager

tests/patchlevel.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
2+
/* Python version identification scheme.
3+
4+
When the major or minor version changes, the VERSION variable in
5+
configure.ac must also be changed.
6+
7+
There is also (independent) API version information in modsupport.h.
8+
*/
9+
10+
/* Values for PY_RELEASE_LEVEL */
11+
#define PY_RELEASE_LEVEL_ALPHA 0xA
12+
#define PY_RELEASE_LEVEL_BETA 0xB
13+
#define PY_RELEASE_LEVEL_GAMMA 0xC /* For release candidates */
14+
#define PY_RELEASE_LEVEL_FINAL 0xF /* Serial should be 0 here */
15+
/* Higher for patch releases */
16+
17+
/* Version parsed out into numeric values */
18+
/*--start constants--*/
19+
#define PY_MAJOR_VERSION 3
20+
#define PY_MINOR_VERSION 14
21+
#define PY_MICRO_VERSION 0
22+
#define PY_RELEASE_LEVEL PY_RELEASE_LEVEL_ALPHA
23+
#define PY_RELEASE_SERIAL 1
24+
25+
/* Version as a string */
26+
#define PY_VERSION "3.14.0a1+"
27+
/*--end constants--*/
28+
29+
/* Version as a single 4-byte hex number, e.g. 0x010502B2 == 1.5.2b2.
30+
Use this for numeric comparisons, e.g. #if PY_VERSION_HEX >= ... */
31+
#define PY_VERSION_HEX ((PY_MAJOR_VERSION << 24) | \
32+
(PY_MINOR_VERSION << 16) | \
33+
(PY_MICRO_VERSION << 8) | \
34+
(PY_RELEASE_LEVEL << 4) | \
35+
(PY_RELEASE_SERIAL << 0))
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"SPDXID": "SPDXRef-DOCUMENT",
3+
"name": "CPython SBOM",
4+
"spdxVersion": "SPDX-2.3",
5+
"dataLicense": "CC0-1.0",
6+
"documentNamespace": "https://www.python.org/ftp/python/3.13.0/fake-artifact.txt.spdx.json",
7+
"creationInfo": {
8+
"created": "2024-10-15T20:11:52Z",
9+
"creators": [],
10+
"licenseListVersion": "3.22"
11+
},
12+
"files": [],
13+
"packages": [],
14+
"relationships": [
15+
{
16+
"relatedSpdxElement": "SPDXRef-FILE-Modules-expat-COPYING",
17+
"relationshipType": "CONTAINS",
18+
"spdxElementId": "SPDXRef-PACKAGE-expat"
19+
}
20+
]
21+
}

tests/sbom/sbom-with-pip.json

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"SPDXID": "SPDXRef-DOCUMENT",
3+
"name": "CPython SBOM",
4+
"spdxVersion": "SPDX-2.3",
5+
"dataLicense": "CC0-1.0",
6+
"documentNamespace": "https://www.python.org/ftp/python/3.13.0/fake-artifact.txt.spdx.json",
7+
"creationInfo": {
8+
"created": "2024-10-15T20:11:52Z",
9+
"creators": [],
10+
"licenseListVersion": "3.22"
11+
},
12+
"files": [],
13+
"packages": [
14+
{
15+
"SPDXID": "SPDXRef-PACKAGE-pip",
16+
"name": "pip",
17+
"versionInfo": "24.0",
18+
"licenseConcluded": "MIT",
19+
"originator": "Organization: Python Software Foundation",
20+
"supplier": "Organization: Python Software Foundation",
21+
"packageFileName": "pip-24.0-py3-none-any.whl",
22+
"externalRefs": [
23+
{
24+
"referenceCategory": "SECURITY",
25+
"referenceLocator": "cpe:2.3:a:pypa:pip:24.0:*:*:*:*:*:*:*",
26+
"referenceType": "cpe23Type"
27+
}
28+
],
29+
"primaryPackagePurpose": "RUNTIME",
30+
"downloadLocation": "https://files.pythonhosted.org/packages/.../pip-24.0-py3-none-any.whl",
31+
"checksums": [
32+
{
33+
"algorithm": "SHA256",
34+
"checksumValue": "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc"
35+
}
36+
]
37+
}
38+
],
39+
"relationships": [
40+
{
41+
"relatedSpdxElement": "SPDXRef-FILE-Modules-expat-COPYING",
42+
"relationshipType": "CONTAINS",
43+
"spdxElementId": "SPDXRef-PACKAGE-expat"
44+
},
45+
{
46+
"relatedSpdxElement": "SPDXRef-PACKAGE-urllib3",
47+
"relationshipType": "DEPENDS_ON",
48+
"spdxElementId": "SPDXRef-PACKAGE-pip"
49+
},
50+
{
51+
"relatedSpdxElement": "SPDXRef-PACKAGE-pip",
52+
"relationshipType": "DEPENDS_ON",
53+
"spdxElementId": "SPDXRef-PACKAGE-cpython"
54+
}
55+
]
56+
}

tests/test_release.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from pathlib import Path
2+
from typing import cast
3+
14
import pytest
25
from pytest_mock import MockerFixture
36

@@ -26,3 +29,42 @@ def test_manual_edit(
2629

2730
# Assert
2831
mock_run_cmd.assert_called_once_with(expected)
32+
33+
34+
def test_task(mocker: MockerFixture) -> None:
35+
# Arrange
36+
db = {"mock": "mock"}
37+
my_task = mocker.Mock()
38+
task = release.Task(my_task, "My task")
39+
40+
# Act
41+
task(cast(release.ReleaseShelf, db))
42+
43+
# Assert
44+
assert task.description == "My task"
45+
assert task.function == my_task
46+
my_task.assert_called_once_with(cast(release.ReleaseShelf, db))
47+
48+
49+
def test_tweak_patchlevel(tmp_path: Path) -> None:
50+
# Arrange
51+
tag = release.Tag("3.14.0b2")
52+
53+
original_patchlevel_file = Path(__file__).parent / "patchlevel.h"
54+
patchlevel_file = tmp_path / "patchlevel.h"
55+
patchlevel_file.write_text(original_patchlevel_file.read_text())
56+
57+
# Act
58+
release.tweak_patchlevel(tag, filename=str(patchlevel_file))
59+
60+
# Assert
61+
new_contents = patchlevel_file.read_text()
62+
for expected in (
63+
"#define PY_MAJOR_VERSION 3",
64+
"#define PY_MINOR_VERSION 14",
65+
"#define PY_MICRO_VERSION 0",
66+
"#define PY_RELEASE_LEVEL PY_RELEASE_LEVEL_BETA",
67+
"#define PY_RELEASE_SERIAL 2",
68+
'#define PY_VERSION "3.14.0b2"',
69+
):
70+
assert expected in new_contents

tests/test_sbom.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import random
55
import re
66
import unittest.mock
7+
from pathlib import Path
78

89
import pytest
910

@@ -132,6 +133,20 @@ def test_fetch_project_metadata_from_pypi(mocker):
132133
)
133134

134135

136+
def test_remove_pip_from_sbom() -> None:
137+
# Arrange
138+
with (Path(__file__).parent / "sbom" / "sbom-with-pip.json").open() as f:
139+
sbom_data = json.load(f)
140+
with (Path(__file__).parent / "sbom" / "sbom-with-pip-removed.json").open() as f:
141+
expected = json.load(f)
142+
143+
# Act
144+
sbom.remove_pip_from_sbom(sbom_data)
145+
146+
# Assert
147+
assert sbom_data == expected
148+
149+
135150
def test_create_cpython_sbom():
136151
sbom_data = {"packages": []}
137152

0 commit comments

Comments
 (0)