diff --git a/.gitsplit.yml b/.gitsplit.yml index 0b732aada..54f804714 100644 --- a/.gitsplit.yml +++ b/.gitsplit.yml @@ -20,6 +20,8 @@ splits: target: "https://${GH_TOKEN}@github.com/opentelemetry-php/sdk.git" - prefix: "src/Contrib" target: "https://${GH_TOKEN}@github.com/opentelemetry-php/sdk-contrib.git" + - prefix: "src/Extension/Propagator/B3" + target: "https://${GH_TOKEN}@github.com/opentelemetry-php/extension-propagator-b3.git" # List of references to split (defined as regexp) origins: diff --git a/src/API/Trace/Propagation/TraceContextPropagator.php b/src/API/Trace/Propagation/TraceContextPropagator.php index 7eeb44325..8b7819bc7 100644 --- a/src/API/Trace/Propagation/TraceContextPropagator.php +++ b/src/API/Trace/Propagation/TraceContextPropagator.php @@ -33,6 +33,11 @@ final class TraceContextPropagator implements TextMapPropagatorInterface public const TRACESTATE = 'tracestate'; private const VERSION = '00'; // Currently, only '00' is supported + public const FIELDS = [ + self::TRACEPARENT, + self::TRACESTATE, + ]; + private static ?self $instance = null; public static function getInstance(): self @@ -47,7 +52,7 @@ public static function getInstance(): self /** {@inheritdoc} */ public function fields(): array { - return [self::TRACEPARENT, self::TRACESTATE]; + return self::FIELDS; } /** {@inheritdoc} */ diff --git a/src/Extension/Propagator/B3/B3DebugFlagContextKey.php b/src/Extension/Propagator/B3/B3DebugFlagContextKey.php new file mode 100644 index 000000000..cde08c10a --- /dev/null +++ b/src/Extension/Propagator/B3/B3DebugFlagContextKey.php @@ -0,0 +1,27 @@ +set($carrier, self::TRACE_ID, $spanContext->getTraceId()); $setter->set($carrier, self::SPAN_ID, $spanContext->getSpanId()); - $setter->set($carrier, self::SAMPLED, $spanContext->isSampled() ? self::IS_SAMPLED : self::IS_NOT_SAMPLED); + + $debugValue = $context->get(B3DebugFlagContextKey::instance()); + if ($debugValue && $debugValue === self::IS_SAMPLED) { + $setter->set($carrier, self::DEBUG_FLAG, self::IS_SAMPLED); + } else { + $setter->set($carrier, self::SAMPLED, $spanContext->isSampled() ? self::IS_SAMPLED : self::IS_NOT_SAMPLED); + } } public function extract($carrier, PropagationGetterInterface $getter = null, Context $context = null): Context @@ -117,7 +127,7 @@ public function extract($carrier, PropagationGetterInterface $getter = null, Con $getter ??= ArrayAccessGetterSetter::getInstance(); $context ??= Context::getCurrent(); - $spanContext = self::extractImpl($carrier, $getter); + $spanContext = self::extractImpl($carrier, $getter, $context); if (!$spanContext->isValid()) { return $context; } @@ -133,39 +143,40 @@ private static function getSampledValue($carrier, PropagationGetterInterface $ge return null; } - if ($value === '0' || $value === '1') { - return (int) $value; + if (in_array(strtolower($value), self::VALID_SAMPLED)) { + return (int) self::IS_SAMPLED; } - - if (strtolower($value) === 'true') { - return 1; - } - if (strtolower($value) === 'false') { - return 0; + if (in_array(strtolower($value), self::VALID_NON_SAMPLED)) { + return (int) self::IS_NOT_SAMPLED; } return null; } - private static function extractImpl($carrier, PropagationGetterInterface $getter): SpanContextInterface + private static function extractImpl($carrier, PropagationGetterInterface $getter, Context &$context): SpanContextInterface { $traceId = $getter->get($carrier, self::TRACE_ID); $spanId = $getter->get($carrier, self::SPAN_ID); $sampled = self::getSampledValue($carrier, $getter); + $debug = $getter->get($carrier, self::DEBUG_FLAG); if ($traceId === null || $spanId === null) { return SpanContext::getInvalid(); } - // Validates the traceId, spanId and sampled + // Validates the traceId and spanId // Returns an invalid spanContext if any of the checks fail if (!SpanContext::isValidTraceId($traceId) || !SpanContext::isValidSpanId($spanId)) { return SpanContext::getInvalid(); } - $isSampled = ($sampled === SpanContext::SAMPLED_FLAG); + if ($debug && $debug === self::IS_SAMPLED) { + $context = $context->with(B3DebugFlagContextKey::instance(), self::IS_SAMPLED); + $isSampled = SpanContext::SAMPLED_FLAG; + } else { + $isSampled = ($sampled === SpanContext::SAMPLED_FLAG); + } - // Only traceparent header is extracted. No tracestate. return SpanContext::createFromRemoteParent( $traceId, $spanId, diff --git a/src/Extension/Propagator/B3/B3Propagator.php b/src/Extension/Propagator/B3/B3Propagator.php new file mode 100644 index 000000000..cb05a812b --- /dev/null +++ b/src/Extension/Propagator/B3/B3Propagator.php @@ -0,0 +1,65 @@ +propagator = $propagator; + } + + public static function getB3SingleHeaderInstance(): self + { + static $instance; + + return $instance ??= new self(B3SinglePropagator::getInstance()); + } + public static function getB3MultiHeaderInstance(): self + { + static $instance; + + return $instance ??= new self(B3MultiPropagator::getInstance()); + } + + /** {@inheritdoc} */ + public function fields(): array + { + return $this->propagator->fields(); + } + + /** {@inheritdoc} */ + public function inject(&$carrier, PropagationSetterInterface $setter = null, Context $context = null): void + { + $this->propagator->inject($carrier, $setter, $context); + } + + /** {@inheritdoc} */ + public function extract($carrier, PropagationGetterInterface $getter = null, Context $context = null): Context + { + $getter ??= ArrayAccessGetterSetter::getInstance(); + $context ??= Context::getCurrent(); + + $b3SingleHeaderContext = B3SinglePropagator::getInstance()->extract($carrier, $getter, $context); + if ($b3SingleHeaderContext !== $context) { + return $b3SingleHeaderContext; + } + + return B3MultiPropagator::getInstance()->extract($carrier, $getter, $context); + } +} diff --git a/src/Extension/Propagator/B3/B3SinglePropagator.php b/src/Extension/Propagator/B3/B3SinglePropagator.php new file mode 100644 index 000000000..35643abbc --- /dev/null +++ b/src/Extension/Propagator/B3/B3SinglePropagator.php @@ -0,0 +1,175 @@ +getContext(); + + if (!$spanContext->isValid()) { + return; + } + + // Build and inject the b3 header + $debugValue = $context->get(B3DebugFlagContextKey::instance()); + if ($debugValue) { + $b3 = $spanContext->getTraceId() . '-' . $spanContext->getSpanId() . '-' . $debugValue; + } elseif ($spanContext->isSampled()) { + $b3 = $spanContext->getTraceId() . '-' . $spanContext->getSpanId() . '-' . self::IS_SAMPLED; + } else { + $b3 = $spanContext->getTraceId() . '-' . $spanContext->getSpanId() . '-' . self::IS_NOT_SAMPLED; + } + $setter->set($carrier, self::B3, $b3); + } + + /** {@inheritdoc} */ + public function extract($carrier, PropagationGetterInterface $getter = null, Context $context = null): Context + { + $getter ??= ArrayAccessGetterSetter::getInstance(); + $context ??= Context::getCurrent(); + + $spanContext = self::extractImpl($carrier, $getter, $context); + if (!$spanContext->isValid()) { + return $context; + } + + return $context->withContextValue(AbstractSpan::wrap($spanContext)); + } + + private static function processSampledValue($value): ?int + { + if ($value === null) { + return null; + } + + if (strtolower($value) === self::IS_DEBUG) { + return (int) self::IS_SAMPLED; + } + + if (in_array(strtolower($value), self::VALID_SAMPLED)) { + return (int) self::IS_SAMPLED; + } + + if (in_array(strtolower($value), self::VALID_NON_SAMPLED)) { + return (int) self::IS_NOT_SAMPLED; + } + + return null; + } + + private static function extractImpl($carrier, PropagationGetterInterface $getter, Context &$context): SpanContextInterface + { + $b3 = $getter->get($carrier, self::B3); + if ($b3 === null) { + return SpanContext::getInvalid(); + } + + $pieces = explode('-', $b3); + + $traceId = null; + $spanId = null; + $samplingState = null; + $parentSpanId = null; + + switch (count($pieces)) { + case 1: + // B3 = {deny-sampling-state} + [$samplingState] = $pieces; + + break; + case 2: + // B3 = {trace-id}-{span-id} + // Case of Defer sampling state; set it null so that default is used + [$traceId, $spanId] = $pieces; + + break; + case 3: + // B3 = {trace-id}-{span-id}-{sampling-state} + [$traceId, $spanId, $samplingState] = $pieces; + + break; + case 4: + // B3 = {trace-id}-{span-id}-{sampling-state}-{parent-span-id} + [$traceId, $spanId, $samplingState, $parentSpanId] = $pieces; + + break; + default: + return SpanContext::getInvalid(); + } + + if ($traceId === null || $spanId === null) { + return SpanContext::getInvalid(); + } + + // Validates the traceId and spanId + // Returns an invalid spanContext if any of the checks fail + if (!SpanContext::isValidTraceId($traceId) || !SpanContext::isValidSpanId($spanId)) { + return SpanContext::getInvalid(); + } + + if ($samplingState && strtolower($samplingState) === self::IS_DEBUG) { + $context = $context->with(B3DebugFlagContextKey::instance(), strtolower($samplingState)); + } + + $sampled = self::processSampledValue($samplingState); + $isSampled = ($sampled === SpanContext::SAMPLED_FLAG); + + return SpanContext::createFromRemoteParent( + $traceId, + $spanId, + $isSampled ? SpanContextInterface::TRACE_FLAG_SAMPLED : SpanContextInterface::TRACE_FLAG_DEFAULT + ); + } +} diff --git a/src/Extension/Propagator/B3/README.md b/src/Extension/Propagator/B3/README.md new file mode 100644 index 000000000..1123db7d1 --- /dev/null +++ b/src/Extension/Propagator/B3/README.md @@ -0,0 +1,21 @@ +# OpenTelemetry Extension +### B3 Propagator + +B3 is a propagator that supports the specification for the header "b3" used for trace context propagation across +service boundaries.(https://github.com/openzipkin/b3-propagation). OpenTelemetry PHP B3 Propagator Extension provides +option to use B3 single header(https://github.com/openzipkin/b3-propagation#single-header) as well as B3 multi header +(https://github.com/openzipkin/b3-propagation#multiple-headers) propagators. + +### Usage +For B3 single header: +```text +B3Propagator::getB3SingleHeaderInstance() +``` + +For B3 multi header: +```text +B3Propagator::getB3MultiHeaderInstance() +``` + +Both of the above have `extract` and `inject` methods available to extract and inject respectively into the +header. \ No newline at end of file diff --git a/src/Extension/Propagator/B3/composer.json b/src/Extension/Propagator/B3/composer.json new file mode 100644 index 000000000..dea11d7e5 --- /dev/null +++ b/src/Extension/Propagator/B3/composer.json @@ -0,0 +1,23 @@ +{ + "name": "open-telemetry/extension-propagator-b3", + "description": "B3 propagator extension for OpenTelemetry PHP.", + "keywords": ["opentelemetry", "otel", "tracing", "apm", "extension", "propagator", "b3"], + "type": "library", + "license": "Apache-2.0", + "authors": [ + { + "name": "opentelemetry-php contributors", + "homepage": "https://github.com/open-telemetry/opentelemetry-php/graphs/contributors" + } + ], + "require": { + "php": "^7.4 || ^8.0", + "open-telemetry/api": "self.version", + "open-telemetry/context": "self.version" + }, + "autoload": { + "psr-4": { + "OpenTelemetry\\Extension\\Propagator\\B3\\": "." + } + } +} diff --git a/tests/Unit/API/Trace/Propagation/B3MultiPropagatorTest.php b/tests/Unit/API/Trace/Propagation/B3MultiPropagatorTest.php deleted file mode 100644 index 54ac2f092..000000000 --- a/tests/Unit/API/Trace/Propagation/B3MultiPropagatorTest.php +++ /dev/null @@ -1,298 +0,0 @@ -b3MultiPropagator = B3MultiPropagator::getInstance(); - } - - public function test_fields(): void - { - $this->assertSame( - ['X-B3-TraceId', 'X-B3-SpanId', 'X-B3-ParentSpanId', 'X-B3-Sampled', 'X-B3-Flags'], - $this->b3MultiPropagator->fields() - ); - } - - public function test_inject_empty(): void - { - $carrier = []; - $this->b3MultiPropagator->inject($carrier); - $this->assertEmpty($carrier); - } - - public function test_inject_invalid_context(): void - { - $carrier = []; - $this - ->b3MultiPropagator - ->inject( - $carrier, - null, - $this->withSpanContext( - SpanContext::create( - SpanContext::INVALID_TRACE, - SpanContext::INVALID_SPAN, - SpanContext::SAMPLED_FLAG - ), - Context::getCurrent() - ) - ); - $this->assertEmpty($carrier); - } - - public function test_inject_sampled_context(): void - { - $carrier = []; - $this - ->b3MultiPropagator - ->inject( - $carrier, - null, - $this->withSpanContext( - SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), - Context::getCurrent() - ) - ); - - $this->assertSame( - [ - B3MultiPropagator::TRACE_ID => self::TRACE_ID_BASE16, - B3MultiPropagator::SPAN_ID => self::SPAN_ID_BASE16, - B3MultiPropagator::SAMPLED => self::IS_SAMPLED, - ], - $carrier - ); - } - - public function test_inject_non_sampled_context(): void - { - $carrier = []; - $this - ->b3MultiPropagator - ->inject( - $carrier, - null, - $this->withSpanContext( - SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), - Context::getCurrent() - ) - ); - - $this->assertSame( - [ - B3MultiPropagator::TRACE_ID => self::TRACE_ID_BASE16, - B3MultiPropagator::SPAN_ID => self::SPAN_ID_BASE16, - B3MultiPropagator::SAMPLED => self::IS_NOT_SAMPLED, - ], - $carrier - ); - } - - public function test_extract_nothing(): void - { - $this->assertSame( - Context::getCurrent(), - $this->b3MultiPropagator->extract([]) - ); - } - - /** - * @dataProvider sampledValueProvider - */ - public function test_extract_sampled_context($sampledValue): void - { - $carrier = [ - B3MultiPropagator::TRACE_ID => self::TRACE_ID_BASE16, - B3MultiPropagator::SPAN_ID => self::SPAN_ID_BASE16, - B3MultiPropagator::SAMPLED => $sampledValue, - ]; - - $this->assertEquals( - SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), - $this->getSpanContext($this->b3MultiPropagator->extract($carrier)) - ); - } - - public function sampledValueProvider() - { - return [ - 'String sampled value' => ['1'], - 'Boolean(lower string) sampled value' => ['true'], - 'Boolean(upper string) sampled value' => ['TRUE'], - 'Boolean(camel string) sampled value' => ['True'], - ]; - } - - /** - * @dataProvider notSampledValueProvider - */ - public function test_extract_non_sampled_context($sampledValue): void - { - $carrier = [ - B3MultiPropagator::TRACE_ID => self::TRACE_ID_BASE16, - B3MultiPropagator::SPAN_ID => self::SPAN_ID_BASE16, - B3MultiPropagator::SAMPLED => $sampledValue, - ]; - - $this->assertEquals( - SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), - $this->getSpanContext($this->b3MultiPropagator->extract($carrier)) - ); - } - - public function notSampledValueProvider() - { - return [ - 'String sampled value' => ['0'], - 'Boolean(lower string) sampled value' => ['false'], - 'Boolean(upper string) sampled value' => ['FALSE'], - 'Boolean(camel string) sampled value' => ['False'], - ]; - } - - /** - * @dataProvider invalidSampledValueProvider - */ - public function test_extract_invalid_sampled_context($sampledValue): void - { - $carrier = [ - B3MultiPropagator::TRACE_ID => self::TRACE_ID_BASE16, - B3MultiPropagator::SPAN_ID => self::SPAN_ID_BASE16, - B3MultiPropagator::SAMPLED => $sampledValue, - ]; - - $this->assertEquals( - SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), - $this->getSpanContext($this->b3MultiPropagator->extract($carrier)) - ); - } - - public function invalidSampledValueProvider() - { - return [ - 'wrong sampled value' => ['wrong'], - 'null sampled value' => [null], - 'empty sampled value' => [[]], - ]; - } - - public function test_extract_and_inject(): void - { - $extractCarrier = [ - B3MultiPropagator::TRACE_ID => self::TRACE_ID_BASE16, - B3MultiPropagator::SPAN_ID => self::SPAN_ID_BASE16, - B3MultiPropagator::SAMPLED => self::IS_SAMPLED, - ]; - $context = $this->b3MultiPropagator->extract($extractCarrier); - $injectCarrier = []; - $this->b3MultiPropagator->inject($injectCarrier, null, $context); - $this->assertSame($injectCarrier, $extractCarrier); - } - - public function test_extract_empty_trace_id(): void - { - $this->assertInvalid( - [ - B3MultiPropagator::TRACE_ID => '', - B3MultiPropagator::SPAN_ID => self::SPAN_ID_BASE16, - B3MultiPropagator::SAMPLED => self::IS_SAMPLED, - ] - ); - } - - public function test_extract_empty_span_id(): void - { - $this->assertInvalid( - [ - B3MultiPropagator::TRACE_ID => self::TRACE_ID_BASE16, - B3MultiPropagator::SPAN_ID => '', - B3MultiPropagator::SAMPLED => self::IS_SAMPLED, - ] - ); - } - - public function test_invalid_trace_id(): void - { - $this->assertInvalid( - [ - B3MultiPropagator::TRACE_ID => 'abcdefghijklmnopabcdefghijklmnop', - B3MultiPropagator::SPAN_ID => self::SPAN_ID_BASE16, - B3MultiPropagator::SAMPLED => self::IS_SAMPLED, - ] - ); - } - - public function test_invalid_trace_id_size(): void - { - $this->assertInvalid( - [ - B3MultiPropagator::TRACE_ID => self::TRACE_ID_BASE16 . '00', - B3MultiPropagator::SPAN_ID => self::SPAN_ID_BASE16, - B3MultiPropagator::SAMPLED => self::IS_SAMPLED, - ] - ); - } - - public function test_invalid_span_id(): void - { - $this->assertInvalid( - [ - B3MultiPropagator::TRACE_ID => self::TRACE_ID_BASE16, - B3MultiPropagator::SPAN_ID => 'abcdefghijklmnop', - B3MultiPropagator::SAMPLED => self::IS_SAMPLED, - ] - ); - } - - public function test_invalid_span_id_size(): void - { - $this->assertInvalid( - [ - B3MultiPropagator::TRACE_ID => self::TRACE_ID_BASE16, - B3MultiPropagator::SPAN_ID => self::SPAN_ID_BASE16 . '00', - B3MultiPropagator::SAMPLED => self::IS_SAMPLED, - ] - ); - } - - private function assertInvalid(array $carrier): void - { - $this->assertSame( - Context::getCurrent(), - $this->b3MultiPropagator->extract($carrier), - ); - } - - private function getSpanContext(Context $context): SpanContextInterface - { - return Span::fromContext($context)->getContext(); - } - - private function withSpanContext(SpanContextInterface $spanContext, Context $context): Context - { - return $context->withContextValue(Span::wrap($spanContext)); - } -} diff --git a/tests/Unit/API/Trace/Propagation/TraceContextPropagatorTest.php b/tests/Unit/API/Trace/Propagation/TraceContextPropagatorTest.php index 7c7cb8579..1d1bb7a85 100644 --- a/tests/Unit/API/Trace/Propagation/TraceContextPropagatorTest.php +++ b/tests/Unit/API/Trace/Propagation/TraceContextPropagatorTest.php @@ -309,6 +309,13 @@ public function test_invalid_trace_format(): void ]); } + public function test_empty_trace_id(): void + { + $this->assertInvalid([ + TraceContextPropagator::TRACEPARENT => '00--' . self::SPAN_ID_BASE16 . '-01', + ]); + } + public function test_invalid_trace_id(): void { $this->assertInvalid([ @@ -323,17 +330,24 @@ public function test_invalid_trace_id_size(): void ]); } + public function test_empty_span_id(): void + { + $this->assertInvalid([ + TraceContextPropagator::TRACEPARENT => '00-' . self::TRACE_ID_BASE16 . '--01', + ]); + } + public function test_invalid_span_id(): void { $this->assertInvalid([ - TraceContextPropagator::TRACEPARENT => '00-' . self::TRACE_ID_BASE16 . 'abcdefghijklmnop-01', + TraceContextPropagator::TRACEPARENT => '00-' . self::TRACE_ID_BASE16 . '-abcdefghijklmnop-01', ]); } public function test_invalid_span_id_size(): void { $this->assertInvalid([ - TraceContextPropagator::TRACEPARENT => '00-' . self::TRACE_ID_BASE16 . 'abcdefghijklmnop-00-01', + TraceContextPropagator::TRACEPARENT => '00-' . self::TRACE_ID_BASE16 . '-' . self::SPAN_ID_BASE16 . '00-01', ]); } diff --git a/tests/Unit/Extension/Propagator/B3/B3DebugFlagContextKeyTest.php b/tests/Unit/Extension/Propagator/B3/B3DebugFlagContextKeyTest.php new file mode 100644 index 000000000..12f5e1008 --- /dev/null +++ b/tests/Unit/Extension/Propagator/B3/B3DebugFlagContextKeyTest.php @@ -0,0 +1,22 @@ +assertSame( + B3DebugFlagContextKey::instance(), + B3DebugFlagContextKey::instance() + ); + } +} diff --git a/tests/Unit/Extension/Propagator/B3/B3MultiPropagatorTest.php b/tests/Unit/Extension/Propagator/B3/B3MultiPropagatorTest.php new file mode 100644 index 000000000..e495c060a --- /dev/null +++ b/tests/Unit/Extension/Propagator/B3/B3MultiPropagatorTest.php @@ -0,0 +1,476 @@ +b3MultiPropagator = B3MultiPropagator::getInstance(); + $b3MultiFields = $this->b3MultiPropagator->fields(); + $this->TRACE_ID = $b3MultiFields[0]; + $this->SPAN_ID = $b3MultiFields[1]; + $this->SAMPLED = $b3MultiFields[3]; + $this->DEBUG_FLAG = $b3MultiFields[4]; + } + + public function test_fields(): void + { + $this->assertSame( + ['X-B3-TraceId', 'X-B3-SpanId', 'X-B3-ParentSpanId', 'X-B3-Sampled', 'X-B3-Flags'], + $this->b3MultiPropagator->fields() + ); + } + + public function test_inject_empty(): void + { + $carrier = []; + $this->b3MultiPropagator->inject($carrier); + $this->assertEmpty($carrier); + } + + public function test_inject_invalid_context(): void + { + $carrier = []; + $this + ->b3MultiPropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create( + SpanContext::INVALID_TRACE, + SpanContext::INVALID_SPAN, + SpanContext::SAMPLED_FLAG + ), + Context::getCurrent() + ) + ); + $this->assertEmpty($carrier); + } + + public function test_inject_sampled_context(): void + { + $carrier = []; + $this + ->b3MultiPropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), + Context::getCurrent() + ) + ); + + $this->assertSame( + [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => self::IS_SAMPLED, + ], + $carrier + ); + } + + public function test_inject_non_sampled_context(): void + { + $carrier = []; + $this + ->b3MultiPropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), + Context::getCurrent() + ) + ); + + $this->assertSame( + [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => self::IS_NOT_SAMPLED, + ], + $carrier + ); + } + + public function test_inject_debug_with_sampled_context(): void + { + $carrier = []; + $this + ->b3MultiPropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), + Context::getCurrent() + )->with(B3DebugFlagContextKey::instance(), self::IS_SAMPLED) + ); + + $this->assertSame( + [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->DEBUG_FLAG => self::IS_SAMPLED, + ], + $carrier + ); + } + + public function test_inject_debug_with_non_sampled_context(): void + { + $carrier = []; + $this + ->b3MultiPropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_DEFAULT), + Context::getCurrent() + )->with(B3DebugFlagContextKey::instance(), self::IS_SAMPLED) + ); + + $this->assertSame( + [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->DEBUG_FLAG => self::IS_SAMPLED, + ], + $carrier + ); + } + + public function test_extract_nothing(): void + { + $this->assertSame( + Context::getCurrent(), + $this->b3MultiPropagator->extract([]) + ); + } + + public function test_extract_debug_context(): void + { + $carrier = [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->DEBUG_FLAG => self::IS_SAMPLED, + ]; + + $context = $this->b3MultiPropagator->extract($carrier); + + $this->assertEquals( + self::IS_SAMPLED, + $context->get(B3DebugFlagContextKey::instance()) + ); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), + $this->getSpanContext($context) + ); + } + + public function test_extract_debug_with_sampled_context(): void + { + $carrier = [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => self::IS_SAMPLED, + $this->DEBUG_FLAG => self::IS_SAMPLED, + ]; + + $context = $this->b3MultiPropagator->extract($carrier); + + $this->assertEquals( + self::IS_SAMPLED, + $context->get(B3DebugFlagContextKey::instance()) + ); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), + $this->getSpanContext($context) + ); + } + + public function test_extract_debug_with_non_sampled_context(): void + { + $carrier = [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => self::IS_NOT_SAMPLED, + $this->DEBUG_FLAG => self::IS_SAMPLED, + ]; + + $context = $this->b3MultiPropagator->extract($carrier); + + $this->assertEquals( + self::IS_SAMPLED, + $context->get(B3DebugFlagContextKey::instance()) + ); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), + $this->getSpanContext($context) + ); + } + + /** + * @dataProvider sampledValueProvider + */ + public function test_extract_sampled_context($sampledValue): void + { + $carrier = [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => $sampledValue, + ]; + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), + $this->getSpanContext($this->b3MultiPropagator->extract($carrier)) + ); + } + + public function sampledValueProvider(): array + { + return [ + 'String sampled value' => ['1'], + 'Boolean(lower string) sampled value' => ['true'], + 'Boolean(upper string) sampled value' => ['TRUE'], + 'Boolean(camel string) sampled value' => ['True'], + ]; + } + + /** + * @dataProvider notSampledValueProvider + */ + public function test_extract_non_sampled_context($sampledValue): void + { + $carrier = [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => $sampledValue, + ]; + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), + $this->getSpanContext($this->b3MultiPropagator->extract($carrier)) + ); + } + + public function notSampledValueProvider(): array + { + return [ + 'String sampled value' => ['0'], + 'Boolean(lower string) sampled value' => ['false'], + 'Boolean(upper string) sampled value' => ['FALSE'], + 'Boolean(camel string) sampled value' => ['False'], + ]; + } + + /** + * @dataProvider invalidDebugValueProvider + */ + public function test_extract_invalid_debug_with_sampled_context($debugValue): void + { + $carrier = [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => self::IS_SAMPLED, + $this->DEBUG_FLAG => $debugValue, + ]; + + $context = $this->b3MultiPropagator->extract($carrier); + + $this->assertNull($context->get(B3DebugFlagContextKey::instance())); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), + $this->getSpanContext($context) + ); + } + + /** + * @dataProvider invalidDebugValueProvider + */ + public function test_extract_invalid_debug_with_non_sampled_context($debugValue): void + { + $carrier = [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => self::IS_NOT_SAMPLED, + $this->DEBUG_FLAG => $debugValue, + ]; + + $context = $this->b3MultiPropagator->extract($carrier); + + $this->assertNull($context->get(B3DebugFlagContextKey::instance())); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_DEFAULT), + $this->getSpanContext($context) + ); + } + + public function invalidDebugValueProvider(): array + { + return [ + 'Invalid debug value - wrong type' => [1], + 'Invalid debug value - wrong character' => ['x'], + 'Invalid debug value - false' => ['false'], + 'Invalid debug value - true' => ['true'], + ]; + } + + /** + * @dataProvider invalidSampledValueProvider + */ + public function test_extract_invalid_sampled_context($sampledValue): void + { + $carrier = [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => $sampledValue, + ]; + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), + $this->getSpanContext($this->b3MultiPropagator->extract($carrier)) + ); + } + + public function invalidSampledValueProvider(): array + { + return [ + 'wrong sampled value' => ['wrong'], + 'null sampled value' => [null], + 'empty sampled value' => [[]], + ]; + } + + public function test_extract_and_inject(): void + { + $extractCarrier = [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => self::IS_SAMPLED, + ]; + $context = $this->b3MultiPropagator->extract($extractCarrier); + $injectCarrier = []; + $this->b3MultiPropagator->inject($injectCarrier, null, $context); + $this->assertSame($injectCarrier, $extractCarrier); + } + + public function test_extract_empty_trace_id(): void + { + $this->assertInvalid( + [ + $this->TRACE_ID => '', + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => self::IS_SAMPLED, + ] + ); + } + + public function test_invalid_trace_id(): void + { + $this->assertInvalid( + [ + $this->TRACE_ID => 'abcdefghijklmnopabcdefghijklmnop', + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => self::IS_SAMPLED, + ] + ); + } + + public function test_invalid_trace_id_size(): void + { + $this->assertInvalid( + [ + $this->TRACE_ID => self::TRACE_ID_BASE16 . '00', + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => self::IS_SAMPLED, + ] + ); + } + + public function test_extract_empty_span_id(): void + { + $this->assertInvalid( + [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => '', + $this->SAMPLED => self::IS_SAMPLED, + ] + ); + } + + public function test_invalid_span_id(): void + { + $this->assertInvalid( + [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => 'abcdefghijklmnop', + $this->SAMPLED => self::IS_SAMPLED, + ] + ); + } + + public function test_invalid_span_id_size(): void + { + $this->assertInvalid( + [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16 . '00', + $this->SAMPLED => self::IS_SAMPLED, + ] + ); + } + + private function assertInvalid(array $carrier): void + { + $this->assertSame( + Context::getCurrent(), + $this->b3MultiPropagator->extract($carrier), + ); + } + + private function getSpanContext(Context $context): SpanContextInterface + { + return Span::fromContext($context)->getContext(); + } + + private function withSpanContext(SpanContextInterface $spanContext, Context $context): Context + { + return $context->withContextValue(Span::wrap($spanContext)); + } +} diff --git a/tests/Unit/Extension/Propagator/B3/B3PropagatorTest.php b/tests/Unit/Extension/Propagator/B3/B3PropagatorTest.php new file mode 100644 index 000000000..eaaf11f46 --- /dev/null +++ b/tests/Unit/Extension/Propagator/B3/B3PropagatorTest.php @@ -0,0 +1,275 @@ +B3] = B3SinglePropagator::getInstance()->fields(); + $b3MultiFields = B3MultiPropagator::getInstance()->fields(); + $this->TRACE_ID = $b3MultiFields[0]; + $this->SPAN_ID = $b3MultiFields[1]; + $this->SAMPLED = $b3MultiFields[3]; + } + + public function test_b3multi_fields(): void + { + $propagator = B3Propagator::getB3MultiHeaderInstance(); + $this->assertSame( + ['X-B3-TraceId', 'X-B3-SpanId', 'X-B3-ParentSpanId', 'X-B3-Sampled', 'X-B3-Flags'], + $propagator->fields() + ); + } + + public function test_b3single_fields(): void + { + $propagator = B3Propagator::getB3SingleHeaderInstance(); + $this->assertSame( + ['b3'], + $propagator->fields() + ); + } + + public function test_b3multi_inject(): void + { + $propagator = B3Propagator::getB3MultiHeaderInstance(); + $carrier = []; + $propagator->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::B3_MULTI_TRACE_ID_BASE16, self::B3_MULTI_SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), + Context::getCurrent() + ) + ); + + $this->assertSame( + [ + $this->TRACE_ID => self::B3_MULTI_TRACE_ID_BASE16, + $this->SPAN_ID => self::B3_MULTI_SPAN_ID_BASE16, + $this->SAMPLED => self::IS_SAMPLED, + ], + $carrier + ); + } + + public function test_b3single_inject(): void + { + $propagator = B3Propagator::getB3SingleHeaderInstance(); + $carrier = []; + $propagator->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::B3_SINGLE_TRACE_ID_BASE16, self::B3_SINGLE_SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), + Context::getCurrent() + ) + ); + + $this->assertSame( + [$this->B3 => self::B3_SINGLE_HEADER_SAMPLED], + $carrier + ); + } + + public function test_extract_only_b3single_sampled_context_with_b3single_instance(): void + { + $carrier = [ + $this->B3 => self::B3_SINGLE_HEADER_SAMPLED, + ]; + + $propagator = B3Propagator::getB3SingleHeaderInstance(); + + $context = $propagator->extract($carrier); + + $this->assertNull($context->get(B3DebugFlagContextKey::instance())); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::B3_SINGLE_TRACE_ID_BASE16, self::B3_SINGLE_SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), + $this->getSpanContext($context) + ); + } + + public function test_extract_only_b3single_sampled_context_with_b3multi_instance(): void + { + $carrier = [ + $this->B3 => self::B3_SINGLE_HEADER_SAMPLED, + ]; + + $propagator = B3Propagator::getB3MultiHeaderInstance(); + + $context = $propagator->extract($carrier); + + $this->assertNull($context->get(B3DebugFlagContextKey::instance())); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::B3_SINGLE_TRACE_ID_BASE16, self::B3_SINGLE_SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), + $this->getSpanContext($context) + ); + } + + public function test_extract_only_b3multi_sampled_context_with_b3single_instance(): void + { + $carrier = [ + $this->TRACE_ID => self::B3_MULTI_TRACE_ID_BASE16, + $this->SPAN_ID => self::B3_MULTI_SPAN_ID_BASE16, + $this->SAMPLED => self::IS_SAMPLED, + ]; + + $propagator = B3Propagator::getB3SingleHeaderInstance(); + + $context = $propagator->extract($carrier); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::B3_MULTI_TRACE_ID_BASE16, self::B3_MULTI_SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), + $this->getSpanContext($context) + ); + } + + public function test_extract_only_b3multi_sampled_context_with_b3multi_instance(): void + { + $carrier = [ + $this->TRACE_ID => self::B3_MULTI_TRACE_ID_BASE16, + $this->SPAN_ID => self::B3_MULTI_SPAN_ID_BASE16, + $this->SAMPLED => self::IS_SAMPLED, + ]; + + $propagator = B3Propagator::getB3MultiHeaderInstance(); + + $context = $propagator->extract($carrier); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::B3_MULTI_TRACE_ID_BASE16, self::B3_MULTI_SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), + $this->getSpanContext($context) + ); + } + + public function test_extract_both_sampled_context_with_b3single_instance(): void + { + $carrier = [ + $this->TRACE_ID => self::B3_MULTI_TRACE_ID_BASE16, + $this->SPAN_ID => self::B3_MULTI_SPAN_ID_BASE16, + $this->SAMPLED => self::IS_NOT_SAMPLED, + $this->B3 => self::B3_SINGLE_HEADER_SAMPLED, + ]; + + $propagator = B3Propagator::getB3SingleHeaderInstance(); + + $context = $propagator->extract($carrier); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::B3_SINGLE_TRACE_ID_BASE16, self::B3_SINGLE_SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), + $this->getSpanContext($context) + ); + } + + public function test_extract_both_sampled_context_with_b3multi_instance(): void + { + $carrier = [ + $this->TRACE_ID => self::B3_MULTI_TRACE_ID_BASE16, + $this->SPAN_ID => self::B3_MULTI_SPAN_ID_BASE16, + $this->SAMPLED => self::IS_NOT_SAMPLED, + $this->B3 => self::B3_SINGLE_HEADER_SAMPLED, + ]; + + $propagator = B3Propagator::getB3MultiHeaderInstance(); + + $context = $propagator->extract($carrier); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::B3_SINGLE_TRACE_ID_BASE16, self::B3_SINGLE_SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), + $this->getSpanContext($context) + ); + } + + /** + * @dataProvider invalidB3SingleHeaderValueProvider + */ + public function test_extract_b3_single_invalid_and_b3_multi_valid_context_with_b3single_instance($headerValue): void + { + $carrier = [ + $this->TRACE_ID => self::B3_MULTI_TRACE_ID_BASE16, + $this->SPAN_ID => self::B3_MULTI_SPAN_ID_BASE16, + $this->SAMPLED => self::IS_NOT_SAMPLED, + $this->B3 => $headerValue, + ]; + + $propagator = B3Propagator::getB3SingleHeaderInstance(); + + $context = $propagator->extract($carrier); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::B3_MULTI_TRACE_ID_BASE16, self::B3_MULTI_SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_DEFAULT), + $this->getSpanContext($context) + ); + } + + /** + * @dataProvider invalidB3SingleHeaderValueProvider + */ + public function test_extract_b3_single_invalid_and_b3_multi_valid_context_with_b3multi_instance($headerValue): void + { + $carrier = [ + $this->TRACE_ID => self::B3_MULTI_TRACE_ID_BASE16, + $this->SPAN_ID => self::B3_MULTI_SPAN_ID_BASE16, + $this->SAMPLED => self::IS_NOT_SAMPLED, + $this->B3 => $headerValue, + ]; + + $propagator = B3Propagator::getB3MultiHeaderInstance(); + + $context = $propagator->extract($carrier); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::B3_MULTI_TRACE_ID_BASE16, self::B3_MULTI_SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_DEFAULT), + $this->getSpanContext($context) + ); + } + + public function invalidB3SingleHeaderValueProvider(): array + { + return [ + 'invalid traceid' => ['abcdefghijklmnopabcdefghijklmnop-' . self::B3_SINGLE_SPAN_ID_BASE16 . '-1'], + 'invalid spanid' => [self::B3_SINGLE_TRACE_ID_BASE16 . '-abcdefghijklmnop-1'], + ]; + } + + private function getSpanContext(Context $context): SpanContextInterface + { + return Span::fromContext($context)->getContext(); + } + + private function withSpanContext(SpanContextInterface $spanContext, Context $context): Context + { + return $context->withContextValue(Span::wrap($spanContext)); + } +} diff --git a/tests/Unit/Extension/Propagator/B3/B3SinglePropagatorTest.php b/tests/Unit/Extension/Propagator/B3/B3SinglePropagatorTest.php new file mode 100644 index 000000000..65643c42d --- /dev/null +++ b/tests/Unit/Extension/Propagator/B3/B3SinglePropagatorTest.php @@ -0,0 +1,359 @@ +b3SinglePropagator = B3SinglePropagator::getInstance(); + [$this->B3] = $this->b3SinglePropagator->fields(); + } + + public function test_fields(): void + { + $this->assertSame( + ['b3'], + $this->b3SinglePropagator->fields() + ); + } + + public function test_inject_empty(): void + { + $carrier = []; + $this->b3SinglePropagator->inject($carrier); + $this->assertEmpty($carrier); + } + + public function test_inject_invalid_context(): void + { + $carrier = []; + $this + ->b3SinglePropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create( + SpanContext::INVALID_TRACE, + SpanContext::INVALID_SPAN, + SpanContext::SAMPLED_FLAG + ), + Context::getCurrent() + ) + ); + $this->assertEmpty($carrier); + } + + public function test_inject_sampled_context(): void + { + $carrier = []; + $this + ->b3SinglePropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), + Context::getCurrent() + ) + ); + + $this->assertSame( + [$this->B3 => self::B3_HEADER_SAMPLED], + $carrier + ); + } + + public function test_inject_non_sampled_context(): void + { + $carrier = []; + $this + ->b3SinglePropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), + Context::getCurrent() + ) + ); + + $this->assertSame( + [$this->B3 => self::B3_HEADER_NOT_SAMPLED], + $carrier + ); + } + + public function test_inject_debug_context(): void + { + $carrier = []; + $this + ->b3SinglePropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), + Context::getCurrent() + )->with(B3DebugFlagContextKey::instance(), self::DEBUG_FLAG) + ); + + $this->assertSame( + [$this->B3 => self::B3_HEADER_DEBUG], + $carrier + ); + } + + public function test_extract_nothing(): void + { + $this->assertSame( + Context::getCurrent(), + $this->b3SinglePropagator->extract([]) + ); + } + + /** + * @dataProvider debugValueProvider + */ + public function test_extract_debug_context($headerValue): void + { + $carrier = [ + $this->B3 => $headerValue, + ]; + + $context = $this->b3SinglePropagator->extract($carrier); + + $this->assertEquals( + self::DEBUG_FLAG, + $context->get(B3DebugFlagContextKey::instance()) + ); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), + $this->getSpanContext($context) + ); + } + + public function debugValueProvider(): array + { + return [ + 'String(lower string) debug value' => [self::TRACE_ID_BASE16 . '-' . self::SPAN_ID_BASE16 . '-' . self::DEBUG_FLAG], + 'String(upper string) debug value' => [self::TRACE_ID_BASE16 . '-' . self::SPAN_ID_BASE16 . '-' . strtoupper(self::DEBUG_FLAG)], + ]; + } + + public function test_extract_sampled_context(): void + { + $carrier = [ + $this->B3 => self::B3_HEADER_SAMPLED, + ]; + + $context = $this->b3SinglePropagator->extract($carrier); + + $this->assertNull($context->get(B3DebugFlagContextKey::instance())); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), + $this->getSpanContext($context) + ); + } + + public function test_extract_non_sampled_context(): void + { + $carrier = [ + $this->B3 => self::B3_HEADER_NOT_SAMPLED, + ]; + + $context = $this->b3SinglePropagator->extract($carrier); + + $this->assertNull($context->get(B3DebugFlagContextKey::instance())); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), + $this->getSpanContext($context) + ); + } + + public function test_extract_sampled_context_with_parent_span_id(): void + { + $carrier = [ + $this->B3 => self::B3_HEADER_SAMPLED . '-' . self::TRACE_ID_BASE16, + ]; + + $context = $this->b3SinglePropagator->extract($carrier); + + $this->assertNull($context->get(B3DebugFlagContextKey::instance())); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, SpanContextInterface::TRACE_FLAG_SAMPLED), + $this->getSpanContext($context) + ); + } + + public function test_extract_non_sampled_context_with_parent_span_id(): void + { + $carrier = [ + $this->B3 => self::B3_HEADER_NOT_SAMPLED . '-' . self::TRACE_ID_BASE16, + ]; + + $context = $this->b3SinglePropagator->extract($carrier); + + $this->assertNull($context->get(B3DebugFlagContextKey::instance())); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), + $this->getSpanContext($context) + ); + } + + public function test_extract_defer_sampling(): void + { + $carrier = [ + $this->B3 => self::TRACE_ID_BASE16 . '-' . self::SPAN_ID_BASE16, + ]; + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), + $this->getSpanContext($this->b3SinglePropagator->extract($carrier)) + ); + } + + /** + * @dataProvider invalidSampledValueProvider + */ + public function test_extract_invalid_sampled_context($headerValue): void + { + $carrier = [ + $this->B3 => $headerValue, + ]; + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), + $this->getSpanContext($this->b3SinglePropagator->extract($carrier)) + ); + } + + public function invalidSampledValueProvider(): array + { + return [ + 'wrong sampled value' => [self::TRACE_ID_BASE16 . '-' . self::SPAN_ID_BASE16 . '-wrong'], + 'empty sampled value' => [self::TRACE_ID_BASE16 . '-' . self::SPAN_ID_BASE16 . '-'], + ]; + } + + public function test_extract_and_inject(): void + { + $extractCarrier = [ + $this->B3 => self::B3_HEADER_SAMPLED, + ]; + $context = $this->b3SinglePropagator->extract($extractCarrier); + $injectCarrier = []; + $this->b3SinglePropagator->inject($injectCarrier, null, $context); + $this->assertSame($injectCarrier, $extractCarrier); + } + + public function test_extract_empty_header(): void + { + $this->assertInvalid([ + $this->B3 => '', + ]); + } + + public function test_extract_header_with_extra_flags(): void + { + $this->assertInvalid([ + $this->B3 => self::B3_HEADER_SAMPLED . '-extra-flags', + ]); + } + + public function test_extract_deny_sampling(): void + { + $this->assertInvalid([ + $this->B3 => self::B3_DENY_SAMPLING, + ]); + } + + public function test_empty_trace_id(): void + { + $this->assertInvalid([ + $this->B3 => '-' . self::SPAN_ID_BASE16 . '-1', + ]); + } + public function test_invalid_trace_id(): void + { + $this->assertInvalid([ + $this->B3 => 'abcdefghijklmnopabcdefghijklmnop-' . self::SPAN_ID_BASE16 . '-1', + ]); + } + + public function test_invalid_trace_id_size(): void + { + $this->assertInvalid([ + $this->B3 => self::TRACE_ID_BASE16 . '00-' . self::SPAN_ID_BASE16 . '-1', + ]); + } + + public function test_empty_span_id(): void + { + $this->assertInvalid([ + $this->B3 => self::TRACE_ID_BASE16 . '--1', + ]); + } + + public function test_invalid_span_id(): void + { + $this->assertInvalid([ + $this->B3 => self::TRACE_ID_BASE16 . '-abcdefghijklmnop-1', + ]); + } + + public function test_invalid_span_id_size(): void + { + $this->assertInvalid([ + $this->B3 => self::TRACE_ID_BASE16 . '-' . self::SPAN_ID_BASE16 . '00-1', + ]); + } + + private function assertInvalid(array $carrier): void + { + $this->assertSame( + Context::getCurrent(), + $this->b3SinglePropagator->extract($carrier), + ); + } + + private function getSpanContext(Context $context): SpanContextInterface + { + return Span::fromContext($context)->getContext(); + } + + private function withSpanContext(SpanContextInterface $spanContext, Context $context): Context + { + return $context->withContextValue(Span::wrap($spanContext)); + } +}