opentelemetry-php/tests/Integration/SDK/SpanBuilderTest.php

637 lines
20 KiB
PHP

<?php
declare(strict_types=1);
namespace OpenTelemetry\Tests\Integration\SDK\Trace;
use Mockery;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use Mockery\MockInterface;
use OpenTelemetry\API\Trace as API;
use OpenTelemetry\API\Trace\SpanContext;
use OpenTelemetry\Context\Context;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Common\Attribute\AttributesInterface;
use OpenTelemetry\SDK\Trace\Link;
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOffSampler;
use OpenTelemetry\SDK\Trace\SamplerInterface;
use OpenTelemetry\SDK\Trace\SamplingResult;
use OpenTelemetry\SDK\Trace\Span;
use OpenTelemetry\SDK\Trace\SpanLimitsBuilder;
use OpenTelemetry\SDK\Trace\SpanProcessorInterface;
use OpenTelemetry\SDK\Trace\TracerProvider;
use function range;
use function str_repeat;
/**
* @coversNothing
*/
class SpanBuilderTest extends MockeryTestCase
{
private const SPAN_NAME = 'span_name';
private API\TracerInterface $tracer;
private API\SpanContextInterface $sampledSpanContext;
/** @var MockInterface&SpanProcessorInterface */
private $spanProcessor;
protected function setUp(): void
{
$this->spanProcessor = Mockery::spy(SpanProcessorInterface::class);
$this->tracer = (new TracerProvider($this->spanProcessor))->getTracer('SpanBuilderTest');
$this->sampledSpanContext = SpanContext::create(
'12345678876543211234567887654321',
'8765432112345678',
API\SpanContextInterface::TRACE_FLAG_SAMPLED,
);
}
/**
* @group trace-compliance
*/
public function test_add_link(): void
{
/** @var Span $span */
$span = $this
->tracer
->spanBuilder(self::SPAN_NAME)
->addLink($this->sampledSpanContext)
->addLink($this->sampledSpanContext, [])
->startSpan();
$this->assertCount(2, $span->toSpanData()->getLinks());
}
public function test_add_link_invalid(): void
{
/** @var Span $span */
$span = $this
->tracer
->spanBuilder(self::SPAN_NAME)
->addLink(Span::getInvalid()->getContext())
->addLink(Span::getInvalid()->getContext(), [])
->startSpan();
$this->assertEmpty($span->toSpanData()->getLinks());
$span->end();
}
public function test_add_link_dropping_links(): void
{
$maxNumberOfLinks = 8;
$spanBuilder = (new TracerProvider([], null, null, (new SpanLimitsBuilder())->setLinkCountLimit($maxNumberOfLinks)->build()))
->getTracer('test')
->spanBuilder(self::SPAN_NAME);
for ($idx = 0; $idx < $maxNumberOfLinks * 2; $idx++) {
$spanBuilder->addLink($this->sampledSpanContext);
}
/** @var Span $span */
$span = $spanBuilder->startSpan();
$spanData = $span->toSpanData();
$links = $spanData->getLinks();
$this->assertCount($maxNumberOfLinks, $links);
$this->assertSame(8, $spanData->getTotalDroppedLinks());
for ($idx = 0; $idx < $maxNumberOfLinks; $idx++) {
$this->assertEquals(new Link($this->sampledSpanContext, Attributes::create([])), $links[$idx]);
}
$span->end();
}
public function test_add_link_truncate_link_attributes(): void
{
/** @var Span $span */
$span = (new TracerProvider([], null, null, (new SpanLimitsBuilder())->setAttributePerLinkCountLimit(1)->build()))
->getTracer('test')
->spanBuilder(self::SPAN_NAME)
->addLink(
$this->sampledSpanContext,
[
'key0' => 0,
'key1' => 1,
'key2' => 2,
]
)
->startSpan();
$this->assertCount(1, $span->toSpanData()->getLinks());
$this->assertCount(1, $span->toSpanData()->getLinks()[0]->getAttributes());
}
public function test_add_link_truncate_link_attribute_value(): void
{
$maxLength = 25;
$strVal = str_repeat('a', $maxLength);
$tooLongStrVal = "${strVal}${strVal}";
/** @var Span $span */
$span = (new TracerProvider([], null, null, (new SpanLimitsBuilder())->setAttributeValueLengthLimit($maxLength)->build()))
->getTracer('test')
->spanBuilder(self::SPAN_NAME)
->addLink(
$this->sampledSpanContext,
[
'string' => $tooLongStrVal,
'bool' => true,
'string_array' => [$strVal, $tooLongStrVal],
'int_array' => [1, 2],
]
)
->startSpan();
$attrs = $span->toSpanData()->getLinks()[0]->getAttributes();
$this->assertSame($strVal, $attrs->get('string'));
$this->assertTrue($attrs->get('bool'));
$this->assertSame(
[$strVal, $strVal],
$attrs->get('string_array')
);
$this->assertSame(
[1, 2],
$attrs->get('int_array')
);
}
/**
* @group trace-compliance
*/
public function test_add_link_no_effect_after_start_span(): void
{
$spanBuilder = $this->tracer->spanBuilder(self::SPAN_NAME);
/** @var Span $span */
$span = $spanBuilder
->addLink($this->sampledSpanContext)
->startSpan();
$this->assertCount(1, $span->toSpanData()->getLinks());
$spanBuilder
->addLink(
SpanContext::create(
'00000000000004d20000000000001a85',
'0000000000002694',
API\SpanContextInterface::TRACE_FLAG_SAMPLED
)
);
$this->assertCount(1, $span->toSpanData()->getLinks());
}
/**
* @group trace-compliance
*/
public function test_set_attribute(): void
{
/** @var Span $span */
$span = $this
->tracer
->spanBuilder(self::SPAN_NAME)
->setAttribute('foo', 'bar')
->setAttribute('bar', 123)
->setAttribute('empty-arr', [])
->setAttribute('int-arr', [1, 2, 3])
->setAttribute('nil', null)
->startSpan();
$attributes = $span->toSpanData()->getAttributes();
$this->assertSame(4, $attributes->count());
$this->assertSame('bar', $attributes->get('foo'));
$this->assertSame(123, $attributes->get('bar'));
$this->assertSame([], $attributes->get('empty-arr'));
$this->assertSame([1, 2, 3], $attributes->get('int-arr'));
$this->assertNull($attributes->get('nil'));
}
/**
* @group trace-compliance
*/
public function test_set_attribute_no_effect_after_end(): void
{
/** @var Span $span */
$span = $this
->tracer
->spanBuilder(self::SPAN_NAME)
->setAttribute('foo', 'bar')
->setAttribute('bar', 123)
->startSpan();
$attributes = $span->toSpanData()->getAttributes();
$this->assertSame(2, $attributes->count());
$this->assertSame('bar', $attributes->get('foo'));
$this->assertSame(123, $attributes->get('bar'));
$span->end();
$span->setAttribute('doo', 'baz');
$this->assertSame(2, $attributes->count());
$this->assertFalse($attributes->has('doo'));
}
/**
* @group trace-compliance
*/
// public function test_set_attribute_empty_string_value_is_set(): void
// {
// /** @var Span $span */
// $span = $this
// ->tracer
// ->spanBuilder(self::SPAN_NAME)
// ->setAttribute('nil', null)
// ->setAttribute('empty-string', '')
// ->startSpan();
// $attributes = $span->toSpanData()->getAttributes();
// $this->assertSame(1, $attributes->count());
// $this->assertSame('', $attributes->get('empty-string'));
// $this->assertNull($attributes->get('nil'));
// $span->end();
// }
/**
* @group trace-compliance
*/
public function test_set_attribute_only_null_string_value_should_not_be_set(): void
{
/** @var Span $span */
$span = $this
->tracer
->spanBuilder(self::SPAN_NAME)
->setAttribute('nil', null)
->startSpan();
$attributes = $span->toSpanData()->getAttributes();
$this->assertEmpty($span->toSpanData()->getAttributes());
$this->assertNull($attributes->get('nil'));
}
/**
* @group trace-compliance
*/
public function test_set_attribute_no_effect_after_start_span(): void
{
$spanBuilder = $this->tracer->spanBuilder(self::SPAN_NAME);
/** @var Span $span */
$span = $spanBuilder
->setAttribute('foo', 'bar')
->setAttribute('bar', 123)
->startSpan();
$attributes = $span->toSpanData()->getAttributes();
$this->assertSame(2, $attributes->count());
$spanBuilder
->setAttribute('bar1', 77);
$attributes = $span->toSpanData()->getAttributes();
$this->assertSame(2, $attributes->count());
$this->assertFalse($attributes->has('bar1'));
}
public function test_set_attribute_dropping(): void
{
$maxNumberOfAttributes = 8;
$spanBuilder = (new TracerProvider(
null,
null,
null,
(new SpanLimitsBuilder())->setAttributeCountLimit($maxNumberOfAttributes)->build()
))->getTracer('test')->spanBuilder(self::SPAN_NAME);
foreach (range(1, $maxNumberOfAttributes * 2) as $idx) {
$spanBuilder->setAttribute("str_attribute_${idx}", $idx);
}
/** @var Span $span */
$span = $spanBuilder->startSpan();
$attributes = $span->toSpanData()->getAttributes();
$this->assertCount($maxNumberOfAttributes, $attributes);
foreach (range(1, $maxNumberOfAttributes) as $idx) {
$this->assertSame($idx, $attributes->get("str_attribute_${idx}"));
}
}
public function test_add_attributes_via_sampler(): void
{
$sampler = new class() implements SamplerInterface {
public function shouldSample(
Context $parentContext,
string $traceId,
string $spanName,
int $spanKind,
?AttributesInterface $attributes = null,
array $links = []
): SamplingResult {
return new SamplingResult(SamplingResult::RECORD_AND_SAMPLE, ['cat' => 'meow']);
}
public function getDescription(): string
{
return 'test';
}
};
/** @var Span $span */
$span = (new TracerProvider([], $sampler))->getTracer('test')->spanBuilder(self::SPAN_NAME)->startSpan();
$span->end();
$attributes = $span->toSpanData()->getAttributes();
$this->assertSame(1, $attributes->count());
$this->assertSame('meow', $attributes->get('cat'));
}
public function test_set_attributes(): void
{
$attributes = ['id' => 1, 'foo' => 'bar'];
/** @var Span $span */
$span = $this->tracer->spanBuilder(self::SPAN_NAME)->setAttributes($attributes)->startSpan();
$attributes = $span->toSpanData()->getAttributes();
$this->assertSame(2, $attributes->count());
$this->assertSame('bar', $attributes->get('foo'));
$this->assertSame(1, $attributes->get('id'));
}
/**
* @group trace-compliance
*/
public function test_set_attributes_merges_attributes_correctly(): void
{
$attributes = ['id' => 2, 'foo' => 'bar', 'key' => 'val'];
/** @var Span $span */
$span = $this
->tracer
->spanBuilder(self::SPAN_NAME)
->setAttribute('key2', 'val2')
->setAttribute('key1', 'val1')
->setAttributes($attributes)
->startSpan();
$attributes = $span->toSpanData()->getAttributes();
$this->assertSame(5, $attributes->count());
$this->assertSame('bar', $attributes->get('foo'));
$this->assertSame(2, $attributes->get('id'));
$this->assertSame('val', $attributes->get('key'));
$this->assertSame('val2', $attributes->get('key2'));
$this->assertSame('val1', $attributes->get('key1'));
}
public function test_set_attributes_overrides_values(): void
{
$attributes = ['id' => 1, 'foo' => 'bar'];
/** @var Span $span */
$span = $this
->tracer
->spanBuilder(self::SPAN_NAME)
->setAttribute('id', 0)
->setAttribute('foo', 'baz')
->setAttributes($attributes)
->startSpan();
$attributes = $span->toSpanData()->getAttributes();
$this->assertSame(2, $attributes->count());
$this->assertSame('bar', $attributes->get('foo'));
$this->assertSame(1, $attributes->get('id'));
}
public function test_is_recording_default(): void
{
/** @var Span $span */
$span = $this->tracer->spanBuilder(self::SPAN_NAME)->startSpan();
$this->assertTrue($span->isRecording());
$span->end();
}
public function test_is_recording_sampler(): void
{
/** @var Span $span */
$span = (new TracerProvider([], new AlwaysOffSampler()))
->getTracer('test')
->spanBuilder(self::SPAN_NAME)
->startSpan();
$this->assertFalse($span->isRecording());
$span->end();
}
public function test_get_kind_default(): void
{
/** @var Span $span */
$span = $this->tracer->spanBuilder(self::SPAN_NAME)->startSpan();
$this->assertSame(API\SpanKind::KIND_INTERNAL, $span->getKind());
$span->end();
}
public function test_get_kind(): void
{
/** @var Span $span */
$span = $this->tracer->spanBuilder(self::SPAN_NAME)->setSpanKind(API\SpanKind::KIND_CONSUMER)->startSpan();
$this->assertSame(API\SpanKind::KIND_CONSUMER, $span->getKind());
$span->end();
}
public function test_start_timestamp(): void
{
/** @var Span $span */
$span = $this->tracer->spanBuilder(self::SPAN_NAME)->setStartTimestamp(123)->startSpan();
$span->end();
$this->assertSame(123, $span->toSpanData()->getStartEpochNanos());
}
public function test_set_no_parent(): void
{
$parentSpan = $this->tracer->spanBuilder(self::SPAN_NAME)->startSpan();
$parentScope = $parentSpan->activate();
/** @var Span $span */
$span = $this->tracer->spanBuilder(self::SPAN_NAME)->setNoParent()->startSpan();
$this->assertNotSame(
$span->getContext()->getTraceId(),
$parentSpan->getContext()->getTraceId()
);
$this
->spanProcessor
->shouldHaveReceived('onStart')
->with($span, Context::getRoot())
->once();
/** @var Span $spanNoParent */
$spanNoParent = $this
->tracer
->spanBuilder(self::SPAN_NAME)
->setNoParent()
->setParent(Context::getCurrent())
->setNoParent()
->startSpan();
$this->assertNotSame($span->getContext()->getTraceId(), $spanNoParent->getContext()->getTraceId());
$this
->spanProcessor
->shouldHaveReceived('onStart')
->with($spanNoParent, Context::getRoot())
->once();
$spanNoParent->end();
$span->end();
$parentScope->detach();
$parentSpan->end();
}
public function test_set_no_parent_override(): void
{
$parentSpan = $this->tracer->spanBuilder(self::SPAN_NAME)->startSpan();
$parentContext = Context::getCurrent()->withContextValue($parentSpan);
/** @var Span $span */
$span = $this->tracer->spanBuilder(self::SPAN_NAME)->setNoParent()->setParent($parentContext)->startSpan();
$this
->spanProcessor
->shouldHaveReceived('onStart')
->with($span, $parentContext)
->once();
$this->assertSame(
$span->getContext()->getTraceId(),
$parentSpan->getContext()->getTraceId()
);
$this->assertSame(
$span->toSpanData()->getParentSpanId(),
$parentSpan->getContext()->getSpanId()
);
$parentContext2 = Context::getCurrent()->withContextValue($parentSpan);
/** @var Span $span2 */
$span2 = $this
->tracer
->spanBuilder(self::SPAN_NAME)
->setNoParent()
->setParent($parentContext2)
->startSpan();
$this
->spanProcessor
->shouldHaveReceived('onStart')
->with($span2, $parentContext2)
->once();
$this->assertSame(
$span2->getContext()->getTraceId(),
$parentSpan->getContext()->getTraceId()
);
$span2->end();
$span->end();
$parentSpan->end();
}
public function test_set_parent_empty_context(): void
{
$emptyContext = Context::getCurrent();
$parentSpan = $this->tracer->spanBuilder(self::SPAN_NAME)->startSpan();
$parentScope = $parentSpan->activate();
/** @var Span $span */
$span = $this->tracer->spanBuilder(self::SPAN_NAME)->setParent($emptyContext)->startSpan();
$this
->spanProcessor
->shouldHaveReceived('onStart')
->with($span, $emptyContext)
->once();
$this->assertNotSame(
$span->getContext()->getTraceId(),
$parentSpan->getContext()->getTraceId()
);
$this->assertNotSame(
$span->toSpanData()->getParentSpanId(),
$parentSpan->getContext()->getSpanId()
);
$span->end();
$parentScope->detach();
$parentSpan->end();
}
public function test_set_parent_current_span(): void
{
$parentSpan = $this->tracer->spanBuilder(self::SPAN_NAME)->startSpan();
$parentScope = $parentSpan->activate();
$implicitContext = Context::getCurrent();
/** @var Span $span */
$span = $this->tracer->spanBuilder(self::SPAN_NAME)->startSpan();
$this
->spanProcessor
->shouldHaveReceived('onStart')
->with($span, $implicitContext)
->once();
$this->assertSame(
$span->getContext()->getTraceId(),
$parentSpan->getContext()->getTraceId()
);
$this->assertSame(
$span->toSpanData()->getParentSpanId(),
$parentSpan->getContext()->getSpanId()
);
$span->end();
$parentScope->detach();
$parentSpan->end();
}
public function test_set_parent_invalid_context(): void
{
$parentSpan = Span::getInvalid();
$parentContext = Context::getCurrent()->withContextValue($parentSpan);
/** @var Span $span */
$span = $this->tracer->spanBuilder(self::SPAN_NAME)->setParent($parentContext)->startSpan();
$this
->spanProcessor
->shouldHaveReceived('onStart')
->with($span, $parentContext)
->once();
$this->assertNotSame(
$span->getContext()->getTraceId(),
$parentSpan->getContext()->getTraceId()
);
$this->assertFalse(SpanContext::isValidSpanId($span->toSpanData()->getParentSpanId()));
$span->end();
$parentSpan->end();
}
}