Files
libretro/tests/test_upstream.py
T

319 lines
11 KiB
Python

"""Tests for the upstream repository access module (no network)."""
from __future__ import annotations
import os
import sys
import tempfile
import unittest
from pathlib import Path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts"))
import upstream
from upstream import make_repo, parse_repo, raw_url
def _no_network(url: str):
"""Any call reaching here is a leak: the suite must stay offline."""
raise AssertionError(f"test reached the network: {url}")
class TestParseRepo(unittest.TestCase):
def test_github(self):
repo = parse_repo("https://github.com/libretro/beetle-psx-libretro")
self.assertEqual(repo.family, "github")
self.assertEqual(repo.owner, "libretro")
self.assertEqual(repo.name, "beetle-psx-libretro")
def test_github_trailing_git_and_slash(self):
self.assertEqual(
parse_repo("https://github.com/mamedev/mame.git/"),
parse_repo("https://github.com/mamedev/mame"),
)
def test_gitlab(self):
repo = parse_repo("https://gitlab.com/recalbox/recalbox")
self.assertEqual(repo.family, "gitlab")
def test_codeberg_is_forgejo(self):
self.assertEqual(parse_repo("https://codeberg.org/a/b").family, "forgejo")
def test_known_forgejo_instances(self):
for url in (
"https://git.citron-emu.org/citron/emu",
"https://git.eden-emu.dev/eden-emu/eden",
):
self.assertEqual(parse_repo(url).family, "forgejo")
def test_unsupported_host(self):
self.assertIsNone(parse_repo("https://sourceforge.net/projects/vice"))
def test_non_repository_url(self):
self.assertIsNone(parse_repo("https://mednafen.github.io/"))
def test_empty(self):
self.assertIsNone(parse_repo(""))
class TestRawUrl(unittest.TestCase):
def test_github_raw(self):
repo = parse_repo("https://github.com/libretro/x")
self.assertEqual(
raw_url(repo, "abc123", "src/main.cpp"),
"https://raw.githubusercontent.com/libretro/x/abc123/src/main.cpp",
)
def test_gitlab_raw(self):
repo = parse_repo("https://gitlab.com/g/p")
self.assertEqual(
raw_url(repo, "abc123", "a.c"),
"https://gitlab.com/g/p/-/raw/abc123/a.c",
)
def test_forgejo_raw(self):
repo = parse_repo("https://codeberg.org/g/p")
self.assertEqual(
raw_url(repo, "abc123", "a.c"),
"https://codeberg.org/g/p/raw/commit/abc123/a.c",
)
def test_path_is_quoted(self):
repo = parse_repo("https://github.com/o/n")
self.assertIn("src/a%20b.cpp", raw_url(repo, "s", "src/a b.cpp"))
class TestMakeRepo(unittest.TestCase):
def test_rebuilds_a_known_host(self):
self.assertEqual(
make_repo("github.com", "o", "n"), parse_repo("https://github.com/o/n")
)
def test_unknown_host_returns_none(self):
self.assertIsNone(make_repo("example.invalid", "o", "n"))
class TestCache(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.dir = self.tmp.name
self.repo = parse_repo("https://github.com/o/n")
self.calls: list[str] = []
self._orig = (upstream._http_text, upstream._http_json)
upstream._http_text = self._fake
upstream._http_json = _no_network
def tearDown(self):
upstream._http_text, upstream._http_json = self._orig
self.tmp.cleanup()
def _fake(self, url: str) -> str | None:
self.calls.append(url)
return "line one\nline two\n"
def test_path_includes_host_slug_and_sha(self):
p = upstream.cache_path(self.dir, self.repo, "abc123", "src/a.cpp")
self.assertIn("github.com", str(p))
self.assertIn("o", str(p))
self.assertIn("abc123", str(p))
def test_path_is_contained_in_cache_dir(self):
p = upstream.cache_path(self.dir, self.repo, "abc", "../../escape.c")
self.assertTrue(str(p.resolve()).startswith(str(Path(self.dir).resolve())))
def test_fetch_then_cache_hit(self):
first = upstream.fetch_file(self.repo, "abc", "a.c", self.dir)
second = upstream.fetch_file(self.repo, "abc", "a.c", self.dir)
self.assertEqual(first, ["line one", "line two"])
self.assertEqual(second, first)
self.assertEqual(len(self.calls), 1)
def test_offline_miss_returns_none_without_request(self):
self.assertIsNone(
upstream.fetch_file(self.repo, "abc", "a.c", self.dir, offline=True)
)
self.assertEqual(self.calls, [])
def test_offline_hit_serves_cache(self):
upstream.fetch_file(self.repo, "abc", "a.c", self.dir)
self.calls.clear()
self.assertEqual(
upstream.fetch_file(self.repo, "abc", "a.c", self.dir, offline=True),
["line one", "line two"],
)
self.assertEqual(self.calls, [])
def test_missing_file_is_cached_as_absent(self):
upstream._http_text = lambda url: None
self.assertIsNone(upstream.fetch_file(self.repo, "abc", "gone.c", self.dir))
self.assertIsNone(
upstream.fetch_file(self.repo, "abc", "gone.c", self.dir, offline=True)
)
def test_write_leaves_no_temporary_behind(self):
target = Path(self.dir) / "sub" / "f.txt"
upstream.write_cache(target, "payload")
self.assertEqual(target.read_text(encoding="utf-8"), "payload")
siblings = list(target.parent.iterdir())
self.assertEqual([p.name for p in siblings], ["f.txt"])
class TestRevisions(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.dir = self.tmp.name
self.repo = parse_repo("https://github.com/o/n")
self.responses: dict[str, object] = {}
self.calls: list[str] = []
self._orig = (upstream._http_json, upstream._http_text)
upstream._http_json = self._fake
upstream._http_text = _no_network
def tearDown(self):
upstream._http_json, upstream._http_text = self._orig
self.tmp.cleanup()
def _fake(self, url: str):
self.calls.append(url)
for fragment, payload in self.responses.items():
if fragment in url:
return payload
return None
def test_resolve_head_github(self):
self.responses["/commits"] = [{"sha": "deadbeef"}]
self.assertEqual(upstream.resolve_head(self.repo, self.dir), "deadbeef")
def test_resolve_head_gitlab_uses_id(self):
repo = parse_repo("https://gitlab.com/g/p")
self.responses["/repository/commits"] = [{"id": "cafe"}]
self.assertEqual(upstream.resolve_head(repo, self.dir), "cafe")
def test_resolve_commit_at_passes_date(self):
self.responses["/commits"] = [{"sha": "abc"}]
self.assertEqual(
upstream.resolve_commit_at(self.repo, "2026-03-29", self.dir), "abc"
)
self.assertIn("2026-03-29", self.calls[0])
def test_api_response_is_cached(self):
self.responses["/commits"] = [{"sha": "abc"}]
upstream.resolve_commit_at(self.repo, "2026-03-29", self.dir)
upstream.resolve_commit_at(self.repo, "2026-03-29", self.dir)
self.assertEqual(len(self.calls), 1)
def test_offline_without_cache_returns_none(self):
self.assertIsNone(upstream.resolve_head(self.repo, self.dir, offline=True))
self.assertEqual(self.calls, [])
def test_empty_history_returns_none(self):
self.responses["/commits"] = []
self.assertIsNone(upstream.resolve_head(self.repo, self.dir))
def test_list_tags(self):
self.responses["/tags"] = [{"name": "v1.2"}, {"name": "v1.1"}]
self.assertEqual(upstream.list_tags(self.repo, self.dir), ["v1.2", "v1.1"])
def test_resolve_tag_commit(self):
self.responses["/tags"] = [{"name": "v1.2", "commit": {"sha": "tagsha"}}]
self.assertEqual(
upstream.resolve_tag_commit(self.repo, "v1.2", self.dir), "tagsha"
)
def test_latest_release(self):
self.responses["/releases/latest"] = {
"tag_name": "v3.0.0",
"published_at": "2026-06-14T10:00:00Z",
"prerelease": False,
}
rel = upstream.latest_release(self.repo, self.dir)
self.assertEqual(rel.tag, "v3.0.0")
self.assertEqual(rel.date, "2026-06-14")
self.assertFalse(rel.is_prerelease)
def test_missing_release_returns_none(self):
self.assertIsNone(upstream.latest_release(self.repo, self.dir))
def test_rate_limit_propagates(self):
def boom(url: str):
raise upstream.RateLimitError("quota")
upstream._http_json = boom
with self.assertRaises(upstream.RateLimitError):
upstream.resolve_head(self.repo, self.dir)
class TestCompare(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.dir = self.tmp.name
self.repo = parse_repo("https://github.com/o/n")
self.payload: object = None
self._orig = (upstream._http_json, upstream._http_text)
upstream._http_json = lambda url: self.payload
upstream._http_text = _no_network
def tearDown(self):
upstream._http_json, upstream._http_text = self._orig
self.tmp.cleanup()
def test_renamed_file_carries_previous_path(self):
self.payload = {
"files": [
{
"status": "renamed",
"filename": "src/new.cpp",
"previous_filename": "old.cpp",
},
{"status": "modified", "filename": "a.c"},
]
}
result = upstream.compare(self.repo, "a", "b", self.dir)
self.assertFalse(result.truncated)
self.assertEqual(upstream.find_renamed(result, "old.cpp"), "src/new.cpp")
def test_no_rename_returns_none(self):
self.payload = {"files": [{"status": "modified", "filename": "a.c"}]}
result = upstream.compare(self.repo, "a", "b", self.dir)
self.assertIsNone(upstream.find_renamed(result, "old.cpp"))
def test_truncated_at_github_cap(self):
self.payload = {
"files": [
{"status": "modified", "filename": f"f{i}.c"} for i in range(300)
]
}
self.assertTrue(upstream.compare(self.repo, "a", "b", self.dir).truncated)
def test_missing_comparison_is_empty_and_truncated(self):
self.payload = None
result = upstream.compare(self.repo, "a", "b", self.dir)
self.assertEqual(result.files, [])
self.assertTrue(result.truncated)
def test_gitlab_diffs_shape(self):
repo = parse_repo("https://gitlab.com/g/p")
self.payload = {
"diffs": [
{
"new_path": "new.c",
"old_path": "old.c",
"renamed_file": True,
"new_file": False,
"deleted_file": False,
}
]
}
result = upstream.compare(repo, "a", "b", self.dir)
self.assertEqual(upstream.find_renamed(result, "old.c"), "new.c")
def test_commits_touching_counts_entries(self):
self.payload = [{"sha": "1"}, {"sha": "2"}, {"sha": "3"}]
self.assertEqual(
upstream.commits_touching(self.repo, "a.c", "base", self.dir), 3
)
if __name__ == "__main__":
unittest.main()