From a52b0afaab71f2676a75095abc6f12517a631b06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BCrk?= Date: Thu, 23 Jul 2026 18:08:39 +0200 Subject: [PATCH] [BUGFIX] Drop only bootstrap-opened output buffers TYPO3 v15 removes the implicit output buffer that Bootstrap::init() opened as its very first action. Two places compensated for that buffer with an unconditional ob_end_clean(): setUpBasicTypo3Bootstrap() in Testbase and executeFrontendSubRequest() in FunctionalTestCase. Without the bootstrap buffer, these calls close phpunit's own output buffer instead. phpunit then reports every functional test as risky with "Test code or tested code closed output buffers other than its own", which fails all functional jobs of the core test suite. Both places now remember ob_get_level() before Bootstrap::init() and unwind only the buffers opened above that level. This keeps working with TYPO3 v14, where the bootstrap still opens a buffer, and it additionally guards the frontend sub request against stray buffers left open by extension code. Resolves: #732 Related: https://forge.typo3.org/issues/110250 Related: https://review.typo3.org/c/Packages/TYPO3.CMS/+/94863 Releases: main --- Classes/Core/Functional/FunctionalTestCase.php | 9 +++++++-- Classes/Core/Testbase.php | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Classes/Core/Functional/FunctionalTestCase.php b/Classes/Core/Functional/FunctionalTestCase.php index 390fa4ce..1dbec1f4 100644 --- a/Classes/Core/Functional/FunctionalTestCase.php +++ b/Classes/Core/Functional/FunctionalTestCase.php @@ -1005,6 +1005,7 @@ private function retrieveFrontendSubRequestResult( $_SERVER['SCRIPT_NAME'] = '/index.php'; $_SERVER['HTTP_HOST'] = $_SERVER['SERVER_NAME'] = $request->getUri()->getHost() ?: 'localhost'; + $outputBufferingLevel = ob_get_level(); $container = Bootstrap::init(ClassLoadingInformation::getClassLoader()); /** @var InternalRequest $serverRequest */ @@ -1050,8 +1051,12 @@ private function retrieveFrontendSubRequestResult( $frontendApplication = $container->get(Application::class); $response = $frontendApplication->handle($serverRequest); } finally { - // Somewhere an ob_start() is called in frontend that is not cleaned. Work around that for now. - ob_end_clean(); + // TYPO3 v14 opens an implicit output buffer in Bootstrap::init(), TYPO3 v15 + // does not. Frontend or extension code may leave further buffers open. Drop + // everything opened by the sub request, but never phpunit's own buffer. + while (ob_get_level() > $outputBufferingLevel) { + ob_end_clean(); + } FrameworkState::pop(); } return $response; diff --git a/Classes/Core/Testbase.php b/Classes/Core/Testbase.php index 010b61a7..6316e94e 100644 --- a/Classes/Core/Testbase.php +++ b/Classes/Core/Testbase.php @@ -754,10 +754,15 @@ public function setUpBasicTypo3Bootstrap(string $instancePath): ContainerInterfa $classLoader = require $this->getPackagesPath() . '/autoload.php'; SystemEnvironmentBuilder::run(0, SystemEnvironmentBuilder::REQUESTTYPE_CLI, false); + $outputBufferingLevel = ob_get_level(); $container = Bootstrap::init($classLoader); // Make sure output is not buffered, so command-line output can take place and - // phpunit does not whine about changed output bufferings in tests. - ob_end_clean(); + // phpunit does not whine about changed output bufferings in tests. TYPO3 v14 + // opens an implicit output buffer in Bootstrap::init(), TYPO3 v15 does not. + // Only drop buffers opened by the bootstrap, never phpunit's own one. + while (ob_get_level() > $outputBufferingLevel) { + ob_end_clean(); + } $this->dumpClassLoadingInformation();