Skip to content

Commit 47dbed6

Browse files
authored
fix: matching will be strict (#348)
* fix: matching will be strict * chore: in strict mode, match deeply * fix: compare status more flexibly * chore: coverage
1 parent d6f5e3e commit 47dbed6

File tree

2 files changed

+684
-25
lines changed

2 files changed

+684
-25
lines changed

src/Utils/Test/src/TraceStructureAssertionTrait.php

Lines changed: 145 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)