diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 32af74aa0d5..04415ddc29c 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -466,7 +466,7 @@ public boolean isNumber() { return _isNumericPrimitive(curType) || (this.rawObj instanceof Number); } - private static boolean _isNumericPrimitive(byte type) { + static boolean _isNumericPrimitive(byte type) { return (type >= BYTE); } @@ -1080,7 +1080,7 @@ abstract class TagMapFactory { createFactory(Config.get().isOptimizedMapEnabled()); static final TagMapFactory createFactory(boolean useOptimized) { - return useOptimized ? new OptimizedTagMapFactory() : new LegacyTagMapFactory(); + return useOptimized ? OptimizedTagMapFactory.INSTANCE : LegacyTagMapFactory.INSTANCE; } public abstract MapT create(); @@ -1091,6 +1091,10 @@ static final TagMapFactory createFactory(boolean useOptimized) { } final class OptimizedTagMapFactory extends TagMapFactory { + static final OptimizedTagMapFactory INSTANCE = new OptimizedTagMapFactory(); + + private OptimizedTagMapFactory() {} + @Override public OptimizedTagMap create() { return new OptimizedTagMap(); @@ -1108,6 +1112,10 @@ public OptimizedTagMap empty() { } final class LegacyTagMapFactory extends TagMapFactory { + static final LegacyTagMapFactory INSTANCE = new LegacyTagMapFactory(); + + private LegacyTagMapFactory() {} + @Override public LegacyTagMap create() { return new LegacyTagMap(); diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java index 95f429737aa..7b7d9bbc2ed 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -32,6 +32,21 @@ * @author dougqh */ public class TagMapEntryTest { + @Test + public void isNumericPrimitive() { + assertFalse(TagMap.Entry._isNumericPrimitive(TagMap.Entry.ANY)); + assertFalse(TagMap.Entry._isNumericPrimitive(TagMap.Entry.BOOLEAN)); + assertFalse(TagMap.Entry._isNumericPrimitive(TagMap.Entry.CHAR)); + assertFalse(TagMap.Entry._isNumericPrimitive(TagMap.Entry.OBJECT)); + + assertTrue(TagMap.Entry._isNumericPrimitive(TagMap.Entry.BYTE)); + assertTrue(TagMap.Entry._isNumericPrimitive(TagMap.Entry.SHORT)); + assertTrue(TagMap.Entry._isNumericPrimitive(TagMap.Entry.INT)); + assertTrue(TagMap.Entry._isNumericPrimitive(TagMap.Entry.LONG)); + assertTrue(TagMap.Entry._isNumericPrimitive(TagMap.Entry.FLOAT)); + assertTrue(TagMap.Entry._isNumericPrimitive(TagMap.Entry.DOUBLE)); + } + @Test public void objectEntry() { test( @@ -42,7 +57,8 @@ public void objectEntry() { checkKey("foo", entry), checkValue("bar", entry), checkEquals("bar", entry::stringValue), - checkTrue(entry::isObject))); + checkTrue(entry::isObject), + checkFalse(entry::isNumber))); } @Test @@ -55,6 +71,7 @@ public void anyEntry_object() { checkKey("foo", entry), checkValue("bar", entry), checkTrue(entry::isObject), + checkFalse(entry::isNumber), checkKey("foo", entry), checkValue("bar", entry))); } @@ -70,6 +87,7 @@ public void booleanEntry(boolean value) { checkKey("foo", entry), checkValue(value, entry), checkFalse(entry::isNumericPrimitive), + checkFalse(entry::isNumber), checkType(TagMap.Entry.BOOLEAN, entry))); } @@ -84,6 +102,7 @@ public void booleanEntry_boxed(boolean value) { checkKey("foo", entry), checkValue(value, entry), checkFalse(entry::isNumericPrimitive), + checkFalse(entry::isNumber), checkType(TagMap.Entry.BOOLEAN, entry))); } @@ -98,6 +117,7 @@ public void anyEntry_boolean(boolean value) { checkKey("foo", entry), checkValue(value, entry), checkFalse(entry::isNumericPrimitive), + checkFalse(entry::isNumber), checkType(TagMap.Entry.BOOLEAN, entry), checkValue(value, entry))); } @@ -112,7 +132,8 @@ public void intEntry(int value) { multiCheck( checkKey("foo", entry), checkValue(value, entry), - checkTrue(entry::isNumericPrimitive), + checkIsNumericPrimitive(entry), + checkInstanceOf(Number.class, entry), checkType(TagMap.Entry.INT, entry))); } @@ -126,7 +147,8 @@ public void intEntry_boxed(int value) { multiCheck( checkKey("foo", entry), checkValue(value, entry), - checkTrue(entry::isNumericPrimitive), + checkIsNumericPrimitive(entry), + checkInstanceOf(Number.class, entry), checkType(TagMap.Entry.INT, entry))); } @@ -140,7 +162,8 @@ public void anyEntry_int(int value) { multiCheck( checkKey("foo", entry), checkValue(value, entry), - checkTrue(entry::isNumericPrimitive), + checkIsNumericPrimitive(entry), + checkInstanceOf(Number.class, entry), checkType(TagMap.Entry.INT, entry), checkValue(value, entry))); } @@ -199,7 +222,7 @@ public void longEntry_boxed(long value) { multiCheck( checkKey("foo", entry), checkValue(value, entry), - checkTrue(entry::isNumericPrimitive), + checkIsNumericPrimitive(entry), checkType(TagMap.Entry.LONG, entry))); } @@ -228,7 +251,7 @@ public void anyEntry_long(long value) { multiCheck( checkKey("foo", entry), checkValue(value, entry), - checkTrue(entry::isNumericPrimitive), + checkIsNumericPrimitive(entry), checkTrue(() -> entry.is(TagMap.Entry.LONG)), checkValue(value, entry))); } @@ -257,7 +280,7 @@ public void floatEntry_boxed(float value) { multiCheck( checkKey("foo", entry), checkValue(value, entry), - checkTrue(entry::isNumericPrimitive), + checkIsNumericPrimitive(entry), checkType(TagMap.Entry.FLOAT, entry))); } @@ -286,7 +309,7 @@ public void doubleEntry(double value) { multiCheck( checkKey("foo", entry), checkValue(value, entry), - checkTrue(entry::isNumericPrimitive), + checkIsNumericPrimitive(entry), checkType(TagMap.Entry.DOUBLE, entry))); } @@ -417,6 +440,20 @@ static final Check checkKey(String expected, TagMap.Entry entry) { return multiCheck(checkEquals(expected, entry::tag), checkEquals(expected, entry::getKey)); } + static final Check checkIsNumericPrimitive(TagMap.Entry entry) { + return multiCheck( + checkTrue(entry::isNumericPrimitive), + checkTrue(entry::isNumber), + checkInstanceOf(Number.class, entry)); + } + + static final Check checkIsBigNumber(TagMap.Entry entry) { + return multiCheck( + checkFalse(entry::isNumericPrimitive), + checkTrue(entry::isNumber), + checkInstanceOf(Number.class, entry)); + } + static final Check checkValue(Object expected, TagMap.Entry entry) { return multiCheck( checkEquals(expected, entry::objectValue), @@ -468,6 +505,16 @@ static final Check checkValue(double expected, TagMap.Entry entry) { checkEquals(Double.toString(expected), entry::stringValue)); } + public static Check checkNumber(Number number, TagMap.Entry entry) { + return multiCheck( + checkEquals(number, entry::objectValue), + checkEquals(number.intValue(), entry::intValue), + checkEquals(number.longValue(), entry::longValue), + checkEquals(number.floatValue(), entry::floatValue), + checkEquals(number.doubleValue(), entry::doubleValue), + checkEquals(number.toString(), entry::stringValue)); + } + static final Check checkValue(float expected, TagMap.Entry entry) { return multiCheck( checkEquals(expected, entry::floatValue), @@ -479,6 +526,13 @@ static final Check checkValue(float expected, TagMap.Entry entry) { checkEquals(Float.toString(expected), entry::stringValue)); } + static final Check checkInstanceOf(Class klass, TagMap.Entry entry) { + return () -> + assertTrue( + klass.isAssignableFrom(entry.objectValue().getClass()), + "instanceof " + klass.getSimpleName()); + } + static final Check checkType(byte entryType, TagMap.Entry entry) { return () -> assertTrue(entry.is(entryType), "type is " + entryType); } @@ -496,23 +550,23 @@ static final Check checkTrue(Supplier actual) { } static final Check checkEquals(float expected, Supplier actual) { - return () -> assertEquals(expected, actual.get(), actual.toString()); + return () -> assertEquals(expected, actual.get().floatValue(), actual.toString()); } static final Check checkEquals(int expected, Supplier actual) { - return () -> assertEquals(expected, actual.get(), actual.toString()); + return () -> assertEquals(expected, actual.get().intValue(), actual.toString()); } static final Check checkEquals(double expected, Supplier actual) { - return () -> assertEquals(expected, actual.get(), actual.toString()); + return () -> assertEquals(expected, actual.get().doubleValue(), actual.toString()); } static final Check checkEquals(long expected, Supplier actual) { - return () -> assertEquals(expected, actual.get(), actual.toString()); + return () -> assertEquals(expected, actual.get().longValue(), actual.toString()); } static final Check checkEquals(boolean expected, Supplier actual) { - return () -> assertEquals(expected, actual.get(), actual.toString()); + return () -> assertEquals(expected, actual.get().booleanValue(), actual.toString()); } static final Check checkEquals(Object expected, Supplier actual) { diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java index d5a2eb17ea7..692aaaf37f3 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; @@ -11,6 +12,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Supplier; import org.junit.jupiter.api.Test; public final class TagMapFuzzTest { @@ -296,8 +298,8 @@ void priorFailingCase0() { "values-9646496", "key-90", "values-1485206899"); - failingAction.apply(map); - failingAction.verify(map); + failingAction.applyToTestMap(map); + failingAction.verifyTestMap(map); } @Test @@ -516,8 +518,8 @@ void priorFailingCase1() { "values--48760205", "key-61", "values--1942966789"); - failingAction.apply(map); - failingAction.verify(map); + failingAction.applyToTestMap(map); + failingAction.verifyTestMap(map); } @Test @@ -859,52 +861,51 @@ void priorFailingCase2() { OptimizedTagMap actual = makeTagMap(testCase); MapAction failingAction = remove("key-127"); - failingAction.apply(expected); - failingAction.verify(expected); + failingAction.applyToExpectedMap(expected); - failingAction.apply(actual); - failingAction.verify(actual); + failingAction.applyToTestMap(actual); + failingAction.verifyTestMap(actual); assertMapEquals(expected, actual); } - public static TagMap test(MapAction... actions) { + public static final TagMap test(MapAction... actions) { return test(new TestCase(Arrays.asList(actions))); } - public static Map makeMap(TestCase testCase) { + public static final Map makeMap(TestCase testCase) { return makeMap(testCase.actions); } - public static Map makeMap(MapAction... actions) { + public static final Map makeMap(MapAction... actions) { return makeMap(Arrays.asList(actions)); } - public static Map makeMap(List actions) { + public static final Map makeMap(List actions) { Map map = new HashMap<>(); for (MapAction action : actions) { - action.apply(map); + action.applyToExpectedMap(map); } return map; } - public static OptimizedTagMap makeTagMap(TestCase testCase) { + public static final OptimizedTagMap makeTagMap(TestCase testCase) { return makeTagMap(testCase.actions); } - public static OptimizedTagMap makeTagMap(MapAction... actions) { + public static final OptimizedTagMap makeTagMap(MapAction... actions) { return makeTagMap(Arrays.asList(actions)); } - public static OptimizedTagMap makeTagMap(List actions) { + public static final OptimizedTagMap makeTagMap(List actions) { OptimizedTagMap map = new OptimizedTagMap(); for (MapAction action : actions) { - action.apply(map); + action.applyToTestMap(map); } return map; } - public static OptimizedTagMap test(TestCase test) { + public static final OptimizedTagMap test(TestCase test) { List actions = test.actions(); Map hashMap = new HashMap<>(); @@ -915,12 +916,12 @@ public static OptimizedTagMap test(TestCase test) { for (actionIndex = 0; actionIndex < actions.size(); ++actionIndex) { MapAction action = actions.get(actionIndex); - Object expected = action.apply(hashMap); - Object result = action.apply(tagMap); + Object expected = action.applyToExpectedMap(hashMap); + Object actual = action.applyToTestMap(tagMap); - assertEquals(expected, result); + action.verifyResults(expected, actual); - action.verify(tagMap); + action.verifyTestMap(tagMap); assertMapEquals(hashMap, tagMap); } @@ -932,11 +933,13 @@ public static OptimizedTagMap test(TestCase test) { return tagMap; } - public static TestCase generateTest() { - return generateTest(ThreadLocalRandom.current().nextInt(MIN_NUM_ACTIONS, MAX_NUM_ACTIONS)); + public static final TestCase generateTest() { + int numActions = + ThreadLocalRandom.current().nextInt(MAX_NUM_ACTIONS - MIN_NUM_ACTIONS) + MIN_NUM_ACTIONS; + return generateTest(numActions); } - public static TestCase generateTest(int size) { + public static final TestCase generateTest(int size) { List actions = new ArrayList<>(size); for (int i = 0; i < size; ++i) { actions.add(randomAction()); @@ -944,47 +947,74 @@ public static TestCase generateTest(int size) { return new TestCase(actions); } - public static MapAction randomAction() { + public static final MapAction randomAction() { float actionSelector = ThreadLocalRandom.current().nextFloat(); - if (actionSelector > 0.5) { - // 50% puts - return put(randomKey(), randomValue()); - } else if (actionSelector > 0.3) { - // 20% removes - return remove(randomKey()); - } else if (actionSelector > 0.2) { - // 10% putAll TagMap - return putAllTagMap(randomKeysAndValues()); - } else if (actionSelector > 0.02) { - // ~10% putAll HashMap - return putAll(randomKeysAndValues()); - } else { - return clear(); + switch (randomChoice(0.02, 0.1, 0.2)) { + case 0: + return clear(); + + case 1: + return randomChoice( + () -> putAll(randomKeysAndValues()), + () -> putAllTagMap(randomKeysAndValues()), + () -> putAllLedger(randomKeysAndValues())); + + case 2: + return randomChoice( + () -> remove(randomKey()), + () -> removeLight(randomKey()), + () -> getAndRemove(randomKey())); + + default: + return randomChoice( + () -> put(randomKey(), randomValue()), + () -> set(randomKey(), randomValue()), + () -> getAndSet(randomKey(), randomValue())); } } - public static MapAction put(String key, String value) { + public static final MapAction put(String key, String value) { return new Put(key, value); } - public static MapAction putAll(String... keysAndValues) { + public static final MapAction set(String key, String value) { + return new Set(key, value); + } + + public static final MapAction getAndSet(String key, String value) { + return new GetAndSet(key, value); + } + + public static final MapAction putAll(String... keysAndValues) { return new PutAll(keysAndValues); } - public static MapAction putAllTagMap(String... keysAndValues) { + public static final MapAction putAllTagMap(String... keysAndValues) { return new PutAllTagMap(keysAndValues); } - public static MapAction clear() { + public static final MapAction putAllLedger(String... keysAndValues) { + return new PutAllLedger(keysAndValues); + } + + public static final MapAction clear() { return Clear.INSTANCE; } - public static MapAction remove(String key) { + public static final MapAction remove(String key) { return new Remove(key); } - static void assertMapEquals(Map expected, OptimizedTagMap actual) { + public static final MapAction removeLight(String key) { + return new RemoveLight(key); + } + + public static final MapAction getAndRemove(String key) { + return new GetAndRemove(key); + } + + static final void assertMapEquals(Map expected, OptimizedTagMap actual) { // checks entries in both directions to make sure there's full intersection for (Map.Entry expectedEntry : expected.entrySet()) { @@ -1001,15 +1031,40 @@ static void assertMapEquals(Map expected, OptimizedTagMap actual actual.checkIntegrity(); } - static String randomKey() { + static final float randomFloat() { + return ThreadLocalRandom.current().nextFloat(); + } + + static final int randomChoice(int numChoices) { + return ThreadLocalRandom.current().nextInt(numChoices); + } + + static final T randomChoice(Supplier... choiceSuppliers) { + int choice = randomChoice(choiceSuppliers.length); + + return choiceSuppliers[choice].get(); + } + + static final int randomChoice(double... proportions) { + double selector = ThreadLocalRandom.current().nextDouble(); + + for (int i = 0; i < proportions.length; ++i) { + if (selector < proportions[i]) return i; + + selector -= proportions[i]; + } + return proportions.length; + } + + static final String randomKey() { return "key-" + ThreadLocalRandom.current().nextInt(NUM_KEYS); } - static String randomValue() { + static final String randomValue() { return "values-" + ThreadLocalRandom.current().nextInt(); } - static String[] randomKeysAndValues() { + static final String[] randomKeysAndValues() { int numEntries = ThreadLocalRandom.current().nextInt(NUM_KEYS); String[] keysAndValues = new String[numEntries << 1]; @@ -1020,11 +1075,11 @@ static String[] randomKeysAndValues() { return keysAndValues; } - static String literal(String str) { + static final String literal(String str) { return "\"" + str + "\""; } - static String literalVarArgs(String... strs) { + static final String literalVarArgs(String... strs) { StringBuilder builder = new StringBuilder(); for (String str : strs) { if (builder.length() != 0) builder.append(','); @@ -1033,7 +1088,7 @@ static String literalVarArgs(String... strs) { return builder.toString(); } - static Map mapOf(String... keysAndValues) { + static final Map mapOf(String... keysAndValues) { HashMap map = new HashMap<>(keysAndValues.length >> 1); for (int i = 0; i < keysAndValues.length; i += 2) { String key = keysAndValues[i]; @@ -1044,7 +1099,7 @@ static Map mapOf(String... keysAndValues) { return map; } - static TagMap tagMapOf(String... keysAndValues) { + static final TagMap tagMapOf(String... keysAndValues) { OptimizedTagMap map = new OptimizedTagMap(); for (int i = 0; i < keysAndValues.length; i += 2) { String key = keysAndValues[i]; @@ -1057,6 +1112,17 @@ static TagMap tagMapOf(String... keysAndValues) { return map; } + static final TagMap.Ledger ledgerOf(String... keysAndValues) { + TagMap.Ledger ledger = TagMap.ledger(); + for (int i = 0; i < keysAndValues.length; i += 2) { + String key = keysAndValues[i]; + String value = keysAndValues[i + 1]; + + ledger.set(key, value); + } + return ledger; + } + static final class TestCase { final List actions; @@ -1068,7 +1134,7 @@ static final class TestCase { this.actions = actions; } - public List actions() { + public final List actions() { return this.actions; } @@ -1083,14 +1149,84 @@ public String toString() { } abstract static class MapAction { - public abstract Object apply(Map mapUnderTest); + public abstract Object applyToTestMap(TagMap testMap); - public abstract void verify(Map mapUnderTest); + public abstract Object applyToExpectedMap(Map expectedMap); + + public abstract void verifyResults(Object expected, Object actual); + + public abstract void verifyTestMap(TagMap testMap); public abstract String toString(); } - static final class Put extends MapAction { + abstract static class BasicAction extends MapAction { + @Override + public final Object applyToTestMap(TagMap testMap) { + _applyToTestMap(testMap); + + return void.class; + } + + protected abstract void _applyToTestMap(TagMap testMap); + + @Override + public final Object applyToExpectedMap(Map expectedMap) { + _applyToExpectedMap(expectedMap); + + return void.class; + } + + protected abstract void _applyToExpectedMap(Map expectedMap); + + public final void verifyResults(Object expected, Object actual) {} + } + + abstract static class BasicReturningAction extends MapAction { + @Override + public final TResult applyToTestMap(TagMap testMap) { + return _applyToTestMap(testMap); + } + + protected abstract TResult _applyToTestMap(TagMap testMap); + + @Override + public final TResult applyToExpectedMap(Map expectedMap) { + return _applyToExpectedMap(expectedMap); + } + + protected abstract TResult _applyToExpectedMap(Map expectedMap); + + @SuppressWarnings("unchecked") + public final void verifyResults(Object expected, Object actual) { + assertEquals(expected, actual); + } + } + + abstract static class ReturningAction extends MapAction { + @Override + public final TActualResult applyToTestMap(TagMap testMap) { + return _applyToTestMap(testMap); + } + + protected abstract TActualResult _applyToTestMap(TagMap testMap); + + @Override + public final TExpectedResult applyToExpectedMap(Map expectedMap) { + return _applyToExpectedMap(expectedMap); + } + + protected abstract TExpectedResult _applyToExpectedMap(Map expectedMap); + + @SuppressWarnings("unchecked") + public final void verifyResults(Object expected, Object actual) { + _verifyResults((TExpectedResult) expected, (TActualResult) actual); + } + + protected abstract void _verifyResults(TExpectedResult expected, TActualResult actual); + } + + static final class Put extends BasicReturningAction { final String key; final String value; @@ -1100,13 +1236,18 @@ static final class Put extends MapAction { } @Override - public Object apply(Map mapUnderTest) { - return mapUnderTest.put(this.key, this.value); + protected Object _applyToTestMap(TagMap testMap) { + return testMap.put(this.key, this.value); + } + + @Override + protected Object _applyToExpectedMap(Map expectedMap) { + return expectedMap.put(this.key, this.value); } @Override - public void verify(Map mapUnderTest) { - assertEquals(this.value, mapUnderTest.get(this.key)); + public void verifyTestMap(TagMap testMap) { + assertEquals(this.value, testMap.get(this.key)); } @Override @@ -1115,7 +1256,76 @@ public String toString() { } } - static final class PutAll extends MapAction { + static final class Set extends BasicAction { + final String key; + final String value; + + Set(String key, String value) { + this.key = key; + this.value = value; + } + + @Override + protected void _applyToTestMap(TagMap testMap) { + testMap.set(this.key, this.value); + } + + @Override + protected void _applyToExpectedMap(Map expectedMap) { + expectedMap.put(this.key, this.value); + } + + @Override + public void verifyTestMap(TagMap testMap) { + assertEquals(this.value, testMap.get(this.key)); + } + + @Override + public String toString() { + return String.format("set(%s,%s)", literal(this.key), literal(this.value)); + } + } + + static final class GetAndSet extends ReturningAction { + final String key; + final String value; + + GetAndSet(String key, String value) { + this.key = key; + this.value = value; + } + + @Override + protected TagMap.Entry _applyToTestMap(TagMap testMap) { + return testMap.getAndSet(this.key, this.value); + } + + @Override + protected Object _applyToExpectedMap(Map expectedMap) { + return expectedMap.put(this.key, this.value); + } + + @Override + protected void _verifyResults(Object expected, TagMap.Entry actual) { + if (expected == null) { + assertNull(actual); + } else { + assertEquals(expected, actual.objectValue()); + } + } + + @Override + public void verifyTestMap(TagMap testMap) { + assertEquals(this.value, testMap.get(this.key)); + } + + @Override + public String toString() { + return String.format("getAndSet(%s,%s)", literal(this.key), literal(this.value)); + } + } + + static final class PutAll extends BasicAction { final String[] keysAndValues; final Map map; @@ -1125,16 +1335,19 @@ static final class PutAll extends MapAction { } @Override - public Object apply(Map mapUnderTest) { - mapUnderTest.putAll(this.map); + protected void _applyToTestMap(TagMap testMap) { + testMap.putAll(this.map); + } - return void.class; + @Override + public void _applyToExpectedMap(Map expectedMap) { + expectedMap.putAll(this.map); } @Override - public void verify(Map mapUnderTest) { + public void verifyTestMap(TagMap expectedMap) { for (Map.Entry entry : this.map.entrySet()) { - assertEquals(entry.getValue(), mapUnderTest.get(entry.getKey())); + assertEquals(entry.getValue(), expectedMap.get(entry.getKey())); } } @@ -1144,7 +1357,7 @@ public String toString() { } } - static final class PutAllTagMap extends MapAction { + static final class PutAllTagMap extends BasicAction { final String[] keysAndValues; final TagMap tagMap; @@ -1154,16 +1367,19 @@ static final class PutAllTagMap extends MapAction { } @Override - public Object apply(Map mapUnderTest) { - mapUnderTest.putAll(this.tagMap); + protected void _applyToTestMap(TagMap testMap) { + testMap.putAll(this.tagMap); + } - return void.class; + @Override + protected void _applyToExpectedMap(Map expectedMap) { + expectedMap.putAll(this.tagMap); } @Override - public void verify(Map mapUnderTest) { + public void verifyTestMap(TagMap expectedMap) { for (TagMap.Entry entry : this.tagMap) { - assertEquals(entry.objectValue(), mapUnderTest.get(entry.tag()), "key=" + entry.tag()); + assertEquals(entry.objectValue(), expectedMap.get(entry.tag()), "key=" + entry.tag()); } } @@ -1173,7 +1389,46 @@ public String toString() { } } - static final class Remove extends MapAction { + static final class PutAllLedger extends BasicAction { + final String[] keysAndValues; + final TagMap.Ledger ledger; + + PutAllLedger(String... keysAndValues) { + this.keysAndValues = keysAndValues; + this.ledger = ledgerOf(keysAndValues); + } + + @Override + protected void _applyToTestMap(TagMap testMap) { + this.ledger.fill(testMap); + } + + @Override + protected void _applyToExpectedMap(Map expectedMap) { + for (TagMap.EntryChange change : this.ledger) { + // ledgerOf - doesn't produce / removes, so this is safe + TagMap.Entry entry = (TagMap.Entry) change; + expectedMap.put(entry.tag(), entry.objectValue()); + } + } + + @Override + public void verifyTestMap(TagMap expectedMap) { + // ledger may contain multiple updates of the same key + // easier to produce a TagMap and check against it + + for (TagMap.Entry entry : this.ledger.buildImmutable()) { + assertEquals(entry.objectValue(), expectedMap.get(entry.tag()), "key=" + entry.tag()); + } + } + + @Override + public String toString() { + return String.format("putAllLedger(%s)", literalVarArgs(this.keysAndValues)); + } + } + + static final class Remove extends BasicReturningAction { final String key; Remove(String key) { @@ -1181,13 +1436,18 @@ static final class Remove extends MapAction { } @Override - public Object apply(Map mapUnderTest) { - return mapUnderTest.remove(this.key); + protected Object _applyToTestMap(TagMap testMap) { + return testMap.remove((Object) this.key); + } + + @Override + protected Object _applyToExpectedMap(Map expectedMap) { + return expectedMap.remove(this.key); } @Override - public void verify(Map mapUnderTest) { - assertFalse(mapUnderTest.containsKey(this.key)); + public void verifyTestMap(TagMap testMap) { + assertFalse(testMap.containsKey(this.key)); } @Override @@ -1196,21 +1456,94 @@ public String toString() { } } - static final class Clear extends MapAction { + static final class RemoveLight extends ReturningAction { + final String key; + + RemoveLight(String key) { + this.key = key; + } + + @Override + protected Boolean _applyToTestMap(TagMap testMap) { + return testMap.remove(this.key); + } + + @Override + protected Object _applyToExpectedMap(Map expectedMap) { + return expectedMap.remove(this.key); + } + + @Override + protected void _verifyResults(Object expected, Boolean actual) { + assertEquals((expected != null), actual); + } + + @Override + public void verifyTestMap(TagMap testMap) { + assertFalse(testMap.containsKey(this.key)); + } + + @Override + public String toString() { + return String.format("removeLight(%s)", literal(this.key)); + } + } + + static final class GetAndRemove extends ReturningAction { + final String key; + + GetAndRemove(String key) { + this.key = key; + } + + @Override + protected TagMap.Entry _applyToTestMap(TagMap testMap) { + return testMap.getAndRemove(this.key); + } + + @Override + protected Object _applyToExpectedMap(Map expectedMap) { + return expectedMap.remove(this.key); + } + + @Override + protected void _verifyResults(Object expected, TagMap.Entry actual) { + if (expected == null) { + assertNull(actual); + } else { + assertEquals(expected, actual.objectValue()); + } + } + + @Override + public void verifyTestMap(TagMap testMap) { + assertFalse(testMap.containsKey(this.key)); + } + + @Override + public String toString() { + return String.format("getAndRemove(%s)", literal(this.key)); + } + } + + static final class Clear extends BasicAction { static final Clear INSTANCE = new Clear(); private Clear() {} @Override - public Object apply(Map mapUnderTest) { - mapUnderTest.clear(); + protected void _applyToTestMap(TagMap testMap) { + testMap.clear(); + } - return void.class; + @Override + protected void _applyToExpectedMap(Map mapUnderTest) { + mapUnderTest.clear(); } @Override - public void verify(Map mapUnderTest) { - assertTrue(mapUnderTest.isEmpty()); + public void verifyTestMap(TagMap testMap) { + assertTrue(testMap.isEmpty()); } @Override diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapScenario.java b/internal-api/src/test/java/datadog/trace/api/TagMapScenario.java new file mode 100644 index 00000000000..20b9b927d1b --- /dev/null +++ b/internal-api/src/test/java/datadog/trace/api/TagMapScenario.java @@ -0,0 +1,30 @@ +package datadog.trace.api; + +public enum TagMapScenario { + LEGACY_EMPTY(LegacyTagMapFactory.INSTANCE, 0), + OPTIMIZED_EMPTY(OptimizedTagMapFactory.INSTANCE, 0), + OPTIMIZED_XSMALL(OptimizedTagMapFactory.INSTANCE, 5), + OPTIMIZED_SMALL(OptimizedTagMapFactory.INSTANCE, 10), + OPTIMIZED_MEDIUM(OptimizedTagMapFactory.INSTANCE, 25), + OPTIMIZED_LARGE(OptimizedTagMapFactory.INSTANCE, 125); + + final TagMapFactory factory; + final int size; + + TagMapScenario(TagMapFactory factory, int size) { + this.factory = factory; + this.size = size; + } + + public final int size() { + return this.size; + } + + public final TagMap create() { + TagMap map = factory.create(); + for (int i = 0; i < this.size; ++i) { + map.put("filler-key-" + i, "filler-value-" + i); + } + return map; + } +} diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java index 064561768cf..68ce0acda20 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -95,47 +95,54 @@ public void optimizedFactory(boolean optimized) { } @ParameterizedTest - @EnumSource(TagMapType.class) - public void map_put(TagMapType mapType) { - TagMap map = mapType.create(); + @EnumSource(TagMapScenario.class) + public void map_put(TagMapScenario scenario) { + TagMap map = scenario.create(); Object prev = map.put("foo", "bar"); assertNull(prev); assertEntry("foo", "bar", map); - assertSize(1, map); + assertSize(scenario.size() + 1, map); assertNotEmpty(map); + + checkIntegrity(map); } @ParameterizedTest - @EnumSource(TagMapType.class) - public void booleanEntry(TagMapType mapType) { + @EnumSource(TagMapScenario.class) + public void booleanEntry(TagMapScenario scenario) { + String BOOL = "bool"; + String UNSET = "unset"; + boolean first = false; boolean second = true; - TagMap map = mapType.create(); - map.set("bool", first); + TagMap map = scenario.create(); + map.set(BOOL, first); - TagMap.Entry firstEntry = map.getEntry("bool"); + TagMap.Entry firstEntry = map.getEntry(BOOL); if (map.isOptimized()) { assertEquals(TagMap.Entry.BOOLEAN, firstEntry.rawType); } assertEquals(first, firstEntry.booleanValue()); - assertEquals(first, map.getBoolean("bool")); + assertEquals(first, map.getBoolean(BOOL)); - TagMap.Entry priorEntry = map.getAndSet("bool", second); + TagMap.Entry priorEntry = map.getAndSet(BOOL, second); if (map.isOptimized()) { assertSame(priorEntry, firstEntry); } assertEquals(first, priorEntry.booleanValue()); - TagMap.Entry newEntry = map.getEntry("bool"); + TagMap.Entry newEntry = map.getEntry(BOOL); assertEquals(second, newEntry.booleanValue()); - assertFalse(map.getBoolean("unset")); - assertTrue(map.getBooleanOrDefault("unset", true)); + assertEquals(false, map.getBoolean(UNSET)); + assertEquals(true, map.getBooleanOrDefault(UNSET, true)); + + checkIntegrity(map); } @ParameterizedTest @@ -153,14 +160,16 @@ public void numericZeroToBooleanCoercion(TagMapType mapType) { .set("doubleObj", Double.valueOf(0D)) .build(mapType.factory); - assertFalse(map.getBoolean("int")); - assertFalse(map.getBoolean("intObj")); - assertFalse(map.getBoolean("long")); - assertFalse(map.getBoolean("longObj")); - assertFalse(map.getBoolean("float")); - assertFalse(map.getBoolean("floatObj")); - assertFalse(map.getBoolean("double")); - assertFalse(map.getBoolean("doubleObj")); + assertBoolean(false, map, "int"); + assertBoolean(false, map, "intObj"); + assertBoolean(false, map, "long"); + assertBoolean(false, map, "longObj"); + assertBoolean(false, map, "float"); + assertBoolean(false, map, "floatObj"); + assertBoolean(false, map, "double"); + assertBoolean(false, map, "doubleObj"); + + checkIntegrity(map); } @ParameterizedTest @@ -178,14 +187,16 @@ public void numericNonZeroToBooleanCoercion(TagMapType mapType) { .set("doubleObj", Double.valueOf(1D)) .build(mapType.factory); - assertTrue(map.getBoolean("int")); - assertTrue(map.getBoolean("intObj")); - assertTrue(map.getBoolean("long")); - assertTrue(map.getBoolean("longObj")); - assertTrue(map.getBoolean("float")); - assertTrue(map.getBoolean("floatObj")); - assertTrue(map.getBoolean("double")); - assertTrue(map.getBoolean("doubleObj")); + assertBoolean(true, map, "int"); + assertBoolean(true, map, "intObj"); + assertBoolean(true, map, "long"); + assertBoolean(true, map, "longObj"); + assertBoolean(true, map, "float"); + assertBoolean(true, map, "floatObj"); + assertBoolean(true, map, "double"); + assertBoolean(true, map, "doubleObj"); + + checkIntegrity(map); } @ParameterizedTest @@ -198,9 +209,11 @@ public void objectToBooleanCoercion(TagMapType mapType) { .set("falseStr", "false") .build(mapType.factory); - assertTrue(map.getBoolean("obj")); - assertTrue(map.getBoolean("trueStr")); - assertTrue(map.getBoolean("falseStr")); + assertBoolean(true, map, "obj"); + assertBoolean(true, map, "trueStr"); + assertBoolean(true, map, "falseStr"); + + checkIntegrity(map); } @ParameterizedTest @@ -208,10 +221,12 @@ public void objectToBooleanCoercion(TagMapType mapType) { public void booleanToNumericCoercion_true(TagMapType mapType) { TagMap map = TagMap.ledger().set("true", true).build(mapType.factory); - assertEquals(1, map.getInt("true")); - assertEquals(1L, map.getLong("true")); - assertEquals(1F, map.getFloat("true")); - assertEquals(1D, map.getDouble("true")); + assertInt(1, map, "true"); + assertLong(1L, map, "true"); + assertFloat(1F, map, "true"); + assertDouble(1D, map, "true"); + + checkIntegrity(map); } @ParameterizedTest @@ -219,10 +234,12 @@ public void booleanToNumericCoercion_true(TagMapType mapType) { public void booleanToNumericCoercion_false(TagMapType mapType) { TagMap map = TagMap.ledger().set("false", false).build(mapType.factory); - assertEquals(0, map.getInt("false")); - assertEquals(0L, map.getLong("false")); - assertEquals(0F, map.getFloat("false")); - assertEquals(0D, map.getDouble("false")); + assertInt(0, map, "false"); + assertLong(0L, map, "false"); + assertFloat(0F, map, "false"); + assertDouble(0D, map, "false"); + + checkIntegrity(map); } @ParameterizedTest @@ -230,21 +247,27 @@ public void booleanToNumericCoercion_false(TagMapType mapType) { public void emptyToPrimitiveCoercion(TagMapType mapType) { TagMap map = mapType.empty(); - assertFalse(map.getBoolean("dne")); + // DQH - assert helpers also check getOrDefault, so they don't work here + assertEquals(false, map.getBoolean("dne")); assertEquals(0, map.getInt("dne")); assertEquals(0L, map.getLong("dne")); assertEquals(0F, map.getFloat("dne")); assertEquals(0D, map.getDouble("dne")); + + checkIntegrity(map); } @ParameterizedTest - @EnumSource(TagMapType.class) - public void intEntry(TagMapType mapType) { + @EnumSource(TagMapScenario.class) + public void intEntry(TagMapScenario scenario) { + String INT = "int"; + String UNSET = "unset"; + int first = 3142; int second = 2718; - TagMap map = mapType.create(); - map.set("int", first); + TagMap map = scenario.create(); + map.set(INT, first); TagMap.Entry firstEntry = map.getEntry("int"); if (map.isOptimized()) { @@ -262,19 +285,23 @@ public void intEntry(TagMapType mapType) { TagMap.Entry newEntry = map.getEntry("int"); assertEquals(second, newEntry.intValue()); + assertEquals(0, map.getInt(UNSET)); + assertEquals(21, map.getIntOrDefault(UNSET, 21)); - assertEquals(0, map.getInt("unset")); - assertEquals(21, map.getIntOrDefault("unset", 21)); + checkIntegrity(map); } @ParameterizedTest - @EnumSource(TagMapType.class) - public void longEntry(TagMapType mapType) { + @EnumSource(TagMapScenario.class) + public void longEntry(TagMapScenario scenario) { + String LONG = "long"; + String UNSET = "unset"; + long first = 3142L; long second = 2718L; - TagMap map = mapType.create(); - map.set("long", first); + TagMap map = scenario.create(); + map.set(LONG, first); TagMap.Entry firstEntry = map.getEntry("long"); if (map.isOptimized()) { @@ -293,68 +320,80 @@ public void longEntry(TagMapType mapType) { TagMap.Entry newEntry = map.getEntry("long"); assertEquals(second, newEntry.longValue()); - assertEquals(0L, map.getLong("unset")); - assertEquals(21L, map.getLongOrDefault("unset", 21L)); + assertEquals(0L, map.getLong(UNSET)); + assertEquals(21L, map.getLongOrDefault(UNSET, 21L)); + + checkIntegrity(map); } @ParameterizedTest - @EnumSource(TagMapType.class) - public void floatEntry(TagMapType mapType) { + @EnumSource(TagMapScenario.class) + public void floatEntry(TagMapScenario scenario) { + String FLOAT = "float"; + String UNSET = "unset"; + float first = 3.14F; float second = 2.718F; - TagMap map = mapType.create(); - map.set("float", first); + TagMap map = scenario.create(); + map.set(FLOAT, first); - TagMap.Entry firstEntry = map.getEntry("float"); + TagMap.Entry firstEntry = map.getEntry(FLOAT); if (map.isOptimized()) { assertEquals(TagMap.Entry.FLOAT, firstEntry.rawType); } assertEquals(first, firstEntry.floatValue()); - assertEquals(first, map.getFloat("float")); + assertEquals(first, map.getFloat(FLOAT)); - TagMap.Entry priorEntry = map.getAndSet("float", second); + TagMap.Entry priorEntry = map.getAndSet(FLOAT, second); if (map.isOptimized()) { assertSame(priorEntry, firstEntry); } assertEquals(first, priorEntry.floatValue()); - TagMap.Entry newEntry = map.getEntry("float"); + TagMap.Entry newEntry = map.getEntry(FLOAT); assertEquals(second, newEntry.floatValue()); - assertEquals(0F, map.getFloat("unset")); - assertEquals(2.718F, map.getFloatOrDefault("unset", 2.718F)); + assertEquals(0F, map.getFloat(UNSET)); + assertEquals(2.718F, map.getFloatOrDefault(UNSET, 2.718F)); + + checkIntegrity(map); } @ParameterizedTest - @EnumSource(TagMapType.class) - public void doubleEntry(TagMapType mapType) { + @EnumSource(TagMapScenario.class) + public void doubleEntry(TagMapScenario scenario) { + String DOUBLE = "double"; + String UNSET = "unset"; + double first = Math.PI; double second = Math.E; - TagMap map = mapType.create(); - map.set("double", Math.PI); + TagMap map = scenario.create(); + map.set(DOUBLE, Math.PI); - TagMap.Entry firstEntry = map.getEntry("double"); + TagMap.Entry firstEntry = map.getEntry(DOUBLE); if (map.isOptimized()) { assertEquals(TagMap.Entry.DOUBLE, firstEntry.rawType); } assertEquals(first, firstEntry.doubleValue()); - assertEquals(first, map.getDouble("double")); + assertEquals(first, map.getDouble(DOUBLE)); - TagMap.Entry priorEntry = map.getAndSet("double", second); + TagMap.Entry priorEntry = map.getAndSet(DOUBLE, second); if (map.isOptimized()) { assertSame(priorEntry, firstEntry); } assertEquals(first, priorEntry.doubleValue()); - TagMap.Entry newEntry = map.getEntry("double"); + TagMap.Entry newEntry = map.getEntry(DOUBLE); assertEquals(second, newEntry.doubleValue()); - assertEquals(0D, map.getDouble("unset")); - assertEquals(2.718D, map.getDoubleOrDefault("unset", 2.718D)); + assertEquals(0D, map.getDouble(UNSET)); + assertEquals(2.718D, map.getDoubleOrDefault(UNSET, 2.718D)); + + checkIntegrity(map); } @ParameterizedTest @@ -366,75 +405,126 @@ public void empty(TagMapType mapType) { assertNull(empty.getEntry("foo")); assertSize(0, empty); assertEmpty(empty); + + checkIntegrity(empty); } @ParameterizedTest @EnumSource(TagMapTypePair.class) public void putAll_empty(TagMapTypePair mapTypePair) { // TagMap.EMPTY breaks the rules and uses a different size bucket array - // This test is just to verify that the commonly use putAll still works with EMPTY + // This test is just to verify that the common use of putAll still works with EMPTY TagMap newMap = mapTypePair.firstType.create(); newMap.putAll(mapTypePair.secondType.empty()); assertSize(0, newMap); assertEmpty(newMap); + + checkIntegrity(newMap); } @ParameterizedTest - @EnumSource(TagMapType.class) - public void clear(TagMapType mapType) { - int size = randomSize(); + @EnumSource(TagMapScenario.class) + public void clear(TagMapScenario scenario) { + TagMap map = scenario.create(); + assertSize(scenario.size(), map); - TagMap map = createTagMap(mapType, size); - assertSize(size, map); - assertNotEmpty(map); + assertEmptiness(scenario.size() == 0, map); map.clear(); assertSize(0, map); assertEmpty(map); + + checkIntegrity(map); } @ParameterizedTest - @EnumSource(TagMapType.class) - public void map_put_replacement(TagMapType mapType) { - TagMap map = mapType.create(); + @EnumSource(TagMapScenario.class) + public void map_put_replacement(TagMapScenario scenario) { + TagMap map = scenario.create(); Object prev1 = map.put("foo", "bar"); assertNull(prev1); assertEntry("foo", "bar", map); - assertSize(1, map); + assertSize(scenario.size() + 1, map); assertNotEmpty(map); Object prev2 = map.put("foo", "baz"); - assertSize(1, map); + assertSize(scenario.size() + 1, map); assertEquals("bar", prev2); assertEntry("foo", "baz", map); + + checkIntegrity(map); } @ParameterizedTest - @EnumSource(TagMapType.class) - public void map_remove(TagMapType mapType) { - TagMap map = mapType.create(); + @EnumSource(TagMapScenario.class) + public void map_remove_Object(TagMapScenario scenario) { + TagMap map = scenario.create(); - Object prev1 = map.remove((Object) "foo"); - assertNull(prev1); + Object prev = map.remove((Object) "foo"); + assertNull(prev); map.put("foo", "bar"); assertEntry("foo", "bar", map); - assertSize(1, map); + assertSize(scenario.size() + 1, map); assertNotEmpty(map); Object prev2 = map.remove((Object) "foo"); assertEquals("bar", prev2); - assertSize(0, map); - assertEmpty(map); + assertSize(scenario.size(), map); + assertEmptiness(scenario, map); + + checkIntegrity(map); } @ParameterizedTest - @EnumSource(TagMapType.class) - public void freeze(TagMapType mapType) { - TagMap map = mapType.create(); + @EnumSource(TagMapScenario.class) + public void map_remove_String(TagMapScenario scenario) { + TagMap map = scenario.create(); + + boolean hadPrev1 = map.remove("foo"); + assertFalse(hadPrev1); + + map.put("foo", "bar"); + assertEntry("foo", "bar", map); + assertSize(scenario.size() + 1, map); + assertNotEmpty(map); + + boolean hadPrev2 = map.remove("foo"); + assertTrue(hadPrev2); + assertSize(scenario.size(), map); + assertEmptiness(scenario, map); + + checkIntegrity(map); + } + + @ParameterizedTest + @EnumSource(TagMapScenario.class) + public void map_getAndRemove(TagMapScenario scenario) { + TagMap map = scenario.create(); + + TagMap.Entry prevEntry1 = map.getAndRemove("foo"); + assertNull(prevEntry1); + + map.put("foo", "bar"); + assertEntry("foo", "bar", map); + assertSize(scenario.size() + 1, map); + assertNotEmpty(map); + + TagMap.Entry prevEntry2 = map.getAndRemove("foo"); + assertEquals("bar", prevEntry2.objectValue()); + assertSize(scenario.size(), map); + assertEmptiness(scenario, map); + + checkIntegrity(map); + } + + @ParameterizedTest + @EnumSource(TagMapScenario.class) + public void freeze(TagMapScenario scenario) { + TagMap map = scenario.create(); map.put("foo", "bar"); assertEntry("foo", "bar", map); @@ -447,6 +537,8 @@ public void freeze(TagMapType mapType) { }); assertEntry("foo", "bar", map); + + checkIntegrity(map); } @ParameterizedTest @@ -458,28 +550,36 @@ public void emptyMap(TagMapType mapType) { assertEmpty(map); assertFrozen(map); + + checkIntegrity(map); } @ParameterizedTest - @EnumSource(TagMapType.class) - public void putMany(TagMapType mapType) { - int size = randomSize(); - TagMap map = createTagMap(mapType, size); + @EnumSource(TagMapScenario.class) + public void putMany(TagMapScenario scenario) { + TagMap map = scenario.create(); + + int size = scenario.size(); + fillMap(map, size); for (int i = 0; i < size; ++i) { assertEntry(key(i), value(i), map); } - assertNotEmpty(map); - assertSize(size, map); + assertEmptiness(scenario, map); + assertSize(scenario.size() + size, map); + + checkIntegrity(map); } @ParameterizedTest - @EnumSource(TagMapType.class) - public void copyMany(TagMapType mapType) { - int size = randomSize(); - TagMap orig = createTagMap(mapType, size); - assertSize(size, orig); + @EnumSource(TagMapScenario.class) + public void copyMany(TagMapScenario scenario) { + int size = scenario.size(); + TagMap orig = scenario.create(); + fillMap(orig, size); + + assertSize(scenario.size() + size, orig); TagMap copy = orig.copy(); orig.clear(); // doing this to make sure that copied isn't modified @@ -487,7 +587,10 @@ public void copyMany(TagMapType mapType) { for (int i = 0; i < size; ++i) { assertEntry(key(i), value(i), copy); } - assertSize(size, copy); + assertSize(scenario.size() + size, copy); + + checkIntegrity(orig); + checkIntegrity(copy); } @ParameterizedTest @@ -503,6 +606,10 @@ public void immutableCopy(TagMapType mapType) { assertEntry(key(i), value(i), immutableCopy); } assertSize(size, immutableCopy); + assertFrozen(immutableCopy); + + checkIntegrity(orig); + checkIntegrity(immutableCopy); } @ParameterizedTest @@ -517,6 +624,8 @@ public void replaceALot(TagMapType mapType) { map.put(key(index), altValue(index)); assertEquals(altValue(index), map.get(key(index))); } + + checkIntegrity(map); } @ParameterizedTest @@ -532,6 +641,9 @@ public void shareEntry(TagMapTypePair mapTypePair) { if (mapTypePair == TagMapTypePair.BOTH_OPTIMIZED) { assertSame(orig.getEntry("foo"), dest.getEntry("foo")); } + + checkIntegrity(orig); + checkIntegrity(dest); } @ParameterizedTest @@ -553,6 +665,28 @@ public void putAll_clobberAll(TagMapTypePair mapTypePair) { assertEntry(key(i), value(i), dest); } assertSize(size, dest); + + checkIntegrity(orig); + checkIntegrity(dest); + } + + @ParameterizedTest + @EnumSource(TagMapScenario.class) + public void putAll_cloberAll(TagMapScenario scenario) { + int size = scenario.size(); + TagMap orig = scenario.create(); + assertSize(size, orig); + + TagMap dest = scenario.create(); + assertSize(size, dest); + + dest.putAll(orig); + + assertSize(size, orig); + assertSize(size, dest); + + checkIntegrity(orig); + checkIntegrity(dest); } @ParameterizedTest @@ -575,6 +709,9 @@ public void putAll_clobberAndExtras(TagMapTypePair mapTypePair) { } assertSize(size, dest); + + checkIntegrity(orig); + checkIntegrity(dest); } @ParameterizedTest @@ -599,6 +736,8 @@ public void removeMany(TagMapType mapType) { } assertEmpty(map); + + checkIntegrity(map); } @ParameterizedTest @@ -617,6 +756,8 @@ public void fillMap(TagMapType mapType) { assertEquals(Integer.valueOf(i), hashMap.remove(key(i))); } assertTrue(hashMap.isEmpty()); + + checkIntegrity(map); } @ParameterizedTest @@ -635,6 +776,8 @@ public void fillStringMap(TagMapType mapType) { assertEquals(Integer.toString(i), hashMap.remove(key(i))); } assertTrue(hashMap.isEmpty()); + + checkIntegrity(map); } @ParameterizedTest @@ -656,6 +799,8 @@ public void iterator(TagMapType mapType) { // no extraneous keys assertTrue(keys.isEmpty()); + + checkIntegrity(map); } @ParameterizedTest @@ -674,6 +819,8 @@ public void forEachConsumer(TagMapType mapType) { // no extraneous keys assertTrue(keys.isEmpty()); + + checkIntegrity(map); } @ParameterizedTest @@ -692,6 +839,8 @@ public void forEachBiConsumer(TagMapType mapType) { // no extraneous keys assertTrue(keys.isEmpty()); + + checkIntegrity(map); } @ParameterizedTest @@ -710,6 +859,8 @@ public void forEachTriConsumer(TagMapType mapType) { // no extraneous keys assertTrue(keys.isEmpty()); + + checkIntegrity(map); } @ParameterizedTest @@ -727,6 +878,8 @@ public void entrySet(TagMapType mapType) { assertTrue(expectedKeys.remove(entry.getKey())); } assertTrue(expectedKeys.isEmpty()); + + checkIntegrity(map); } @ParameterizedTest @@ -744,6 +897,8 @@ public void keySet(TagMapType mapType) { assertTrue(expectedKeys.remove(key)); } assertTrue(expectedKeys.isEmpty()); + + checkIntegrity(map); } @ParameterizedTest @@ -761,6 +916,8 @@ public void values(TagMapType mapType) { assertTrue(expectedValues.remove(value)); } assertTrue(expectedValues.isEmpty()); + + checkIntegrity(map); } @ParameterizedTest @@ -768,11 +925,29 @@ public void values(TagMapType mapType) { public void _toString(TagMapType mapType) { int size = 4; TagMap map = createTagMap(mapType, size); - assertEquals("{key-1=value-1, key-0=value-0, key-3=value-3, key-2=value-2}", map.toString()); + + String str = map.toString(); + + for (int i = 0; i < size; ++i) { + assertTrue(str.contains(key(i) + "=" + value(i))); + } + } + + @ParameterizedTest + @ValueSource(ints = {0, 5, 25, 125}) + public void _toInternalString(int size) { + OptimizedTagMap tagMap = new OptimizedTagMap(); + fillMap(tagMap, size); + + String str = tagMap.toInternalString(); + + for (int i = 0; i < size; ++i) { + assertTrue(str.contains(key(i) + "=" + value(i))); + } } static int randomSize() { - return ThreadLocalRandom.current().nextInt(1, MANY_SIZE); + return ThreadLocalRandom.current().nextInt(16, MANY_SIZE); } static TagMap createTagMap(TagMapType mapType) { @@ -781,10 +956,14 @@ static TagMap createTagMap(TagMapType mapType) { static TagMap createTagMap(TagMapType mapType, int size) { TagMap map = mapType.create(); + fillMap(map, size); + return map; + } + + static void fillMap(TagMap map, int size) { for (int i = 0; i < size; ++i) { map.set(key(i), value(i)); } - return map; } static Set expectedKeys(int size) { @@ -827,7 +1006,36 @@ static int count(Iterator iter) { return count; } - static void assertEntry(String key, String value, TagMap map) { + static final void assertBoolean(boolean expected, TagMap map, String key) { + assertEquals(expected, map.getBoolean(key)); + assertEquals(expected, map.getBooleanOrDefault(key, !expected)); + } + + static final void assertInt(int expected, TagMap map, String key) { + assertEquals(expected, map.getInt(key)); + assertEquals(expected, map.getIntOrDefault(key, Integer.MAX_VALUE)); + } + + static final void assertLong(long expected, TagMap map, String key) { + assertEquals(expected, map.getLong(key)); + assertEquals(expected, map.getLongOrDefault(key, Long.MAX_VALUE)); + } + + static final void assertFloat(float expected, TagMap map, String key) { + assertEquals(expected, map.getFloat(key)); + assertEquals(expected, map.getFloatOrDefault(key, Float.MAX_VALUE)); + } + + static final void assertDouble(double expected, TagMap map, String key) { + assertEquals(expected, map.getDouble(key)); + assertEquals(expected, map.getDoubleOrDefault(key, Double.MAX_VALUE)); + } + + static final void assertString(String expected, TagMap map, String key) { + assertEquals(expected, map.getString(key)); + } + + static final void assertEntry(String key, String value, TagMap map) { TagMap.Entry entry = map.getEntry(key); assertNotNull(entry); @@ -841,25 +1049,37 @@ static void assertEntry(String key, String value, TagMap map) { assertEquals(value, entry.stringValue()); assertTrue(map.containsKey(key)); - assertTrue(map.containsKey(key)); + assertTrue(map.keySet().contains(key)); assertTrue(map.containsValue(value)); - assertTrue(map.containsValue(value)); + assertTrue(map.values().contains(value)); } - static void assertSize(int size, TagMap map) { + static final void assertSize(int size, TagMap map) { if (map instanceof OptimizedTagMap) { assertEquals(size, ((OptimizedTagMap) map).computeSize()); } assertEquals(size, map.size()); assertEquals(size, count(map)); - assertEquals(size, map.size()); - assertEquals(size, map.size()); + assertEquals(size, map.keySet().size()); + assertEquals(size, map.values().size()); assertEquals(size, count(map.keySet())); assertEquals(size, count(map.values())); } + static void assertEmptiness(TagMapScenario scenario, TagMap map) { + assertEmptiness(scenario.size() == 0, map); + } + + static void assertEmptiness(boolean expectEmpty, TagMap map) { + if (expectEmpty) { + assertEmpty(map); + } else { + assertNotEmpty(map); + } + } + static void assertNotEmpty(TagMap map) { if (map instanceof OptimizedTagMap) { assertFalse(((OptimizedTagMap) map).checkIfEmpty()); @@ -893,4 +1113,11 @@ static void assertFrozen(Runnable runnable) { } assertNotNull(ex); } + + static void checkIntegrity(TagMap map) { + if (map instanceof OptimizedTagMap) { + OptimizedTagMap optMap = (OptimizedTagMap) map; + optMap.checkIntegrity(); + } + } } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapType.java b/internal-api/src/test/java/datadog/trace/api/TagMapType.java index a5c07410c48..512473d3439 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapType.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapType.java @@ -1,8 +1,8 @@ package datadog.trace.api; public enum TagMapType { - OPTIMIZED(new OptimizedTagMapFactory()), - LEGACY(new LegacyTagMapFactory()); + OPTIMIZED(OptimizedTagMapFactory.INSTANCE), + LEGACY(LegacyTagMapFactory.INSTANCE); final TagMapFactory factory;