diff --git a/.github/workflows/bc.yml b/.github/workflows/bc.yml index d13f960..85232cc 100644 --- a/.github/workflows/bc.yml +++ b/.github/workflows/bc.yml @@ -1,6 +1,25 @@ on: - - pull_request - - push + pull_request: + paths-ignore: + - 'docs/**' + - 'README.md' + - 'CHANGELOG.md' + - '.gitignore' + - '.gitattributes' + - 'infection.json.dist' + - 'phpunit.xml.dist' + - 'psalm.xml' + push: + branches: ['master'] + paths-ignore: + - 'docs/**' + - 'README.md' + - 'CHANGELOG.md' + - '.gitignore' + - '.gitattributes' + - 'infection.json.dist' + - 'phpunit.xml.dist' + - 'psalm.xml' name: backwards compatibility @@ -8,7 +27,6 @@ jobs: roave_bc_check: uses: yiisoft/actions/.github/workflows/bc.yml@master with: - extensions: uopz os: >- ['ubuntu-latest'] php: >- diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 31f9e52..05e7b40 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,6 +10,7 @@ on: - 'psalm.xml' push: + branches: ['master'] paths-ignore: - 'docs/**' - 'README.md' @@ -23,68 +24,11 @@ name: build jobs: phpunit: - name: PHP ${{ matrix.php }}-${{ matrix.os }} - - runs-on: ${{ matrix.os }} - - strategy: - matrix: - os: - - ubuntu-latest - - windows-latest - - php: - - 8.1 - - 8.2 - - 8.3 - - exclude: - - os: windows-latest - php: 8.2 - - - os: windows-latest - php: 8.3 - - steps: - - name: Checkout. - uses: actions/checkout@v4 - - - name: Install PHP with extensions. - uses: shivammathur/setup-php@v2 - with: - coverage: pcov - extensions: uopz - ini-values: date.timezone='UTC' - php-version: ${{ matrix.php }} - tools: composer:v2 - - - name: Determine composer cache directory on Linux. - if: matrix.os == 'ubuntu-latest' - run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV - - - name: Determine composer cache directory on Windows. - if: matrix.os == 'windows-latest' - run: echo "COMPOSER_CACHE_DIR=~\AppData\Local\Composer" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - - - name: Cache dependencies installed with composer. - uses: actions/cache@v4 - with: - path: ${{ env.COMPOSER_CACHE_DIR }} - key: php${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} - restore-keys: | - php${{ matrix.php }}-composer- - - - name: Update composer. - run: composer self-update - - - name: Install dependencies with composer. - run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi - - - name: Run tests with phpunit with code coverage. - run: vendor/bin/phpunit --coverage-clover=coverage.xml --colors=always --configuration phpunit.xml.dist - - - name: Upload coverage to Codecov. - if: matrix.os == 'ubuntu-latest' - uses: codecov/codecov-action@v3 - with: - files: ./coverage.xml + uses: yiisoft/actions/.github/workflows/phpunit.yml@master + secrets: + codecovToken: ${{ secrets.CODECOV_TOKEN }} + with: + os: >- + ['ubuntu-latest', 'windows-latest'] + php: >- + ['8.1', '8.2', '8.3'] diff --git a/.github/workflows/composer-require-checker.yml b/.github/workflows/composer-require-checker.yml index 825dbd9..a857bce 100644 --- a/.github/workflows/composer-require-checker.yml +++ b/.github/workflows/composer-require-checker.yml @@ -31,5 +31,4 @@ jobs: os: >- ['ubuntu-latest'] php: >- - ['8.3'] - extensions: uopz + ['8.1', '8.2', '8.3'] diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index a7a2b21..df1db8d 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -9,6 +9,7 @@ on: - 'psalm.xml' push: + branches: ['master'] paths-ignore: - 'docs/**' - 'README.md' @@ -27,7 +28,6 @@ jobs: ['ubuntu-latest'] php: >- ['8.3'] - extensions: uopz min-covered-msi: 100 secrets: STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml index f5da663..457772a 100644 --- a/.github/workflows/rector.yml +++ b/.github/workflows/rector.yml @@ -22,4 +22,3 @@ jobs: ['ubuntu-latest'] php: >- ['8.3'] - extensions: uopz diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 61c7bd6..e33eca8 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -10,6 +10,7 @@ on: - 'phpunit.xml.dist' push: + branches: ['master'] paths-ignore: - 'docs/**' - 'README.md' @@ -29,4 +30,3 @@ jobs: ['ubuntu-latest'] php: >- ['8.1', '8.2', '8.3'] - extensions: uopz diff --git a/composer.json b/composer.json index b9d77ea..095570f 100644 --- a/composer.json +++ b/composer.json @@ -33,15 +33,14 @@ "yiisoft/var-dumper": "^1.7" }, "require-dev": { - "ext-uopz": "*", - "maglnet/composer-require-checker": "^4.3", - "phpunit/phpunit": "^10.5.2", + "maglnet/composer-require-checker": "^4.7.1", + "phpunit/phpunit": "^10.5.45", "psr/clock": "^1.0", "rector/rector": "^2.0.11", - "roave/infection-static-analysis-plugin": "^1.18", - "spatie/phpunit-watcher": "^1.23", - "vimeo/psalm": "^4.30|^5.17", - "yiisoft/files": "^1.0" + "roave/infection-static-analysis-plugin": "^1.35", + "spatie/phpunit-watcher": "^1.24", + "vimeo/psalm": "^5.26.1", + "yiisoft/files": "^1.0.2" }, "autoload": { "psr-4": { @@ -52,10 +51,7 @@ "psr-4": { "Yiisoft\\Rbac\\Php\\Tests\\": "tests", "Yiisoft\\Rbac\\Tests\\": "vendor/yiisoft/rbac/tests" - }, - "files": [ - "tests/bootstrap.php" - ] + } }, "config": { "sort-packages": true, diff --git a/infection.json.dist b/infection.json.dist index 3776e22..9419316 100644 --- a/infection.json.dist +++ b/infection.json.dist @@ -11,6 +11,11 @@ } }, "mutators": { - "@default": true + "@default": true, + "MethodCallRemoval": { + "ignoreSourceCodeByRegex": [ + "\\$this->invalidateScriptCache.*" + ] + } } } diff --git a/src/FileStorageTrait.php b/src/FileStorageTrait.php index 95e5a66..d755192 100644 --- a/src/FileStorageTrait.php +++ b/src/FileStorageTrait.php @@ -84,6 +84,8 @@ private function saveToFile(array $data): void /** * Invalidates precompiled script cache (such as OPCache) for the given file. + * + * @infection-ignore-all */ private function invalidateScriptCache(): void { diff --git a/tests/ItemsStorage/CreateNestedDirectory/.gitignore b/tests/ItemsStorage/CreateNestedDirectory/.gitignore new file mode 100644 index 0000000..f78da73 --- /dev/null +++ b/tests/ItemsStorage/CreateNestedDirectory/.gitignore @@ -0,0 +1 @@ +/runtime diff --git a/tests/ItemsStorage/CreateNestedDirectory/CreateNestedDirectoryTest.php b/tests/ItemsStorage/CreateNestedDirectory/CreateNestedDirectoryTest.php new file mode 100644 index 0000000..474fc52 --- /dev/null +++ b/tests/ItemsStorage/CreateNestedDirectory/CreateNestedDirectoryTest.php @@ -0,0 +1,70 @@ +add(new Permission('createPost')); + + assertFileExists($directory . '/items.php'); + } + + public function testRestoreErrorHandler(): void + { + $directory = self::RUNTIME_DIRECTORY . '/test/create/nested/directory'; + $errorHandler = static fn() => true; + set_error_handler($errorHandler); + + try { + $storage = new ItemsStorage($directory . '/items.php'); + $storage->add(new Permission('createPost')); + $currentErrorHandler = TestHelper::getCurrentErrorHandler(); + } finally { + restore_error_handler(); + } + + assertSame($errorHandler, $currentErrorHandler); + } + + /** + * @requires OS Linux + */ + public function testDirectoryPermission(): void + { + $directory = self::RUNTIME_DIRECTORY . '/test/create/nested/directory-permissions'; + + $storage = new ItemsStorage($directory . '/items.php'); + $storage->add(new Permission('createPost')); + + $this->assertSame(0755, TestHelper::getDirectoryPermissions($directory)); + } +} diff --git a/tests/ItemsStorage/ItemsStorageTest.php b/tests/ItemsStorage/ItemsStorageTest.php new file mode 100644 index 0000000..77bdb54 --- /dev/null +++ b/tests/ItemsStorage/ItemsStorageTest.php @@ -0,0 +1,125 @@ +name() === 'testCreateDirectoryException') { + FileHelper::ensureDirectory($this->getTempDirectory()); + FileHelper::clearDirectory($this->getTempDirectory()); + } + + if (!in_array($this->name(), self::EMPTY_STORAGE_TESTS, strict: true)) { + $this->traitSetUp(); + } + } + + protected function tearDown(): void + { + if ($this->name() === 'testCreateDirectoryException') { + FileHelper::removeDirectory($this->getTempDirectory()); + } + + $this->clearStoragesFiles(); + } + + public function testCreateDirectoryException(): void + { + $directory = $this->getTempDirectory() . '/file.txt'; + touch($directory); + + $storage = new ItemsStorage($directory . '/items.php'); + $permission = new Permission('createPost'); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Failed to create directory "' . $directory . '". mkdir(): File exists'); + $storage->add($permission); + } + + public function testSaveWithNullAttributes(): void + { + $this->createItemsStorage()->add(new Permission('test')); + + $data = require $this->getStoragesDirectory() . DIRECTORY_SEPARATOR . 'items.php'; + $this->assertSame([['name' => 'test', 'type' => 'permission']], $data); + } + + public function testSaveAndLoadWithAllAttributes(): void + { + $time = time(); + $permission = (new Permission('testName')) + ->withDescription('testDescription') + ->withRuleName('testRule') + ->withCreatedAt($time) + ->withUpdatedAt($time); + $storage = $this->createItemsStorage(); + $storage->add($permission); + $storage = $this->createItemsStorage(); + + $this->assertEquals($permission, $storage->get('testName')); + } + + public function testLoadWithCustomGetFileUpdatedAt(): void + { + $time = 1683707079; + $storage = $this->createItemsStorage(); + $storage->add(new Permission('test')); + + $storage = new ItemsStorage( + $this->getItemsStorageFilePath(), + getFileUpdatedAt: static fn (string $filePath): int|false => $time, + ); + $this->assertSame($time, $storage->get('test')->getCreatedAt()); + } + + public function testGetFileUpdatedAtException(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('getFileUpdatedAt callable must return a UNIX timestamp.'); + new ItemsStorage( + $this->getItemsStorageFilePath(), + getFileUpdatedAt: static fn (string $filePath): string => 'test', + ); + } + + protected function createItemsStorage(): ItemsStorageInterface + { + return new ItemsStorage($this->getItemsStorageFilePath()); + } + + protected function getItemsStorageForModificationAssertions(): ItemsStorageInterface + { + return $this->getItemsStorage(); + } + + private function getTempDirectory(): string + { + return __DIR__ . '/temp'; + } +} diff --git a/tests/ItemsStorageTest.php b/tests/ItemsStorageTest.php deleted file mode 100644 index 2621faf..0000000 --- a/tests/ItemsStorageTest.php +++ /dev/null @@ -1,232 +0,0 @@ -name() === 'testCreateDirectoryException' || $this->name() === 'testCreateNestedDirectory') { - FileHelper::ensureDirectory($this->getTempDirectory()); - FileHelper::clearDirectory($this->getTempDirectory()); - } - - if ($this->name() === 'testCreateNestedDirectory') { - $storage = $this; - uopz_set_return( - 'mkdir', - static function ( - string $directory, - int $permissions = 0777, - bool $recursive = false, - ) use ($storage): bool { - $storage->directoryPermissions = $permissions; - - return mkdir($directory, $permissions, $recursive); - }, - true, - ); - uopz_set_return( - 'restore_error_handler', - static function () use ($storage): bool { - $storage->errorHandlerRestored = true; - - return true; - }, - true, - ); - } - - if ($this->name() === 'testSaveAndInvalidateOpcacheWithExtension') { - uopz_set_return( - 'function_exists', - static function (string $function): bool { - return $function === 'opcache_invalidate' ? true : function_exists($function); - }, - true, - ); - - $storage = $this; - uopz_set_return( - 'opcache_invalidate', - static function (string $filename, bool $force = true) use ($storage): bool { - $storage->opcacheInvalidated = true; - - return true; - }, - true, - ); - } - - if ($this->name() === 'testSaveAndInvalidateOpcacheWithoutExtension') { - uopz_set_return( - 'function_exists', - static function (string $function): bool { - return $function === 'opcache_invalidate' ? false : function_exists($function); - }, - true, - ); - } - - if (!in_array($this->name(), self::EMPTY_STORAGE_TESTS, strict: true)) { - $this->traitSetUp(); - } - } - - protected function tearDown(): void - { - if ($this->name() === 'testCreateDirectoryException' || $this->name() === 'testCreateNestedDirectory') { - FileHelper::removeDirectory($this->getTempDirectory()); - } - - if ($this->name() === 'testCreateNestedDirectory') { - uopz_unset_return('mkdir'); - uopz_unset_return('restore_error_handler'); - $this->directoryPermissions = null; - $this->errorHandlerRestored = false; - } - - if ($this->name() === 'testSaveAndInvalidateOpcacheWithExtension') { - uopz_unset_return('function_exists'); - uopz_unset_return('opcache_invalidate'); - $this->opcacheInvalidated = false; - } - - if ($this->name() === 'testSaveAndInvalidateOpcacheWithoutExtension') { - uopz_unset_return('function_exists'); - } - - $this->clearStoragesFiles(); - } - - public function testCreateDirectoryException(): void - { - $directory = $this->getTempDirectory() . '/file.txt'; - touch($directory); - - $storage = new ItemsStorage($directory . '/items.php'); - $permission = new Permission('createPost'); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Failed to create directory "' . $directory . '". mkdir(): File exists'); - $storage->add($permission); - } - - public function testCreateNestedDirectory(): void - { - $directory = $this->getTempDirectory() . '/test/create/nested/directory'; - - $storage = new ItemsStorage($directory . '/items.php'); - $storage->add(new Permission('createPost')); - - $this->assertFileExists($directory . '/items.php'); - // 509 - int transformation for 0775 - $this->assertSame(509, $this->directoryPermissions); - $this->assertTrue($this->errorHandlerRestored); - } - - public function testSaveWithNullAttributes(): void - { - $this->createItemsStorage()->add(new Permission('test')); - - $data = require $this->getStoragesDirectory() . DIRECTORY_SEPARATOR . 'items.php'; - $this->assertSame([['name' => 'test', 'type' => 'permission']], $data); - } - - public function testSaveAndLoadWithAllAttributes(): void - { - $time = time(); - $permission = (new Permission('testName')) - ->withDescription('testDescription') - ->withRuleName('testRule') - ->withCreatedAt($time) - ->withUpdatedAt($time); - $storage = $this->createItemsStorage(); - $storage->add($permission); - $storage = $this->createItemsStorage(); - - $this->assertEquals($permission, $storage->get('testName')); - } - - public function testLoadWithCustomGetFileUpdatedAt(): void - { - $time = 1683707079; - $storage = $this->createItemsStorage(); - $storage->add(new Permission('test')); - - $storage = new ItemsStorage( - $this->getItemsStorageFilePath(), - getFileUpdatedAt: static fn (string $filePath): int|false => $time, - ); - $this->assertSame($time, $storage->get('test')->getCreatedAt()); - } - - public function testSaveAndInvalidateOpcacheWithExtension(): void - { - $storage = $this->createItemsStorage(); - $storage->add(new Permission('test')); - - $this->assertCount(1, $storage->getAll()); - $this->assertTrue($this->opcacheInvalidated); - } - - public function testSaveAndInvalidateOpcacheWithoutExtension(): void - { - $storage = $this->createItemsStorage(); - $storage->add(new Permission('test')); - - $this->assertCount(1, $storage->getAll()); - $this->assertFalse($this->opcacheInvalidated); - } - - public function testGetFileUpdatedAtException(): void - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('getFileUpdatedAt callable must return a UNIX timestamp.'); - new ItemsStorage( - $this->getItemsStorageFilePath(), - getFileUpdatedAt: static fn (string $filePath): string => 'test', - ); - } - - protected function createItemsStorage(): ItemsStorageInterface - { - return new ItemsStorage($this->getItemsStorageFilePath()); - } - - protected function getItemsStorageForModificationAssertions(): ItemsStorageInterface - { - return $this->getItemsStorage(); - } - - private function getTempDirectory(): string - { - return __DIR__ . '/temp'; - } -} diff --git a/tests/Support/TestHelper.php b/tests/Support/TestHelper.php new file mode 100644 index 0000000..2913650 --- /dev/null +++ b/tests/Support/TestHelper.php @@ -0,0 +1,26 @@ + true); + restore_error_handler(); + return $currentHandler; + } + + public static function getDirectoryPermissions(string $path): int + { + return octdec(substr(sprintf('%o', fileperms($path)), -4)); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index f7cb39e..0000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,10 +0,0 @@ -