diff --git a/.danger.php b/.danger.php index 8b53442bf44..b15ad1afb35 100644 --- a/.danger.php +++ b/.danger.php @@ -10,7 +10,6 @@ use Danger\Rule\DisallowRepeatedCommits; use Danger\Struct\Gitlab\File as GitlabFile; - return (new Config()) ->useThreadOnFails() ->useRule(new DisallowRepeatedCommits) @@ -196,14 +195,21 @@ function (Context $context) { ] )) ->useRule(function (Context $context) { - $files = $context->platform->pullRequest->getFiles(); + function checkMigrationForBundle(string $bundle, Context $context): void + { + $files = $context->platform->pullRequest->getFiles(); - $migrationFiles = $files->filterStatus(File::STATUS_ADDED)->matches('src/Core/Migration/V*/Migration*.php'); - $migrationTestFiles = $files->filterStatus(File::STATUS_ADDED)->matches('src/Core/Migration/Test/*.php'); + $migrationFiles = $files->filterStatus(File::STATUS_ADDED)->matches('src/Core/Migration/V*/Migration*.php'); + $migrationTestFiles = $files->filterStatus(File::STATUS_ADDED)->matches('tests/migration/Core/V*/*.php'); - if ($migrationFiles->count() && !$migrationTestFiles->count()) { - $context->failure('Please add tests for your new Migration file'); + if ($migrationFiles->count() && !$migrationTestFiles->count()) { + $context->failure('Please add tests for your new Migration file'); + } } + + checkMigrationForBundle('Core', $context); + checkMigrationForBundle('Administration', $context); + checkMigrationForBundle('Storefront', $context); }) ->useRule(function (Context $context) { $files = $context->platform->pullRequest->getFiles(); diff --git a/.github/workflows/02-unit.yml b/.github/workflows/02-unit.yml index ebb356798f1..d50eef0187d 100644 --- a/.github/workflows/02-unit.yml +++ b/.github/workflows/02-unit.yml @@ -15,7 +15,7 @@ jobs: matrix: php-version: ["7.4", "8.0", "8.1"] env: - TEST_SUITES: 'administration storefront checkout content framework profiling migration system elasticsearch docs unit integration' + TEST_SUITES: 'administration storefront checkout content framework profiling migration system elasticsearch docs unit integration migration-tests' APP_ENV: test DATABASE_URL: mysql://root:root@database:3306/root APP_URL: http://localhost:8000 diff --git a/.gitlab/stages/20-unit.yml b/.gitlab/stages/20-unit.yml index 5da1366c19f..39c47e9842f 100644 --- a/.gitlab/stages/20-unit.yml +++ b/.gitlab/stages/20-unit.yml @@ -174,6 +174,44 @@ PHP unit coverage: coverage_format: cobertura path: cobertura.xml +PHP migration coverage: + extends: .base-no-setup + image: shopware/development:7.4-composer-2 + stage: unit + needs: [] + timeout: 10m + services: + - name: mariadb:10.4 + alias: database + entrypoint: [ "sh", "-c", "docker-entrypoint.sh $MYSQL_CMD" ] + variables: + APP_ENV: "test" + FEATURE_ALL: "false" + rules: + - !reference [.rules, skip] + - !reference [.rules, run] + - when: always + before_script: + - composer install --optimize-autoloader --no-interaction --quiet + script: + - PHP_OPTIONS="-d pcov.enabled=1 -d pcov.directory=$PWD/src -d pcov.exclude='~(vendor|Test|node_modules)~'" + - php $PHP_OPTIONS vendor/bin/phpunit + --configuration tests/migration/phpunit.xml + --log-junit phpunit.junit.xml + --testsuite migration-tests + --coverage-text + --coverage-cobertura cobertura.xml | sed -E -n '1,/^\s*Lines:\s*([0-9]+(\.[0-9]+)?)%/ p' # do not output covered files lines + coverage: '/^\s*Lines:\s*(\d+(?:\.\d+)?%)/' + artifacts: + when: always + paths: + - cobertura.xml + reports: + junit: phpunit.junit.xml + coverage_report: + coverage_format: cobertura + path: cobertura.xml + PHP Full: extends: .long-running image: $DEV_IMAGE @@ -191,7 +229,7 @@ PHP Full: - name: redis alias: redis variables: - TEST_SUITES: "docs administration storefront checkout content framework profiling system elasticsearch unit integration migration" + TEST_SUITES: "docs administration storefront checkout content framework profiling system elasticsearch unit integration migration migration-tests" APP_ENV: test GIT_DEPTH: 0 # we need all commits for composer to detect the version which is then checked in a unit test RUNNER_INSTANCE_TYPE: m5.large diff --git a/composer.json b/composer.json index 21fb109ff4a..1c5e0af3d44 100644 --- a/composer.json +++ b/composer.json @@ -435,7 +435,8 @@ "Shopware\\Core\\Test\\": "src/Core/Test/", "Shopware\\Tests\\Unit\\": "tests/unit/php/", "Shopware\\Tests\\Integration\\": "tests/integration/php/", - "Shopware\\Tests\\Bench\\": "tests/performance/bench/" + "Shopware\\Tests\\Bench\\": "tests/performance/bench/", + "Shopware\\Tests\\Migration\\": "tests/migration/" } } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 0cb04029ba0..e3286fb6dfa 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -8189,21 +8189,6 @@ parameters: count: 1 path: src/Core/Framework/Test/Plugin/KernelPluginIntegrationTest.php - - - message: "#^Method Shopware\\\\Core\\\\Framework\\\\Test\\\\Plugin\\\\KernelPluginIntegrationTest\\:\\:makeKernel\\(\\) should return Shopware\\\\Core\\\\Kernel but returns object\\.$#" - count: 1 - path: src/Core/Framework/Test/Plugin/KernelPluginIntegrationTest.php - - - - message: "#^Parameter \\#1 \\$argument of class ReflectionClass constructor expects class\\-string\\\\|T of object, string given\\.$#" - count: 1 - path: src/Core/Framework/Test/Plugin/KernelPluginIntegrationTest.php - - - - message: "#^Property Shopware\\\\Core\\\\Framework\\\\Test\\\\Plugin\\\\KernelPluginIntegrationTest\\:\\:\\$kernel \\(Shopware\\\\Core\\\\Kernel\\|null\\) does not accept object\\.$#" - count: 1 - path: src/Core/Framework/Test/Plugin/KernelPluginIntegrationTest.php - - message: "#^Cannot call method format\\(\\) on DateTimeInterface\\|null\\.$#" count: 1 @@ -8904,36 +8889,6 @@ parameters: count: 5 path: src/Core/Framework/Test/TestCaseBase/DatadogListener.php - - - message: "#^Cannot call method fetchAll\\(\\) on Doctrine\\\\DBAL\\\\Connection\\|null\\.$#" - count: 1 - path: src/Core/Framework/Test/TestCaseBase/KernelLifecycleManager.php - - - - message: "#^Method Shopware\\\\Core\\\\Framework\\\\Test\\\\TestCaseBase\\\\KernelLifecycleManager\\:\\:bootKernel\\(\\) should return Shopware\\\\Core\\\\Kernel but returns Symfony\\\\Component\\\\HttpKernel\\\\KernelInterface\\.$#" - count: 1 - path: src/Core/Framework/Test/TestCaseBase/KernelLifecycleManager.php - - - - message: "#^Method Shopware\\\\Core\\\\Framework\\\\Test\\\\TestCaseBase\\\\KernelLifecycleManager\\:\\:createKernel\\(\\) should return Symfony\\\\Component\\\\HttpKernel\\\\KernelInterface but returns object\\.$#" - count: 1 - path: src/Core/Framework/Test/TestCaseBase/KernelLifecycleManager.php - - - - message: "#^Parameter \\#1 \\$classname of function class_exists expects string, bool\\|float\\|int\\|string\\|null given\\.$#" - count: 1 - path: src/Core/Framework/Test/TestCaseBase/KernelLifecycleManager.php - - - - message: "#^Service \"test\\.browser\" is not registered in the container\\.$#" - count: 1 - path: src/Core/Framework/Test/TestCaseBase/KernelLifecycleManager.php - - - - message: "#^Static property Shopware\\\\Core\\\\Framework\\\\Test\\\\TestCaseBase\\\\KernelLifecycleManager\\:\\:\\$kernel \\(Shopware\\\\Core\\\\Kernel\\|null\\) does not accept Symfony\\\\Component\\\\HttpKernel\\\\KernelInterface\\.$#" - count: 1 - path: src/Core/Framework/Test/TestCaseBase/KernelLifecycleManager.php - - message: "#^Property Shopware\\\\Core\\\\Framework\\\\Test\\\\TestCaseBase\\\\KernelTestBehaviourTest\\:\\:\\$kernelId has no type specified\\.$#" count: 1 @@ -9329,11 +9284,6 @@ parameters: count: 1 path: src/Core/Migration/Migration1614249488ChangeProductSortingsToCheapestPrice.php - - - message: "#^Parameter \\#1 \\$filename of function sha1_file expects string, string\\|false given\\.$#" - count: 1 - path: src/Core/Migration/Test/BasicDataUntouchedTest.php - - message: "#^Parameter \\#1 \\$filename of function file_get_contents expects string, string\\|false given\\.$#" count: 1 diff --git a/phpstan.neon b/phpstan.neon index 953c8514eee..66e8b1f65bc 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -53,12 +53,12 @@ parameters: message: '#Service ".*" is not registered in the container\.#' paths: - src/**/*Test.php - - tests/**/*Test.php + - src/Core/Framework/Test/TestCaseBase/*.php - message: '#Service ".*" is private#' paths: - src/**/*Test.php - - tests/**/*Test.php + - tests/integration/**/*Test.php # ignore errors caused by static::markTestSkipped - diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9cdf4cc11e5..ae12461ac83 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -21,8 +21,6 @@ src/Core/Profiling src/Core/System src/Elasticsearch - src/Core/Migration - src/Core/Migration src/Core/Defaults.php src/Core/HttpKernel.php @@ -127,6 +125,10 @@ tests/integration/php + + tests/migration + + src/Docs/Test src/Docs/Resources/current/ diff --git a/src/Core/DevOps/StaticAnalyze/PHPStan/Rules/Tests/CoversAnnotationRule.php b/src/Core/DevOps/StaticAnalyze/PHPStan/Rules/Tests/CoversAnnotationRule.php index abc862b7384..17d7474ab61 100644 --- a/src/Core/DevOps/StaticAnalyze/PHPStan/Rules/Tests/CoversAnnotationRule.php +++ b/src/Core/DevOps/StaticAnalyze/PHPStan/Rules/Tests/CoversAnnotationRule.php @@ -42,7 +42,7 @@ private function isTestClass(InClassNode $node): bool { $namespace = $node->getClassReflection()->getName(); - if (!\str_contains($namespace, 'Shopware\\Tests\\Unit\\')) { + if (!\str_contains($namespace, 'Shopware\\Tests\\Unit\\') && !\str_contains($namespace, 'Shopware\\Tests\\Migration\\')) { return false; } @@ -61,6 +61,6 @@ private function hasCovers(InClassNode $class): bool return false; } - return \str_contains($doc->getText(), '@covers'); + return \str_contains($doc->getText(), '@covers') || \str_contains($doc->getText(), '@coversNothing'); } } diff --git a/src/Core/Framework/Test/TestCaseBase/KernelLifecycleManager.php b/src/Core/Framework/Test/TestCaseBase/KernelLifecycleManager.php index 6de21625f40..8771ed95a82 100644 --- a/src/Core/Framework/Test/TestCaseBase/KernelLifecycleManager.php +++ b/src/Core/Framework/Test/TestCaseBase/KernelLifecycleManager.php @@ -5,6 +5,7 @@ use Composer\Autoload\ClassLoader; use Doctrine\DBAL\Connection; use Shopware\Core\DevOps\Environment\EnvironmentHelper; +use Shopware\Core\Framework\Adapter\Database\MySQLFactory; use Shopware\Core\Framework\Plugin\KernelPluginLoader\DbalKernelPluginLoader; use Shopware\Core\Framework\Plugin\KernelPluginLoader\StaticKernelPluginLoader; use Shopware\Core\Framework\Test\Filesystem\Adapter\MemoryAdapterFactory; @@ -20,7 +21,7 @@ class KernelLifecycleManager { /** - * @var string + * @var class-string */ protected static $class; @@ -102,7 +103,10 @@ public static function bootKernel(bool $reuseConnection = true, string $cacheId return static::$kernel; } - public static function createKernel(?string $kernelClass = null, bool $reuseConnection = true, string $cacheId = 'h8f3f0ee9c61829627676afd6294bb029', ?string $projectDir = null): KernelInterface + /** + * @param class-string|null $kernelClass + */ + public static function createKernel(?string $kernelClass = null, bool $reuseConnection = true, string $cacheId = 'h8f3f0ee9c61829627676afd6294bb029', ?string $projectDir = null): Kernel { if ($kernelClass === null) { if (static::$class === null) { @@ -122,7 +126,7 @@ public static function createKernel(?string $kernelClass = null, bool $reuseConn try { $existingConnection = null; if ($reuseConnection) { - $existingConnection = self::$connection; + $existingConnection = self::getConnection(); try { $existingConnection->fetchAll('SELECT 1'); @@ -148,12 +152,11 @@ public static function createKernel(?string $kernelClass = null, bool $reuseConn } /** - * @throws \RuntimeException - * @throws \LogicException + * @return class-string */ public static function getKernelClass(): string { - if (!class_exists($class = EnvironmentHelper::getVariable('KERNEL_CLASS', Kernel::class))) { + if (!class_exists($class = (string) EnvironmentHelper::getVariable('KERNEL_CLASS', Kernel::class))) { throw new \RuntimeException( sprintf( 'Class "%s" doesn\'t exist or cannot be autoloaded. Check that the KERNEL_CLASS value in phpunit.xml matches the fully-qualified class name of your Kernel or override the %s::createKernel() method.', @@ -163,6 +166,17 @@ public static function getKernelClass(): string ); } + if (!is_a($class, Kernel::class, true)) { + throw new \RuntimeException( + sprintf( + 'Class "%s" must extend "%s". Check that the KERNEL_CLASS value in phpunit.xml matches the fully-qualified class name of your Kernel or override the %s::createKernel() method.', + $class, + Kernel::class, + static::class + ) + ); + } + return $class; } @@ -184,4 +198,13 @@ public static function ensureKernelShutdown(): void static::$kernel = null; } + + public static function getConnection(): Connection + { + if (!static::$connection) { + static::$connection = MySQLFactory::create(); + } + + return static::$connection; + } } diff --git a/tests/unit/php/Core/.gitkeep b/tests/migration/Administration/.gitkeep similarity index 100% rename from tests/unit/php/Core/.gitkeep rename to tests/migration/Administration/.gitkeep diff --git a/src/Core/Migration/Test/BasicDataUntouchedTest.php b/tests/migration/Core/BasicDataUntouchedTest.php similarity index 82% rename from src/Core/Migration/Test/BasicDataUntouchedTest.php rename to tests/migration/Core/BasicDataUntouchedTest.php index 48eff09446e..f216cb49732 100644 --- a/src/Core/Migration/Test/BasicDataUntouchedTest.php +++ b/tests/migration/Core/BasicDataUntouchedTest.php @@ -1,22 +1,21 @@ findFile(Migration1536233560BasicData::class); static::assertSame( diff --git a/src/Core/Migration/Test/Migration1653376989ResetDefaultAlwaysValidConditionValueTest.php b/tests/migration/Core/V6_4/Migration1653376989ResetDefaultAlwaysValidConditionValueTest.php similarity index 85% rename from src/Core/Migration/Test/Migration1653376989ResetDefaultAlwaysValidConditionValueTest.php rename to tests/migration/Core/V6_4/Migration1653376989ResetDefaultAlwaysValidConditionValueTest.php index a74cf2bc61a..df3dec6e7c5 100644 --- a/src/Core/Migration/Test/Migration1653376989ResetDefaultAlwaysValidConditionValueTest.php +++ b/tests/migration/Core/V6_4/Migration1653376989ResetDefaultAlwaysValidConditionValueTest.php @@ -1,26 +1,28 @@ connection = $this->getContainer()->get(Connection::class); + $this->connection = KernelLifecycleManager::getConnection(); } public function testResetDefaultValwaysValidConditionValue(): void diff --git a/src/Core/Migration/Test/Migration1657011337AddFillableInStorefrontTest.php b/tests/migration/Core/V6_4/Migration1657011337AddFillableInStorefrontTest.php similarity index 74% rename from src/Core/Migration/Test/Migration1657011337AddFillableInStorefrontTest.php rename to tests/migration/Core/V6_4/Migration1657011337AddFillableInStorefrontTest.php index da3348c9980..577f2bda3d0 100644 --- a/src/Core/Migration/Test/Migration1657011337AddFillableInStorefrontTest.php +++ b/tests/migration/Core/V6_4/Migration1657011337AddFillableInStorefrontTest.php @@ -1,24 +1,22 @@ getContainer()->get(Connection::class); + $con = KernelLifecycleManager::getConnection(); $m = new Migration1657011337AddFillableInStorefront(); $m->update($con); @@ -29,7 +27,7 @@ public function testMultipleExecution(): void public function testColumnGetsCreated(): void { - $con = $this->getContainer()->get(Connection::class); + $con = KernelLifecycleManager::getConnection(); $con->executeStatement('ALTER TABLE `custom_field` DROP `allow_customer_write`;'); @@ -43,7 +41,7 @@ public function testColumnGetsCreated(): void private function columnExists(Connection $connection): bool { - $field = $connection->fetchColumn( + $field = $connection->fetchOne( 'SHOW COLUMNS FROM `custom_field` WHERE `Field` LIKE :column;', ['column' => 'allow_customer_write'] ); diff --git a/tests/migration/MigrationTestTrait.php b/tests/migration/MigrationTestTrait.php new file mode 100644 index 00000000000..48d1999f1e4 --- /dev/null +++ b/tests/migration/MigrationTestTrait.php @@ -0,0 +1,27 @@ +beginTransaction(); + } + + /** + * @after + */ + public function rollbackTransaction(): void + { + KernelLifecycleManager::getConnection()->rollBack(); + } +} diff --git a/tests/migration/Storefront/.gitkeep b/tests/migration/Storefront/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/migration/phpunit.xml b/tests/migration/phpunit.xml new file mode 100644 index 00000000000..e0a065af2c6 --- /dev/null +++ b/tests/migration/phpunit.xml @@ -0,0 +1,56 @@ + + + + + + ../../src/Core/Migration + ../../src/Administration/Migration + ../../src/Storefront/Migration + + + + + + + + + + + + + + + + + + + + + . + + + + + + + + 0 + + + + + + + + + + + +