diff --git a/composer.json b/composer.json index 53a027cf..cef617e7 100644 --- a/composer.json +++ b/composer.json @@ -62,9 +62,9 @@ "phpstan/phpstan-deprecation-rules": "^2.0", "phpstan/phpstan-mockery": "^2.0", "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^10.5", + "phpunit/phpunit": "^11.5 || ^12.0 || ^13.0", "psalm/plugin-mockery": "^1.1", - "psalm/plugin-phpunit": "^0.19.0", + "psalm/plugin-phpunit": "^0.19.0 || ^0.20.0", "rector/rector": "^2.0", "rekalogika/direct-property-access": "^1.1.2 || ^1.2", "symfony/asset": "^6.2 || ^7.0 || ^8.0", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 3bbf5e80..41a26bd7 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,7 @@ + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\File\Tests; + +use PHPUnit\Framework\Attributes\After; +use PHPUnit\Framework\Attributes\Before; + +trait RestoresExceptionHandlersTrait +{ + private int $exceptionHandlerStackDepth = 0; + private int $errorHandlerStackDepth = 0; + + #[Before] + protected function recordHandlerStackDepth(): void + { + $this->exceptionHandlerStackDepth = self::countExceptionHandlers(); + $this->errorHandlerStackDepth = self::countErrorHandlers(); + } + + #[After] + protected function restoreExceptionAndErrorHandlers(): void + { + while (self::countExceptionHandlers() > $this->exceptionHandlerStackDepth) { + restore_exception_handler(); + } + + while (self::countErrorHandlers() > $this->errorHandlerStackDepth) { + restore_error_handler(); + } + } + + private static function countExceptionHandlers(): int + { + $count = 0; + $stack = []; + + while (true) { + $handler = set_exception_handler(static fn() => null); + restore_exception_handler(); + if ($handler === null) { + break; + } + $stack[] = $handler; + restore_exception_handler(); + $count++; + } + + foreach (array_reverse($stack) as $handler) { + set_exception_handler($handler); + } + + return $count; + } + + private static function countErrorHandlers(): int + { + $count = 0; + $stack = []; + + while (true) { + $handler = set_error_handler(static fn(): bool => false); + restore_error_handler(); + if ($handler === null) { + break; + } + $stack[] = $handler; + restore_error_handler(); + $count++; + } + + foreach (array_reverse($stack) as $handler) { + set_error_handler($handler); + } + + return $count; + } +} diff --git a/tests/src/Tests/FileAssociation/ClassSignatureTest.php b/tests/src/Tests/FileAssociation/ClassSignatureTest.php index 705aab37..2f284e51 100644 --- a/tests/src/Tests/FileAssociation/ClassSignatureTest.php +++ b/tests/src/Tests/FileAssociation/ClassSignatureTest.php @@ -13,17 +13,21 @@ namespace Rekalogika\File\Tests\Tests\FileAssociation; +use PHPUnit\Framework\Attributes\DataProvider; use Rekalogika\File\Association\Contracts\ClassSignatureResolverInterface; +use Rekalogika\File\Tests\RestoresExceptionHandlersTrait; use Rekalogika\File\Tests\Tests\Model\Entity; use Rekalogika\File\Tests\Tests\Model\EntityWithOverridenSignature; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; final class ClassSignatureTest extends KernelTestCase { + use RestoresExceptionHandlersTrait; + /** * @param class-string $class - * @dataProvider classSignatureProvider */ + #[DataProvider('classSignatureProvider')] public function testClassSignature( string $class, string $expectedSignature, diff --git a/tests/src/Tests/FileAssociation/DoctrineTestCase.php b/tests/src/Tests/FileAssociation/DoctrineTestCase.php index 0cc0692f..2efd798f 100644 --- a/tests/src/Tests/FileAssociation/DoctrineTestCase.php +++ b/tests/src/Tests/FileAssociation/DoctrineTestCase.php @@ -17,10 +17,13 @@ use Doctrine\ORM\Tools\SchemaTool; use Doctrine\Persistence\Proxy; use Rekalogika\Contracts\File\FileRepositoryInterface; +use Rekalogika\File\Tests\RestoresExceptionHandlersTrait; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; abstract class DoctrineTestCase extends KernelTestCase { + use RestoresExceptionHandlersTrait; + protected EntityManagerInterface $entityManager; protected FileRepositoryInterface $fileRepository; diff --git a/tests/src/Tests/FileAssociation/ObjectManagerTest.php b/tests/src/Tests/FileAssociation/ObjectManagerTest.php index 752aed75..780c3079 100644 --- a/tests/src/Tests/FileAssociation/ObjectManagerTest.php +++ b/tests/src/Tests/FileAssociation/ObjectManagerTest.php @@ -21,6 +21,7 @@ use Rekalogika\File\Association\Model\MissingFile; use Rekalogika\File\File; use Rekalogika\File\TemporaryFile; +use Rekalogika\File\Tests\RestoresExceptionHandlersTrait; use Rekalogika\File\Tests\Tests\File\FileTestTrait; use Rekalogika\File\Tests\Tests\Model\Entity; use Rekalogika\File\Tests\Tests\Model\EntityWithLazyFile; @@ -30,6 +31,7 @@ final class ObjectManagerTest extends KernelTestCase { use FileTestTrait; + use RestoresExceptionHandlersTrait; private ObjectManagerInterface $objectManager; diff --git a/tests/src/Tests/FileAssociation/PropertyListerTest.php b/tests/src/Tests/FileAssociation/PropertyListerTest.php index 63efbe7c..c8eb0c7a 100644 --- a/tests/src/Tests/FileAssociation/PropertyListerTest.php +++ b/tests/src/Tests/FileAssociation/PropertyListerTest.php @@ -13,6 +13,7 @@ namespace Rekalogika\File\Tests\Tests\FileAssociation; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Rekalogika\File\Association\Contracts\PropertyListerInterface; use Rekalogika\File\Association\Model\Property; @@ -28,8 +29,8 @@ final class PropertyListerTest extends TestCase /** * @param class-string $class * @param list $expectedProperties - * @dataProvider propertyListerProvider */ + #[DataProvider('propertyListerProvider')] public function testPropertyLister( PropertyListerInterface $lister, string $class, diff --git a/tests/src/Tests/FileAssociation/ProxyUtilTest.php b/tests/src/Tests/FileAssociation/ProxyUtilTest.php index f8e34298..2be27f95 100644 --- a/tests/src/Tests/FileAssociation/ProxyUtilTest.php +++ b/tests/src/Tests/FileAssociation/ProxyUtilTest.php @@ -14,15 +14,14 @@ namespace Rekalogika\File\Tests\Tests\FileAssociation; use MongoDBODMProxies\__PM__\Rekalogika\File\Tests\App\Entity\DummyEntity\Generated93deedc1e7b56ba9c8d5a337a376eda9; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Rekalogika\File\Association\Util\ProxyUtil; use Rekalogika\File\Tests\App\Entity\DummyEntity; final class ProxyUtilTest extends TestCase { - /** - * @dataProvider provideTestProxy - */ + #[DataProvider('provideTestProxy')] public function testProxy(string $class, string $expected): void { $this->assertTrue(class_exists($class), \sprintf( diff --git a/tests/src/Tests/FileImage/TwigTest.php b/tests/src/Tests/FileImage/TwigTest.php index e7f30b70..a7c544be 100644 --- a/tests/src/Tests/FileImage/TwigTest.php +++ b/tests/src/Tests/FileImage/TwigTest.php @@ -13,6 +13,8 @@ namespace Rekalogika\File\Tests\Tests\FileImage; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use Rekalogika\Contracts\File\FileRepositoryInterface; use Rekalogika\File\FilePointer; use Rekalogika\File\Image\ImageResizer; @@ -74,4 +76,101 @@ protected function getFixturesDir(): string { return __DIR__ . '/Fixtures/'; } + + /** + * @param mixed $file + * @param mixed $message + * @param mixed $condition + * @param mixed $templates + * @param mixed $exception + * @param mixed $outputs + * @param mixed $deprecation + */ + #[DataProvider('provideIntegrationTests')] + #[\Override] + public function testIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = ''): void + { + $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation); + } + + /** + * @param mixed $file + * @param mixed $message + * @param mixed $condition + * @param mixed $templates + * @param mixed $exception + * @param mixed $outputs + * @param mixed $deprecation + */ + #[DataProvider('provideLegacyIntegrationTests')] + #[Group('legacy')] + #[\Override] + public function testLegacyIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = ''): void + { + $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation); + } + + /** + * @return iterable> + */ + public static function provideIntegrationTests(): iterable + { + return self::collectFixtures(false); + } + + /** + * @return iterable> + */ + public static function provideLegacyIntegrationTests(): iterable + { + return self::collectFixtures(true); + } + + /** + * @return array> + */ + private static function collectFixtures(bool $legacyTests): array + { + $fixturesDir = (string) realpath(static::getFixturesDirectory()); + $tests = []; + + /** @var \SplFileInfo $file */ + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($fixturesDir), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + if (!preg_match('/\.test$/', (string) $file)) { + continue; + } + + if ($legacyTests xor str_contains($file->getRealpath(), '.legacy.test')) { + continue; + } + + $test = (string) file_get_contents($file->getRealpath()); + + if (preg_match('/--TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*(?:--DEPRECATION--\s*(.*?))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)\s*(?:--DATA--\s*(.*))?\s*--EXCEPTION--\s*(.*)/sx', $test, $match)) { + $message = $match[1]; + $condition = $match[2]; + $deprecation = $match[3]; + $templates = IntegrationTestCase::parseTemplates($match[4]); + $exception = $match[6]; + $outputs = [[null, $match[5], null, '']]; + } elseif (preg_match('/--TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*(?:--DEPRECATION--\s*(.*?))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)--DATA--.*?--EXPECT--.*/s', $test, $match)) { + $message = $match[1]; + $condition = $match[2]; + $deprecation = $match[3]; + $templates = IntegrationTestCase::parseTemplates($match[4]); + $exception = false; + preg_match_all('/--DATA--(.*?)(?:--CONFIG--(.*?))?--EXPECT--(.*?)(?=\-\-DATA\-\-|$)/s', $test, $outputs, \PREG_SET_ORDER); + } else { + throw new \InvalidArgumentException(\sprintf('Test "%s" is not valid.', str_replace($fixturesDir . '/', '', (string) $file))); + } + + $tests[str_replace($fixturesDir . '/', '', (string) $file)] = [str_replace($fixturesDir . '/', '', (string) $file), $message, $condition, $templates, $exception, $outputs, $deprecation]; + } + + if ($legacyTests && !$tests) { + return [['not', '-', '', [], '', []]]; + } + + return $tests; + } } diff --git a/tests/src/Tests/FileNull/NullFileTest.php b/tests/src/Tests/FileNull/NullFileTest.php index a1a2d360..5e5ed4ef 100644 --- a/tests/src/Tests/FileNull/NullFileTest.php +++ b/tests/src/Tests/FileNull/NullFileTest.php @@ -13,6 +13,7 @@ namespace Rekalogika\File\Tests\Tests\FileNull; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Rekalogika\Contracts\File\FileInterface; use Rekalogika\Contracts\File\FilePointerInterface; @@ -38,9 +39,7 @@ public function testNullFile(): void $this->assertInstanceOf(NullPointer::class, $nullPointer); } - /** - * @dataProvider equalityProvider - */ + #[DataProvider('equalityProvider')] public function testEquality( bool $expected, FilePointerInterface|FileInterface $file1, diff --git a/tests/src/Tests/FileServer/TemporaryUrlTest.php b/tests/src/Tests/FileServer/TemporaryUrlTest.php index 4bc342d3..0bf638d2 100644 --- a/tests/src/Tests/FileServer/TemporaryUrlTest.php +++ b/tests/src/Tests/FileServer/TemporaryUrlTest.php @@ -15,11 +15,14 @@ use Rekalogika\File\File; use Rekalogika\File\FilePointer; +use Rekalogika\File\Tests\RestoresExceptionHandlersTrait; use Rekalogika\TemporaryUrl\TemporaryUrlGeneratorInterface; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; final class TemporaryUrlTest extends KernelTestCase { + use RestoresExceptionHandlersTrait; + public function testTemporaryUrlWithFilePointer(): void { $this->markTestSkipped(); diff --git a/tests/src/Tests/FileSymfonyBridge/FileAdapterTest.php b/tests/src/Tests/FileSymfonyBridge/FileAdapterTest.php index eae8955c..85f7f2b0 100644 --- a/tests/src/Tests/FileSymfonyBridge/FileAdapterTest.php +++ b/tests/src/Tests/FileSymfonyBridge/FileAdapterTest.php @@ -20,6 +20,7 @@ use Rekalogika\File\FilePointer; use Rekalogika\File\LocalTemporaryFile; use Rekalogika\File\TemporaryFile; +use Rekalogika\File\Tests\RestoresExceptionHandlersTrait; use Rekalogika\File\Tests\Tests\File\FileTestTrait; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\HttpFoundation\File\File; @@ -27,6 +28,7 @@ final class FileAdapterTest extends KernelTestCase { use FileTestTrait; + use RestoresExceptionHandlersTrait; private function createRemoteFile(): FileInterface { diff --git a/tests/src/Tests/FileZip/ZipTest.php b/tests/src/Tests/FileZip/ZipTest.php index 29b8deaa..e1fc145f 100644 --- a/tests/src/Tests/FileZip/ZipTest.php +++ b/tests/src/Tests/FileZip/ZipTest.php @@ -16,12 +16,15 @@ use Rekalogika\Contracts\File\FileInterface; use Rekalogika\Contracts\File\FileRepositoryInterface; use Rekalogika\File\FilePointer; +use Rekalogika\File\Tests\RestoresExceptionHandlersTrait; use Rekalogika\File\Zip\FileZip; use Rekalogika\File\Zip\Model\Directory; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; final class ZipTest extends KernelTestCase { + use RestoresExceptionHandlersTrait; + private ?FileRepositoryInterface $fileRepository = null; private ?FileZip $fileZip = null;