test: property tests for store structures (+ fix PathRefStore ref leak)#35
Open
danReynolds wants to merge 1 commit into
Open
test: property tests for store structures (+ fix PathRefStore ref leak)#35danReynolds wants to merge 1 commit into
danReynolds wants to merge 1 commit into
Conversation
…thRefStore ref leak Adds randomized model-based property tests for PathRefStore, ValueStore, and ValueRefStore. Each drives random operation sequences against the real store and a from-scratch reference model, asserting they agree and that the structures empty out fully. The seed and op log are printed on failure for replay. The harness immediately found a PathRefStore bug. In `_dec`, when a path had become a transient node (it and a descendant were inc'd, then the descendant dec'd), dec'ing the path itself hit the Map branch, which signalled removal via a return value — but the top-level `dec` ignores that return, so the node lingered: `has` stayed true and the ref count leaked. The int branch directly above already handled its analogous case correctly. Fixed to remove the node in place (or drop just its ref key if it still routes to live descendants), symmetric with the int branch. Reachable with only well-formed inc/dec pairs, e.g. inc(c); inc(c__c); dec(c__c); inc(a); dec(c). A focused regression test pins the scenario.
There was a problem hiding this comment.
Pull request overview
This PR adds model-based property tests for the library’s path-keyed store structures (PathRefStore, ValueStore, ValueRefStore) and applies a fix to PathRefStore._dec intended to prevent a ref-count / node-linger leak discovered by the new harness.
Changes:
- Add randomized, model-based property tests covering
inc/decforPathRefStoreandwrite/delete(recursive)forValueStoreandValueRefStore. - Add a focused regression test for the transient-node decrement scenario in
PathRefStore. - Update
PathRefStore._decto remove a map child node in-place when its last reference is released.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
test/core/store/store_property_test.dart |
Introduces model-based randomized property tests for the three store types. |
test/core/store/path_ref_store_test.dart |
Adds a regression test to pin the transient-node decrement/ref-leak scenario. |
lib/store/path_ref_store.dart |
Adjusts _dec behavior for map children when releasing the last reference. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+112
to
124
| final Map child = node[segment]; | ||
| if (child[_refKey] == 1) { | ||
| // Releasing the last reference to this node. If it has no descendants, | ||
| // remove it entirely; otherwise it still routes to live descendants, so | ||
| // drop only its ref key and keep it as a transient node. | ||
| if (child.length == 1) { | ||
| node.remove(segment); | ||
| } else { | ||
| child.remove(_refKey); | ||
| } | ||
| } else { | ||
| node[segment][_refKey]--; | ||
| child[_refKey]--; | ||
| } |
3 tasks
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
Adds model-based property tests for the three path-keyed store structures —
PathRefStore,ValueStore, andValueRefStore— and fixes aPathRefStoreref-count leak the harness found on its first run.These stores are where the audit's bugs clustered (ref counting, tree restructuring, recursive delete): invariant-maintaining state machines whose bugs hide in specific operation sequences, not in control flow. Example-based tests miss those; property tests explore the sequence space.
The harness
Each test drives a randomized sequence of operations against the real store and a trivial reference model, then asserts they agree after every step and that the structure empties out completely. The model is recomputed from scratch each step, so it's an independent oracle for the stores' incremental bookkeeping. A small segment/value alphabet is used deliberately to force path collisions and shared prefixes. On failure, the seed and full operation log are printed for replay.
inc/dec(dec only targets inc'd paths, mirroring real usage); assertshas/isEmpty.write/ recursivedelete; assertsget/hasValue/hasPath/getChildValues/isEmpty.write/ recursivedelete; asserts the__refsvalue→count aggregation in every subtree (getRefs/extractValues) matches the values actually present — i.e. the aggregation maintained across the recursive-delete ref propagation.Scope:
writeand recursivedelete. Non-recursivedeletehas subtler semantics (prunes a node's immediate values while keeping deeper descendants) and is left for a dedicated model.The bug it found
In
PathRefStore._dec, when a path had become a transient node — it and a descendant were both inc'd, then the descendant was dec'd — dec'ing the path itself reached theMapbranch, which signalled removal via a return value that the top-leveldecignores. So the node lingered:has(path)stayedtrueand the ref count leaked (the node was never removed and the parent's ref was never decremented). Theintbranch immediately above already handled its analogous case correctly with a directnode.remove(segment).Effect in the library:
PathRefStorebacks observer/query dependency tracking, so a staletruefromhascauses spurious rebroadcasts and a slow ref-count/memory leak as dependencies churn.Reachable with only well-formed
inc/decpairs:Fix: remove the node in place when its last reference is released (or drop only its ref key if it still routes to live descendants), symmetric with the
intbranch. A focused regression test pins the exact scenario.Test plan
flutter test test/core/store/store_property_test.dart— 3 property tests × 200 seeds each, green_dec(caught it on seed 0)flutter test test/core— green (110 tests; the one intermittent failure seen is the pre-existing broadcast-timing flake, in the broadcast path, unrelated to_dec— confirmed by 5+ clean reruns)Generated by Claude Code