diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9be661d00..2c1b19f45 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -92,6 +92,7 @@ updates: - "/src/Instrumentation/Yii" - "/src/Logs/Monolog" - "/src/MetaPackages/opentelemetry" + - "/src/Propagation/CloudTrace" - "/src/Propagation/Instana" - "/src/Propagation/ServerTiming" - "/src/Propagation/TraceResponse" diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 022d20add..e833c354f 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -54,6 +54,7 @@ jobs: 'Instrumentation/Symfony', 'Instrumentation/Yii', 'Logs/Monolog', + 'Propagation/CloudTrace', 'Propagation/Instana', 'Propagation/ServerTiming', 'Propagation/TraceResponse', diff --git a/.gitsplit.yml b/.gitsplit.yml index 66cac9025..7830c9b64 100644 --- a/.gitsplit.yml +++ b/.gitsplit.yml @@ -72,6 +72,8 @@ splits: target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-logger-monolog.git" - prefix: "src/MetaPackages/opentelemetry" target: "https://${GH_TOKEN}@github.com/opentelemetry-php/opentelemetry-meta.git" + - prefix: "src/Propagation/CloudTrace" + target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-propagator-cloudtrace.git" - prefix: "src/Propagation/Instana" target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-propagator-instana.git" - prefix: "src/Propagation/ServerTiming" diff --git a/composer.json b/composer.json index 56945bd4b..3c64f2e40 100644 --- a/composer.json +++ b/composer.json @@ -45,6 +45,7 @@ "OpenTelemetry\\Contrib\\Instrumentation\\Wordpress\\": "src/Instrumentation/Wordpress/src", "OpenTelemetry\\Contrib\\Instrumentation\\Yii\\": "src/Instrumentation/Yii/src", "OpenTelemetry\\Contrib\\Logs\\Monolog\\": "src/Logs/Monolog/src", + "OpenTelemetry\\Contrib\\Propagation\\CloudTrace\\": "src/Propagation/CloudTrace/src", "OpenTelemetry\\Contrib\\Propagation\\Instana\\": "src/Propagation/Instana/src", "OpenTelemetry\\Contrib\\Propagation\\ServerTiming\\": "src/Propagation/ServerTiming/src", "OpenTelemetry\\Contrib\\Propagation\\TraceResponse\\": "src/Propagation/TraceResponse/src", @@ -84,6 +85,7 @@ "src/Instrumentation/Symfony/_register.php", "src/Instrumentation/Wordpress/_register.php", "src/Instrumentation/Yii/_register.php", + "src/Propagation/CloudTrace/_register.php", "src/Propagation/Instana/_register.php", "src/ResourceDetectors/Azure/_register.php", "src/ResourceDetectors/Container/_register.php", @@ -117,6 +119,7 @@ "OpenTelemetry\\Tests\\Instrumentation\\Symfony\\tests\\": "src/Instrumentation/Symfony/tests", "OpenTelemetry\\Tests\\Instrumentation\\Wordpress\\": "src/Instrumentation/Wordpress/tests", "OpenTelemetry\\Tests\\Instrumentation\\Yii\\": "src/Instrumentation/Yii/tests", + "OpenTelemetry\\Tests\\Propagation\\CloudTrace\\": "src/Propagation/CloudTrace/tests", "OpenTelemetry\\Tests\\Resource\\Detector\\Azure\\": "src/ResourceDetectors/Azure/tests", "OpenTelemetry\\Contrib\\Resource\\Detector\\DigitalOcean\\": "src/ResourceDetectors/DigitalOcean/tests", "OpenTelemetry\\Tests\\Contrib\\Symfony\\": "src/Symfony/tests", @@ -160,6 +163,7 @@ "open-telemetry/opentelemetry-exporter-instana": "self.version", "open-telemetry/opentelemetry-instrumentation-installer": "self.version", "open-telemetry/opentelemetry-logger-monolog": "self.version", + "open-telemetry/opentelemetry-propagation-cloudtrace": "self.version", "open-telemetry/opentelemetry-propagation-instana": "self.version", "open-telemetry/opentelemetry-propagation-server-timing": "self.version", "open-telemetry/opentelemetry-propagation-traceresponse": "self.version", diff --git a/src/Propagation/CloudTrace/.gitattributes b/src/Propagation/CloudTrace/.gitattributes new file mode 100644 index 000000000..1676cf825 --- /dev/null +++ b/src/Propagation/CloudTrace/.gitattributes @@ -0,0 +1,12 @@ +* text=auto + +*.md diff=markdown +*.php diff=php + +/.gitattributes export-ignore +/.gitignore export-ignore +/.php-cs-fixer.php export-ignore +/phpstan.neon.dist export-ignore +/phpunit.xml.dist export-ignore +/psalm.xml.dist export-ignore +/tests export-ignore diff --git a/src/Propagation/CloudTrace/.gitignore b/src/Propagation/CloudTrace/.gitignore new file mode 100644 index 000000000..57872d0f1 --- /dev/null +++ b/src/Propagation/CloudTrace/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/src/Propagation/CloudTrace/.php-cs-fixer.php b/src/Propagation/CloudTrace/.php-cs-fixer.php new file mode 100644 index 000000000..e35fa078c --- /dev/null +++ b/src/Propagation/CloudTrace/.php-cs-fixer.php @@ -0,0 +1,43 @@ +exclude('vendor') + ->exclude('var/cache') + ->in(__DIR__); + +$config = new PhpCsFixer\Config(); +return $config->setRules([ + 'concat_space' => ['spacing' => 'one'], + 'declare_equal_normalize' => ['space' => 'none'], + 'is_null' => true, + 'modernize_types_casting' => true, + 'ordered_imports' => true, + 'php_unit_construct' => true, + 'single_line_comment_style' => true, + 'yoda_style' => false, + '@PSR2' => true, + 'array_syntax' => ['syntax' => 'short'], + 'blank_line_after_opening_tag' => true, + 'blank_line_before_statement' => true, + 'cast_spaces' => true, + 'declare_strict_types' => true, + 'type_declaration_spaces' => true, + 'include' => true, + 'lowercase_cast' => true, + 'new_with_parentheses' => true, + 'no_extra_blank_lines' => true, + 'no_leading_import_slash' => true, + 'echo_tag_syntax' => true, + 'no_unused_imports' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'phpdoc_order' => true, + 'phpdoc_scalar' => true, + 'phpdoc_types' => true, + 'short_scalar_cast' => true, + 'blank_lines_before_namespace' => true, + 'single_quote' => true, + 'trailing_comma_in_multiline' => true, + ]) + ->setRiskyAllowed(true) + ->setFinder($finder); + diff --git a/src/Propagation/CloudTrace/README.md b/src/Propagation/CloudTrace/README.md new file mode 100644 index 000000000..f1e048aba --- /dev/null +++ b/src/Propagation/CloudTrace/README.md @@ -0,0 +1,35 @@ +[![Releases](https://img.shields.io/badge/releases-purple)](https://github.com/opentelemetry-php/extension-propagator-cloudtrace/releases) +[![Issues](https://img.shields.io/badge/issues-pink)](https://github.com/open-telemetry/opentelemetry-php/issues) +[![Source](https://img.shields.io/badge/source-contrib-green)](https://github.com/open-telemetry/opentelemetry-php-contrib/tree/main/src/Propagation/CloudTrace) +[![Mirror](https://img.shields.io/badge/mirror-opentelemetry--php--contrib-blue)](https://github.com/opentelemetry-php/extension-propagator-cloudtrace) +[![Latest Version](http://poser.pugx.org/open-telemetry/extension-propagator-cloudtrace/v/unstable)](https://packagist.org/packages/open-telemetry/extension-propagator-cloudtrace/) +[![Stable](http://poser.pugx.org/open-telemetry/extension-propagator-cloudtrace/v/stable)](https://packagist.org/packages/open-telemetry/extension-propagator-cloudtrace/) + +This is a read-only subtree split of https://github.com/open-telemetry/opentelemetry-php-contrib. + +# OpenTelemetry CloudTrace Propagator + +CloudTrace is a propagator that supports the specification for the header "x-cloud-trace-context" used for trace context propagation across +service boundaries. (https://cloud.google.com/trace/docs/setup#force-trace). OpenTelemetry PHP CloudTrace Propagator Extension provides +option to use it bi-directionally or one-way. One-way does not inject the header for downstream consumption, it only processes the incoming headers +and returns the correct span context. It only attaches to existing X-Cloud-Trace-Context traces and does not create downstream ones. + +## Installation + +```sh +composer require open-telemetry/extension-propagator-cloudtrace +``` + +## Usage + +For one-way CloudTrace: + +``` +$propagator = CloudTracePropagator::getOneWayInstance(); +``` + +For bi-directional CloudTrace: + +``` +$propagator = CloudTracePropagator::getInstance(); +``` diff --git a/src/Propagation/CloudTrace/_register.php b/src/Propagation/CloudTrace/_register.php new file mode 100644 index 000000000..5244f8fea --- /dev/null +++ b/src/Propagation/CloudTrace/_register.php @@ -0,0 +1,20 @@ + + + + + + + src + + + + + + + + + + + + + tests/Unit + + + + diff --git a/src/Propagation/CloudTrace/psalm.xml.dist b/src/Propagation/CloudTrace/psalm.xml.dist new file mode 100644 index 000000000..155711712 --- /dev/null +++ b/src/Propagation/CloudTrace/psalm.xml.dist @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/src/Propagation/CloudTrace/src/CloudTraceFormatter.php b/src/Propagation/CloudTrace/src/CloudTraceFormatter.php new file mode 100644 index 000000000..816ab9ad3 --- /dev/null +++ b/src/Propagation/CloudTrace/src/CloudTraceFormatter.php @@ -0,0 +1,57 @@ +[/][;o=]`. + * The options are a bitmask of options. Currently the only option is the + * least significant bit which signals whether the request was traced or not + * (1 = traced, 0 = not traced). + */ +final class CloudTraceFormatter +{ + const CONTEXT_HEADER_FORMAT = '/([0-9a-fA-F]{32})(?:\/(\d+))?(?:;o=(\d+))?/'; + + /** + * Generate a SpanContext object from the Trace Context header + */ + public static function deserialize(string $header) : SpanContextInterface + { + $matched = preg_match(self::CONTEXT_HEADER_FORMAT, $header, $matches); + + if (!$matched) { + return SpanContext::getInvalid(); + } + if (!array_key_exists(2, $matches) || empty($matches[2])) { + return SpanContext::getInvalid(); + } + if (!array_key_exists(3, $matches)) { + return SpanContext::getInvalid(); + } + + return SpanContext::createFromRemoteParent( + strtolower($matches[1]), + Utils::leftZeroPad(Utils::decToHex($matches[2])), + (int) ($matches[3] === '1') + ); + } + + /** + * Convert a SpanContextInterface to header string + */ + public static function serialize(SpanContextInterface $context) : string + { + $ret = $context->getTraceId(); + if ($context->getSpanId()) { + $ret .= '/' . Utils::hexToDec($context->getSpanId()); + } + + return $ret . (';o=' . ($context->isSampled() ? '1' : '0')); + } +} diff --git a/src/Propagation/CloudTrace/src/CloudTracePropagator.php b/src/Propagation/CloudTrace/src/CloudTracePropagator.php new file mode 100644 index 000000000..90e56ee52 --- /dev/null +++ b/src/Propagation/CloudTrace/src/CloudTracePropagator.php @@ -0,0 +1,99 @@ +oneWay) { + return; + } + + $setter ??= ArrayAccessGetterSetter::getInstance(); + $context ??= Context::getCurrent(); + $spanContext = Span::fromContext($context)->getContext(); + + if (!$spanContext->isValid()) { + return; + } + + $headerValue = CloudTraceFormatter::serialize($spanContext); + $setter->set($carrier, self::XCLOUD, $headerValue); + } + + /** {@inheritdoc} */ + #[\Override] + public function extract($carrier, ?PropagationGetterInterface $getter = null, ?ContextInterface $context = null): ContextInterface + { + $getter ??= ArrayAccessGetterSetter::getInstance(); + $context ??= Context::getCurrent(); + + $headerValue = $getter->get($carrier, self::XCLOUD); + if ($headerValue === null) { + return $context; + } + + $spanContext = CloudTraceFormatter::deserialize($headerValue); + if (!$spanContext->isValid()) { + return $context; + } + + return $context->withContextValue(Span::wrap($spanContext)); + } +} diff --git a/src/Propagation/CloudTrace/src/Utils.php b/src/Propagation/CloudTrace/src/Utils.php new file mode 100644 index 000000000..700bf7095 --- /dev/null +++ b/src/Propagation/CloudTrace/src/Utils.php @@ -0,0 +1,122 @@ += and no >, because this function is used in context of what method to use + * to convert to some base (in our case hex to octal and vice versa). + * So it's ok if we use >=, because it means that only for MAX_INT we will use the slower baseConvert + * method. + * + * @param int|float $number The number to test. + * @return bool Whether it was bigger or not than the max. + */ + public static function isBigNum($number) : bool + { + return $number >= PHP_INT_MAX; + } + + /** + * Custom function to convert a number in string format from one base to another. + * Built-in functions, specifically for hex, do not work well in PHP under + * all versions (32/64-bit) or if the number only fits into an unsigned long. + * PHP does not have unsigned longs, so this function is necessary. + * + * @param string $num The number to convert (in some base). + * @param int $fromBase The base to convert from. + * @param int $toBase The base to convert to. + * @return string Converted number in the new base. + */ + public static function baseConvert(string $num, int $fromBase, int $toBase) : string + { + $num = strtolower($num); + $chars = '0123456789abcdefghijklmnopqrstuvwxyz'; + $newstring = substr($chars, 0, $toBase); + + $length = strlen($num); + $result = ''; + + $number = []; + for ($i = 0; $i < $length; $i++) { + $number[$i] = strpos($chars, $num[$i]); + } + + do { + $divide = 0; + $newlen = 0; + for ($i = 0; $i < $length; $i++) { + if (!isset($number[$i]) || $number[$i] === false) { + return ''; + } + $divide = $divide * $fromBase + $number[$i]; + if ($divide >= $toBase) { + $number[$newlen++] = (int) ($divide / $toBase); + $divide %= $toBase; + } elseif ($newlen > 0) { + $number[$newlen++] = 0; + } + } + $length = $newlen; + $result = $newstring[$divide] . $result; + } while ($newlen != 0); + + return $result; + } +} diff --git a/src/Propagation/CloudTrace/tests/Unit/CloudTraceFormatterTest.php b/src/Propagation/CloudTrace/tests/Unit/CloudTraceFormatterTest.php new file mode 100644 index 000000000..0a7af692e --- /dev/null +++ b/src/Propagation/CloudTrace/tests/Unit/CloudTraceFormatterTest.php @@ -0,0 +1,51 @@ +assertEquals($result->getTraceId(), $traceId, "Given deserialize($header), traceId != $traceId (result={$result->getTraceId()}"); + $this->assertEquals($result->getSpanId(), $spanId, "Given deserialize($header), spanId != $spanId (result={$result->getSpanId()}"); + $this->assertEquals($result->getTraceFlags(), $sample, "Given deserialize($header), traceFlags != $sample (result={$result->getTraceFlags()}"); + } + + public static function for_test_deserialize() : array + { + return [ + ['00000000000000000000000000000001/1;o=0', '00000000000000000000000000000001', '0000000000000001', 0], + ['10000000000000000000000000000001/10;o=1', '10000000000000000000000000000001', '000000000000000a', 1], + ]; + } + + /** + * @dataProvider for_test_serialize + */ + public function test_serialize(SpanContextInterface $span, string $header) : void + { + $result = CloudTraceFormatter::serialize($span); + $this->assertEquals($result, $header, "Given serialize(header), result != $header (result=$result"); + } + + public static function for_test_serialize() : array + { + return [ + [SpanContext::createFromRemoteParent('00000000000000000000000000000001', '0000000000000001', TraceFlags::DEFAULT), '00000000000000000000000000000001/1;o=0'], + [SpanContext::createFromRemoteParent('00000000000000000000000000000001', '000000000000000a', TraceFlags::SAMPLED), '00000000000000000000000000000001/10;o=1'], + ]; + } +} diff --git a/src/Propagation/CloudTrace/tests/Unit/CloudTracePropagatorOneWayTest.php b/src/Propagation/CloudTrace/tests/Unit/CloudTracePropagatorOneWayTest.php new file mode 100644 index 000000000..0282a4524 --- /dev/null +++ b/src/Propagation/CloudTrace/tests/Unit/CloudTracePropagatorOneWayTest.php @@ -0,0 +1,177 @@ +cloudTracePropagator = CloudTracePropagator::getOneWayInstance(); + [$this->xcloud] = $this->cloudTracePropagator->fields(); + } + + public function test_fields(): void + { + $this->assertSame( + ['x-cloud-trace-context'], + $this->cloudTracePropagator->fields() + ); + } + + public function test_inject_invalid_context(): void + { + $carrier = []; + $this + ->cloudTracePropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create( + SpanContextValidator::INVALID_TRACE, + SpanContextValidator::INVALID_SPAN, + TraceFlags::SAMPLED + ), + Context::getCurrent() + ) + ); + $this->assertEmpty($carrier); + } + + public function test_inject_sampled_context(): void + { + $carrier = []; + $this + ->cloudTracePropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::SAMPLED), + Context::getCurrent() + ) + ); + $this->assertEmpty($carrier); + } + + public function test_inject_non_sampled_context(): void + { + $carrier = []; + $this + ->cloudTracePropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::DEFAULT), + Context::getCurrent() + ) + ); + $this->assertEmpty($carrier); + } + + public function test_inject_non_sampled_default_context(): void + { + $carrier = []; + $this + ->cloudTracePropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), + Context::getCurrent() + ) + ); + $this->assertEmpty($carrier); + } + + public function test_extract_nothing(): void + { + $this->assertSame( + Context::getCurrent(), + $this->cloudTracePropagator->extract([]) + ); + } + + public function test_extract_sampled_context(): void + { + $carrier = [ + $this->xcloud => $this->generateTraceIdHeaderValue( + self::TRACE_ID_BASE16, + self::SPAN_ID_BASE10, + self::TRACE_ENABLED + ), + ]; + + $context = $this->cloudTracePropagator->extract($carrier); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::SAMPLED), + $this->getSpanContext($context) + ); + } + + public function test_extract_non_sampled_context(): void + { + $carrier = [ + $this->xcloud => $this->generateTraceIdHeaderValue( + self::TRACE_ID_BASE16, + self::SPAN_ID_BASE10, + self::TRACE_DISABLED + ), + ]; + + $context = $this->cloudTracePropagator->extract($carrier); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::DEFAULT), + $this->getSpanContext($context) + ); + } + + private function getSpanContext(ContextInterface $context): SpanContextInterface + { + return Span::fromContext($context)->getContext(); + } + + private function withSpanContext(SpanContextInterface $spanContext, ContextInterface $context): ContextInterface + { + return $context->withContextValue(Span::wrap($spanContext)); + } + + private function generateTraceIdHeaderValue( + string $traceId, + string $spanId, + int $traceEnabled + ): string { + return sprintf('%s/%s;o=%d', $traceId, $spanId, $traceEnabled); + } +} diff --git a/src/Propagation/CloudTrace/tests/Unit/CloudTracePropagatorTest.php b/src/Propagation/CloudTrace/tests/Unit/CloudTracePropagatorTest.php new file mode 100644 index 000000000..50d2b7e2b --- /dev/null +++ b/src/Propagation/CloudTrace/tests/Unit/CloudTracePropagatorTest.php @@ -0,0 +1,201 @@ +cloudTracePropagator = CloudTracePropagator::getInstance(); + [$this->xcloud] = $this->cloudTracePropagator->fields(); + } + + public function test_fields(): void + { + $this->assertSame( + ['x-cloud-trace-context'], + $this->cloudTracePropagator->fields() + ); + } + + public function test_inject_invalid_context(): void + { + $carrier = []; + $this + ->cloudTracePropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create( + SpanContextValidator::INVALID_TRACE, + SpanContextValidator::INVALID_SPAN, + TraceFlags::SAMPLED + ), + Context::getCurrent() + ) + ); + $this->assertEmpty($carrier); + } + + public function test_inject_sampled_context(): void + { + $carrier = []; + $this + ->cloudTracePropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::SAMPLED), + Context::getCurrent() + ) + ); + + $this->assertSame( + [$this->xcloud => $this->generateTraceIdHeaderValue( + self::TRACE_ID_BASE16, + self::SPAN_ID_BASE10, + self::TRACE_ENABLED + )], + $carrier + ); + } + + public function test_inject_non_sampled_context(): void + { + $carrier = []; + $this + ->cloudTracePropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::DEFAULT), + Context::getCurrent() + ) + ); + + $this->assertSame( + [$this->xcloud => $this->generateTraceIdHeaderValue( + self::TRACE_ID_BASE16, + self::SPAN_ID_BASE10, + self::TRACE_DISABLED + )], + $carrier + ); + } + + public function test_inject_non_sampled_default_context(): void + { + $carrier = []; + $this + ->cloudTracePropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), + Context::getCurrent() + ) + ); + + $this->assertSame( + [$this->xcloud => $this->generateTraceIdHeaderValue( + self::TRACE_ID_BASE16, + self::SPAN_ID_BASE10, + self::TRACE_DISABLED + )], + $carrier + ); + } + + public function test_extract_nothing(): void + { + $this->assertSame( + Context::getCurrent(), + $this->cloudTracePropagator->extract([]) + ); + } + + public function test_extract_sampled_context(): void + { + $carrier = [ + $this->xcloud => $this->generateTraceIdHeaderValue( + self::TRACE_ID_BASE16, + self::SPAN_ID_BASE10, + self::TRACE_ENABLED + ), + ]; + + $context = $this->cloudTracePropagator->extract($carrier); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::SAMPLED), + $this->getSpanContext($context) + ); + } + + public function test_extract_non_sampled_context(): void + { + $carrier = [ + $this->xcloud => $this->generateTraceIdHeaderValue( + self::TRACE_ID_BASE16, + self::SPAN_ID_BASE10, + self::TRACE_DISABLED + ), + ]; + + $context = $this->cloudTracePropagator->extract($carrier); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::DEFAULT), + $this->getSpanContext($context) + ); + } + + private function getSpanContext(ContextInterface $context): SpanContextInterface + { + return Span::fromContext($context)->getContext(); + } + + private function withSpanContext(SpanContextInterface $spanContext, ContextInterface $context): ContextInterface + { + return $context->withContextValue(Span::wrap($spanContext)); + } + + private function generateTraceIdHeaderValue( + string $traceId, + string $spanId, + int $traceEnabled + ): string { + return sprintf('%s/%s;o=%d', $traceId, $spanId, $traceEnabled); + } +} diff --git a/src/Propagation/CloudTrace/tests/Unit/UtilsTest.php b/src/Propagation/CloudTrace/tests/Unit/UtilsTest.php new file mode 100644 index 000000000..ae00d241e --- /dev/null +++ b/src/Propagation/CloudTrace/tests/Unit/UtilsTest.php @@ -0,0 +1,123 @@ +assertEquals(Utils::leftZeroPad($pad, $howMuch), $equalsTo, "Given leftZeroPad($pad, $howMuch) != $equalsTo"); + } + + public static function for_test_left_zero_pad() : array + { + return [ + ['a', 3, '00a'], + ['aaa', 3, 'aaa'], + ['aaa', 16, '0000000000000aaa'], + ['', 1, '0'], + ]; + } + + /** + * @dataProvider for_test_dec_to_hex + */ + public function test_dec_to_hex(string $decNum, string $equalsTo) : void + { + $this->assertEquals(Utils::decToHex($decNum), $equalsTo, "Given decToHex($decNum) != $equalsTo"); + } + + public static function for_test_dec_to_hex() : array + { + return [ + ['10', 'a'], + ['1', '1'], + ['9223372036854775807', '7fffffffffffffff'], + ['18446744073709551615', 'ffffffffffffffff'], + ['28446744073709551615', '18ac7230489e7ffff'], + ]; + } + + /** + * @dataProvider for_test_hex_to_dec + */ + public function test_hex_to_dec(string $hexNum, string $equalsTo) : void + { + $this->assertEquals(Utils::hexToDec($hexNum), $equalsTo, "Given hexToDec($hexNum) != $equalsTo"); + } + + public static function for_test_hex_to_dec() : array + { + return [ + ['a', '10'], + ['B', '11'], + ['0xc', '12'], + ['1', '1'], + ['7fffffffffffffff', '9223372036854775807'], + ['1ffffffffffffffA', '2305843009213693946'], + ['1fffffffffffffff', '2305843009213693951'], + ['8fffffffffffffff', '10376293541461622783'], + ['7fffffffffffffff', '9223372036854775807'], + ['8000000000000000', '9223372036854775808'], + ['ffffffffffffffff', '18446744073709551615'], + ['18ac7230489e7ffff', '28446744073709551615'], + ]; + } + + /** + * @dataProvider for_test_is_big_num + */ + public function test_is_big_num($num, bool $equalsTo) : void + { + $this->assertEquals(Utils::isBigNum($num), $equalsTo, "Given isBigNum($num) != $equalsTo"); + } + + public static function for_test_is_big_num() : array + { + return [ + [-100.5, false], + [-1, false], + [1, false], + [100.5, false], + [9223372036854775806, false], + [9223372036854775807, true], + [9223372036854775808, true], + [18446744073709551615, true], + [28446744073709551615, true], + ]; + } + + /** + * @dataProvider for_test_base_convert + */ + public function test_base_convert(string $num, int $fromBase, int $toBase, string $equalsTo) : void + { + $result = Utils::baseConvert($num, $fromBase, $toBase); + $this->assertEquals($result, $equalsTo, "Given baseConvert($num, $fromBase, $toBase) != $equalsTo (result=$result)"); + } + + public static function for_test_base_convert() : array + { + return [ + ['b', 16, 10, '11'], + ['c', 16, 10, '12'], + ['fffffffffffff', 16, 10, '4503599627370495'], + ['7fffffff', 16, 10, '2147483647'], + ['7ffffffffffffffe', 16, 10, '9223372036854775806'], + ['7fffffffffffffff', 16, 10, '9223372036854775807'], + ['8000000000000000', 16, 10, '9223372036854775808'], // bigger than signed int max 64 bit + ['18ac7230489e7ffff', 16, 10, '28446744073709551615'], + ['28446744073709551615', 10, 16, '18ac7230489e7ffff'], + ['10', 10, 16, 'a'], + ]; + } +}