diff --git a/CHANGELOG.md b/CHANGELOG.md index ed69854..56775cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Unreleased + +### Test hygiene + +- Sweep remaining `time.sleep` → `await asyncio.sleep` in async tests + (`test_memory_state.py`, `test_state_postgres.py`). Closes the same + flaky-test hazard fixed for the Redis backend in PR #73. + ## 0.4.26.1 (2026-04-23) Python-only follow-up on `0.4.26`. Still alpha — APIs may change. diff --git a/tests/test_memory_state.py b/tests/test_memory_state.py index 192a00e..fb50d25 100644 --- a/tests/test_memory_state.py +++ b/tests/test_memory_state.py @@ -5,6 +5,7 @@ from __future__ import annotations +import asyncio import time import pytest @@ -139,7 +140,7 @@ async def test_set_with_ttl_expires(self, memory_state: MemoryStateAdapter): # Set with a very short TTL await memory_state.set("key", "value", ttl_ms=1) # Wait for expiry - time.sleep(0.005) # 5ms -- well past 1ms TTL + await asyncio.sleep(0.005) # 5ms -- well past 1ms TTL assert await memory_state.get("key") is None @pytest.mark.asyncio @@ -151,13 +152,13 @@ async def test_set_with_ttl_available_before_expiry(self, memory_state: MemorySt async def test_set_without_ttl_never_expires(self, memory_state: MemoryStateAdapter): await memory_state.set("key", "value") # Even after a brief wait - time.sleep(0.005) + await asyncio.sleep(0.005) assert await memory_state.get("key") == "value" @pytest.mark.asyncio async def test_set_if_not_exists_respects_expired_key(self, memory_state: MemoryStateAdapter): await memory_state.set("key", "old", ttl_ms=1) - time.sleep(0.005) + await asyncio.sleep(0.005) result = await memory_state.set_if_not_exists("key", "new") assert result is True assert await memory_state.get("key") == "new" @@ -191,7 +192,7 @@ async def test_acquire_lock_fails_when_already_held(self, memory_state: MemorySt async def test_acquire_lock_succeeds_after_expiry(self, memory_state: MemoryStateAdapter): lock1 = await memory_state.acquire_lock("thread-1", 1) # 1ms TTL assert lock1 is not None - time.sleep(0.005) + await asyncio.sleep(0.005) lock2 = await memory_state.acquire_lock("thread-1", 30_000) assert lock2 is not None @@ -234,7 +235,7 @@ async def test_extend_lock(self, memory_state: MemoryStateAdapter): assert result is True # Lock should still be held after original TTL would have expired - time.sleep(0.15) + await asyncio.sleep(0.15) lock2 = await memory_state.acquire_lock("thread-1", 30_000) assert lock2 is None # Still held due to extension @@ -251,7 +252,7 @@ async def test_extend_lock_fails_with_wrong_token(self, memory_state: MemoryStat async def test_extend_lock_fails_after_expiry(self, memory_state: MemoryStateAdapter): lock = await memory_state.acquire_lock("thread-1", 1) assert lock is not None - time.sleep(0.005) + await asyncio.sleep(0.005) result = await memory_state.extend_lock(lock, 60_000) assert result is False @@ -317,7 +318,7 @@ async def test_append_to_list_with_max_length(self, memory_state: MemoryStateAda @pytest.mark.asyncio async def test_list_with_ttl_expires(self, memory_state: MemoryStateAdapter): await memory_state.append_to_list("key", "a", ttl_ms=1) - time.sleep(0.005) + await asyncio.sleep(0.005) result = await memory_state.get_list("key") assert result == [] diff --git a/tests/test_state_postgres.py b/tests/test_state_postgres.py index cd22bcf..6bc899f 100644 --- a/tests/test_state_postgres.py +++ b/tests/test_state_postgres.py @@ -8,6 +8,7 @@ class simulates the asyncpg pool interface using in-memory dicts to from __future__ import annotations +import asyncio import datetime as _dt import re import time @@ -542,7 +543,7 @@ class TestPostgresStateTTL: @pytest.mark.asyncio async def test_set_with_ttl_expires(self, pg_state: PostgresStateAdapter): await pg_state.set("key", "value", ttl_ms=1) - time.sleep(0.005) + await asyncio.sleep(0.005) assert await pg_state.get("key") is None @pytest.mark.asyncio @@ -553,14 +554,14 @@ async def test_set_with_ttl_available_before_expiry(self, pg_state: PostgresStat @pytest.mark.asyncio async def test_set_without_ttl_never_expires(self, pg_state: PostgresStateAdapter): await pg_state.set("key", "value") - time.sleep(0.005) + await asyncio.sleep(0.005) assert await pg_state.get("key") == "value" @pytest.mark.asyncio async def test_expired_key_cleaned_on_get(self, pg_state: PostgresStateAdapter, mock_pool: MockAsyncpgPool): """When get() returns None for an expired key, the adapter runs an opportunistic DELETE.""" await pg_state.set("key", "value", ttl_ms=1) - time.sleep(0.005) + await asyncio.sleep(0.005) await pg_state.get("key") # After the get, the expired row should have been cleaned up @@ -598,7 +599,7 @@ async def test_with_ttl(self, pg_state: PostgresStateAdapter): @pytest.mark.asyncio async def test_succeeds_after_expired_key(self, pg_state: PostgresStateAdapter): await pg_state.set("key", "old", ttl_ms=1) - time.sleep(0.005) + await asyncio.sleep(0.005) # The key is expired; set_if_not_exists should succeed result = await pg_state.set_if_not_exists("key", "new") assert result is True @@ -632,7 +633,7 @@ async def test_acquire_lock_fails_when_held(self, pg_state: PostgresStateAdapter async def test_acquire_lock_succeeds_when_expired(self, pg_state: PostgresStateAdapter): lock1 = await pg_state.acquire_lock("thread-1", 1) assert lock1 is not None - time.sleep(0.005) + await asyncio.sleep(0.005) lock2 = await pg_state.acquire_lock("thread-1", 30_000) assert lock2 is not None assert lock2.thread_id == "thread-1" @@ -670,7 +671,7 @@ async def test_extend_lock(self, pg_state: PostgresStateAdapter): assert result is True # Lock should still be held after original TTL - time.sleep(0.15) + await asyncio.sleep(0.15) lock2 = await pg_state.acquire_lock("thread-1", 30_000) assert lock2 is None @@ -687,7 +688,7 @@ async def test_extend_lock_wrong_token_fails(self, pg_state: PostgresStateAdapte async def test_extend_lock_after_expiry_fails(self, pg_state: PostgresStateAdapter): lock = await pg_state.acquire_lock("thread-1", 1) assert lock is not None - time.sleep(0.005) + await asyncio.sleep(0.005) result = await pg_state.extend_lock(lock, 60_000) assert result is False @@ -752,7 +753,6 @@ async def test_acquire_lock_uses_single_atomic_upsert( # Third acquire after expiry: should succeed in single query mock_pool.executed_queries.clear() - time.sleep(0.005) # Force-expire the lock for testing lock_key = ("test", "race-thread") mock_pool.locks[lock_key]["expires_at"] = _dt.datetime.now(_dt.timezone.utc) - _dt.timedelta(seconds=1) @@ -801,7 +801,7 @@ async def test_append_preserves_order(self, pg_state: PostgresStateAdapter): @pytest.mark.asyncio async def test_list_with_ttl_expires(self, pg_state: PostgresStateAdapter): await pg_state.append_to_list("key", "a", ttl_ms=1) - time.sleep(0.005) + await asyncio.sleep(0.005) result = await pg_state.get_list("key") assert result == []