diff --git a/pom.xml b/pom.xml index b45dc9e8d..87a97b4ec 100644 --- a/pom.xml +++ b/pom.xml @@ -442,6 +442,9 @@ maven-surefire-plugin ${maven-surefire-plugin.version} + + **/CopyOnWriteMapTest.* + **/FakerRepeatabilityIntegrationTest.java diff --git a/src/test/java/net/datafaker/internal/helper/CopyOnWriteMapTest.java b/src/test/java/net/datafaker/internal/helper/CopyOnWriteMapTest.java new file mode 100644 index 000000000..4943eaf00 --- /dev/null +++ b/src/test/java/net/datafaker/internal/helper/CopyOnWriteMapTest.java @@ -0,0 +1,64 @@ +package net.datafaker.internal.helper; + +import net.datafaker.Faker; +import net.datafaker.providers.base.BaseFaker; +import net.datafaker.providers.base.IdNumber; +import net.datafaker.service.FakeValuesService; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Test; + +import java.util.Base64; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Random; +import java.util.WeakHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static java.util.concurrent.Executors.newFixedThreadPool; +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; + +class CopyOnWriteMapTest { + private static final Logger log = Logger.getLogger(CopyOnWriteMapTest.class.getName()); + private static final Class[] classes = {Faker.class, BaseFaker.class, Boolean.class, Base64.class, IdNumber.class, FakerIDNTest.class, FakeValuesService.class}; + private static final String[] methods = {"a", "b", "c", "d", "e", "f", "g", "h"}; + + @Test + void concurrentPutAndGet() throws InterruptedException { + int count = 10_000; + Map, Map>> MAP_OF_METHOD_AND_COERCED_ARGS = new CopyOnWriteMap<>(IdentityHashMap::new); + + SoftAssertions softly = new SoftAssertions(); + Random random = new Random(); + ExecutorService pool = newFixedThreadPool(20); + CountDownLatch countDown = new CountDownLatch(count); + for (int i = 0; i < count; i++) { + Class klass = classes[random.nextInt(classes.length)]; + String methodName = methods[random.nextInt(methods.length)]; + + pool.submit(() -> { + final Map> stringMapMap = + MAP_OF_METHOD_AND_COERCED_ARGS.computeIfAbsent(klass, t -> new CopyOnWriteMap<>(WeakHashMap::new)); + + try { + Thread.sleep(random.nextInt(10)); + stringMapMap.putIfAbsent(methodName, new CopyOnWriteMap<>(WeakHashMap::new)); + if (random.nextInt(10) < 4) System.gc(); + + // here `stringMapMap.get(methodName)` sometimes returns NULL + stringMapMap.get(methodName).putIfAbsent(new String[0], 42); + } catch (Throwable e) { + log.log(Level.SEVERE, e, () -> "Fail to put and get (%s, %s)".formatted(klass, methodName)); + softly.fail(e); + } + countDown.countDown(); + }); + } + pool.shutdown(); + assertThat(countDown.await(1, MINUTES)).isTrue(); + softly.assertAll(); + } +}