Skip to content

[BUGFIX] Skip class loading info dump in Composer mode#730

Merged
sbuerk merged 1 commit into
mainfrom
bugfix/725-skip-class-loading-dump-composer-mode
Jul 23, 2026
Merged

[BUGFIX] Skip class loading info dump in Composer mode#730
sbuerk merged 1 commit into
mainfrom
bugfix/725-skip-class-loading-dump-composer-mode

Conversation

@sbuerk

@sbuerk sbuerk commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Closes #725

Problem

Testbase::dumpClassLoadingInformation() is called unconditionally — also from
Resources/Core/Build/UnitTestsBootstrap.php, which runs in the scope of the outer
Composer installation
.

TYPO3 v14.3.1 changed where the classic-mode autoload information of the testing context
is written. ClassLoadingInformation::getClassLoadingInformationDirectory() now derives
the directory from Environment::getExtensionsPath(), and that resolves to
<projectRoot>/packages/ext in Composer mode:

v13.4 v14.3.1+
getExtensionsPath() (Composer mode) <public>/typo3conf/ext <project>/packages/ext
dump dir (testing context) <public>/typo3conf/autoload-tests/ <project>/packages/autoload-tests/

So every unit test run now creates <projectRoot>/packages/autoload-tests/ — colliding
with the directory many projects use for their own local extensions (the layout documented
in TYPO3's Composer best-practices guide). Previously the same dump landed in the web dir,
i.e. inside gitignored build output, and nobody noticed.

Accountable Core change: edfb03ae775 / 770c5383930 "[TASK] Allow public folder in
classic mode"
, forge #109470, Gerrit
93491 /
93799. First released in
v14.3.1 — v14.0/14.2/14.3.0 are not affected, so this is not a testing-framework
version regression but a Core patch-level one.

Why the dump can simply be skipped

It is a no-op in Composer mode and always has been. PackageManager::scanAvailablePackages()
returns early when Environment::isComposerMode() is true — identical code in v13.4.33 and
v14.3.5 — so UnitTestPackageManager registers zero packages and the generated files are
empty:

autoload_classaliasmap.php  118 bytes   # empty alias mappings
autoload_classmap.php       147 bytes   # return array();
autoload_files.php          147 bytes   # return array();
autoload_psr4.php           147 bytes   # return array();

Only the location regressed. Nothing can be lost by not writing them.

Every other consumer of this API in typo3/cms-core (DumpAutoloadCommand,
MaintenanceController, SetupService, ClassLoadingInformationUpdater) already guards on
Composer mode. Testbase was the only unguarded call site.

Functional tests are not affected

This is the important part, because functional test instances genuinely do need the
classic-mode dump for their fixture extensions:

Caller Composer mode Dump
UnitTestsBootstrap.php (extension/project) TYPO3_COMPOSER_MODEtrue skipped (was empty anyway)
UnitTestsBootstrap.php (Core mono-repository) undefined → false unchanged
Testbase::setUpBasicTypo3Bootstrap() (all functional tests) hard-coded false (Testbase.php:756) unchanged

Verified after the change: functional instances still get a non-empty dump at
.Build/Web/typo3temp/var/tests/functional-*/typo3conf/autoload-tests/, with all fixture
and extension PSR-4 prefixes including …\Tests\, and a 71-line class map.

The fix has to live in Testbase, not in UnitTestsBootstrap.php: that file is boilerplate
which extensions are encouraged to copy, so guarding it there would not reach any of the
already-copied bootstraps.

No Core bugfix is required — dirname(getExtensionsPath()) is the correct anchor for
classic-mode autoload information, and Core itself never calls the dump in Composer mode.

Steps to reproduce

With web-vision/deepl-write on main (project root ≠ web root, web-dir = .Build/Web):

# broken with v14
rm -rf .Build/ packages/ composer.lock
Build/Scripts/runTests.sh -t 14 -p 8.2 -s composerUpdate
Build/Scripts/runTests.sh -t 14 -p 8.2 -s unit
ls packages/autoload-tests/          # <-- created, four empty files

# fine with v13
rm -rf .Build/ packages/ composer.lock
Build/Scripts/runTests.sh -t 13 -p 8.2 -s composerUpdate
Build/Scripts/runTests.sh -t 13 -p 8.2 -s unit
ls packages/                         # <-- does not exist

Test results

web-vision/deepl-write, PHP 8.2, testing-framework patched with this change:

Core Suite Result
v14.3.5 -s unit SUCCESS — no packages/ (before: packages/autoload-tests/ created)
v14.3.5 -d sqlite -s functional SUCCESS (10 tests, 11 assertions), instance dumps intact and non-empty
v13.4.33 -s unit SUCCESS — no packages/, no typo3conf/autoload-tests/ either
v13.4.33 -d sqlite -s functional SUCCESS (4 tests, 3 assertions)

TYPO3 Core mono-repository (main, v15-dev), PHP 8.4, vendored testing-framework patched:

Suite Result
-s unit -- --filter GeneralUtilityTest SUCCESS (676 tests, 756 assertions) — typo3conf/autoload-tests/ still written, behaviour unchanged
-d sqlite -s functional -- typo3/sysext/core/Tests/Functional/Package/ SUCCESS (6 tests, 26 assertions)
-d sqlite -s functional -- typo3/sysext/extbase/Tests/Functional/Persistence/ SUCCESS (468 tests, 1671 assertions)

This repository's own gates, PHP 8.2:

Gate Result
-s composerUpdate SUCCESS
-s cgl -n SUCCESS
-s lint SUCCESS
-s phpstan SUCCESS — no errors
-s unit SUCCESS — 124 tests, 329 assertions

Branches

Releases: main, 9. Branch 9 is the branch v14 users actually consume today, so the port
should not lag behind. No core-version-aware conditional is needed there:
Environment::isComposerMode() exists unchanged since v9, and the Composer-mode early
return in PackageManager::scanAvailablePackages() is identical on v12, v13, v14 and main.

Note for users

A packages/autoload-tests/ directory left behind by an earlier test run has to be removed
manually once — this change only stops new ones from being created.

Testbase::dumpClassLoadingInformation() is called unconditionally, also
from the unit test bootstrap, which runs in the scope of the outer
Composer installation. TYPO3 v14.3.1 changed where the classic mode
autoload information of the testing context is written: the dump
directory is now derived from Environment::getExtensionsPath(), which
resolves to "<projectRoot>/packages/ext" in Composer mode. Unit test
runs therefore create "<projectRoot>/packages/autoload-tests/",
colliding with the directory many projects use for their own local
extensions.

That dump is a no-op in Composer mode anyway, because
PackageManager::scanAvailablePackages() bails out early there and
leaves UnitTestPackageManager without any registered package. The
generated files have always been empty in that case, on v13 as well -
only their location changed, from build output to the project root.

Skip the dump in Composer mode, mirroring the guard TYPO3 Core uses on
all of its own call sites. Functional tests are unaffected:
setUpBasicTypo3Bootstrap() forces Composer mode to false, so their
instances keep receiving the class loading information they need for
their fixture extensions. The Core mono repository keeps its current
behaviour for the same reason.

Note that a "packages/autoload-tests/" directory left behind by an
earlier test run has to be removed manually once.

Resolves: #725
Releases: main, 9
@sbuerk

sbuerk commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator Author

Additional verification against web-vision/deepltranslate-core (main, commit b342aad), picked because it ships three functional fixture extensions in Tests/Functional/Fixtures/Extensions/ (test_tca_override, test_container, testing_framework_backenduserhandler_replacement) — the case that would actually break if the guard were wrong.

Vendored typo3/testing-framework 9.6.0 patched with the guard from this PR. PHP 8.2, sqlite. An unpatched baseline unit run was done on each core first, so the before/after is on the same project rather than inferred.

Core Suite Result
v13.4.33 -s unit (baseline, unpatched) SUCCESS — no packages/, dump at .Build/Web/typo3conf/autoload-tests/
v13.4.33 -s unit (patched) SUCCESS (16 tests, 30 assertions) — no packages/, no typo3conf/autoload-tests/
v13.4.33 -s functional (patched) SUCCESS (44 tests, 101 assertions)
v14.3.5 -s unit (baseline, unpatched) SUCCESS — packages/autoload-tests/ created, four empty files (118/147/147/147 bytes)
v14.3.5 -s unit (patched) SUCCESS (16 tests, 30 assertions) — no packages/
v14.3.5 -s functional (patched) SUCCESS (44 tests, 101 assertions)

Commands used:

rm -rf vendor/ packages/ composer.lock && \
Build/Scripts/runTests.sh -t 13 -p 8.2 -s composerUpdate && \
Build/Scripts/runTests.sh -t 13 -p 8.2 -s unit && \
Build/Scripts/runTests.sh -t 13 -p 8.2 -s functional && \
rm -rf vendor/ packages/ composer.lock && \
Build/Scripts/runTests.sh -t 14 -p 8.2 -s composerUpdate && \
Build/Scripts/runTests.sh -t 14 -p 8.2 -s unit && \
Build/Scripts/runTests.sh -t 14 -p 8.2 -s functional

The decisive bit: with the guard in place the functional test instances still receive the full classic-mode dump, including the fixture extension and all Tests/ directories.

.Build/Web/typo3temp/var/tests/functional-*/typo3conf/autoload-tests/autoload_psr4.php (v14 run):

'TYPO3\JsonResponse\'                     => typo3conf/ext/json_response/Classes
'TYPO3\PrivateContainer\'                 => typo3conf/ext/private_container/Classes
'WebVision\Deepl\Base\'                   => typo3conf/ext/deepl_base/Classes
'WebVision\Deepl\Base\Core13\'            => typo3conf/ext/deepl_base/Core13/Classes
'WebVision\Deepl\Base\Tests\'             => typo3conf/ext/deepl_base/Tests
'WebVision\Deepltranslate\Core\'          => typo3conf/ext/deepltranslate_core/Classes
'WebVision\Deepltranslate\Core\Core13\'   => typo3conf/ext/deepltranslate_core/Core13
'WebVision\Deepltranslate\Core\Core14\'   => typo3conf/ext/deepltranslate_core/Core14
'WebVision\Deepltranslate\Core\Testing\'  => typo3conf/ext/deepltranslate_core/TestClasses
'WebVision\Deepltranslate\Core\Tests\'    => typo3conf/ext/deepltranslate_core/Tests
'WebVision\TestingFrameworkBackendUserHandlerReplacement\'
    => typo3conf/ext/deepltranslate_core/Tests/Functional/Fixtures/Extensions/
       testing_framework_backenduserhandler_replacement/Classes

plus 100 class map entries, with all five extensions correctly symlinked into the instance's typo3conf/ext/.

So the guard drops only the empty Composer-mode dump in the outer project scope and leaves the classic-mode instance dump untouched.

@sbuerk
sbuerk merged commit 6073033 into main Jul 23, 2026
8 checks passed
@sbuerk
sbuerk deleted the bugfix/725-skip-class-loading-dump-composer-mode branch July 23, 2026 07:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Testbase::dumpClassLoadingInformation() writes into the real project root even in composer mode

2 participants