diff options
| author | Yuriy Andamasov <yuriy@vyos.io> | 2026-07-22 18:53:45 +0300 |
|---|---|---|
| committer | Yuriy Andamasov <yuriy@vyos.io> | 2026-07-22 18:53:45 +0300 |
| commit | cef13a0cd6aa64ab2a9b30fe1199d60939846357 (patch) | |
| tree | 968729918bd9f5e11ef1b4dbd2752fd7be52eeef /scripts/docs_gates/test_smoke.py | |
| parent | 95b8a4115439826e7dff767bf3515684535d0783 (diff) | |
| download | vyos-documentation-cef13a0cd6aa64ab2a9b30fe1199d60939846357.tar.gz vyos-documentation-cef13a0cd6aa64ab2a9b30fe1199d60939846357.zip | |
docs-gates: hard-bound the smoke deadline (cap probe timeout + inter-round sleep to remaining budget)
Codex adversarial finding: DEADLINE_SECONDS was only checked BEFORE each op, so a
probe or sleep starting at 479s could overshoot to ~510s — 480 was a soft target,
not a hard bound.
Make it hard: run() now computes an absolute deadline = start + DEADLINE_SECONDS
plus a _remaining() helper. The per-probe socket timeout is capped to
min(PROBE_TIMEOUT_SECONDS, max(1, remaining)) — the previously hardcoded 30 is now
the PROBE_TIMEOUT_SECONDS constant; a probe with < 1s of budget is skipped and
counted unresolved. The inter-round sleep is capped to
min(RETRY_SLEEP_SECONDS, remaining) and is skipped entirely when the budget is
exhausted (falling into the existing deadline path). No body-read-level deadline is
added — pages are small, so the socket-op timeout bounds reads adequately.
Deadline-path failure accounting is unchanged. Tests: the probe timeout is capped
to the remaining budget (fake opener records the timeout it was opened with); the
inter-round sleep is capped to the remaining budget (sleep spy); the existing suite
stays green.
🤖 Generated by [robots](https://vyos.io)
Diffstat (limited to 'scripts/docs_gates/test_smoke.py')
| -rw-r--r-- | scripts/docs_gates/test_smoke.py | 37 |
1 files changed, 35 insertions, 2 deletions
diff --git a/scripts/docs_gates/test_smoke.py b/scripts/docs_gates/test_smoke.py index f0a37c7a..a2654dcf 100644 --- a/scripts/docs_gates/test_smoke.py +++ b/scripts/docs_gates/test_smoke.py @@ -101,15 +101,18 @@ class _FakeResp: class _FakeOpener: - """Yields queued responses in order; an Exception item is raised (models a transport - error, or a non-2xx delivered as HTTPError). Records each opened Request for assertions.""" + """Yields queued responses in order; an Exception item is raised (models a transport error, + or a non-2xx delivered as HTTPError). Records each opened Request + the timeout it was + called with, for assertions.""" def __init__(self, responses: list[object]): self._responses = list(responses) self.calls: list[urllib.request.Request] = [] + self.timeouts: list[float | None] = [] def open(self, req: urllib.request.Request, timeout: float | None = None) -> object: self.calls.append(req) + self.timeouts.append(timeout) item = self._responses.pop(0) if isinstance(item, Exception): raise item @@ -150,10 +153,12 @@ class _PathOpener: def __init__(self, by_path: dict[str, list[object]]): self._by_path = {p: list(v) for p, v in by_path.items()} self.calls: list[str] = [] + self.timeouts: list[float | None] = [] def open(self, req: urllib.request.Request, timeout: float | None = None) -> object: path = urlsplit(req.full_url).path self.calls.append(path) + self.timeouts.append(timeout) item = self._by_path[path].pop(0) if isinstance(item, Exception): raise item @@ -331,3 +336,31 @@ def test_probe_once_detail_joins_multiple_failed_checks(monkeypatch): ok, _, _, detail = smoke._probe_once("host", probe, "sha", "id", "sec") assert ok is False assert detail == "status+apex-build" + + +# --- Adversarial round: DEADLINE_SECONDS is a HARD bound — the per-probe socket timeout and the +# inter-round sleep are both capped to the remaining budget so neither can overshoot it. --- + +def test_probe_timeout_capped_to_remaining_budget(monkeypatch): + monkeypatch.setattr(smoke, "probe_plan", lambda slug, pdf, critical: _plan(["/a"])) + monkeypatch.setattr(smoke, "DEADLINE_SECONDS", 5) # << PROBE_TIMEOUT_SECONDS (30) + opener = _PathOpener({"/a": [_FakeResp(200, {})]}) + monkeypatch.setattr(smoke, "_OPENER", opener) + smoke.run("host", "rolling", "sha", "id", "sec", None, []) + assert opener.timeouts, "the probe recorded the timeout it was opened with" + t = opener.timeouts[0] + assert 1 <= t <= smoke.DEADLINE_SECONDS # capped DOWN to the ~5s remaining budget + assert t < smoke.PROBE_TIMEOUT_SECONDS # NOT the full 30s socket timeout + + +def test_inter_round_sleep_capped_to_remaining_budget(monkeypatch): + monkeypatch.setattr(smoke, "probe_plan", lambda slug, pdf, critical: _plan(["/a"])) + monkeypatch.setattr(smoke, "DEADLINE_SECONDS", 10) # << RETRY_SLEEP_SECONDS (30) + sleeps: list[float] = [] + monkeypatch.setattr(smoke.time, "sleep", lambda s: sleeps.append(s)) + opener = _PathOpener({"/a": [_FakeResp(500, {}), _FakeResp(200, {})]}) # fail r1, pass r2 + monkeypatch.setattr(smoke, "_OPENER", opener) + assert smoke.run("host", "rolling", "sha", "id", "sec", None, []) == 0 + assert len(sleeps) == 1 + assert 0 < sleeps[0] <= smoke.DEADLINE_SECONDS # capped to the ~10s remaining budget + assert sleeps[0] < smoke.RETRY_SLEEP_SECONDS # NOT the full 30s sleep |
