Trust test: wait for Gitea to dismiss a stale approval before merging (#162) #164
@@ -5,6 +5,7 @@ and a commit pushed after the approval needs approving again: at once by the
|
|||||||
agent's own check, and by Gitea once it has dismissed the approval."""
|
agent's own check, and by Gitea once it has dismissed the approval."""
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
import re
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -44,13 +45,29 @@ def wait_for_dismissal(gitea: GiteaClient, repo: str, pr: int) -> None:
|
|||||||
in the push itself, and its merge check counts what isn't dismissed yet.
|
in the push itself, and its merge check counts what isn't dismissed yet.
|
||||||
A merge tried before that job has run can still go through (#162)."""
|
A merge tried before that job has run can still go through (#162)."""
|
||||||
deadline = time.monotonic() + DISMISS_WAIT_S
|
deadline = time.monotonic() + DISMISS_WAIT_S
|
||||||
|
|
|||||||
while gitea.approvals(repo, pr): # dismissed ones aren't listed
|
while True:
|
||||||
|
try:
|
||||||
|
if not gitea.approvals(repo, pr): # dismissed ones aren't listed
|
||||||
|
return
|
||||||
|
except GiteaError as exc:
|
||||||
|
# A busy Gitea may drop a call or answer 5xx; ask again. Anything
|
||||||
|
# else, such as a 4xx, is a real failure.
|
||||||
|
if not transient(exc) or time.monotonic() > deadline:
|
||||||
|
raise
|
||||||
assert time.monotonic() < deadline, (
|
assert time.monotonic() < deadline, (
|
||||||
f"Gitea didn't dismiss the approval within {DISMISS_WAIT_S}s"
|
f"Gitea didn't dismiss the approval within {DISMISS_WAIT_S}s"
|
||||||
)
|
)
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
|
||||||
|
def transient(exc: GiteaError) -> bool:
|
||||||
|
"""A dropped connection or a 5xx: worth asking again."""
|
||||||
|
text = str(exc)
|
||||||
|
return text.startswith("gitea request failed") or bool(
|
||||||
|
re.search(r" returned 5\d\d:", text)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_merging_needs_the_operators_approval_of_the_pr_as_it_is(
|
def test_merging_needs_the_operators_approval_of_the_pr_as_it_is(
|
||||||
tmp_path: Path, live_config: AgentConfig, gitea: GiteaClient
|
tmp_path: Path, live_config: AgentConfig, gitea: GiteaClient
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
Reference in new issue
Block a user
This loop makes up to DISMISS_WAIT_S / 0.5 ≈ 60 GET calls, so it has far more exposure to a transient Gitea error (a 5xx, a dropped connection) than any single call elsewhere in the test. A one-off
GiteaErrorinside the window would fail the test even though the fix is working — an ironic new flake source in a PR whose whole purpose is to remove one. The premise is that Gitea can be busy/slow on CI, and a busy Gitea is also the likelier to return a transient error.The sibling live test (
test_end_to_end_loop.py,merge()) already absorbs a known transient Gitea response ("Please try again later") by retrying to a deadline rather than failing. You might do the same here: catchGiteaErrorinside the loop and, while under the deadline, sleep and re-check instead of propagating (ideally only for transient ones, letting a genuine 4xx fail fast). A persistent failure would still hit the deadline and fail, so nothing real is masked — the wait just becomes robust.Agreed, done in
a1eeab1. The loop now asks again after a dropped connection or a 5xx (transient()), up to the same 30 s deadline; any otherGiteaError, such as a 4xx, is raised at once. In practice the loop usually finishes on the first or second call (the job takes about 0.2 s), but a busy Gitea is exactly when it would matter.