test: isolate entity-graph query-count assertion from parallel-execution pollution#341
Conversation
…tion UserRepositoryEntityGraphTest.shouldLoadRolesAndPrivilegesInBoundedQueryCountViaEntityGraphFinder was intermittently failing (GH-337). It read the SessionFactory-global Hibernate Statistics prepared-statement counter, which is shared across @DataJpaTest classes in the cached Spring context and mutated by tests running concurrently on other threads under JUnit 5 parallel execution. The counter is not transactional, so @DataJpaTest rollback does not isolate it, producing counts higher than the asserted bound. Replace the global counter with a thread-local StatementInspector (StatementCountInspector) registered via spring.jpa.properties.hibernate.session_factory.statement_inspector. Each parallel test runs on its own thread and JDBC connection, so a thread-local statement count is naturally isolated from concurrent pollution. The assertion now uses isBetween(1, 3): the upper bound preserves the N+1 regression guard, and the lower bound fails loudly if the inspector is ever unregistered rather than silently passing as a false green.
There was a problem hiding this comment.
Pull request overview
This pull request fixes a flaky JPA repository test by replacing a SessionFactory-global Hibernate Statistics counter (polluted under JUnit 5 parallel execution) with a per-thread statement counter implemented via a Hibernate StatementInspector.
Changes:
- Add
StatementCountInspector(ThreadLocal-backed) to count prepared statements per test thread. - Register the inspector in the test Spring Boot configuration (
application-test.properties). - Update
UserRepositoryEntityGraphTestto reset/assert against the per-thread counter (isBetween(1, 3)).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/test/resources/application-test.properties | Registers the Hibernate statement_inspector for the test profile. |
| src/test/java/com/digitalsanctuary/spring/user/test/config/StatementCountInspector.java | Introduces the per-thread statement-counting StatementInspector. |
| src/test/java/com/digitalsanctuary/spring/user/persistence/repository/UserRepositoryEntityGraphTest.java | Switches the bounded-query assertion to use the per-thread counter instead of global Statistics. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * stable, per-test measurement that is immune to concurrent pollution. A single instance is registered on the shared test {@code SessionFactory} via | ||
| * the {@code hibernate.session_factory.statement_inspector} property (see {@code application-test.yml}); the counter state is {@code static} so the | ||
| * inspecting instance created by Hibernate and the assertions in a test share the same per-thread counter. |
|
Review Nice, tightly-scoped fix for the flaky What the PR does
Strengths
Minor issues
Security / performance Test coverage Overall: solid, well-reasoned fix. Recommend fixing the |
ReviewOverviewFixes the flaky Code quality
Potential issues / suggestions
SecurityNo concerns — this is test-only infrastructure, not shipped in the published library artifact ( Test coverageThis is the test fix; no additional coverage needed. The approach (fixing flakiness via a mechanism that's structurally immune to cross-thread pollution, rather than e.g. adding retries or Overall: solid, well-reasoned fix for a real flakiness root cause. The two items above ( |
… tradeoffs Address non-blocking PR review feedback on #341: - Add StatementCountInspectorTest covering reset/count/inspect semantics and cross-thread isolation (an interleaved two-thread scenario proving one thread's reset/count cannot perturb another's), locking in the mechanism independently of its one consumer test. - Document that the inspector is registered profile-wide by design and that the per-statement ThreadLocal increment is an inert, negligible overhead for tests that never read the counter. - Note that the ThreadLocal value is intentionally left set (not removed) for the pooled worker thread's lifetime, since every measurement calls reset() first.
|
Thanks for the thorough reviews. Addressed the feedback in Fixed
Verified (no change needed)
Declined (with rationale)
Full suite green (991 tests, 0 failures). |
|
Review: test: isolate entity-graph query-count assertion from parallel-execution pollution Nice root-cause fix — moving from the SessionFactory-global Strengths
Minor points worth considering
Test coverage Security / performance Overall: solid, well-reasoned flaky-test fix with good accompanying test coverage. Approving from a code-quality/correctness standpoint. |
Summary
Fixes the flaky
UserRepositoryEntityGraphTest.shouldLoadRolesAndPrivilegesInBoundedQueryCountViaEntityGraphFindertest reported in #337.The test read the
SessionFactory-global HibernateStatistics.getPrepareStatementCount()counter to guard against N+1 query regressions. Under JUnit 5 parallel execution,@DataJpaTestclasses share a single cached Spring context — and therefore oneSessionFactoryand oneStatisticsobject. Tests running concurrently on other threads incremented that shared, non-transactional counter betweenstats.clear()and the assertion read, so the observed count intermittently exceeded the bound (e.g. 7 vs. ≤3) even though the actual per-query behavior was correct.Changes
StatementCountInspector— a HibernateStatementInspectorthat records prepared-statement counts in aThreadLocal. Each parallel test runs on its own thread and JDBC connection, so the count is naturally isolated from concurrent pollution.spring.jpa.properties.hibernate.session_factory.statement_inspectorinapplication-test.properties(co-located with the effective H2 JPA config).StatementCountInspector.reset()before the measured query and assert on the per-thread count.isBetween(1, 3): the upper bound preserves the original N+1 regression guard; the new lower bound fails loudly if the inspector is ever unregistered instead of silently passing at 0.Testing
./gradlew test— full suite green (987 tests, 0 failures).ThreadLocalcounter cannot be mutated by tests on other threads.Closes #337