88use OpenTelemetry \SDK \Trace \ImmutableSpan ;
99use PHPUnit \Framework \Assert ;
1010use PHPUnit \Framework \AssertionFailedError ;
11+ use PHPUnit \Framework \Constraint \Constraint ;
1112use Traversable ;
1213
1314/**
@@ -225,20 +226,50 @@ private function findMatchingSpan(array $expectedSpan, array $actualSpans, bool
225226 throw new \InvalidArgumentException ('Expected span must have a name ' );
226227 }
227228
228- // Find a span with the matching name
229- $ matchingSpans = array_filter ($ actualSpans , function ($ actualSpan ) use ($ expectedName ) {
230- return $ actualSpan ['name ' ] === $ expectedName ;
231- });
229+ // Check if the expected name is a constraint
230+ if ($ this ->isConstraint ($ expectedName )) {
231+ // Find spans that match the constraint
232+ $ matchingSpans = [];
233+ foreach ($ actualSpans as $ actualSpan ) {
234+ try {
235+ Assert::assertThat (
236+ $ actualSpan ['name ' ],
237+ $ expectedName ,
238+ 'Span name does not match constraint '
239+ );
240+ $ matchingSpans [] = $ actualSpan ;
241+ } catch (AssertionFailedError $ e ) {
242+ // This span doesn't match the constraint, skip it
243+ continue ;
244+ }
245+ }
246+ } else {
247+ // Find spans with the exact matching name
248+ $ matchingSpans = array_filter ($ actualSpans , function ($ actualSpan ) use ($ expectedName ) {
249+ return $ actualSpan ['name ' ] === $ expectedName ;
250+ });
251+ }
232252
233253 Assert::assertNotEmpty (
234254 $ matchingSpans ,
235- sprintf ('No span with name "%s" found ' , $ expectedName )
255+ sprintf (
256+ 'No span matching name "%s" found ' ,
257+ $ this ->isConstraint ($ expectedName ) ? 'constraint ' : $ expectedName
258+ )
236259 );
237260
238- // If multiple spans have the same name , try to match based on other properties
261+ // If multiple spans match , try to match based on other properties
239262 foreach ($ matchingSpans as $ actualSpan ) {
240263 try {
241- $ this ->compareSpans ($ expectedSpan , $ actualSpan , $ strict );
264+ // For constraint-based names, we need to modify the expected span for comparison
265+ // since compareSpans expects exact name matching
266+ $ spanToCompare = $ expectedSpan ;
267+ if ($ this ->isConstraint ($ expectedName )) {
268+ $ spanToCompare = $ expectedSpan ;
269+ $ spanToCompare ['name ' ] = $ actualSpan ['name ' ];
270+ }
271+
272+ $ this ->compareSpans ($ spanToCompare , $ actualSpan , $ strict );
242273
243274 // If we get here, the spans match
244275 // Now check children if they exist
@@ -256,7 +287,10 @@ private function findMatchingSpan(array $expectedSpan, array $actualSpans, bool
256287
257288 // If we get here, none of the spans matched
258289 Assert::fail (
259- sprintf ('No matching span found for expected span "%s" ' , $ expectedName )
290+ sprintf (
291+ 'No matching span found for expected span "%s" ' ,
292+ $ this ->isConstraint ($ expectedName ) ? 'constraint ' : $ expectedName
293+ )
260294 );
261295 }
262296
@@ -280,11 +314,21 @@ private function compareSpans(array $expectedSpan, array $actualSpan, bool $stri
280314
281315 // Compare kind if specified
282316 if (isset ($ expectedSpan ['kind ' ])) {
283- Assert::assertSame (
284- $ expectedSpan ['kind ' ],
285- $ actualSpan ['kind ' ],
286- sprintf ('Span kinds do not match for span "%s" ' , $ expectedSpan ['name ' ])
287- );
317+ $ expectedKind = $ expectedSpan ['kind ' ];
318+
319+ if ($ this ->isConstraint ($ expectedKind )) {
320+ Assert::assertThat (
321+ $ actualSpan ['kind ' ],
322+ $ expectedKind ,
323+ sprintf ('Span kind does not match constraint for span "%s" ' , $ expectedSpan ['name ' ])
324+ );
325+ } else {
326+ Assert::assertSame (
327+ $ expectedKind ,
328+ $ actualSpan ['kind ' ],
329+ sprintf ('Span kinds do not match for span "%s" ' , $ expectedSpan ['name ' ])
330+ );
331+ }
288332 }
289333
290334 // Compare attributes if specified
@@ -345,6 +389,17 @@ private function compareChildren(array $expectedChildren, array $actualChildren,
345389 }
346390 }
347391
392+ /**
393+ * Checks if a value is a PHPUnit constraint object.
394+ *
395+ * @param mixed $value
396+ * @return bool
397+ */
398+ private function isConstraint ($ value ): bool
399+ {
400+ return $ value instanceof Constraint;
401+ }
402+
348403 /**
349404 * Compares the attributes of an expected span with the attributes of an actual span.
350405 *
@@ -370,12 +425,25 @@ private function compareAttributes(array $expectedAttributes, array $actualAttri
370425 continue ;
371426 }
372427
373- // Compare the attribute values
374- Assert::assertEquals (
375- $ expectedValue ,
376- $ actualAttributes [$ key ],
377- sprintf ('Attribute "%s" value does not match in span "%s" ' , $ key , $ spanName )
378- );
428+ // Get the actual value
429+ $ actualValue = $ actualAttributes [$ key ];
430+
431+ // Check if the expected value is a constraint
432+ if ($ this ->isConstraint ($ expectedValue )) {
433+ // Use assertThat for constraint evaluation
434+ Assert::assertThat (
435+ $ actualValue ,
436+ $ expectedValue ,
437+ sprintf ('Attribute "%s" value does not match constraint in span "%s" ' , $ key , $ spanName )
438+ );
439+ } else {
440+ // Use regular assertEquals for direct comparison
441+ Assert::assertEquals (
442+ $ expectedValue ,
443+ $ actualValue ,
444+ sprintf ('Attribute "%s" value does not match in span "%s" ' , $ key , $ spanName )
445+ );
446+ }
379447 }
380448 }
381449
@@ -392,20 +460,40 @@ private function compareStatus(array $expectedStatus, array $actualStatus, strin
392460 {
393461 // Compare status code if specified
394462 if (isset ($ expectedStatus ['code ' ])) {
395- Assert::assertSame (
396- $ expectedStatus ['code ' ],
397- $ actualStatus ['code ' ],
398- sprintf ('Status code does not match for span "%s" ' , $ spanName )
399- );
463+ $ expectedCode = $ expectedStatus ['code ' ];
464+
465+ if ($ this ->isConstraint ($ expectedCode )) {
466+ Assert::assertThat (
467+ $ actualStatus ['code ' ],
468+ $ expectedCode ,
469+ sprintf ('Status code does not match constraint for span "%s" ' , $ spanName )
470+ );
471+ } else {
472+ Assert::assertSame (
473+ $ expectedCode ,
474+ $ actualStatus ['code ' ],
475+ sprintf ('Status code does not match for span "%s" ' , $ spanName )
476+ );
477+ }
400478 }
401479
402480 // Compare status description if specified
403481 if (isset ($ expectedStatus ['description ' ])) {
404- Assert::assertSame (
405- $ expectedStatus ['description ' ],
406- $ actualStatus ['description ' ],
407- sprintf ('Status description does not match for span "%s" ' , $ spanName )
408- );
482+ $ expectedDescription = $ expectedStatus ['description ' ];
483+
484+ if ($ this ->isConstraint ($ expectedDescription )) {
485+ Assert::assertThat (
486+ $ actualStatus ['description ' ],
487+ $ expectedDescription ,
488+ sprintf ('Status description does not match constraint for span "%s" ' , $ spanName )
489+ );
490+ } else {
491+ Assert::assertSame (
492+ $ expectedDescription ,
493+ $ actualStatus ['description ' ],
494+ sprintf ('Status description does not match for span "%s" ' , $ spanName )
495+ );
496+ }
409497 }
410498 }
411499
@@ -471,17 +559,40 @@ private function findMatchingEvent(array $expectedEvent, array $actualEvents, bo
471559 throw new \InvalidArgumentException ('Expected event must have a name ' );
472560 }
473561
474- // Find an event with the matching name
475- $ matchingEvents = array_filter ($ actualEvents , function ($ actualEvent ) use ($ expectedName ) {
476- return $ actualEvent ['name ' ] === $ expectedName ;
477- });
562+ // Check if the expected name is a constraint
563+ if ($ this ->isConstraint ($ expectedName )) {
564+ // Find events that match the constraint
565+ $ matchingEvents = [];
566+ foreach ($ actualEvents as $ actualEvent ) {
567+ try {
568+ Assert::assertThat (
569+ $ actualEvent ['name ' ],
570+ $ expectedName ,
571+ 'Event name does not match constraint '
572+ );
573+ $ matchingEvents [] = $ actualEvent ;
574+ } catch (AssertionFailedError $ e ) {
575+ // This event doesn't match the constraint, skip it
576+ continue ;
577+ }
578+ }
579+ } else {
580+ // Find events with the exact matching name
581+ $ matchingEvents = array_filter ($ actualEvents , function ($ actualEvent ) use ($ expectedName ) {
582+ return $ actualEvent ['name ' ] === $ expectedName ;
583+ });
584+ }
478585
479586 Assert::assertNotEmpty (
480587 $ matchingEvents ,
481- sprintf ('No event with name "%s" found in span "%s" ' , $ expectedName , $ spanName )
588+ sprintf (
589+ 'No event matching name "%s" found in span "%s" ' ,
590+ $ this ->isConstraint ($ expectedName ) ? 'constraint ' : $ expectedName ,
591+ $ spanName
592+ )
482593 );
483594
484- // If multiple events have the same name , try to match based on attributes
595+ // If multiple events match , try to match based on attributes
485596 foreach ($ matchingEvents as $ actualEvent ) {
486597 try {
487598 // Compare attributes if specified
@@ -490,7 +601,11 @@ private function findMatchingEvent(array $expectedEvent, array $actualEvents, bo
490601 $ expectedEvent ['attributes ' ],
491602 $ actualEvent ['attributes ' ],
492603 $ strict ,
493- sprintf ('Event "%s" in span "%s" ' , $ expectedName , $ spanName )
604+ sprintf (
605+ 'Event "%s" in span "%s" ' ,
606+ $ this ->isConstraint ($ expectedName ) ? $ actualEvent ['name ' ] : $ expectedName ,
607+ $ spanName
608+ )
494609 );
495610 }
496611
@@ -504,7 +619,11 @@ private function findMatchingEvent(array $expectedEvent, array $actualEvents, bo
504619
505620 // If we get here, none of the events matched
506621 Assert::fail (
507- sprintf ('No matching event found for expected event "%s" in span "%s" ' , $ expectedName , $ spanName )
622+ sprintf (
623+ 'No matching event found for expected event "%s" in span "%s" ' ,
624+ $ this ->isConstraint ($ expectedName ) ? 'constraint ' : $ expectedName ,
625+ $ spanName
626+ )
508627 );
509628 }
510629}
0 commit comments