From c16f0144492fd46e6cab4eb2702219cd4ed25a29 Mon Sep 17 00:00:00 2001 From: svrf-maintainer Date: Mon, 12 Oct 2026 15:34:12 +0000 Subject: [PATCH 1/2] test: the snapshot lists ten pages by default and a configured limit reaches every repository client (RED) --- tests/test_snapshot_limit_config.py | 48 +++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/test_snapshot_limit_config.py diff --git a/tests/test_snapshot_limit_config.py b/tests/test_snapshot_limit_config.py new file mode 100644 index 0000000..9295ba0 --- /dev/null +++ b/tests/test_snapshot_limit_config.py @@ -0,0 +1,48 @@ +"""[train] snapshot_limit bounds how many open pull requests one snapshot may list. + +The snapshot refuses a listing longer than its limit (SNAPSHOT_LIMIT_EXCEEDED), so a runaway listing never becomes +a round. The limit was fixed at 300. A repository with more open pull requests than that read no round at all: +every tick retried, and nothing was admitted or landed, including the configuration change that would have raised +it. The default is now 1000 (ten 100-row pages), a [train] snapshot_limit overrides it, and every place that builds +the repository client reads it. +""" + +from __future__ import annotations + +import tempfile +import unittest +from pathlib import Path + +from svrf.app import build +from svrf.config import ConfigError, from_dict + + +def _config(**train): + tmp = Path(tempfile.mkdtemp()) + doc = {"repo": "o/r", "clone": str(tmp / "clone"), "state_dir": str(tmp / "state"), + "gate": {"commands": ["true"]}} + if train: + doc["train"] = train + return from_dict(doc) + + +class SnapshotLimitConfig(unittest.TestCase): + def test_the_default_lists_ten_pages(self): + self.assertEqual(_config().train.snapshot_limit, 1000) + + def test_a_configured_limit_is_read(self): + self.assertEqual(_config(snapshot_limit=2000).train.snapshot_limit, 2000) + + def test_a_limit_that_is_not_a_positive_whole_count_is_refused(self): + for bad in (0, -1, 2.5, "1000", True): + with self.assertRaises(ConfigError, msg=repr(bad)): + _config(snapshot_limit=bad) + + def test_the_daemon_s_repository_client_reads_the_configured_limit(self): + daemon = build(_config(snapshot_limit=2000)) + self.assertEqual(daemon.gh.limit, 2000) + self.assertEqual(build(_config()).gh.limit, 1000) + + +if __name__ == "__main__": + unittest.main() From 694f4970f175d19d7858f21ec30b87d7d2f9c984 Mon Sep 17 00:00:00 2001 From: svrf-maintainer Date: Mon, 12 Oct 2026 15:35:04 +0000 Subject: [PATCH 2/2] feat: the snapshot lists ten pages by default and a configured limit reaches every repository client A repository with more open pull requests than the fixed 300 read no round at all. [train] snapshot_limit (default 1000, a positive whole count) now bounds the listing, and app, cli replay and doctor build the client with it. --- src/svrf/app.py | 2 +- src/svrf/cli.py | 2 +- src/svrf/config.py | 4 ++++ src/svrf/doctor.py | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/svrf/app.py b/src/svrf/app.py index df8bd99..a37bd87 100644 --- a/src/svrf/app.py +++ b/src/svrf/app.py @@ -76,7 +76,7 @@ def build(config: Config, *, github=None, dry_run: bool = False, clock=None, sle extra["demand"] = demand except (ImportError, AttributeError, TypeError, ValueError) as exc: raise ConfigError(f"demand driver unavailable: {exc}") from exc - return Daemon(git, github or RealGitHub(config.repo), gate, admission, state_dir=config.state_dir, + return Daemon(git, github or RealGitHub(config.repo, limit=config.train.snapshot_limit), gate, admission, state_dir=config.state_dir, receipts=config.receipts, base=config.base, dry_run=dry_run, rate_floor=config.train.rate_floor, lock_path=config.lock, hold_label=config.hold_label, admission_watch=config.admission_watch, is_union=union, repair=config.repair, jobs=config.train.jobs, memory=memory, diff --git a/src/svrf/cli.py b/src/svrf/cli.py index 5dc7ba4..3c6c895 100644 --- a/src/svrf/cli.py +++ b/src/svrf/cli.py @@ -229,7 +229,7 @@ def main(argv: list[str] | None = None, *, github=None) -> int: except ReadFailed as failure: _print({"tick": "RETRY", "reason": failure.reason}) return 0 - hub = github or RealGitHub(config.repo) + hub = github or RealGitHub(config.repo, limit=config.train.snapshot_limit) git = RealGit(config.clone, base=config.base) numbers = _numbers(args.merged) found, problems = replayer.reconstruct(git, hub.merge_commit, numbers) diff --git a/src/svrf/config.py b/src/svrf/config.py index a531639..fd01a30 100644 --- a/src/svrf/config.py +++ b/src/svrf/config.py @@ -41,6 +41,7 @@ class TrainConfig: rate_floor: int = 200 comment: bool = True interval_seconds: float = 300 + snapshot_limit: int = 1000 # the most open pull requests one snapshot lists (ten 100-row pages) @dataclass @@ -176,6 +177,9 @@ def from_dict(value: dict, *, root: Path | None = None) -> Config: if bad: raise ConfigError(f"unknown keys in [{name}]: {', '.join(bad)}") kwargs[name] = cls(**section) + limit = kwargs["train"].snapshot_limit + if type(limit) is not int or limit < 1: + raise ConfigError("[train] snapshot_limit must be a positive whole count") for key in ("clone", "state_dir"): if key in kwargs: path = Path(os.path.expandvars(str(kwargs[key]))).expanduser() diff --git a/src/svrf/doctor.py b/src/svrf/doctor.py index 132708e..7f011c5 100644 --- a/src/svrf/doctor.py +++ b/src/svrf/doctor.py @@ -114,7 +114,7 @@ def run(config: Config, *, github=None, subprocess_run=_run) -> list[Check]: if github is None: from .github import RealGitHub - github = RealGitHub(config.repo) + github = RealGitHub(config.repo, limit=config.train.snapshot_limit) return [ check_git(subprocess_run), check_gh(subprocess_run),