138 lines
7.5 KiB
Python
138 lines
7.5 KiB
Python
"""Offline tests for the compatibility tracker."""
|
||
|
||
import importlib.util
|
||
import io
|
||
import json
|
||
import tempfile
|
||
import unittest
|
||
from pathlib import Path
|
||
from unittest.mock import patch
|
||
|
||
|
||
SCRIPT = Path(__file__).with_name("track_gitea_release.py")
|
||
SPEC = importlib.util.spec_from_file_location("track_gitea_release", SCRIPT)
|
||
TRACKER = importlib.util.module_from_spec(SPEC)
|
||
SPEC.loader.exec_module(TRACKER)
|
||
|
||
|
||
class TrackerTests(unittest.TestCase):
|
||
def setUp(self):
|
||
self.directory = tempfile.TemporaryDirectory(prefix="tracker-test-", dir=TRACKER.ROOT)
|
||
self.addCleanup(self.directory.cleanup)
|
||
self.root = Path(self.directory.name)
|
||
self.paths = ("COMPATIBILITY.md", "README.md", "AGENTS.md", "docs/README.zh-CN.md")
|
||
self.originals = {}
|
||
for name in self.paths:
|
||
destination = self.root / name
|
||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||
content = (TRACKER.ROOT / name).read_text(encoding="utf-8")
|
||
destination.write_text(content, encoding="utf-8")
|
||
self.originals[name] = content
|
||
|
||
def read(self, name):
|
||
return (self.root / name).read_text(encoding="utf-8")
|
||
|
||
def assert_unchanged(self):
|
||
for name, content in self.originals.items():
|
||
self.assertEqual(content, self.read(name), name)
|
||
|
||
def test_new_release_adds_only_pending_rows(self):
|
||
changed = TRACKER.apply_release(self.root, "28.2.0", "2026-11-01")
|
||
self.assertEqual({self.root / name for name in self.paths}, set(changed))
|
||
compatibility = self.read("COMPATIBILITY.md")
|
||
self.assertIn("| 28.2.0 | [PENDING] Compatibility verification", compatibility)
|
||
self.assertIn("| **28.2.0** | 2026-11-01 | [PENDING]", compatibility)
|
||
self.assertIn("| **v28.0.0** | **28.0.0** | **28.0.0** | [PASS] Active |", compatibility)
|
||
self.assertIn("Latest verified:** Release v28.0.0", compatibility)
|
||
self.assertIn("Gitea-28.2.0%20%5BPENDING%5D%20%7C%2028.0.0%20tested-yellow", self.read("README.md"))
|
||
self.assertIn("Gitea 28.2.0:** [PENDING]", self.read("README.md"))
|
||
self.assertIn("Gitea 28.2.0:** [PENDING]", self.read("docs/README.zh-CN.md"))
|
||
self.assertIn("Gitea release: 28.2.0 [PENDING]", self.read("AGENTS.md"))
|
||
self.assertIn("Latest tested:** Gitea 28.0.0", self.read("README.md"))
|
||
self.assertIn("最新测试:** Gitea 28.0.0", self.read("docs/README.zh-CN.md"))
|
||
self.assertEqual([], TRACKER.apply_release(self.root, "28.2.0", "2026-11-01"))
|
||
|
||
def test_existing_version_is_idempotent(self):
|
||
self.assertEqual([], TRACKER.apply_release(self.root, "28.1.0", "2026-10-06"))
|
||
self.assert_unchanged()
|
||
|
||
def test_partial_update_is_repaired(self):
|
||
compatibility = self.root / "COMPATIBILITY.md"
|
||
partial = self.read("COMPATIBILITY.md").replace("| 28.1.0 | [PENDING] Compatibility verification; no tested template release yet |\n", "")
|
||
compatibility.write_text(partial, encoding="utf-8")
|
||
self.assertEqual([compatibility], TRACKER.apply_release(self.root, "28.1.0", "2026-10-06"))
|
||
self.assertIn("| 28.1.0 | [PENDING] Compatibility verification", self.read("COMPATIBILITY.md"))
|
||
|
||
def test_older_version_does_not_rewrite_history(self):
|
||
self.assertEqual([], TRACKER.apply_release(self.root, "28.0.0", "2026-09-29"))
|
||
self.assert_unchanged()
|
||
|
||
def test_dry_run_does_not_write(self):
|
||
self.assertEqual(4, len(TRACKER.apply_release(self.root, "28.2.0", "2026-11-01", dry_run=True)))
|
||
self.assert_unchanged()
|
||
|
||
def test_missing_marker_fails_without_writing(self):
|
||
compatibility = self.root / "COMPATIBILITY.md"
|
||
compatibility.write_text(self.read("COMPATIBILITY.md").replace("<!-- TRACKER:HISTORY -->", ""), encoding="utf-8")
|
||
with self.assertRaises(ValueError):
|
||
TRACKER.apply_release(self.root, "28.2.0", "2026-11-01")
|
||
self.assertEqual(self.originals["README.md"], self.read("README.md"))
|
||
|
||
def test_unknown_marker_fails_without_writing(self):
|
||
news = self.root / "docs/NEWS.md"
|
||
news.write_text("Gitea 28.1.0 [PENDING] <!-- TRACKER:FUTURE-UNKNOWN -->\n", encoding="utf-8")
|
||
with self.assertRaisesRegex(ValueError, "Unknown TRACKER marker"):
|
||
TRACKER.apply_release(self.root, "28.2.0", "2026-11-01")
|
||
self.assert_unchanged()
|
||
|
||
def test_new_marked_document_is_discovered(self):
|
||
news = self.root / "docs/NEWS.md"
|
||
news.write_text("Gitea 28.1.0 [PENDING] <!-- TRACKER:UPSTREAM -->\n", encoding="utf-8")
|
||
changed = TRACKER.apply_release(self.root, "28.2.0", "2026-11-01")
|
||
self.assertIn(news, changed)
|
||
self.assertEqual("Gitea 28.2.0 [PENDING] <!-- TRACKER:UPSTREAM -->\n", news.read_text(encoding="utf-8"))
|
||
|
||
def test_disagreeing_tested_versions_fail(self):
|
||
chinese = self.root / "docs/README.zh-CN.md"
|
||
chinese.write_text(self.read("docs/README.zh-CN.md").replace("**最新测试:** Gitea 28.0.0", "**最新测试:** Gitea 28.1.0"), encoding="utf-8")
|
||
with self.assertRaisesRegex(ValueError, "disagree"):
|
||
TRACKER.apply_release(self.root, "28.2.0", "2026-11-01")
|
||
|
||
def test_after_verification_new_version_becomes_pending(self):
|
||
english = self.root / "README.md"
|
||
chinese = self.root / "docs/README.zh-CN.md"
|
||
agents = self.root / "AGENTS.md"
|
||
english.write_text(self.read("README.md")
|
||
.replace("**Latest tested:** Gitea 28.0.0", "**Latest tested:** Gitea 28.1.0")
|
||
.replace("Gitea-28.1.0%20%5BPENDING%5D%20%7C%2028.0.0%20tested-yellow", "Gitea-28.1.0%20%7C%2028.1.0%20tested-blue")
|
||
.replace("**Upstream Gitea 28.1.0:** [PENDING]", "**Upstream Gitea 28.1.0:** [PASS]"), encoding="utf-8")
|
||
chinese.write_text(self.read("docs/README.zh-CN.md")
|
||
.replace("**最新测试:** Gitea 28.0.0", "**最新测试:** Gitea 28.1.0")
|
||
.replace("**上游 Gitea 28.1.0:** [PENDING]", "**上游 Gitea 28.1.0:** [PASS]"), encoding="utf-8")
|
||
agents.write_text(self.read("AGENTS.md").replace("28.1.0 [PENDING]", "28.1.0 [PASS]"), encoding="utf-8")
|
||
TRACKER.apply_release(self.root, "28.2.0", "2026-11-01")
|
||
self.assertIn("Gitea-28.2.0%20%5BPENDING%5D%20%7C%2028.1.0%20tested-yellow", self.read("README.md"))
|
||
self.assertIn("Gitea 28.2.0:** [PENDING]", self.read("README.md"))
|
||
self.assertIn("Latest tested:** Gitea 28.1.0", self.read("README.md"))
|
||
self.assertIn("最新测试:** Gitea 28.1.0", self.read("docs/README.zh-CN.md"))
|
||
|
||
def test_invalid_version_rejected(self):
|
||
with self.assertRaises(ValueError):
|
||
TRACKER.apply_release(self.root, "28.2.0-rc1", "2026-11-01")
|
||
|
||
def test_release_api_uses_published_date(self):
|
||
payload = {"tag_name": "v28.2.0", "published_at": "2026-11-01T08:15:00Z", "draft": False, "prerelease": False}
|
||
with patch.object(TRACKER, "urlopen", return_value=io.BytesIO(json.dumps(payload).encode("utf-8"))) as mocked:
|
||
self.assertEqual(("28.2.0", "2026-11-01"), TRACKER.fetch_release("28.2.0"))
|
||
self.assertEqual(f"{TRACKER.RELEASES_API}/tags/v28.2.0", mocked.call_args.args[0].full_url)
|
||
|
||
def test_prerelease_is_rejected(self):
|
||
payload = {"tag_name": "v28.2.0", "published_at": "2026-11-01T08:15:00Z", "prerelease": True}
|
||
with patch.object(TRACKER, "urlopen", return_value=io.BytesIO(json.dumps(payload).encode("utf-8"))):
|
||
with self.assertRaises(ValueError):
|
||
TRACKER.fetch_release(None)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|