mirror of
https://github.com/hanzoai/datastore.git
synced 2026-09-26 13:55:07 +00:00
The CI unit-test job runs `pytest ci/tests/` (see `ci/jobs/ci_tests_job.py`), so unit tests placed under `tests/ci/` were never executed in CI. Move the three recently added unit-test files there: - `test_create_release.py` - `test_cherry_pick_branches.py` - `test_pr_version_info.py` Each module under test still lives in `tests/ci/` and imports its siblings by bare name, so the import puts that directory on `sys.path` only for the duration of the import and removes it afterwards, mirroring the existing `ci/tests/test_download_release_packages.py`. Follow-up to https://github.com/ClickHouse/ClickHouse/pull/109489
266 lines
10 KiB
Python
266 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Unit tests for the pure helpers in `pr_version_info.py`. Run as part of the
|
|
`ci/tests/` suite with `pytest ci/tests/test_pr_version_info.py` from the repo
|
|
root.
|
|
|
|
`pr_version_info` keeps its GitHub / CIDB imports inside `main()`, so importing
|
|
the helpers here pulls in no heavyweight dependencies.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import unittest
|
|
|
|
# `pr_version_info` lives under `tests/ci` and imports sibling modules by bare
|
|
# name, so put that directory on `sys.path` only while importing it and remove
|
|
# it again afterwards to avoid leaking it into the rest of the pytest session.
|
|
_CI_DIR = os.path.abspath(
|
|
os.path.join(os.path.dirname(__file__), "..", "..", "tests", "ci")
|
|
)
|
|
sys.path.insert(0, _CI_DIR)
|
|
try:
|
|
# pylint: disable=import-error
|
|
from pr_version_info import (
|
|
SECTION_END,
|
|
SECTION_START,
|
|
MergedPR,
|
|
has_backport_label,
|
|
has_ignore_label,
|
|
original_pr_number_from_backport_ref,
|
|
partition_merged_prs,
|
|
release_from_backport_ref,
|
|
render_issue_section,
|
|
render_section,
|
|
upsert_section,
|
|
version_key,
|
|
)
|
|
finally:
|
|
sys.path.remove(_CI_DIR)
|
|
|
|
|
|
class TestRenderSection(unittest.TestCase):
|
|
def test_merged_only(self):
|
|
self.assertEqual(
|
|
render_section("26.6.1.1", []),
|
|
"### Version info\n- Merged into: `26.6.1.1`",
|
|
)
|
|
|
|
def test_merged_and_backported(self):
|
|
self.assertEqual(
|
|
render_section("26.6.1.1", ["25.12.1.100", "25.8.1.200"]),
|
|
"### Version info\n"
|
|
"- Merged into: `26.6.1.1`\n"
|
|
"- Backported to: `25.12.1.100`, `25.8.1.200`",
|
|
)
|
|
|
|
def test_default_branch_version_names_the_first_release(self):
|
|
# An original PR's version comes from the default branch's commit
|
|
# counter (master's `26.2.1.1291` is not a `release/26.2` build); it
|
|
# means the change landed before the `release/26.2` branch was cut,
|
|
# so the release series that first includes it is spelled out.
|
|
self.assertEqual(
|
|
render_section("26.2.1.1291", [], from_default_branch=True),
|
|
"### Version info\n"
|
|
"- Merged into: `26.2.1.1291` (included in `26.2` and later)",
|
|
)
|
|
|
|
def test_default_branch_version_with_backports(self):
|
|
self.assertEqual(
|
|
render_section("26.2.1.1291", ["25.8.1.500"], from_default_branch=True),
|
|
"### Version info\n"
|
|
"- Merged into: `26.2.1.1291` (included in `26.2` and later)\n"
|
|
"- Backported to: `25.8.1.500`",
|
|
)
|
|
|
|
def test_unparseable_version_gets_no_annotation(self):
|
|
self.assertEqual(
|
|
render_section("not-a-version", [], from_default_branch=True),
|
|
"### Version info\n- Merged into: `not-a-version`",
|
|
)
|
|
|
|
def test_backported_only(self):
|
|
self.assertEqual(
|
|
render_section(None, ["25.8.1.200"]),
|
|
"### Version info\n- Backported to: `25.8.1.200`",
|
|
)
|
|
|
|
|
|
class TestUpsertSection(unittest.TestCase):
|
|
def test_appends_when_absent(self):
|
|
body = "Original description."
|
|
section = render_section("26.6.1.1", [])
|
|
result = upsert_section(body, section)
|
|
# Two blank lines separate the section from the preceding description.
|
|
self.assertTrue(result.startswith("Original description.\n\n\n"))
|
|
self.assertIn(SECTION_START, result)
|
|
self.assertIn(SECTION_END, result)
|
|
self.assertIn("Merged into: `26.6.1.1`", result)
|
|
|
|
def test_empty_body(self):
|
|
result = upsert_section("", render_section("26.6.1.1", []))
|
|
self.assertTrue(result.startswith(SECTION_START))
|
|
|
|
def test_none_body(self):
|
|
result = upsert_section(None, render_section("26.6.1.1", []))
|
|
self.assertTrue(result.startswith(SECTION_START))
|
|
|
|
def test_idempotent(self):
|
|
section = render_section("26.6.1.1", ["25.12.1.100"])
|
|
once = upsert_section("Hello.", section)
|
|
twice = upsert_section(once, section)
|
|
self.assertEqual(once, twice)
|
|
|
|
def test_replaces_existing_section(self):
|
|
section_v1 = render_section("26.6.1.1", [])
|
|
section_v2 = render_section("26.6.1.1", ["25.12.1.100"])
|
|
body = upsert_section("Description.", section_v1)
|
|
updated = upsert_section(body, section_v2)
|
|
# Exactly one section, with the new content.
|
|
self.assertEqual(updated.count(SECTION_START), 1)
|
|
self.assertEqual(updated.count(SECTION_END), 1)
|
|
self.assertIn("Backported to: `25.12.1.100`", updated)
|
|
self.assertTrue(updated.startswith("Description."))
|
|
|
|
def test_preserves_surrounding_text(self):
|
|
section = render_section("26.6.1.1", [])
|
|
body = "Before.\n\n" + f"{SECTION_START}\nold\n{SECTION_END}" + "\n\nAfter."
|
|
updated = upsert_section(body, section)
|
|
self.assertTrue(updated.startswith("Before."))
|
|
self.assertTrue(updated.endswith("After."))
|
|
self.assertNotIn("old", updated)
|
|
|
|
|
|
class TestRenderIssueSection(unittest.TestCase):
|
|
def test_inserts_resolved_by_after_header(self):
|
|
section = render_section("26.6.1.1", ["25.12.1.100"])
|
|
issue_section = render_issue_section(12345, section)
|
|
# No delimiters here -- `upsert_section` adds them, as for PR bodies.
|
|
self.assertNotIn(SECTION_START, issue_section)
|
|
self.assertNotIn(SECTION_END, issue_section)
|
|
# `Resolved by` is the first list item, right after the header.
|
|
self.assertEqual(
|
|
issue_section,
|
|
"### Version info\n"
|
|
"- Resolved by: #12345\n"
|
|
"- Merged into: `26.6.1.1`\n"
|
|
"- Backported to: `25.12.1.100`",
|
|
)
|
|
|
|
def test_upserts_into_issue_body_idempotently(self):
|
|
section = render_issue_section(1, render_section("26.6.1.1", []))
|
|
once = upsert_section("Issue description.", section)
|
|
twice = upsert_section(once, section)
|
|
self.assertEqual(once, twice)
|
|
self.assertEqual(once.count(SECTION_START), 1)
|
|
self.assertTrue(once.startswith("Issue description."))
|
|
self.assertIn("- Resolved by: #1", once)
|
|
|
|
|
|
class TestVersionKey(unittest.TestCase):
|
|
def test_orders_numerically(self):
|
|
versions = ["25.8.1.200", "26.6.1.1", "25.12.1.100"]
|
|
ordered = sorted(versions, key=version_key, reverse=True)
|
|
self.assertEqual(ordered, ["26.6.1.1", "25.12.1.100", "25.8.1.200"])
|
|
|
|
|
|
class TestBackportRefParsing(unittest.TestCase):
|
|
def test_valid_ref(self):
|
|
self.assertEqual(
|
|
original_pr_number_from_backport_ref("backport/25.12/92538"), 92538
|
|
)
|
|
|
|
def test_release_prefix_ref(self):
|
|
self.assertEqual(
|
|
original_pr_number_from_backport_ref("backport/release/25.12/92538"),
|
|
92538,
|
|
)
|
|
|
|
def test_non_backport_ref(self):
|
|
self.assertIsNone(original_pr_number_from_backport_ref("my-feature-branch"))
|
|
self.assertIsNone(original_pr_number_from_backport_ref("cherrypick/25.12/1"))
|
|
|
|
def test_release_extraction(self):
|
|
self.assertEqual(release_from_backport_ref("backport/25.12/92538"), "25.12")
|
|
self.assertEqual(
|
|
release_from_backport_ref("backport/release/26.5/58548"), "release/26.5"
|
|
)
|
|
self.assertIsNone(release_from_backport_ref("my-feature-branch"))
|
|
|
|
|
|
class TestHasBackportLabel(unittest.TestCase):
|
|
def test_plain_labels(self):
|
|
self.assertTrue(has_backport_label(["pr-must-backport"]))
|
|
self.assertTrue(has_backport_label(["pr-backports-created"]))
|
|
|
|
def test_version_specific_label(self):
|
|
self.assertTrue(has_backport_label(["v25.12-must-backport"]))
|
|
|
|
def test_no_backport_label(self):
|
|
self.assertFalse(has_backport_label(["pr-feature", "documentation"]))
|
|
|
|
|
|
class TestPartitionMergedPRs(unittest.TestCase):
|
|
def test_original_and_its_backport_in_window(self):
|
|
prs = [
|
|
MergedPR(100, "feature-branch", "master", ["pr-must-backport"]),
|
|
MergedPR(200, "backport/25.12/100", "25.12", ["pr-backport"]),
|
|
]
|
|
backports, originals, need_scan = partition_merged_prs(prs, "master")
|
|
self.assertEqual(backports, {200})
|
|
self.assertEqual(originals, {100})
|
|
self.assertEqual(need_scan, {100})
|
|
|
|
def test_backport_merged_later_pulls_in_original(self):
|
|
# Only the backport is in the lookback window; the original (#100)
|
|
# merged long ago and is not present. It must still be pulled into the
|
|
# originals/need_scan sets so its `Backported to` list gets refreshed.
|
|
prs = [MergedPR(200, "backport/release/25.8/100", "release/25.8", [])]
|
|
backports, originals, need_scan = partition_merged_prs(prs, "master")
|
|
self.assertEqual(backports, {200})
|
|
self.assertEqual(originals, {100})
|
|
self.assertEqual(need_scan, {100})
|
|
|
|
def test_original_without_backport_label_is_not_scanned(self):
|
|
prs = [MergedPR(100, "feature-branch", "master", ["pr-feature"])]
|
|
backports, originals, need_scan = partition_merged_prs(prs, "master")
|
|
self.assertEqual(backports, set())
|
|
self.assertEqual(originals, {100})
|
|
self.assertEqual(need_scan, set())
|
|
|
|
def test_pr_to_release_branch_directly_is_ignored(self):
|
|
# A non-backport PR targeting a release branch is neither an original
|
|
# (not the default branch) nor a backport.
|
|
prs = [MergedPR(300, "some-fix", "25.12", [])]
|
|
backports, originals, need_scan = partition_merged_prs(prs, "master")
|
|
self.assertEqual((backports, originals, need_scan), (set(), set(), set()))
|
|
|
|
def test_ignore_label_pr_is_dropped(self):
|
|
# The automated periodic upstream-sync PR merges to master but must not
|
|
# get a version-info section.
|
|
prs = [
|
|
MergedPR(
|
|
400, "sync-upstream/master", "master", ["pr-periodic-sync-upstream"]
|
|
),
|
|
MergedPR(401, "feature-branch", "master", ["pr-must-backport"]),
|
|
]
|
|
backports, originals, need_scan = partition_merged_prs(prs, "master")
|
|
self.assertEqual(backports, set())
|
|
self.assertEqual(originals, {401})
|
|
self.assertEqual(need_scan, {401})
|
|
|
|
|
|
class TestHasIgnoreLabel(unittest.TestCase):
|
|
def test_periodic_sync_label(self):
|
|
self.assertTrue(has_ignore_label(["pr-periodic-sync-upstream"]))
|
|
|
|
def test_other_labels(self):
|
|
self.assertFalse(has_ignore_label(["pr-sync-upstream", "pr-backport"]))
|
|
|
|
def test_no_labels(self):
|
|
self.assertFalse(has_ignore_label([]))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|