mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-23 20:29:06 -05:00
fix: wait out a 429 and keep the pass going
A single 429 from git.libretro.com on cemu's tree ended profile_sync --all with a traceback and no report for the other 646 profiles. upstream now waits a 429 out (Retry-After up to 120 s, else 5, 20, 60 s), as base_scraper already did, and stops asking a host that outlasts the waits. profile_sync reports that profile as rate limited, fails the run, and carries on with the other forges.
This commit is contained in:
1 parent
f2faf4d9ed
commit
d458cf653a
4 files changed
+198
-53
No files matched your search
@@ -4163,6 +4163,40 @@ class TestRealignProse(unittest.TestCase):
|
||||
self.assertIn("(deep.c:14)", self.path.read_text())
|
||||
|
||||
|
||||
class TestRateLimitEndsOneProfile(unittest.TestCase):
|
||||
"""git.libretro.com answered 429 on cemu's tree and the pass over 647
|
||||
profiles stopped with a traceback and no report at all."""
|
||||
|
||||
def test_the_pass_goes_on_and_fails(self):
|
||||
from unittest import mock
|
||||
|
||||
def build(name, *_args, **_kwargs):
|
||||
if name == "cemu":
|
||||
raise profile_sync.upstream.RateLimitError(
|
||||
"https://git.libretro.com/x: HTTP 429", "git.libretro.com"
|
||||
)
|
||||
return ProfileReport(name=name, counts={"CHANGED": 1})
|
||||
|
||||
out, err = io.StringIO(), io.StringIO()
|
||||
saved = sys.argv
|
||||
try:
|
||||
sys.argv = ["profile_sync.py", "--all", "--json"]
|
||||
with mock.patch.object(profile_sync, "load_emulator_profiles", return_value={}), \
|
||||
mock.patch.object(profile_sync, "select_profiles",
|
||||
return_value={"cemu": {}, "after": {}}), \
|
||||
mock.patch.object(profile_sync, "_check_quota"), \
|
||||
mock.patch.object(profile_sync, "build_report", side_effect=build), \
|
||||
contextlib.redirect_stdout(out), contextlib.redirect_stderr(err):
|
||||
with self.assertRaises(SystemExit) as caught:
|
||||
profile_sync.main()
|
||||
finally:
|
||||
sys.argv = saved
|
||||
self.assertEqual(caught.exception.code, 1)
|
||||
by_name = {r["name"]: r for r in json.loads(out.getvalue())}
|
||||
self.assertEqual(by_name["cemu"]["skipped"], "rate limited: git.libretro.com")
|
||||
self.assertEqual(by_name["after"]["needs_review"], 1)
|
||||
|
||||
|
||||
class TestOutputModesApplyTheirFlags(unittest.TestCase):
|
||||
"""--changed-only narrows --json too. Extras are refused off the text report."""
|
||||
|
||||
|
||||
+44
-3
@@ -237,9 +237,11 @@ class TestFetchRetry(unittest.TestCase):
|
||||
self._orig = (urllib.request.urlopen, upstream._sleep)
|
||||
self.slept: list[float] = []
|
||||
upstream._sleep = self.slept.append
|
||||
upstream._exhausted_hosts.clear()
|
||||
|
||||
def tearDown(self):
|
||||
urllib.request.urlopen, upstream._sleep = self._orig
|
||||
upstream._exhausted_hosts.clear()
|
||||
|
||||
def _serve(self, outcomes):
|
||||
self.calls = 0
|
||||
@@ -283,11 +285,50 @@ class TestFetchRetry(unittest.TestCase):
|
||||
self.assertIsNone(upstream._fetch("https://host/x"))
|
||||
self.assertEqual(self.calls, 1)
|
||||
|
||||
def test_rate_limit_is_not_retried(self):
|
||||
self._serve([_http_error(429)])
|
||||
@staticmethod
|
||||
def _limited(retry_after: str | None = None) -> urllib.error.HTTPError:
|
||||
import email.message # noqa: PLC0415
|
||||
|
||||
headers = email.message.Message()
|
||||
if retry_after is not None:
|
||||
headers["Retry-After"] = retry_after
|
||||
return _http_error(429, headers)
|
||||
|
||||
def test_rate_limit_is_waited_out(self):
|
||||
# git.libretro.com answered 429 on cemu's tree a few hundred
|
||||
# requests into a pass, and the whole pass stopped.
|
||||
self._serve([self._limited("7"), self._limited(), b"payload"])
|
||||
self.assertEqual(upstream._fetch("https://host/x"), b"payload")
|
||||
self.assertEqual(self.slept, [7.0, upstream.RATE_LIMIT_WAITS[1]])
|
||||
|
||||
def test_lasting_rate_limit_mutes_the_host_only(self):
|
||||
self._serve([self._limited()])
|
||||
with self.assertRaises(upstream.RateLimitError) as caught:
|
||||
upstream._fetch("https://host/x")
|
||||
self.assertEqual(caught.exception.host, "host")
|
||||
self.assertEqual(self.calls, len(upstream.RATE_LIMIT_WAITS) + 1)
|
||||
# The same host is not asked again, another one still is.
|
||||
with self.assertRaises(upstream.RateLimitError):
|
||||
upstream._fetch("https://host/y")
|
||||
self.assertEqual(self.calls, len(upstream.RATE_LIMIT_WAITS) + 1)
|
||||
self._serve([b"other"])
|
||||
self.assertEqual(upstream._fetch("https://other/x"), b"other")
|
||||
|
||||
def test_long_retry_after_is_not_waited(self):
|
||||
self._serve([self._limited(str(int(upstream.RATE_LIMIT_WAIT_CAP) + 1))])
|
||||
with self.assertRaises(upstream.RateLimitError):
|
||||
upstream._fetch("https://host/x")
|
||||
self.assertEqual(self.calls, 1)
|
||||
self.assertEqual((self.calls, self.slept), (1, []))
|
||||
|
||||
def test_exhausted_github_quota_is_not_waited(self):
|
||||
import email.message # noqa: PLC0415
|
||||
|
||||
headers = email.message.Message()
|
||||
headers["X-RateLimit-Remaining"] = "0"
|
||||
self._serve([_http_error(403, headers)])
|
||||
with self.assertRaises(upstream.RateLimitError):
|
||||
upstream._fetch("https://host/x")
|
||||
self.assertEqual((self.calls, self.slept), (1, []))
|
||||
|
||||
|
||||
class TestGoneUpstream(unittest.TestCase):
|
||||
|
||||
Reference in new issue
Block a user