From ecb4cd1512f2530c7bd8acfd133a48620074991b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Thu, 10 Sep 2026 14:05:57 +0200 Subject: [PATCH 1/3] IBX-12530: Rebuilt DatabaseSchemaHook on SchemaBuilderInterface instead of a raw-file reimplementation (#43) Previously imported schema.yaml files listed by SchemaFilesProviderInterface via LegacySchemaImporter -- a test-only mechanism that never actually dispatched SchemaBuilderEvent, so any package's own event subscriber (e.g. one deriving its schema from Doctrine ORM entity mappings instead of a schema.yaml file) was never exercised by integration tests at all. Now calls SchemaBuilderInterface::buildSchema() directly -- the same production code ibexa:install's legacy path uses -- and applies the resulting Schema's SQL directly, since the test database is always freshly created. No per-package schema file list needed. Only registered when DoctrineSchemaBundle is actually present (detected in IbexaTestCoreBundle::build(), which runs against the real, shared container -- unlike Extension::load(), which runs against a temporary per-extension copy MergeExtensionConfigurationPass uses to avoid cross-extension leakage, so sibling bundles' extensions never show up there). --- .../RemoveUnsatisfiableHooksPass.php | 51 +++++++++++ src/bundle/IbexaTestCoreBundle.php | 2 + src/bundle/Resources/config/services.php | 19 +---- .../Bootstrapper/DatabaseSchemaHook.php | 33 ++++--- .../SchemaFilesProviderInterface.php | 29 ------- src/contracts/IbexaTestKernel.php | 13 +++ .../SchemaFilesKernelMethodProvider.php | 35 -------- .../SchemaFilesParameterProvider.php | 36 -------- .../Bootstrapper/SchemaFilesProviderChain.php | 45 ---------- .../SchemaFilesProviderChainTest.php | 85 ------------------- 10 files changed, 86 insertions(+), 262 deletions(-) create mode 100644 src/bundle/DependencyInjection/CompilerPass/RemoveUnsatisfiableHooksPass.php delete mode 100644 src/contracts/Bootstrapper/SchemaFilesProviderInterface.php delete mode 100644 src/lib/Bootstrapper/SchemaFilesKernelMethodProvider.php delete mode 100644 src/lib/Bootstrapper/SchemaFilesParameterProvider.php delete mode 100644 src/lib/Bootstrapper/SchemaFilesProviderChain.php delete mode 100644 tests/lib/Bootstrapper/SchemaFilesProviderChainTest.php diff --git a/src/bundle/DependencyInjection/CompilerPass/RemoveUnsatisfiableHooksPass.php b/src/bundle/DependencyInjection/CompilerPass/RemoveUnsatisfiableHooksPass.php new file mode 100644 index 0000000..e76fd70 --- /dev/null +++ b/src/bundle/DependencyInjection/CompilerPass/RemoveUnsatisfiableHooksPass.php @@ -0,0 +1,51 @@ +> hook service id => service ids it cannot work without + */ + private const HOOK_REQUIREMENTS = [ + DatabaseSchemaHook::class => [ + SchemaBuilderInterface::class, + // core's own schema contribution; without it the event yields every other package's + // tables but none of core's + BuildSchemaSubscriber::class, + ], + ]; + + public function process(ContainerBuilder $container): void + { + foreach (self::HOOK_REQUIREMENTS as $hookId => $requiredServiceIds) { + if (!$container->hasDefinition($hookId)) { + continue; + } + + foreach ($requiredServiceIds as $serviceId) { + if (!$container->has($serviceId)) { + $container->removeDefinition($hookId); + + break; + } + } + } + } +} diff --git a/src/bundle/IbexaTestCoreBundle.php b/src/bundle/IbexaTestCoreBundle.php index a0bbb49..435b6d4 100644 --- a/src/bundle/IbexaTestCoreBundle.php +++ b/src/bundle/IbexaTestCoreBundle.php @@ -9,6 +9,7 @@ namespace Ibexa\Bundle\Test\Core; use Ibexa\Bundle\Test\Core\DependencyInjection\CompilerPass\PersistenceCheckCompilerPass; +use Ibexa\Bundle\Test\Core\DependencyInjection\CompilerPass\RemoveUnsatisfiableHooksPass; use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -18,5 +19,6 @@ final class IbexaTestCoreBundle extends Bundle public function build(ContainerBuilder $container): void { $container->addCompilerPass(new PersistenceCheckCompilerPass(), PassConfig::TYPE_AFTER_REMOVING); + $container->addCompilerPass(new RemoveUnsatisfiableHooksPass()); } } diff --git a/src/bundle/Resources/config/services.php b/src/bundle/Resources/config/services.php index 654a94a..26607ed 100644 --- a/src/bundle/Resources/config/services.php +++ b/src/bundle/Resources/config/services.php @@ -17,21 +17,16 @@ use Ibexa\Contracts\Test\Core\Bootstrapper\HooksExecutorInterface; use Ibexa\Contracts\Test\Core\Bootstrapper\PurgeIndexAfterFixturesHook; use Ibexa\Contracts\Test\Core\Bootstrapper\PurgeSearchIndexHook; -use Ibexa\Contracts\Test\Core\Bootstrapper\SchemaFilesProviderInterface; use Ibexa\Test\Core\Bootstrapper\FixtureKernelMethodProvider; use Ibexa\Test\Core\Bootstrapper\FixtureParameterProvider; use Ibexa\Test\Core\Bootstrapper\FixtureProviderChain; use Ibexa\Test\Core\Bootstrapper\HooksExecutor; -use Ibexa\Test\Core\Bootstrapper\SchemaFilesKernelMethodProvider; -use Ibexa\Test\Core\Bootstrapper\SchemaFilesParameterProvider; -use Ibexa\Test\Core\Bootstrapper\SchemaFilesProviderChain; return static function (ContainerConfigurator $containerConfigurator): void { $containerConfigurator->parameters() // null (not []): an unset/unconfigured parameter must be distinguishable from a consumer // deliberately configuring an empty list, since only the latter should stop the fallback - // chain in *ProviderChain — see SchemaFilesProviderInterface's docblock. - ->set('ibexa.test.schema_files', null) + // chain in FixtureProviderChain — see FixtureProviderInterface's docblock. ->set('ibexa.test.fixture_files', null); $services = $containerConfigurator->services(); @@ -56,17 +51,6 @@ $services->set(DefaultFixtureProvider::class); - $services->set(SchemaFilesKernelMethodProvider::class) - ->arg('$kernel', service('kernel')) - ->tag(SchemaFilesProviderInterface::TAG, ['priority' => 0]); - - $services->set(SchemaFilesParameterProvider::class) - ->arg('$schemaFiles', '%ibexa.test.schema_files%') - ->tag(SchemaFilesProviderInterface::TAG, ['priority' => 100]); - - $services->set(SchemaFilesProviderChain::class) - ->arg('$providers', tagged_iterator(SchemaFilesProviderInterface::TAG)); - $services->set(FixtureKernelMethodProvider::class) ->arg('$kernel', service('kernel')) ->tag(FixtureProviderInterface::TAG, ['priority' => 0]); @@ -79,7 +63,6 @@ ->arg('$providers', tagged_iterator(FixtureProviderInterface::TAG)); $services->set(DatabaseSchemaHook::class) - ->arg('$provider', service(SchemaFilesProviderChain::class)) ->tag(HookInterface::TAG, ['priority' => DatabaseSchemaHook::PRIORITY]); $services->set(FixtureHook::class) diff --git a/src/contracts/Bootstrapper/DatabaseSchemaHook.php b/src/contracts/Bootstrapper/DatabaseSchemaHook.php index f2d728a..94097f3 100644 --- a/src/contracts/Bootstrapper/DatabaseSchemaHook.php +++ b/src/contracts/Bootstrapper/DatabaseSchemaHook.php @@ -8,14 +8,18 @@ namespace Ibexa\Contracts\Test\Core\Bootstrapper; -use Ibexa\Tests\Core\Repository\LegacySchemaImporter; +use Doctrine\DBAL\Connection; +use Ibexa\Contracts\DoctrineSchema\Builder\SchemaBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; /** - * Imports the legacy raw-SQL schema files exposed by {@see SchemaFilesProviderInterface}. + * Installs the database schema built by {@see SchemaBuilderInterface::buildSchema()}, i.e. from + * every registered bundle's own SchemaBuilderEvent subscriber. Requires `DoctrineSchemaBundle` and + * `IbexaRepositoryInstallerBundle`; removed from the container when either is missing, by + * {@see \Ibexa\Bundle\Test\Core\DependencyInjection\CompilerPass\RemoveUnsatisfiableHooksPass}. * - * Enabled by default; pass `[self::OPTION_LOAD_SCHEMA => false]` as this hook's own options (keyed - * by its own service id in the bootstrap options array) to skip it. + * Enabled by default; pass `[self::OPTION_LOAD_SCHEMA => false]` as this hook's own options to + * skip it. */ final class DatabaseSchemaHook implements HookInterface { @@ -27,16 +31,14 @@ final class DatabaseSchemaHook implements HookInterface public const OPTION_LOAD_SCHEMA = 'load_schema'; - private SchemaFilesProviderInterface $provider; + private SchemaBuilderInterface $schemaBuilder; - private LegacySchemaImporter $schemaImporter; + private Connection $connection; - public function __construct( - SchemaFilesProviderInterface $provider, - LegacySchemaImporter $schemaImporter - ) { - $this->provider = $provider; - $this->schemaImporter = $schemaImporter; + public function __construct(SchemaBuilderInterface $schemaBuilder, Connection $connection) + { + $this->schemaBuilder = $schemaBuilder; + $this->connection = $connection; } public function configureOptions(OptionsResolver $resolver): void @@ -52,8 +54,11 @@ public function __invoke(array $options): void return; } - foreach ($this->provider->getSchemaFiles() ?? [] as $file) { - $this->schemaImporter->importSchema($file); + $schema = $this->schemaBuilder->buildSchema(); + $platform = $this->connection->getDatabasePlatform(); + + foreach ($schema->toSql($platform) as $sql) { + $this->connection->executeStatement($sql); } } } diff --git a/src/contracts/Bootstrapper/SchemaFilesProviderInterface.php b/src/contracts/Bootstrapper/SchemaFilesProviderInterface.php deleted file mode 100644 index 7f7a6f7..0000000 --- a/src/contracts/Bootstrapper/SchemaFilesProviderInterface.php +++ /dev/null @@ -1,29 +0,0 @@ -|null null if this provider has nothing to contribute — the caller - * should fall back to another provider, not treat this the same - * as a deliberately empty list - */ - public function getSchemaFiles(): ?iterable; -} diff --git a/src/contracts/IbexaTestKernel.php b/src/contracts/IbexaTestKernel.php index bc91560..603645e 100644 --- a/src/contracts/IbexaTestKernel.php +++ b/src/contracts/IbexaTestKernel.php @@ -12,7 +12,9 @@ use Doctrine\DBAL\Connection; use FOS\JsRoutingBundle\FOSJsRoutingBundle; use Ibexa\Bundle\Core\IbexaCoreBundle; +use Ibexa\Bundle\DoctrineSchema\DoctrineSchemaBundle; use Ibexa\Bundle\LegacySearchEngine\IbexaLegacySearchEngineBundle; +use Ibexa\Bundle\RepositoryInstaller\IbexaRepositoryInstallerBundle; use Ibexa\Contracts\Core\Persistence\TransactionHandler; use Ibexa\Contracts\Core\Repository; use Ibexa\Contracts\Core\Test\IbexaTestKernelInterface; @@ -61,6 +63,15 @@ * {@see \Ibexa\Contracts\Test\Core\Bootstrapper\HooksExecutorInterface} and the built-in hooks it * registers) must `yield new IbexaTestCoreBundle();` from its own registerBundles() override. * + * It does register {@see \Ibexa\Bundle\DoctrineSchema\DoctrineSchemaBundle} and + * {@see \Ibexa\Bundle\RepositoryInstaller\IbexaRepositoryInstallerBundle}, which together make + * SchemaBuilderEvent usable: the former provides + * {@see \Ibexa\Contracts\DoctrineSchema\Builder\SchemaBuilderInterface}, the latter carries core's + * own BuildSchemaSubscriber (it lives there rather than in IbexaCoreBundle), without which the + * event yields every other package's tables but none of core's. They are a pair — + * IbexaRepositoryInstallerBundle::build() throws if DoctrineSchemaBundle is absent. A subclass must + * therefore NOT yield either of them again; Symfony rejects two bundles with the same name. + * * ## Exposing your services * * To add services to the test Kernel and make them available in tests via IbexaKernelTestCase::getServiceByClassName(), @@ -144,6 +155,8 @@ public function registerBundles(): iterable { yield new SecurityBundle(); yield new IbexaCoreBundle(); + yield new DoctrineSchemaBundle(); + yield new IbexaRepositoryInstallerBundle(); yield new IbexaLegacySearchEngineBundle(); yield new JMSTranslationBundle(); yield new FOSJsRoutingBundle(); diff --git a/src/lib/Bootstrapper/SchemaFilesKernelMethodProvider.php b/src/lib/Bootstrapper/SchemaFilesKernelMethodProvider.php deleted file mode 100644 index 252b25c..0000000 --- a/src/lib/Bootstrapper/SchemaFilesKernelMethodProvider.php +++ /dev/null @@ -1,35 +0,0 @@ -kernel = $kernel; - } - - public function getSchemaFiles(): ?iterable - { - if (!$this->kernel instanceof IbexaTestKernelInterface) { - return null; - } - - return $this->kernel->getSchemaFiles(); - } -} diff --git a/src/lib/Bootstrapper/SchemaFilesParameterProvider.php b/src/lib/Bootstrapper/SchemaFilesParameterProvider.php deleted file mode 100644 index 172dcef..0000000 --- a/src/lib/Bootstrapper/SchemaFilesParameterProvider.php +++ /dev/null @@ -1,36 +0,0 @@ -|null - */ - private ?iterable $schemaFiles; - - /** - * @param iterable|null $schemaFiles - */ - public function __construct(?iterable $schemaFiles) - { - $this->schemaFiles = $schemaFiles; - } - - public function getSchemaFiles(): ?iterable - { - return $this->schemaFiles; - } -} diff --git a/src/lib/Bootstrapper/SchemaFilesProviderChain.php b/src/lib/Bootstrapper/SchemaFilesProviderChain.php deleted file mode 100644 index e187cbd..0000000 --- a/src/lib/Bootstrapper/SchemaFilesProviderChain.php +++ /dev/null @@ -1,45 +0,0 @@ - - */ - private iterable $providers; - - /** - * @param iterable $providers priority-sorted - */ - public function __construct(iterable $providers) - { - $this->providers = $providers; - } - - public function getSchemaFiles(): ?iterable - { - foreach ($this->providers as $provider) { - $schemaFiles = $provider->getSchemaFiles(); - if ($schemaFiles !== null) { - return $schemaFiles; - } - } - - return null; - } -} diff --git a/tests/lib/Bootstrapper/SchemaFilesProviderChainTest.php b/tests/lib/Bootstrapper/SchemaFilesProviderChainTest.php deleted file mode 100644 index f94fb3f..0000000 --- a/tests/lib/Bootstrapper/SchemaFilesProviderChainTest.php +++ /dev/null @@ -1,85 +0,0 @@ -providerReturning(null), - $this->providerReturning(['a.sql']), - ]); - - self::assertSame(['a.sql'], $chain->getSchemaFiles()); - } - - public function testStopsAtFirstNonNullResultEvenIfEmpty(): void - { - $chain = new SchemaFilesProviderChain([ - $this->providerReturning([]), - $this->providerReturning(['never-reached.sql']), - ]); - - self::assertSame([], $chain->getSchemaFiles()); - } - - public function testTriesProvidersInTheGivenOrder(): void - { - $chain = new SchemaFilesProviderChain([ - $this->providerReturning(['first.sql']), - $this->providerReturning(['second.sql']), - ]); - - self::assertSame(['first.sql'], $chain->getSchemaFiles()); - } - - public function testReturnsNullWhenNoProviderHasAnAnswer(): void - { - $chain = new SchemaFilesProviderChain([ - $this->providerReturning(null), - $this->providerReturning(null), - ]); - - self::assertNull($chain->getSchemaFiles()); - } - - /** - * @param iterable|null $schemaFiles - */ - private function providerReturning(?iterable $schemaFiles): SchemaFilesProviderInterface - { - return new class($schemaFiles) implements SchemaFilesProviderInterface { - /** - * @var iterable|null - */ - private ?iterable $schemaFiles; - - /** - * @param iterable|null $schemaFiles - */ - public function __construct(?iterable $schemaFiles) - { - $this->schemaFiles = $schemaFiles; - } - - public function getSchemaFiles(): ?iterable - { - return $this->schemaFiles; - } - }; - } -} From 9d20086cbfb2ba47644208e5c6ff7f8afd091538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Thu, 10 Sep 2026 14:06:25 +0200 Subject: [PATCH 2/3] IBX-12530: Allowed Bootstrapper to run against any KernelInterface, not just IbexaTestKernel (#44) Let Bootstrapper run against any KernelInterface, not just IbexaTestKernel KernelProvider asserted an IbexaTestKernel subclass and DatabasePreparer/Bootstrapper echoed that in their signatures, yet none of those bodies touch anything beyond KernelInterface: an Application to run console commands through, a container, and shutdown(). Relaxed all of them, so a package testing its own bundle in isolation can bring its own kernel. FixtureKernelMethodProvider already worked this way, duck-typing getFixtures() off a plain KernelInterface. That alone wasn't enough: FixtureHook autowires FixtureImporter (registered by IbexaTestKernel::loadServices()) and both purge hooks reference "ibexa.spi.search" (registered by IbexaCoreBundle), so a minimal kernel failed container compilation just for registering IbexaTestCoreBundle. Added them to RemoveUnsatisfiableHooksPass, which already drops DatabaseSchemaHook the same way -- one mechanism for every built-in hook rather than a special case per hook. --- .../RemoveUnsatisfiableHooksPass.php | 7 ++++ src/contracts/Bootstrapper/Bootstrapper.php | 4 +- .../Bootstrapper/DatabasePreparer.php | 4 +- .../DatabasePreparerInterface.php | 4 +- src/contracts/Bootstrapper/KernelProvider.php | 10 ++--- .../Bootstrapper/KernelProviderInterface.php | 8 ++-- .../Bootstrapper/KernelProviderTest.php | 38 ++++++++++++++++++- 7 files changed, 58 insertions(+), 17 deletions(-) diff --git a/src/bundle/DependencyInjection/CompilerPass/RemoveUnsatisfiableHooksPass.php b/src/bundle/DependencyInjection/CompilerPass/RemoveUnsatisfiableHooksPass.php index e76fd70..1c4f0d8 100644 --- a/src/bundle/DependencyInjection/CompilerPass/RemoveUnsatisfiableHooksPass.php +++ b/src/bundle/DependencyInjection/CompilerPass/RemoveUnsatisfiableHooksPass.php @@ -9,8 +9,12 @@ namespace Ibexa\Bundle\Test\Core\DependencyInjection\CompilerPass; use Ibexa\Bundle\RepositoryInstaller\Event\Subscriber\BuildSchemaSubscriber; +use Ibexa\Contracts\Core\Test\Persistence\Fixture\FixtureImporter; use Ibexa\Contracts\DoctrineSchema\Builder\SchemaBuilderInterface; use Ibexa\Contracts\Test\Core\Bootstrapper\DatabaseSchemaHook; +use Ibexa\Contracts\Test\Core\Bootstrapper\FixtureHook; +use Ibexa\Contracts\Test\Core\Bootstrapper\PurgeIndexAfterFixturesHook; +use Ibexa\Contracts\Test\Core\Bootstrapper\PurgeSearchIndexHook; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -30,6 +34,9 @@ final class RemoveUnsatisfiableHooksPass implements CompilerPassInterface // tables but none of core's BuildSchemaSubscriber::class, ], + FixtureHook::class => [FixtureImporter::class], + PurgeSearchIndexHook::class => ['ibexa.spi.search'], + PurgeIndexAfterFixturesHook::class => ['ibexa.spi.search'], ]; public function process(ContainerBuilder $container): void diff --git a/src/contracts/Bootstrapper/Bootstrapper.php b/src/contracts/Bootstrapper/Bootstrapper.php index f73705d..e6b3c13 100644 --- a/src/contracts/Bootstrapper/Bootstrapper.php +++ b/src/contracts/Bootstrapper/Bootstrapper.php @@ -8,10 +8,10 @@ namespace Ibexa\Contracts\Test\Core\Bootstrapper; -use Ibexa\Contracts\Test\Core\IbexaTestKernel; use LogicException; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -74,7 +74,7 @@ public function __construct( public function bootstrap( ?string $kernelClass = null, array $options = [] - ): IbexaTestKernel { + ): KernelInterface { $kernel = $this->kernelProvider->getKernel($kernelClass); $testContainer = self::getService($kernel->getContainer(), 'test.service_container', ContainerInterface::class); diff --git a/src/contracts/Bootstrapper/DatabasePreparer.php b/src/contracts/Bootstrapper/DatabasePreparer.php index 6c70a61..0a1ceb8 100644 --- a/src/contracts/Bootstrapper/DatabasePreparer.php +++ b/src/contracts/Bootstrapper/DatabasePreparer.php @@ -8,8 +8,8 @@ namespace Ibexa\Contracts\Test\Core\Bootstrapper; -use Ibexa\Contracts\Test\Core\IbexaTestKernel; use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Component\HttpKernel\KernelInterface; /** * @internal @@ -23,7 +23,7 @@ public function __construct() $this->consoleCommandRunner = new ConsoleCommandRunner(); } - public function prepareDatabase(IbexaTestKernel $kernel, bool $runSchemaUpdate): void + public function prepareDatabase(KernelInterface $kernel, bool $runSchemaUpdate): void { $application = new Application($kernel); $application->setAutoExit(false); diff --git a/src/contracts/Bootstrapper/DatabasePreparerInterface.php b/src/contracts/Bootstrapper/DatabasePreparerInterface.php index be1c269..2ffe0f0 100644 --- a/src/contracts/Bootstrapper/DatabasePreparerInterface.php +++ b/src/contracts/Bootstrapper/DatabasePreparerInterface.php @@ -8,7 +8,7 @@ namespace Ibexa\Contracts\Test\Core\Bootstrapper; -use Ibexa\Contracts\Test\Core\IbexaTestKernel; +use Symfony\Component\HttpKernel\KernelInterface; /** * @experimental @@ -22,5 +22,5 @@ interface DatabasePreparerInterface /** * @throws \Exception command failures propagate as-is */ - public function prepareDatabase(IbexaTestKernel $kernel, bool $runSchemaUpdate): void; + public function prepareDatabase(KernelInterface $kernel, bool $runSchemaUpdate): void; } diff --git a/src/contracts/Bootstrapper/KernelProvider.php b/src/contracts/Bootstrapper/KernelProvider.php index 78c6d7e..a32338a 100644 --- a/src/contracts/Bootstrapper/KernelProvider.php +++ b/src/contracts/Bootstrapper/KernelProvider.php @@ -8,22 +8,22 @@ namespace Ibexa\Contracts\Test\Core\Bootstrapper; -use Ibexa\Contracts\Test\Core\IbexaTestKernel; use LogicException; +use Symfony\Component\HttpKernel\KernelInterface; /** * @internal */ final class KernelProvider implements KernelProviderInterface { - public function getKernel(?string $kernelClass): IbexaTestKernel + public function getKernel(?string $kernelClass): KernelInterface { $kernelClass ??= $_ENV['KERNEL_CLASS'] ?? $_SERVER['KERNEL_CLASS'] ?? null; - if ($kernelClass === null || !is_a($kernelClass, IbexaTestKernel::class, true)) { + if ($kernelClass === null || !is_a($kernelClass, KernelInterface::class, true)) { throw new LogicException(sprintf( - 'The kernel class "%s" must be a subclass of "%s". Ensure that the KERNEL_CLASS environment variable is set to a valid test kernel class.', + 'The kernel class "%s" must implement "%s". Ensure that the KERNEL_CLASS environment variable is set to a valid test kernel class.', $kernelClass ?? 'null', - IbexaTestKernel::class, + KernelInterface::class, )); } diff --git a/src/contracts/Bootstrapper/KernelProviderInterface.php b/src/contracts/Bootstrapper/KernelProviderInterface.php index abf20a5..d692fe1 100644 --- a/src/contracts/Bootstrapper/KernelProviderInterface.php +++ b/src/contracts/Bootstrapper/KernelProviderInterface.php @@ -8,7 +8,7 @@ namespace Ibexa\Contracts\Test\Core\Bootstrapper; -use Ibexa\Contracts\Test\Core\IbexaTestKernel; +use Symfony\Component\HttpKernel\KernelInterface; /** * @experimental @@ -20,8 +20,8 @@ interface KernelProviderInterface { /** - * @throws \LogicException if $kernelClass (or its env/server fallback) isn't a valid - * IbexaTestKernel subclass + * @throws \LogicException if $kernelClass (or its env/server fallback) doesn't name a + * bootable {@see KernelInterface} implementation */ - public function getKernel(?string $kernelClass): IbexaTestKernel; + public function getKernel(?string $kernelClass): KernelInterface; } diff --git a/tests/contracts/Bootstrapper/KernelProviderTest.php b/tests/contracts/Bootstrapper/KernelProviderTest.php index 9eb1682..249cc84 100644 --- a/tests/contracts/Bootstrapper/KernelProviderTest.php +++ b/tests/contracts/Bootstrapper/KernelProviderTest.php @@ -13,6 +13,8 @@ use LogicException; use PHPUnit\Framework\TestCase; use stdClass; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\HttpKernel\Kernel; /** * @covers \Ibexa\Contracts\Test\Core\Bootstrapper\KernelProvider @@ -52,8 +54,8 @@ protected function tearDown(): void } /** - * @testWith [null, "The kernel class \"null\" must be a subclass of \"Ibexa\\Contracts\\Test\\Core\\IbexaTestKernel\". Ensure that the KERNEL_CLASS environment variable is set to a valid test kernel class."] - * ["stdClass", "The kernel class \"stdClass\" must be a subclass of \"Ibexa\\Contracts\\Test\\Core\\IbexaTestKernel\". Ensure that the KERNEL_CLASS environment variable is set to a valid test kernel class."] + * @testWith [null, "The kernel class \"null\" must implement \"Symfony\\Component\\HttpKernel\\KernelInterface\". Ensure that the KERNEL_CLASS environment variable is set to a valid test kernel class."] + * ["stdClass", "The kernel class \"stdClass\" must implement \"Symfony\\Component\\HttpKernel\\KernelInterface\". Ensure that the KERNEL_CLASS environment variable is set to a valid test kernel class."] */ public function testThrowsWhenKernelClassIsInvalid(?string $kernelClass, string $exceptionMessage): void { @@ -91,6 +93,14 @@ public function testArgumentTakesPrecedenceOverEnvAndServerFallbacks(): void self::assertInstanceOf(NoopBootTestKernel::class, $kernel); } + public function testAcceptsAnyKernelInterfaceImplementation(): void + { + $kernel = (new KernelProvider())->getKernel(NoopBootPlainKernel::class); + + self::assertInstanceOf(NoopBootPlainKernel::class, $kernel); + self::assertTrue($kernel->didBoot); + } + public function testBootsTheReturnedKernel(): void { $kernel = (new KernelProvider())->getKernel(NoopBootTestKernel::class); @@ -100,6 +110,30 @@ public function testBootsTheReturnedKernel(): void } } +final class NoopBootPlainKernel extends Kernel +{ + public bool $didBoot = false; + + public function __construct() + { + parent::__construct('test', true); + } + + public function boot(): void + { + $this->didBoot = true; + } + + public function registerBundles(): iterable + { + return []; + } + + public function registerContainerConfiguration(LoaderInterface $loader): void + { + } +} + final class NoopBootTestKernel extends IbexaTestKernel { public bool $didBoot = false; From d5e4366eccd7b8565be24dec10566e29254a7079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Fri, 11 Sep 2026 10:57:27 +0200 Subject: [PATCH 3/3] Removed accidentally committed .phpunit.result.cache A PHPUnit run artifact swept into the merge commit by a 'git add -A' during conflict resolution. It is not tracked on 4.6, 5.0 or 6.0 and should never have been added. --- .phpunit.result.cache | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .phpunit.result.cache diff --git a/.phpunit.result.cache b/.phpunit.result.cache deleted file mode 100644 index e28df0e..0000000 --- a/.phpunit.result.cache +++ /dev/null @@ -1 +0,0 @@ -{"version":1,"defects":[],"times":{"Ibexa\\Tests\\Bundle\\Test\\Core\\DependencyInjection\\IbexaTestCoreExtensionTest::testRegistersNoServicesOutsideTheTestEnvironment":0.007,"Ibexa\\Tests\\Bundle\\Test\\Core\\DependencyInjection\\IbexaTestCoreExtensionTest::testRegistersServicesInTheTestEnvironment":0.005,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\BootstrapperTest::testDelegatesKernelResolutionToTheProviderWithTheGivenKernelClass":0.008,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\BootstrapperTest::testReturnsTheKernelObtainedFromTheProvider":0,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\BootstrapperTest::testPreparesTheDatabaseWithTheDefaultSchemaUpdateOption":0,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\BootstrapperTest::testPreparesTheDatabaseWithSchemaUpdateDisabledWhenOptedOut":0,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\BootstrapperTest::testSkipsDatabasePreparationEntirelyWhenOptedOut":0,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\BootstrapperTest::testExecutesHooksWithTheFullyResolvedOptionsArray":0,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\BootstrapperTest::testShutsDownTheKernelByDefault":0,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\BootstrapperTest::testSkipsKernelShutdownWhenOptedOut":0,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\BootstrapperTest::testRejectsAnUnrecognizedOptions with data set #0":0,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\BootstrapperTest::testRejectsAnUnrecognizedOptions with data set #1":0,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\ConsoleCommandRunnerTest::testDoesNothingOnSuccess":0.004,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\ConsoleCommandRunnerTest::testThrowsNamingTheCommandAndExitCodeOnFailure":0,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\DefaultFixtureProviderTest::testYieldsTheBuiltInBaseFixture":0.001,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\DefaultSchemaFilesProviderTest::testYieldsSchemaFileResolvedThroughTheKernel":0.001,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\FixtureHookTest::testLoadFixturesOptionDefaultsToTrue":0.005,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\FixtureHookTest::testAsksProviderForFixturesWhenEnabled":0.009,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\FixtureHookTest::testDoesNotAskProviderForFixturesWhenDisabled":0,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\KernelProviderTest::testThrowsWhenKernelClassIsInvalid with data set #0":0,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\KernelProviderTest::testThrowsWhenKernelClassIsInvalid with data set #1":0,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\KernelProviderTest::testFallsBackToEnvKernelClassWhenArgumentIsNull":0,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\KernelProviderTest::testFallsBackToServerKernelClassWhenArgumentAndEnvAreNull":0,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\KernelProviderTest::testArgumentTakesPrecedenceOverEnvAndServerFallbacks":0,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\KernelProviderTest::testAcceptsAnyKernelInterfaceImplementation":0,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\KernelProviderTest::testBootsTheReturnedKernel":0,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\PurgeIndexAfterFixturesHookTest::testPurgeIndexOptionDefaultsToFalse":0.001,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\PurgeIndexAfterFixturesHookTest::testPurgesIndexWhenEnabled":0,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\PurgeSearchIndexHookTest::testPurgeIndexOptionDefaultsToFalse":0,"Ibexa\\Tests\\Contracts\\Test\\Core\\Bootstrapper\\PurgeSearchIndexHookTest::testPurgesIndexWhenEnabled":0,"Ibexa\\Tests\\Integration\\Test\\Core\\SampleTest::testCompilesSuccessfully":0.818,"Ibexa\\Tests\\Test\\Core\\Bootstrapper\\FixtureProviderChainTest::testSkipsProvidersReturningNull":0,"Ibexa\\Tests\\Test\\Core\\Bootstrapper\\FixtureProviderChainTest::testStopsAtFirstNonNullResultEvenIfEmpty":0,"Ibexa\\Tests\\Test\\Core\\Bootstrapper\\FixtureProviderChainTest::testTriesProvidersInTheGivenOrder":0,"Ibexa\\Tests\\Test\\Core\\Bootstrapper\\FixtureProviderChainTest::testReturnsNullWhenNoProviderHasAnAnswer":0,"Ibexa\\Tests\\Test\\Core\\Bootstrapper\\HooksExecutorTest::testConfigureOptionsRejectsUnrecognizedTopLevelKey":0,"Ibexa\\Tests\\Test\\Core\\Bootstrapper\\HooksExecutorTest::testConfigureOptionsResolvesEachHookSOwnOptions":0,"Ibexa\\Tests\\Test\\Core\\Bootstrapper\\HooksExecutorTest::testConfigureOptionsDefaultsAMissingHookKeyToItsOwnDefaults":0,"Ibexa\\Tests\\Test\\Core\\Bootstrapper\\HooksExecutorTest::testExecuteInvokesEachHookWithItsOwnSubArray":0,"Ibexa\\Tests\\Test\\Core\\Translation\\AbstractTranslationCaseTest::testEmptyChangeSetPasses":0,"Ibexa\\Tests\\Test\\Core\\Translation\\AbstractTranslationCaseTest::testChangeSetWithPendingMessagesFails with data set \"added message\"":0,"Ibexa\\Tests\\Test\\Core\\Translation\\AbstractTranslationCaseTest::testChangeSetWithPendingMessagesFails with data set \"deleted message\"":0,"Ibexa\\Tests\\Test\\Core\\Translation\\AbstractTranslationCaseTest::testChangeSetWithPendingMessagesFails with data set \"changed message\"":0}} \ No newline at end of file