diff --git a/src/Utils/Test/src/TraceStructureAssertionTrait.php b/src/Utils/Test/src/TraceStructureAssertionTrait.php index d62fcf2e2..f0303aad6 100644 --- a/src/Utils/Test/src/TraceStructureAssertionTrait.php +++ b/src/Utils/Test/src/TraceStructureAssertionTrait.php @@ -451,7 +451,12 @@ private function findMatchingSpan(array $expectedSpan, array $actualSpans, bool // If we get here, the span and its children match return; } catch (AssertionFailedError $e) { - // This span didn't match, try the next one + // If the error is about an unexpected field in the status, rethrow it + if (strpos($e->getMessage(), 'Unexpected field') !== false) { + throw $e; + } + + // Otherwise, this span didn't match, try the next one continue; } } @@ -500,6 +505,10 @@ private function compareSpans(array $expectedSpan, array $actualSpan, bool $stri sprintf('Span kinds do not match for span "%s"', $expectedSpan['name']) ); } + } elseif ($strict && isset($actualSpan['kind']) && $actualSpan['kind'] !== 0) { + // In strict mode, if kind is not specified in expected span but exists in actual span (and is not default), + // the test should fail + Assert::fail(sprintf('Actual span has kind %d but expected span does not specify kind for span "%s"', $actualSpan['kind'], $expectedSpan['name'])); } // Compare attributes if specified @@ -510,6 +519,10 @@ private function compareSpans(array $expectedSpan, array $actualSpan, bool $stri $strict, $expectedSpan['name'] ); + } elseif ($strict && isset($actualSpan['attributes']) && !empty($actualSpan['attributes'])) { + // In strict mode, if attributes are not specified in expected span but exist in actual span, + // the test should fail + Assert::fail(sprintf('Actual span has attributes but expected span does not specify attributes for span "%s"', $expectedSpan['name'])); } // Compare status if specified @@ -517,8 +530,14 @@ private function compareSpans(array $expectedSpan, array $actualSpan, bool $stri $this->compareStatus( $expectedSpan['status'], $actualSpan['status'], + $strict, $expectedSpan['name'] ); + } elseif ($strict && isset($actualSpan['status']) && + ($actualSpan['status']['code'] !== 0 || $actualSpan['status']['description'] !== '')) { + // In strict mode, if status is not specified in expected span but exists in actual span (and is not default), + // the test should fail + Assert::fail(sprintf('Actual span has non-default status but expected span does not specify status for span "%s"', $expectedSpan['name'])); } // Compare events if specified @@ -529,6 +548,15 @@ private function compareSpans(array $expectedSpan, array $actualSpan, bool $stri $strict, $expectedSpan['name'] ); + } elseif ($strict && isset($actualSpan['events']) && !empty($actualSpan['events'])) { + // In strict mode, if events are not specified in expected span but exist in actual span, + // the test should fail + Assert::fail(sprintf('Actual span has events but expected span does not specify events for span "%s"', $expectedSpan['name'])); + } + + // In strict mode, check for children if not specified in expected span + if ($strict && !isset($expectedSpan['children']) && isset($actualSpan['children']) && !empty($actualSpan['children'])) { + Assert::fail(sprintf('Actual span has children but expected span does not specify children for span "%s"', $expectedSpan['name'])); } } @@ -583,18 +611,27 @@ private function isConstraint($value): bool */ private function compareAttributes(array $expectedAttributes, array $actualAttributes, bool $strict, string $spanName): void { + // In strict mode, verify that the number of attributes matches exactly + if ($strict) { + Assert::assertCount( + count($expectedAttributes), + $actualAttributes, + sprintf( + 'Expected %d attributes, but found %d in span "%s"', + count($expectedAttributes), + count($actualAttributes), + $spanName + ) + ); + } + foreach ($expectedAttributes as $key => $expectedValue) { - // In strict mode, all attributes must be present and match - if ($strict) { - Assert::assertArrayHasKey( - $key, - $actualAttributes, - sprintf('Attribute "%s" not found in span "%s"', $key, $spanName) - ); - } elseif (!isset($actualAttributes[$key])) { - // In non-strict mode, if the attribute is not present, skip it - continue; - } + // Both in strict and non-strict mode, all expected attributes must be present + Assert::assertArrayHasKey( + $key, + $actualAttributes, + sprintf('Attribute "%s" not found in span "%s"', $key, $spanName) + ); // Get the actual value $actualValue = $actualAttributes[$key]; @@ -621,18 +658,43 @@ private function compareAttributes(array $expectedAttributes, array $actualAttri /** * Compares the status of an expected span with the status of an actual span. * - * @param array $expectedStatus - * @param array $actualStatus - * @param string $spanName + * @param mixed $expectedStatus The expected status (multiple formats supported) + * @param array $actualStatus The actual status + * @param bool $strict Whether to perform strict matching + * @param string $spanName The name of the span being compared * @throws AssertionFailedError * @return void */ - private function compareStatus(array $expectedStatus, array $actualStatus, string $spanName): void + private function compareStatus($expectedStatus, array $actualStatus, bool $strict, string $spanName): void { - // Compare status code if specified - if (isset($expectedStatus['code'])) { - $expectedCode = $expectedStatus['code']; + // Case 1: Constraint directly on status code + if ($this->isConstraint($expectedStatus)) { + Assert::assertThat( + $actualStatus['code'], + $expectedStatus, + sprintf('Status code does not match constraint for span "%s"', $spanName) + ); + + return; + } + + // Case 2: Scalar value (direct status code comparison) + if (is_scalar($expectedStatus)) { + Assert::assertSame( + $expectedStatus, + $actualStatus['code'], + sprintf('Status code does not match for span "%s"', $spanName) + ); + return; + } + + // Case 3: Simple indexed array [code, description] + if (is_array($expectedStatus) && array_keys($expectedStatus) === [0, 1] && count($expectedStatus) === 2) { + $expectedCode = $expectedStatus[0]; + $expectedDescription = $expectedStatus[1]; + + // Compare code if ($this->isConstraint($expectedCode)) { Assert::assertThat( $actualStatus['code'], @@ -646,12 +708,8 @@ private function compareStatus(array $expectedStatus, array $actualStatus, strin sprintf('Status code does not match for span "%s"', $spanName) ); } - } - - // Compare status description if specified - if (isset($expectedStatus['description'])) { - $expectedDescription = $expectedStatus['description']; + // Compare description if ($this->isConstraint($expectedDescription)) { Assert::assertThat( $actualStatus['description'], @@ -665,6 +723,69 @@ private function compareStatus(array $expectedStatus, array $actualStatus, strin sprintf('Status description does not match for span "%s"', $spanName) ); } + + return; + } + + // Case 4: Traditional associative array with keys + if (is_array($expectedStatus)) { + // In strict mode, verify that the expected status doesn't have unexpected fields + if ($strict) { + // Check for unexpected fields in expected status + foreach (array_keys($expectedStatus) as $key) { + if (!in_array($key, ['code', 'description'])) { + Assert::fail(sprintf('Unexpected field "%s" in expected status for span "%s"', $key, $spanName)); + } + } + + // Check if code is specified in expected status + if (!isset($expectedStatus['code']) && $actualStatus['code'] !== 0) { + Assert::fail(sprintf('Actual status has non-default code but expected status does not specify code for span "%s"', $spanName)); + } + + // Check if description is specified in expected status + if (!isset($expectedStatus['description']) && $actualStatus['description'] !== '') { + Assert::fail(sprintf('Actual status has description but expected status does not specify description for span "%s"', $spanName)); + } + } + + // Compare status code if specified + if (isset($expectedStatus['code'])) { + $expectedCode = $expectedStatus['code']; + + if ($this->isConstraint($expectedCode)) { + Assert::assertThat( + $actualStatus['code'], + $expectedCode, + sprintf('Status code does not match constraint for span "%s"', $spanName) + ); + } else { + Assert::assertSame( + $expectedCode, + $actualStatus['code'], + sprintf('Status code does not match for span "%s"', $spanName) + ); + } + } + + // Compare status description if specified + if (isset($expectedStatus['description'])) { + $expectedDescription = $expectedStatus['description']; + + if ($this->isConstraint($expectedDescription)) { + Assert::assertThat( + $actualStatus['description'], + $expectedDescription, + sprintf('Status description does not match constraint for span "%s"', $spanName) + ); + } else { + Assert::assertSame( + $expectedDescription, + $actualStatus['description'], + sprintf('Status description does not match for span "%s"', $spanName) + ); + } + } } } diff --git a/src/Utils/Test/tests/Unit/TraceStructureAssertionTraitTest.php b/src/Utils/Test/tests/Unit/TraceStructureAssertionTraitTest.php index 22ffc16f7..948859912 100644 --- a/src/Utils/Test/tests/Unit/TraceStructureAssertionTraitTest.php +++ b/src/Utils/Test/tests/Unit/TraceStructureAssertionTraitTest.php @@ -188,15 +188,20 @@ public function test_assert_trace_structure_with_strict_matching(): void // Assert the trace structure with non-strict matching (should pass) $this->assertTraceStructure($this->storage, $expectedStructure, false); - // Define the expected structure with all attributes + // Define the expected structure with all attributes and fields $expectedStructureStrict = [ [ 'name' => 'test-span', + 'kind' => 0, // Default kind 'attributes' => [ 'attribute.one' => 'value1', 'attribute.two' => 42, 'attribute.three' => true, ], + 'status' => [ + 'code' => StatusCode::STATUS_UNSET, // Default status code + 'description' => '', // Correct field name + ], ], ]; @@ -204,6 +209,71 @@ public function test_assert_trace_structure_with_strict_matching(): void $this->assertTraceStructure($this->storage, $expectedStructureStrict, true); } + /** + * Test that strict mode fails when status has an unexpected field. + */ + public function test_assert_fails_with_unexpected_status_field_in_strict_mode(): void + { + $tracer = $this->tracerProvider->getTracer('test-tracer'); + + // Create a span with default status + $span = $tracer->spanBuilder('test-span') + ->startSpan(); + $span->end(); + + // Define expected structure with a typo in the status field name + $expectedStructure = [ + [ + 'name' => 'test-span', + 'status' => [ + 'code' => StatusCode::STATUS_UNSET, + 'descriptions' => '', // Typo: should be 'description' + ], + ], + ]; + + // Expect assertion to fail in strict mode due to unexpected field + $this->expectException(\PHPUnit\Framework\AssertionFailedError::class); + $this->expectExceptionMessage('Unexpected field "descriptions" in expected status'); + + $this->assertTraceStructure($this->storage, $expectedStructure, true); + } + + /** + * Test that assertTraceStructure fails when expected attributes don't exist in the actual span, + * even in non-strict mode. + */ + public function test_assert_fails_with_nonexistent_attributes_in_nonstrict_mode(): void + { + $tracer = $this->tracerProvider->getTracer('test-tracer'); + + // Create a span with specific attributes + $span = $tracer->spanBuilder('test-span') + ->startSpan(); + + $span->setAttribute('attribute.one', 'value1'); + $span->setAttribute('attribute.two', 42); + + $span->end(); + + // Define expected structure with an attribute that doesn't exist in the actual span + $expectedStructure = [ + [ + 'name' => 'test-span', + 'attributes' => [ + 'attribute.one' => 'value1', + 'nonexistent.attribute' => 'this-does-not-exist', // This attribute doesn't exist + ], + ], + ]; + + // Expect assertion to fail even in non-strict mode + $this->expectException(\PHPUnit\Framework\AssertionFailedError::class); + $this->expectExceptionMessage('No matching span found for expected span "test-span"'); + + $this->assertTraceStructure($this->storage, $expectedStructure, false); + } + /** * Test asserting a trace structure with multiple root spans. */ @@ -647,6 +717,266 @@ public function test_trace_structure_diff_output_with_missing_nested_span(): voi } } + /** + * Test that strict mode fails when actual span has extra attributes. + */ + public function test_assert_fails_with_extra_attributes_in_strict_mode(): void + { + $tracer = $this->tracerProvider->getTracer('test-tracer'); + + // Create a span with multiple attributes + $span = $tracer->spanBuilder('test-span') + ->startSpan(); + + $span->setAttribute('attribute.one', 'value1'); + $span->setAttribute('attribute.two', 42); + $span->setAttribute('attribute.three', true); + + $span->end(); + + // Define expected structure with only a subset of attributes + $expectedStructure = [ + [ + 'name' => 'test-span', + 'attributes' => [ + 'attribute.one' => 'value1', + 'attribute.two' => 42, + ], + ], + ]; + + // Expect assertion to fail in strict mode + $this->expectException(\PHPUnit\Framework\AssertionFailedError::class); + $this->expectExceptionMessageMatches('/No matching span found for expected span "test-span"/'); + + $this->assertTraceStructure($this->storage, $expectedStructure, true); + } + + /** + * Test that strict mode fails when actual span has a kind but expected doesn't. + */ + public function test_assert_fails_with_extra_kind_in_strict_mode(): void + { + $tracer = $this->tracerProvider->getTracer('test-tracer'); + + // Create a span with a specific kind + $span = $tracer->spanBuilder('test-span') + ->setSpanKind(SpanKind::KIND_SERVER) + ->startSpan(); + + $span->end(); + + // Define expected structure without kind + $expectedStructure = [ + [ + 'name' => 'test-span', + ], + ]; + + // Expect assertion to fail in strict mode + $this->expectException(\PHPUnit\Framework\AssertionFailedError::class); + $this->expectExceptionMessageMatches('/No matching span found for expected span "test-span"/'); + + $this->assertTraceStructure($this->storage, $expectedStructure, true); + } + + /** + * Test that strict mode fails when actual span has events but expected doesn't. + */ + public function test_assert_fails_with_extra_events_in_strict_mode(): void + { + $tracer = $this->tracerProvider->getTracer('test-tracer'); + + // Create a span with events + $span = $tracer->spanBuilder('test-span') + ->startSpan(); + + $span->addEvent('event-1'); + $span->addEvent('event-2'); + + $span->end(); + + // Define expected structure without events + $expectedStructure = [ + [ + 'name' => 'test-span', + ], + ]; + + // Expect assertion to fail in strict mode + $this->expectException(\PHPUnit\Framework\AssertionFailedError::class); + $this->expectExceptionMessageMatches('/No matching span found for expected span "test-span"/'); + + $this->assertTraceStructure($this->storage, $expectedStructure, true); + } + + /** + * Test that strict mode fails when actual span has a non-default status but expected doesn't. + */ + public function test_assert_fails_with_extra_status_in_strict_mode(): void + { + $tracer = $this->tracerProvider->getTracer('test-tracer'); + + // Create a span with a non-default status + $span = $tracer->spanBuilder('test-span') + ->startSpan(); + + $span->setStatus(StatusCode::STATUS_ERROR, 'Something went wrong'); + + $span->end(); + + // Define expected structure without status + $expectedStructure = [ + [ + 'name' => 'test-span', + ], + ]; + + // Expect assertion to fail in strict mode + $this->expectException(\PHPUnit\Framework\AssertionFailedError::class); + $this->expectExceptionMessageMatches('/No matching span found for expected span "test-span"/'); + + $this->assertTraceStructure($this->storage, $expectedStructure, true); + } + + /** + * Test that strict mode fails when actual status has a non-default code but expected status doesn't specify a code. + */ + public function test_assert_fails_when_actual_status_has_code_but_expected_doesnt_in_strict_mode(): void + { + $tracer = $this->tracerProvider->getTracer('test-tracer'); + + // Create a span with a non-default status code + $span = $tracer->spanBuilder('test-span') + ->startSpan(); + + $span->setStatus(StatusCode::STATUS_ERROR, ''); + + $span->end(); + + // Define expected structure with status but without code + $expectedStructure = [ + [ + 'name' => 'test-span', + 'status' => [ + 'description' => '', + ], + ], + ]; + + // Expect assertion to fail in strict mode + $this->expectException(\PHPUnit\Framework\AssertionFailedError::class); + $this->expectExceptionMessageMatches('/No matching span found for expected span "test-span"/'); + + $this->assertTraceStructure($this->storage, $expectedStructure, true); + } + + /** + * Test that strict mode fails when actual status has a description but expected status doesn't specify a description. + */ + public function test_assert_fails_when_actual_status_has_description_but_expected_doesnt_in_strict_mode(): void + { + $tracer = $this->tracerProvider->getTracer('test-tracer'); + + // Create a span with a status description + $span = $tracer->spanBuilder('test-span') + ->startSpan(); + + $span->setStatus(StatusCode::STATUS_ERROR, 'Something went wrong'); + + $span->end(); + + // Define expected structure with status but without description + $expectedStructure = [ + [ + 'name' => 'test-span', + 'status' => [ + 'code' => StatusCode::STATUS_ERROR, + ], + ], + ]; + + // Expect assertion to fail in strict mode + $this->expectException(\PHPUnit\Framework\AssertionFailedError::class); + $this->expectExceptionMessageMatches('/No matching span found for expected span "test-span"/'); + + $this->assertTraceStructure($this->storage, $expectedStructure, true); + } + + /** + * Test that status code constraint checking fails with the correct error message. + */ + public function test_assert_fails_with_status_code_constraint_error_message(): void + { + $tracer = $this->tracerProvider->getTracer('test-tracer'); + + // Create a span with error status + $span = $tracer->spanBuilder('test-span') + ->startSpan(); + + $span->setStatus(StatusCode::STATUS_ERROR, 'Something went wrong'); + + $span->end(); + + // Create a constraint that will fail (expecting OK status) + $constraint = new \PHPUnit\Framework\Constraint\IsIdentical(StatusCode::STATUS_OK); + + // Define expected structure with a constraint that will fail + $expectedStructure = [ + [ + 'name' => 'test-span', + 'status' => $constraint, + ], + ]; + + try { + $this->assertTraceStructure($this->storage, $expectedStructure); + $this->fail('Expected assertion to fail but it passed'); + } catch (\PHPUnit\Framework\AssertionFailedError $e) { + // Verify that the error message contains the expected text + $errorMessage = $e->getMessage(); + $this->assertStringContainsString('No matching span found for expected span "test-span"', $errorMessage); + } + } + + /** + * Test that strict mode fails when actual span has children but expected doesn't. + */ + public function test_assert_fails_with_extra_children_in_strict_mode(): void + { + $tracer = $this->tracerProvider->getTracer('test-tracer'); + + // Create a root span + $rootSpan = $tracer->spanBuilder('root-span') + ->startSpan(); + + // Activate the root span + $rootScope = $rootSpan->activate(); + + try { + // Create a child span + $childSpan = $tracer->spanBuilder('child-span') + ->startSpan(); + $childSpan->end(); + } finally { + $rootSpan->end(); + $rootScope->detach(); + } + + // Define expected structure without children + $expectedStructure = [ + [ + 'name' => 'root-span', + ], + ]; + + // Expect assertion to fail in strict mode + $this->expectException(\PHPUnit\Framework\AssertionFailedError::class); + $this->expectExceptionMessageMatches('/No matching span found for expected span "root-span"/'); + + $this->assertTraceStructure($this->storage, $expectedStructure, true); + } + /** * Test asserting a trace structure using PHPUnit matchers. */ @@ -768,4 +1098,212 @@ public function test_assert_trace_structure_with_phpunit_matchers(): void // Assert the trace structure with matchers $this->assertTraceStructure($storage, $expectedStructure); } + + /** + * Test asserting a trace structure with status as a constraint. + */ + public function test_assert_trace_structure_with_status_as_constraint(): void + { + // Create a new test setup + $storage = new ArrayObject(); + $tracerProvider = new TracerProvider( + new SimpleSpanProcessor( + new InMemoryExporter($storage) + ) + ); + + $tracer = $tracerProvider->getTracer('test-tracer'); + + // Create a span with default status (UNSET) + $span = $tracer->spanBuilder('default-status-span') + ->startSpan(); + $span->end(); + + // Test Format 1: Constraint directly on status code + $expectedStructure = [ + [ + 'name' => 'default-status-span', + 'status' => new IsIdentical(StatusCode::STATUS_UNSET), + ], + ]; + $this->assertTraceStructure($storage, $expectedStructure); + } + + /** + * Test asserting a trace structure with status as a scalar value. + */ + public function test_assert_trace_structure_with_status_as_scalar(): void + { + // Create a new test setup + $storage = new ArrayObject(); + $tracerProvider = new TracerProvider( + new SimpleSpanProcessor( + new InMemoryExporter($storage) + ) + ); + + $tracer = $tracerProvider->getTracer('test-tracer'); + + // Create a span with error status + $span = $tracer->spanBuilder('error-status-span') + ->startSpan(); + $span->setStatus(StatusCode::STATUS_ERROR, 'Something went wrong'); + $span->end(); + + // Test Format 2: Scalar value (direct status code comparison) + $expectedStructure = [ + [ + 'name' => 'error-status-span', + 'status' => StatusCode::STATUS_ERROR, + ], + ]; + $this->assertTraceStructure($storage, $expectedStructure); + } + + /** + * Test asserting a trace structure with status as an indexed array. + */ + public function test_assert_trace_structure_with_status_as_indexed_array(): void + { + // Create a new test setup + $storage = new ArrayObject(); + $tracerProvider = new TracerProvider( + new SimpleSpanProcessor( + new InMemoryExporter($storage) + ) + ); + + $tracer = $tracerProvider->getTracer('test-tracer'); + + // Create a span with error status + $span = $tracer->spanBuilder('error-status-span') + ->startSpan(); + $span->setStatus(StatusCode::STATUS_ERROR, 'Something went wrong'); + $span->end(); + + // Test Format 3: Simple indexed array [code, description] + $expectedStructure = [ + [ + 'name' => 'error-status-span', + 'status' => [StatusCode::STATUS_ERROR, 'Something went wrong'], + ], + ]; + $this->assertTraceStructure($storage, $expectedStructure); + } + + /** + * Test asserting a trace structure with status as an indexed array with constraint. + */ + public function test_assert_trace_structure_with_status_as_indexed_array_with_constraint(): void + { + // Create a new test setup + $storage = new ArrayObject(); + $tracerProvider = new TracerProvider( + new SimpleSpanProcessor( + new InMemoryExporter($storage) + ) + ); + + $tracer = $tracerProvider->getTracer('test-tracer'); + + // Create a span with error status + $span = $tracer->spanBuilder('error-status-span') + ->startSpan(); + $span->setStatus(StatusCode::STATUS_ERROR, 'Something went wrong'); + $span->end(); + + // Test Format 3 with constraint for description + $expectedStructure = [ + [ + 'name' => 'error-status-span', + 'status' => [StatusCode::STATUS_ERROR, new StringContains('went wrong')], + ], + ]; + $this->assertTraceStructure($storage, $expectedStructure); + } + + /** + * Test asserting a trace structure with status as an associative array. + */ + public function test_assert_trace_structure_with_status_as_associative_array(): void + { + // Create a new test setup + $storage = new ArrayObject(); + $tracerProvider = new TracerProvider( + new SimpleSpanProcessor( + new InMemoryExporter($storage) + ) + ); + + $tracer = $tracerProvider->getTracer('test-tracer'); + + // Create a span with OK status + $span = $tracer->spanBuilder('ok-status-span') + ->startSpan(); + $span->setStatus(StatusCode::STATUS_OK, ''); + $span->end(); + + // Test Format 4: Traditional associative array with keys + $expectedStructure = [ + [ + 'name' => 'ok-status-span', + 'status' => [ + 'code' => StatusCode::STATUS_OK, + 'description' => '', + ], + ], + ]; + $this->assertTraceStructure($storage, $expectedStructure); + } + + /** + * Test asserting a trace structure with multiple spans and different status formats. + */ + public function test_assert_trace_structure_with_multiple_spans_and_status_formats(): void + { + // Create a new test setup + $storage = new ArrayObject(); + $tracerProvider = new TracerProvider( + new SimpleSpanProcessor( + new InMemoryExporter($storage) + ) + ); + + $tracer = $tracerProvider->getTracer('test-tracer'); + + // Create spans with different status codes + // Span 1: Default status (UNSET) + $defaultSpan = $tracer->spanBuilder('default-status-span') + ->startSpan(); + $defaultSpan->end(); + + // Span 2: Error status with description + $errorSpan = $tracer->spanBuilder('error-status-span') + ->startSpan(); + $errorSpan->setStatus(StatusCode::STATUS_ERROR, 'Something went wrong'); + $errorSpan->end(); + + // Span 3: OK status + $okSpan = $tracer->spanBuilder('ok-status-span') + ->startSpan(); + $okSpan->setStatus(StatusCode::STATUS_OK, ''); + $okSpan->end(); + + // Test multiple spans with different status formats in one assertion + $expectedStructure = [ + [ + 'name' => 'default-status-span', + 'status' => new IsIdentical(StatusCode::STATUS_UNSET), + ], + [ + 'name' => 'error-status-span', + 'status' => [StatusCode::STATUS_ERROR, new StringContains('went wrong')], + ], + [ + 'name' => 'ok-status-span', + 'status' => StatusCode::STATUS_OK, + ], + ]; + $this->assertTraceStructure($storage, $expectedStructure); + } }