test: move TTL expiry on a controlled clock instead of sleeping - #288
Merged
Merged
Conversation
A clock fixture in tests/conftest.py points the memory backend's time.time() and the session and state modules' datetime.now() at a settable clock. The TTL tests advance it instead of sleeping; tests that also run against a live Redis still sleep there, since the server keeps its own time. The sweeper tests shorten the cleanup interval, the JWT expiry test creates its session in the past because PyJWT reads the real time, and the Memcached dead-timeout test shortens _DEAD_TIMEOUT. The suite without live servers drops from about 25.5 s to 3.6 s.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #185
Part of #123. About 22 s of the ~25.5 s suite (without live servers) was spent sleeping until TTLs ran out. The tests now move a clock instead.
The
clockfixture (tests/conftest.py)clockpoints every time read behind a TTL or expiry check at a settableClock:time.time()in the memory backend;datetime.now()insession.manager,session.models,state.managerandstate.models.clock.advance(seconds)moves it.await clock.wait(seconds, backend)advances it and also sleeps for real whenbackendis not the memory backend. It is for the tests parametrized over memory and a live Redis, whose server keeps its own time.Converted tests
test_memory.py:ttl_expiry,cleanup,get_evicts_the_expired_entry_it_skips;test_proxybackend.py::test_backend_cleanupclock.advancetest_memory.py:cleanup_task_impl,write_only_use_starts_the_cleanup_taskclock.advance; the instance'scleanup_intervalis set to 10 ms withmonkeypatch, and the test polls the dict for up to 1 s of real timetest_cache_manager.py: 6 TTL tests;state/test_manager.py::test_get_state_metadata_after_expireclock.advance, orclock.waitfor the tests also parametrized with Redissession/test_jwt.py::test_jwt_expiration_enforcedexpagainst the real time, so the session is created 5 s in the past on the clock. Its backend entry and the session itself are still current on the clock, so only the token check can reject it.backends/test_memcached.py::test_memcached_tries_a_failed_server_again_after_the_dead_timeout_DEAD_TIMEOUTto 0.05 s and sleeps 0.1 s. A separate assertion pins the documented value of 1 s.session/test_models.py::test_session_renew,session/test_manager.py::test_sliding_expirationclock.advance(1).test_session_renewnow asserts>instead of>=.Sleeps that stay
clock.wait.test_redis.py:316(1.5 s), andtest_memcached.py:460(2 s) and:790(2.1 s). Memcached's expiry has one-second resolution.time.sleep(0.05)in the proxy tests);test_lock.py);test_cache_hit.py's slow handler;test_cache_manager.py.Tests
Runtime (
uv run pytest, without live servers): 25.5 s → 3.6 s, with 845 passed and 190 skipped both before and after. The suite also passed three times in random order.Mutation checks, full suite, with each mutation applied on its own:
_is_livereturnsTrue)test_backend_cleanup, and all 6CacheManagerTTL tests (25 tests fail in total)cleanup_task_impl,write_only_use_starts_the_cleanup_task,test_backend_cleanupCacheManagerignoresdefault_ttltest_add_uses_default_ttl,test_default_ttl_used_when_not_specifiedSession.renewkeeps the old expirytest_session_renewtest_sliding_expiration, plus 7 other sliding testsverify_expisFalsetest_jwt_expiration_enforceddead_timeoutto pymemcache_is_pastdisabled togethertest_get_state_metadata_after_expire[MemoryBackend]On the last row: that state test, like before this PR, passes if only one of the two expiry paths is broken, because either one hides the state.
Other checks
mypy --stricton the changed test files: clean.uv lock --check: passes.Live servers were not run by me.
CHANGELOG
None: test-only.