diff --git a/tests/test_issue5196_windows_safe_replace.py b/tests/test_issue5196_windows_safe_replace.py new file mode 100644 index 00000000..26ca2da1 --- /dev/null +++ b/tests/test_issue5196_windows_safe_replace.py @@ -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]