diff --git a/dd-trace-core/src/test/java/datadog/trace/core/propagation/AbstractOTBaggageTest.java b/dd-trace-core/src/test/java/datadog/trace/core/propagation/AbstractOTBaggageTest.java new file mode 100644 index 00000000000..8eed302039d --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/core/propagation/AbstractOTBaggageTest.java @@ -0,0 +1,111 @@ +package datadog.trace.core.propagation; + +import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_MAX_BYTES; +import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_MAX_ITEMS; +import static datadog.trace.bootstrap.instrumentation.api.ContextVisitors.stringValuesMap; +import static datadog.trace.core.propagation.HttpCodecTestHelper.baggageItems; +import static datadog.trace.core.propagation.HttpCodecTestHelper.generateBaggageItems; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import datadog.trace.bootstrap.instrumentation.api.TagContext; +import datadog.trace.test.junit.utils.config.WithConfig; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import org.junit.jupiter.api.Test; + +/** + * Behavior contract for the baggage limits enforced by {@link ContextInterpreter#addBaggageItem}. + * The limits are applied after the propagation style has parsed the key and value, so they are + * style independent: every codec carrying caller baggage runs this suite as a {@code @Nested} class + * of its extractor test, supplying only the wire format. + */ +abstract class AbstractOTBaggageTest { + + /** Returns the extractor under test, built by the enclosing extractor test. */ + protected abstract HttpCodec.Extractor extractor(); + + /** + * Returns the given baggage items, in order, in this style's wire format. A repeated key must be + * spelled so that the style reads it back as the same baggage key. + */ + protected abstract Map baggageHeaders(List> items); + + /** Returns the headers carrying {@code itemCount} generated baggage items. */ + protected final Map generateBaggageHeaders(int itemCount) { + return baggageHeaders(generateBaggageItems(itemCount)); + } + + /** + * Extracts the given headers and returns the baggage of the resulting context. Headers carrying + * nothing but baggage create no context at all once every item is dropped, which is reported as + * empty baggage rather than as a missing context. + */ + protected final Map extractBaggage(Map headers) { + TagContext context = this.extractor().extract(headers, stringValuesMap()); + return context == null ? emptyMap() : context.getBaggage(); + } + + @Test + @WithConfig(key = TRACE_BAGGAGE_MAX_ITEMS, value = "3") + void stopsAtItemLimit() { + assertEquals(3, extractBaggage(generateBaggageHeaders(50)).size()); + } + + @Test + @WithConfig(key = TRACE_BAGGAGE_MAX_BYTES, value = "24") + void stopsAtByteLimit() { + // with single digit indices each stored item is "keyN" + "valueN" = 10 bytes, so 2 fit in 24 + // bytes and a third would take the total to 30 + assertEquals(2, extractBaggage(generateBaggageHeaders(10)).size()); + } + + @Test + @WithConfig(key = TRACE_BAGGAGE_MAX_BYTES, value = "24") + @WithConfig(key = TRACE_BAGGAGE_MAX_ITEMS, value = "2") + void chargesRepeatedKeyOnce() { + Map baggage = + extractBaggage( + baggageHeaders(baggageItems("key0", "val0", "a", "0123456789", "key0", "val1"))); + + Map expected = new HashMap<>(); + expected.put("key0", "val1"); + expected.put("a", "0123456789"); + assertEquals(expected, baggage); + } + + @Test + @WithConfig(key = TRACE_BAGGAGE_MAX_BYTES, value = "8") + void chargesEncodedValueSize() { + // the budget is charged before decoding, so the 9 character raw value does not fit + Map baggage = + extractBaggage(baggageHeaders(baggageItems("a", "b", "c", "%E2%99%A5"))); + + assertEquals(singletonMap("a", "b"), baggage); + } + + @Test + @WithConfig(key = TRACE_BAGGAGE_MAX_BYTES, value = "3") + void chargesLiteralUtf8ByCharacterCount() { + // "a" + "♥" is 2 characters, so the 2 characters of "b" + "c" no longer fit + Map baggage = extractBaggage(baggageHeaders(baggageItems("a", "♥", "b", "c"))); + + assertEquals(singletonMap("a", "♥"), baggage); + } + + @Test + @WithConfig(key = TRACE_BAGGAGE_MAX_ITEMS, value = "0") + void dropsAllBaggageWhenItemLimitIsZero() { + assertTrue(extractBaggage(generateBaggageHeaders(10)).isEmpty()); + } + + @Test + @WithConfig(key = TRACE_BAGGAGE_MAX_BYTES, value = "0") + void dropsAllBaggageWhenByteLimitIsZero() { + assertTrue(extractBaggage(generateBaggageHeaders(10)).isEmpty()); + } +} diff --git a/dd-trace-core/src/test/java/datadog/trace/core/propagation/DatadogHttpExtractorTest.java b/dd-trace-core/src/test/java/datadog/trace/core/propagation/DatadogHttpExtractorTest.java index 6b0fa6e7daf..5be84423457 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/propagation/DatadogHttpExtractorTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/propagation/DatadogHttpExtractorTest.java @@ -1,7 +1,6 @@ package datadog.trace.core.propagation; import static datadog.trace.api.config.TracerConfig.REQUEST_HEADER_TAGS_COMMA_ALLOWED; -import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_MAX_BYTES; import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_MAX_ITEMS; import static datadog.trace.api.sampling.PrioritySampling.UNSET; import static datadog.trace.bootstrap.instrumentation.api.ContextVisitors.stringValuesMap; @@ -12,6 +11,7 @@ import static datadog.trace.core.propagation.DatadogHttpCodec.SPAN_ID_KEY; import static datadog.trace.core.propagation.DatadogHttpCodec.TRACE_ID_KEY; import static datadog.trace.core.propagation.HttpCodecTestHelper.headers; +import static datadog.trace.core.propagation.HttpCodecTestHelper.otBaggageHeaders; import static datadog.trace.test.junit.utils.converter.TraceIdConverter.TRACE_ID_MAX_PLUS_1; import static java.util.Collections.singletonMap; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -34,8 +34,11 @@ import datadog.trace.test.junit.utils.converter.TraceIdConverter; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.function.Supplier; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.converter.ConvertWith; @@ -328,117 +331,31 @@ void baggageIsMappedOnContextCreation( } } - @Test - @WithConfig(key = TRACE_BAGGAGE_MAX_ITEMS, value = "3") - void extractOtBaggageStopsAtItemLimit() { - Map headers = otBaggageHeaders(50); - headers.put(SOME_CUSTOM_BAGGAGE_HEADER, "mappedBaggageValue"); - - TagContext context = this.extractor.extract(headers, stringValuesMap()); - - assertEquals(3, context.getBaggage().size()); - } - - @Test - @WithConfig(key = TRACE_BAGGAGE_MAX_ITEMS, value = "1") - void extractMappedBaggageStopsAtItemLimit() { - Map headers = otBaggageHeaders(50); - headers.put(SOME_CUSTOM_BAGGAGE_HEADER, "mappedBaggageValue"); - - TagContext context = this.extractor.extract(headers, stringValuesMap()); - - assertEquals(1, context.getBaggage().size()); - } - - @Test - @WithConfig(key = TRACE_BAGGAGE_MAX_BYTES, value = "24") - void extractOtBaggageStopsAtByteLimit() { - // with single digit indices each stored item is "keyN" + "valueN" = 10 bytes, so 2 fit in 24 - // bytes and a third would take the total to 30 - TagContext context = this.extractor.extract(otBaggageHeaders(10), stringValuesMap()); - - assertEquals(2, context.getBaggage().size()); - } - - @Test - @WithConfig(key = TRACE_BAGGAGE_MAX_BYTES, value = "24") - void extractOtBaggageChargesRepeatedKeyOnce() { - // headers are visited in insertion order, so the duplicate key is seen before the last item - Map headers = new LinkedHashMap<>(); - // "key0" + "val0" is 8 bytes, and the duplicate refunds the value it replaces, so the total - // stays at 8 rather than doubling - headers.put(OT_BAGGAGE_PREFIX + "key0", "val0"); - headers.put(OT_BAGGAGE_PREFIX + "KEY0", "val0"); - // leaving room for these 11 bytes, taking the total to 19 - headers.put(OT_BAGGAGE_PREFIX + "a", "0123456789"); - - TagContext context = this.extractor.extract(headers, stringValuesMap()); - - Map expected = new HashMap<>(); - expected.put("key0", "val0"); - expected.put("a", "0123456789"); - assertEquals(expected, context.getBaggage()); - } - @Test @WithConfig(key = TRACE_BAGGAGE_MAX_ITEMS, value = "1") - void extractOtBaggageAllowsReplacementAtItemLimit() { - Map headers = new LinkedHashMap<>(); - headers.put(OT_BAGGAGE_PREFIX + "key0", "old"); - headers.put(OT_BAGGAGE_PREFIX + "KEY0", "replacement"); - - TagContext context = this.extractor.extract(headers, stringValuesMap()); - - assertEquals(singletonMap("key0", "replacement"), context.getBaggage()); - } - - @Test - @WithConfig(key = TRACE_BAGGAGE_MAX_BYTES, value = "24") - void extractOtBaggageChargesOnlyTheDeltaWhenReplacingAValue() { - Map headers = new LinkedHashMap<>(); - headers.put(OT_BAGGAGE_PREFIX + "key0", "val0"); // 8 bytes - // replaces the value, charging the 8 byte difference rather than another 16 bytes - headers.put(OT_BAGGAGE_PREFIX + "KEY0", "012345678901"); - headers.put(OT_BAGGAGE_PREFIX + "a", "0123456"); // 8 bytes, taking the total to exactly 24 - - TagContext context = this.extractor.extract(headers, stringValuesMap()); - - Map expected = new HashMap<>(); - expected.put("key0", "012345678901"); - expected.put("a", "0123456"); - assertEquals(expected, context.getBaggage()); - } - - @Test - @WithConfig(key = TRACE_BAGGAGE_MAX_BYTES, value = "8") - void extractOtBaggageChargesEncodedValueSize() { + void extractMappedBaggageIsSubjectToTheItemLimit() { + // mapped baggage shares the item budget with the baggage read off the wire, so the wire item + // is dropped once the mapped header has claimed the only slot Map headers = new LinkedHashMap<>(); - headers.put(OT_BAGGAGE_PREFIX + "a", "b"); // 2 characters - headers.put(OT_BAGGAGE_PREFIX + "c", "%E2%99%A5"); // 1 character key + 9 character raw value + headers.put(SOME_CUSTOM_BAGGAGE_HEADER, "mappedBaggageValue"); + headers.put(OT_BAGGAGE_PREFIX + "wireKey", "wireValue"); TagContext context = this.extractor.extract(headers, stringValuesMap()); - assertEquals(singletonMap("a", "b"), context.getBaggage()); + assertEquals(singletonMap(SOME_BAGGAGE, "mappedBaggageValue"), context.getBaggage()); } - @Test - @WithConfig(key = TRACE_BAGGAGE_MAX_BYTES, value = "3") - void extractOtBaggageChargesLiteralUtf8ByCharacterCount() { - Map headers = new LinkedHashMap<>(); - headers.put(OT_BAGGAGE_PREFIX + "a", "♥"); // 2 characters - headers.put(OT_BAGGAGE_PREFIX + "b", "c"); // 2 more characters, no longer fits - - TagContext context = this.extractor.extract(headers, stringValuesMap()); - - assertEquals(singletonMap("a", "♥"), context.getBaggage()); - } + @Nested + class BaggageLimits extends AbstractOTBaggageTest { + @Override + protected HttpCodec.Extractor extractor() { + return DatadogHttpExtractorTest.this.extractor; + } - private static Map otBaggageHeaders(int count) { - Map headers = new HashMap<>(); - for (int i = 0; i < count; i++) { - headers.put(OT_BAGGAGE_PREFIX + "key" + i, "value" + i); + @Override + protected Map baggageHeaders(List> items) { + return otBaggageHeaders(OT_BAGGAGE_PREFIX, items); } - return headers; } private static String asString(CharSequence cs) { diff --git a/dd-trace-core/src/test/java/datadog/trace/core/propagation/HaystackHttpExtractorTest.java b/dd-trace-core/src/test/java/datadog/trace/core/propagation/HaystackHttpExtractorTest.java index 794b071f283..6bf02211256 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/propagation/HaystackHttpExtractorTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/propagation/HaystackHttpExtractorTest.java @@ -10,7 +10,9 @@ import static datadog.trace.core.propagation.HaystackHttpCodec.PARENT_ID_KEY; import static datadog.trace.core.propagation.HaystackHttpCodec.SPAN_ID_KEY; import static datadog.trace.core.propagation.HaystackHttpCodec.TRACE_ID_KEY; +import static datadog.trace.core.propagation.HttpCodecTestHelper.generateBaggageItems; import static datadog.trace.core.propagation.HttpCodecTestHelper.headers; +import static datadog.trace.core.propagation.HttpCodecTestHelper.otBaggageHeaders; import static datadog.trace.test.junit.utils.converter.TraceIdConverter.TRACE_ID_MAX_PLUS_1; import static java.util.Collections.singletonMap; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -26,8 +28,11 @@ import datadog.trace.test.junit.utils.config.WithConfig; import datadog.trace.test.junit.utils.converter.TraceIdConverter; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.function.Supplier; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.converter.ConvertWith; import org.tabletest.junit.TableTest; @@ -39,17 +44,17 @@ protected HttpCodec.Extractor newExtractor( return HaystackHttpCodec.newExtractor(config, traceConfigSupplier); } - @Test - @WithConfig(key = TRACE_BAGGAGE_MAX_ITEMS, value = "3") - void extractBaggageStopsAtItemLimit() { - Map headers = new HashMap<>(); - for (int i = 0; i < 50; i++) { - headers.put(OT_BAGGAGE_PREFIX + "key" + i, "value" + i); + @Nested + class BaggageLimits extends AbstractOTBaggageTest { + @Override + protected HttpCodec.Extractor extractor() { + return HaystackHttpExtractorTest.this.extractor; } - TagContext context = this.extractor.extract(headers, stringValuesMap()); - - assertEquals(3, context.getBaggage().size()); + @Override + protected Map baggageHeaders(List> items) { + return otBaggageHeaders(OT_BAGGAGE_PREFIX, items); + } } @Test @@ -57,10 +62,8 @@ void extractBaggageStopsAtItemLimit() { void extractKeepsHaystackIdsWhenBaggageLimitReached() { // the Haystack ids are recorded by the tracer for lossless injection, so caller supplied // Baggage-* headers must not be able to evict them by exhausting the item limit - Map headers = new HashMap<>(); - for (int i = 0; i < 50; i++) { - headers.put(OT_BAGGAGE_PREFIX + "key" + i, "value" + i); - } + Map headers = + new HashMap<>(otBaggageHeaders(OT_BAGGAGE_PREFIX, generateBaggageItems(50))); headers.put(TRACE_ID_KEY, "44617461-646f-6721-0000-000000000001"); headers.put(SPAN_ID_KEY, "44617461-646f-6721-0000-000000000002"); diff --git a/dd-trace-core/src/test/java/datadog/trace/core/propagation/HttpCodecTestHelper.java b/dd-trace-core/src/test/java/datadog/trace/core/propagation/HttpCodecTestHelper.java index a109c935cf7..f7473bf150d 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/propagation/HttpCodecTestHelper.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/propagation/HttpCodecTestHelper.java @@ -2,8 +2,13 @@ import datadog.trace.api.Config; import datadog.trace.api.TraceConfig; +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.function.Supplier; /** Helper class used only for tests to bridge package-private classes */ @@ -17,6 +22,52 @@ public static HttpCodec.Extractor newW3cHttpCodecExtractor( return W3CHttpCodec.newExtractor(config, traceConfigSupplier); } + /** + * Returns the given baggage key and value pairs, in order. A key may be repeated to exercise the + * replacement of a value already recorded. + */ + static List> baggageItems(String... keysAndValues) { + List> items = new ArrayList<>(); + for (int i = 0; i < keysAndValues.length; i += 2) { + items.add(new SimpleImmutableEntry<>(keysAndValues[i], keysAndValues[i + 1])); + } + return items; + } + + /** + * Returns {@code itemCount} generated baggage items, mapping {@code key0} to {@code value0} + * through {@code key(itemCount-1)} to {@code value(itemCount-1)}. + * + *

While the indices stay below 10, every item is exactly 10 characters of key plus value, + * which is what lets the byte limit cases state a limit as a number of items. + */ + static List> generateBaggageItems(int itemCount) { + List> items = new ArrayList<>(); + for (int i = 0; i < itemCount; i++) { + items.add(new SimpleImmutableEntry<>("key" + i, "value" + i)); + } + return items; + } + + /** + * Returns the given baggage items as one prefixed header per item, preserving order. + * + *

Each item needs its own header, so a repeated key is spelled by upper casing it: this only + * carries the same baggage key for the propagation styles that lowercase the keys they read. + */ + static Map otBaggageHeaders(String prefix, List> items) { + Map headers = new LinkedHashMap<>(); + for (Entry item : items) { + String key = item.getKey(); + String header = prefix + (headers.containsKey(prefix + key) ? key.toUpperCase() : key); + if (headers.containsKey(header)) { + throw new IllegalArgumentException("Cannot repeat the baggage key " + key + " any further"); + } + headers.put(header, item.getValue()); + } + return headers; + } + static Map headers(String... headerKeysAndValues) { HashMap headers = new HashMap<>(); for (int i = 0; i < headerKeysAndValues.length / 2; i++) { diff --git a/dd-trace-core/src/test/java/datadog/trace/core/propagation/W3CHttpExtractorTest.java b/dd-trace-core/src/test/java/datadog/trace/core/propagation/W3CHttpExtractorTest.java index e12aad95455..427c8b898e0 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/propagation/W3CHttpExtractorTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/propagation/W3CHttpExtractorTest.java @@ -1,9 +1,8 @@ package datadog.trace.core.propagation; -import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_MAX_BYTES; -import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_MAX_ITEMS; import static datadog.trace.bootstrap.instrumentation.api.ContextVisitors.stringValuesMap; import static datadog.trace.core.propagation.HttpCodecTestHelper.headers; +import static datadog.trace.core.propagation.HttpCodecTestHelper.otBaggageHeaders; import static datadog.trace.core.propagation.W3CHttpCodec.OT_BAGGAGE_PREFIX; import static datadog.trace.core.propagation.W3CHttpCodec.TRACE_PARENT_KEY; import static datadog.trace.core.propagation.W3CHttpCodec.TRACE_STATE_KEY; @@ -21,13 +20,15 @@ import datadog.trace.api.DDTraceId; import datadog.trace.api.TraceConfig; import datadog.trace.bootstrap.instrumentation.api.TagContext; -import datadog.trace.test.junit.utils.config.WithConfig; import datadog.trace.test.junit.utils.converter.PrioritySamplingConverter; import datadog.trace.test.junit.utils.converter.SamplingMechanismConverter; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.function.Supplier; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.params.ParameterizedTest; @@ -52,32 +53,17 @@ protected HttpCodec.Extractor newExtractor( return W3CHttpCodec.newExtractor(config, traceConfigSupplier); } - @Test - @WithConfig(key = TRACE_BAGGAGE_MAX_ITEMS, value = "3") - void extractOtBaggageStopsAtItemLimit() { - Map headers = new HashMap<>(); - for (int i = 0; i < 50; i++) { - headers.put(OT_BAGGAGE_PREFIX + "key" + i, "value" + i); + @Nested + class BaggageLimits extends AbstractOTBaggageTest { + @Override + protected HttpCodec.Extractor extractor() { + return W3CHttpExtractorTest.this.extractor; } - TagContext context = this.extractor.extract(headers, stringValuesMap()); - - assertEquals(3, context.getBaggage().size()); - } - - @Test - @WithConfig(key = TRACE_BAGGAGE_MAX_BYTES, value = "24") - void extractOtBaggageStopsAtByteLimit() { - // with single digit indices each stored item is "keyN" + "valueN" = 10 bytes, so 2 fit in 24 - // bytes and a third would take the total to 30 - Map headers = new HashMap<>(); - for (int i = 0; i < 10; i++) { - headers.put(OT_BAGGAGE_PREFIX + "key" + i, "value" + i); + @Override + protected Map baggageHeaders(List> items) { + return otBaggageHeaders(OT_BAGGAGE_PREFIX, items); } - - TagContext context = this.extractor.extract(headers, stringValuesMap()); - - assertEquals(2, context.getBaggage().size()); } @TableTest({ diff --git a/dd-trace-core/src/test/java/datadog/trace/core/propagation/XRayHttpExtractorTest.java b/dd-trace-core/src/test/java/datadog/trace/core/propagation/XRayHttpExtractorTest.java index f904ce67593..a8dc7628165 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/propagation/XRayHttpExtractorTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/propagation/XRayHttpExtractorTest.java @@ -1,9 +1,9 @@ package datadog.trace.core.propagation; -import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_MAX_BYTES; import static datadog.trace.api.config.TracerConfig.TRACE_BAGGAGE_MAX_ITEMS; import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_KEEP; import static datadog.trace.bootstrap.instrumentation.api.ContextVisitors.stringValuesMap; +import static datadog.trace.core.propagation.HttpCodecTestHelper.generateBaggageItems; import static datadog.trace.core.propagation.HttpCodecTestHelper.headers; import static datadog.trace.core.propagation.XRayHttpCodec.X_AMZN_TRACE_ID; import static datadog.trace.core.propagation.XRayTestHelper.zeroPadId; @@ -20,8 +20,11 @@ import datadog.trace.test.junit.utils.config.WithConfig; import datadog.trace.test.junit.utils.converter.PrioritySamplingConverter; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.function.Supplier; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.converter.ConvertWith; import org.tabletest.junit.TableTest; @@ -33,24 +36,17 @@ protected HttpCodec.Extractor newExtractor( return XRayHttpCodec.newExtractor(config, traceConfigSupplier); } - @Test - @WithConfig(key = TRACE_BAGGAGE_MAX_ITEMS, value = "3") - void extractTraceHeaderBaggageStopsAtItemLimit() { - TagContext context = - this.extractor.extract(headers(X_AMZN_TRACE_ID, baggageHeader(50)), stringValuesMap()); - - assertEquals(3, context.getBaggage().size()); - } - - @Test - @WithConfig(key = TRACE_BAGGAGE_MAX_BYTES, value = "24") - void extractTraceHeaderBaggageStopsAtByteLimit() { - // with single digit indices each stored item is "keyN" + "valueN" = 10 bytes, so 2 fit in 24 - // bytes and a third would take the total to 30 - TagContext context = - this.extractor.extract(headers(X_AMZN_TRACE_ID, baggageHeader(9)), stringValuesMap()); + @Nested + class BaggageLimits extends AbstractOTBaggageTest { + @Override + protected HttpCodec.Extractor extractor() { + return XRayHttpExtractorTest.this.extractor; + } - assertEquals(2, context.getBaggage().size()); + @Override + protected Map baggageHeaders(List> items) { + return headers(X_AMZN_TRACE_ID, traceHeader(items)); + } } @Test @@ -61,7 +57,8 @@ void extractTraceHeaderKeepsParsingContextAfterBaggageLimit() { TagContext context = this.extractor.extract( headers( - X_AMZN_TRACE_ID, baggageHeader(50) + ";Parent=" + zeroPadId("2") + ";Sampled=1"), + X_AMZN_TRACE_ID, + traceHeader(generateBaggageItems(50)) + ";Parent=" + zeroPadId("2") + ";Sampled=1"), stringValuesMap()); assertEquals(3, context.getBaggage().size()); @@ -70,11 +67,12 @@ void extractTraceHeaderKeepsParsingContextAfterBaggageLimit() { assertEquals(SAMPLER_KEEP, context.getSamplingPriority()); } - private static String baggageHeader(int itemCount) { - // a single X-Amzn-Trace-Id header carries an arbitrary number of `key=value` segments + private static String traceHeader(List> baggage) { + // a single X-Amzn-Trace-Id header carries an arbitrary number of `key=value` segments, and can + // repeat a key outright StringBuilder header = new StringBuilder("Root=1-00000000-00000000").append(zeroPadId("1")); - for (int i = 0; i < itemCount; i++) { - header.append(";key").append(i).append("=value").append(i); + for (Entry item : baggage) { + header.append(';').append(item.getKey()).append('=').append(item.getValue()); } return header.toString(); }