diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/util/random/CachedRandomPropertySourceTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/util/random/CachedRandomPropertySourceTests.java index bf92318b..c0349225 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/util/random/CachedRandomPropertySourceTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/util/random/CachedRandomPropertySourceTests.java @@ -18,6 +18,8 @@ package org.springframework.cloud.util.random; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -25,11 +27,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.boot.test.system.CapturedOutput; -import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.core.env.PropertySource; import org.springframework.test.annotation.DirtiesContext; -import org.springframework.util.StringUtils; import static org.assertj.core.api.BDDAssertions.then; import static org.mockito.ArgumentMatchers.eq; @@ -39,7 +38,6 @@ import static org.mockito.Mockito.when; * @author Ryan Baxter */ @ExtendWith(MockitoExtension.class) -@ExtendWith(OutputCaptureExtension.class) @DirtiesContext public class CachedRandomPropertySourceTests { @@ -51,11 +49,13 @@ public class CachedRandomPropertySourceTests { when(randomValuePropertySource.getProperty(eq("random.long"))).thenReturn(1234L); } - @SuppressWarnings("unchecked") @Test - public void getProperty(CapturedOutput output) { - Map> cache = new HashMap<>(); - Map typeCache = new HashMap<>(); + public void getProperty() { + HashMap keyCount = new HashMap<>(); + HashMap, String> typeToKeyLookup = new HashMap<>(); + HashMap typeCount = new HashMap<>(); + Map> cache = createCache(keyCount, typeToKeyLookup, typeCount); + Map typeCache = createTypeCache(typeToKeyLookup, typeCount); typeCache.put("long", 5678L); cache.put("foo", typeCache); @@ -66,13 +66,46 @@ public class CachedRandomPropertySourceTests { then(cachedRandomPropertySource.getProperty("cachedrandom.app.long")).isEqualTo(1234L); then(cachedRandomPropertySource.getProperty("cachedrandom.foo.long")).isEqualTo(5678L); - String str = output.toString(); - then(StringUtils.countOccurrencesOf(str, "No cached value found for key: app")).isEqualTo(1); - then(StringUtils.countOccurrencesOf(str, - "No random value found in cache for key: app and type: long, generating a new value")).isEqualTo(1); - then(StringUtils.countOccurrencesOf(str, "No cached value found for key: foo")).isEqualTo(0); - then(StringUtils.countOccurrencesOf(str, - "No random value found in cache for key: foot and type: long, generating a new value")).isEqualTo(0); + then(keyCount).containsOnlyKeys("app"); // verifies foo wasn't computed + then(keyCount.get("app").get()).isEqualTo(1); + then(typeCount).containsOnlyKeys("app.long"); // verifies foo.long wasn't computed + then(typeCount.get("app.long").get()).isEqualTo(1); + } + + private HashMap> createCache(HashMap keyCount, + HashMap, String> typeToKeyLookup, HashMap typeCount) { + return new HashMap>() { + @Override + public Map computeIfAbsent(String key, + Function> mappingFunction) { + boolean computed = false; + if (!containsKey(key)) { + keyCount.computeIfAbsent(key, s -> new AtomicInteger()).incrementAndGet(); + computed = true; + } + Map value = super.computeIfAbsent(key, mappingFunction); + if (computed && value.isEmpty()) { + // replace value with instrumented map + value = createTypeCache(typeToKeyLookup, typeCount); + typeToKeyLookup.put(value, key); + } + return value; + } + }; + } + + private HashMap createTypeCache(HashMap, String> typeToKeyLookup, + HashMap typeCount) { + return new HashMap() { + @Override + public Object computeIfAbsent(String key, Function mappingFunction) { + if (!containsKey(key)) { + String parentKey = typeToKeyLookup.get(this); + typeCount.computeIfAbsent(parentKey + "." + key, s -> new AtomicInteger()).incrementAndGet(); + } + return super.computeIfAbsent(key, mappingFunction); + } + }; } }