test: speed up slow loopback tests (closes #1700)#1703
Merged
Conversation
Drops full-suite runtime from ~85s to ~46s by removing real-time waits from tests that don't need them on a loopback socket. - Apply the `quick_timing` fixture to 13 register/announce-heavy tests so probe/announce intervals shrink from 500ms/225ms/125ms to 10ms each. The tests pin behaviour, not the RFC 6762 timing constants, so the production values stay in place for everything outside the fixture's `with patch.object(...)` block. - Introduce `_backdate_cache(zc, ms=1100)` in `tests/__init__.py`. `test_async_updates_from_response` and the inline backdate in `test_cache_flush_bit` use it to satisfy RFC 6762 §10.2's "received more than one second ago" check without `time.sleep`. The helper iterates `store.values()`, not the inner dict directly — when a record is re-added with an equal hash, the dict key stays the original object while the value gets the newer one, so mutating the keys would touch stale objects no one reads. - `test_service_browser_expire_callbacks` and `test_reaper` re-add records via `_async_set_created_ttl(past, 1)` so the expire heap picks up the new `when`; mutating `record.created` alone leaves the heap entry pointing at the original future expiration time and the reaper never wakes the record up. - `test_service_info_async_request` caps the post-unregister `async_get_service_info` to 200ms (the service is gone, the default 3000ms wait is pure overhead) and tightens the `_is_complete=False` TOCTOU-race loop to 1500ms — enough for the initial `_LISTENER_TIME + random_delay` (≈220-320ms) plus margin for the loopback response. - `test_info_asking_default_is_asking_qm_questions_after_the_first_qu` drops its forced-loop timeout from 1200ms to 500ms (covers the QU at t=0 and the QM at t≈_LISTENER_TIME + max random delay). - `test_register_and_lookup_type_by_uppercase_name` replaces `time.sleep(1)` with a 50×20ms poll on the cache. - `test_integration_with_listener_class` drains pending multicast responses on the registrar instead of sleeping for them, same pattern as #1701. All 335 tests still pass and all changed tests are stable across five back-to-back runs.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1703 +/- ##
=======================================
Coverage 99.76% 99.76%
=======================================
Files 33 33
Lines 3410 3410
Branches 464 464
=======================================
Hits 3402 3402
Misses 5 5
Partials 3 3 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
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 #1700. Drops full-suite runtime from ~85s to ~46s by
removing real-time waits from tests that don't need them on a
loopback socket. Behaviour under test is unchanged — only the
test setup is shortened.
Details
quick_timingfixture applied to 13 register/announce-heavytests (
test_register_service_with_custom_ttl,test_logging_packets,test_ptr_optimization,test_qu_response,test_async_context_manager,test_integrationintest_asyncio.py,test_asking_default_is_asking_qm_questions_after_the_first_qu,test_ttl_refresh_cancelled_rescue_query, bothtest_legacy_record_update_listeners,test_close_zeroconf_without_browser_before_start_up_queries,test_close_zeroconf_without_browser_after_start_up_queries).The fixture shrinks
_CHECK_TIME/_REGISTER_TIME/_UNREGISTER_TIMEfrom RFC 6762 §8.1/§8.3 production values(500ms / 225ms / 125ms) to 10ms each via
patch.object, sonothing outside the
withblock is affected._backdate_cache(zc, ms=1100)new helper intests/__init__.py.Replaces real-time sleeps that exist only to satisfy RFC 6762 §10.2's
"received more than one second ago" check (the cache flush logic in
DNSCache.async_mark_unique_records_older_than_1s_to_expire):test_async_updates_from_response: 3 ×time.sleep(1.1)→ 3 ×_backdate_cache(zc)(3.3s → 0.03s).test_cache_flush_bit:await asyncio.sleep(1.1)→ inlinebackdate of the TTL=1 records (1.1s → 0.0s).
The helper iterates
store.values()rather than the inner dictdirectly because
DNSCache._async_addre-keys the dict withstore[record] = record: when a new record with equal hasharrives, the key stays the original object but the value
becomes the new record. Mutating the keys would update stale
objects no one reads.
Reaper-driven tests (
test_reaper,test_reaper_aborts_when_done,test_service_browser_expire_callbacks) re-add records viacache._async_set_created_ttl(record, past, 1)so the cache'sexpire heap picks up the updated
when = created + ttl*1000.Mutating
record.createdalone leaves the heap entry pointingat the original future expiration time and the reaper never wakes
the record up. Combined with the
_CACHE_CLEANUP_INTERVAL=0.01spatch these tests already had, the reaper now fires within ~10ms.
test_service_info_async_requestcaps the post-unregisterasync_get_service_infototimeout=200(the service is gone,the default 3000ms is pure overhead) and tightens the
_is_complete=FalseTOCTOU-race loop from 3000ms to 1500ms.1500ms covers the initial
_LISTENER_TIME + random_delay(≈220-320ms) and leaves plenty of margin for the loopback
response to land before the loop times out.
test_info_asking_default_is_asking_qm_questions_after_the_first_qudrops its forced-loop timeout from 1200ms to 500ms — enough for
the QU query at t=0 and the QM query at t≈
_LISTENER_TIME+max random delay (320ms).
test_register_and_lookup_type_by_uppercase_namereplacestime.sleep(1)with a 50×20ms poll on the cache (same patternas
test_sending_unicastintest_core.py).test_integration_with_listener_classdrains the registrar'sout_queue/out_delay_queueinstead oftime.sleep(0.5)for"multicast timers to expire" — same pattern as test: drop pending multicast responses before TOCTOU assertion #1701.
The remaining ~2-3s tests in the slowest-20 are intrinsic-timing:
_DUPLICATE_QUESTION_INTERVAL-bound queries(
test_we_try_four_times_with_random_delay,test_get_info_suppressed_by_question_history,test_get_info_partial), the multicast aggregation windows(
test_response_aggregation_timings,…_multiple), and the 1sasyncio.run_coroutine_threadsafe(...).result(1)guard intest_shutdown_loop._DUPLICATE_QUESTION_INTERVALis a cdefconstant in
_services/info.pxdand_history.pxd, so it can'tbe
patch.object-ed in Cython mode.Test plan
codespell, pyupgrade).
now drop off the list (most go from ~1.9-3.3s down to
0.03-0.4s).