Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/scripts/infra_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,20 @@ def _until_not_rate_limited(attempt: Callable[[], subprocess.CompletedProcess])
result = attempt()
if result.returncode == 0 or not _RATE_LIMITED.search(f"{result.stdout}{result.stderr}"):
return result
print(f"::warning::a registry rate-limited `{' '.join(result.args)[:_PRINTED_CHARS]}`; trying again in {wait}s")
print(f"::warning::a registry rate-limited `{' '.join(result.args)[:_PRINTED_CHARS]}`; trying again in {wait}s",
flush=True)
time.sleep(wait)
return attempt()


def _streamed(argv: list[str]) -> subprocess.CompletedProcess:
"""Run ``argv``, printing what it prints as it prints it, which is kept too, to look for a rate limit in."""
"""Run ``argv``, printing what it prints as it prints it, which is kept too, to look for a rate limit in. Each line is
flushed, since the workflow's log is a pipe, which Python would otherwise fill before passing on."""
process = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
printed = []
for line in process.stdout:
sys.stdout.write(line)
sys.stdout.flush()
printed.append(line)
return subprocess.CompletedProcess(argv, process.wait(), "".join(printed), "")

Expand Down
27 changes: 27 additions & 0 deletions tst/unit/test_infra_build_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,30 @@ def test_a_rate_limited_registry_lookup_is_tried_again(script, monkeypatch, caps
monkeypatch.setattr(script.time, "sleep", lambda seconds: None)

assert _run(script, monkeypatch, "exists", ref) == 0


class _Log:
"""A pipe standing in for the workflow's log: what was written, and what had been flushed each time."""

def __init__(self):
self.written, self.flushed = "", []

def write(self, text: str) -> int:
self.written += text
return len(text)

def flush(self) -> None:
self.flushed.append(self.written)


def test_retry_passes_on_each_line_and_its_warning_as_it_goes(script, monkeypatch):
"""Through a pipe, Python holds output until the buffer fills, so a build or a wait would look stuck."""
runs, waits = _attempts(script, monkeypatch, [(_LIMITED, 1), ("#1 DONE\n#2 DONE\n", 0)])
log = _Log()
monkeypatch.setattr(script.sys, "stdout", log)

assert _run(script, monkeypatch, "retry", "docker", "build", ".") == 0

assert log.flushed[0] == _LIMITED
assert log.flushed[1].endswith("trying again in 30s\n")
assert log.flushed[2:] == [log.flushed[1] + "#1 DONE\n", log.flushed[1] + "#1 DONE\n#2 DONE\n"]
Loading