@@ -451,7 +451,12 @@ private function findMatchingSpan(array $expectedSpan, array $actualSpans, bool
451451 // If we get here, the span and its children match
452452 return ;
453453 } catch (AssertionFailedError $ e ) {
454- // This span didn't match, try the next one
454+ // If the error is about an unexpected field in the status, rethrow it
455+ if (strpos ($ e ->getMessage (), 'Unexpected field ' ) !== false ) {
456+ throw $ e ;
457+ }
458+
459+ // Otherwise, this span didn't match, try the next one
455460 continue ;
456461 }
457462 }
@@ -500,6 +505,10 @@ private function compareSpans(array $expectedSpan, array $actualSpan, bool $stri
500505 sprintf ('Span kinds do not match for span "%s" ' , $ expectedSpan ['name ' ])
501506 );
502507 }
508+ } elseif ($ strict && isset ($ actualSpan ['kind ' ]) && $ actualSpan ['kind ' ] !== 0 ) {
509+ // In strict mode, if kind is not specified in expected span but exists in actual span (and is not default),
510+ // the test should fail
511+ Assert::fail (sprintf ('Actual span has kind %d but expected span does not specify kind for span "%s" ' , $ actualSpan ['kind ' ], $ expectedSpan ['name ' ]));
503512 }
504513
505514 // Compare attributes if specified
@@ -510,15 +519,25 @@ private function compareSpans(array $expectedSpan, array $actualSpan, bool $stri
510519 $ strict ,
511520 $ expectedSpan ['name ' ]
512521 );
522+ } elseif ($ strict && isset ($ actualSpan ['attributes ' ]) && !empty ($ actualSpan ['attributes ' ])) {
523+ // In strict mode, if attributes are not specified in expected span but exist in actual span,
524+ // the test should fail
525+ Assert::fail (sprintf ('Actual span has attributes but expected span does not specify attributes for span "%s" ' , $ expectedSpan ['name ' ]));
513526 }
514527
515528 // Compare status if specified
516529 if (isset ($ expectedSpan ['status ' ])) {
517530 $ this ->compareStatus (
518531 $ expectedSpan ['status ' ],
519532 $ actualSpan ['status ' ],
533+ $ strict ,
520534 $ expectedSpan ['name ' ]
521535 );
536+ } elseif ($ strict && isset ($ actualSpan ['status ' ]) &&
537+ ($ actualSpan ['status ' ]['code ' ] !== 0 || $ actualSpan ['status ' ]['description ' ] !== '' )) {
538+ // In strict mode, if status is not specified in expected span but exists in actual span (and is not default),
539+ // the test should fail
540+ Assert::fail (sprintf ('Actual span has non-default status but expected span does not specify status for span "%s" ' , $ expectedSpan ['name ' ]));
522541 }
523542
524543 // Compare events if specified
@@ -529,6 +548,15 @@ private function compareSpans(array $expectedSpan, array $actualSpan, bool $stri
529548 $ strict ,
530549 $ expectedSpan ['name ' ]
531550 );
551+ } elseif ($ strict && isset ($ actualSpan ['events ' ]) && !empty ($ actualSpan ['events ' ])) {
552+ // In strict mode, if events are not specified in expected span but exist in actual span,
553+ // the test should fail
554+ Assert::fail (sprintf ('Actual span has events but expected span does not specify events for span "%s" ' , $ expectedSpan ['name ' ]));
555+ }
556+
557+ // In strict mode, check for children if not specified in expected span
558+ if ($ strict && !isset ($ expectedSpan ['children ' ]) && isset ($ actualSpan ['children ' ]) && !empty ($ actualSpan ['children ' ])) {
559+ Assert::fail (sprintf ('Actual span has children but expected span does not specify children for span "%s" ' , $ expectedSpan ['name ' ]));
532560 }
533561 }
534562
@@ -583,18 +611,27 @@ private function isConstraint($value): bool
583611 */
584612 private function compareAttributes (array $ expectedAttributes , array $ actualAttributes , bool $ strict , string $ spanName ): void
585613 {
614+ // In strict mode, verify that the number of attributes matches exactly
615+ if ($ strict ) {
616+ Assert::assertCount (
617+ count ($ expectedAttributes ),
618+ $ actualAttributes ,
619+ sprintf (
620+ 'Expected %d attributes, but found %d in span "%s" ' ,
621+ count ($ expectedAttributes ),
622+ count ($ actualAttributes ),
623+ $ spanName
624+ )
625+ );
626+ }
627+
586628 foreach ($ expectedAttributes as $ key => $ expectedValue ) {
587- // In strict mode, all attributes must be present and match
588- if ($ strict ) {
589- Assert::assertArrayHasKey (
590- $ key ,
591- $ actualAttributes ,
592- sprintf ('Attribute "%s" not found in span "%s" ' , $ key , $ spanName )
593- );
594- } elseif (!isset ($ actualAttributes [$ key ])) {
595- // In non-strict mode, if the attribute is not present, skip it
596- continue ;
597- }
629+ // Both in strict and non-strict mode, all expected attributes must be present
630+ Assert::assertArrayHasKey (
631+ $ key ,
632+ $ actualAttributes ,
633+ sprintf ('Attribute "%s" not found in span "%s" ' , $ key , $ spanName )
634+ );
598635
599636 // Get the actual value
600637 $ actualValue = $ actualAttributes [$ key ];
@@ -621,18 +658,43 @@ private function compareAttributes(array $expectedAttributes, array $actualAttri
621658 /**
622659 * Compares the status of an expected span with the status of an actual span.
623660 *
624- * @param array $expectedStatus
625- * @param array $actualStatus
626- * @param string $spanName
661+ * @param mixed $expectedStatus The expected status (multiple formats supported)
662+ * @param array $actualStatus The actual status
663+ * @param bool $strict Whether to perform strict matching
664+ * @param string $spanName The name of the span being compared
627665 * @throws AssertionFailedError
628666 * @return void
629667 */
630- private function compareStatus (array $ expectedStatus , array $ actualStatus , string $ spanName ): void
668+ private function compareStatus ($ expectedStatus , array $ actualStatus, bool $ strict , string $ spanName ): void
631669 {
632- // Compare status code if specified
633- if (isset ($ expectedStatus ['code ' ])) {
634- $ expectedCode = $ expectedStatus ['code ' ];
670+ // Case 1: Constraint directly on status code
671+ if ($ this ->isConstraint ($ expectedStatus )) {
672+ Assert::assertThat (
673+ $ actualStatus ['code ' ],
674+ $ expectedStatus ,
675+ sprintf ('Status code does not match constraint for span "%s" ' , $ spanName )
676+ );
677+
678+ return ;
679+ }
680+
681+ // Case 2: Scalar value (direct status code comparison)
682+ if (is_scalar ($ expectedStatus )) {
683+ Assert::assertSame (
684+ $ expectedStatus ,
685+ $ actualStatus ['code ' ],
686+ sprintf ('Status code does not match for span "%s" ' , $ spanName )
687+ );
635688
689+ return ;
690+ }
691+
692+ // Case 3: Simple indexed array [code, description]
693+ if (is_array ($ expectedStatus ) && array_keys ($ expectedStatus ) === [0 , 1 ] && count ($ expectedStatus ) === 2 ) {
694+ $ expectedCode = $ expectedStatus [0 ];
695+ $ expectedDescription = $ expectedStatus [1 ];
696+
697+ // Compare code
636698 if ($ this ->isConstraint ($ expectedCode )) {
637699 Assert::assertThat (
638700 $ actualStatus ['code ' ],
@@ -646,12 +708,8 @@ private function compareStatus(array $expectedStatus, array $actualStatus, strin
646708 sprintf ('Status code does not match for span "%s" ' , $ spanName )
647709 );
648710 }
649- }
650-
651- // Compare status description if specified
652- if (isset ($ expectedStatus ['description ' ])) {
653- $ expectedDescription = $ expectedStatus ['description ' ];
654711
712+ // Compare description
655713 if ($ this ->isConstraint ($ expectedDescription )) {
656714 Assert::assertThat (
657715 $ actualStatus ['description ' ],
@@ -665,6 +723,69 @@ private function compareStatus(array $expectedStatus, array $actualStatus, strin
665723 sprintf ('Status description does not match for span "%s" ' , $ spanName )
666724 );
667725 }
726+
727+ return ;
728+ }
729+
730+ // Case 4: Traditional associative array with keys
731+ if (is_array ($ expectedStatus )) {
732+ // In strict mode, verify that the expected status doesn't have unexpected fields
733+ if ($ strict ) {
734+ // Check for unexpected fields in expected status
735+ foreach (array_keys ($ expectedStatus ) as $ key ) {
736+ if (!in_array ($ key , ['code ' , 'description ' ])) {
737+ Assert::fail (sprintf ('Unexpected field "%s" in expected status for span "%s" ' , $ key , $ spanName ));
738+ }
739+ }
740+
741+ // Check if code is specified in expected status
742+ if (!isset ($ expectedStatus ['code ' ]) && $ actualStatus ['code ' ] !== 0 ) {
743+ Assert::fail (sprintf ('Actual status has non-default code but expected status does not specify code for span "%s" ' , $ spanName ));
744+ }
745+
746+ // Check if description is specified in expected status
747+ if (!isset ($ expectedStatus ['description ' ]) && $ actualStatus ['description ' ] !== '' ) {
748+ Assert::fail (sprintf ('Actual status has description but expected status does not specify description for span "%s" ' , $ spanName ));
749+ }
750+ }
751+
752+ // Compare status code if specified
753+ if (isset ($ expectedStatus ['code ' ])) {
754+ $ expectedCode = $ expectedStatus ['code ' ];
755+
756+ if ($ this ->isConstraint ($ expectedCode )) {
757+ Assert::assertThat (
758+ $ actualStatus ['code ' ],
759+ $ expectedCode ,
760+ sprintf ('Status code does not match constraint for span "%s" ' , $ spanName )
761+ );
762+ } else {
763+ Assert::assertSame (
764+ $ expectedCode ,
765+ $ actualStatus ['code ' ],
766+ sprintf ('Status code does not match for span "%s" ' , $ spanName )
767+ );
768+ }
769+ }
770+
771+ // Compare status description if specified
772+ if (isset ($ expectedStatus ['description ' ])) {
773+ $ expectedDescription = $ expectedStatus ['description ' ];
774+
775+ if ($ this ->isConstraint ($ expectedDescription )) {
776+ Assert::assertThat (
777+ $ actualStatus ['description ' ],
778+ $ expectedDescription ,
779+ sprintf ('Status description does not match constraint for span "%s" ' , $ spanName )
780+ );
781+ } else {
782+ Assert::assertSame (
783+ $ expectedDescription ,
784+ $ actualStatus ['description ' ],
785+ sprintf ('Status description does not match for span "%s" ' , $ spanName )
786+ );
787+ }
788+ }
668789 }
669790 }
670791
0 commit comments