|
| 1 | +package datadog.context.propagation; |
| 2 | + |
| 3 | +import static datadog.context.Context.root; |
| 4 | +import static datadog.context.propagation.Concern.DEFAULT_PRIORITY; |
| 5 | +import static java.util.Objects.requireNonNull; |
| 6 | +import static org.junit.jupiter.api.Assertions.assertEquals; |
| 7 | +import static org.junit.jupiter.api.Assertions.assertFalse; |
| 8 | +import static org.junit.jupiter.api.Assertions.assertNotNull; |
| 9 | +import static org.junit.jupiter.api.Assertions.assertTrue; |
| 10 | + |
| 11 | +import datadog.context.Context; |
| 12 | +import datadog.context.ContextKey; |
| 13 | +import java.util.HashMap; |
| 14 | +import java.util.Map; |
| 15 | +import java.util.function.BiConsumer; |
| 16 | +import javax.annotation.Nullable; |
| 17 | +import org.junit.jupiter.api.AfterEach; |
| 18 | +import org.junit.jupiter.api.BeforeEach; |
| 19 | +import org.junit.jupiter.api.Test; |
| 20 | + |
| 21 | +class PropagatorsTest { |
| 22 | + static final MapCarrierAccessor ACCESSOR = new MapCarrierAccessor(); |
| 23 | + |
| 24 | + static final Concern TRACING = Concern.named("tracing"); |
| 25 | + static final ContextKey<String> TRACING_KEY = ContextKey.named("tracing"); |
| 26 | + static final Propagator TRACING_PROPAGATOR = new BasicPropagator(TRACING_KEY, "tracing"); |
| 27 | + |
| 28 | + static final Concern IAST = Concern.named("iast"); |
| 29 | + static final ContextKey<String> IAST_KEY = ContextKey.named("iast"); |
| 30 | + static final Propagator IAST_PROPAGATOR = new BasicPropagator(IAST_KEY, "iast"); |
| 31 | + |
| 32 | + static final Concern DEBUGGER = Concern.withPriority("debugger", DEFAULT_PRIORITY - 10); |
| 33 | + static final ContextKey<String> DEBUGGER_KEY = ContextKey.named("debugger"); |
| 34 | + static final DependentPropagator DEBUGGER_PROPAGATOR = |
| 35 | + new DependentPropagator(DEBUGGER_KEY, "debugger", TRACING_KEY); |
| 36 | + |
| 37 | + static final Concern PROFILING = Concern.withPriority("profiling", DEFAULT_PRIORITY + 10); |
| 38 | + static final ContextKey<String> PROFILING_KEY = ContextKey.named("profiling"); |
| 39 | + static final DependentPropagator PROFILING_PROPAGATOR = |
| 40 | + new DependentPropagator(PROFILING_KEY, "profiling", TRACING_KEY); |
| 41 | + |
| 42 | + static final Context CONTEXT = |
| 43 | + root() |
| 44 | + .with(TRACING_KEY, "sampled") |
| 45 | + .with(IAST_KEY, "standalone") |
| 46 | + .with(DEBUGGER_KEY, "debug") |
| 47 | + .with(PROFILING_KEY, "profile"); |
| 48 | + |
| 49 | + static class MapCarrierAccessor |
| 50 | + implements CarrierSetter<Map<String, String>>, CarrierVisitor<Map<String, String>> { |
| 51 | + @Override |
| 52 | + public void set(@Nullable Map<String, String> carrier, String key, String value) { |
| 53 | + if (carrier != null && key != null) { |
| 54 | + carrier.put(key, value); |
| 55 | + } |
| 56 | + } |
| 57 | + |
| 58 | + @Override |
| 59 | + public void forEachKeyValue(Map<String, String> carrier, BiConsumer<String, String> visitor) { |
| 60 | + carrier.forEach(visitor); |
| 61 | + } |
| 62 | + } |
| 63 | + |
| 64 | + static class BasicPropagator implements Propagator { |
| 65 | + private final ContextKey<String> contextKey; |
| 66 | + private final String carrierKey; |
| 67 | + |
| 68 | + public BasicPropagator(ContextKey<String> contextKey, String carrierKey) { |
| 69 | + this.contextKey = requireNonNull(contextKey); |
| 70 | + this.carrierKey = requireNonNull(carrierKey); |
| 71 | + } |
| 72 | + |
| 73 | + @Override |
| 74 | + public <C> void inject(Context context, C carrier, CarrierSetter<C> setter) { |
| 75 | + String value = context.get(this.contextKey); |
| 76 | + if (value != null) { |
| 77 | + setter.set(carrier, this.carrierKey, value); |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + @Override |
| 82 | + public <C> Context extract(Context context, C carrier, CarrierVisitor<C> visitor) { |
| 83 | + String[] valueRef = new String[1]; |
| 84 | + visitor.forEachKeyValue( |
| 85 | + carrier, |
| 86 | + (key, value) -> { |
| 87 | + if (this.carrierKey.equals(key)) { |
| 88 | + valueRef[0] = value; |
| 89 | + } |
| 90 | + }); |
| 91 | + if (valueRef[0] != null) { |
| 92 | + context = context.with(this.contextKey, valueRef[0]); |
| 93 | + } |
| 94 | + return context; |
| 95 | + } |
| 96 | + } |
| 97 | + |
| 98 | + static class DependentPropagator extends BasicPropagator implements Propagator { |
| 99 | + private final ContextKey<String> requiredContextKey; |
| 100 | + private boolean keyFound; |
| 101 | + |
| 102 | + public DependentPropagator( |
| 103 | + ContextKey<String> contextKey, String carrierKey, ContextKey<String> requiredContextKey) { |
| 104 | + super(contextKey, carrierKey); |
| 105 | + this.requiredContextKey = requiredContextKey; |
| 106 | + this.keyFound = false; |
| 107 | + } |
| 108 | + |
| 109 | + @Override |
| 110 | + public <C> Context extract(Context context, C carrier, CarrierVisitor<C> visitor) { |
| 111 | + this.keyFound = context.get(this.requiredContextKey) != null; |
| 112 | + return super.extract(context, carrier, visitor); |
| 113 | + } |
| 114 | + |
| 115 | + public void reset() { |
| 116 | + this.keyFound = false; |
| 117 | + } |
| 118 | + } |
| 119 | + |
| 120 | + @BeforeEach |
| 121 | + @AfterEach |
| 122 | + void resetPropagators() { |
| 123 | + Propagators.reset(); |
| 124 | + DEBUGGER_PROPAGATOR.reset(); |
| 125 | + PROFILING_PROPAGATOR.reset(); |
| 126 | + } |
| 127 | + |
| 128 | + @Test |
| 129 | + void testDefaultPropagator() { |
| 130 | + Propagator noopPropagator = Propagators.defaultPropagator(); |
| 131 | + assertNotNull( |
| 132 | + noopPropagator, "Default propagator should not be null when no propagator is registered"); |
| 133 | + assertInjectExtractContext(CONTEXT, noopPropagator); |
| 134 | + |
| 135 | + Propagators.register(TRACING, TRACING_PROPAGATOR); |
| 136 | + Propagator single = Propagators.defaultPropagator(); |
| 137 | + assertInjectExtractContext(CONTEXT, single, TRACING_KEY); |
| 138 | + |
| 139 | + Propagators.register(IAST, IAST_PROPAGATOR); |
| 140 | + Propagators.register(DEBUGGER, DEBUGGER_PROPAGATOR); |
| 141 | + Propagators.register(PROFILING, PROFILING_PROPAGATOR); |
| 142 | + Propagator composite = Propagators.defaultPropagator(); |
| 143 | + assertInjectExtractContext( |
| 144 | + CONTEXT, composite, TRACING_KEY, IAST_KEY, DEBUGGER_KEY, PROFILING_KEY); |
| 145 | + assertFalse( |
| 146 | + DEBUGGER_PROPAGATOR.keyFound, |
| 147 | + "Debugger propagator should have run before tracing propagator"); |
| 148 | + assertTrue( |
| 149 | + PROFILING_PROPAGATOR.keyFound, |
| 150 | + "Profiling propagator should have run after tracing propagator"); |
| 151 | + |
| 152 | + Propagator cached = Propagators.defaultPropagator(); |
| 153 | + assertEquals(composite, cached, "default propagator should be cached"); |
| 154 | + } |
| 155 | + |
| 156 | + @Test |
| 157 | + void testForConcern() { |
| 158 | + // Test when not registered |
| 159 | + Propagator propagator = Propagators.forConcern(TRACING); |
| 160 | + assertNotNull(propagator, "Propagator should not be null when no propagator is registered"); |
| 161 | + assertNoopPropagator(propagator); |
| 162 | + // Test when registered |
| 163 | + Propagators.register(TRACING, TRACING_PROPAGATOR); |
| 164 | + propagator = Propagators.forConcern(TRACING); |
| 165 | + assertNotNull(propagator, "Propagator should not be null when registered"); |
| 166 | + assertInjectExtractContext(CONTEXT, propagator, TRACING_KEY); |
| 167 | + } |
| 168 | + |
| 169 | + @Test |
| 170 | + void testForConcerns() { |
| 171 | + // Test when none registered |
| 172 | + Propagator propagator = Propagators.forConcerns(TRACING, IAST); |
| 173 | + assertNotNull(propagator, "Propagator should not be null when no propagator is registered"); |
| 174 | + assertNoopPropagator(propagator); |
| 175 | + // Test when only one is registered |
| 176 | + Propagators.register(TRACING, TRACING_PROPAGATOR); |
| 177 | + propagator = Propagators.forConcerns(TRACING, IAST); |
| 178 | + assertNotNull(propagator, "Propagator should not be null when one is registered"); |
| 179 | + assertInjectExtractContext(CONTEXT, propagator, TRACING_KEY); |
| 180 | + // Test when all registered |
| 181 | + Propagators.register(IAST, IAST_PROPAGATOR); |
| 182 | + propagator = Propagators.forConcerns(TRACING, IAST); |
| 183 | + assertNotNull(propagator, "Propagator should not be null when all are registered"); |
| 184 | + assertInjectExtractContext(CONTEXT, propagator, TRACING_KEY, IAST_KEY); |
| 185 | + // Test propagator order follow the given concerns order despite concern priority |
| 186 | + Propagators.register(DEBUGGER, DEBUGGER_PROPAGATOR); |
| 187 | + Propagators.register(PROFILING, PROFILING_PROPAGATOR); |
| 188 | + propagator = Propagators.forConcerns(PROFILING, TRACING, DEBUGGER); |
| 189 | + assertInjectExtractContext(CONTEXT, propagator, PROFILING_KEY, TRACING_KEY, DEBUGGER_KEY); |
| 190 | + assertFalse( |
| 191 | + PROFILING_PROPAGATOR.keyFound, |
| 192 | + "Profiling propagator should have run before tracing propagator"); |
| 193 | + assertTrue( |
| 194 | + DEBUGGER_PROPAGATOR.keyFound, |
| 195 | + "Debugger propagator should have run before tracing propagator"); |
| 196 | + } |
| 197 | + |
| 198 | + @Test |
| 199 | + void testNoopPropagator() { |
| 200 | + Propagator noopPropagator = Propagators.noop(); |
| 201 | + assertNotNull(noopPropagator, "noop propagator should not be null"); |
| 202 | + assertNoopPropagator(noopPropagator); |
| 203 | + } |
| 204 | + |
| 205 | + void assertNoopPropagator(Propagator noopPropagator) { |
| 206 | + Map<String, String> carrier = new HashMap<>(); |
| 207 | + noopPropagator.inject(CONTEXT, carrier, ACCESSOR); |
| 208 | + assertTrue(carrier.isEmpty(), "carrier should be empty"); |
| 209 | + Context extracted = noopPropagator.extract(root(), carrier, ACCESSOR); |
| 210 | + assertEquals(root(), extracted, "extracted context should be empty"); |
| 211 | + } |
| 212 | + |
| 213 | + void assertInjectExtractContext(Context context, Propagator propagator, ContextKey<?>... keys) { |
| 214 | + Map<String, String> carrier = new HashMap<>(); |
| 215 | + propagator.inject(context, carrier, ACCESSOR); |
| 216 | + Context extracted = propagator.extract(root(), carrier, ACCESSOR); |
| 217 | + for (ContextKey<?> key : keys) { |
| 218 | + assertEquals( |
| 219 | + context.get(key), extracted.get(key), "Key " + key + " not injected nor extracted"); |
| 220 | + } |
| 221 | + } |
| 222 | +} |
0 commit comments