-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_rollup_changelog.py
More file actions
204 lines (143 loc) · 7.13 KB
/
Copy pathtest_rollup_changelog.py
File metadata and controls
204 lines (143 loc) · 7.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
"""Tests for `.github/scripts/rollup_changelog.py` (#150)."""
from __future__ import annotations
import importlib.util
from pathlib import Path
from typing import TYPE_CHECKING
import pytest
if TYPE_CHECKING:
from types import ModuleType
def _load_script() -> ModuleType:
script_path = (
Path(__file__).parent.parent / ".github" / "scripts" / "rollup_changelog.py"
)
spec = importlib.util.spec_from_file_location("rollup_changelog", script_path)
assert spec is not None
assert spec.loader is not None
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
rc = _load_script()
# ---------------------------------------------------------------------------
# _bump_patch / _strip_v — semver helpers.
# ---------------------------------------------------------------------------
class TestBumpPatch:
def test_basic_increment(self) -> None:
assert rc._bump_patch("1.9.10") == "1.9.11"
def test_zero_patch(self) -> None:
assert rc._bump_patch("0.1.0") == "0.1.1"
def test_strips_v_prefix(self) -> None:
assert rc._bump_patch(rc._strip_v("v2.5.7")) == "2.5.8"
def test_rejects_non_semver(self) -> None:
with pytest.raises(ValueError, match="unsupported semver"):
rc._bump_patch("1.9")
# ---------------------------------------------------------------------------
# rollup_changelog_text — pure-string transform.
# ---------------------------------------------------------------------------
_BASE_CHANGELOG = """# Changelog
## [Unreleased]
### Tests
- Some test entry. ([#999](https://github.com/x/y/issues/999))
## [1.9.4] - 2026-04-29
### Features
- Old feature.
[Unreleased]: https://github.com/constk/harness-python-react/compare/v1.9.4...HEAD
[1.9.4]: https://github.com/constk/harness-python-react/compare/v1.4.13...v1.9.4
"""
def test_rollup_inserts_version_heading() -> None:
out = rc.rollup_changelog_text(_BASE_CHANGELOG, "v1.9.10", "v1.9.4", "2026-05-15")
assert "## [1.9.10] - 2026-05-15" in out
assert "## [Unreleased]\n\n## [1.9.10] - 2026-05-15" in out
def test_rollup_heading_followed_by_blank_line() -> None:
"""#166 — the new heading must have a blank line before the next section.
Pre-#166 the rollup produced `## [v1.9.10] - …\n### Tests`, missing the
blank line that every existing release section has. Cosmetic, but the
kind of inconsistency that accumulates as the file grows.
"""
out = rc.rollup_changelog_text(_BASE_CHANGELOG, "v1.9.10", "v1.9.4", "2026-05-15")
assert "## [1.9.10] - 2026-05-15\n\n### Tests" in out
assert "## [1.9.10] - 2026-05-15\n### Tests" not in out
def test_rollup_updates_unreleased_compare_link() -> None:
out = rc.rollup_changelog_text(_BASE_CHANGELOG, "v1.9.10", "v1.9.4", "2026-05-15")
assert (
"[Unreleased]: https://github.com/constk/harness-python-react/"
"compare/v1.9.10...HEAD" in out
)
assert "compare/v1.9.4...HEAD" not in out
def test_rollup_inserts_new_version_footer_link() -> None:
out = rc.rollup_changelog_text(_BASE_CHANGELOG, "v1.9.10", "v1.9.4", "2026-05-15")
assert (
"[1.9.10]: https://github.com/constk/harness-python-react/"
"compare/v1.9.4...v1.9.10" in out
)
def test_rollup_preserves_existing_version_links() -> None:
out = rc.rollup_changelog_text(_BASE_CHANGELOG, "v1.9.10", "v1.9.4", "2026-05-15")
assert "[1.9.4]: https://github.com/constk/harness-python-react/" in out
def test_rollup_idempotent_on_existing_heading() -> None:
"""Re-running on already-rolled-up text doesn't duplicate the heading."""
once = rc.rollup_changelog_text(_BASE_CHANGELOG, "v1.9.10", "v1.9.4", "2026-05-15")
twice = rc.rollup_changelog_text(once, "v1.9.10", "v1.9.4", "2026-05-15")
assert once.count("## [1.9.10] - 2026-05-15") == 1
assert twice.count("## [1.9.10] - 2026-05-15") == 1
def test_rollup_idempotent_on_existing_footer_link() -> None:
once = rc.rollup_changelog_text(_BASE_CHANGELOG, "v1.9.10", "v1.9.4", "2026-05-15")
assert once.count("[1.9.10]:") == 1
twice = rc.rollup_changelog_text(once, "v1.9.10", "v1.9.4", "2026-05-15")
assert twice.count("[1.9.10]:") == 1
def test_rollup_first_release_uses_releases_tag_url() -> None:
"""No prior tag → footer link points at /releases/tag/<tag> not /compare/."""
initial = """# Changelog
## [Unreleased]
[Unreleased]: https://github.com/constk/harness-python-react/compare/HEAD...HEAD
"""
out = rc.rollup_changelog_text(initial, "v0.1.0", "", "2026-01-01")
assert (
"[0.1.0]: https://github.com/constk/harness-python-react/"
"releases/tag/v0.1.0" in out
)
# Compare-style link not used when prior_tag is empty.
assert "compare/...v0.1.0" not in out
def test_rollup_handles_two_digit_minor_and_patch() -> None:
"""v1.10.42 has two-digit numbers — common after enough patch cycles."""
out = rc.rollup_changelog_text(_BASE_CHANGELOG, "v1.10.42", "v1.9.4", "2026-12-31")
assert "## [1.10.42] - 2026-12-31" in out
assert "compare/v1.9.4...v1.10.42" in out
# ---------------------------------------------------------------------------
# bump_pyproject_text / bump_uv_lock_text — line-level edits.
# ---------------------------------------------------------------------------
def test_bump_pyproject_basic() -> None:
text = '[project]\nname = "harness-python-react"\nversion = "1.9.10"\n'
out = rc.bump_pyproject_text(text, "1.9.10", "1.9.11")
assert 'version = "1.9.11"' in out
assert 'version = "1.9.10"' not in out
def test_bump_pyproject_only_replaces_project_version_line() -> None:
"""A `version = "..."` line elsewhere (e.g. a dependency) isn't touched."""
text = (
'[project]\nversion = "1.9.10"\n\n'
'[tool.dummy]\nversion = "0.0.1" # unrelated\n'
)
out = rc.bump_pyproject_text(text, "1.9.10", "1.9.11")
assert out.count('version = "1.9.11"') == 1
assert 'version = "0.0.1"' in out
def test_bump_pyproject_raises_when_current_missing() -> None:
with pytest.raises(ValueError, match="not found"):
rc.bump_pyproject_text('version = "9.9.9"\n', "1.9.10", "1.9.11")
def test_bump_uv_lock_basic() -> None:
text = (
'[[package]]\nname = "harness-python-react"\nversion = "1.9.10"\n'
'source = { editable = "." }\n'
)
out = rc.bump_uv_lock_text(text, "1.9.10", "1.9.11")
assert 'version = "1.9.11"' in out
def test_bump_uv_lock_does_not_touch_other_packages() -> None:
"""Only the self-version block's version line is replaced."""
text = (
'[[package]]\nname = "fastapi"\nversion = "1.9.10"\n\n'
'[[package]]\nname = "harness-python-react"\nversion = "1.9.10"\n'
)
out = rc.bump_uv_lock_text(text, "1.9.10", "1.9.11")
# FastAPI's coincidentally-same version stays put.
assert out.count('name = "fastapi"\nversion = "1.9.10"') == 1
assert out.count('name = "harness-python-react"\nversion = "1.9.11"') == 1
# main() integration tests live in `tests/test_rollup_changelog_main.py` —
# split out so neither file approaches the 300-line cap (same pattern as
# `test_check_commit_types_subject.py`'s sibling-file split for #128).