Skip to content

feat: CoverageStore contract test and configurable eviction - #15

Merged
lsmin3388 merged 3 commits into
reqover-labs:mainfrom
VedantMadane:fix/issue-14-coverage-store
Sep 2, 2026
Merged

feat: CoverageStore contract test and configurable eviction#15
lsmin3388 merged 3 commits into
reqover-labs:mainfrom
VedantMadane:fix/issue-14-coverage-store

Conversation

@VedantMadane

Copy link
Copy Markdown
Contributor

Summary

CoverageStore landed in 0.2.0 without a shared contract test or a choice of eviction policy (#14).

Changes

  • CoverageStoreContract abstract JUnit class in reqover-core tests covering:
    • flushed bucket appears in snapshots()
    • snapshots() returns a copy safe to iterate while another thread flushes
    • clear() empties the store
    • hits after flush do not mutate the retained snapshot
  • InMemoryCoverageStoreTest extends the contract (replacing the parts it subsumes) and keeps capacity / reject-when-full coverage
  • SnapshotEvictionPolicy: OLDEST_FIRST (default) and REJECT_WHEN_FULL
  • Properties: reqover.mvc.snapshot-eviction / reqover.webflux.snapshot-eviction (wired beside max-snapshots)
  • Integration guide documents both next to max-snapshots

Non-goals

No production database store.

Test plan

  • ./gradlew :reqover-core:test --tests io.reqover.core.InMemoryCoverageStoreTest
  • ./gradlew :reqover-spring-mvc:test --tests io.reqover.spring.mvc.ReqoverMvcAutoConfigurationTest

Fixes #14

Add a reusable CoverageStoreContract JUnit base that any store can
extend, run InMemoryCoverageStore against it, and let the in-memory
store choose oldest-first or reject-when-full eviction via
reqover.mvc.snapshot-eviction / reqover.webflux.snapshot-eviction.

Document both next to max-snapshots in the integration guide.

Fixes reqover-labs#14

Signed-off-by: Vedant Madane <6527493+VedantMadane@users.noreply.github.com>

@lsmin3388 lsmin3388 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you for this — and sorry for the slow first response. This is the first
contribution to Reqover from outside the team, and it landed as a better piece
of work than the issue asked for.

Things I want to name specifically, because they were the hard parts:

  • CoverageStoreContract tests the property that actually matters — that
    snapshots() hands back a list which stays stable while another thread is
    flushing into the store. That is the invariant a second implementation is
    most likely to get wrong, and it is not the obvious thing to test.
  • Reserving the slot with a CAS before adding in REJECT_WHEN_FULL is right.
    The naive version overshoots the bound under concurrent flushes.
  • hitsAfterFlushDoNotMutateStoredSnapshot asserts both halves — the stored
    snapshot frozen and the live bucket still recording. Good.

One change before merge, then two notes.

SnapshotEvictionPolicy.fromProperty is unreachable, and has a latent bug

Nothing in src/main calls it. Spring's relaxed binding already converts
reqover.mvc.snapshot-eviction=reject-when-full straight to the enum constant —
your own configuresSnapshotEvictionOnTheDefaultStore test proves it, since it
passes without fromProperty participating. So the method is exercised only by
parsesEvictionPolicyTokens.

That matters because it is a second, divergent parser for something Spring
already parses, and it has the Turkish-locale bug:

$ java -Duser.language=tr -Duser.country=TR ...
default locale : tr_TR
"OLDEST_FIRST".toLowerCase() -> oldest-fırst   // dotless ı
matches token  : false
with Locale.ROOT -> oldest-first -> true

On a JVM with a Turkish default locale, an OLDEST_FIRST token throws
"Unknown snapshot eviction policy".

Preferred fix: delete fromProperty and its test. Spring is already doing
this job, and one parser is better than two that can drift apart. If you would
rather keep it for callers constructing the store outside Spring, that is a
reasonable argument — in that case make it toLowerCase(Locale.ROOT) and say in
the Javadoc that it is for non-Spring callers, so the next person does not
wonder why there are two paths.

Either way, please keep the accepted-token set narrow. oldest/reject as
aliases will outlive the moment they seemed convenient.

Notes, no action needed from you

  • Korean docs. docs/17_integration_guide.ko.md needs the same two rows and
    the retention paragraph. Per CONTRIBUTING that is explicitly not your job — a
    maintainer follows up on the other language. We will take it.
  • Stray newline. ReqoverMvcConfiguration.java and
    ReqoverWebFluxConfiguration.java each pick up a trailing blank line at EOF.
    Worth dropping while you are in there.

CI was waiting on maintainer approval because this is a first contribution from
a fork; I have approved it, so the checks are running now.

One last thing: #14 asked for the contract test, and you also worked out
where the policy had to be threaded through both adapters. If you want another,
#2 (Servlet async
attribution) is the most interesting open problem in the repository — no
pressure at all.

Spring's relaxed binding already turns `oldest-first` and
`reject-when-full` into the enum constants, so `fromProperty` had no
caller in main and was a second parser that could drift from the first.
It also lower-cased with the default locale, which turns `OLDEST_FIRST`
into `oldest-fırst` under tr_TR and throws.

While finishing the review of reqover-labs#15: the Korean integration guide gets the
same two property rows and the retention paragraph, the changelog
records the feature under Unreleased, the English guide stops attributing
the policy to 0.2.0, and the two configuration classes lose a trailing
blank line.
@lsmin3388

Copy link
Copy Markdown
Contributor

Following up on the review above. It has been just over a week, and I would rather have this in main than keep it waiting, so I pushed the one requested change to your branch myself (the PR allows maintainer edits). Your commit is untouched; mine sits on top of it.

What the follow-up commit does:

  • Deletes SnapshotEvictionPolicy.fromProperty and parsesEvictionPolicyTokens. Spring's relaxed binding is now the only parser, and the Turkish-locale case goes away with it. The enum's Javadoc says so, so nobody re-adds one later.
  • Drops the trailing blank line in the two configuration classes.
  • Adds the two property rows and the retention paragraph to the Korean guide (docs/17_integration_guide.ko.md), as promised.
  • Records the feature in CHANGELOG.md under Unreleased, with credit to you.
  • One wording fix in the English guide: the policy is not in 0.2.0 (it ships in the next release), so the sentence no longer says it is.

Build is green locally with 130 tests. Merging once CI agrees. Thank you again — this was a good first outside contribution, and the contract test in particular will keep earning its place.

@lsmin3388
lsmin3388 merged commit 3207397 into reqover-labs:main Sep 2, 2026
4 checks passed
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.

Give CoverageStore a reusable contract test and a configurable eviction policy

2 participants