Skip to content

test: 4.0 cleanup and missing coverage (#576) - #595

Merged
lesnik512 merged 2 commits into
mainfrom
md-576
Oct 5, 2026
Merged

lesnik512 merged 2 commits into
mainfrom
md-576

Conversation

@lesnik512

@lesnik512 lesnik512 commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

Closes #576.

Summary

The issue was written against 9cd88e7. Ten PRs landed since, so I re-checked every bullet against current main (634b5d5).

  • pyproject.toml sets filterwarnings = ["error"]. The two test_parse_creator cases with unresolvable annotations moved to their own parametrized test that wraps the call in pytest.warns(UserWarning).
  • Removed the hand-rolled "does not warn" blocks for warnings that no longer exist: two in test_container.py, two in test_context_provider.py. With the guard gone, test_set_context_provider_direct_resolve_does_not_warn was a copy of test_context_provider, so I deleted it.
  • The two bare TypeError asserts for removed constructor arguments now have match=.
  • Deleted duplicate close/open tests in test_container.py: test_explicit_open_after_close_does_not_warn and test_construct_then_close_then_reuse_raises_until_reopened (both covered by test_open_reopens_closed_container), and test_build_child_off_closed_parent_is_allowed (a subset of test_child_built_off_closed_parent_raises_only_when_the_parent_resolves).
  • Replaced the RLock acquire/release check with test_warm_cached_resolve_does_not_wait_for_the_lock. The main thread holds the container lock while a worker resolves a warm cached value through a child container, and the worker must finish within a 5 second join timeout.
  • New coverage: test_suggest_skips_bound_type_without_a_name (a ForwardRef bound type, suggester.py 53->40) and test_sync_finalizer_returning_a_future_raises_in_sync_close_then_recovers (a non-coroutine awaitable from a sync finalizer, cache_registry.py 72->74).
  • Removed 258 open() calls on containers that had just been built and never closed. open() only clears the closed flag, so those calls did nothing. I used an AST pass that drops x.open() only when x was assigned from a call earlier in the same block and nothing in between could close it. test_open_never_validates keeps its call because open() is what it tests.
  • Fixed the stale OMIT, not UNWIRABLE docstring in test_factory.py.
  • Renamed tests/providers/test_singleton.py to test_cached_factory.py. Its two close/reopen tests moved to test_container.py. The concurrency test no longer sleeps to widen the race (see below) and dropped @pytest.mark.repeat(10), since it is deterministic now.

Branch coverage (pytest --cov=modern_di --cov-branch): 5 partial branches before, 3 after. Test count went from 615 to 604. Most of the drop is the removed repeat(10). The 100% line coverage gate is unchanged and passes.

Design decisions

  • test_concurrent_cache_misses_create_once puts a threading.Barrier(4, timeout=5) inside a transient dependency of the cached factory. Dependencies resolve outside the lock, so all four threads are past the unlocked cache check before any of them can create, and the test asserts the creator ran once. If dependency resolution ever moved under the lock, the barrier times out and the test fails with BrokenBarrierError after 5 seconds instead of hanging. I checked this by moving resolve() under the lock locally.
  • I checked the lock test the same way: making the compiled resolver read the cache under target._lock turns it red after the 5 second join timeout.
  • The two moved close/reopen tests are merged into test_reopen_rebuilds_a_value_that_close_cleared, which uses a local group. Their clear_cache=False half was already covered by test_app_cached_factory, so it was dropped. Test names and provider attributes in test_cached_factory.py now say "cached" instead of "singleton".
  • The catch_warnings blocks that guard warnings the library still emits (test_factory.py skip_creator_parsing with a bound type, test_types_parser.py union return type with a bound type) stay as they are. They state what the test checks and still hold under the global filter.

Skipped bullets

  • "Closing a root resets overrides" was reversed in feat!: closing a root container keeps overrides (#580) #584. Root close now keeps overrides, and test_override_survives_root_close_sync_and_reopen and _async_ already cover that, so there is no test to add.
  • Dead branches "listed in the types_parser and refactor issues" belong to those issues and are not touched here.

Open questions

Three partial branches remain, all outside this issue's list:

Test plan

  • just lint and just lint-ci
  • just test-ci (100% line coverage)
  • pytest --cov=modern_di --cov-branch: 3 partial branches, down from 5
  • New lock and concurrency tests run 200 times each with no failures
  • Both tests fail, without hanging, when the lock behaviour is broken locally
  • mkdocs build --strict

@github-actions github-actions Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Details
Benchmark suite Current: baa1de0 Previous: d653e35 Ratio
benchmarks/test_guard_by_type.py::test_g16_resolve_by_type 6494550.682298163 iter/sec (stddev: 5.851735687942965e-9) 3047679.6993326377 iter/sec (stddev: 7.68398095030079e-9) 0.47
benchmarks/test_guard_by_type.py::test_g17_resolve_by_type_large_registry 6598767.1127743665 iter/sec (stddev: 4.293556413666038e-9) 2865716.75416891 iter/sec (stddev: 1.0895383143788401e-8) 0.43
benchmarks/test_guard_cold.py::test_g8_cold_first_resolve 37184.40656966034 iter/sec (stddev: 0.00004473324922685243) 20141.29411413181 iter/sec (stddev: 0.000060512136922370814) 0.54
benchmarks/test_guard_cold.py::test_g8b_cold_first_resolve_cached 30381.475639833036 iter/sec (stddev: 0.00012256014172113596) 16227.953564347019 iter/sec (stddev: 0.00012174055036984476) 0.53
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[1] 873.3331984560833 iter/sec (stddev: 0.00004723603845158256) 394.7618303242616 iter/sec (stddev: 0.00007930621161047323) 0.45
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[2] 808.8569543873873 iter/sec (stddev: 0.0000419013861282321) 370.305000465928 iter/sec (stddev: 0.00005600518613977446) 0.46
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[4] 750.8059377924143 iter/sec (stddev: 0.00002616986229979413) 343.96319541635654 iter/sec (stddev: 0.00005469358116412396) 0.46
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[1] 3679.4575547426684 iter/sec (stddev: 0.00010551241219228818) 1910.842569787941 iter/sec (stddev: 0.000162661718918218) 0.52
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[2] 2829.1033444572854 iter/sec (stddev: 0.00012990435949108989) 1470.0730071324613 iter/sec (stddev: 0.00019458681356333786) 0.52
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[4] 1951.5797769946635 iter/sec (stddev: 0.00012682315560502754) 1024.2656207545813 iter/sec (stddev: 0.00022810651173013014) 0.52
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[1] 3661.522400603589 iter/sec (stddev: 0.00012312199572803097) 1862.3647861059314 iter/sec (stddev: 0.00018649465933071155) 0.51
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[2] 1746.6143481918753 iter/sec (stddev: 0.001982887164665335) 1296.7650208188347 iter/sec (stddev: 0.00019925902064693113) 0.74
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[4] 1601.192375939192 iter/sec (stddev: 0.00013268703447170884) 756.3003458505401 iter/sec (stddev: 0.0012513716793371842) 0.47
benchmarks/test_guard_lifecycle.py::test_g6_build_child_container 1772948.6596151898 iter/sec (stddev: 1.363381581178034e-7) 931547.817002775 iter/sec (stddev: 1.3562406349897537e-7) 0.53
benchmarks/test_guard_lifecycle.py::test_g6b_build_child_container_auto_scope 1629118.2735386593 iter/sec (stddev: 1.659587571492171e-8) 862077.9393011483 iter/sec (stddev: 4.603612433079451e-8) 0.53
benchmarks/test_guard_lifecycle.py::test_g7_request_lifecycle_batch 4871.44759639082 iter/sec (stddev: 0.000007747005260560703) 2434.0141014239816 iter/sec (stddev: 0.000010514100178307428) 0.50
benchmarks/test_guard_lifecycle.py::test_g7c_event_loop_floor_control 115056.85223269826 iter/sec (stddev: 0.0000015689318403539552) 57790.66542745276 iter/sec (stddev: 0.000002017861486579691) 0.50
benchmarks/test_guard_lifecycle.py::test_g13_teardown_at_scale 84665.72055428299 iter/sec (stddev: 7.710670161675587e-7) 42337.38482583684 iter/sec (stddev: 0.00000195560890905176) 0.50
benchmarks/test_guard_lifecycle.py::test_g13b_teardown_at_scale_async_no_finalizers 1099.7571501121174 iter/sec (stddev: 0.00001224649302786763) 527.9760349185017 iter/sec (stddev: 0.00005864358048386608) 0.48
benchmarks/test_guard_resolve.py::test_g1_transient_resolve 4456031.611958201 iter/sec (stddev: 1.9063046161860242e-8) 2056805.8935774143 iter/sec (stddev: 2.984496256464475e-8) 0.46
benchmarks/test_guard_resolve.py::test_g2_cached_resolve 7372067.89824232 iter/sec (stddev: 4.564719075047762e-9) 2997882.9250505473 iter/sec (stddev: 9.425210929455026e-9) 0.41
benchmarks/test_guard_resolve.py::test_g3_deep_chain 1548691.0598629222 iter/sec (stddev: 2.487485711928981e-8) 693179.6082788195 iter/sec (stddev: 5.861598282461815e-8) 0.45
benchmarks/test_guard_resolve.py::test_g4_wide_resolve 879820.2492005874 iter/sec (stddev: 3.150982717261068e-7) 367969.91183531785 iter/sec (stddev: 4.2236427939196184e-7) 0.42
benchmarks/test_guard_resolve.py::test_g5_cross_scope 4063591.550518981 iter/sec (stddev: 1.8174168272849375e-8) 1819533.731741305 iter/sec (stddev: 2.5719724263261836e-8) 0.45
benchmarks/test_guard_resolve.py::test_g9_context_resolve 3056283.9911284638 iter/sec (stddev: 1.3644593654572645e-7) 1293317.0685799515 iter/sec (stddev: 1.5552437513477855e-7) 0.42
benchmarks/test_guard_resolve.py::test_g12_override_active_resolve 1544801.6420670622 iter/sec (stddev: 2.352957490628623e-8) 659745.3442362943 iter/sec (stddev: 5.5669749410889215e-8) 0.43
benchmarks/test_guard_resolve.py::test_g18_alias_hop 7203067.065933864 iter/sec (stddev: 5.889923100113078e-9) 3089956.8954028534 iter/sec (stddev: 1.4619977910560272e-8) 0.43
benchmarks/test_guard_validate.py::test_g10_validate_deep_chain 54398.70661629766 iter/sec (stddev: 0.000012601078788198747) 26681.65084469777 iter/sec (stddev: 0.000039429132466512916) 0.49
benchmarks/test_guard_validate.py::test_g11_validate_wide 31951.128235850723 iter/sec (stddev: 0.000015521816857705598) 15769.247632211958 iter/sec (stddev: 0.00002931769417972418) 0.49

This comment was automatically generated by workflow using github-action-benchmark.

@lesnik512
lesnik512 merged commit ed03e40 into main Oct 5, 2026
9 checks passed
@lesnik512
lesnik512 deleted the md-576 branch October 5, 2026 09:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tests: 4.0 cleanup and missing coverage

1 participant