diff options
author | Bogdan Drutu <bdrutu@google.com> | 2017-12-11 16:12:53 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-12-11 16:12:53 -0800 |
commit | 381e11c5168e5d31e09ec92840f908b42be647e6 (patch) | |
tree | b4d69bd3f2e5b1c7ef903278be6960420d3aa737 | |
parent | e2664fb9a41ea54394d4b111052ef2c3ab0d7b0c (diff) | |
download | opencensus-java-381e11c5168e5d31e09ec92840f908b42be647e6.tar.gz |
Add initial support for b3-propagation headers. (#889)
* Add initial support for b3-propagation headers.
* Update tests and throw exception when missing span_id or trace_id.
* Cleanup and add more tests.
* Update comments. Update tests.
8 files changed, 410 insertions, 8 deletions
diff --git a/api/src/main/java/io/opencensus/trace/SpanId.java b/api/src/main/java/io/opencensus/trace/SpanId.java index 446a228e..a986d013 100644 --- a/api/src/main/java/io/opencensus/trace/SpanId.java +++ b/api/src/main/java/io/opencensus/trace/SpanId.java @@ -84,6 +84,22 @@ public final class SpanId implements Comparable<SpanId> { } /** + * Returns a {@code SpanId} built from a lowercase base16 representation. + * + * @param src the lowercase base16 representation. + * @return a {@code SpanId} built from a lowercase base16 representation. + * @throws NullPointerException if {@code src} is null. + * @throws IllegalArgumentException if {@code src.length} is not {@code 2 * SpanId.SIZE} OR if the + * {@code str} has invalid characters. + */ + public static SpanId fromLowerBase16(String src) { + checkArgument( + src.length() == 2 * SIZE, "Invalid size: expected %s, got %s", 2 * SIZE, src.length()); + byte[] bytes = BaseEncoding.base16().lowerCase().decode(src); + return new SpanId(bytes); + } + + /** * Generates a new random {@code SpanId}. * * @param random The random number generator. @@ -136,6 +152,15 @@ public final class SpanId implements Comparable<SpanId> { return !Arrays.equals(bytes, INVALID.bytes); } + /** + * Returns the lowercase base16 encoding of this {@code SpanId}. + * + * @return the lowercase base16 encoding of this {@code SpanId}. + */ + public String toLowerBase16() { + return BaseEncoding.base16().lowerCase().encode(bytes); + } + @Override public boolean equals(Object obj) { if (obj == this) { diff --git a/api/src/main/java/io/opencensus/trace/TraceId.java b/api/src/main/java/io/opencensus/trace/TraceId.java index 6badf6e5..44b80a66 100644 --- a/api/src/main/java/io/opencensus/trace/TraceId.java +++ b/api/src/main/java/io/opencensus/trace/TraceId.java @@ -85,6 +85,22 @@ public final class TraceId implements Comparable<TraceId> { } /** + * Returns a {@code TraceId} built from a lowercase base16 representation. + * + * @param src the lowercase base16 representation. + * @return a {@code TraceId} built from a lowercase base16 representation. + * @throws NullPointerException if {@code src} is null. + * @throws IllegalArgumentException if {@code src.length} is not {@code 2 * TraceId.SIZE} OR if + * the {@code str} has invalid characters. + */ + public static TraceId fromLowerBase16(String src) { + checkArgument( + src.length() == 2 * SIZE, "Invalid size: expected %s, got %s", 2 * SIZE, src.length()); + byte[] bytes = BaseEncoding.base16().lowerCase().decode(src); + return new TraceId(bytes); + } + + /** * Generates a new random {@code TraceId}. * * @param random the random number generator. @@ -138,6 +154,15 @@ public final class TraceId implements Comparable<TraceId> { } /** + * Returns the lowercase base16 encoding of this {@code TraceId}. + * + * @return the lowercase base16 encoding of this {@code TraceId}. + */ + public String toLowerBase16() { + return BaseEncoding.base16().lowerCase().encode(bytes); + } + + /** * Returns the lower 8 bytes of the trace-id as a long value, assuming little-endian order. This * is used in ProbabilitySampler. * diff --git a/api/src/test/java/io/opencensus/trace/SpanIdTest.java b/api/src/test/java/io/opencensus/trace/SpanIdTest.java index 36226da5..4a5bc2ae 100644 --- a/api/src/test/java/io/opencensus/trace/SpanIdTest.java +++ b/api/src/test/java/io/opencensus/trace/SpanIdTest.java @@ -28,7 +28,7 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class SpanIdTest { private static final byte[] firstBytes = new byte[] {0, 0, 0, 0, 0, 0, 0, 'a'}; - private static final byte[] secondBytes = new byte[] {(byte) 0xFF, 0, 0, 0, 0, 0, 0, 0}; + private static final byte[] secondBytes = new byte[] {(byte) 0xFF, 0, 0, 0, 0, 0, 0, 'A'}; private static final SpanId first = SpanId.fromBytes(firstBytes); private static final SpanId second = SpanId.fromBytes(secondBytes); @@ -45,6 +45,20 @@ public class SpanIdTest { } @Test + public void fromLowerBase16() { + assertThat(SpanId.fromLowerBase16("0000000000000000")).isEqualTo(SpanId.INVALID); + assertThat(SpanId.fromLowerBase16("0000000000000061")).isEqualTo(first); + assertThat(SpanId.fromLowerBase16("ff00000000000041")).isEqualTo(second); + } + + @Test + public void toLowerBase16() { + assertThat(SpanId.INVALID.toLowerBase16()).isEqualTo("0000000000000000"); + assertThat(first.toLowerBase16()).isEqualTo("0000000000000061"); + assertThat(second.toLowerBase16()).isEqualTo("ff00000000000041"); + } + + @Test public void getBytes() { assertThat(first.getBytes()).isEqualTo(firstBytes); assertThat(second.getBytes()).isEqualTo(secondBytes); @@ -71,6 +85,6 @@ public class SpanIdTest { public void traceId_ToString() { assertThat(SpanId.INVALID.toString()).contains("0000000000000000"); assertThat(first.toString()).contains("0000000000000061"); - assertThat(second.toString()).contains("ff00000000000000"); + assertThat(second.toString()).contains("ff00000000000041"); } } diff --git a/api/src/test/java/io/opencensus/trace/TraceIdTest.java b/api/src/test/java/io/opencensus/trace/TraceIdTest.java index 8c1a1004..c8b5dc8f 100644 --- a/api/src/test/java/io/opencensus/trace/TraceIdTest.java +++ b/api/src/test/java/io/opencensus/trace/TraceIdTest.java @@ -30,7 +30,7 @@ public class TraceIdTest { private static final byte[] firstBytes = new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'a'}; private static final byte[] secondBytes = - new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'A'}; + new byte[] {(byte) 0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'A'}; private static final TraceId first = TraceId.fromBytes(firstBytes); private static final TraceId second = TraceId.fromBytes(secondBytes); @@ -53,6 +53,21 @@ public class TraceIdTest { } @Test + public void fromLowerBase16() { + assertThat(TraceId.fromLowerBase16("00000000000000000000000000000000")) + .isEqualTo(TraceId.INVALID); + assertThat(TraceId.fromLowerBase16("00000000000000000000000000000061")).isEqualTo(first); + assertThat(TraceId.fromLowerBase16("ff000000000000000000000000000041")).isEqualTo(second); + } + + @Test + public void toLowerBase16() { + assertThat(TraceId.INVALID.toLowerBase16()).isEqualTo("00000000000000000000000000000000"); + assertThat(first.toLowerBase16()).isEqualTo("00000000000000000000000000000061"); + assertThat(second.toLowerBase16()).isEqualTo("ff000000000000000000000000000041"); + } + + @Test public void traceId_CompareTo() { assertThat(first.compareTo(second)).isGreaterThan(0); assertThat(second.compareTo(first)).isLessThan(0); @@ -73,6 +88,6 @@ public class TraceIdTest { public void traceId_ToString() { assertThat(TraceId.INVALID.toString()).contains("00000000000000000000000000000000"); assertThat(first.toString()).contains("00000000000000000000000000000061"); - assertThat(second.toString()).contains("00000000000000000000000000000041"); + assertThat(second.toString()).contains("ff000000000000000000000000000041"); } } diff --git a/api/src/test/java/io/opencensus/trace/TracerTest.java b/api/src/test/java/io/opencensus/trace/TracerTest.java index 7c65742e..8f0ac64f 100644 --- a/api/src/test/java/io/opencensus/trace/TracerTest.java +++ b/api/src/test/java/io/opencensus/trace/TracerTest.java @@ -23,9 +23,7 @@ import static org.mockito.Mockito.when; import io.grpc.Context; import io.opencensus.common.Scope; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mock; @@ -38,7 +36,6 @@ import org.mockito.MockitoAnnotations; public class TracerTest { private static final Tracer noopTracer = Tracer.getNoopTracer(); private static final String SPAN_NAME = "MySpanName"; - @Rule public ExpectedException thrown = ExpectedException.none(); @Mock private Tracer tracer; @Mock private SpanBuilder spanBuilder; @Mock private Span span; diff --git a/impl_core/src/main/java/io/opencensus/implcore/trace/propagation/B3Format.java b/impl_core/src/main/java/io/opencensus/implcore/trace/propagation/B3Format.java new file mode 100644 index 00000000..c27234db --- /dev/null +++ b/impl_core/src/main/java/io/opencensus/implcore/trace/propagation/B3Format.java @@ -0,0 +1,105 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.implcore.trace.propagation; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.VisibleForTesting; +import io.opencensus.trace.SpanContext; +import io.opencensus.trace.SpanId; +import io.opencensus.trace.TraceId; +import io.opencensus.trace.TraceOptions; +import io.opencensus.trace.propagation.SpanContextParseException; +import io.opencensus.trace.propagation.TextFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Implementation of the B3 propagation protocol. See <a + * href=https://github.com/openzipkin/b3-propagation>b3-propagation</a>. + */ +final class B3Format extends TextFormat { + @VisibleForTesting static final String X_B3_TRACE_ID = "X─B3─TraceId"; + @VisibleForTesting static final String X_B3_SPAN_ID = "X─B3─SpanId"; + @VisibleForTesting static final String X_B3_PARENT_SPAN_ID = "X─B3─ParentSpanId"; + @VisibleForTesting static final String X_B3_SAMPLED = "X─B3─Sampled"; + @VisibleForTesting static final String X_B3_FLAGS = "X-B3-Flags"; + private static final List<String> FIELDS = + Collections.unmodifiableList( + Arrays.asList( + X_B3_TRACE_ID, X_B3_SPAN_ID, X_B3_PARENT_SPAN_ID, X_B3_SAMPLED, X_B3_FLAGS)); + + // Used as the upper TraceId.SIZE hex characters of the traceID. B3-propagation used to send + // TraceId.SIZE hex characters (8-bytes traceId) in the past. + private static final String UPPER_TRACE_ID = "0000000000000000"; + // Sampled value via the X_B3_SAMPLED header. + private static final String SAMPLED_VALUE = "1"; + // "Debug" sampled value. + private static final String FLAGS_VALUE = "1"; + + @Override + public List<String> fields() { + return FIELDS; + } + + @Override + public <C> void inject(SpanContext spanContext, C carrier, Setter<C> setter) { + checkNotNull(spanContext, "spanContext"); + checkNotNull(setter, "setter"); + checkNotNull(carrier, "carrier"); + setter.put(carrier, X_B3_TRACE_ID, spanContext.getTraceId().toLowerBase16()); + setter.put(carrier, X_B3_SPAN_ID, spanContext.getSpanId().toLowerBase16()); + if (spanContext.getTraceOptions().isSampled()) { + setter.put(carrier, X_B3_SAMPLED, SAMPLED_VALUE); + } + } + + @Override + public <C> SpanContext extract(C carrier, Getter<C> getter) throws SpanContextParseException { + checkNotNull(carrier, "carrier"); + checkNotNull(getter, "getter"); + try { + TraceId traceId; + String traceIdStr = getter.get(carrier, X_B3_TRACE_ID); + if (traceIdStr != null) { + if (traceIdStr.length() == TraceId.SIZE) { + // This is an 8-byte traceID. + traceIdStr = UPPER_TRACE_ID + traceIdStr; + } + traceId = TraceId.fromLowerBase16(traceIdStr); + } else { + throw new SpanContextParseException("Missing X_B3_TRACE_ID."); + } + SpanId spanId; + String spanIdStr = getter.get(carrier, X_B3_SPAN_ID); + if (spanIdStr != null) { + spanId = SpanId.fromLowerBase16(spanIdStr); + } else { + throw new SpanContextParseException("Missing X_B3_SPAN_ID."); + } + TraceOptions traceOptions = TraceOptions.DEFAULT; + if (SAMPLED_VALUE.equals(getter.get(carrier, X_B3_SAMPLED)) + || FLAGS_VALUE.equals(getter.get(carrier, X_B3_FLAGS))) { + traceOptions = TraceOptions.builder().setIsSampled(true).build(); + } + return SpanContext.create(traceId, spanId, traceOptions); + } catch (IllegalArgumentException e) { + throw new SpanContextParseException("Invalid input.", e); + } + } +} diff --git a/impl_core/src/main/java/io/opencensus/implcore/trace/propagation/BinaryFormatImpl.java b/impl_core/src/main/java/io/opencensus/implcore/trace/propagation/BinaryFormatImpl.java index 6075c80b..8a4377d3 100644 --- a/impl_core/src/main/java/io/opencensus/implcore/trace/propagation/BinaryFormatImpl.java +++ b/impl_core/src/main/java/io/opencensus/implcore/trace/propagation/BinaryFormatImpl.java @@ -59,7 +59,7 @@ import io.opencensus.trace.propagation.SpanContextParseException; * </ul> * </ul> */ -public final class BinaryFormatImpl extends BinaryFormat { +final class BinaryFormatImpl extends BinaryFormat { private static final byte VERSION_ID = 0; private static final int VERSION_ID_OFFSET = 0; // The version_id/field_id size in bytes. diff --git a/impl_core/src/test/java/io/opencensus/implcore/trace/propagation/B3FormatTest.java b/impl_core/src/test/java/io/opencensus/implcore/trace/propagation/B3FormatTest.java new file mode 100644 index 00000000..45514d06 --- /dev/null +++ b/impl_core/src/test/java/io/opencensus/implcore/trace/propagation/B3FormatTest.java @@ -0,0 +1,221 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.implcore.trace.propagation; + +import static com.google.common.truth.Truth.assertThat; +import static io.opencensus.implcore.trace.propagation.B3Format.X_B3_FLAGS; +import static io.opencensus.implcore.trace.propagation.B3Format.X_B3_PARENT_SPAN_ID; +import static io.opencensus.implcore.trace.propagation.B3Format.X_B3_SAMPLED; +import static io.opencensus.implcore.trace.propagation.B3Format.X_B3_SPAN_ID; +import static io.opencensus.implcore.trace.propagation.B3Format.X_B3_TRACE_ID; + +import io.opencensus.trace.SpanContext; +import io.opencensus.trace.SpanId; +import io.opencensus.trace.TraceId; +import io.opencensus.trace.TraceOptions; +import io.opencensus.trace.propagation.SpanContextParseException; +import io.opencensus.trace.propagation.TextFormat.Getter; +import io.opencensus.trace.propagation.TextFormat.Setter; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link B3Format}. */ +@RunWith(JUnit4.class) +public class B3FormatTest { + private static final String TRACE_ID_BASE16 = "ff000000000000000000000000000041"; + private static final TraceId TRACE_ID = TraceId.fromLowerBase16(TRACE_ID_BASE16); + private static final String TRACE_ID_BASE16_EIGHT_BYTES = "0000000000000041"; + private static final TraceId TRACE_ID_EIGHT_BYTES = + TraceId.fromLowerBase16("0000000000000000" + TRACE_ID_BASE16_EIGHT_BYTES); + private static final String SPAN_ID_BASE16 = "ff00000000000041"; + private static final SpanId SPAN_ID = SpanId.fromLowerBase16(SPAN_ID_BASE16); + private static final byte[] TRACE_OPTIONS_BYTES = new byte[] {1}; + private static final TraceOptions TRACE_OPTIONS = TraceOptions.fromBytes(TRACE_OPTIONS_BYTES); + private final B3Format b3Format = new B3Format(); + @Rule public ExpectedException thrown = ExpectedException.none(); + private final Setter<Map<String, String>> setter = + new Setter<Map<String, String>>() { + @Override + public void put(Map<String, String> carrier, String key, String value) { + carrier.put(key, value); + } + }; + private final Getter<Map<String, String>> getter = + new Getter<Map<String, String>>() { + @Nullable + @Override + public String get(Map<String, String> carrier, String key) { + return carrier.get(key); + } + }; + + @Test + public void serialize_SampledContext() { + Map<String, String> carrier = new HashMap<String, String>(); + b3Format.inject(SpanContext.create(TRACE_ID, SPAN_ID, TRACE_OPTIONS), carrier, setter); + assertThat(carrier) + .containsExactly( + X_B3_TRACE_ID, TRACE_ID_BASE16, X_B3_SPAN_ID, SPAN_ID_BASE16, X_B3_SAMPLED, "1"); + } + + @Test + public void serialize_NotSampledContext() { + Map<String, String> carrier = new HashMap<String, String>(); + b3Format.inject(SpanContext.create(TRACE_ID, SPAN_ID, TraceOptions.DEFAULT), carrier, setter); + assertThat(carrier) + .containsExactly(X_B3_TRACE_ID, TRACE_ID_BASE16, X_B3_SPAN_ID, SPAN_ID_BASE16); + } + + @Test + public void parseMissingSampledAndMissingFlag() throws SpanContextParseException { + Map<String, String> headersNotSampled = new HashMap<String, String>(); + headersNotSampled.put(X_B3_TRACE_ID, TRACE_ID_BASE16); + headersNotSampled.put(X_B3_SPAN_ID, SPAN_ID_BASE16); + SpanContext spanContext = SpanContext.create(TRACE_ID, SPAN_ID, TraceOptions.DEFAULT); + assertThat(b3Format.extract(headersNotSampled, getter)).isEqualTo(spanContext); + } + + @Test + public void parseSampled() throws SpanContextParseException { + Map<String, String> headersSampled = new HashMap<String, String>(); + headersSampled.put(X_B3_TRACE_ID, TRACE_ID_BASE16); + headersSampled.put(X_B3_SPAN_ID, SPAN_ID_BASE16); + headersSampled.put(X_B3_SAMPLED, "1"); + assertThat(b3Format.extract(headersSampled, getter)) + .isEqualTo(SpanContext.create(TRACE_ID, SPAN_ID, TRACE_OPTIONS)); + } + + @Test + public void parseZeroSampled() throws SpanContextParseException { + Map<String, String> headersNotSampled = new HashMap<String, String>(); + headersNotSampled.put(X_B3_TRACE_ID, TRACE_ID_BASE16); + headersNotSampled.put(X_B3_SPAN_ID, SPAN_ID_BASE16); + headersNotSampled.put(X_B3_SAMPLED, "0"); + assertThat(b3Format.extract(headersNotSampled, getter)) + .isEqualTo(SpanContext.create(TRACE_ID, SPAN_ID, TraceOptions.DEFAULT)); + } + + @Test + public void parseFlag() throws SpanContextParseException { + Map<String, String> headersFlagSampled = new HashMap<String, String>(); + headersFlagSampled.put(X_B3_TRACE_ID, TRACE_ID_BASE16); + headersFlagSampled.put(X_B3_SPAN_ID, SPAN_ID_BASE16); + headersFlagSampled.put(X_B3_FLAGS, "1"); + assertThat(b3Format.extract(headersFlagSampled, getter)) + .isEqualTo(SpanContext.create(TRACE_ID, SPAN_ID, TRACE_OPTIONS)); + } + + @Test + public void parseZeroFlag() throws SpanContextParseException { + Map<String, String> headersFlagNotSampled = new HashMap<String, String>(); + headersFlagNotSampled.put(X_B3_TRACE_ID, TRACE_ID_BASE16); + headersFlagNotSampled.put(X_B3_SPAN_ID, SPAN_ID_BASE16); + headersFlagNotSampled.put(X_B3_FLAGS, "0"); + assertThat(b3Format.extract(headersFlagNotSampled, getter)) + .isEqualTo(SpanContext.create(TRACE_ID, SPAN_ID, TraceOptions.DEFAULT)); + } + + @Test + public void parseEightBytesTraceId() throws SpanContextParseException { + Map<String, String> headersEightBytes = new HashMap<String, String>(); + headersEightBytes.put(X_B3_TRACE_ID, TRACE_ID_BASE16_EIGHT_BYTES); + headersEightBytes.put(X_B3_SPAN_ID, SPAN_ID_BASE16); + headersEightBytes.put(X_B3_SAMPLED, "1"); + assertThat(b3Format.extract(headersEightBytes, getter)) + .isEqualTo(SpanContext.create(TRACE_ID_EIGHT_BYTES, SPAN_ID, TRACE_OPTIONS)); + } + + @Test + public void parseEightBytesTraceId_NotSampledSpanContext() throws SpanContextParseException { + Map<String, String> headersEightBytes = new HashMap<String, String>(); + headersEightBytes.put(X_B3_TRACE_ID, TRACE_ID_BASE16_EIGHT_BYTES); + headersEightBytes.put(X_B3_SPAN_ID, SPAN_ID_BASE16); + assertThat(b3Format.extract(headersEightBytes, getter)) + .isEqualTo(SpanContext.create(TRACE_ID_EIGHT_BYTES, SPAN_ID, TraceOptions.DEFAULT)); + } + + @Test + public void parseInvalidTraceId() throws SpanContextParseException { + Map<String, String> invalidHeaders = new HashMap<String, String>(); + invalidHeaders.put(X_B3_TRACE_ID, "abcdefghijklmnop"); + invalidHeaders.put(X_B3_SPAN_ID, SPAN_ID_BASE16); + thrown.expect(SpanContextParseException.class); + thrown.expectMessage("Invalid input."); + b3Format.extract(invalidHeaders, getter); + } + + @Test + public void parseInvalidTraceId_Size() throws SpanContextParseException { + Map<String, String> invalidHeaders = new HashMap<String, String>(); + invalidHeaders.put(X_B3_TRACE_ID, "0123456789abcdef00"); + invalidHeaders.put(X_B3_SPAN_ID, SPAN_ID_BASE16); + thrown.expect(SpanContextParseException.class); + thrown.expectMessage("Invalid input."); + b3Format.extract(invalidHeaders, getter); + } + + @Test + public void parseMissingTraceId() throws SpanContextParseException { + Map<String, String> invalidHeaders = new HashMap<String, String>(); + invalidHeaders.put(X_B3_SPAN_ID, SPAN_ID_BASE16); + thrown.expect(SpanContextParseException.class); + thrown.expectMessage("Missing X_B3_TRACE_ID."); + b3Format.extract(invalidHeaders, getter); + } + + @Test + public void parseInvalidSpanId() throws SpanContextParseException { + Map<String, String> invalidHeaders = new HashMap<String, String>(); + invalidHeaders.put(X_B3_TRACE_ID, TRACE_ID_BASE16); + invalidHeaders.put(X_B3_SPAN_ID, "abcdefghijklmnop"); + thrown.expect(SpanContextParseException.class); + thrown.expectMessage("Invalid input."); + b3Format.extract(invalidHeaders, getter); + } + + @Test + public void parseInvalidSpanId_Size() throws SpanContextParseException { + Map<String, String> invalidHeaders = new HashMap<String, String>(); + invalidHeaders.put(X_B3_TRACE_ID, TRACE_ID_BASE16); + invalidHeaders.put(X_B3_SPAN_ID, "0123456789abcdef00"); + thrown.expect(SpanContextParseException.class); + thrown.expectMessage("Invalid input."); + b3Format.extract(invalidHeaders, getter); + } + + @Test + public void parseMissingSpanId() throws SpanContextParseException { + Map<String, String> invalidHeaders = new HashMap<String, String>(); + invalidHeaders.put(X_B3_TRACE_ID, TRACE_ID_BASE16); + thrown.expect(SpanContextParseException.class); + thrown.expectMessage("Missing X_B3_SPAN_ID."); + b3Format.extract(invalidHeaders, getter); + } + + @Test + public void fields_list() { + assertThat(b3Format.fields()) + .containsExactly( + X_B3_TRACE_ID, X_B3_SPAN_ID, X_B3_PARENT_SPAN_ID, X_B3_SAMPLED, X_B3_FLAGS); + } +} |