Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions py/envoy.base.utils/envoy/base/utils/abstract/project/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@
CHANGELOG_PATH_GLOB = "changelogs/*.*.*.yaml"
CHANGELOG_PATH_FMT = "changelogs/{version}.yaml"
CHANGELOG_CURRENT_PATH = "changelogs/current.yaml"
CHANGELOG_CURRENT_DIR_PATH = "changelogs/current"
CHANGELOG_ENTRY_GLOB = "*/*.rst"
CHANGELOG_SECTIONS_PATH = "changelogs/sections.yaml"
ENTRY_SEPARATOR = "__"
CHANGELOG_SUMMARY_PATH = "changelogs/summary.md"
CHANGELOG_URL_TPL = (
"https://raw.githubusercontent.com/envoyproxy/envoy/"
Expand Down Expand Up @@ -148,6 +151,32 @@ def get_data(cls, path) -> typing.ChangelogDict:
in data.items()
if v})

@classmethod
def get_data_from_entries(
cls,
yaml_path: pathlib.Path,
entry_dir: pathlib.Path) -> "typing.ChangelogDict":
try:
yaml_data = utils.from_yaml(yaml_path, typing.ChangelogSourceDict)
except (_yaml.reader.ReaderError, utils.TypeCastingError) as e:
raise exceptions.ChangelogParseError(
f"Failed to parse: {yaml_path}\n{e}")
date = yaml_data["date"]
sections: dict[str, list[typing.ChangeDict]] = {}
for path in sorted(entry_dir.glob(CHANGELOG_ENTRY_GLOB)):
section = path.parent.name
if path.stem.count(ENTRY_SEPARATOR) != 1:
raise exceptions.ChangelogParseError(
f"Invalid entry filename "
f"(expected exactly one '{ENTRY_SEPARATOR}'): {path}")
area, _slug = path.stem.split(ENTRY_SEPARATOR, 1)
change = typing.Change(path.read_text())
entry: typing.ChangeDict = dict(area=area, change=change)
sections.setdefault(section, []).append(entry)
return cast(
typing.ChangelogDict,
dict(date=date, **sections))

def __init__(
self,
project,
Expand Down
102 changes: 102 additions & 0 deletions py/envoy.base.utils/tests/test_abstract_project_changelogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,108 @@ def test_abstract_changelog_get_data(iters, patches, raises):
== [(m_typing.ChangelogDict, expected), {}])


def test_abstract_changelog_get_data_from_entries_happy_path(tmp_path):
yaml_path = tmp_path / "current.yaml"
yaml_path.write_text("date: Pending\n")
entry_dir = tmp_path / "entries"
(entry_dir / "bug_fixes").mkdir(parents=True)
(entry_dir / "new_features").mkdir(parents=True)
bug1 = entry_dir / "bug_fixes" / "oauth2__foo_fix.rst"
bug1.write_text("Fixed oauth2.\n")
bug2 = entry_dir / "bug_fixes" / "jwt__bar_fix.rst"
bug2.write_text("Fixed jwt.\n")
feat1 = entry_dir / "new_features" / "grpc__cool.rst"
feat1.write_text("New feature.\n")

result = abstract.AChangelog.get_data_from_entries(yaml_path, entry_dir)

assert result["date"] == "Pending"
assert set(result.keys()) == {"date", "bug_fixes", "new_features"}
assert len(result["bug_fixes"]) == 2
assert len(result["new_features"]) == 1
assert result["bug_fixes"][0]["area"] == "jwt"
assert result["bug_fixes"][1]["area"] == "oauth2"
assert result["new_features"][0]["area"] == "grpc"


def test_abstract_changelog_get_data_from_entries_arbitrary_section(tmp_path):
yaml_path = tmp_path / "current.yaml"
yaml_path.write_text("date: Pending\n")
entry_dir = tmp_path / "entries"
(entry_dir / "weird_section").mkdir(parents=True)
(entry_dir / "weird_section" / "foo__bar.rst").write_text("content\n")

result = abstract.AChangelog.get_data_from_entries(yaml_path, entry_dir)

assert "weird_section" in result
assert result["weird_section"][0]["area"] == "foo"


def test_abstract_changelog_get_data_from_entries_missing_separator(tmp_path):
yaml_path = tmp_path / "current.yaml"
yaml_path.write_text("date: Pending\n")
entry_dir = tmp_path / "entries"
(entry_dir / "bug_fixes").mkdir(parents=True)
(entry_dir / "bug_fixes" / "no_separator.rst").write_text("content\n")

with pytest.raises(exceptions.ChangelogParseError) as exc_info:
abstract.AChangelog.get_data_from_entries(yaml_path, entry_dir)

assert "no_separator.rst" in str(exc_info.value)


def test_abstract_changelog_get_data_from_entries_multiple_separators(
tmp_path):
yaml_path = tmp_path / "current.yaml"
yaml_path.write_text("date: Pending\n")
entry_dir = tmp_path / "entries"
(entry_dir / "bug_fixes").mkdir(parents=True)
(entry_dir / "bug_fixes" / "a__b__c.rst").write_text("content\n")

with pytest.raises(exceptions.ChangelogParseError) as exc_info:
abstract.AChangelog.get_data_from_entries(yaml_path, entry_dir)

assert "a__b__c.rst" in str(exc_info.value)


def test_abstract_changelog_get_data_from_entries_missing_yaml(tmp_path):
yaml_path = tmp_path / "nonexistent.yaml"
entry_dir = tmp_path / "entries"
entry_dir.mkdir()

with pytest.raises(FileNotFoundError):
abstract.AChangelog.get_data_from_entries(yaml_path, entry_dir)


def test_abstract_changelog_get_data_from_entries_empty_dir(tmp_path):
yaml_path = tmp_path / "current.yaml"
yaml_path.write_text("date: Pending\n")
entry_dir = tmp_path / "entries"
entry_dir.mkdir()

result = abstract.AChangelog.get_data_from_entries(yaml_path, entry_dir)

assert result["date"] == "Pending"
assert list(result.keys()) == ["date"]


def test_abstract_changelog_get_data_from_entries_stable_ordering(tmp_path):
yaml_path = tmp_path / "current.yaml"
yaml_path.write_text("date: Pending\n")
entry_dir = tmp_path / "entries"
(entry_dir / "bug_fixes").mkdir(parents=True)
(entry_dir / "bug_fixes" / "z__last.rst").write_text("Last\n")
(entry_dir / "bug_fixes" / "a__first.rst").write_text("First\n")
(entry_dir / "bug_fixes" / "m__middle.rst").write_text("Middle\n")

result1 = abstract.AChangelog.get_data_from_entries(yaml_path, entry_dir)
result2 = abstract.AChangelog.get_data_from_entries(yaml_path, entry_dir)

assert result1 == result2
areas = [e["area"] for e in result1["bug_fixes"]]
assert areas == ["a", "m", "z"]


def test_abstract_changelog_constructor():

with pytest.raises(TypeError):
Expand Down