From b28ae9a8f209bc0787337a46aafa58f087dac17d Mon Sep 17 00:00:00 2001 From: Vedavith Ravula Date: Fri, 23 Jan 2026 12:07:20 +0530 Subject: [PATCH 01/38] Update composer.json Updated php version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3c96fd1..9ddeb15 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ ], "homepage": "https://github.com/malkusch/lock", "require": { - "php": ">=7.4 <8.5", + "php": ">=7.4 <=8.5", "psr/log": "^1.0 || ^2.0 || ^3.0", "symfony/polyfill-php80": "^1.28" }, From 8aab3506335a2d7f7f8cd194df13821b7f2b015e Mon Sep 17 00:00:00 2001 From: Vedavith Ravula Date: Fri, 23 Jan 2026 12:14:22 +0530 Subject: [PATCH 02/38] Update test-unit.yml --- .github/workflows/test-unit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index df0c79f..e3009a4 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -64,7 +64,7 @@ jobs: strategy: fail-fast: false matrix: - php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5] type: ['Phpunit', 'Phpunit Lowest'] include: - php: 'latest' From 4a4329aa964b24835083648b62c15109e05263ae Mon Sep 17 00:00:00 2001 From: Vedavith Ravula Date: Fri, 23 Jan 2026 12:25:41 +0530 Subject: [PATCH 03/38] test_workflow.yml --- .github/workflow.yml | 177 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 .github/workflow.yml diff --git a/.github/workflow.yml b/.github/workflow.yml new file mode 100644 index 0000000..807cb52 --- /dev/null +++ b/.github/workflow.yml @@ -0,0 +1,177 @@ +Skip to content +php-lock +lock +Repository navigation +Code +Issues +1 + (1) +Pull requests +3 + (3) +Actions +Security +Insights +Unit +Unit #5720 +All jobs +Run details +Workflow file for this run +.github/workflows/test-unit.yml at 8ad61c1 +name: Unit + +on: + pull_request: + push: + schedule: + - cron: '0 0/2 * * *' + +jobs: + smoke-test: + name: Smoke + runs-on: ubuntu-latest + container: + image: ghcr.io/mvorisek/image-php:${{ matrix.php }} + strategy: + fail-fast: false + matrix: + php: ['latest'] + type: ['Phpunit'] + include: + - php: 'latest' + type: 'CodingStyle' + - php: 'latest' + type: 'StaticAnalysis' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure PHP + run: | + rm /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini + php --version + - name: Install PHP dependencies + run: | + if [ "${{ matrix.type }}" != "Phpunit" ] && [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpunit/phpunit ergebnis/phpunit-slow-test-detector --dev; fi + if [ "${{ matrix.type }}" != "CodingStyle" ]; then composer remove --no-interaction --no-update friendsofphp/php-cs-fixer ergebnis/composer-normalize --dev; fi + if [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpstan/\* --dev; fi + composer remove --no-interaction --no-update ext-lzf ext-memcached ext-sysvsem --dev + composer update --ansi --prefer-dist --no-interaction --no-progress --optimize-autoloader + - name: "Run tests (only for Phpunit)" + if: startsWith(matrix.type, 'Phpunit') + run: | + vendor/bin/phpunit --exclude-group none --no-coverage --fail-on-warning --fail-on-risky $(if vendor/bin/phpunit --version | grep -q '^PHPUnit 9\.'; then echo -v; else echo --fail-on-notice --fail-on-deprecation --display-notices --display-deprecations --display-phpunit-deprecations --display-warnings --display-errors --display-incomplete --display-skipped; fi) + - name: Check Coding Style (only for CodingStyle) + if: matrix.type == 'CodingStyle' + run: | + vendor/bin/php-cs-fixer fix --dry-run --using-cache=no --diff --verbose + composer validate --strict --no-check-lock && composer normalize --dry-run --no-check-lock + - name: Run Static Analysis (only for StaticAnalysis) + if: matrix.type == 'StaticAnalysis' + run: | + echo "memory_limit = 2G" > /usr/local/etc/php/conf.d/custom-memory-limit.ini + vendor/bin/phpstan analyse -v + unit-test: + name: Unit + runs-on: ubuntu-latest + container: + image: ghcr.io/mvorisek/image-php:${{ matrix.php }} + strategy: + fail-fast: false + matrix: + php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + type: ['Phpunit', 'Phpunit Lowest'] + include: + - php: 'latest' + type: 'Phpunit Burn' + env: + LOG_COVERAGE: "${{ fromJSON('{true: \"1\", false: \"\"}')[matrix.php == '8.4' && matrix.type == 'Phpunit' && (github.event_name == 'pull_request' || (github.event_name == 'push' && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master')))] }}" + services: + mysql: + image: mysql + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 -e MYSQL_ROOT_PASSWORD=test_pass_root -e MYSQL_USER=test_user -e MYSQL_PASSWORD=test_pass -e MYSQL_DATABASE=test_db + mariadb: + image: mariadb + options: --health-cmd="mariadb-admin ping" --health-interval=10s --health-timeout=5s --health-retries=5 -e MYSQL_ROOT_PASSWORD=test_pass_root -e MYSQL_USER=test_user -e MYSQL_PASSWORD=test_pass -e MYSQL_DATABASE=test_db + postgres: + image: postgres:12-alpine + env: + POSTGRES_USER: test_user + POSTGRES_PASSWORD: test_pass + POSTGRES_DB: test_db + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + redis1: + image: redis:alpine + redis2: + image: redis:alpine + redis3: + image: redis:alpine + valkey1: + image: valkey/valkey:alpine + valkey2: + image: valkey/valkey:alpine + valkey3: + image: valkey/valkey:alpine + memcached: + image: memcached:alpine + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure PHP + run: | + install-php-extensions lzf memcached sysvsem + if [ -n "$LOG_COVERAGE" ]; then echo "xdebug.mode=coverage" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini; else rm /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini; fi + php --version + - name: Install PHP dependencies + run: | + if [ "${{ matrix.type }}" != "Phpunit" ] && [ "${{ matrix.type }}" != "Phpunit Lowest" ] && [ "${{ matrix.type }}" != "Phpunit Burn" ]; then composer remove --no-interaction --no-update phpunit/phpunit ergebnis/phpunit-slow-test-detector --dev; fi + if [ "${{ matrix.type }}" != "CodingStyle" ]; then composer remove --no-interaction --no-update friendsofphp/php-cs-fixer ergebnis/composer-normalize --dev; fi + if [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpstan/\* --dev; fi + if [ -n "$LOG_COVERAGE" ]; then composer require --no-interaction --no-install phpunit/phpcov; fi + composer update --ansi --prefer-dist --no-interaction --no-progress --optimize-autoloader + if [ "${{ matrix.type }}" = "Phpunit Lowest" ]; then composer update --ansi --prefer-dist --prefer-lowest --prefer-stable --no-interaction --no-progress --optimize-autoloader; fi + if [ "${{ matrix.type }}" = "Phpunit Burn" ]; then sed -i 's~public function runBare(): void~public function runBare(): void { gc_collect_cycles(); $memDiffs = array_fill(0, '"$(if [ \"$GITHUB_EVENT_NAME\" == \"schedule\" ]; then echo 64; else echo 16; fi)"', 0); $emitter = Event\\Facade::emitter(); for ($i = -1; $i < count($memDiffs); ++$i) { $this->_runBare(); if ($this->inIsolation) { $dispatcher = \\Closure::bind(static fn () => $emitter->dispatcher, null, Event\\DispatchingEmitter::class)(); if ($i === -1) { $dispatcherEvents = $dispatcher->flush()->asArray(); } else { $dispatcher->flush(); } foreach ($dispatcherEvents as $event) { $dispatcher->dispatch($event); } } gc_collect_cycles(); $mem = memory_get_usage(); if ($i !== -1) { $memDiffs[$i] = $mem - $memPrev; } $memPrev = $mem; rsort($memDiffs); if (array_sum($memDiffs) >= 4096 * 1024 || $memDiffs[2] > 0) { $e = new AssertionFailedError("Memory leak detected! (" . implode(" + ", array_map(static fn ($v) => number_format($v / 1024, 3, ".", " "), array_filter($memDiffs))) . " KB, " . ($i + 2) . " iterations)"); $this->status = TestStatus::failure($e->getMessage()); $emitter->testFailed($this->valueObjectForEvents(), Event\\Code\\ThrowableBuilder::from($e), Event\\Code\\ComparisonFailureBuilder::from($e)); $this->onNotSuccessfulTest($e); } } } private function _runBare(): void~' vendor/phpunit/phpunit/src/Framework/TestCase.php && cat vendor/phpunit/phpunit/src/Framework/TestCase.php | grep '_runBare('; fi + - name: Init + run: | + php -r '(new PDO("mysql:host=mysql", "root", "test_pass_root"))->exec("ALTER USER '"'"'test_user'"'"'@'"'"'%'"'"' WITH MAX_USER_CONNECTIONS 15");' + php -r '(new PDO("mysql:host=mariadb", "root", "test_pass_root"))->exec("ALTER USER '"'"'test_user'"'"'@'"'"'%'"'"' WITH MAX_USER_CONNECTIONS 15");' + php -r '(new PDO("pgsql:host=postgres;dbname=test_db", "test_user", "test_pass"))->exec("ALTER ROLE test_user CONNECTION LIMIT 1");' + if [ -n "$LOG_COVERAGE" ]; then mkdir coverage; fi + - name: "Run tests" + env: + MYSQL_DSN: "mysql:host=mysql;dbname=test_db" + MYSQL_USER: test_user + MYSQL_PASSWORD: test_pass + PGSQL_DSN: "pgsql:host=postgres;dbname=test_db" + PGSQL_USER: test_user + PGSQL_PASSWORD: test_pass + REDIS_URIS: "redis://redis1,redis://redis2,redis://redis3" + MEMCACHE_HOST: memcached + run: | + php -d opcache.enable_cli=1 vendor/bin/phpunit --exclude-group none $(if [ -n "$LOG_COVERAGE" ]; then echo --coverage-text; else echo --no-coverage; fi) --fail-on-warning --fail-on-risky $(if vendor/bin/phpunit --version | grep -q '^PHPUnit 9\.'; then echo -v; else echo --fail-on-notice --fail-on-deprecation --display-notices --display-deprecations --display-phpunit-deprecations --display-warnings --display-errors --display-incomplete --display-skipped; fi) + - name: "Run tests /w MariaDB and Valkey (only for cron)" + if: (success() || failure()) && github.event_name == 'schedule' + env: + MYSQL_DSN: "mysql:host=mariadb;dbname=test_db" + MYSQL_USER: test_user + MYSQL_PASSWORD: test_pass + PGSQL_DSN: "pgsql:host=postgres;dbname=test_db" + PGSQL_USER: test_user + PGSQL_PASSWORD: test_pass + REDIS_URIS: "redis://valkey1,redis://valkey2,redis://valkey3" + MEMCACHE_HOST: memcached + run: | + php -d opcache.enable_cli=1 vendor/bin/phpunit --exclude-group none $(if [ -n "$LOG_COVERAGE" ]; then echo --coverage-text; else echo --no-coverage; fi) --fail-on-warning --fail-on-risky $(if vendor/bin/phpunit --version | grep -q '^PHPUnit 9\.'; then echo -v; else echo --fail-on-notice --fail-on-deprecation --display-notices --display-deprecations --display-phpunit-deprecations --display-warnings --display-errors --display-incomplete --display-skipped; fi) + - name: Upload coverage logs 1/2 (only for coverage) + if: env.LOG_COVERAGE + run: | + ls -l coverage | wc -l + php -d memory_limit=2G vendor/bin/phpcov merge coverage/ --clover coverage/merged.xml + - name: Upload coverage logs 2/2 (only for coverage) + if: env.LOG_COVERAGE + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: true + files: coverage/merged.xml From 01af359115589ab75893e0b51cf805a7dc6018e8 Mon Sep 17 00:00:00 2001 From: Vedavith Ravula Date: Fri, 23 Jan 2026 12:30:35 +0530 Subject: [PATCH 04/38] Update workflow.yml Updated PHP version matrix --- .github/workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflow.yml b/.github/workflow.yml index 807cb52..1d8343b 100644 --- a/.github/workflow.yml +++ b/.github/workflow.yml @@ -79,7 +79,7 @@ jobs: strategy: fail-fast: false matrix: - php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4','8.5'] type: ['Phpunit', 'Phpunit Lowest'] include: - php: 'latest' From dabbcb10fa45f198d7f0a7a81595ec6e897b4f29 Mon Sep 17 00:00:00 2001 From: Vedavith Ravula Date: Fri, 23 Jan 2026 12:38:25 +0530 Subject: [PATCH 05/38] Create workflow.yml --- .github/workflows/workflow.yml | 168 +++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 .github/workflows/workflow.yml diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml new file mode 100644 index 0000000..c9f8bc3 --- /dev/null +++ b/.github/workflows/workflow.yml @@ -0,0 +1,168 @@ +name: Unit + +on: + pull_request: + push: + schedule: + - cron: '0 0/2 * * *' + +jobs: + smoke-test: + name: Smoke + runs-on: ubuntu-latest + container: + image: ghcr.io/mvorisek/image-php:${{ matrix.php }} + strategy: + fail-fast: false + matrix: + php: ['latest'] + type: ['Phpunit'] + include: + - php: 'latest' + type: 'CodingStyle' + - php: 'latest' + type: 'StaticAnalysis' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure PHP + run: | + rm /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini + php --version + + - name: Install PHP dependencies + run: | + if [ "${{ matrix.type }}" != "Phpunit" ] && [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpunit/phpunit ergebnis/phpunit-slow-test-detector --dev; fi + if [ "${{ matrix.type }}" != "CodingStyle" ]; then composer remove --no-interaction --no-update friendsofphp/php-cs-fixer ergebnis/composer-normalize --dev; fi + if [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpstan/\* --dev; fi + composer remove --no-interaction --no-update ext-lzf ext-memcached ext-sysvsem --dev + composer update --ansi --prefer-dist --no-interaction --no-progress --optimize-autoloader + + - name: "Run tests (only for Phpunit)" + if: startsWith(matrix.type, 'Phpunit') + run: | + vendor/bin/phpunit --exclude-group none --no-coverage --fail-on-warning --fail-on-risky $(if vendor/bin/phpunit --version | grep -q '^PHPUnit 9\.'; then echo -v; else echo --fail-on-notice --fail-on-deprecation --display-notices --display-deprecations --display-phpunit-deprecations --display-warnings --display-errors --display-incomplete --display-skipped; fi) + + - name: Check Coding Style (only for CodingStyle) + if: matrix.type == 'CodingStyle' + run: | + vendor/bin/php-cs-fixer fix --dry-run --using-cache=no --diff --verbose + composer validate --strict --no-check-lock && composer normalize --dry-run --no-check-lock + + - name: Run Static Analysis (only for StaticAnalysis) + if: matrix.type == 'StaticAnalysis' + run: | + echo "memory_limit = 2G" > /usr/local/etc/php/conf.d/custom-memory-limit.ini + vendor/bin/phpstan analyse -v + + unit-test: + name: Unit + runs-on: ubuntu-latest + container: + image: ghcr.io/mvorisek/image-php:${{ matrix.php }} + strategy: + fail-fast: false + matrix: + php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] + type: ['Phpunit', 'Phpunit Lowest'] + include: + - php: 'latest' + type: 'Phpunit Burn' + env: + LOG_COVERAGE: "${{ fromJSON('{true: \"1\", false: \"\"}')[matrix.php >= '8.4' && matrix.type == 'Phpunit' && (github.event_name == 'pull_request' || (github.event_name == 'push' && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master')))] }}" + services: + mysql: + image: mysql + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 -e MYSQL_ROOT_PASSWORD=test_pass_root -e MYSQL_USER=test_user -e MYSQL_PASSWORD=test_pass -e MYSQL_DATABASE=test_db + mariadb: + image: mariadb + options: --health-cmd="mariadb-admin ping" --health-interval=10s --health-timeout=5s --health-retries=5 -e MYSQL_ROOT_PASSWORD=test_pass_root -e MYSQL_USER=test_user -e MYSQL_PASSWORD=test_pass -e MYSQL_DATABASE=test_db + postgres: + image: postgres:12-alpine + env: + POSTGRES_USER: test_user + POSTGRES_PASSWORD: test_pass + POSTGRES_DB: test_db + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + redis1: + image: redis:alpine + redis2: + image: redis:alpine + redis3: + image: redis:alpine + valkey1: + image: valkey/valkey:alpine + valkey2: + image: valkey/valkey:alpine + valkey3: + image: valkey/valkey:alpine + memcached: + image: memcached:alpine + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure PHP + run: | + install-php-extensions lzf memcached sysvsem + if [ -n "$LOG_COVERAGE" ]; then echo "xdebug.mode=coverage" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini; else rm /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini; fi + php --version + + - name: Install PHP dependencies + run: | + if [ "${{ matrix.type }}" != "Phpunit" ] && [ "${{ matrix.type }}" != "Phpunit Lowest" ] && [ "${{ matrix.type }}" != "Phpunit Burn" ]; then composer remove --no-interaction --no-update phpunit/phpunit ergebnis/phpunit-slow-test-detector --dev; fi + if [ "${{ matrix.type }}" != "CodingStyle" ]; then composer remove --no-interaction --no-update friendsofphp/php-cs-fixer ergebnis/composer-normalize --dev; fi + if [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpstan/\* --dev; fi + if [ -n "$LOG_COVERAGE" ]; then composer require --no-interaction --no-install phpunit/phpcov; fi + composer update --ansi --prefer-dist --no-interaction --no-progress --optimize-autoloader + if [ "${{ matrix.type }}" = "Phpunit Lowest" ]; then composer update --ansi --prefer-dist --prefer-lowest --prefer-stable --no-interaction --no-progress --optimize-autoloader; fi + if [ "${{ matrix.type }}" = "Phpunit Burn" ]; then sed -i 's~public function runBare(): void~public function runBare(): void { gc_collect_cycles(); $memDiffs = array_fill(0, '"$(if [ \"$GITHUB_EVENT_NAME\" == \"schedule\" ]; then echo 64; else echo 16; fi)"', 0); $emitter = Event\\Facade::emitter(); for ($i = -1; $i < count($memDiffs); ++$i) { $this->_runBare(); if ($this->inIsolation) { $dispatcher = \\Closure::bind(static fn () => $emitter->dispatcher, null, Event\\DispatchingEmitter::class)(); if ($i === -1) { $dispatcherEvents = $dispatcher->flush()->asArray(); } else { $dispatcher->flush(); } foreach ($dispatcherEvents as $event) { $dispatcher->dispatch($event); } } gc_collect_cycles(); $mem = memory_get_usage(); if ($i !== -1) { $memDiffs[$i] = $mem - $memPrev; } $memPrev = $mem; rsort($memDiffs); if (array_sum($memDiffs) >= 4096 * 1024 || $memDiffs[2] > 0) { $e = new AssertionFailedError("Memory leak detected! (" . implode(" + ", array_map(static fn ($v) => number_format($v / 1024, 3, ".", " "), array_filter($memDiffs))) . " KB, " . ($i + 2) . " iterations)"); $this->status = TestStatus::failure($e->getMessage()); $emitter->testFailed($this->valueObjectForEvents(), Event\\Code\\ThrowableBuilder::from($e), Event\\Code\\ComparisonFailureBuilder::from($e)); $this->onNotSuccessfulTest($e); } } } private function _runBare(): void~' vendor/phpunit/phpunit/src/Framework/TestCase.php && cat vendor/phpunit/phpunit/src/Framework/TestCase.php | grep '_runBare('; fi + + - name: Init + run: | + php -r '(new PDO("mysql:host=mysql", "root", "test_pass_root"))->exec("ALTER USER '"'"'test_user'"'"'@'"'"'%'"'"' WITH MAX_USER_CONNECTIONS 15");' + php -r '(new PDO("mysql:host=mariadb", "root", "test_pass_root"))->exec("ALTER USER '"'"'test_user'"'"'@'"'"'%'"'"' WITH MAX_USER_CONNECTIONS 15");' + php -r '(new PDO("pgsql:host=postgres;dbname=test_db", "test_user", "test_pass"))->exec("ALTER ROLE test_user CONNECTION LIMIT 1");' + if [ -n "$LOG_COVERAGE" ]; then mkdir coverage; fi + + - name: "Run tests" + env: + MYSQL_DSN: "mysql:host=mysql;dbname=test_db" + MYSQL_USER: test_user + MYSQL_PASSWORD: test_pass + PGSQL_DSN: "pgsql:host=postgres;dbname=test_db" + PGSQL_USER: test_user + PGSQL_PASSWORD: test_pass + REDIS_URIS: "redis://redis1,redis://redis2,redis://redis3" + MEMCACHE_HOST: memcached + run: | + php -d opcache.enable_cli=1 vendor/bin/phpunit --exclude-group none $(if [ -n "$LOG_COVERAGE" ]; then echo --coverage-text; else echo --no-coverage; fi) --fail-on-warning --fail-on-risky $(if vendor/bin/phpunit --version | grep -q '^PHPUnit 9\.'; then echo -v; else echo --fail-on-notice --fail-on-deprecation --display-notices --display-deprecations --display-phpunit-deprecations --display-warnings --display-errors --display-incomplete --display-skipped; fi) + + - name: "Run tests /w MariaDB and Valkey (only for cron)" + if: (success() || failure()) && github.event_name == 'schedule' + env: + MYSQL_DSN: "mysql:host=mariadb;dbname=test_db" + MYSQL_USER: test_user + MYSQL_PASSWORD: test_pass + PGSQL_DSN: "pgsql:host=postgres;dbname=test_db" + PGSQL_USER: test_user + PGSQL_PASSWORD: test_pass + REDIS_URIS: "redis://valkey1,redis://valkey2,redis://valkey3" + MEMCACHE_HOST: memcached + run: | + php -d opcache.enable_cli=1 vendor/bin/phpunit --exclude-group none $(if [ -n "$LOG_COVERAGE" ]; then echo --coverage-text; else echo --no-coverage; fi) --fail-on-warning --fail-on-risky $(if vendor/bin/phpunit --version | grep -q '^PHPUnit 9\.'; then echo -v; else echo --fail-on-notice --fail-on-deprecation --display-notices --display-deprecations --display-phpunit-deprecations --display-warnings --display-errors --display-incomplete --display-skipped; fi) + + - name: Upload coverage logs 1/2 (only for coverage) + if: env.LOG_COVERAGE + run: | + ls -l coverage | wc -l + php -d memory_limit=2G vendor/bin/phpcov merge coverage/ --clover coverage/merged.xml + + - name: Upload coverage logs 2/2 (only for coverage) + if: env.LOG_COVERAGE + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: true + files: coverage/merged.xml From 6c169e2b22a5b9b7dd578e95276143bf195dc58b Mon Sep 17 00:00:00 2001 From: Vedavith Ravula Date: Fri, 23 Jan 2026 14:01:13 +0530 Subject: [PATCH 06/38] Updated composer.json to support php8.5 Updated composer.json to support php8.5 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9ddeb15..643e026 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ ], "homepage": "https://github.com/malkusch/lock", "require": { - "php": ">=7.4 <=8.5", + "php": ">=7.4 <= ^8.4", "psr/log": "^1.0 || ^2.0 || ^3.0", "symfony/polyfill-php80": "^1.28" }, From 71df896de865da1c6a483ae73b7e62ca217ddbe8 Mon Sep 17 00:00:00 2001 From: Vedavith Ravula Date: Fri, 23 Jan 2026 14:01:49 +0530 Subject: [PATCH 07/38] Update composer.json to support 8.5 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 643e026..95616e5 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ ], "homepage": "https://github.com/malkusch/lock", "require": { - "php": ">=7.4 <= ^8.4", + "php": ">=7.4 <= ^8.5", "psr/log": "^1.0 || ^2.0 || ^3.0", "symfony/polyfill-php80": "^1.28" }, From 55d5b2ee4fefa87d2f58a6e6a0cee1dd6c793d75 Mon Sep 17 00:00:00 2001 From: Vedavith Ravula Date: Fri, 23 Jan 2026 14:09:02 +0530 Subject: [PATCH 08/38] Update composer.json Updated Composer Json with PHP 85 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 95616e5..3f5b19e 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ ], "homepage": "https://github.com/malkusch/lock", "require": { - "php": ">=7.4 <= ^8.5", + "php": "7.4 || ^8.5", "psr/log": "^1.0 || ^2.0 || ^3.0", "symfony/polyfill-php80": "^1.28" }, From 0263dcf83097a3c88a4d82c56bd7f5a7e0b97688 Mon Sep 17 00:00:00 2001 From: Vedavith Ravula Date: Fri, 23 Jan 2026 14:12:07 +0530 Subject: [PATCH 09/38] Update workflow.yml --- .github/workflow.yml | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/.github/workflow.yml b/.github/workflow.yml index 1d8343b..6ec3dec 100644 --- a/.github/workflow.yml +++ b/.github/workflow.yml @@ -1,23 +1,3 @@ -Skip to content -php-lock -lock -Repository navigation -Code -Issues -1 - (1) -Pull requests -3 - (3) -Actions -Security -Insights -Unit -Unit #5720 -All jobs -Run details -Workflow file for this run -.github/workflows/test-unit.yml at 8ad61c1 name: Unit on: From 23a35ab2c49640e95ee6b72b2944b7eda39e6768 Mon Sep 17 00:00:00 2001 From: Vedavith Ravula Date: Fri, 23 Jan 2026 14:12:43 +0530 Subject: [PATCH 10/38] Delete .github/workflows/workflow.yml --- .github/workflows/workflow.yml | 168 --------------------------------- 1 file changed, 168 deletions(-) delete mode 100644 .github/workflows/workflow.yml diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml deleted file mode 100644 index c9f8bc3..0000000 --- a/.github/workflows/workflow.yml +++ /dev/null @@ -1,168 +0,0 @@ -name: Unit - -on: - pull_request: - push: - schedule: - - cron: '0 0/2 * * *' - -jobs: - smoke-test: - name: Smoke - runs-on: ubuntu-latest - container: - image: ghcr.io/mvorisek/image-php:${{ matrix.php }} - strategy: - fail-fast: false - matrix: - php: ['latest'] - type: ['Phpunit'] - include: - - php: 'latest' - type: 'CodingStyle' - - php: 'latest' - type: 'StaticAnalysis' - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Configure PHP - run: | - rm /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini - php --version - - - name: Install PHP dependencies - run: | - if [ "${{ matrix.type }}" != "Phpunit" ] && [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpunit/phpunit ergebnis/phpunit-slow-test-detector --dev; fi - if [ "${{ matrix.type }}" != "CodingStyle" ]; then composer remove --no-interaction --no-update friendsofphp/php-cs-fixer ergebnis/composer-normalize --dev; fi - if [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpstan/\* --dev; fi - composer remove --no-interaction --no-update ext-lzf ext-memcached ext-sysvsem --dev - composer update --ansi --prefer-dist --no-interaction --no-progress --optimize-autoloader - - - name: "Run tests (only for Phpunit)" - if: startsWith(matrix.type, 'Phpunit') - run: | - vendor/bin/phpunit --exclude-group none --no-coverage --fail-on-warning --fail-on-risky $(if vendor/bin/phpunit --version | grep -q '^PHPUnit 9\.'; then echo -v; else echo --fail-on-notice --fail-on-deprecation --display-notices --display-deprecations --display-phpunit-deprecations --display-warnings --display-errors --display-incomplete --display-skipped; fi) - - - name: Check Coding Style (only for CodingStyle) - if: matrix.type == 'CodingStyle' - run: | - vendor/bin/php-cs-fixer fix --dry-run --using-cache=no --diff --verbose - composer validate --strict --no-check-lock && composer normalize --dry-run --no-check-lock - - - name: Run Static Analysis (only for StaticAnalysis) - if: matrix.type == 'StaticAnalysis' - run: | - echo "memory_limit = 2G" > /usr/local/etc/php/conf.d/custom-memory-limit.ini - vendor/bin/phpstan analyse -v - - unit-test: - name: Unit - runs-on: ubuntu-latest - container: - image: ghcr.io/mvorisek/image-php:${{ matrix.php }} - strategy: - fail-fast: false - matrix: - php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] - type: ['Phpunit', 'Phpunit Lowest'] - include: - - php: 'latest' - type: 'Phpunit Burn' - env: - LOG_COVERAGE: "${{ fromJSON('{true: \"1\", false: \"\"}')[matrix.php >= '8.4' && matrix.type == 'Phpunit' && (github.event_name == 'pull_request' || (github.event_name == 'push' && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master')))] }}" - services: - mysql: - image: mysql - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 -e MYSQL_ROOT_PASSWORD=test_pass_root -e MYSQL_USER=test_user -e MYSQL_PASSWORD=test_pass -e MYSQL_DATABASE=test_db - mariadb: - image: mariadb - options: --health-cmd="mariadb-admin ping" --health-interval=10s --health-timeout=5s --health-retries=5 -e MYSQL_ROOT_PASSWORD=test_pass_root -e MYSQL_USER=test_user -e MYSQL_PASSWORD=test_pass -e MYSQL_DATABASE=test_db - postgres: - image: postgres:12-alpine - env: - POSTGRES_USER: test_user - POSTGRES_PASSWORD: test_pass - POSTGRES_DB: test_db - options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 - redis1: - image: redis:alpine - redis2: - image: redis:alpine - redis3: - image: redis:alpine - valkey1: - image: valkey/valkey:alpine - valkey2: - image: valkey/valkey:alpine - valkey3: - image: valkey/valkey:alpine - memcached: - image: memcached:alpine - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Configure PHP - run: | - install-php-extensions lzf memcached sysvsem - if [ -n "$LOG_COVERAGE" ]; then echo "xdebug.mode=coverage" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini; else rm /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini; fi - php --version - - - name: Install PHP dependencies - run: | - if [ "${{ matrix.type }}" != "Phpunit" ] && [ "${{ matrix.type }}" != "Phpunit Lowest" ] && [ "${{ matrix.type }}" != "Phpunit Burn" ]; then composer remove --no-interaction --no-update phpunit/phpunit ergebnis/phpunit-slow-test-detector --dev; fi - if [ "${{ matrix.type }}" != "CodingStyle" ]; then composer remove --no-interaction --no-update friendsofphp/php-cs-fixer ergebnis/composer-normalize --dev; fi - if [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpstan/\* --dev; fi - if [ -n "$LOG_COVERAGE" ]; then composer require --no-interaction --no-install phpunit/phpcov; fi - composer update --ansi --prefer-dist --no-interaction --no-progress --optimize-autoloader - if [ "${{ matrix.type }}" = "Phpunit Lowest" ]; then composer update --ansi --prefer-dist --prefer-lowest --prefer-stable --no-interaction --no-progress --optimize-autoloader; fi - if [ "${{ matrix.type }}" = "Phpunit Burn" ]; then sed -i 's~public function runBare(): void~public function runBare(): void { gc_collect_cycles(); $memDiffs = array_fill(0, '"$(if [ \"$GITHUB_EVENT_NAME\" == \"schedule\" ]; then echo 64; else echo 16; fi)"', 0); $emitter = Event\\Facade::emitter(); for ($i = -1; $i < count($memDiffs); ++$i) { $this->_runBare(); if ($this->inIsolation) { $dispatcher = \\Closure::bind(static fn () => $emitter->dispatcher, null, Event\\DispatchingEmitter::class)(); if ($i === -1) { $dispatcherEvents = $dispatcher->flush()->asArray(); } else { $dispatcher->flush(); } foreach ($dispatcherEvents as $event) { $dispatcher->dispatch($event); } } gc_collect_cycles(); $mem = memory_get_usage(); if ($i !== -1) { $memDiffs[$i] = $mem - $memPrev; } $memPrev = $mem; rsort($memDiffs); if (array_sum($memDiffs) >= 4096 * 1024 || $memDiffs[2] > 0) { $e = new AssertionFailedError("Memory leak detected! (" . implode(" + ", array_map(static fn ($v) => number_format($v / 1024, 3, ".", " "), array_filter($memDiffs))) . " KB, " . ($i + 2) . " iterations)"); $this->status = TestStatus::failure($e->getMessage()); $emitter->testFailed($this->valueObjectForEvents(), Event\\Code\\ThrowableBuilder::from($e), Event\\Code\\ComparisonFailureBuilder::from($e)); $this->onNotSuccessfulTest($e); } } } private function _runBare(): void~' vendor/phpunit/phpunit/src/Framework/TestCase.php && cat vendor/phpunit/phpunit/src/Framework/TestCase.php | grep '_runBare('; fi - - - name: Init - run: | - php -r '(new PDO("mysql:host=mysql", "root", "test_pass_root"))->exec("ALTER USER '"'"'test_user'"'"'@'"'"'%'"'"' WITH MAX_USER_CONNECTIONS 15");' - php -r '(new PDO("mysql:host=mariadb", "root", "test_pass_root"))->exec("ALTER USER '"'"'test_user'"'"'@'"'"'%'"'"' WITH MAX_USER_CONNECTIONS 15");' - php -r '(new PDO("pgsql:host=postgres;dbname=test_db", "test_user", "test_pass"))->exec("ALTER ROLE test_user CONNECTION LIMIT 1");' - if [ -n "$LOG_COVERAGE" ]; then mkdir coverage; fi - - - name: "Run tests" - env: - MYSQL_DSN: "mysql:host=mysql;dbname=test_db" - MYSQL_USER: test_user - MYSQL_PASSWORD: test_pass - PGSQL_DSN: "pgsql:host=postgres;dbname=test_db" - PGSQL_USER: test_user - PGSQL_PASSWORD: test_pass - REDIS_URIS: "redis://redis1,redis://redis2,redis://redis3" - MEMCACHE_HOST: memcached - run: | - php -d opcache.enable_cli=1 vendor/bin/phpunit --exclude-group none $(if [ -n "$LOG_COVERAGE" ]; then echo --coverage-text; else echo --no-coverage; fi) --fail-on-warning --fail-on-risky $(if vendor/bin/phpunit --version | grep -q '^PHPUnit 9\.'; then echo -v; else echo --fail-on-notice --fail-on-deprecation --display-notices --display-deprecations --display-phpunit-deprecations --display-warnings --display-errors --display-incomplete --display-skipped; fi) - - - name: "Run tests /w MariaDB and Valkey (only for cron)" - if: (success() || failure()) && github.event_name == 'schedule' - env: - MYSQL_DSN: "mysql:host=mariadb;dbname=test_db" - MYSQL_USER: test_user - MYSQL_PASSWORD: test_pass - PGSQL_DSN: "pgsql:host=postgres;dbname=test_db" - PGSQL_USER: test_user - PGSQL_PASSWORD: test_pass - REDIS_URIS: "redis://valkey1,redis://valkey2,redis://valkey3" - MEMCACHE_HOST: memcached - run: | - php -d opcache.enable_cli=1 vendor/bin/phpunit --exclude-group none $(if [ -n "$LOG_COVERAGE" ]; then echo --coverage-text; else echo --no-coverage; fi) --fail-on-warning --fail-on-risky $(if vendor/bin/phpunit --version | grep -q '^PHPUnit 9\.'; then echo -v; else echo --fail-on-notice --fail-on-deprecation --display-notices --display-deprecations --display-phpunit-deprecations --display-warnings --display-errors --display-incomplete --display-skipped; fi) - - - name: Upload coverage logs 1/2 (only for coverage) - if: env.LOG_COVERAGE - run: | - ls -l coverage | wc -l - php -d memory_limit=2G vendor/bin/phpcov merge coverage/ --clover coverage/merged.xml - - - name: Upload coverage logs 2/2 (only for coverage) - if: env.LOG_COVERAGE - uses: codecov/codecov-action@v5 - with: - token: ${{ secrets.CODECOV_TOKEN }} - fail_ci_if_error: true - files: coverage/merged.xml From b37e49cbbe1742d5504ee7d36a1e9899fc406207 Mon Sep 17 00:00:00 2001 From: Vedavith Ravula Date: Fri, 23 Jan 2026 14:13:21 +0530 Subject: [PATCH 11/38] Update test-unit.yml --- .github/workflows/test-unit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index df0c79f..8bd57de 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -64,7 +64,7 @@ jobs: strategy: fail-fast: false matrix: - php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] type: ['Phpunit', 'Phpunit Lowest'] include: - php: 'latest' From c9a2cbcb05ce9f96c51abe2da0272271a0615d80 Mon Sep 17 00:00:00 2001 From: Vedavith Ravula Date: Fri, 23 Jan 2026 14:29:38 +0530 Subject: [PATCH 12/38] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3f5b19e..e14ac4e 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ ], "homepage": "https://github.com/malkusch/lock", "require": { - "php": "7.4 || ^8.5", + "php": ">=7.4 <=8.6", "psr/log": "^1.0 || ^2.0 || ^3.0", "symfony/polyfill-php80": "^1.28" }, From 23c345d989837a8057eceb4a40cfe8c2bec4d08a Mon Sep 17 00:00:00 2001 From: Vedavith Ravula Date: Fri, 23 Jan 2026 14:35:24 +0530 Subject: [PATCH 13/38] Update composer.json - php85 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e14ac4e..618cecd 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ ], "homepage": "https://github.com/malkusch/lock", "require": { - "php": ">=7.4 <=8.6", + "php": ">=7.4 <8.6", "psr/log": "^1.0 || ^2.0 || ^3.0", "symfony/polyfill-php80": "^1.28" }, From 044a97bb1752ed60fe2dae08b0f0de594c977053 Mon Sep 17 00:00:00 2001 From: Vedavith Ravula Date: Fri, 23 Jan 2026 14:39:27 +0530 Subject: [PATCH 14/38] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 618cecd..5bf0d95 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ ], "homepage": "https://github.com/malkusch/lock", "require": { - "php": ">=7.4 <8.6", + "php": ">=7.4 <8.6", "psr/log": "^1.0 || ^2.0 || ^3.0", "symfony/polyfill-php80": "^1.28" }, From 09f25738f53f6fc95d3c4e804622834cbb314a13 Mon Sep 17 00:00:00 2001 From: Vedavith Ravula Date: Fri, 23 Jan 2026 15:10:13 +0530 Subject: [PATCH 15/38] Update composer.json - eloquent/liberator Updated the composer for eloquent/liberator --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5bf0d95..ef36cc5 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,7 @@ "ext-pdo_sqlite": "*", "ext-redis": "*", "ext-sysvsem": "*", - "eloquent/liberator": "^2.0 || ^3.0", + "eloquent/liberator": "^3.0 || ^2.0", "ergebnis/composer-normalize": "^2.13", "ergebnis/phpunit-slow-test-detector": "^2.9", "friendsofphp/php-cs-fixer": "^3.0", From fcb17df0e6e686d9c6c88d2bd453eed9f5905222 Mon Sep 17 00:00:00 2001 From: Vedavith Ravula Date: Fri, 23 Jan 2026 15:18:55 +0530 Subject: [PATCH 16/38] Update composer.json Updated related versions --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index ef36cc5..3a9ebc2 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,7 @@ "ext-pdo_sqlite": "*", "ext-redis": "*", "ext-sysvsem": "*", - "eloquent/liberator": "^3.0 || ^2.0", + "eloquent/liberator": "^3.0", "ergebnis/composer-normalize": "^2.13", "ergebnis/phpunit-slow-test-detector": "^2.9", "friendsofphp/php-cs-fixer": "^3.0", @@ -59,7 +59,7 @@ "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.5.25 || ^10.0 || ^11.0", "predis/predis": "^1.1.8 || ^2.0", - "spatie/async": "^1.5" + "spatie/async": "^1.8" }, "suggest": { "ext-igbinary": "To use this library with PHP Redis igbinary serializer enabled.", From c1845a1d5bfa6eda9f7f54855b04982c55274706 Mon Sep 17 00:00:00 2001 From: Vedavith Ravula Date: Fri, 23 Jan 2026 15:25:41 +0530 Subject: [PATCH 17/38] Update composer.json - Backward compatibility --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 3a9ebc2..1a950ba 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,7 @@ "ext-pdo_sqlite": "*", "ext-redis": "*", "ext-sysvsem": "*", - "eloquent/liberator": "^3.0", + "eloquent/liberator": "^2.0 || ^3.0", "ergebnis/composer-normalize": "^2.13", "ergebnis/phpunit-slow-test-detector": "^2.9", "friendsofphp/php-cs-fixer": "^3.0", @@ -59,7 +59,7 @@ "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.5.25 || ^10.0 || ^11.0", "predis/predis": "^1.1.8 || ^2.0", - "spatie/async": "^1.8" + "spatie/async": "^1.5 || ^1.8" }, "suggest": { "ext-igbinary": "To use this library with PHP Redis igbinary serializer enabled.", From 49452c6d70946af9776c1a6b857b183fee430bdc Mon Sep 17 00:00:00 2001 From: vedavith Date: Sat, 24 Jan 2026 06:43:16 +0530 Subject: [PATCH 18/38] Prevent INF/NAN from being passed to acquireMutex --- .github/workflow.yml | 157 --------------------------------- src/Mutex/DistributedMutex.php | 9 +- 2 files changed, 8 insertions(+), 158 deletions(-) delete mode 100644 .github/workflow.yml diff --git a/.github/workflow.yml b/.github/workflow.yml deleted file mode 100644 index 6ec3dec..0000000 --- a/.github/workflow.yml +++ /dev/null @@ -1,157 +0,0 @@ -name: Unit - -on: - pull_request: - push: - schedule: - - cron: '0 0/2 * * *' - -jobs: - smoke-test: - name: Smoke - runs-on: ubuntu-latest - container: - image: ghcr.io/mvorisek/image-php:${{ matrix.php }} - strategy: - fail-fast: false - matrix: - php: ['latest'] - type: ['Phpunit'] - include: - - php: 'latest' - type: 'CodingStyle' - - php: 'latest' - type: 'StaticAnalysis' - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Configure PHP - run: | - rm /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini - php --version - - name: Install PHP dependencies - run: | - if [ "${{ matrix.type }}" != "Phpunit" ] && [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpunit/phpunit ergebnis/phpunit-slow-test-detector --dev; fi - if [ "${{ matrix.type }}" != "CodingStyle" ]; then composer remove --no-interaction --no-update friendsofphp/php-cs-fixer ergebnis/composer-normalize --dev; fi - if [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpstan/\* --dev; fi - composer remove --no-interaction --no-update ext-lzf ext-memcached ext-sysvsem --dev - composer update --ansi --prefer-dist --no-interaction --no-progress --optimize-autoloader - - name: "Run tests (only for Phpunit)" - if: startsWith(matrix.type, 'Phpunit') - run: | - vendor/bin/phpunit --exclude-group none --no-coverage --fail-on-warning --fail-on-risky $(if vendor/bin/phpunit --version | grep -q '^PHPUnit 9\.'; then echo -v; else echo --fail-on-notice --fail-on-deprecation --display-notices --display-deprecations --display-phpunit-deprecations --display-warnings --display-errors --display-incomplete --display-skipped; fi) - - name: Check Coding Style (only for CodingStyle) - if: matrix.type == 'CodingStyle' - run: | - vendor/bin/php-cs-fixer fix --dry-run --using-cache=no --diff --verbose - composer validate --strict --no-check-lock && composer normalize --dry-run --no-check-lock - - name: Run Static Analysis (only for StaticAnalysis) - if: matrix.type == 'StaticAnalysis' - run: | - echo "memory_limit = 2G" > /usr/local/etc/php/conf.d/custom-memory-limit.ini - vendor/bin/phpstan analyse -v - unit-test: - name: Unit - runs-on: ubuntu-latest - container: - image: ghcr.io/mvorisek/image-php:${{ matrix.php }} - strategy: - fail-fast: false - matrix: - php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4','8.5'] - type: ['Phpunit', 'Phpunit Lowest'] - include: - - php: 'latest' - type: 'Phpunit Burn' - env: - LOG_COVERAGE: "${{ fromJSON('{true: \"1\", false: \"\"}')[matrix.php == '8.4' && matrix.type == 'Phpunit' && (github.event_name == 'pull_request' || (github.event_name == 'push' && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master')))] }}" - services: - mysql: - image: mysql - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 -e MYSQL_ROOT_PASSWORD=test_pass_root -e MYSQL_USER=test_user -e MYSQL_PASSWORD=test_pass -e MYSQL_DATABASE=test_db - mariadb: - image: mariadb - options: --health-cmd="mariadb-admin ping" --health-interval=10s --health-timeout=5s --health-retries=5 -e MYSQL_ROOT_PASSWORD=test_pass_root -e MYSQL_USER=test_user -e MYSQL_PASSWORD=test_pass -e MYSQL_DATABASE=test_db - postgres: - image: postgres:12-alpine - env: - POSTGRES_USER: test_user - POSTGRES_PASSWORD: test_pass - POSTGRES_DB: test_db - options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 - redis1: - image: redis:alpine - redis2: - image: redis:alpine - redis3: - image: redis:alpine - valkey1: - image: valkey/valkey:alpine - valkey2: - image: valkey/valkey:alpine - valkey3: - image: valkey/valkey:alpine - memcached: - image: memcached:alpine - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Configure PHP - run: | - install-php-extensions lzf memcached sysvsem - if [ -n "$LOG_COVERAGE" ]; then echo "xdebug.mode=coverage" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini; else rm /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini; fi - php --version - - name: Install PHP dependencies - run: | - if [ "${{ matrix.type }}" != "Phpunit" ] && [ "${{ matrix.type }}" != "Phpunit Lowest" ] && [ "${{ matrix.type }}" != "Phpunit Burn" ]; then composer remove --no-interaction --no-update phpunit/phpunit ergebnis/phpunit-slow-test-detector --dev; fi - if [ "${{ matrix.type }}" != "CodingStyle" ]; then composer remove --no-interaction --no-update friendsofphp/php-cs-fixer ergebnis/composer-normalize --dev; fi - if [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpstan/\* --dev; fi - if [ -n "$LOG_COVERAGE" ]; then composer require --no-interaction --no-install phpunit/phpcov; fi - composer update --ansi --prefer-dist --no-interaction --no-progress --optimize-autoloader - if [ "${{ matrix.type }}" = "Phpunit Lowest" ]; then composer update --ansi --prefer-dist --prefer-lowest --prefer-stable --no-interaction --no-progress --optimize-autoloader; fi - if [ "${{ matrix.type }}" = "Phpunit Burn" ]; then sed -i 's~public function runBare(): void~public function runBare(): void { gc_collect_cycles(); $memDiffs = array_fill(0, '"$(if [ \"$GITHUB_EVENT_NAME\" == \"schedule\" ]; then echo 64; else echo 16; fi)"', 0); $emitter = Event\\Facade::emitter(); for ($i = -1; $i < count($memDiffs); ++$i) { $this->_runBare(); if ($this->inIsolation) { $dispatcher = \\Closure::bind(static fn () => $emitter->dispatcher, null, Event\\DispatchingEmitter::class)(); if ($i === -1) { $dispatcherEvents = $dispatcher->flush()->asArray(); } else { $dispatcher->flush(); } foreach ($dispatcherEvents as $event) { $dispatcher->dispatch($event); } } gc_collect_cycles(); $mem = memory_get_usage(); if ($i !== -1) { $memDiffs[$i] = $mem - $memPrev; } $memPrev = $mem; rsort($memDiffs); if (array_sum($memDiffs) >= 4096 * 1024 || $memDiffs[2] > 0) { $e = new AssertionFailedError("Memory leak detected! (" . implode(" + ", array_map(static fn ($v) => number_format($v / 1024, 3, ".", " "), array_filter($memDiffs))) . " KB, " . ($i + 2) . " iterations)"); $this->status = TestStatus::failure($e->getMessage()); $emitter->testFailed($this->valueObjectForEvents(), Event\\Code\\ThrowableBuilder::from($e), Event\\Code\\ComparisonFailureBuilder::from($e)); $this->onNotSuccessfulTest($e); } } } private function _runBare(): void~' vendor/phpunit/phpunit/src/Framework/TestCase.php && cat vendor/phpunit/phpunit/src/Framework/TestCase.php | grep '_runBare('; fi - - name: Init - run: | - php -r '(new PDO("mysql:host=mysql", "root", "test_pass_root"))->exec("ALTER USER '"'"'test_user'"'"'@'"'"'%'"'"' WITH MAX_USER_CONNECTIONS 15");' - php -r '(new PDO("mysql:host=mariadb", "root", "test_pass_root"))->exec("ALTER USER '"'"'test_user'"'"'@'"'"'%'"'"' WITH MAX_USER_CONNECTIONS 15");' - php -r '(new PDO("pgsql:host=postgres;dbname=test_db", "test_user", "test_pass"))->exec("ALTER ROLE test_user CONNECTION LIMIT 1");' - if [ -n "$LOG_COVERAGE" ]; then mkdir coverage; fi - - name: "Run tests" - env: - MYSQL_DSN: "mysql:host=mysql;dbname=test_db" - MYSQL_USER: test_user - MYSQL_PASSWORD: test_pass - PGSQL_DSN: "pgsql:host=postgres;dbname=test_db" - PGSQL_USER: test_user - PGSQL_PASSWORD: test_pass - REDIS_URIS: "redis://redis1,redis://redis2,redis://redis3" - MEMCACHE_HOST: memcached - run: | - php -d opcache.enable_cli=1 vendor/bin/phpunit --exclude-group none $(if [ -n "$LOG_COVERAGE" ]; then echo --coverage-text; else echo --no-coverage; fi) --fail-on-warning --fail-on-risky $(if vendor/bin/phpunit --version | grep -q '^PHPUnit 9\.'; then echo -v; else echo --fail-on-notice --fail-on-deprecation --display-notices --display-deprecations --display-phpunit-deprecations --display-warnings --display-errors --display-incomplete --display-skipped; fi) - - name: "Run tests /w MariaDB and Valkey (only for cron)" - if: (success() || failure()) && github.event_name == 'schedule' - env: - MYSQL_DSN: "mysql:host=mariadb;dbname=test_db" - MYSQL_USER: test_user - MYSQL_PASSWORD: test_pass - PGSQL_DSN: "pgsql:host=postgres;dbname=test_db" - PGSQL_USER: test_user - PGSQL_PASSWORD: test_pass - REDIS_URIS: "redis://valkey1,redis://valkey2,redis://valkey3" - MEMCACHE_HOST: memcached - run: | - php -d opcache.enable_cli=1 vendor/bin/phpunit --exclude-group none $(if [ -n "$LOG_COVERAGE" ]; then echo --coverage-text; else echo --no-coverage; fi) --fail-on-warning --fail-on-risky $(if vendor/bin/phpunit --version | grep -q '^PHPUnit 9\.'; then echo -v; else echo --fail-on-notice --fail-on-deprecation --display-notices --display-deprecations --display-phpunit-deprecations --display-warnings --display-errors --display-incomplete --display-skipped; fi) - - name: Upload coverage logs 1/2 (only for coverage) - if: env.LOG_COVERAGE - run: | - ls -l coverage | wc -l - php -d memory_limit=2G vendor/bin/phpcov merge coverage/ --clover coverage/merged.xml - - name: Upload coverage logs 2/2 (only for coverage) - if: env.LOG_COVERAGE - uses: codecov/codecov-action@v5 - with: - token: ${{ secrets.CODECOV_TOKEN }} - fail_ci_if_error: true - files: coverage/merged.xml diff --git a/src/Mutex/DistributedMutex.php b/src/Mutex/DistributedMutex.php index 8ccc862..da15d97 100644 --- a/src/Mutex/DistributedMutex.php +++ b/src/Mutex/DistributedMutex.php @@ -57,7 +57,14 @@ protected function acquireWithToken(string $key, float $expireTimeout) $exception = null; foreach ($this->getMutexesInRandomOrder() as $index => $mutex) { try { - if ($this->acquireMutex($mutex, $key, $acquireTimeout - (microtime(true) - $startTs), $expireTimeout)) { + $remainingTimeout = $acquireTimeout - (microtime(true) - $startTs); + + // Prevent INF/NAN from being passed to acquireMutex + if (is_infinite($remainingTimeout) || is_nan($remainingTimeout) || $remainingTimeout < 0) { + $remainingTimeout = 0.0; + } + + if ($this->acquireMutex($mutex, $key, $remainingTimeout, $expireTimeout)) { $acquiredIndexes[] = $index; } } catch (LockAcquireException $exception) { From 2f5653f5f12713ff9a147d9d3c1ff07aca01561a Mon Sep 17 00:00:00 2001 From: vedavith Date: Sat, 24 Jan 2026 07:03:04 +0530 Subject: [PATCH 19/38] Updated composer - eloquent --- composer.json | 2 +- src/Mutex/DistributedMutex.php | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 1a950ba..cd75e36 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,7 @@ "ext-pdo_sqlite": "*", "ext-redis": "*", "ext-sysvsem": "*", - "eloquent/liberator": "^2.0 || ^3.0", + "eloquent/liberator": "^1.0 || ^2.0 || ^3.0", "ergebnis/composer-normalize": "^2.13", "ergebnis/phpunit-slow-test-detector": "^2.9", "friendsofphp/php-cs-fixer": "^3.0", diff --git a/src/Mutex/DistributedMutex.php b/src/Mutex/DistributedMutex.php index da15d97..a7aeb37 100644 --- a/src/Mutex/DistributedMutex.php +++ b/src/Mutex/DistributedMutex.php @@ -58,8 +58,7 @@ protected function acquireWithToken(string $key, float $expireTimeout) foreach ($this->getMutexesInRandomOrder() as $index => $mutex) { try { $remainingTimeout = $acquireTimeout - (microtime(true) - $startTs); - - // Prevent INF/NAN from being passed to acquireMutex +// Prevent INF/NAN from being passed to acquireMutex if (is_infinite($remainingTimeout) || is_nan($remainingTimeout) || $remainingTimeout < 0) { $remainingTimeout = 0.0; } From 355875c18fcfab990eed4ad9be4de80cb2ee3be2 Mon Sep 17 00:00:00 2001 From: vedavith Date: Sat, 24 Jan 2026 07:17:41 +0530 Subject: [PATCH 20/38] Updated files: composer.json src/Mutex/DistributedMutex.php --- composer.json | 2 +- src/Mutex/DistributedMutex.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index cd75e36..1a950ba 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,7 @@ "ext-pdo_sqlite": "*", "ext-redis": "*", "ext-sysvsem": "*", - "eloquent/liberator": "^1.0 || ^2.0 || ^3.0", + "eloquent/liberator": "^2.0 || ^3.0", "ergebnis/composer-normalize": "^2.13", "ergebnis/phpunit-slow-test-detector": "^2.9", "friendsofphp/php-cs-fixer": "^3.0", diff --git a/src/Mutex/DistributedMutex.php b/src/Mutex/DistributedMutex.php index a7aeb37..e84c182 100644 --- a/src/Mutex/DistributedMutex.php +++ b/src/Mutex/DistributedMutex.php @@ -58,7 +58,7 @@ protected function acquireWithToken(string $key, float $expireTimeout) foreach ($this->getMutexesInRandomOrder() as $index => $mutex) { try { $remainingTimeout = $acquireTimeout - (microtime(true) - $startTs); -// Prevent INF/NAN from being passed to acquireMutex + // Prevent INF/NAN from being passed to acquireMutex if (is_infinite($remainingTimeout) || is_nan($remainingTimeout) || $remainingTimeout < 0) { $remainingTimeout = 0.0; } From 6337b31f834e467da1fca2880f7c81f268503883 Mon Sep 17 00:00:00 2001 From: vedavith Date: Sat, 24 Jan 2026 07:28:26 +0530 Subject: [PATCH 21/38] Refactor formatTimeout to simplify NaN and infinite value handling --- src/Util/LockUtil.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Util/LockUtil.php b/src/Util/LockUtil.php index d07add1..2ff3d33 100644 --- a/src/Util/LockUtil.php +++ b/src/Util/LockUtil.php @@ -71,11 +71,13 @@ public function castFloatToInt(float $value): int */ public function formatTimeout(float $value): string { + if (!is_finite($value) || is_nan($value)) { + return '0.0'; + } $res = (string) round($value, 6); - if (\is_finite($value) && strpos($res, '.') === false) { + if (strpos($res, '.') === false) { $res .= '.0'; } - return $res; } } From ff26db6c9c35308c9f840ee31eb43587d2a5e830 Mon Sep 17 00:00:00 2001 From: vedavith Date: Tue, 27 Jan 2026 11:36:44 +0530 Subject: [PATCH 22/38] Refactor tests to remove dependency on Eloquent Liberator and introduce TestAccess for property access --- composer.json | 1 - tests/Mutex/FlockMutexTest.php | 6 +-- tests/Mutex/MutexConcurrencyTest.php | 10 ++-- tests/Mutex/MutexTest.php | 10 ++-- tests/Mutex/PostgreSQLMutexTest.php | 5 +- tests/TestAccess.php | 69 ++++++++++++++++++++++++++++ 6 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 tests/TestAccess.php diff --git a/composer.json b/composer.json index 1a950ba..402efaf 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,6 @@ "ext-pdo_sqlite": "*", "ext-redis": "*", "ext-sysvsem": "*", - "eloquent/liberator": "^2.0 || ^3.0", "ergebnis/composer-normalize": "^2.13", "ergebnis/phpunit-slow-test-detector": "^2.9", "friendsofphp/php-cs-fixer": "^3.0", diff --git a/tests/Mutex/FlockMutexTest.php b/tests/Mutex/FlockMutexTest.php index cd147d1..ca8ec88 100644 --- a/tests/Mutex/FlockMutexTest.php +++ b/tests/Mutex/FlockMutexTest.php @@ -4,7 +4,7 @@ namespace Malkusch\Lock\Tests\Mutex; -use Eloquent\Liberator\Liberator; +require_once __DIR__ . '/../TestAccess.php'; use Malkusch\Lock\Exception\DeadlineException; use Malkusch\Lock\Exception\LockAcquireTimeoutException; use Malkusch\Lock\Mutex\FlockMutex; @@ -28,7 +28,7 @@ protected function setUp(): void $this->file = LockUtil::getInstance()->makeRandomTemporaryFilePath('flock'); touch($this->file); - $this->mutex = Liberator::liberate(new FlockMutex(fopen($this->file, 'r'), 1)); // @phpstan-ignore assign.propertyType + $this->mutex = new FlockMutex(fopen($this->file, 'r'), 1); } #[\Override] @@ -47,7 +47,7 @@ protected function tearDown(): void #[DataProvider('provideTimeoutableStrategiesCases')] public function testCodeExecutedOutsideLockIsNotThrown(string $strategy): void { - $this->mutex->strategy = $strategy; // @phpstan-ignore property.private + TestAccess::setProperty($this->mutex, 'strategy', $strategy); self::assertTrue($this->mutex->synchronized(static function () { // @phpstan-ignore staticMethod.alreadyNarrowedType usleep(1100 * 1000); diff --git a/tests/Mutex/MutexConcurrencyTest.php b/tests/Mutex/MutexConcurrencyTest.php index 76a39e9..35040b1 100644 --- a/tests/Mutex/MutexConcurrencyTest.php +++ b/tests/Mutex/MutexConcurrencyTest.php @@ -4,7 +4,7 @@ namespace Malkusch\Lock\Tests\Mutex; -use Eloquent\Liberator\Liberator; +require_once __DIR__ . '/../TestAccess.php'; use Malkusch\Lock\Mutex\DistributedMutex; use Malkusch\Lock\Mutex\FlockMutex; use Malkusch\Lock\Mutex\MemcachedMutex; @@ -163,8 +163,8 @@ public static function provideExecutionIsSerializedWhenLockedCases(): iterable if (extension_loaded('pcntl')) { yield 'flockWithTimoutPcntl' => [static function ($timeout) use ($filename) { $file = fopen($filename, 'w'); - $lock = Liberator::liberate(new FlockMutex($file, $timeout)); - $lock->strategy = \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)(); // @phpstan-ignore property.notFound + $lock = new FlockMutex($file, $timeout); + TestAccess::setProperty($lock, 'strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)()); return $lock->popsValue(); }]; @@ -172,8 +172,8 @@ public static function provideExecutionIsSerializedWhenLockedCases(): iterable yield 'flockWithTimoutLoop' => [static function ($timeout) use ($filename) { $file = fopen($filename, 'w'); - $lock = Liberator::liberate(new FlockMutex($file, $timeout)); - $lock->strategy = \Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)(); // @phpstan-ignore property.notFound + $lock = new FlockMutex($file, $timeout); + TestAccess::setProperty($lock, 'strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)()); return $lock->popsValue(); }]; diff --git a/tests/Mutex/MutexTest.php b/tests/Mutex/MutexTest.php index 626c962..d2c7715 100644 --- a/tests/Mutex/MutexTest.php +++ b/tests/Mutex/MutexTest.php @@ -4,7 +4,7 @@ namespace Malkusch\Lock\Tests\Mutex; -use Eloquent\Liberator\Liberator; +require_once __DIR__ . '/../TestAccess.php'; use Malkusch\Lock\Mutex\AbstractLockMutex; use Malkusch\Lock\Mutex\AbstractSpinlockMutex; use Malkusch\Lock\Mutex\DistributedMutex; @@ -114,8 +114,8 @@ public static function provideMutexFactoriesCases(): iterable if (extension_loaded('pcntl')) { yield 'flockWithTimoutPcntl' => [static function () { $file = fopen(vfsStream::url('test/lock'), 'w'); - $lock = Liberator::liberate(new FlockMutex($file, 3)); - $lock->strategy = \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)(); // @phpstan-ignore property.notFound + $lock = new FlockMutex($file, 3); + TestAccess::setProperty($lock, 'strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)()); return $lock->popsValue(); }]; @@ -123,8 +123,8 @@ public static function provideMutexFactoriesCases(): iterable yield 'flockWithTimoutLoop' => [static function () { $file = fopen(vfsStream::url('test/lock'), 'w'); - $lock = Liberator::liberate(new FlockMutex($file, 3)); - $lock->strategy = \Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)(); // @phpstan-ignore property.notFound + $lock = new FlockMutex($file, 3); + TestAccess::setProperty($lock, 'strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)()); return $lock->popsValue(); }]; diff --git a/tests/Mutex/PostgreSQLMutexTest.php b/tests/Mutex/PostgreSQLMutexTest.php index 4cfe932..6ed4bac 100644 --- a/tests/Mutex/PostgreSQLMutexTest.php +++ b/tests/Mutex/PostgreSQLMutexTest.php @@ -4,7 +4,7 @@ namespace Malkusch\Lock\Tests\Mutex; -use Eloquent\Liberator\Liberator; +use Malkusch\Lock\Tests\TestAccess; use Malkusch\Lock\Exception\LockAcquireTimeoutException; use Malkusch\Lock\Mutex\PostgreSQLMutex; use PHPUnit\Framework\Constraint\IsType; @@ -26,7 +26,8 @@ protected function setUp(): void $this->pdo = $this->createMock(\PDO::class); - $this->mutex = Liberator::liberate(new PostgreSQLMutex($this->pdo, 'test-one-negative-key')); // @phpstan-ignore assign.propertyType + $this->mutex = new PostgreSQLMutex($this->pdo, 'test-one-negative-key'); + $this->mutex = new TestAccess($this->mutex); // @phpstan-ignore assign.propertyType } private function isPhpunit9x(): bool diff --git a/tests/TestAccess.php b/tests/TestAccess.php new file mode 100644 index 0000000..ee4ba3c --- /dev/null +++ b/tests/TestAccess.php @@ -0,0 +1,69 @@ +object = $object; + } + + public function getProperty($property) { + $ref = new \ReflectionProperty($this->object, $property); + if (PHP_VERSION_ID < 80100) { + $ref->setAccessible(true); + } + return $ref->getValue($this->object); + } + + public function setProperty($property, $value) { + $ref = new \ReflectionProperty($this->object, $property); + if (PHP_VERSION_ID < 80100) { + $ref->setAccessible(true); + } + $ref->setValue($this->object, $value); + } + + public function callMethod($method, ...$args) { + $ref = new \ReflectionMethod($this->object, $method); + if (PHP_VERSION_ID < 80100) { + $ref->setAccessible(true); + } + return $ref->invokeArgs($this->object, $args); + } +} +/** + * Helper for accessing private/protected properties and methods in tests. + */ +class TestAccess +{ + public static function getProperty($object, $property) + { + $ref = new \ReflectionProperty($object, $property); + if (PHP_VERSION_ID < 80100) { + $ref->setAccessible(true); + } + return $ref->getValue($object); + } + + public static function setProperty($object, $property, $value) + { + $ref = new \ReflectionProperty($object, $property); + if (PHP_VERSION_ID < 80100) { + $ref->setAccessible(true); + } + $ref->setValue($object, $value); + } + + public static function callMethod($object, $method, ...$args) + { + $ref = new \ReflectionMethod($object, $method); + if (PHP_VERSION_ID < 80100) { + $ref->setAccessible(true); + } + return $ref->invokeArgs($object, $args); + } +} \ No newline at end of file From 3a3bed1d626fef13ffd5a58ccd30567091544a6d Mon Sep 17 00:00:00 2001 From: vedavith Date: Tue, 27 Jan 2026 11:40:03 +0530 Subject: [PATCH 23/38] Refactor TestAccess class to remove static methods and streamline property/method access --- tests/TestAccess.php | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/tests/TestAccess.php b/tests/TestAccess.php index ee4ba3c..440698b 100644 --- a/tests/TestAccess.php +++ b/tests/TestAccess.php @@ -1,5 +1,3 @@ -invokeArgs($this->object, $args); } -} -/** - * Helper for accessing private/protected properties and methods in tests. - */ -class TestAccess -{ - public static function getProperty($object, $property) - { - $ref = new \ReflectionProperty($object, $property); - if (PHP_VERSION_ID < 80100) { - $ref->setAccessible(true); - } - return $ref->getValue($object); - } - - public static function setProperty($object, $property, $value) - { - $ref = new \ReflectionProperty($object, $property); - if (PHP_VERSION_ID < 80100) { - $ref->setAccessible(true); - } - $ref->setValue($object, $value); - } - - public static function callMethod($object, $method, ...$args) - { - $ref = new \ReflectionMethod($object, $method); - if (PHP_VERSION_ID < 80100) { - $ref->setAccessible(true); - } - return $ref->invokeArgs($object, $args); - } } \ No newline at end of file From daebb8a0d0a38a3241108792a1110610fa338e38 Mon Sep 17 00:00:00 2001 From: vedavith Date: Tue, 27 Jan 2026 11:45:55 +0530 Subject: [PATCH 24/38] Add namespace declaration to TestAccess class --- tests/TestAccess.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/TestAccess.php b/tests/TestAccess.php index 440698b..e7dae51 100644 --- a/tests/TestAccess.php +++ b/tests/TestAccess.php @@ -1,4 +1,7 @@ Date: Tue, 27 Jan 2026 11:53:11 +0530 Subject: [PATCH 25/38] Refactor tests to use instance of TestAccess for property access --- tests/Mutex/FlockMutexTest.php | 2 +- tests/Mutex/MutexConcurrencyTest.php | 4 ++-- tests/Mutex/MutexTest.php | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Mutex/FlockMutexTest.php b/tests/Mutex/FlockMutexTest.php index ca8ec88..ee802ab 100644 --- a/tests/Mutex/FlockMutexTest.php +++ b/tests/Mutex/FlockMutexTest.php @@ -47,7 +47,7 @@ protected function tearDown(): void #[DataProvider('provideTimeoutableStrategiesCases')] public function testCodeExecutedOutsideLockIsNotThrown(string $strategy): void { - TestAccess::setProperty($this->mutex, 'strategy', $strategy); + (new \Malkusch\Lock\Tests\TestAccess($this->mutex))->setProperty('strategy', $strategy); self::assertTrue($this->mutex->synchronized(static function () { // @phpstan-ignore staticMethod.alreadyNarrowedType usleep(1100 * 1000); diff --git a/tests/Mutex/MutexConcurrencyTest.php b/tests/Mutex/MutexConcurrencyTest.php index 35040b1..50c9325 100644 --- a/tests/Mutex/MutexConcurrencyTest.php +++ b/tests/Mutex/MutexConcurrencyTest.php @@ -164,7 +164,7 @@ public static function provideExecutionIsSerializedWhenLockedCases(): iterable yield 'flockWithTimoutPcntl' => [static function ($timeout) use ($filename) { $file = fopen($filename, 'w'); $lock = new FlockMutex($file, $timeout); - TestAccess::setProperty($lock, 'strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)()); + (new \Malkusch\Lock\Tests\TestAccess($lock))->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)()); return $lock->popsValue(); }]; @@ -173,7 +173,7 @@ public static function provideExecutionIsSerializedWhenLockedCases(): iterable yield 'flockWithTimoutLoop' => [static function ($timeout) use ($filename) { $file = fopen($filename, 'w'); $lock = new FlockMutex($file, $timeout); - TestAccess::setProperty($lock, 'strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)()); + (new \Malkusch\Lock\Tests\TestAccess($lock))->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)()); return $lock->popsValue(); }]; diff --git a/tests/Mutex/MutexTest.php b/tests/Mutex/MutexTest.php index d2c7715..80ff654 100644 --- a/tests/Mutex/MutexTest.php +++ b/tests/Mutex/MutexTest.php @@ -115,7 +115,7 @@ public static function provideMutexFactoriesCases(): iterable yield 'flockWithTimoutPcntl' => [static function () { $file = fopen(vfsStream::url('test/lock'), 'w'); $lock = new FlockMutex($file, 3); - TestAccess::setProperty($lock, 'strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)()); + (new \Malkusch\Lock\Tests\TestAccess($lock))->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)()); return $lock->popsValue(); }]; @@ -124,7 +124,7 @@ public static function provideMutexFactoriesCases(): iterable yield 'flockWithTimoutLoop' => [static function () { $file = fopen(vfsStream::url('test/lock'), 'w'); $lock = new FlockMutex($file, 3); - TestAccess::setProperty($lock, 'strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)()); + (new \Malkusch\Lock\Tests\TestAccess($lock))->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)()); return $lock->popsValue(); }]; From 6a0e20e792a806c00ee58270b3bf7d06c58bcf92 Mon Sep 17 00:00:00 2001 From: vedavith Date: Tue, 27 Jan 2026 12:04:47 +0530 Subject: [PATCH 26/38] Refactor TestAccess class for improved readability and consistency --- tests/Mutex/FlockMutexTest.php | 3 ++- tests/TestAccess.php | 23 +++++++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/tests/Mutex/FlockMutexTest.php b/tests/Mutex/FlockMutexTest.php index ee802ab..4501bb8 100644 --- a/tests/Mutex/FlockMutexTest.php +++ b/tests/Mutex/FlockMutexTest.php @@ -8,6 +8,7 @@ use Malkusch\Lock\Exception\DeadlineException; use Malkusch\Lock\Exception\LockAcquireTimeoutException; use Malkusch\Lock\Mutex\FlockMutex; +use Malkusch\Lock\Tests\TestAccess; use Malkusch\Lock\Util\LockUtil; use Malkusch\Lock\Util\PcntlTimeout; use PHPUnit\Framework\Attributes\DataProvider; @@ -47,7 +48,7 @@ protected function tearDown(): void #[DataProvider('provideTimeoutableStrategiesCases')] public function testCodeExecutedOutsideLockIsNotThrown(string $strategy): void { - (new \Malkusch\Lock\Tests\TestAccess($this->mutex))->setProperty('strategy', $strategy); + (new TestAccess($this->mutex))->setProperty('strategy', $strategy); self::assertTrue($this->mutex->synchronized(static function () { // @phpstan-ignore staticMethod.alreadyNarrowedType usleep(1100 * 1000); diff --git a/tests/TestAccess.php b/tests/TestAccess.php index e7dae51..3a1a829 100644 --- a/tests/TestAccess.php +++ b/tests/TestAccess.php @@ -5,34 +5,41 @@ /** * TestAccess - Helper for accessing private/protected properties and methods in tests. */ -class TestAccess { +class TestAccess +{ private $object; - public function __construct($object) { + public function __construct($object) + { $this->object = $object; } - public function getProperty($property) { + public function getProperty($property) + { $ref = new \ReflectionProperty($this->object, $property); - if (PHP_VERSION_ID < 80100) { + if (\PHP_VERSION_ID < 80100) { $ref->setAccessible(true); } + return $ref->getValue($this->object); } - public function setProperty($property, $value) { + public function setProperty($property, $value) + { $ref = new \ReflectionProperty($this->object, $property); - if (PHP_VERSION_ID < 80100) { + if (\PHP_VERSION_ID < 80100) { $ref->setAccessible(true); } $ref->setValue($this->object, $value); } - public function callMethod($method, ...$args) { + public function callMethod($method, ...$args) + { $ref = new \ReflectionMethod($this->object, $method); - if (PHP_VERSION_ID < 80100) { + if (\PHP_VERSION_ID < 80100) { $ref->setAccessible(true); } + return $ref->invokeArgs($this->object, $args); } } \ No newline at end of file From ef9cd52da389b03fb3fb24599c6c41eba565df67 Mon Sep 17 00:00:00 2001 From: vedavith Date: Tue, 27 Jan 2026 14:28:36 +0530 Subject: [PATCH 27/38] Remove version constraint for spatie/async in composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 402efaf..c55a3e7 100644 --- a/composer.json +++ b/composer.json @@ -58,7 +58,7 @@ "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9.5.25 || ^10.0 || ^11.0", "predis/predis": "^1.1.8 || ^2.0", - "spatie/async": "^1.5 || ^1.8" + "spatie/async": "^1.5" }, "suggest": { "ext-igbinary": "To use this library with PHP Redis igbinary serializer enabled.", From 4a00e6a4340d5d28e513cfc0c72de9f8f4683f7f Mon Sep 17 00:00:00 2001 From: vedavith Date: Tue, 27 Jan 2026 14:39:55 +0530 Subject: [PATCH 28/38] Update phpunit version constraint in composer.json to support newer versions --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c55a3e7..59ec7d5 100644 --- a/composer.json +++ b/composer.json @@ -56,7 +56,7 @@ "phpstan/phpstan": "^2.0", "phpstan/phpstan-deprecation-rules": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^9.5.25 || ^10.0 || ^11.0", + "phpunit/phpunit": "^10.0 || ^11.0 || ^12.0", "predis/predis": "^1.1.8 || ^2.0", "spatie/async": "^1.5" }, From 3783124f937a8f45c924310ebad22680592154f6 Mon Sep 17 00:00:00 2001 From: vedavith Date: Tue, 27 Jan 2026 14:41:50 +0530 Subject: [PATCH 29/38] Update PHP version constraints in composer.json and test-unit.yml to support PHP 8.2 and above --- .github/workflows/test-unit.yml | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index 8bd57de..0a2e3d0 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -64,7 +64,7 @@ jobs: strategy: fail-fast: false matrix: - php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] + php: ['8.2', '8.3', '8.4', '8.5'] type: ['Phpunit', 'Phpunit Lowest'] include: - php: 'latest' diff --git a/composer.json b/composer.json index 59ec7d5..11f85fd 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ ], "homepage": "https://github.com/malkusch/lock", "require": { - "php": ">=7.4 <8.6", + "php": ">=8.2 <8.6", "psr/log": "^1.0 || ^2.0 || ^3.0", "symfony/polyfill-php80": "^1.28" }, From f9ab21a4627b961b016e2b3ac5b33099831b5c17 Mon Sep 17 00:00:00 2001 From: Vedavith Ravula Date: Fri, 30 Jan 2026 16:19:33 +0530 Subject: [PATCH 30/38] Enhance formatTimeout to handle NaN and infinities Added explicit handling for NaN and infinities in formatTimeout method. --- src/Util/LockUtil.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Util/LockUtil.php b/src/Util/LockUtil.php index 2ff3d33..740c0fc 100644 --- a/src/Util/LockUtil.php +++ b/src/Util/LockUtil.php @@ -71,13 +71,21 @@ public function castFloatToInt(float $value): int */ public function formatTimeout(float $value): string { - if (!is_finite($value) || is_nan($value)) { + // Handle NaN explicitly (normalize to a safe numeric string) + if (\is_nan($value)) { return '0.0'; } + + // Handle infinities explicitly + if (!\is_finite($value)) { + return $value > 0 ? 'INF' : '-INF'; + } + $res = (string) round($value, 6); if (strpos($res, '.') === false) { $res .= '.0'; } + return $res; } } From 05ff89c4a143b45f8169524adfb4f725d05eef8a Mon Sep 17 00:00:00 2001 From: vedavith Date: Sun, 1 Feb 2026 08:12:02 +0530 Subject: [PATCH 31/38] Refactored TestAccess --- tests/TestAccess.php | 76 ++++++++++++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 23 deletions(-) diff --git a/tests/TestAccess.php b/tests/TestAccess.php index 3a1a829..ff13cc8 100644 --- a/tests/TestAccess.php +++ b/tests/TestAccess.php @@ -1,45 +1,75 @@ object = $object; } - public function getProperty($property) + /** + * @param string $property + * @return mixed + */ + public function getProperty(string $property): mixed { - $ref = new \ReflectionProperty($this->object, $property); - if (\PHP_VERSION_ID < 80100) { - $ref->setAccessible(true); - } + $accessor = \Closure::bind( + function (string $property) { + /** @phpstan-ignore-next-line */ + return $this->$property; + }, + $this->object, + $this->object + ); - return $ref->getValue($this->object); + return $accessor($property); } - public function setProperty($property, $value) + /** + * @param string $property + * @param mixed $value + */ + public function setProperty(string $property, mixed $value): void { - $ref = new \ReflectionProperty($this->object, $property); - if (\PHP_VERSION_ID < 80100) { - $ref->setAccessible(true); - } - $ref->setValue($this->object, $value); + $accessor = \Closure::bind( + function (string $property, mixed $value): void { + /** @phpstan-ignore-next-line */ + $this->$property = $value; + }, + $this->object, + $this->object + ); + + $accessor($property, $value); } - public function callMethod($method, ...$args) + /** + * @param string $method + * @param array $args + * @return mixed + */ + public function callMethod(string $method, array $args = []): mixed { - $ref = new \ReflectionMethod($this->object, $method); - if (\PHP_VERSION_ID < 80100) { - $ref->setAccessible(true); - } + $caller = \Closure::bind( + function (string $method, array $args): mixed { + /** @phpstan-ignore-next-line */ + return $this->$method(...$args); + }, + $this->object, + $this->object + ); - return $ref->invokeArgs($this->object, $args); + return $caller($method, $args); } -} \ No newline at end of file +} From cc35223301273864d9b5e304d6aab66c5542ba2b Mon Sep 17 00:00:00 2001 From: vedavith Date: Sun, 1 Feb 2026 09:57:27 +0530 Subject: [PATCH 32/38] Updated TestAccess class --- tests/TestAccess.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/TestAccess.php b/tests/TestAccess.php index ff13cc8..cda95cd 100644 --- a/tests/TestAccess.php +++ b/tests/TestAccess.php @@ -4,6 +4,8 @@ namespace Malkusch\Lock\Tests; +use Closure; + /** * Helper to access private/protected members for tests. * @@ -56,7 +58,7 @@ function (string $property, mixed $value): void { /** * @param string $method - * @param array $args + * @param array $args * @return mixed */ public function callMethod(string $method, array $args = []): mixed @@ -72,4 +74,14 @@ function (string $method, array $args): mixed { return $caller($method, $args); } + + /** + * Proxy calls to inaccessible methods on the wrapped object. + * + * @param array $args + */ + public function __call(string $method, array $args): mixed + { + return $this->callMethod($method, $args); + } } From 5b51cd6593bd7d3b88a6a64d41f1a44796148fc6 Mon Sep 17 00:00:00 2001 From: vedavith Date: Sun, 1 Feb 2026 10:27:16 +0530 Subject: [PATCH 33/38] Updated TestAccess.php --- tests/TestAccess.php | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/TestAccess.php b/tests/TestAccess.php index cda95cd..6497fbd 100644 --- a/tests/TestAccess.php +++ b/tests/TestAccess.php @@ -14,6 +14,7 @@ final class TestAccess { private object $object; + private ?float $acquireTimeout = null; public function __construct(object $object) { @@ -21,6 +22,8 @@ public function __construct(object $object) } /** + * Gets a private/protected property on the wrapped object. + * * @param string $property * @return mixed */ @@ -39,6 +42,8 @@ function (string $property) { } /** + * Sets a private/protected property on the wrapped object. + * * @param string $property * @param mixed $value */ @@ -57,6 +62,8 @@ function (string $property, mixed $value): void { } /** + * Proxy calls to inaccessible methods on the wrapped object. + * * @param string $method * @param array $args * @return mixed @@ -84,4 +91,25 @@ public function __call(string $method, array $args): mixed { return $this->callMethod($method, $args); } + + /** + * Proxy access to inaccessible properties on the wrapped object. + * + * @param string $property + */ + public function __get(string $property): mixed + { + return $this->getProperty($property); + } + + /** + * Proxy setting of inaccessible properties on the wrapped object. + * + * @param string $property + * @param mixed $value + */ + public function __set(string $property, mixed $value): void + { + $this->setProperty($property, $value); + } } From 990955331d0d055b36800728109b5df940a02266 Mon Sep 17 00:00:00 2001 From: vedavith Date: Sun, 1 Feb 2026 10:47:36 +0530 Subject: [PATCH 34/38] Calling PopsValue from TestAccess --- src/Util/LockUtil.php | 2 +- tests/Mutex/MutexConcurrencyTest.php | 5 +++-- tests/Mutex/MutexTest.php | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Util/LockUtil.php b/src/Util/LockUtil.php index 740c0fc..2714249 100644 --- a/src/Util/LockUtil.php +++ b/src/Util/LockUtil.php @@ -73,7 +73,7 @@ public function formatTimeout(float $value): string { // Handle NaN explicitly (normalize to a safe numeric string) if (\is_nan($value)) { - return '0.0'; + return 'NAN'; } // Handle infinities explicitly diff --git a/tests/Mutex/MutexConcurrencyTest.php b/tests/Mutex/MutexConcurrencyTest.php index 50c9325..891bb9b 100644 --- a/tests/Mutex/MutexConcurrencyTest.php +++ b/tests/Mutex/MutexConcurrencyTest.php @@ -166,7 +166,8 @@ public static function provideExecutionIsSerializedWhenLockedCases(): iterable $lock = new FlockMutex($file, $timeout); (new \Malkusch\Lock\Tests\TestAccess($lock))->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)()); - return $lock->popsValue(); + return (new \Malkusch\Lock\Tests\TestAccess($lock))->popsValue(); + }]; } @@ -175,7 +176,7 @@ public static function provideExecutionIsSerializedWhenLockedCases(): iterable $lock = new FlockMutex($file, $timeout); (new \Malkusch\Lock\Tests\TestAccess($lock))->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)()); - return $lock->popsValue(); + return (new \Malkusch\Lock\Tests\TestAccess($lock))->popsValue(); }]; if (extension_loaded('sysvsem')) { diff --git a/tests/Mutex/MutexTest.php b/tests/Mutex/MutexTest.php index 80ff654..7f152dd 100644 --- a/tests/Mutex/MutexTest.php +++ b/tests/Mutex/MutexTest.php @@ -117,7 +117,7 @@ public static function provideMutexFactoriesCases(): iterable $lock = new FlockMutex($file, 3); (new \Malkusch\Lock\Tests\TestAccess($lock))->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)()); - return $lock->popsValue(); + return (new \Malkusch\Lock\Tests\TestAccess($lock))->popsValue(); }]; } @@ -126,7 +126,7 @@ public static function provideMutexFactoriesCases(): iterable $lock = new FlockMutex($file, 3); (new \Malkusch\Lock\Tests\TestAccess($lock))->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)()); - return $lock->popsValue(); + return (new \Malkusch\Lock\Tests\TestAccess($lock))->popsValue(); }]; if (extension_loaded('sysvsem')) { From dd99643fdc17a4995fcc8a308ae230f8a3f9fc68 Mon Sep 17 00:00:00 2001 From: vedavith Date: Mon, 2 Feb 2026 08:32:45 +0530 Subject: [PATCH 35/38] Refactor MutexConcurrencyTest to simplify TestAccess usage and enhance parameter consistency --- tests/Mutex/MutexConcurrencyTest.php | 16 ++++++------- tests/TestAccess.php | 35 +++++++++++++++++----------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/tests/Mutex/MutexConcurrencyTest.php b/tests/Mutex/MutexConcurrencyTest.php index 891bb9b..ffb9e4d 100644 --- a/tests/Mutex/MutexConcurrencyTest.php +++ b/tests/Mutex/MutexConcurrencyTest.php @@ -13,6 +13,7 @@ use Malkusch\Lock\Mutex\PostgreSQLMutex; use Malkusch\Lock\Mutex\RedisMutex; use Malkusch\Lock\Mutex\SemaphoreMutex; +use Malkusch\Lock\Tests\TestAccess; use Malkusch\Lock\Util\LockUtil; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Constraint\IsType; @@ -31,7 +32,7 @@ class MutexConcurrencyTest extends TestCase { /** @var list */ - protected static $temporaryFiles = []; + protected static array $temporaryFiles = []; #[\Override] public static function tearDownAfterClass(): void @@ -63,9 +64,9 @@ private function fork(int $concurrency, \Closure $code): void /** * Tests high contention empirically. * - * @param \Closure(0|1): int $code The counter code + * @param \Closure $code * @param \Closure(float): Mutex $mutexFactory - * @param \Closure(): void $setUp + * @param \Closure|null $setUp * * @dataProvider provideHighContentionCases */ @@ -164,19 +165,18 @@ public static function provideExecutionIsSerializedWhenLockedCases(): iterable yield 'flockWithTimoutPcntl' => [static function ($timeout) use ($filename) { $file = fopen($filename, 'w'); $lock = new FlockMutex($file, $timeout); - (new \Malkusch\Lock\Tests\TestAccess($lock))->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)()); - - return (new \Malkusch\Lock\Tests\TestAccess($lock))->popsValue(); + new TestAccess($lock)->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)()); + return new TestAccess($lock)->popsValue(); }]; } yield 'flockWithTimoutLoop' => [static function ($timeout) use ($filename) { $file = fopen($filename, 'w'); $lock = new FlockMutex($file, $timeout); - (new \Malkusch\Lock\Tests\TestAccess($lock))->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)()); + new TestAccess($lock)->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)()); - return (new \Malkusch\Lock\Tests\TestAccess($lock))->popsValue(); + return new TestAccess($lock)->popsValue(); }]; if (extension_loaded('sysvsem')) { diff --git a/tests/TestAccess.php b/tests/TestAccess.php index 6497fbd..96066d9 100644 --- a/tests/TestAccess.php +++ b/tests/TestAccess.php @@ -4,7 +4,6 @@ namespace Malkusch\Lock\Tests; -use Closure; /** * Helper to access private/protected members for tests. @@ -23,7 +22,7 @@ public function __construct(object $object) /** * Gets a private/protected property on the wrapped object. - * + * * @param string $property * @return mixed */ @@ -31,8 +30,8 @@ public function getProperty(string $property): mixed { $accessor = \Closure::bind( function (string $property) { - /** @phpstan-ignore-next-line */ - return $this->$property; + // @phpstan-ignore-next-line + return $this->{$property}; }, $this->object, $this->object @@ -43,7 +42,7 @@ function (string $property) { /** * Sets a private/protected property on the wrapped object. - * + * * @param string $property * @param mixed $value */ @@ -51,8 +50,8 @@ public function setProperty(string $property, mixed $value): void { $accessor = \Closure::bind( function (string $property, mixed $value): void { - /** @phpstan-ignore-next-line */ - $this->$property = $value; + // @phpstan-ignore-next-line + $this->{$property} = $value; }, $this->object, $this->object @@ -63,7 +62,7 @@ function (string $property, mixed $value): void { /** * Proxy calls to inaccessible methods on the wrapped object. - * + * * @param string $method * @param array $args * @return mixed @@ -72,8 +71,8 @@ public function callMethod(string $method, array $args = []): mixed { $caller = \Closure::bind( function (string $method, array $args): mixed { - /** @phpstan-ignore-next-line */ - return $this->$method(...$args); + // @phpstan-ignore-next-line + return $this->{$method}(...$args); }, $this->object, $this->object @@ -94,7 +93,7 @@ public function __call(string $method, array $args): mixed /** * Proxy access to inaccessible properties on the wrapped object. - * + * * @param string $property */ public function __get(string $property): mixed @@ -104,12 +103,22 @@ public function __get(string $property): mixed /** * Proxy setting of inaccessible properties on the wrapped object. - * + * * @param string $property - * @param mixed $value + * @param mixed $value */ public function __set(string $property, mixed $value): void { $this->setProperty($property, $value); } + + /** + * Returns the wrapped object. + * + * @return object + */ + public function popsValue(): object + { + return $this->object; + } } From 09c825df4053e25797820b479679c0e585097bd0 Mon Sep 17 00:00:00 2001 From: vedavith Date: Mon, 2 Feb 2026 08:36:18 +0530 Subject: [PATCH 36/38] Refactor MutexConcurrencyTest: Remove unused imports, adjust type annotations, and update TestAccess invocation for consistency --- tests/Mutex/MutexConcurrencyTest.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/Mutex/MutexConcurrencyTest.php b/tests/Mutex/MutexConcurrencyTest.php index ffb9e4d..891bb9b 100644 --- a/tests/Mutex/MutexConcurrencyTest.php +++ b/tests/Mutex/MutexConcurrencyTest.php @@ -13,7 +13,6 @@ use Malkusch\Lock\Mutex\PostgreSQLMutex; use Malkusch\Lock\Mutex\RedisMutex; use Malkusch\Lock\Mutex\SemaphoreMutex; -use Malkusch\Lock\Tests\TestAccess; use Malkusch\Lock\Util\LockUtil; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Constraint\IsType; @@ -32,7 +31,7 @@ class MutexConcurrencyTest extends TestCase { /** @var list */ - protected static array $temporaryFiles = []; + protected static $temporaryFiles = []; #[\Override] public static function tearDownAfterClass(): void @@ -64,9 +63,9 @@ private function fork(int $concurrency, \Closure $code): void /** * Tests high contention empirically. * - * @param \Closure $code + * @param \Closure(0|1): int $code The counter code * @param \Closure(float): Mutex $mutexFactory - * @param \Closure|null $setUp + * @param \Closure(): void $setUp * * @dataProvider provideHighContentionCases */ @@ -165,18 +164,19 @@ public static function provideExecutionIsSerializedWhenLockedCases(): iterable yield 'flockWithTimoutPcntl' => [static function ($timeout) use ($filename) { $file = fopen($filename, 'w'); $lock = new FlockMutex($file, $timeout); - new TestAccess($lock)->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)()); + (new \Malkusch\Lock\Tests\TestAccess($lock))->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)()); + + return (new \Malkusch\Lock\Tests\TestAccess($lock))->popsValue(); - return new TestAccess($lock)->popsValue(); }]; } yield 'flockWithTimoutLoop' => [static function ($timeout) use ($filename) { $file = fopen($filename, 'w'); $lock = new FlockMutex($file, $timeout); - new TestAccess($lock)->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)()); + (new \Malkusch\Lock\Tests\TestAccess($lock))->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)()); - return new TestAccess($lock)->popsValue(); + return (new \Malkusch\Lock\Tests\TestAccess($lock))->popsValue(); }]; if (extension_loaded('sysvsem')) { From 5ba7f99508e6ff93439f5e339679c403d950c63f Mon Sep 17 00:00:00 2001 From: vedavith Date: Mon, 2 Feb 2026 09:06:59 +0530 Subject: [PATCH 37/38] Refactor FlockMutexTest: Update TestAccess usage for setting private properties and improve strategy assignment consistency --- tests/Mutex/FlockMutexTest.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/Mutex/FlockMutexTest.php b/tests/Mutex/FlockMutexTest.php index 4501bb8..caa52bf 100644 --- a/tests/Mutex/FlockMutexTest.php +++ b/tests/Mutex/FlockMutexTest.php @@ -68,7 +68,7 @@ public function testAcquireTimeoutOccurs(string $strategy): void $anotherResource = fopen($this->file, 'r'); flock($anotherResource, \LOCK_EX); - $this->mutex->strategy = $strategy; // @phpstan-ignore property.private + new TestAccess($this->mutex)->setProperty('strategy', $strategy); $this->expectException(LockAcquireTimeoutException::class); $this->expectExceptionMessage('Lock acquire timeout of 1.0 seconds has been exceeded'); @@ -102,8 +102,10 @@ public function testNoTimeoutWaitsForever(): void $anotherResource = fopen($this->file, 'r'); flock($anotherResource, \LOCK_EX); - $this->mutex->strategy = \Closure::bind(static fn () => FlockMutex::STRATEGY_BLOCK, null, FlockMutex::class)(); // @phpstan-ignore property.private - + (new TestAccess($this->mutex))->setProperty( + 'strategy', + \Closure::bind(static fn () => FlockMutex::STRATEGY_BLOCK, null, FlockMutex::class)() + ); $timebox = new PcntlTimeout(1); $this->expectException(DeadlineException::class); From 5cdaa0067d90b94026f477acfbd8a248c5462c64 Mon Sep 17 00:00:00 2001 From: vedavith Date: Mon, 2 Feb 2026 09:30:26 +0530 Subject: [PATCH 38/38] Refactor FlockMutexTest: Simplify TestAccess usage and adjust annotations for improved clarity --- tests/Mutex/FlockMutexTest.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/Mutex/FlockMutexTest.php b/tests/Mutex/FlockMutexTest.php index caa52bf..f617afc 100644 --- a/tests/Mutex/FlockMutexTest.php +++ b/tests/Mutex/FlockMutexTest.php @@ -43,7 +43,6 @@ protected function tearDown(): void /** * @param FlockMutex::STRATEGY_* $strategy * - * @dataProvider provideTimeoutableStrategiesCases */ #[DataProvider('provideTimeoutableStrategiesCases')] public function testCodeExecutedOutsideLockIsNotThrown(string $strategy): void @@ -58,9 +57,8 @@ public function testCodeExecutedOutsideLockIsNotThrown(string $strategy): void } /** - * @param FlockMutex::STRATEGY_* $strategy - * - * @dataProvider provideTimeoutableStrategiesCases + * @param string $strategy + * @throws \Throwable */ #[DataProvider('provideTimeoutableStrategiesCases')] public function testAcquireTimeoutOccurs(string $strategy): void @@ -68,7 +66,7 @@ public function testAcquireTimeoutOccurs(string $strategy): void $anotherResource = fopen($this->file, 'r'); flock($anotherResource, \LOCK_EX); - new TestAccess($this->mutex)->setProperty('strategy', $strategy); + (new TestAccess($this->mutex))->setProperty('strategy', $strategy); $this->expectException(LockAcquireTimeoutException::class); $this->expectExceptionMessage('Lock acquire timeout of 1.0 seconds has been exceeded');