diff --git a/composer.lock b/composer.lock index 07d1756284..a9ddabebf0 100644 --- a/composer.lock +++ b/composer.lock @@ -577,16 +577,16 @@ }, { "name": "firebase/php-jwt", - "version": "v7.0.5", + "version": "v7.1.0", "source": { "type": "git", "url": "https://github.com/googleapis/php-jwt.git", - "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380" + "reference": "b374a5d1a4f1f67fadc2165cdb284645945e2fc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/47ad26bab5e7c70ae8a6f08ed25ff83631121380", - "reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380", + "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/b374a5d1a4f1f67fadc2165cdb284645945e2fc0", + "reference": "b374a5d1a4f1f67fadc2165cdb284645945e2fc0", "shasum": "" }, "require": { @@ -595,6 +595,7 @@ "require-dev": { "guzzlehttp/guzzle": "^7.4", "phpfastcache/phpfastcache": "^9.2", + "phpseclib/phpseclib": "~3.0", "phpspec/prophecy-phpunit": "^2.0", "phpunit/phpunit": "^9.5", "psr/cache": "^2.0||^3.0", @@ -603,7 +604,8 @@ }, "suggest": { "ext-sodium": "Support EdDSA (Ed25519) signatures", - "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present", + "phpseclib/phpseclib": "Support PS256 (RSASSA-PSS) signatures" }, "type": "library", "autoload": { @@ -628,16 +630,16 @@ } ], "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", - "homepage": "https://github.com/firebase/php-jwt", + "homepage": "https://github.com/googleapis/php-jwt", "keywords": [ "jwt", "php" ], "support": { "issues": "https://github.com/googleapis/php-jwt/issues", - "source": "https://github.com/googleapis/php-jwt/tree/v7.0.5" + "source": "https://github.com/googleapis/php-jwt/tree/v7.1.0" }, - "time": "2026-04-01T20:38:03+00:00" + "time": "2026-06-11T17:54:14+00:00" }, { "name": "google/apiclient", @@ -712,16 +714,16 @@ }, { "name": "google/apiclient-services", - "version": "v0.443.0", + "version": "v0.445.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client-services.git", - "reference": "102aba0523d5105adaffdcac6c0c74a527f2bbfc" + "reference": "d76b09227d898db351457010c88f39eedfb815aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/102aba0523d5105adaffdcac6c0c74a527f2bbfc", - "reference": "102aba0523d5105adaffdcac6c0c74a527f2bbfc", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/d76b09227d898db351457010c88f39eedfb815aa", + "reference": "d76b09227d898db351457010c88f39eedfb815aa", "shasum": "" }, "require": { @@ -750,22 +752,22 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client-services/issues", - "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.443.0" + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.445.0" }, - "time": "2026-05-29T01:56:23+00:00" + "time": "2026-06-15T01:58:30+00:00" }, { "name": "google/auth", - "version": "v1.50.2", + "version": "v1.51.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-auth-library-php.git", - "reference": "58788f86d47e59de692c0dae0bb0c006bd0b6018" + "reference": "4c4776e398ff255e81b3b8c4373983f5e1b765bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/58788f86d47e59de692c0dae0bb0c006bd0b6018", - "reference": "58788f86d47e59de692c0dae0bb0c006bd0b6018", + "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/4c4776e398ff255e81b3b8c4373983f5e1b765bf", + "reference": "4c4776e398ff255e81b3b8c4373983f5e1b765bf", "shasum": "" }, "require": { @@ -812,9 +814,9 @@ "support": { "docs": "https://cloud.google.com/php/docs/reference/auth/latest", "issues": "https://github.com/googleapis/google-auth-library-php/issues", - "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.50.2" + "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.51.0" }, - "time": "2026-05-29T19:22:31+00:00" + "time": "2026-06-10T00:39:33+00:00" }, { "name": "google/protobuf", @@ -862,22 +864,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.11.1", + "version": "7.12.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "5af96f374e0ab4ebd747b8310888c99d3adb0a8c" + "reference": "eaa81598031cf57a9e36258c8546defffc994cba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/5af96f374e0ab4ebd747b8310888c99d3adb0a8c", - "reference": "5af96f374e0ab4ebd747b8310888c99d3adb0a8c", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/eaa81598031cf57a9e36258c8546defffc994cba", + "reference": "eaa81598031cf57a9e36258c8546defffc994cba", "shasum": "" }, "require": { "ext-json": "*", "guzzlehttp/promises": "^2.5", - "guzzlehttp/psr7": "^2.11", + "guzzlehttp/psr7": "^2.12", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.5 || ^3.0", @@ -970,7 +972,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.11.1" + "source": "https://github.com/guzzle/guzzle/tree/7.12.0" }, "funding": [ { @@ -986,7 +988,7 @@ "type": "tidelift" } ], - "time": "2026-06-07T22:54:06+00:00" + "time": "2026-06-16T22:11:48+00:00" }, { "name": "guzzlehttp/promises", @@ -1074,16 +1076,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.11.0", + "version": "2.12.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "bbb5e61349fa5cb822b3e87842b951088b76b81f" + "reference": "9b38012e7b54f594707e6db52c684dc0a74b3a43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/bbb5e61349fa5cb822b3e87842b951088b76b81f", - "reference": "bbb5e61349fa5cb822b3e87842b951088b76b81f", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/9b38012e7b54f594707e6db52c684dc0a74b3a43", + "reference": "9b38012e7b54f594707e6db52c684dc0a74b3a43", "shasum": "" }, "require": { @@ -1173,7 +1175,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.11.0" + "source": "https://github.com/guzzle/psr7/tree/2.12.0" }, "funding": [ { @@ -1189,7 +1191,7 @@ "type": "tidelift" } ], - "time": "2026-06-02T12:30:48+00:00" + "time": "2026-06-16T21:50:11+00:00" }, { "name": "halaxa/json-machine", @@ -3525,16 +3527,16 @@ }, { "name": "symfony/polyfill-php83", - "version": "v1.38.1", + "version": "v1.38.2", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "8339098cae28673c15cce00d80734af0453054e2" + "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/8339098cae28673c15cce00d80734af0453054e2", - "reference": "8339098cae28673c15cce00d80734af0453054e2", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/796a26abb75ce49f3a84433cd81bf1009d73d5f8", + "reference": "796a26abb75ce49f3a84433cd81bf1009d73d5f8", "shasum": "" }, "require": { @@ -3581,7 +3583,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.2" }, "funding": [ { @@ -3601,7 +3603,7 @@ "type": "tidelift" } ], - "time": "2026-05-26T12:51:13+00:00" + "time": "2026-05-27T06:51:48+00:00" }, { "name": "symfony/polyfill-php85", diff --git a/src/adapter/etl-adapter-seal/src/Flow/ETL/Adapter/Seal/RowsNormalizer/EntryNormalizer.php b/src/adapter/etl-adapter-seal/src/Flow/ETL/Adapter/Seal/RowsNormalizer/EntryNormalizer.php index edec5707f9..0a7e538acc 100644 --- a/src/adapter/etl-adapter-seal/src/Flow/ETL/Adapter/Seal/RowsNormalizer/EntryNormalizer.php +++ b/src/adapter/etl-adapter-seal/src/Flow/ETL/Adapter/Seal/RowsNormalizer/EntryNormalizer.php @@ -17,6 +17,7 @@ use Flow\ETL\Row\Entry\XMLElementEntry; use Flow\ETL\Row\Entry\XMLEntry; +use function array_keys; use function is_array; use function is_bool; use function is_float; @@ -60,8 +61,8 @@ private function normalizeArray(mixed $value): ?array $normalized = []; - foreach ($value as $key => $nested) { - $normalized[$key] = $this->normalizeValue($nested); + foreach (array_keys($value) as $key) { + $normalized[$key] = $this->normalizeValue($value[$key]); } return $normalized; diff --git a/src/bridge/symfony/telemetry-bundle/src/Flow/Bridge/Symfony/TelemetryBundle/Instrumentation/Twig/TracingTwigExtension.php b/src/bridge/symfony/telemetry-bundle/src/Flow/Bridge/Symfony/TelemetryBundle/Instrumentation/Twig/TracingTwigExtension.php index 73ea2ef0c8..cdb908a6db 100644 --- a/src/bridge/symfony/telemetry-bundle/src/Flow/Bridge/Symfony/TelemetryBundle/Instrumentation/Twig/TracingTwigExtension.php +++ b/src/bridge/symfony/telemetry-bundle/src/Flow/Bridge/Symfony/TelemetryBundle/Instrumentation/Twig/TracingTwigExtension.php @@ -8,6 +8,7 @@ use Flow\Telemetry\Telemetry; use Flow\Telemetry\Tracer\Span; use Flow\Telemetry\Tracer\SpanKind; +use Flow\Telemetry\Tracer\SpanStatus; use Flow\Telemetry\Tracer\Tracer; use Override; use SplObjectStorage; @@ -94,12 +95,29 @@ public function leave(Profile $profile): void // @mago-expect analysis:no-value(2),redundant-type-comparison(2),redundant-logical-operation(2) if (is_array($spanData) && $spanData['tracer'] instanceof Tracer && $spanData['span'] instanceof Span) { + $spanData['span']->setStatus(SpanStatus::ok()); $spanData['tracer']->complete($spanData['span']); } unset($this->activeSpans[$profile]); } + public function reset(): void + { + foreach ($this->activeSpans as $profile) { + // @mago-expect analysis:mixed-assignment + $spanData = $this->activeSpans[$profile]; + + if (is_array($spanData) && $spanData['tracer'] instanceof Tracer && $spanData['span'] instanceof Span) { + $spanData['span']->setStatus(SpanStatus::error('Twig rendering did not complete')); + $spanData['tracer']->complete($spanData['span']); + } + } + + $this->activeSpans = new SplObjectStorage(); + $this->excludedDepth = 0; + } + private function getSpanName(Profile $profile): string { if ($profile->isRoot()) { diff --git a/src/bridge/symfony/telemetry-bundle/src/Flow/Bridge/Symfony/TelemetryBundle/Instrumentation/Twig/TwigSpanCleanupSubscriber.php b/src/bridge/symfony/telemetry-bundle/src/Flow/Bridge/Symfony/TelemetryBundle/Instrumentation/Twig/TwigSpanCleanupSubscriber.php new file mode 100644 index 0000000000..1e3fe64570 --- /dev/null +++ b/src/bridge/symfony/telemetry-bundle/src/Flow/Bridge/Symfony/TelemetryBundle/Instrumentation/Twig/TwigSpanCleanupSubscriber.php @@ -0,0 +1,39 @@ + ['onTerminate', -15000], + ConsoleEvents::TERMINATE => ['onConsoleTerminate', -15000], + ]; + } + + public function onConsoleTerminate(ConsoleTerminateEvent $event): void + { + $this->extension->reset(); + } + + public function onTerminate(TerminateEvent $event): void + { + $this->extension->reset(); + } +} diff --git a/src/bridge/symfony/telemetry-bundle/src/Flow/Bridge/Symfony/TelemetryBundle/Resources/config/instrumentation/twig.php b/src/bridge/symfony/telemetry-bundle/src/Flow/Bridge/Symfony/TelemetryBundle/Resources/config/instrumentation/twig.php index 3c5b2d8e4b..990f860720 100644 --- a/src/bridge/symfony/telemetry-bundle/src/Flow/Bridge/Symfony/TelemetryBundle/Resources/config/instrumentation/twig.php +++ b/src/bridge/symfony/telemetry-bundle/src/Flow/Bridge/Symfony/TelemetryBundle/Resources/config/instrumentation/twig.php @@ -3,6 +3,7 @@ declare(strict_types=1); use Flow\Bridge\Symfony\TelemetryBundle\Instrumentation\Twig\TracingTwigExtension; +use Flow\Bridge\Symfony\TelemetryBundle\Instrumentation\Twig\TwigSpanCleanupSubscriber; use Flow\Telemetry\Telemetry; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; @@ -20,5 +21,12 @@ '%flow.telemetry.twig.trace_macros%', '%flow.telemetry.twig.exclude_templates%', ]) - ->tag('twig.extension'); + ->tag('twig.extension')->tag('kernel.reset', ['method' => 'reset']); + + $services + ->set('flow.telemetry.twig.span_cleanup_subscriber', TwigSpanCleanupSubscriber::class) + ->args([ + service('flow.telemetry.twig.extension'), + ]) + ->tag('kernel.event_subscriber'); }; diff --git a/src/bridge/symfony/telemetry-bundle/tests/Flow/Bridge/Symfony/TelemetryBundle/Tests/Mother/TelemetryMother.php b/src/bridge/symfony/telemetry-bundle/tests/Flow/Bridge/Symfony/TelemetryBundle/Tests/Mother/TelemetryMother.php new file mode 100644 index 0000000000..66d4dc6b09 --- /dev/null +++ b/src/bridge/symfony/telemetry-bundle/tests/Flow/Bridge/Symfony/TelemetryBundle/Tests/Mother/TelemetryMother.php @@ -0,0 +1,32 @@ + 'test']), + new TracerProvider($spanProcessor, $clock, $contextStorage), + new MeterProvider(new VoidMetricProcessor(), $clock), + new LoggerProvider(new VoidLogProcessor(), $clock, $contextStorage), + ); + } +} diff --git a/src/bridge/symfony/telemetry-bundle/tests/Flow/Bridge/Symfony/TelemetryBundle/Tests/Unit/Instrumentation/Twig/TracingTwigExtensionTest.php b/src/bridge/symfony/telemetry-bundle/tests/Flow/Bridge/Symfony/TelemetryBundle/Tests/Unit/Instrumentation/Twig/TracingTwigExtensionTest.php index 79252f9a08..fa7606ff1c 100644 --- a/src/bridge/symfony/telemetry-bundle/tests/Flow/Bridge/Symfony/TelemetryBundle/Tests/Unit/Instrumentation/Twig/TracingTwigExtensionTest.php +++ b/src/bridge/symfony/telemetry-bundle/tests/Flow/Bridge/Symfony/TelemetryBundle/Tests/Unit/Instrumentation/Twig/TracingTwigExtensionTest.php @@ -5,18 +5,10 @@ namespace Flow\Bridge\Symfony\TelemetryBundle\Tests\Unit\Instrumentation\Twig; use Flow\Bridge\Symfony\TelemetryBundle\Instrumentation\Twig\TracingTwigExtension; -use Flow\Telemetry\Context\MemoryContextStorage; -use Flow\Telemetry\Logger\LoggerProvider; -use Flow\Telemetry\Meter\MeterProvider; -use Flow\Telemetry\Provider\Clock\SystemClock; +use Flow\Bridge\Symfony\TelemetryBundle\Tests\Mother\TelemetryMother; use Flow\Telemetry\Provider\Memory\MemoryExporter; use Flow\Telemetry\Provider\Memory\MemorySpanProcessor; -use Flow\Telemetry\Provider\Void\VoidLogProcessor; -use Flow\Telemetry\Provider\Void\VoidMetricProcessor; -use Flow\Telemetry\Resource; -use Flow\Telemetry\Telemetry; use Flow\Telemetry\Tracer\SpanKind; -use Flow\Telemetry\Tracer\TracerProvider; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Twig\Profiler\NodeVisitor\ProfilerNodeVisitor; @@ -28,7 +20,7 @@ final class TracingTwigExtensionTest extends TestCase public function test_excluded_template_does_not_trace_nested_blocks(): void { $spanProcessor = new MemorySpanProcessor(new MemoryExporter()); - $telemetry = $this->createTelemetry($spanProcessor); + $telemetry = TelemetryMother::withSpanProcessor($spanProcessor); $extension = new TracingTwigExtension( $telemetry, @@ -55,7 +47,7 @@ public function test_excluded_template_does_not_trace_nested_blocks(): void public function test_get_node_visitors_returns_profiler_node_visitor(): void { $spanProcessor = new MemorySpanProcessor(new MemoryExporter()); - $telemetry = $this->createTelemetry($spanProcessor); + $telemetry = TelemetryMother::withSpanProcessor($spanProcessor); $extension = new TracingTwigExtension($telemetry); @@ -68,7 +60,7 @@ public function test_get_node_visitors_returns_profiler_node_visitor(): void public function test_get_span_name_with_block_profile_returns_formatted_string(): void { $spanProcessor = new MemorySpanProcessor(new MemoryExporter()); - $telemetry = $this->createTelemetry($spanProcessor); + $telemetry = TelemetryMother::withSpanProcessor($spanProcessor); $extension = new TracingTwigExtension($telemetry, traceTemplates: true, traceBlocks: true); @@ -84,7 +76,7 @@ public function test_get_span_name_with_block_profile_returns_formatted_string() public function test_get_span_name_with_macro_profile_returns_formatted_string(): void { $spanProcessor = new MemorySpanProcessor(new MemoryExporter()); - $telemetry = $this->createTelemetry($spanProcessor); + $telemetry = TelemetryMother::withSpanProcessor($spanProcessor); $extension = new TracingTwigExtension($telemetry, traceTemplates: true, traceMacros: true); @@ -100,7 +92,7 @@ public function test_get_span_name_with_macro_profile_returns_formatted_string() public function test_get_span_name_with_root_profile_returns_profile_name(): void { $spanProcessor = new MemorySpanProcessor(new MemoryExporter()); - $telemetry = $this->createTelemetry($spanProcessor); + $telemetry = TelemetryMother::withSpanProcessor($spanProcessor); $extension = new TracingTwigExtension($telemetry); @@ -116,7 +108,7 @@ public function test_get_span_name_with_root_profile_returns_profile_name(): voi public function test_get_span_name_with_template_profile_returns_template_path(): void { $spanProcessor = new MemorySpanProcessor(new MemoryExporter()); - $telemetry = $this->createTelemetry($spanProcessor); + $telemetry = TelemetryMother::withSpanProcessor($spanProcessor); $extension = new TracingTwigExtension($telemetry, traceTemplates: true); @@ -132,7 +124,7 @@ public function test_get_span_name_with_template_profile_returns_template_path() public function test_is_template_excluded_exact_match(): void { $spanProcessor = new MemorySpanProcessor(new MemoryExporter()); - $telemetry = $this->createTelemetry($spanProcessor); + $telemetry = TelemetryMother::withSpanProcessor($spanProcessor); $extension = new TracingTwigExtension( $telemetry, @@ -154,7 +146,7 @@ public function test_is_template_excluded_exact_match(): void public function test_is_template_excluded_no_match_returns_false(): void { $spanProcessor = new MemorySpanProcessor(new MemoryExporter()); - $telemetry = $this->createTelemetry($spanProcessor); + $telemetry = TelemetryMother::withSpanProcessor($spanProcessor); $extension = new TracingTwigExtension( $telemetry, @@ -174,7 +166,7 @@ public function test_is_template_excluded_no_match_returns_false(): void public function test_is_template_excluded_regex_match(): void { $spanProcessor = new MemorySpanProcessor(new MemoryExporter()); - $telemetry = $this->createTelemetry($spanProcessor); + $telemetry = TelemetryMother::withSpanProcessor($spanProcessor); $extension = new TracingTwigExtension($telemetry, traceTemplates: true, excludeTemplates: ['/^@WebProfiler/']); @@ -188,7 +180,7 @@ public function test_is_template_excluded_regex_match(): void public function test_should_trace_respects_block_flag(): void { $spanProcessor = new MemorySpanProcessor(new MemoryExporter()); - $telemetry = $this->createTelemetry($spanProcessor); + $telemetry = TelemetryMother::withSpanProcessor($spanProcessor); $extension = new TracingTwigExtension($telemetry, traceTemplates: true, traceBlocks: false); @@ -202,7 +194,7 @@ public function test_should_trace_respects_block_flag(): void public function test_should_trace_respects_macro_flag(): void { $spanProcessor = new MemorySpanProcessor(new MemoryExporter()); - $telemetry = $this->createTelemetry($spanProcessor); + $telemetry = TelemetryMother::withSpanProcessor($spanProcessor); $extension = new TracingTwigExtension($telemetry, traceTemplates: true, traceMacros: false); @@ -216,7 +208,7 @@ public function test_should_trace_respects_macro_flag(): void public function test_should_trace_respects_template_flag(): void { $spanProcessor = new MemorySpanProcessor(new MemoryExporter()); - $telemetry = $this->createTelemetry($spanProcessor); + $telemetry = TelemetryMother::withSpanProcessor($spanProcessor); $extension = new TracingTwigExtension($telemetry, traceTemplates: false); @@ -230,7 +222,7 @@ public function test_should_trace_respects_template_flag(): void public function test_span_has_correct_attributes_for_block(): void { $spanProcessor = new MemorySpanProcessor(new MemoryExporter()); - $telemetry = $this->createTelemetry($spanProcessor); + $telemetry = TelemetryMother::withSpanProcessor($spanProcessor); $extension = new TracingTwigExtension($telemetry, traceTemplates: true, traceBlocks: true); @@ -250,7 +242,7 @@ public function test_span_has_correct_attributes_for_block(): void public function test_span_has_correct_attributes_for_template(): void { $spanProcessor = new MemorySpanProcessor(new MemoryExporter()); - $telemetry = $this->createTelemetry($spanProcessor); + $telemetry = TelemetryMother::withSpanProcessor($spanProcessor); $extension = new TracingTwigExtension($telemetry, traceTemplates: true); @@ -267,10 +259,29 @@ public function test_span_has_correct_attributes_for_template(): void static::assertArrayNotHasKey('twig.name', $attributes); } + public function test_span_has_ok_status_after_leave(): void + { + $spanProcessor = new MemorySpanProcessor(new MemoryExporter()); + $telemetry = TelemetryMother::withSpanProcessor($spanProcessor); + + $extension = new TracingTwigExtension($telemetry, traceTemplates: true); + + $profile = new Profile('templates/home.html.twig', Profile::TEMPLATE, 'templates/home.html.twig'); + $extension->enter($profile); + $extension->leave($profile); + + $spans = $spanProcessor->endedSpans(); + static::assertCount(1, $spans); + + $status = $spans[0]->status(); + static::assertNotNull($status); + static::assertTrue($status->isOk()); + } + public function test_span_kind_is_internal(): void { $spanProcessor = new MemorySpanProcessor(new MemoryExporter()); - $telemetry = $this->createTelemetry($spanProcessor); + $telemetry = TelemetryMother::withSpanProcessor($spanProcessor); $extension = new TracingTwigExtension($telemetry); @@ -283,16 +294,68 @@ public function test_span_kind_is_internal(): void static::assertSame(SpanKind::INTERNAL, $spans[0]->kind()); } - private function createTelemetry(MemorySpanProcessor $spanProcessor): Telemetry + public function test_reset_completes_orphaned_span_as_error(): void { - $clock = new SystemClock(); - $contextStorage = new MemoryContextStorage(); - - return new Telemetry( - Resource::create(['service.name' => 'test']), - new TracerProvider($spanProcessor, $clock, $contextStorage), - new MeterProvider(new VoidMetricProcessor(), $clock), - new LoggerProvider(new VoidLogProcessor(), $clock, $contextStorage), + $spanProcessor = new MemorySpanProcessor(new MemoryExporter()); + $telemetry = TelemetryMother::withSpanProcessor($spanProcessor); + + $extension = new TracingTwigExtension($telemetry, traceTemplates: true); + + $profile = new Profile('templates/home.html.twig', Profile::TEMPLATE, 'templates/home.html.twig'); + $extension->enter($profile); + + static::assertCount(0, $spanProcessor->endedSpans()); + + $extension->reset(); + + $spans = $spanProcessor->endedSpans(); + static::assertCount(1, $spans); + + $status = $spans[0]->status(); + static::assertNotNull($status); + static::assertTrue($status->isError()); + static::assertSame('Twig rendering did not complete', $status->description); + } + + public function test_reset_clears_excluded_depth_so_tracing_resumes(): void + { + $spanProcessor = new MemorySpanProcessor(new MemoryExporter()); + $telemetry = TelemetryMother::withSpanProcessor($spanProcessor); + + $extension = new TracingTwigExtension( + $telemetry, + traceTemplates: true, + excludeTemplates: ['@WebProfiler/layout.html.twig'], ); + + $excluded = new Profile('@WebProfiler/layout.html.twig', Profile::TEMPLATE, '@WebProfiler/layout.html.twig'); + $extension->enter($excluded); + + $extension->reset(); + + $profile = new Profile('templates/home.html.twig', Profile::TEMPLATE, 'templates/home.html.twig'); + $extension->enter($profile); + $extension->leave($profile); + + $spans = $spanProcessor->endedSpans(); + static::assertCount(1, $spans); + static::assertSame('templates/home.html.twig', $spans[0]->name()); + } + + public function test_reset_without_active_spans_is_noop(): void + { + $spanProcessor = new MemorySpanProcessor(new MemoryExporter()); + $telemetry = TelemetryMother::withSpanProcessor($spanProcessor); + + $extension = new TracingTwigExtension($telemetry, traceTemplates: true); + + $profile = new Profile('templates/home.html.twig', Profile::TEMPLATE, 'templates/home.html.twig'); + $extension->enter($profile); + $extension->leave($profile); + + $extension->reset(); + $extension->reset(); + + static::assertCount(1, $spanProcessor->endedSpans()); } } diff --git a/src/bridge/symfony/telemetry-bundle/tests/Flow/Bridge/Symfony/TelemetryBundle/Tests/Unit/Instrumentation/Twig/TwigSpanCleanupSubscriberTest.php b/src/bridge/symfony/telemetry-bundle/tests/Flow/Bridge/Symfony/TelemetryBundle/Tests/Unit/Instrumentation/Twig/TwigSpanCleanupSubscriberTest.php new file mode 100644 index 0000000000..d443c83aa3 --- /dev/null +++ b/src/bridge/symfony/telemetry-bundle/tests/Flow/Bridge/Symfony/TelemetryBundle/Tests/Unit/Instrumentation/Twig/TwigSpanCleanupSubscriberTest.php @@ -0,0 +1,76 @@ +enter(new Profile('templates/home.html.twig', Profile::TEMPLATE, 'templates/home.html.twig')); + + $subscriber = new TwigSpanCleanupSubscriber($extension); + $command = new Command('app:process'); + $subscriber->onConsoleTerminate(new ConsoleTerminateEvent($command, new ArrayInput([]), new NullOutput(), 0)); + + $spans = $spanProcessor->endedSpans(); + static::assertCount(1, $spans); + + $status = $spans[0]->status(); + static::assertNotNull($status); + static::assertTrue($status->isError()); + } + + public function test_on_terminate_completes_orphaned_span(): void + { + $spanProcessor = new MemorySpanProcessor(new MemoryExporter()); + $extension = new TracingTwigExtension(TelemetryMother::withSpanProcessor($spanProcessor), traceTemplates: true); + + $extension->enter(new Profile('templates/home.html.twig', Profile::TEMPLATE, 'templates/home.html.twig')); + + $subscriber = new TwigSpanCleanupSubscriber($extension); + $kernel = $this->createStub(HttpKernelInterface::class); + $subscriber->onTerminate(new TerminateEvent($kernel, new Request(), new Response())); + + $spans = $spanProcessor->endedSpans(); + static::assertCount(1, $spans); + + $status = $spans[0]->status(); + static::assertNotNull($status); + static::assertTrue($status->isError()); + } +}