|
| 1 | +package datadog.context; |
| 2 | + |
| 3 | +import static datadog.context.Context.current; |
| 4 | +import static datadog.context.Context.root; |
| 5 | +import static datadog.context.ContextHelpers.CURRENT; |
| 6 | +import static datadog.context.ContextHelpers.combine; |
| 7 | +import static datadog.context.ContextHelpers.findAll; |
| 8 | +import static datadog.context.ContextHelpers.findFirst; |
| 9 | +import static datadog.context.ContextTest.BOOLEAN_KEY; |
| 10 | +import static datadog.context.ContextTest.FLOAT_KEY; |
| 11 | +import static datadog.context.ContextTest.STRING_KEY; |
| 12 | +import static java.util.Arrays.asList; |
| 13 | +import static java.util.Collections.emptyList; |
| 14 | +import static java.util.Collections.singleton; |
| 15 | +import static java.util.logging.Level.ALL; |
| 16 | +import static java.util.logging.Level.INFO; |
| 17 | +import static java.util.logging.Level.SEVERE; |
| 18 | +import static java.util.logging.Level.WARNING; |
| 19 | +import static org.junit.jupiter.api.Assertions.assertEquals; |
| 20 | +import static org.junit.jupiter.api.Assertions.assertIterableEquals; |
| 21 | +import static org.junit.jupiter.api.Assertions.assertNotNull; |
| 22 | +import static org.junit.jupiter.api.Assertions.assertNull; |
| 23 | +import static org.junit.jupiter.api.Assertions.assertThrows; |
| 24 | +import static org.junit.jupiter.params.provider.Arguments.arguments; |
| 25 | + |
| 26 | +import java.util.function.BinaryOperator; |
| 27 | +import java.util.logging.Level; |
| 28 | +import java.util.stream.Stream; |
| 29 | +import org.junit.jupiter.api.BeforeAll; |
| 30 | +import org.junit.jupiter.api.Test; |
| 31 | +import org.junit.jupiter.params.ParameterizedTest; |
| 32 | +import org.junit.jupiter.params.provider.Arguments; |
| 33 | +import org.junit.jupiter.params.provider.MethodSource; |
| 34 | + |
| 35 | +class ContextHelpersTest { |
| 36 | + private static final Object CARRIER_1 = new Object(); |
| 37 | + private static final Object CARRIER_2 = new Object(); |
| 38 | + private static final Object UNSET_CARRIER = new Object(); |
| 39 | + private static final Object NON_CARRIER = new Object(); |
| 40 | + private static final String VALUE_1 = "value1"; |
| 41 | + private static final String VALUE_2 = "value2"; |
| 42 | + |
| 43 | + @BeforeAll |
| 44 | + static void init() { |
| 45 | + Context context1 = root().with(STRING_KEY, VALUE_1); |
| 46 | + context1.attachTo(CARRIER_1); |
| 47 | + |
| 48 | + Context context2 = root().with(STRING_KEY, VALUE_2); |
| 49 | + context2.attachTo(CARRIER_2); |
| 50 | + |
| 51 | + root().attachTo(UNSET_CARRIER); |
| 52 | + } |
| 53 | + |
| 54 | + @ParameterizedTest |
| 55 | + @MethodSource("findFirstArguments") |
| 56 | + void testFindFirst(Object[] carriers, String expected) { |
| 57 | + assertEquals(expected, findFirst(STRING_KEY, carriers), "Cannot find first value"); |
| 58 | + } |
| 59 | + |
| 60 | + static Stream<Arguments> findFirstArguments() { |
| 61 | + return Stream.of( |
| 62 | + arguments(emptyArray(), null), |
| 63 | + arguments(arrayOf(NON_CARRIER), null), |
| 64 | + arguments(arrayOf(UNSET_CARRIER), null), |
| 65 | + arguments(arrayOf(CARRIER_1), VALUE_1), |
| 66 | + arguments(arrayOf(CARRIER_1, CARRIER_2), VALUE_1), |
| 67 | + arguments(arrayOf(NON_CARRIER, CARRIER_1), VALUE_1), |
| 68 | + arguments(arrayOf(UNSET_CARRIER, CARRIER_1), VALUE_1), |
| 69 | + arguments(arrayOf(CARRIER_1, NON_CARRIER), VALUE_1), |
| 70 | + arguments(arrayOf(CARRIER_1, UNSET_CARRIER), VALUE_1)); |
| 71 | + } |
| 72 | + |
| 73 | + @ParameterizedTest |
| 74 | + @MethodSource("findAllArguments") |
| 75 | + void testFindAll(Object[] carriers, Iterable<String> expected) { |
| 76 | + assertIterableEquals(expected, findAll(STRING_KEY, carriers), "Cannot find all values"); |
| 77 | + } |
| 78 | + |
| 79 | + static Stream<Arguments> findAllArguments() { |
| 80 | + return Stream.of( |
| 81 | + arguments(emptyArray(), emptyList()), |
| 82 | + arguments(arrayOf(CARRIER_1), singleton(VALUE_1)), |
| 83 | + arguments(arrayOf(CARRIER_1, CARRIER_2), asList(VALUE_1, VALUE_2)), |
| 84 | + arguments(arrayOf(NON_CARRIER, CARRIER_1), singleton(VALUE_1)), |
| 85 | + arguments(arrayOf(UNSET_CARRIER, CARRIER_1), singleton(VALUE_1)), |
| 86 | + arguments(arrayOf(CARRIER_1, NON_CARRIER), singleton(VALUE_1)), |
| 87 | + arguments(arrayOf(CARRIER_1, UNSET_CARRIER), singleton(VALUE_1))); |
| 88 | + } |
| 89 | + |
| 90 | + @Test |
| 91 | + void testNullCarriers() { |
| 92 | + assertThrows( |
| 93 | + NullPointerException.class, () -> findFirst(null, CARRIER_1), "Should fail on null key"); |
| 94 | + assertThrows( |
| 95 | + NullPointerException.class, |
| 96 | + () -> findFirst(STRING_KEY, (Object) null), |
| 97 | + "Should fail on null context"); |
| 98 | + assertThrows( |
| 99 | + NullPointerException.class, |
| 100 | + () -> findFirst(STRING_KEY, null, CARRIER_1), |
| 101 | + "Should fail on null context"); |
| 102 | + assertThrows( |
| 103 | + NullPointerException.class, () -> findAll(null, CARRIER_1), "Should fail on null key"); |
| 104 | + assertThrows( |
| 105 | + NullPointerException.class, |
| 106 | + () -> findAll(STRING_KEY, (Object) null), |
| 107 | + "Should fail on null context"); |
| 108 | + assertThrows( |
| 109 | + NullPointerException.class, |
| 110 | + () -> findAll(STRING_KEY, null, CARRIER_1), |
| 111 | + "Should fail on null context"); |
| 112 | + } |
| 113 | + |
| 114 | + @Test |
| 115 | + void testCurrent() { |
| 116 | + assertEquals(root(), current(), "Current context is already set"); |
| 117 | + Context context = root().with(STRING_KEY, VALUE_1); |
| 118 | + try (ContextScope ignored = context.attach()) { |
| 119 | + assertEquals( |
| 120 | + VALUE_1, findFirst(STRING_KEY, CURRENT), "Failed to get value from current context"); |
| 121 | + assertIterableEquals( |
| 122 | + singleton(VALUE_1), |
| 123 | + findAll(STRING_KEY, CURRENT), |
| 124 | + "Failed to get value from current context"); |
| 125 | + } |
| 126 | + assertEquals(root(), current(), "Current context stayed attached"); |
| 127 | + } |
| 128 | + |
| 129 | + @Test |
| 130 | + void testCombine() { |
| 131 | + Context context1 = root().with(STRING_KEY, VALUE_1).with(BOOLEAN_KEY, true); |
| 132 | + Context context2 = root().with(STRING_KEY, VALUE_2).with(FLOAT_KEY, 3.14F); |
| 133 | + Context context3 = root(); |
| 134 | + Context context4 = root().with(FLOAT_KEY, 567F); |
| 135 | + |
| 136 | + Context combined = combine(context1, context2, context3, context4); |
| 137 | + assertEquals(VALUE_1, combined.get(STRING_KEY), "First duplicate value should be kept"); |
| 138 | + assertEquals(true, combined.get(BOOLEAN_KEY), "Values from first context should be kept"); |
| 139 | + assertEquals(3.14f, combined.get(FLOAT_KEY), "Values from second context should be kept"); |
| 140 | + } |
| 141 | + |
| 142 | + @Test |
| 143 | + void testCombiner() { |
| 144 | + ContextKey<ErrorStats> errorKey = ContextKey.named("error"); |
| 145 | + Context context1 = root().with(errorKey, ErrorStats.from(INFO, 12)).with(STRING_KEY, VALUE_1); |
| 146 | + Context context2 = root().with(errorKey, ErrorStats.from(SEVERE, 1)).with(FLOAT_KEY, 3.14F); |
| 147 | + Context context3 = root().with(errorKey, ErrorStats.from(WARNING, 6)).with(BOOLEAN_KEY, true); |
| 148 | + |
| 149 | + BinaryOperator<Context> errorStatsMerger = |
| 150 | + (left, right) -> { |
| 151 | + ErrorStats mergedStats = ErrorStats.merge(left.get(errorKey), right.get(errorKey)); |
| 152 | + return left.with(errorKey, mergedStats); |
| 153 | + }; |
| 154 | + Context combined = combine(errorStatsMerger, context1, context2, context3); |
| 155 | + ErrorStats combinedStats = combined.get(errorKey); |
| 156 | + assertNotNull(combinedStats, "Failed to combined error stats"); |
| 157 | + assertEquals(19, combinedStats.errorCount, "Failed to combine error stats"); |
| 158 | + assertEquals(SEVERE, combinedStats.maxLevel, "Failed to combine error stats"); |
| 159 | + assertNull(combined.get(STRING_KEY), "Combiner should drop any other context values"); |
| 160 | + assertNull(combined.get(FLOAT_KEY), "Combiner should drop any other context values"); |
| 161 | + assertNull(combined.get(BOOLEAN_KEY), "Combiner should drop any other context values"); |
| 162 | + } |
| 163 | + |
| 164 | + @Test |
| 165 | + void testNullCombine() { |
| 166 | + assertThrows( |
| 167 | + NullPointerException.class, |
| 168 | + () -> combine((BinaryOperator<Context>) null, root()), |
| 169 | + "Should fail on null combiner"); |
| 170 | + assertThrows( |
| 171 | + NullPointerException.class, |
| 172 | + () -> combine((left, right) -> left, (Context) null), |
| 173 | + "Should fail on null context"); |
| 174 | + } |
| 175 | + |
| 176 | + private static class ErrorStats { |
| 177 | + int errorCount; |
| 178 | + Level maxLevel; |
| 179 | + |
| 180 | + public ErrorStats() { |
| 181 | + this.errorCount = 0; |
| 182 | + this.maxLevel = ALL; |
| 183 | + } |
| 184 | + |
| 185 | + public static ErrorStats from(Level logLevel, int count) { |
| 186 | + ErrorStats stats = new ErrorStats(); |
| 187 | + stats.errorCount = count; |
| 188 | + stats.maxLevel = logLevel; |
| 189 | + return stats; |
| 190 | + } |
| 191 | + |
| 192 | + public static ErrorStats merge(ErrorStats a, ErrorStats b) { |
| 193 | + if (a == null) { |
| 194 | + return b; |
| 195 | + } |
| 196 | + Level maxLevel = a.maxLevel.intValue() > b.maxLevel.intValue() ? a.maxLevel : b.maxLevel; |
| 197 | + return from(maxLevel, a.errorCount + b.errorCount); |
| 198 | + } |
| 199 | + } |
| 200 | + |
| 201 | + private static Object[] emptyArray() { |
| 202 | + return new Object[0]; |
| 203 | + } |
| 204 | + |
| 205 | + private static Object[] arrayOf(Object... objects) { |
| 206 | + return objects; |
| 207 | + } |
| 208 | +} |
0 commit comments