From f632bab2eddcfdfd7e78cc341f63aca2491ebef6 Mon Sep 17 00:00:00 2001 From: Nicolas PHILIPPE Date: Sun, 1 Sep 2024 18:01:01 +0200 Subject: [PATCH] refactor: extract reset:migration tests in another testsuite --- .env | 10 +- .github/workflows/ci.yml | 68 +++++++++- .gitignore | 2 +- README.md | 30 +++-- composer.json | 26 ++-- phpunit-10.xml.dist | 9 +- phpunit.xml.dist | 7 +- src/Mongo/MongoSchemaResetter.php | 12 +- src/ORM/ResetDatabase/BaseOrmResetter.php | 51 +++++--- .../ResetDatabase/MigrateDatabaseResetter.php | 16 ++- .../ResetDatabase/SchemaDatabaseResetter.php | 23 ++-- src/Persistence/SymfonyCommandRunner.php | 49 -------- src/symfony_console.php | 40 ++++++ .../Version20240611065130.php | 44 ------- .../Entity/Contact/StandardContact.php | 2 +- .../EntityInAnotherSchema/Article.php | 8 +- .../MigrationTests/TestMigrationKernel.php | 119 ++++++++++++++++++ .../migration-configuration-transactional.php | 8 ++ .../configs/migration-configuration.php | 8 ++ tests/Fixture/TestKernel.php | 41 +++--- .../ResetDatabaseWithMigrationTest.php | 97 ++++++++++++++ tests/bootstrap-migrate.php | 48 +++++++ tests/bootstrap.php | 31 ----- 23 files changed, 517 insertions(+), 232 deletions(-) delete mode 100644 src/Persistence/SymfonyCommandRunner.php create mode 100644 src/symfony_console.php delete mode 100644 tests/Fixture/CustomMigrations/Version20240611065130.php rename tests/Fixture/{EdgeCases/Migrate/ORM => }/EntityInAnotherSchema/Article.php (68%) create mode 100644 tests/Fixture/MigrationTests/TestMigrationKernel.php create mode 100644 tests/Fixture/MigrationTests/configs/migration-configuration-transactional.php create mode 100644 tests/Fixture/MigrationTests/configs/migration-configuration.php create mode 100644 tests/Integration/Migration/ResetDatabaseWithMigrationTest.php create mode 100644 tests/bootstrap-migrate.php diff --git a/.env b/.env index d443158a..673ee095 100644 --- a/.env +++ b/.env @@ -1,6 +1,12 @@ +# DBMS can be changed by adding the following in .env.local: +#DATABASE_URL="postgresql://zenstruck:zenstruck@127.0.0.1:5433/zenstruck_foundry?serverVersion=15" +#DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" DATABASE_URL="mysql://root:1234@127.0.0.1:3307/foundry_test?serverVersion=5.7.42" + +# Mongo ca be disabled with the following in .env.local: +# MONGO_URL="" MONGO_URL="mongodb://127.0.0.1:27018/dbName?compressors=disabled&gssapiServiceName=mongodb" -DATABASE_RESET_MODE="schema" + USE_DAMA_DOCTRINE_TEST_BUNDLE="0" USE_FOUNDRY_PHPUNIT_EXTENSION="0" -PHPUNIT_VERSION="9" +PHPUNIT_VERSION="9" # allowed values: 9, 10, 11 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f994b621..3c318d6e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: jobs: tests: - name: P:${{ matrix.php }}, S:${{ matrix.symfony }}, D:${{ matrix.database }}, PU:${{ matrix.phpunit || 9 }}${{ matrix.deps == 'lowest' && ' (lowest)' || '' }}${{ matrix.without-dama == 0 && contains(matrix.database, 'sql') && ' (dama)' || '' }}${{ !contains(matrix.database, 'sql') && '' || matrix.use-migrate == 1 && ' (migrate)' || ' (schema)' }}${{ matrix.use-phpunit-extension == 1 && ' (phpunit extension)' || '' }} + name: P:${{ matrix.php }}, S:${{ matrix.symfony }}, D:${{ matrix.database }}, PU:${{ matrix.phpunit || 9 }}${{ matrix.deps == 'lowest' && ' (lowest)' || '' }}${{ matrix.without-dama == 0 && contains(matrix.database, 'sql') && ' (dama)' || '' }}${{ matrix.use-phpunit-extension == 1 && ' (phpunit extension)' || '' }} runs-on: ubuntu-latest strategy: fail-fast: false @@ -20,7 +20,6 @@ jobs: # default values: # deps: [ highest ] # without-dama: [ 0 ] - # use-migrate: [ 0 ] # use-phpunit-extension: [ 0 ] # phpunit: [ 9 ] @@ -35,7 +34,6 @@ jobs: - {php: 8.3, symfony: '*', database: sqlite, without-dama: 1} - {php: 8.3, symfony: '*', database: sqlite, without-dama: 1, deps: lowest} - {php: 8.3, symfony: '*', database: mysql, deps: lowest} - - {php: 8.3, symfony: '*', database: mysql, use-migrate: 1} - {php: 8.3, symfony: '*', database: mysql|mongo, phpunit: 10} - {php: 8.3, symfony: '*', database: mysql|mongo, phpunit: 11} - {php: 8.3, symfony: '*', database: mysql|mongo, use-phpunit-extension: 1, phpunit: 11} @@ -46,7 +44,6 @@ jobs: USE_DAMA_DOCTRINE_TEST_BUNDLE: ${{ matrix.without-dama == 0 && contains(matrix.database, 'sql') && 1 || 0 }} USE_FOUNDRY_PHPUNIT_EXTENSION: ${{ matrix.use-phpunit-extension || 0 }} PHPUNIT_VERSION: ${{ matrix.phpunit || 9 }} - DATABASE_RESET_MODE: ${{ matrix.use-migrate == 1 && 'migrate' || 'schema' }} services: postgres: image: ${{ contains(matrix.database, 'pgsql') && 'postgres:15' || '' }} @@ -92,6 +89,69 @@ jobs: run: ./phpunit shell: bash + test-reset-database-with-migration: + name: Test migration - D:${{ matrix.database }} ${{ matrix.use-dama == 1 && ' (dama)' || '' }} ${{ contains(matrix.with-migration-configuration-file, 'transactional') && '(configuration file transactional)' || contains(matrix.with-migration-configuration-file, 'configuration') && '(configuration file)' || '' }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + database: [ mysql, pgsql, sqlite ] + use-dama: [ 0, 1 ] + with-migration-configuration-file: + - '' + - 'tests/Fixture/MigrationTests/configs/migration-configuration.php' + - 'tests/Fixture/MigrationTests/configs/migration-configuration-transactional.php' + exclude: + # there is currently a bug with MySQL and transactional migrations + - database: mysql + with-migration-configuration-file: 'tests/Fixture/MigrationTests/configs/migration-configuration-transactional.php' + env: + DATABASE_URL: ${{ contains(matrix.database, 'mysql') && 'mysql://root:root@localhost:3306/foundry?serverVersion=5.7.42' || contains(matrix.database, 'pgsql') && 'postgresql://root:root@localhost:5432/foundry?serverVersion=15' || 'sqlite:///%kernel.project_dir%/var/data.db' }} + MONGO_URL: '' + USE_DAMA_DOCTRINE_TEST_BUNDLE: ${{ matrix.use-dama == 1 && 1 || 0 }} + WITH_MIGRATION_CONFIGURATION_FILE: ${{ matrix.with-migration-configuration-file }} + PHPUNIT_VERSION: 9 + services: + postgres: + image: ${{ contains(matrix.database, 'pgsql') && 'postgres:15' || '' }} + env: + POSTGRES_USER: root + POSTGRES_PASSWORD: root + POSTGRES_DB: foundry + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.3 + coverage: none + tools: flex + + - name: Install dependencies + uses: ramsey/composer-install@v2 + with: + dependency-versions: highest + composer-options: --prefer-dist + env: + SYMFONY_REQUIRE: 7.1.* + + - name: Set up MySQL + if: contains(matrix.database, 'mysql') + run: sudo /etc/init.d/mysql start + + - name: Test + run: ./phpunit --testsuite migrate --bootstrap tests/bootstrap-migrate.php + shell: bash + code-coverage: name: Code Coverage runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index ee9e61fd..6d5aba94 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,5 @@ /docker/.makefile /.env.local /docker-compose.override.yaml -/tests/Fixture/Migrations/ +/tests/Fixture/MigrationTests/Migrations/ /tests/Fixture/Maker/tmp/ diff --git a/README.md b/README.md index 4472e279..ba954bc3 100644 --- a/README.md +++ b/README.md @@ -47,20 +47,15 @@ $ docker compose up --detach # install dependencies $ composer update -# run test suite with all available permutations -$ composer test - -# run only one permutation +# run main testsuite (with "schema" reset database strategy) +$ composer test-schema +# or $ ./phpunit -# run test suite with dama/doctrine-test-bundle -$ USE_DAMA_DOCTRINE_TEST_BUNDLE=1 vendor/bin/phpunit - -# run test suite with postgreSQL instead of MySQL -$ DATABASE_URL="postgresql://zenstruck:zenstruck@127.0.0.1:5433/zenstruck_foundry?serverVersion=15" vendor/bin/phpunit - -# run test suite with another PHPUnit version -$ PHPUNIT_VERSION=10 vendor/bin/phpunit +# run "migrate" testsuite (with "migrate" reset database strategy) +$ composer test-migrate +# or +$ ./phpunit --testsuite migrate --bootstrap tests/bootstrap-migrate.php ``` ### Overriding the default configuration @@ -69,11 +64,20 @@ You can override default environment variables by creating a `.env.local` file, ```bash # .env.local -DATABASE_URL="postgresql://zenstruck:zenstruck@127.0.0.1:5433/zenstruck_foundry?serverVersion=15" # enables postgreSQL instead of MySQL + +# change the database to postgreSQL... +DATABASE_URL="postgresql://zenstruck:zenstruck@127.0.0.1:5433/zenstruck_foundry?serverVersion=15" +# ...or to SQLite +DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" + MONGO_URL="" # disables Mongo USE_DAMA_DOCTRINE_TEST_BUNDLE="1" # enables dama/doctrine-test-bundle PHPUNIT_VERSION="11" # possible values: 9, 10, 11, 11.4 +# test reset database with configuration migration, +# only relevant for "migrate" testsuite +WITH_MIGRATION_CONFIGURATION_FILE="tests/Fixture/MigrationTests/configs/migration-configuration.php" + # run test suite with postgreSQL $ vendor/bin/phpunit ``` diff --git a/composer.json b/composer.json index aec2e99e..3a6b8ad5 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,12 @@ "Zenstruck\\Foundry\\": "src/", "Zenstruck\\Foundry\\Psalm\\": "utils/psalm" }, - "files": ["src/functions.php", "src/Persistence/functions.php", "src/phpunit_helper.php"] + "files": [ + "src/functions.php", + "src/Persistence/functions.php", + "src/phpunit_helper.php", + "src/symfony_console.php" + ] }, "autoload-dev": { "psr-4": { @@ -77,22 +82,15 @@ }, "scripts": { "test": [ - "@test-schema-no-dama", - "@test-migrate-no-dama", - "@test-schema-dama", - "@test-migrate-dama" + "@test-schema", + "@test-migrate" ], - "test-schema-no-dama": "DATABASE_RESET_MODE=schema USE_DAMA_DOCTRINE_TEST_BUNDLE=0 ./phpunit", - "test-migrate-no-dama": "DATABASE_RESET_MODE=migrate USE_DAMA_DOCTRINE_TEST_BUNDLE=0 ./phpunit", - "test-schema-dama": "DATABASE_RESET_MODE=schema USE_DAMA_DOCTRINE_TEST_BUNDLE=1 ./phpunit", - "test-migrate-dama": "DATABASE_RESET_MODE=migrate USE_DAMA_DOCTRINE_TEST_BUNDLE=1 ./phpunit" + "test-schema": "./phpunit", + "test-migrate": "./phpunit --testsuite migrate --bootstrap tests/bootstrap-migrate.php" }, "scripts-descriptions": { - "test": "Run all test permutations", - "test-schema-no-dama": "Test with schema reset (no dama/doctrine-test-bundle)", - "test-migrate-no-dama": "Test with migrations reset (no dama/doctrine-test-bundle)", - "test-schema-dama": "Test with schema reset and dama/doctrine-test-bundle", - "test-migrate-dama": "Test with migrations reset and dama/doctrine-test-bundle" + "test-schema": "Test with schema reset", + "test-migrate": "Test with migrations reset" }, "minimum-stability": "dev", "prefer-stable": true diff --git a/phpunit-10.xml.dist b/phpunit-10.xml.dist index 21bfa9c4..b9758154 100644 --- a/phpunit-10.xml.dist +++ b/phpunit-10.xml.dist @@ -6,7 +6,8 @@ colors="true" failOnRisky="true" failOnWarning="true" - cacheDirectory=".phpunit.cache"> + cacheDirectory=".phpunit.cache" + defaultTestSuite="main"> @@ -14,8 +15,12 @@ - + tests + tests/Integration/Migration/ResetDatabaseWithMigrationTest.php + + + tests/Integration/Migration/ResetDatabaseWithMigrationTest.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4c12dd39..c6b9f26c 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -7,6 +7,7 @@ colors="true" failOnRisky="true" failOnWarning="true" + defaultTestSuite="main" > @@ -16,8 +17,12 @@ - + tests + tests/Integration/Migration/ResetDatabaseWithMigrationTest.php + + + tests/Integration/Migration/ResetDatabaseWithMigrationTest.php diff --git a/src/Mongo/MongoSchemaResetter.php b/src/Mongo/MongoSchemaResetter.php index 555eb553..ad40159f 100644 --- a/src/Mongo/MongoSchemaResetter.php +++ b/src/Mongo/MongoSchemaResetter.php @@ -14,7 +14,9 @@ namespace Zenstruck\Foundry\Mongo; use Symfony\Component\HttpKernel\KernelInterface; -use Zenstruck\Foundry\Persistence\SymfonyCommandRunner; + +use function Zenstruck\Foundry\application; +use function Zenstruck\Foundry\runCommand; /** * @internal @@ -22,8 +24,6 @@ */ final class MongoSchemaResetter implements MongoResetter { - use SymfonyCommandRunner; - /** * @param list $managers */ @@ -33,15 +33,15 @@ public function __construct(private array $managers) public function resetBeforeEachTest(KernelInterface $kernel): void { - $application = self::application($kernel); + $application = application($kernel); foreach ($this->managers as $manager) { try { - self::runCommand($application, 'doctrine:mongodb:schema:drop', ['--dm' => $manager]); + runCommand($application, "doctrine:mongodb:schema:drop --dm={$manager}"); } catch (\Exception) { } - self::runCommand($application, 'doctrine:mongodb:schema:create', ['--dm' => $manager]); + runCommand($application, "doctrine:mongodb:schema:create --dm={$manager}"); } } } diff --git a/src/ORM/ResetDatabase/BaseOrmResetter.php b/src/ORM/ResetDatabase/BaseOrmResetter.php index 4044fe28..8f2bf840 100644 --- a/src/ORM/ResetDatabase/BaseOrmResetter.php +++ b/src/ORM/ResetDatabase/BaseOrmResetter.php @@ -9,15 +9,20 @@ use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SQLitePlatform; use Symfony\Bundle\FrameworkBundle\Console\Application; -use Zenstruck\Foundry\Persistence\SymfonyCommandRunner; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\HttpKernel\KernelInterface; + +use Zenstruck\Foundry\ORM\DoctrineOrmVersionGuesser; + +use function Zenstruck\Foundry\runCommand; /** * @author Nicolas PHILIPPE * @internal */ -abstract class BaseOrmResetter +abstract class BaseOrmResetter implements OrmResetter { - use SymfonyCommandRunner; + private static bool $inFirstTest = true; /** * @param list $managers @@ -30,6 +35,19 @@ public function __construct( ) { } + final public function resetBeforeEachTest(KernelInterface $kernel): void + { + if (self::$inFirstTest) { + self::$inFirstTest = false; + + return; + } + + $this->doResetBeforeEachTest($kernel); + } + + abstract protected function doResetBeforeEachTest(KernelInterface $kernel): void; + final protected function dropAndResetDatabase(Application $application): void { foreach ($this->connections as $connectionName) { @@ -39,29 +57,26 @@ final protected function dropAndResetDatabase(Application $application): void if ($databasePlatform instanceof SQLitePlatform) { // we don't need to create the sqlite database - it's created when the schema is created + // let's only drop the .db file + + $dbPath = $connection->getParams()['path'] ?? null; + $fs = new Filesystem(); + if (DoctrineOrmVersionGuesser::isOrmV3() && $dbPath && $fs->exists($dbPath)) { + (new Filesystem())->remove($dbPath); + } + continue; } if ($databasePlatform instanceof PostgreSQLPlatform) { // let's drop all connections to the database to be able to drop it - self::runCommand( - $application, - 'dbal:run-sql', - [ - '--connection' => $connectionName, - 'sql' => 'SELECT pid, pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = current_database() AND pid <> pg_backend_pid()', - ], - canFail: true, - ); + $sql = 'SELECT pid, pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = current_database() AND pid <> pg_backend_pid()'; + runCommand($application, "dbal:run-sql --connection={$connectionName} '{$sql}'", canFail: true); } - self::runCommand( - $application, - 'doctrine:database:drop', - ['--connection' => $connectionName, '--force' => true, '--if-exists' => true] - ); + runCommand($application, "doctrine:database:drop --connection={$connectionName} --force --if-exists"); - self::runCommand($application, 'doctrine:database:create', ['--connection' => $connectionName]); + runCommand($application, "doctrine:database:create --connection={$connectionName}"); } } } diff --git a/src/ORM/ResetDatabase/MigrateDatabaseResetter.php b/src/ORM/ResetDatabase/MigrateDatabaseResetter.php index fd99b57b..52a15698 100644 --- a/src/ORM/ResetDatabase/MigrateDatabaseResetter.php +++ b/src/ORM/ResetDatabase/MigrateDatabaseResetter.php @@ -5,13 +5,17 @@ namespace Zenstruck\Foundry\ORM\ResetDatabase; use Doctrine\Bundle\DoctrineBundle\Registry; +use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\HttpKernel\KernelInterface; +use function Zenstruck\Foundry\application; +use function Zenstruck\Foundry\runCommand; + /** * @internal * @author Nicolas PHILIPPE */ -final class MigrateDatabaseResetter extends BaseOrmResetter implements OrmResetter +final class MigrateDatabaseResetter extends BaseOrmResetter { /** * @param list $configurations @@ -26,30 +30,30 @@ public function __construct( parent::__construct($registry, $managers, $connections); } - final public function resetBeforeEachTest(KernelInterface $kernel): void + final public function resetBeforeFirstTest(KernelInterface $kernel): void { $this->resetWithMigration($kernel); } - public function resetBeforeFirstTest(KernelInterface $kernel): void + public function doResetBeforeEachTest(KernelInterface $kernel): void { $this->resetWithMigration($kernel); } private function resetWithMigration(KernelInterface $kernel): void { - $application = self::application($kernel); + $application = application($kernel); $this->dropAndResetDatabase($application); if (!$this->configurations) { - self::runCommand($application, 'doctrine:migrations:migrate'); + runCommand($application, 'doctrine:migrations:migrate'); return; } foreach ($this->configurations as $configuration) { - self::runCommand($application, 'doctrine:migrations:migrate', ['--configuration' => $configuration]); + runCommand($application, "doctrine:migrations:migrate --configuration={$configuration}"); } } } diff --git a/src/ORM/ResetDatabase/SchemaDatabaseResetter.php b/src/ORM/ResetDatabase/SchemaDatabaseResetter.php index 40dc765e..12342b43 100644 --- a/src/ORM/ResetDatabase/SchemaDatabaseResetter.php +++ b/src/ORM/ResetDatabase/SchemaDatabaseResetter.php @@ -16,23 +16,26 @@ use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\HttpKernel\KernelInterface; +use function Zenstruck\Foundry\application; +use function Zenstruck\Foundry\runCommand; + /** * @internal * @author Nicolas PHILIPPE */ -final class SchemaDatabaseResetter extends BaseOrmResetter implements OrmResetter +final class SchemaDatabaseResetter extends BaseOrmResetter { public function resetBeforeFirstTest(KernelInterface $kernel): void { - $application = self::application($kernel); + $application = application($kernel); $this->dropAndResetDatabase($application); $this->createSchema($application); } - public function resetBeforeEachTest(KernelInterface $kernel): void + public function doResetBeforeEachTest(KernelInterface $kernel): void { - $application = self::application($kernel); + $application = application($kernel); $this->dropSchema($application); $this->createSchema($application); @@ -41,22 +44,14 @@ public function resetBeforeEachTest(KernelInterface $kernel): void private function createSchema(Application $application): void { foreach ($this->managers as $manager) { - self::runCommand( - $application, - 'doctrine:schema:update', - ['--em' => $manager, '--force' => true] - ); + runCommand($application, "doctrine:schema:update --em={$manager} --force -v"); } } private function dropSchema(Application $application): void { foreach ($this->managers as $manager) { - self::runCommand( - $application, - 'doctrine:schema:drop', - ['--em' => $manager, '--force' => true, '--full-database' => true] - ); + runCommand($application, "doctrine:schema:drop --em={$manager} --force --full-database"); } } } diff --git a/src/Persistence/SymfonyCommandRunner.php b/src/Persistence/SymfonyCommandRunner.php deleted file mode 100644 index 76c61cf6..00000000 --- a/src/Persistence/SymfonyCommandRunner.php +++ /dev/null @@ -1,49 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Zenstruck\Foundry\Persistence; - -use Symfony\Bundle\FrameworkBundle\Console\Application; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\BufferedOutput; -use Symfony\Component\HttpKernel\KernelInterface; - -/** - * @internal - * @author Nicolas PHILIPPE - */ -trait SymfonyCommandRunner -{ - /** - * @param array $parameters - */ - final protected static function runCommand(Application $application, string $command, array $parameters = [], bool $canFail = false): void - { - $exit = $application->run( - new ArrayInput(\array_merge(['command' => $command], $parameters + ['--no-interaction' => true])), - $output = new BufferedOutput() - ); - - if (0 !== $exit && !$canFail) { - throw new \RuntimeException(\sprintf('Error running "%s": %s', $command, $output->fetch())); - } - } - - final protected static function application(KernelInterface $kernel): Application - { - $application = new Application($kernel); - $application->setAutoExit(false); - - return $application; - } -} diff --git a/src/symfony_console.php b/src/symfony_console.php new file mode 100644 index 00000000..4ace905c --- /dev/null +++ b/src/symfony_console.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Zenstruck\Foundry; + +use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * @internal + */ +function runCommand(Application $application, string $command, bool $canFail = false): void +{ + $exit = $application->run(new StringInput("$command --no-interaction"), $output = new BufferedOutput()); + + if (0 !== $exit && !$canFail) { + throw new \RuntimeException(\sprintf('Error running "%s": %s', $command, $output->fetch())); + } +} + +/** + * @internal + */ +function application(KernelInterface $kernel): Application +{ + $application = new Application($kernel); + $application->setAutoExit(false); + + return $application; +} diff --git a/tests/Fixture/CustomMigrations/Version20240611065130.php b/tests/Fixture/CustomMigrations/Version20240611065130.php deleted file mode 100644 index 9d51237f..00000000 --- a/tests/Fixture/CustomMigrations/Version20240611065130.php +++ /dev/null @@ -1,44 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -// to "Migrations" directory on boot (cf. bootstrap.php) - -namespace Zenstruck\Foundry\Tests\Fixture\Migrations; - -use Doctrine\DBAL\Schema\Schema; -use Doctrine\Migrations\AbstractMigration; -use Zenstruck\Foundry\Tests\Fixture\EdgeCases\Migrate\ORM\EntityInAnotherSchema\Article; - -/** - * Create custom "cms" schema ({@see Article}) to ensure "migrate" mode is still working with multiple schemas. - * Note: the doctrine:migrations:diff command doesn't seem able to add this custom "CREATE SCHEMA" automatically. - * - * @see https://github.com/zenstruck/foundry/issues/618 - */ -final class Version20240611065130 extends AbstractMigration -{ - public function getDescription(): string - { - return 'Create custom "cms" schema.'; - } - - public function up(Schema $schema): void - { - $this->addSql('CREATE SCHEMA cms'); - } - - public function down(Schema $schema): void - { - $this->addSql('DROP SCHEMA cms'); - } -} diff --git a/tests/Fixture/Entity/Contact/StandardContact.php b/tests/Fixture/Entity/Contact/StandardContact.php index cd3f613a..98fb9132 100644 --- a/tests/Fixture/Entity/Contact/StandardContact.php +++ b/tests/Fixture/Entity/Contact/StandardContact.php @@ -44,7 +44,7 @@ class StandardContact extends Contact #[ORM\JoinTable(name: 'category_tag_standard_secondary')] protected Collection $secondaryTags; - #[ORM\OneToOne(targetEntity: StandardAddress::class)] + #[ORM\OneToOne(targetEntity: StandardAddress::class, inversedBy: 'contact')] #[ORM\JoinColumn(nullable: false)] protected Address $address; } diff --git a/tests/Fixture/EdgeCases/Migrate/ORM/EntityInAnotherSchema/Article.php b/tests/Fixture/EntityInAnotherSchema/Article.php similarity index 68% rename from tests/Fixture/EdgeCases/Migrate/ORM/EntityInAnotherSchema/Article.php rename to tests/Fixture/EntityInAnotherSchema/Article.php index fba361ef..e95aa52b 100644 --- a/tests/Fixture/EdgeCases/Migrate/ORM/EntityInAnotherSchema/Article.php +++ b/tests/Fixture/EntityInAnotherSchema/Article.php @@ -11,11 +11,17 @@ * file that was distributed with this source code. */ -namespace Zenstruck\Foundry\Tests\Fixture\EdgeCases\Migrate\ORM\EntityInAnotherSchema; +namespace Zenstruck\Foundry\Tests\Fixture\EntityInAnotherSchema; use Doctrine\ORM\Mapping as ORM; use Zenstruck\Foundry\Tests\Fixture\Model\Base; +/** + * Create custom "cms" schema ({@see Article}) to ensure "migrate" mode is still working with multiple schemas. + * Note: this entity is added to mapping only for PostgreSQ, as it is the only supported DBMS which handles multiple schemas. + * + * @see https://github.com/zenstruck/foundry/issues/618 + */ #[ORM\Entity] #[ORM\Table(name: 'article', schema: 'cms')] class Article extends Base diff --git a/tests/Fixture/MigrationTests/TestMigrationKernel.php b/tests/Fixture/MigrationTests/TestMigrationKernel.php new file mode 100644 index 00000000..a81d5e57 --- /dev/null +++ b/tests/Fixture/MigrationTests/TestMigrationKernel.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Zenstruck\Foundry\Tests\Fixture\MigrationTests; + +use DAMA\DoctrineTestBundle\DAMADoctrineTestBundle; +use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; +use Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle; +use Psr\Log\NullLogger; +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Kernel; +use Zenstruck\Foundry\ORM\ResetDatabase\ResetDatabaseMode; +use Zenstruck\Foundry\Tests\Fixture\Stories\GlobalInvokableService; +use Zenstruck\Foundry\Tests\Fixture\Stories\GlobalStory; +use Zenstruck\Foundry\ZenstruckFoundryBundle; + +/** + * @author Nicolas PHILIPPE + */ +final class TestMigrationKernel extends Kernel +{ + use MicroKernelTrait; + + public function registerBundles(): iterable + { + yield new FrameworkBundle(); + yield new DoctrineBundle(); + yield new DoctrineMigrationsBundle(); + + yield new ZenstruckFoundryBundle(); + + if (\getenv('USE_DAMA_DOCTRINE_TEST_BUNDLE')) { + yield new DAMADoctrineTestBundle(); + } + } + + protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader): void + { + $c->loadFromExtension('framework', [ + 'http_method_override' => false, + 'secret' => 'S3CRET', + 'router' => ['utf8' => true], + 'test' => true, + ]); + + $c->loadFromExtension('zenstruck_foundry', [ + 'global_state' => [ + GlobalStory::class, + GlobalInvokableService::class, + ], + 'orm' => [ + 'reset' => [ + 'mode' => ResetDatabaseMode::MIGRATE, + 'migrations' => [ + 'configurations' => ($configFile = \getenv('WITH_MIGRATION_CONFIGURATION_FILE')) ? [$configFile] : [] + ], + ], + ], + ]); + + if (!\getenv('WITH_MIGRATION_CONFIGURATION_FILE')) { + $c->loadFromExtension('doctrine_migrations', include __DIR__ . '/configs/migration-configuration.php'); + } + + $c->loadFromExtension('doctrine', [ + 'dbal' => ['url' => '%env(resolve:DATABASE_URL)%', 'use_savepoints' => true], + 'orm' => [ + 'auto_generate_proxy_classes' => true, + 'auto_mapping' => true, + 'mappings' => [ + 'Entity' => [ + 'is_bundle' => false, + 'type' => 'attribute', + 'dir' => '%kernel.project_dir%/tests/Fixture/Entity', + 'prefix' => 'Zenstruck\Foundry\Tests\Fixture\Entity', + 'alias' => 'Entity', + ], + 'Model' => [ + 'is_bundle' => false, + 'type' => 'attribute', + 'dir' => '%kernel.project_dir%/tests/Fixture/Model', + 'prefix' => 'Zenstruck\Foundry\Tests\Fixture\Model', + 'alias' => 'Model', + ], + + // postgres acts weirdly with multiple schemas + // @see https://github.com/doctrine/DoctrineBundle/issues/548 + ...(str_starts_with(\getenv('DATABASE_URL') ?: '', 'postgresql') + ? [ + 'EntityInAnotherSchema' => [ + 'is_bundle' => false, + 'type' => 'attribute', + 'dir' => '%kernel.project_dir%/tests/Fixture/EntityInAnotherSchema', + 'prefix' => 'Zenstruck\Foundry\Tests\Fixture\EntityInAnotherSchema', + 'alias' => 'Migrate', + ] + ] + : [] + ), + ], + 'controller_resolver' => ['auto_mapping' => false], + ], + ]); + + $c->register('logger', NullLogger::class); + $c->register(GlobalInvokableService::class); + } +} diff --git a/tests/Fixture/MigrationTests/configs/migration-configuration-transactional.php b/tests/Fixture/MigrationTests/configs/migration-configuration-transactional.php new file mode 100644 index 00000000..f404363d --- /dev/null +++ b/tests/Fixture/MigrationTests/configs/migration-configuration-transactional.php @@ -0,0 +1,8 @@ + [ + 'Zenstruck\\Foundry\\Tests\\Fixture\\MigrationTests\\Migrations' => dirname(__DIR__).'/Migrations', + ], + 'transactional' => true, +]; diff --git a/tests/Fixture/MigrationTests/configs/migration-configuration.php b/tests/Fixture/MigrationTests/configs/migration-configuration.php new file mode 100644 index 00000000..96533496 --- /dev/null +++ b/tests/Fixture/MigrationTests/configs/migration-configuration.php @@ -0,0 +1,8 @@ + [ + 'Zenstruck\\Foundry\\Tests\\Fixture\\MigrationTests\\Migrations' => dirname(__DIR__).'/Migrations', + ], + 'transactional' => false, +]; diff --git a/tests/Fixture/TestKernel.php b/tests/Fixture/TestKernel.php index 589e903e..66dfc1bb 100644 --- a/tests/Fixture/TestKernel.php +++ b/tests/Fixture/TestKernel.php @@ -13,7 +13,6 @@ use DAMA\DoctrineTestBundle\DAMADoctrineTestBundle; use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; -use Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle; use Doctrine\Bundle\MongoDBBundle\DoctrineMongoDBBundle; use Psr\Log\NullLogger; use Symfony\Bundle\FrameworkBundle\FrameworkBundle; @@ -45,7 +44,6 @@ public function registerBundles(): iterable if (\getenv('DATABASE_URL')) { yield new DoctrineBundle(); - yield new DoctrineMigrationsBundle(); } if (\getenv('MONGO_URL')) { @@ -75,7 +73,7 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load ], 'orm' => [ 'reset' => [ - 'mode' => \getenv('DATABASE_RESET_MODE') ?: ResetDatabaseMode::SCHEMA, + 'mode' => ResetDatabaseMode::SCHEMA, ], ], ]); @@ -101,30 +99,23 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load 'prefix' => 'Zenstruck\Foundry\Tests\Fixture\Model', 'alias' => 'Model', ], - ], - 'controller_resolver' => ['auto_mapping' => true], - ], - ]); - if (ResetDatabaseMode::MIGRATE->value === \getenv('DATABASE_RESET_MODE')) { - $c->loadFromExtension('doctrine', [ - 'orm' => [ - 'mappings' => [ - 'Migrate' => [ - 'is_bundle' => false, - 'type' => 'attribute', - 'dir' => '%kernel.project_dir%/tests/Fixture/EdgeCases/Migrate/ORM', - 'prefix' => 'Zenstruck\Foundry\Tests\Fixture\EdgeCases\Migrate\ORM', - 'alias' => 'Migrate', - ], - ], + // postgres acts weirdly with multiple schemas + // @see https://github.com/doctrine/DoctrineBundle/issues/548 + ...(str_starts_with(\getenv('DATABASE_URL'), 'postgresql') + ? [ + 'EntityInAnotherSchema' => [ + 'is_bundle' => false, + 'type' => 'attribute', + 'dir' => '%kernel.project_dir%/tests/Fixture/EntityInAnotherSchema', + 'prefix' => 'Zenstruck\Foundry\Tests\Fixture\EntityInAnotherSchema', + 'alias' => 'Migrate', + ] + ] + : [] + ), ], - ]); - } - - $c->loadFromExtension('doctrine_migrations', [ - 'migrations_paths' => [ - 'Zenstruck\\Foundry\\Tests\\Fixture\\Migrations' => '%kernel.project_dir%/tests/Fixture/Migrations', + 'controller_resolver' => ['auto_mapping' => false], ], ]); } diff --git a/tests/Integration/Migration/ResetDatabaseWithMigrationTest.php b/tests/Integration/Migration/ResetDatabaseWithMigrationTest.php new file mode 100644 index 00000000..cd1e1f92 --- /dev/null +++ b/tests/Integration/Migration/ResetDatabaseWithMigrationTest.php @@ -0,0 +1,97 @@ + + */ +final class ResetDatabaseWithMigrationTest extends KernelTestCase +{ + use Factories; + use ResetDatabase; + use RequiresORM; + + /** + * @test + */ + public function it_generates_valid_schema(): void + { + $application = new Application(self::bootKernel()); + $application->setAutoExit(false); + + $exit = $application->run( + new ArrayInput(['command' => 'doctrine:schema:validate', '-v' => true]), + $output = new BufferedOutput() + ); + + // The command actually fails, because of a bug in doctrine ORM 3! + // https://github.com/doctrine/migrations/issues/1406 + self::assertSame(2, $exit, \sprintf('Schema is not valid: %s', $commandOutput = $output->fetch())); + self::assertStringContainsString('1 schema diff(s) detected', $commandOutput); + self::assertStringContainsString('DROP TABLE doctrine_migration_versions', $commandOutput); + } + + /** + * @test + */ + public function it_can_store_object(): void + { + StandardContactFactory::assert()->count(0); + + StandardContactFactory::createOne(); + + StandardContactFactory::assert()->count(1); + } + + /** + * @test + * @depends it_can_store_object + */ + public function it_starts_from_fresh_db(): void + { + StandardContactFactory::assert()->count(0); + } + + /** + * @test + */ + public function global_objects_are_created(): void + { + repository(GlobalEntity::class)->assert()->count(2); + } + + /** + * @test + */ + public function can_create_object_in_another_schema(): void + { + if (!str_starts_with(\getenv('DATABASE_URL') ?: '', 'postgresql')) { + self::markTestSkipped('PostgreSQL needed.'); + } + + persist(Article::class, ['title' => 'Hello World!']); + repository(Article::class)->assert()->count(1); + } + + protected static function getKernelClass(): string + { + return TestMigrationKernel::class; + } +} diff --git a/tests/bootstrap-migrate.php b/tests/bootstrap-migrate.php new file mode 100644 index 00000000..b319d778 --- /dev/null +++ b/tests/bootstrap-migrate.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Dotenv\Dotenv; +use Symfony\Component\ErrorHandler\ErrorHandler; +use Symfony\Component\Filesystem\Filesystem; +use Zenstruck\Foundry\Tests\Fixture\MigrationTests\TestMigrationKernel; + +use function Zenstruck\Foundry\application; +use function Zenstruck\Foundry\runCommand; + +require \dirname(__DIR__) . '/vendor/autoload.php'; + +$fs = new Filesystem(); + +$fs->remove(__DIR__.'/../var'); + +(new Dotenv())->usePutenv()->loadEnv(__DIR__ . '/../.env'); + +$fs->remove(__DIR__ . '/Fixture/MigrationTests/Migrations'); +$fs->mkdir(__DIR__ . '/Fixture/MigrationTests/Migrations'); + +$kernel = new TestMigrationKernel('test', true); +$kernel->boot(); + +$application = application($kernel); + +runCommand($application, 'doctrine:database:drop --if-exists --force', canFail: true); +runCommand($application, 'doctrine:database:create', canFail: true); + +$configuration = ''; +if (getenv('WITH_MIGRATION_CONFIGURATION_FILE')) { + $configuration = '--configuration '.getcwd().'/'.getenv('WITH_MIGRATION_CONFIGURATION_FILE'); +} +runCommand($application, "doctrine:migrations:diff {$configuration}"); +runCommand($application, 'doctrine:database:drop --force', canFail: true); + +$kernel->shutdown(); + +\set_exception_handler([new ErrorHandler(), 'handleException']); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 03193ea6..4d9bb4c6 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -9,15 +9,9 @@ * file that was distributed with this source code. */ -use Symfony\Bundle\FrameworkBundle\Console\Application; -use Symfony\Component\Console\Input\StringInput; -use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Dotenv\Dotenv; use Symfony\Component\ErrorHandler\ErrorHandler; use Symfony\Component\Filesystem\Filesystem; -use Symfony\Component\Finder\Finder; -use Zenstruck\Foundry\ORM\ResetDatabase\ResetDatabaseMode; -use Zenstruck\Foundry\Tests\Fixture\TestKernel; require \dirname(__DIR__).'/vendor/autoload.php'; @@ -27,29 +21,4 @@ (new Dotenv())->usePutenv()->loadEnv(__DIR__.'/../.env'); -if (\getenv('DATABASE_URL') && ResetDatabaseMode::MIGRATE->value === \getenv('DATABASE_RESET_MODE')) { - $fs->remove(__DIR__.'/Fixture/Migrations'); - $fs->mkdir(__DIR__.'/Fixture/Migrations'); - - $kernel = new TestKernel('test', true); - $kernel->boot(); - - $application = new Application($kernel); - $application->setAutoExit(false); - - $application->run(new StringInput('doctrine:database:drop --if-exists --force'), new NullOutput()); - $application->run(new StringInput('doctrine:database:create'), new NullOutput()); - $application->run(new StringInput('doctrine:migrations:diff'), new NullOutput()); - $application->run(new StringInput('doctrine:database:drop --force'), new NullOutput()); - - // restore custom migrations - // this must be after "doctrine:migrations:diff" otherwise - // Doctrine is not able to run its diff command - foreach ((new Finder())->files()->in(__DIR__.'/Fixture/CustomMigrations') as $customMigrationFile) { - $fs->copy($customMigrationFile->getRealPath(), __DIR__.'/Fixture/Migrations/'.$customMigrationFile->getFilename()); - } - - $kernel->shutdown(); -} - \set_exception_handler([new ErrorHandler(), 'handleException']);