From ffc6b01bec0f1881cd209c526aff088df5592fc2 Mon Sep 17 00:00:00 2001 From: Shihyu Ho Date: Thu, 16 Jul 2026 14:15:17 +0800 Subject: [PATCH] test(mapper): strengthen concurrency and nested-AND coverage Make ReflectionDatabindTest.fireOnlyOnce launch 32 threads gated on a start barrier so they genuinely contend on the AtomicBoolean CAS + CountDownLatch dedup in getFieldValue(), while still asserting the inner reflective read runs exactly once per Databind. Fix the NestedInNestedAnd.name annotation from @NestedSpec to @Spec (mirroring NestedInNestedOr) so the depth-3 AND scenario actually composes a predicate, and extend allAnd's assertions to a full three-level Conjunction tree symmetric to allOr's Disjunction tree. Refs #182 Co-authored-by: Claude Opus 4.8 (1M context) --- .../spec/NestedSpecificationResolverTest.java | 7 ++++-- .../data/jpa/spec/ReflectionDatabindTest.java | 22 ++++++++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/mapper/src/test/java/tw/com/softleader/data/jpa/spec/NestedSpecificationResolverTest.java b/mapper/src/test/java/tw/com/softleader/data/jpa/spec/NestedSpecificationResolverTest.java index ff62406..3bb85b2 100644 --- a/mapper/src/test/java/tw/com/softleader/data/jpa/spec/NestedSpecificationResolverTest.java +++ b/mapper/src/test/java/tw/com/softleader/data/jpa/spec/NestedSpecificationResolverTest.java @@ -111,7 +111,10 @@ void allAnd() { .extracting("specs", LIST) .hasSize(2); depth1.first().isInstanceOf(Equals.class); - depth1 + var depth2 = + depth1.element(1).isInstanceOf(Conjunction.class).extracting("specs", LIST).hasSize(2); + depth2.first().isInstanceOf(Equals.class); + depth2 .element(1) .isInstanceOf(Conjunction.class) .extracting("specs", LIST) @@ -389,7 +392,7 @@ public static class NestedAnd { @AllArgsConstructor public static class NestedInNestedAnd { - @NestedSpec String name; + @Spec String name; } @Builder diff --git a/mapper/src/test/java/tw/com/softleader/data/jpa/spec/ReflectionDatabindTest.java b/mapper/src/test/java/tw/com/softleader/data/jpa/spec/ReflectionDatabindTest.java index 1fff467..180ba22 100644 --- a/mapper/src/test/java/tw/com/softleader/data/jpa/spec/ReflectionDatabindTest.java +++ b/mapper/src/test/java/tw/com/softleader/data/jpa/spec/ReflectionDatabindTest.java @@ -29,6 +29,7 @@ import java.util.Collection; import java.util.Optional; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.CyclicBarrier; import lombok.AllArgsConstructor; import lombok.SneakyThrows; import org.junit.jupiter.api.Test; @@ -49,17 +50,28 @@ void fireOnlyOnce() { assertThat(databind).hasSize(5); - var numberOfThreads = 1; + // Release every thread at the same instant so multiple threads race into + // getFieldValue() on the *same* Databind instances and genuinely contend on + // the AtomicBoolean CAS + CountDownLatch dedup. + var numberOfThreads = 32; var service = newFixedThreadPool(numberOfThreads); - var latch = new CountDownLatch(numberOfThreads); + var startBarrier = new CyclicBarrier(numberOfThreads); + var done = new CountDownLatch(numberOfThreads); for (int i = 0; i < numberOfThreads; i++) { service.submit( () -> { - databind.forEach(Databind::getFieldValue); - latch.countDown(); + try { + startBarrier.await(); + databind.forEach(Databind::getFieldValue); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + done.countDown(); + } }); } - latch.await(); + done.await(); + service.shutdown(); databind.forEach( bind -> {