test(#5196): add _safe_replace retry/backoff coverage (auto-fix)
Adds the test the project's contribution rules require for non-trivial logic (AGENTS.md). Covers: posix passthrough (1 call, no sleep), Windows retry-then-succeed, reraise after exhausting 5 attempts (no silent success on a save path), no-retry on non-PermissionError, and the 50/100/200/400ms backoff sequence. Production _safe_replace unchanged.
This commit is contained in:
1 parent
17f2d898f3
commit
683a96acda
1 file changed
+61
@@ -0,0 +1,61 @@
|
||||
# tests/test_issue5196_windows_safe_replace.py
|
||||
from unittest import mock
|
||||
from api import models
|
||||
|
||||
def test_safe_replace_passthrough_off_windows():
|
||||
calls = []
|
||||
with mock.patch.object(models.os, "name", "posix"), \
|
||||
mock.patch.object(models.os, "replace", lambda s, d: calls.append((s, d))), \
|
||||
mock.patch.object(models.time, "sleep") as slept:
|
||||
models._safe_replace("a", "b")
|
||||
assert len(calls) == 1 and not slept.called
|
||||
|
||||
def test_safe_replace_retries_then_succeeds_on_windows():
|
||||
n = {"c": 0}
|
||||
def flaky(s, d):
|
||||
n["c"] += 1
|
||||
if n["c"] <= 2:
|
||||
raise PermissionError("WinError 5")
|
||||
with mock.patch.object(models.os, "name", "nt"), \
|
||||
mock.patch.object(models.os, "replace", flaky), \
|
||||
mock.patch.object(models.time, "sleep", lambda x: None):
|
||||
models._safe_replace("a", "b")
|
||||
assert n["c"] == 3
|
||||
|
||||
def test_safe_replace_reraises_after_exhausting_retries():
|
||||
n = {"c": 0}
|
||||
def always(s, d):
|
||||
n["c"] += 1
|
||||
raise PermissionError("WinError 5")
|
||||
with mock.patch.object(models.os, "name", "nt"), \
|
||||
mock.patch.object(models.os, "replace", always), \
|
||||
mock.patch.object(models.time, "sleep", lambda x: None):
|
||||
import pytest
|
||||
with pytest.raises(PermissionError):
|
||||
models._safe_replace("a", "b")
|
||||
assert n["c"] == 5 # no silent success on a save path
|
||||
|
||||
def test_safe_replace_does_not_retry_non_permission_errors():
|
||||
n = {"c": 0}
|
||||
def oserr(s, d):
|
||||
n["c"] += 1
|
||||
raise OSError("disk full")
|
||||
with mock.patch.object(models.os, "name", "nt"), \
|
||||
mock.patch.object(models.os, "replace", oserr), \
|
||||
mock.patch.object(models.time, "sleep", lambda x: None):
|
||||
import pytest
|
||||
with pytest.raises(OSError):
|
||||
models._safe_replace("a", "b")
|
||||
assert n["c"] == 1
|
||||
|
||||
def test_safe_replace_backoff_sequence():
|
||||
delays = []
|
||||
def always(s, d):
|
||||
raise PermissionError("x")
|
||||
with mock.patch.object(models.os, "name", "nt"), \
|
||||
mock.patch.object(models.os, "replace", always), \
|
||||
mock.patch.object(models.time, "sleep", lambda x: delays.append(round(x, 3))):
|
||||
import pytest
|
||||
with pytest.raises(PermissionError):
|
||||
models._safe_replace("a", "b")
|
||||
assert delays == [0.05, 0.1, 0.2, 0.4]
|
||||
Reference in new issue
Block a user