Skip to content

Commit 06d5799

Browse files
twofclaude
andcommitted
perf: default pool admission to featureOwnership (culling), not everyDiscovery
The library default for `MutationScheduler.weightedPool(admission:)` was `everyDiscovery` — every strategy-accepted input joins the pool and nothing ever leaves. On stlc that floods the pool with ~2400 entries whose median wire size is 421 chars (max 1469); mutating one of those giants rarely lands on the relevant node. Flip the default to `featureOwnership` (libFuzzer-style REDUCE): each feature is owned by the smallest witness, larger owners are evicted. Measured on the clean stlc baseline this collapses the live pool to ~20 entries of median size 49, and the mean *executed* term shrinks 5x (323 -> 65 chars). On the hard de Bruijn mutant shift_var_leq the flip finds the bug 20/20 at median 4.0s vs everyDiscovery's 17/20 at 6.7s — better detection AND speed, the direct payoff of smaller, better-targeted mutation parents. Callers that want the old keep-everything behavior still pass `admission: .everyDiscovery` explicitly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4a71968 commit 06d5799

2 files changed

Lines changed: 18 additions & 1 deletion

File tree

Sources/PropertyTestingKit/Fuzzing/Scheduler/MutationScheduler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public struct MutationScheduler: Sendable {
4949
/// inputs from how many of them may stay — without it, a fine
5050
/// vocabulary silently raises the population ceiling.
5151
public static func weightedPool(
52-
admission: PoolAdmission = .everyDiscovery,
52+
admission: PoolAdmission = .featureOwnership,
5353
policies: @escaping @Sendable () -> [any PoolPlugin] = { [] },
5454
burstLength: Int = 16,
5555
focusOnInsert: Bool = true,

Tests/PropertyTestingKitTests/Fuzzing/WeightedPoolCoreTests.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,23 @@ struct WeightedPoolCoreTests {
9595
#expect(core.next() == .mutate(id: 0))
9696
}
9797

98+
@Test("The default weightedPool admission culls: a same-feature, non-smaller redundant input is rejected")
99+
func defaultAdmissionCulls() {
100+
// The library default is feature ownership (REDUCE), not everyDiscovery:
101+
// an unbounded pool of every accepted input bloats with large entries
102+
// whose features a smaller input already owns. Build the core straight
103+
// from the public default so this pins the default itself.
104+
let core = MutationScheduler.weightedPool().makeCore()
105+
// First input owns edges {1,2} (size 2) — admitted as id 0.
106+
#expect(core.observe(PoolIterationOutcome(
107+
source: .generated, newCoverage: SparseCoverage(indices: [1, 2]))) == 0)
108+
// Second input: SAME features, SAME size — owns nothing new, steals
109+
// nothing (ties don't steal), so it is rejected (nil). Under the old
110+
// everyDiscovery default it would have been admitted as id 1.
111+
#expect(core.observe(PoolIterationOutcome(
112+
source: .generated, newCoverage: SparseCoverage(indices: [1, 2]))) == nil)
113+
}
114+
98115
@Test("Admitted entries get sequential stable IDs")
99116
func sequentialIDs() {
100117
let core = makeCore()

0 commit comments

Comments
 (0)