Skip to content

Commit 8035f6f

Browse files
committed
added unit tests
1 parent afb8528 commit 8035f6f

12 files changed

+931
-26
lines changed

src/Sampler/Xray/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"autoload": {
2626
"psr-4": {
2727
"OpenTelemetry\\Contrib\\Sampler\\Xray\\": "src/"
28-
}
28+
},
29+
"classmap": ["src/AWSXRayRemoteSampler.php"]
2930
},
3031
"minimum-stability": "dev",
3132
"prefer-stable": true,

src/Sampler/Xray/src/AWSXRaySamplerClient.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public function getSamplingTargets(array $statistics): ?object
7676
$docs = [];
7777
foreach ($statistics as $d) {
7878
$docs[] = [
79-
'ClientID' => $d->ClientId,
79+
'ClientID' => $d->ClientID,
8080
'RuleName' => $d->RuleName,
8181
'RequestCount' => $d->RequestCount,
8282
'SampleCount' => $d->SampleCount,

src/Sampler/Xray/src/SamplingRuleApplier.php

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -48,31 +48,17 @@ public function __construct(string $clientId, Clock $clock, SamplingRule $rule,
4848
public function matches(AttributesInterface $attributes, ResourceInfo $resource): bool
4949
{
5050
// Extract HTTP path
51-
$httpTarget = $attributes->get(TraceAttributes::HTTP_TARGET)
52-
?? (
53-
null !== $attributes->get(TraceAttributes::HTTP_URL)
54-
? (preg_match('~^[^:]+://[^/]+(/.*)?$~', $attributes->get(TraceAttributes::HTTP_URL), $m)
55-
? ($m[1] ?? '/')
56-
: null
57-
)
58-
: null
59-
);
60-
61-
if (empty($httpTarget)) {
62-
// Extract HTTP path
63-
$httpTarget = $attributes->get(TraceAttributes::URL_PATH)
64-
?? (
65-
null !== $attributes->get(TraceAttributes::URL_FULL)
66-
? (preg_match('~^[^:]+://[^/]+(/.*)?$~', $attributes->get(TraceAttributes::URL_FULL), $m)
67-
? ($m[1] ?? '/')
68-
: null
69-
)
70-
: null
71-
);
51+
$httpTarget = $attributes->get(TraceAttributes::HTTP_TARGET) ?? $attributes->get(TraceAttributes::URL_PATH);
52+
$httpUrl = $attributes->get(TraceAttributes::HTTP_URL) ?? $attributes->get(TraceAttributes::URL_FULL);
53+
if ($httpTarget == null && isset($httpUrl)) {
54+
$httpTarget = parse_url($httpUrl, PHP_URL_PATH);
7255
}
7356

7457
$httpMethod = $attributes->get(TraceAttributes::HTTP_METHOD) ?? $attributes->get(TraceAttributes::HTTP_REQUEST_METHOD);
75-
$httpHost = $attributes->get(TraceAttributes::HTTP_HOST) ?? null;
58+
if ($httpMethod == "_OTHER") {
59+
$httpMethod = $attributes->get(TraceAttributes::HTTP_REQUEST_METHOD_ORIGINAL);
60+
}
61+
$httpHost = $attributes->get(TraceAttributes::HTTP_HOST) ?? $attributes->get(TraceAttributes::SERVER_ADDRESS) ;
7662
$serviceName= $resource->getAttributes()->get(TraceAttributes::SERVICE_NAME) ?? '';
7763
$cloudPlat = $resource->getAttributes()->get(TraceAttributes::CLOUD_PLATFORM) ?? null;
7864
$serviceType= Matcher::getXRayCloudPlatform($cloudPlat);

src/Sampler/Xray/src/SamplingStatisticsDocument.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
class SamplingStatisticsDocument
66
{
7-
public string $ClientId;
7+
public string $ClientID;
88
public string $RuleName;
99
public int $RequestCount;
1010
public int $SampleCount;
@@ -13,7 +13,7 @@ class SamplingStatisticsDocument
1313

1414
public function __construct(string $clientId, string $ruleName, int $req, int $samp, int $borrow, float $ts)
1515
{
16-
$this->ClientId = $clientId;
16+
$this->ClientID = $clientId;
1717
$this->RuleName = $ruleName;
1818
$this->RequestCount = $req;
1919
$this->SampleCount = $samp;
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
use PHPUnit\Framework\TestCase;
5+
use OpenTelemetry\Contrib\Sampler\Xray\_AWSXRayRemoteSampler;
6+
use OpenTelemetry\Contrib\Sampler\Xray\Clock;
7+
use OpenTelemetry\Contrib\Sampler\Xray\RulesCache;
8+
use OpenTelemetry\Contrib\Sampler\Xray\AWSXRaySamplerClient;
9+
use OpenTelemetry\Contrib\Sampler\Xray\FallbackSampler;
10+
use OpenTelemetry\SDK\Resource\ResourceInfo;
11+
use OpenTelemetry\Context\ContextInterface;
12+
use OpenTelemetry\SDK\Common\Attribute\Attributes;
13+
use OpenTelemetry\SDK\Trace\SamplingResult;
14+
15+
final class AWSXRayRemoteSamplerTest extends TestCase
16+
{
17+
public function testShouldSampleUpdatesRulesAndTargets(): void
18+
{
19+
$resource = ResourceInfo::create(Attributes::create([]));
20+
21+
// 1) Mock client
22+
$mockClient = $this->createMock(AWSXRaySamplerClient::class);
23+
$dummyRules = ['rule1', 'rule2'];
24+
$mockClient->expects($this->once())
25+
->method('getSamplingRules')
26+
->willReturn($dummyRules);
27+
$mockClient->expects($this->once())
28+
->method('getSamplingTargets')
29+
->willReturn((object)[
30+
'SamplingTargetDocuments' => [],
31+
'Interval' => 5,
32+
'LastRuleModification' => 0,
33+
]);
34+
35+
// 2) Mock RulesCache
36+
$mockRulesCache = $this->getMockBuilder(RulesCache::class)
37+
->disableOriginalConstructor()
38+
->getMock();
39+
$mockRulesCache->expects($this->once())
40+
->method('updateRules')
41+
->with($dummyRules);
42+
$mockRulesCache->expects($this->once())
43+
->method('getAppliers')
44+
->willReturn([]);
45+
$mockRulesCache->expects($this->once())
46+
->method('updateTargets')
47+
->with([]);
48+
$mockRulesCache->expects($this->once())
49+
->method('expired')
50+
->willReturn(false);
51+
$expectedResult = new SamplingResult(SamplingResult::RECORD_AND_SAMPLE);
52+
$mockRulesCache->expects($this->once())
53+
->method('shouldSample')
54+
->willReturn($expectedResult);
55+
56+
// 3) Instantiate and inject mocks
57+
$sampler = new _AWSXRayRemoteSampler($resource, 'host', 0);
58+
$ref = new ReflectionClass($sampler);
59+
$ref->getProperty('client')->setAccessible(true);
60+
$ref->getProperty('client')->setValue($sampler, $mockClient);
61+
$ref->getProperty('rulesCache')->setAccessible(true);
62+
$ref->getProperty('rulesCache')->setValue($sampler, $mockRulesCache);
63+
$ref->getProperty('fallback')->setAccessible(true);
64+
$ref->getProperty('fallback')->setValue($sampler, $this->createMock(FallbackSampler::class));
65+
66+
// 4) Force fetch times into the past so updates run
67+
$now = (new Clock())->now();
68+
$ref->getProperty('nextRulesFetchTime')->setAccessible(true);
69+
$ref->getProperty('nextRulesFetchTime')->setValue($sampler, $now->sub(new DateInterval('PT1S')));
70+
$ref->getProperty('nextTargetFetchTime')->setAccessible(true);
71+
$ref->getProperty('nextTargetFetchTime')->setValue($sampler, $now->sub(new DateInterval('PT1S')));
72+
73+
// 5) Call shouldSample
74+
$result = $sampler->shouldSample(
75+
$this->createMock(ContextInterface::class),
76+
'traceId', 'spanName', 1,
77+
Attributes::create([]),
78+
[]
79+
);
80+
$this->assertSame($expectedResult, $result);
81+
}
82+
83+
public function testShouldSampleFallbackWhenExpired(): void
84+
{
85+
$resource = ResourceInfo::create(Attributes::create([]));
86+
$mockClient = $this->createMock(AWSXRaySamplerClient::class);
87+
88+
$mockRulesCache = $this->getMockBuilder(RulesCache::class)
89+
->disableOriginalConstructor()
90+
->getMock();
91+
// No updateRules call since expired
92+
$mockRulesCache->expects($this->never())
93+
->method('updateRules');
94+
$mockRulesCache->expects($this->once())
95+
->method('expired')
96+
->willReturn(true);
97+
98+
$fallback = $this->createMock(FallbackSampler::class);
99+
$expected = new SamplingResult(SamplingResult::DROP);
100+
$fallback->expects($this->once())
101+
->method('shouldSample')
102+
->willReturn($expected);
103+
104+
$sampler = new _AWSXRayRemoteSampler($resource, 'host', 0);
105+
$ref = new ReflectionClass($sampler);
106+
$ref->getProperty('client')->setAccessible(true);
107+
$ref->getProperty('client')->setValue($sampler, $mockClient);
108+
$ref->getProperty('rulesCache')->setAccessible(true);
109+
$ref->getProperty('rulesCache')->setValue($sampler, $mockRulesCache);
110+
$ref->getProperty('fallback')->setAccessible(true);
111+
$ref->getProperty('fallback')->setValue($sampler, $fallback);
112+
113+
$result = $sampler->shouldSample(
114+
$this->createMock(ContextInterface::class),
115+
't', 'n', 1,
116+
Attributes::create([]),
117+
[]
118+
);
119+
$this->assertSame($expected, $result);
120+
}
121+
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
use PHPUnit\Framework\TestCase;
5+
use OpenTelemetry\Contrib\Sampler\Xray\AWSXRaySamplerClient;
6+
use OpenTelemetry\Contrib\Sampler\Xray\SamplingRule;
7+
use GuzzleHttp\Client as HttpClient;
8+
use GuzzleHttp\Psr7\Response;
9+
10+
final class AWSXRaySamplerClientTest extends TestCase
11+
{
12+
public function testGetSamplingRulesParsesResponse(): void
13+
{
14+
$json = <<<'JSON'
15+
{
16+
"NextToken": null,
17+
"SamplingRuleRecords": [
18+
{
19+
"CreatedAt": 1.676038494E9,
20+
"ModifiedAt": 1.676038494E9,
21+
"SamplingRule": {
22+
"Attributes": {
23+
"foo": "bar",
24+
"abc": "1234"
25+
},
26+
"FixedRate": 0.05,
27+
"HTTPMethod": "*",
28+
"Host": "*",
29+
"Priority": 10000,
30+
"ReservoirSize": 100,
31+
"ResourceARN": "*",
32+
"RuleARN": "arn:aws:xray:us-east-1:999999999999:sampling-rule/Default",
33+
"RuleName": "Default",
34+
"ServiceName": "*",
35+
"ServiceType": "*",
36+
"URLPath": "*",
37+
"Version": 1
38+
}
39+
},
40+
{
41+
"CreatedAt": 1.67799933E9,
42+
"ModifiedAt": 1.67799933E9,
43+
"SamplingRule": {
44+
"Attributes": {
45+
"abc": "1234"
46+
},
47+
"FixedRate": 0.11,
48+
"HTTPMethod": "*",
49+
"Host": "*",
50+
"Priority": 20,
51+
"ReservoirSize": 1,
52+
"ResourceARN": "*",
53+
"RuleARN": "arn:aws:xray:us-east-1:999999999999:sampling-rule/test",
54+
"RuleName": "test",
55+
"ServiceName": "*",
56+
"ServiceType": "*",
57+
"URLPath": "*",
58+
"Version": 1
59+
}
60+
}
61+
]
62+
}
63+
JSON;
64+
65+
// Mock the Guzzle HTTP client
66+
$mockHttp = $this->createMock(HttpClient::class);
67+
$response = new Response(200, ['Content-Type' => 'application/json'], $json);
68+
$mockHttp->expects($this->once())
69+
->method('post')
70+
->with('GetSamplingRules')
71+
->willReturn($response);
72+
73+
// Instantiate and inject the mock client
74+
$client = new AWSXRaySamplerClient('https://dummy');
75+
$ref = new ReflectionClass($client);
76+
$prop = $ref->getProperty('httpClient');
77+
$prop->setAccessible(true);
78+
$prop->setValue($client, $mockHttp);
79+
80+
// Exercise getSamplingRules()
81+
$rules = $client->getSamplingRules();
82+
83+
// Assertions
84+
$this->assertCount(2, $rules);
85+
$this->assertInstanceOf(SamplingRule::class, $rules[0]);
86+
$this->assertEquals('Default', $rules[0]->RuleName);
87+
$this->assertEquals(10000, $rules[0]->Priority);
88+
$this->assertEquals(0.05, $rules[0]->FixedRate);
89+
$this->assertEquals(100, $rules[0]->ReservoirSize);
90+
$this->assertEquals(['foo' => 'bar', 'abc' => '1234'], $rules[0]->Attributes);
91+
92+
$this->assertEquals('test', $rules[1]->RuleName);
93+
$this->assertEquals(20, $rules[1]->Priority);
94+
$this->assertEquals(0.11, $rules[1]->FixedRate);
95+
$this->assertEquals(1, $rules[1]->ReservoirSize);
96+
$this->assertEquals(['abc' => '1234'], $rules[1]->Attributes);
97+
}
98+
99+
public function testGetSamplingTargetsParsesResponse(): void
100+
{
101+
$json = <<<'JSON'
102+
{
103+
"LastRuleModification": 1707551387.0,
104+
"SamplingTargetDocuments": [
105+
{
106+
"FixedRate": 0.10,
107+
"Interval": 10,
108+
"ReservoirQuota": 30,
109+
"ReservoirQuotaTTL": 1707764006.0,
110+
"RuleName": "test"
111+
},
112+
{
113+
"FixedRate": 0.05,
114+
"Interval": 10,
115+
"ReservoirQuota": 0,
116+
"ReservoirQuotaTTL": 1707764006.0,
117+
"RuleName": "Default"
118+
}
119+
],
120+
"UnprocessedStatistics": []
121+
}
122+
JSON;
123+
124+
$stats = [
125+
(object)[
126+
'ClientID' => 'cid',
127+
'RuleName' => 'test',
128+
'RequestCount' => 1,
129+
'SampleCount' => 1,
130+
'BorrowCount' => 0,
131+
'Timestamp' => 1234567890.0,
132+
]
133+
];
134+
135+
// Mock the Guzzle HTTP client
136+
$mockHttp = $this->createMock(HttpClient::class);
137+
$response = new Response(200, ['Content-Type' => 'application/json'], $json);
138+
$mockHttp->expects($this->once())
139+
->method('post')
140+
->with(
141+
'SamplingTargets',
142+
$this->callback(fn($opts) =>
143+
isset($opts['json']['SamplingStatisticsDocuments']) &&
144+
count($opts['json']['SamplingStatisticsDocuments']) === 1
145+
)
146+
)
147+
->willReturn($response);
148+
149+
// Instantiate and inject the mock client
150+
$client = new AWSXRaySamplerClient('https://dummy');
151+
$ref = new ReflectionClass($client);
152+
$prop = $ref->getProperty('httpClient');
153+
$prop->setAccessible(true);
154+
$prop->setValue($client, $mockHttp);
155+
156+
// Exercise getSamplingTargets()
157+
$result = $client->getSamplingTargets($stats);
158+
159+
// Assertions on the stdClass response
160+
$this->assertIsObject($result);
161+
$this->assertEquals(1707551387.0, $result->LastRuleModification);
162+
163+
$docs = $result->SamplingTargetDocuments;
164+
$this->assertCount(2, $docs);
165+
166+
$this->assertEquals('test', $docs[0]->RuleName);
167+
$this->assertEquals(0.10, $docs[0]->FixedRate);
168+
$this->assertEquals(30, $docs[0]->ReservoirQuota);
169+
$this->assertEquals(1707764006.0, $docs[0]->ReservoirQuotaTTL);
170+
$this->assertEquals(10, $docs[0]->Interval);
171+
172+
$this->assertEquals('Default',$docs[1]->RuleName);
173+
$this->assertEquals(0.05, $docs[1]->FixedRate);
174+
$this->assertEquals(0, $docs[1]->ReservoirQuota);
175+
}
176+
}

0 commit comments

Comments
 (0)