Skip to content

Commit ea137c0

Browse files
authored
chore: init test-utils (open-telemetry#343)
1 parent 3b32d09 commit ea137c0

22 files changed

+4079
-1
lines changed

.github/workflows/php.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ jobs:
5454
'Sampler/RuleBased',
5555
'Shims/OpenTracing',
5656
'Symfony',
57+
'Utils/Test'
5758
]
5859
exclude:
5960

.gitsplit.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ splits:
7878
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-sampler-rulebased.git"
7979
- prefix: "src/Shims/OpenTracing"
8080
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-shim-opentracing.git"
81+
- prefix: "src/Utils/Test"
82+
target: "https://${GH_TOKEN}@github.com/opentelemetry-php/contrib-utils-test.git"
8183
# List of references to split (defined as regexp)
8284
origins:
8385
- ^main$

composer.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@
5959
"src/ResourceDetectors/Container/_register.php"
6060
]
6161
},
62+
"autoload-dev": {
63+
"psr-4": {
64+
"OpenTelemetry\\TestUtils\\": "src/",
65+
"OpenTelemetry\\TestUtils\\Tests\\": "tests/"
66+
}
67+
},
6268
"replace": {
6369
"open-telemetry/contrib-aws": "self.version",
6470
"open-telemetry/contrib-sdk-bundle": "self.version",
@@ -81,7 +87,8 @@
8187
"open-telemetry/opentelemetry-logger-monolog": "self.version",
8288
"open-telemetry/detector-container": "self.version",
8389
"open-telemetry/symfony-sdk-bundle": "self.version",
84-
"open-telemetry/opentracing-shim": "self.version"
90+
"open-telemetry/opentracing-shim": "self.version",
91+
"open-telemetry/test-utils": "self.version"
8592
},
8693
"config": {
8794
"sort-packages": true,

src/Utils/Test/.gitattributes

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
* text=auto
2+
3+
*.md diff=markdown
4+
*.php diff=php
5+
6+
/.gitattributes export-ignore
7+
/.gitignore export-ignore
8+
/.php-cs-fixer.php export-ignore
9+
/phpstan.neon.dist export-ignore
10+
/phpunit.xml.dist export-ignore
11+
/psalm.xml.dist export-ignore
12+
/tests export-ignore

src/Utils/Test/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/vendor/

src/Utils/Test/.php-cs-fixer.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
$finder = PhpCsFixer\Finder::create()
3+
->exclude('vendor')
4+
->exclude('var/cache')
5+
->in(__DIR__);
6+
7+
$config = new PhpCsFixer\Config();
8+
return $config->setRules([
9+
'concat_space' => ['spacing' => 'one'],
10+
'declare_equal_normalize' => ['space' => 'none'],
11+
'is_null' => true,
12+
'modernize_types_casting' => true,
13+
'ordered_imports' => true,
14+
'php_unit_construct' => true,
15+
'single_line_comment_style' => true,
16+
'yoda_style' => false,
17+
'@PSR2' => true,
18+
'array_syntax' => ['syntax' => 'short'],
19+
'blank_line_after_opening_tag' => true,
20+
'blank_line_before_statement' => true,
21+
'cast_spaces' => true,
22+
'declare_strict_types' => true,
23+
'type_declaration_spaces' => true,
24+
'include' => true,
25+
'lowercase_cast' => true,
26+
'new_with_parentheses' => true,
27+
'no_extra_blank_lines' => true,
28+
'no_leading_import_slash' => true,
29+
'echo_tag_syntax' => true,
30+
'no_unused_imports' => true,
31+
'no_useless_else' => true,
32+
'no_useless_return' => true,
33+
'phpdoc_order' => true,
34+
'phpdoc_scalar' => true,
35+
'phpdoc_types' => true,
36+
'short_scalar_cast' => true,
37+
'blank_lines_before_namespace' => true,
38+
'single_quote' => true,
39+
'trailing_comma_in_multiline' => true,
40+
])
41+
->setRiskyAllowed(true)
42+
->setFinder($finder);
43+

src/Utils/Test/README.md

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
[![Releases](https://img.shields.io/badge/releases-purple)](https://github.com/opentelemetry-php/contrib-test-utils/releases)
2+
[![Issues](https://img.shields.io/badge/issues-pink)](https://github.com/open-telemetry/opentelemetry-php/issues)
3+
[![Source](https://img.shields.io/badge/source-contrib-green)](https://github.com/open-telemetry/opentelemetry-php-contrib/tree/main/src/Utils/Test)
4+
[![Mirror](https://img.shields.io/badge/mirror-opentelemetry--php--contrib-blue)](https://github.com/opentelemetry-php/contrib-test-utils)
5+
[![Latest Version](http://poser.pugx.org/open-telemetry/test-utils/v/unstable)](https://packagist.org/packages/open-telemetry/test-utils/)
6+
[![Stable](http://poser.pugx.org/open-telemetry/test-utils/v/stable)](https://packagist.org/packages/open-telemetry/test-utils/)
7+
8+
This is a read-only subtree split of https://github.com/open-telemetry/opentelemetry-php-contrib.
9+
10+
# OpenTelemetry Test Utilities
11+
12+
This package provides testing utilities for OpenTelemetry PHP instrumentations. It includes tools to help test and validate trace structures, span relationships, and other OpenTelemetry-specific functionality.
13+
14+
## Features
15+
16+
### TraceStructureAssertionTrait
17+
18+
The `TraceStructureAssertionTrait` provides methods to assess if spans match an expected trace structure. It's particularly useful for testing complex trace hierarchies and relationships between spans.
19+
20+
Key features:
21+
- Support for hierarchical span relationships
22+
- Verification of span names, kinds, attributes, events, and status
23+
- Flexible matching with strict and non-strict modes
24+
- Support for PHPUnit matchers/constraints for more flexible assertions
25+
- Detailed error messages for failed assertions
26+
- Two interfaces: array-based and fluent
27+
28+
## Requirements
29+
30+
* PHP 7.4 or higher
31+
* OpenTelemetry SDK and API (for testing)
32+
* PHPUnit 9.5 or higher
33+
34+
## Usage
35+
36+
### TraceStructureAssertionTrait
37+
38+
Add the trait to your test class:
39+
40+
```php
41+
use OpenTelemetry\TestUtils\TraceStructureAssertionTrait;
42+
use PHPUnit\Framework\TestCase;
43+
44+
class MyTest extends TestCase
45+
{
46+
use TraceStructureAssertionTrait;
47+
48+
// Your test methods...
49+
}
50+
```
51+
52+
#### Array-Based Interface
53+
54+
Use the `assertTraceStructure` method to verify trace structures using an array-based approach:
55+
56+
```php
57+
public function testTraceStructure(): void
58+
{
59+
// Create spans using the OpenTelemetry SDK
60+
// ...
61+
62+
// Define the expected structure
63+
$expectedStructure = [
64+
[
65+
'name' => 'root-span',
66+
'kind' => SpanKind::KIND_SERVER,
67+
'children' => [
68+
[
69+
'name' => 'child-span',
70+
'kind' => SpanKind::KIND_INTERNAL,
71+
'attributes' => [
72+
'attribute.one' => 'value1',
73+
'attribute.two' => 42,
74+
],
75+
'events' => [
76+
[
77+
'name' => 'event.processed',
78+
'attributes' => [
79+
'processed.id' => 'abc123',
80+
],
81+
],
82+
],
83+
],
84+
[
85+
'name' => 'another-child-span',
86+
'kind' => SpanKind::KIND_CLIENT,
87+
'status' => [
88+
'code' => StatusCode::STATUS_ERROR,
89+
'description' => 'Something went wrong',
90+
],
91+
],
92+
],
93+
],
94+
];
95+
96+
// Assert the trace structure
97+
$this->assertTraceStructure($spans, $expectedStructure);
98+
}
99+
```
100+
101+
The `assertTraceStructure` method takes the following parameters:
102+
- `$spans`: An array or ArrayObject of spans (typically from an InMemoryExporter)
103+
- `$expectedStructure`: An array defining the expected structure of the trace
104+
- `$strict` (optional): Whether to perform strict matching (all attributes must match)
105+
106+
#### Fluent Interface
107+
108+
Use the `assertTrace` method to verify trace structures using a fluent, chainable interface:
109+
110+
```php
111+
public function testTraceStructure(): void
112+
{
113+
// Create spans using the OpenTelemetry SDK
114+
// ...
115+
116+
// Assert the trace structure using the fluent interface
117+
$this->assertTrace($spans)
118+
->hasRootSpan('root-span')
119+
->withKind(SpanKind::KIND_SERVER)
120+
->hasChild('child-span')
121+
->withKind(SpanKind::KIND_INTERNAL)
122+
->withAttribute('attribute.one', 'value1')
123+
->withAttribute('attribute.two', 42)
124+
->hasEvent('event.processed')
125+
->withAttribute('processed.id', 'abc123')
126+
->end()
127+
->end()
128+
->hasChild('another-child-span')
129+
->withKind(SpanKind::KIND_CLIENT)
130+
->withStatus(StatusCode::STATUS_ERROR, 'Something went wrong')
131+
->end()
132+
->end();
133+
}
134+
```
135+
136+
The fluent interface provides the following methods:
137+
138+
**TraceAssertion:**
139+
- `hasRootSpan(string|Constraint $name)`: Assert that the trace has a root span with the given name
140+
- `hasRootSpans(int $count)`: Assert that the trace has the expected number of root spans
141+
- `inStrictMode()`: Enable strict mode for all assertions
142+
143+
**SpanAssertion:**
144+
- `withKind(int|Constraint $kind)`: Assert that the span has the expected kind
145+
- `withAttribute(string $key, mixed|Constraint $value)`: Assert that the span has an attribute with the expected key and value
146+
- `withAttributes(array $attributes)`: Assert that the span has the expected attributes
147+
- `withStatus(int|Constraint $code, string|Constraint|null $description = null)`: Assert that the span has the expected status
148+
- `hasEvent(string|Constraint $name)`: Assert that the span has an event with the expected name
149+
- `hasChild(string|Constraint $name)`: Assert that the span has a child span with the expected name
150+
- `hasChildren(int $count)`: Assert that the span has the expected number of children
151+
- `end()`: Return to the parent assertion
152+
153+
**SpanEventAssertion:**
154+
- `withAttribute(string $key, mixed|Constraint $value)`: Assert that the event has an attribute with the expected key and value
155+
- `withAttributes(array $attributes)`: Assert that the event has the expected attributes
156+
- `end()`: Return to the parent span assertion
157+
158+
### Using PHPUnit Matchers
159+
160+
You can use PHPUnit constraints/matchers for more flexible assertions with both interfaces:
161+
162+
#### Array-Based Interface with Matchers
163+
164+
```php
165+
use PHPUnit\Framework\Constraint\Callback;
166+
use PHPUnit\Framework\Constraint\IsIdentical;
167+
use PHPUnit\Framework\Constraint\IsType;
168+
use PHPUnit\Framework\Constraint\RegularExpression;
169+
use PHPUnit\Framework\Constraint\StringContains;
170+
171+
// Define the expected structure with matchers
172+
$expectedStructure = [
173+
[
174+
'name' => 'root-span',
175+
'kind' => new IsIdentical(SpanKind::KIND_SERVER),
176+
'attributes' => [
177+
'string.attribute' => new StringContains('World'),
178+
'numeric.attribute' => new Callback(function ($value) {
179+
return $value > 40 || $value === 42;
180+
}),
181+
'boolean.attribute' => new IsType('boolean'),
182+
'array.attribute' => new Callback(function ($value) {
183+
return is_array($value) && count($value) === 3 && in_array('b', $value);
184+
}),
185+
],
186+
'children' => [
187+
[
188+
'name' => new RegularExpression('/child-span-\d+/'),
189+
'kind' => SpanKind::KIND_INTERNAL,
190+
'attributes' => [
191+
'timestamp' => new IsType('integer'),
192+
],
193+
'events' => [
194+
[
195+
'name' => 'process.start',
196+
'attributes' => [
197+
'process.id' => new IsType('integer'),
198+
'process.name' => new StringContains('process'),
199+
],
200+
],
201+
],
202+
],
203+
],
204+
],
205+
];
206+
207+
// Assert the trace structure with matchers
208+
$this->assertTraceStructure($spans, $expectedStructure);
209+
```
210+
211+
#### Fluent Interface with Matchers
212+
213+
```php
214+
use PHPUnit\Framework\Constraint\Callback;
215+
use PHPUnit\Framework\Constraint\IsIdentical;
216+
use PHPUnit\Framework\Constraint\IsType;
217+
use PHPUnit\Framework\Constraint\RegularExpression;
218+
use PHPUnit\Framework\Constraint\StringContains;
219+
220+
// Assert the trace structure using the fluent interface with matchers
221+
$this->assertTrace($spans)
222+
->hasRootSpan('root-span')
223+
->withKind(new IsIdentical(SpanKind::KIND_SERVER))
224+
->withAttribute('string.attribute', new StringContains('World'))
225+
->withAttribute('numeric.attribute', new Callback(function ($value) {
226+
return $value > 40 || $value === 42;
227+
}))
228+
->withAttribute('boolean.attribute', new IsType('boolean'))
229+
->withAttribute('array.attribute', new Callback(function ($value) {
230+
return is_array($value) && count($value) === 3 && in_array('b', $value);
231+
}))
232+
->hasChild(new RegularExpression('/child-span-\d+/'))
233+
->withKind(SpanKind::KIND_INTERNAL)
234+
->withAttribute('timestamp', new IsType('integer'))
235+
->hasEvent('process.start')
236+
->withAttribute('process.id', new IsType('integer'))
237+
->withAttribute('process.name', new StringContains('process'))
238+
->end()
239+
->end()
240+
->end();
241+
```
242+
243+
Supported PHPUnit matchers include:
244+
- `StringContains` for partial string matching
245+
- `RegularExpression` for pattern matching
246+
- `IsIdentical` for strict equality
247+
- `IsEqual` for loose equality
248+
- `IsType` for type checking
249+
- `Callback` for custom validation logic
250+
251+
## Installation via composer
252+
253+
```bash
254+
$ composer require --dev open-telemetry/test-utils
255+
```
256+
257+
## Installing dependencies and executing tests
258+
259+
From the Test Utils subdirectory:
260+
261+
```bash
262+
$ composer install
263+
$ ./vendor/bin/phpunit tests
264+
```

src/Utils/Test/composer.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "open-telemetry/test-utils",
3+
"description": "Testing utilities for OpenTelemetry PHP instrumentations",
4+
"type": "library",
5+
"license": "Apache-2.0",
6+
"autoload": {
7+
"psr-4": {
8+
"OpenTelemetry\\TestUtils\\": "src/"
9+
}
10+
},
11+
"autoload-dev": {
12+
"psr-4": {
13+
"OpenTelemetry\\TestUtils\\Tests\\": "tests/"
14+
}
15+
},
16+
"minimum-stability": "dev",
17+
"require": {},
18+
"require-dev": {
19+
"friendsofphp/php-cs-fixer": "^3",
20+
"phan/phan": "^5.0",
21+
"phpstan/phpstan": "^1.1",
22+
"phpstan/phpstan-phpunit": "^1.0",
23+
"psalm/plugin-phpunit": "^0.19.2",
24+
"open-telemetry/api": "^1.0",
25+
"open-telemetry/sdk": "^1.0",
26+
"phpunit/phpunit": "^9.5",
27+
"vimeo/psalm": "^4|^5|^6"
28+
}
29+
}

0 commit comments

Comments
 (0)