Skip to content

no-fixed-sleep-in-loop

Do not sleep for a fixed literal interval inside a production loop.

Why

A polling or cleanup loop that sleeps for a literal interval gives tests no seam: every test that drives the loop pays the whole interval in real time, and replacing the module's sleep is a monkeypatch that SARJ445 forbids. Suites slow down one hardcoded interval at a time.

Fix

Route the interval through a constructor or configuration parameter, or call an injected sleeper or clock, so tests can pass a zero interval or a recording fake. Keep a literal only behind an exact SARJ485 suppression that names why the interval must never change under test.

Examples

Before — flagged A synchronous retry loop blocks for a hardcoded interval
app/health.py
import time
def wait_for_health(probe):
for _ in range(30):
if probe():
return
time.sleep(0.5)
After — preferred A synchronous retry loop calls an injected sleeper
app/health.py
from collections.abc import Callable
def wait_for_health(probe, sleep: Callable[[float], None]):
for _ in range(30):
if probe():
return
sleep(0.5)
Before — flagged A polling loop sleeps for a hardcoded interval
app/jobs.py
import asyncio
async def wait_until_done(job):
while not await job.done():
await asyncio.sleep(2)
After — preferred A polling loop sleeps for an injected interval
app/jobs.py
import asyncio
class JobWaiter:
def __init__(self, poll_seconds: float) -> None:
self._poll_seconds = poll_seconds
async def wait_until_done(self, job):
while not await job.done():
await asyncio.sleep(self._poll_seconds)