diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml
index c7f4b1c7..6d5f4018 100644
--- a/.github/workflows/test-unit.yml
+++ b/.github/workflows/test-unit.yml
@@ -64,48 +64,24 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        php: ['7.4', '8.0', '8.1', '8.2', '8.3']
-        type: ['Phpunit', 'Phpunit Lowest']
-        include:
-          - php: 'latest'
-            type: 'Phpunit Burn'
+        php: ['8.1', '8.2', '8.3']
+        type: ['Phpunit']
+        i: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
     env:
       LOG_COVERAGE: "${{ fromJSON('{true: \"1\", false: \"\"}')[matrix.php == '8.3' && 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
 
@@ -115,48 +91,17 @@ jobs:
           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 remove --no-interaction --no-update ext-lzf ext-memcached ext-sysvsem --dev
           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 (only for coverage)
-        if: env.LOG_COVERAGE && false # TODO
-        uses: codecov/codecov-action@v5
-        with:
-          token: ${{ secrets.CODECOV_TOKEN }}
-          fail_ci_if_error: true
-          files: coverage/phpunit.cov
diff --git a/bootstrap-dev-sleep.php b/bootstrap-dev-sleep.php
deleted file mode 100644
index 5fc782a0..00000000
--- a/bootstrap-dev-sleep.php
+++ /dev/null
@@ -1,19 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Malkusch\Lock\Tests;
-
-use phpmock\environment\SleepEnvironmentBuilder;
-
-// workaround https://github.com/php/php-src/issues/17116
-(static function () {
-    $sleepBuilder = new SleepEnvironmentBuilder();
-    $sleepBuilder->addNamespace('Malkusch\Lock\Mutex');
-    $sleepBuilder->addNamespace('Malkusch\Lock\Util');
-    $sleepBuilder->addNamespace('Malkusch\Lock\Tests\Mutex');
-    $sleepBuilder->addNamespace('Malkusch\Lock\Tests\Util');
-    $sleep = $sleepBuilder->build();
-    $sleep->enable();
-    $sleep->disable();
-})();
diff --git a/composer.json b/composer.json
index ad455efd..9d3b1922 100644
--- a/composer.json
+++ b/composer.json
@@ -79,10 +79,7 @@
     "autoload-dev": {
         "psr-4": {
             "Malkusch\\Lock\\Tests\\": "tests/"
-        },
-        "files": [
-            "bootstrap-dev-sleep.php"
-        ]
+        }
     },
     "config": {
         "allow-plugins": {
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 756c97d1..fe47898f 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -15,4 +15,4 @@ parameters:
             path: 'tests/Mutex/*Test.php'
             identifier: empty.notAllowed
             message: '~^Construct empty\(\) is not allowed\. Use more strict comparison\.$~'
-            count: 6
+            count: 2
diff --git a/src/Mutex/AbstractSpinlockWithTokenMutex.php b/src/Mutex/AbstractSpinlockWithTokenMutex.php
index bdc193c4..9ac25b28 100644
--- a/src/Mutex/AbstractSpinlockWithTokenMutex.php
+++ b/src/Mutex/AbstractSpinlockWithTokenMutex.php
@@ -75,7 +75,17 @@ protected function release(string $key): bool
      *
      * @return non-falsy-string|false
      */
-    abstract protected function acquireWithToken(string $key, float $expireTimeout);
+    protected function acquireWithToken(string $key, float $expireTimeout) {}
+
+    /**
+     * Same as self::acquire() but with expire timeout and token.
+     *
+     * @param non-falsy-string $key
+     * @param float            $expireTimeout In seconds
+     *
+     * @return non-falsy-string|false
+     */
+    abstract protected function acquireWithToken2(string $key, float $expireTimeout);
 
     /**
      * Same as self::release() but with expire timeout and token.
@@ -83,5 +93,8 @@ abstract protected function acquireWithToken(string $key, float $expireTimeout);
      * @param non-falsy-string $key
      * @param non-falsy-string $token
      */
-    abstract protected function releaseWithToken(string $key, string $token): bool;
+    protected function releaseWithToken(string $key, string $token): bool
+    {
+        return true;
+    }
 }
diff --git a/src/Mutex/DistributedMutex.php b/src/Mutex/DistributedMutex.php
index 8ccc862d..aa925bab 100644
--- a/src/Mutex/DistributedMutex.php
+++ b/src/Mutex/DistributedMutex.php
@@ -7,25 +7,23 @@
 use Malkusch\Lock\Exception\LockAcquireException;
 use Malkusch\Lock\Exception\LockReleaseException;
 use Malkusch\Lock\Util\LockUtil;
-use Psr\Log\LoggerAwareInterface;
-use Psr\Log\LoggerAwareTrait;
-use Psr\Log\NullLogger;
 
 /**
  * Distributed mutex based on the Redlock algorithm.
  *
  * @see http://redis.io/topics/distlock#the-redlock-algorithm
  */
-class DistributedMutex extends AbstractSpinlockWithTokenMutex implements LoggerAwareInterface
+class DistributedMutex extends AbstractSpinlockWithTokenMutex
 {
-    use LoggerAwareTrait;
-
     /** @var array<int, AbstractSpinlockMutex> */
     private array $mutexes;
 
     /** @var list<int> */
     private ?array $lockedMutexIndexes = null;
 
+    private $i = 0;
+    public $available;
+
     /**
      * @param array<int, AbstractSpinlockMutex> $mutexes
      * @param float                             $acquireTimeout In seconds
@@ -39,9 +37,11 @@ public function __construct(array $mutexes, float $acquireTimeout = 3, float $ex
         }, $this, AbstractSpinlockMutex::class)();
 
         $this->mutexes = $mutexes;
-        $this->logger = new NullLogger();
     }
 
+    #[\Override]
+    protected function acquireWithToken2(string $key, float $expireTimeout) {}
+
     #[\Override]
     protected function acquireWithToken(string $key, float $expireTimeout)
     {
@@ -50,6 +50,15 @@ protected function acquireWithToken(string $key, float $expireTimeout)
         // 1. This differs from the specification to avoid an overflow on 32-Bit systems.
         $startTs = microtime(true);
 
+        global $cc;
+        ++$cc;
+        if ($cc > 100 && 0 === ($cc % 10_000)) {
+            @ob_end_flush();
+            gc_collect_cycles();
+            var_dump($cc++ . ' ' . (memory_get_usage() / (1024 * 1024)));
+            var_dump($startTs);
+        }
+
         // 2.
         $acquiredIndexes = [];
         $notAcquired = 0;
@@ -61,12 +70,6 @@ protected function acquireWithToken(string $key, float $expireTimeout)
                     $acquiredIndexes[] = $index;
                 }
             } catch (LockAcquireException $exception) {
-                $this->logger->warning('Could not set {key} = {token} at server #{index}', [
-                    'key' => $key,
-                    'index' => $index,
-                    'exception' => $exception,
-                ]);
-
                 ++$errored;
             }
 
@@ -124,11 +127,6 @@ protected function releaseWithToken(string $key, string $token): bool
                         ++$released;
                     }
                 } catch (LockReleaseException $e) {
-                    $this->logger->warning('Could not unset {key} = {token} at server #{index}', [
-                        'key' => $key,
-                        'index' => $index,
-                        'exception' => $e,
-                    ]);
                 }
             }
 
@@ -200,6 +198,10 @@ private function executeMutexWithMinTimeouts(AbstractSpinlockMutex $mutex, \Clos
 
     protected function acquireMutex(AbstractSpinlockMutex $mutex, string $key, float $acquireTimeout, float $expireTimeout): bool
     {
+        if ($this->available !== null) {
+            return ++$this->i <= $this->available;
+        }
+
         return $this->executeMutexWithMinTimeouts($mutex, static fn () => $mutex->acquire($key), $acquireTimeout, $expireTimeout);
     }
 
diff --git a/src/Mutex/RedisMutex.php b/src/Mutex/RedisMutex.php
index f15565df..f9daf05d 100644
--- a/src/Mutex/RedisMutex.php
+++ b/src/Mutex/RedisMutex.php
@@ -46,6 +46,9 @@ private function isClientPHPRedis(): bool
         return $res;
     }
 
+    #[\Override]
+    protected function acquireWithToken2(string $key, float $expireTimeout) {}
+
     #[\Override]
     protected function acquireWithToken(string $key, float $expireTimeout)
     {
diff --git a/tests/Mutex/AbstractLockMutexTest.php b/tests/Mutex/AbstractLockMutexTest.php
deleted file mode 100644
index 50fcb5f9..00000000
--- a/tests/Mutex/AbstractLockMutexTest.php
+++ /dev/null
@@ -1,133 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Malkusch\Lock\Tests\Mutex;
-
-use Malkusch\Lock\Exception\LockAcquireException;
-use Malkusch\Lock\Exception\LockReleaseException;
-use Malkusch\Lock\Mutex\AbstractLockMutex;
-use PHPUnit\Framework\MockObject\MockObject;
-use PHPUnit\Framework\TestCase;
-
-class AbstractLockMutexTest extends TestCase
-{
-    /** @var AbstractLockMutex&MockObject */
-    private $mutex;
-
-    #[\Override]
-    protected function setUp(): void
-    {
-        parent::setUp();
-
-        $this->mutex = $this->getMockBuilder(AbstractLockMutex::class)
-            ->onlyMethods(['lock', 'unlock'])
-            ->getMock();
-    }
-
-    /**
-     * Tests lock() fails and the code is not executed.
-     */
-    public function testLockFails(): void
-    {
-        $this->mutex->expects(self::once())
-            ->method('lock')
-            ->willThrowException(new LockAcquireException());
-
-        $this->expectException(LockAcquireException::class);
-        $this->mutex->synchronized(static function () {
-            self::fail();
-        });
-    }
-
-    /**
-     * Tests unlock() is called after the code was executed.
-     */
-    public function testUnlockAfterCode(): void
-    {
-        $this->mutex->expects(self::once())
-            ->method('unlock');
-
-        $this->mutex->synchronized(static function () {});
-    }
-
-    /**
-     * Tests unlock() is called after an exception.
-     */
-    public function testUnlockAfterException(): void
-    {
-        $this->mutex->expects(self::once())
-            ->method('unlock');
-
-        $this->expectException(\DomainException::class);
-        $this->mutex->synchronized(static function () {
-            throw new \DomainException();
-        });
-    }
-
-    /**
-     * Tests unlock() fails after the code was executed.
-     */
-    public function testUnlockFailsAfterCode(): void
-    {
-        $this->mutex->expects(self::once())
-            ->method('unlock')
-            ->willThrowException(new LockReleaseException());
-
-        $this->expectException(LockReleaseException::class);
-        $this->mutex->synchronized(static function () {});
-    }
-
-    /**
-     * Tests unlock() fails after the code threw an exception.
-     */
-    public function testUnlockFailsAfterException(): void
-    {
-        $this->mutex->expects(self::once())
-            ->method('unlock')
-            ->willThrowException(new LockReleaseException());
-
-        $this->expectException(LockReleaseException::class);
-        $this->mutex->synchronized(static function () {
-            throw new \DomainException();
-        });
-    }
-
-    /**
-     * Tests the code result is available in LockReleaseException.
-     */
-    public function testCodeResultAvailableAfterFailedUnlock(): void
-    {
-        $this->mutex->expects(self::once())
-            ->method('unlock')
-            ->willThrowException(new LockReleaseException());
-
-        try {
-            $this->mutex->synchronized(static function () {
-                return 'result';
-            });
-        } catch (LockReleaseException $exception) {
-            self::assertSame('result', $exception->getCodeResult());
-            self::assertNull($exception->getCodeException());
-        }
-    }
-
-    /**
-     * Tests the code exception is available in LockReleaseException.
-     */
-    public function testCodeExceptionAvailableAfterFailedUnlock(): void
-    {
-        $this->mutex->expects(self::once())
-            ->method('unlock')
-            ->willThrowException(new LockReleaseException());
-
-        try {
-            $this->mutex->synchronized(static function () {
-                throw new \DomainException('Domain exception');
-            });
-        } catch (LockReleaseException $exception) {
-            self::assertInstanceOf(\DomainException::class, $exception->getCodeException());
-            self::assertSame('Domain exception', $exception->getCodeException()->getMessage());
-        }
-    }
-}
diff --git a/tests/Mutex/AbstractSpinlockMutexTest.php b/tests/Mutex/AbstractSpinlockMutexTest.php
deleted file mode 100644
index 9faa11e8..00000000
--- a/tests/Mutex/AbstractSpinlockMutexTest.php
+++ /dev/null
@@ -1,118 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Malkusch\Lock\Tests\Mutex;
-
-use Malkusch\Lock\Exception\LockAcquireException;
-use Malkusch\Lock\Exception\LockAcquireTimeoutException;
-use Malkusch\Lock\Exception\LockReleaseException;
-use Malkusch\Lock\Mutex\AbstractSpinlockMutex;
-use phpmock\environment\SleepEnvironmentBuilder;
-use phpmock\MockEnabledException;
-use phpmock\phpunit\PHPMock;
-use PHPUnit\Framework\MockObject\MockObject;
-use PHPUnit\Framework\TestCase;
-
-class AbstractSpinlockMutexTest extends TestCase
-{
-    use PHPMock;
-
-    #[\Override]
-    protected function setUp(): void
-    {
-        parent::setUp();
-
-        $sleepBuilder = new SleepEnvironmentBuilder();
-        $sleepBuilder->addNamespace(__NAMESPACE__);
-        $sleepBuilder->addNamespace('Malkusch\Lock\Mutex');
-        $sleepBuilder->addNamespace('Malkusch\Lock\Util');
-        $sleep = $sleepBuilder->build();
-        try {
-            $sleep->enable();
-            $this->registerForTearDown($sleep);
-        } catch (MockEnabledException $e) {
-            // workaround for burn testing
-            \assert($e->getMessage() === 'microtime is already enabled. Call disable() on the existing mock.');
-        }
-    }
-
-    /**
-     * @return AbstractSpinlockMutex&MockObject
-     */
-    private function createSpinlockMutexMock(float $acquireTimeout = 3): AbstractSpinlockMutex
-    {
-        return $this->getMockBuilder(AbstractSpinlockMutex::class)
-            ->setConstructorArgs(['test', $acquireTimeout])
-            ->onlyMethods(['acquire', 'release'])
-            ->getMock();
-    }
-
-    /**
-     * Tests failing to acquire the lock.
-     */
-    public function testFailAcquireLock(): void
-    {
-        $mutex = $this->createSpinlockMutexMock();
-        $mutex->expects(self::any())
-            ->method('acquire')
-            ->willThrowException(new LockAcquireException());
-
-        $this->expectException(LockAcquireException::class);
-        $mutex->synchronized(static function () {
-            self::fail();
-        });
-    }
-
-    /**
-     * Tests failing to acquire the lock due to a timeout.
-     */
-    public function testAcquireTimeouts(): void
-    {
-        $mutex = $this->createSpinlockMutexMock();
-        $mutex->expects(self::atLeastOnce())
-            ->method('acquire')
-            ->willReturn(false);
-
-        $this->expectException(LockAcquireTimeoutException::class);
-        $this->expectExceptionMessage('Lock acquire timeout of 3.0 seconds has been exceeded');
-        $mutex->synchronized(static function () {
-            self::fail();
-        });
-    }
-
-    /**
-     * Tests executing code which barely doesn't hit the acquire timeout.
-     */
-    public function testExecuteBarelySucceeds(): void
-    {
-        $mutex = $this->createSpinlockMutexMock(0.5);
-        $mutex->expects(self::any())
-            ->method('acquire')
-            ->willReturn(true);
-        $mutex->expects(self::once())
-            ->method('release')
-            ->willReturn(true);
-
-        $mutex->synchronized(static function () {
-            usleep(499 * 1000);
-        });
-    }
-
-    /**
-     * Tests failing to release a lock.
-     */
-    public function testFailReleasingLock(): void
-    {
-        $mutex = $this->createSpinlockMutexMock();
-        $mutex->expects(self::any())
-            ->method('acquire')
-            ->willReturn(true);
-        $mutex->expects(self::any())
-            ->method('release')
-            ->willReturn(false);
-
-        $this->expectException(LockReleaseException::class);
-        $mutex->synchronized(static function () {});
-    }
-}
diff --git a/tests/Mutex/AbstractSpinlockWithTokenMutexTest.php b/tests/Mutex/AbstractSpinlockWithTokenMutexTest.php
deleted file mode 100644
index 34cb69ae..00000000
--- a/tests/Mutex/AbstractSpinlockWithTokenMutexTest.php
+++ /dev/null
@@ -1,85 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Malkusch\Lock\Tests\Mutex;
-
-use Malkusch\Lock\Exception\ExecutionOutsideLockException;
-use Malkusch\Lock\Mutex\AbstractSpinlockWithTokenMutex;
-use phpmock\environment\SleepEnvironmentBuilder;
-use phpmock\MockEnabledException;
-use phpmock\phpunit\PHPMock;
-use PHPUnit\Framework\MockObject\MockObject;
-use PHPUnit\Framework\TestCase;
-
-class AbstractSpinlockWithTokenMutexTest extends TestCase
-{
-    use PHPMock;
-
-    #[\Override]
-    protected function setUp(): void
-    {
-        parent::setUp();
-
-        $sleepBuilder = new SleepEnvironmentBuilder();
-        $sleepBuilder->addNamespace(__NAMESPACE__);
-        $sleepBuilder->addNamespace('Malkusch\Lock\Mutex');
-        $sleepBuilder->addNamespace('Malkusch\Lock\Util');
-        $sleep = $sleepBuilder->build();
-        try {
-            $sleep->enable();
-            $this->registerForTearDown($sleep);
-        } catch (MockEnabledException $e) {
-            // workaround for burn testing
-            \assert($e->getMessage() === 'microtime is already enabled. Call disable() on the existing mock.');
-        }
-    }
-
-    /**
-     * @return AbstractSpinlockWithTokenMutex&MockObject
-     */
-    private function createSpinlockWithTokenMutexMock(float $acquireTimeout = 3, float $expireTimeout = \INF): AbstractSpinlockWithTokenMutex
-    {
-        return $this->getMockBuilder(AbstractSpinlockWithTokenMutex::class)
-            ->setConstructorArgs(['test', $acquireTimeout, $expireTimeout])
-            ->onlyMethods(['acquireWithToken', 'releaseWithToken'])
-            ->getMock();
-    }
-
-    public function testExecuteExpireTimeout(): void
-    {
-        $mutex = $this->createSpinlockWithTokenMutexMock(0.1, 0.2);
-        $mutex->expects(self::once())
-            ->method('acquireWithToken')
-            ->with(self::anything(), 0.2)
-            ->willReturn('xx');
-
-        $mutex->expects(self::once())
-            ->method('releaseWithToken')
-            ->with(self::anything(), 'xx')
-            ->willReturn(true);
-
-        $mutex->synchronized(static function () {
-            usleep(199 * 1000);
-        });
-    }
-
-    public function testExecuteTooLong(): void
-    {
-        $mutex = $this->createSpinlockWithTokenMutexMock(0.1, 0.2);
-        $mutex->expects(self::any())
-            ->method('acquireWithToken')
-            ->with(self::anything(), 0.2)
-            ->willReturn('xx');
-
-        $mutex->expects(self::any())
-            ->method('releaseWithToken')
-            ->willReturn(true);
-
-        $this->expectException(ExecutionOutsideLockException::class);
-        $this->expectExceptionMessageMatches('~^The code executed for 0\.2\d+ seconds\. But the expire timeout is 0\.2 seconds. The last 0\.0\d+ seconds were executed outside of the lock\.$~');
-        $mutex->synchronized(static function () {
-            usleep(201 * 1000);
-        });
-    }
-}
diff --git a/tests/Mutex/DistributedMutexTest.php b/tests/Mutex/DistributedMutexTest.php
index 883c85cc..a3926f96 100644
--- a/tests/Mutex/DistributedMutexTest.php
+++ b/tests/Mutex/DistributedMutexTest.php
@@ -8,100 +8,35 @@
 use Malkusch\Lock\Exception\LockAcquireTimeoutException;
 use Malkusch\Lock\Exception\LockReleaseException;
 use Malkusch\Lock\Exception\MutexException;
-use Malkusch\Lock\Mutex\AbstractSpinlockMutex;
 use Malkusch\Lock\Mutex\AbstractSpinlockWithTokenMutex;
 use Malkusch\Lock\Mutex\DistributedMutex;
-use Malkusch\Lock\Util\LockUtil;
+use Malkusch\Lock\Mutex\RedisMutex;
 use phpmock\environment\SleepEnvironmentBuilder;
-use phpmock\MockEnabledException;
 use phpmock\phpunit\PHPMock;
 use PHPUnit\Framework\Attributes\DataProvider;
-use PHPUnit\Framework\MockObject\MockObject;
 use PHPUnit\Framework\TestCase;
-use Predis\PredisException;
-use Psr\Log\LoggerInterface;
 
 class DistributedMutexTest extends TestCase
 {
     use PHPMock;
 
-    #[\Override]
-    protected function setUp(): void
+    public function testAddFails(): void
     {
-        parent::setUp();
-
-        $sleepBuilder = new SleepEnvironmentBuilder();
-        $sleepBuilder->addNamespace(__NAMESPACE__);
-        $sleepBuilder->addNamespace('Malkusch\Lock\Mutex');
-        $sleepBuilder->addNamespace('Malkusch\Lock\Util');
-        $sleep = $sleepBuilder->build();
-        try {
-            $sleep->enable();
-            $this->registerForTearDown($sleep);
-        } catch (MockEnabledException $e) {
-            // workaround for burn testing
-            \assert($e->getMessage() === 'microtime is already enabled. Call disable() on the existing mock.');
-        }
-    }
-
-    /**
-     * @param int $count The amount of redis APIs
-     *
-     * @return DistributedMutex&MockObject
-     */
-    private function createDistributedMutexMock(int $count, float $acquireTimeout = 1, float $expireTimeout = \INF): DistributedMutex
-    {
-        $mutexes = array_map(
-            function (int $i) {
-                $mutex = $this->getMockBuilder(AbstractSpinlockWithTokenMutex::class)
-                    ->setConstructorArgs(['test', \INF])
-                    ->onlyMethods(['acquireWithToken', 'releaseWithToken'])
-                    ->getMock();
-
-                $mutex
-                    ->method('acquireWithToken')
-                    ->with(self::anything(), \INF)
-                    ->willReturn('x' . $i);
-
-                $mutex
-                    ->method('releaseWithToken')
-                    ->with(self::anything(), 'x' . $i)
-                    ->willReturn(true);
-
-                return $mutex;
-            },
-            range(0, $count - 1)
-        );
-
-        return $this->getMockBuilder(DistributedMutex::class)
-            ->setConstructorArgs([$mutexes, $acquireTimeout, $expireTimeout])
-            ->onlyMethods(['acquireMutex', 'releaseMutex'])
-            ->getMock();
-    }
-
-    /**
-     * Tests acquire() fails because too few servers are available.
-     *
-     * @param int $count     The total count of servers
-     * @param int $available The count of available servers
-     *
-     * @dataProvider provideMinorityCases
-     */
-    #[DataProvider('provideMinorityCases')]
-    public function testTooFewServerToAcquire(int $count, int $available): void
-    {
-        $mutex = $this->createDistributedMutexMock($count);
-
-        $i = 0;
-        $mutex->expects(self::atMost((int) floor($count / 2) + $count - $available))
-            ->method('acquireMutex')
-            ->willReturnCallback(static function () use (&$i, $available) {
-                if ($i++ < $available) {
-                    return true;
+        $mutex = new DistributedMutex(array_map(static function (string $v) {
+            return new class(new \stdClass(), 'test') extends RedisMutex {
+                #[\Override]
+                protected function acquireWithToken(string $key, float $expireTimeout)
+                {
+                    throw new LockAcquireException('xxx');
                 }
 
-                throw new LockAcquireException();
-            });
+                #[\Override]
+                protected function releaseWithToken(string $key, string $token): bool
+                {
+                    throw new LockReleaseException('yyy');
+                }
+            };
+        }, ['a', 'b']));
 
         $this->expectException(LockAcquireException::class);
         $this->expectExceptionCode(MutexException::CODE_REDLOCK_NOT_ENOUGH_SERVERS);
@@ -110,186 +45,65 @@ public function testTooFewServerToAcquire(int $count, int $available): void
         });
     }
 
-    /**
-     * Tests synchronized() does work if the majority of servers is up.
-     *
-     * @param int $count     The total count of servers
-     * @param int $available The count of available servers
-     *
-     * @dataProvider provideMajorityCases
-     */
-    #[DataProvider('provideMajorityCases')]
-    public function testFaultTolerance(int $count, int $available): void
-    {
-        $mutex = $this->createDistributedMutexMock($count);
-        $mutex->expects(self::exactly($available))
-            ->method('releaseMutex')
-            ->willReturn(true);
-
-        $i = 0;
-        $mutex->expects(self::exactly($count))
-            ->method('acquireMutex')
-            ->willReturnCallback(static function () use (&$i, $available) {
-                if ($i++ < $available) {
-                    return true;
-                }
-
-                throw new LockAcquireException();
-            });
-
-        $mutex->synchronized(static function () {});
-    }
-
     /**
      * Tests too few keys could be acquired.
      *
      * @param int $count     The total count of servers
      * @param int $available The count of available servers
      *
-     * @dataProvider provideMinorityCases
+     * @dataProvider provideAcquireTooFewKeysCases
      */
-    #[DataProvider('provideMinorityCases')]
+    #[DataProvider('provideAcquireTooFewKeysCases')]
     public function testAcquireTooFewKeys(int $count, int $available): void
     {
-        $mutex = $this->createDistributedMutexMock($count);
-
-        $i = 0;
-        $mutex->expects(self::any())
-            ->method('acquireMutex')
-            ->willReturnCallback(static function () use (&$i, $available) {
-                return ++$i <= $available;
-            });
-
-        $this->expectException(LockAcquireTimeoutException::class);
-        $this->expectExceptionMessage('Lock acquire timeout of 1.0 seconds has been exceeded');
-        $mutex->synchronized(static function () {
-            self::fail();
-        });
-    }
+        var_dump('x ' . (memory_get_usage() / (1024 * 1024)));
 
-    /**
-     * Tests acquiring keys takes too long.
-     *
-     * @param int   $count   The total count of servers
-     * @param float $timeout In seconds
-     * @param float $delay   In seconds
-     *
-     * @dataProvider provideAcquireTimeoutsCases
-     */
-    #[DataProvider('provideAcquireTimeoutsCases')]
-    public function testAcquireTimeouts(int $count, float $timeout, float $delay): void
-    {
-        $mutex = $this->createDistributedMutexMock($count, $timeout, $timeout);
-        $mutex->expects(self::exactly($count))
-            ->method('releaseMutex')
-            ->willReturn(true);
+        $sleepBuilder = new SleepEnvironmentBuilder();
+        $sleepBuilder->addNamespace('Malkusch\Lock\Mutex');
+        $sleepBuilder->addNamespace('Malkusch\Lock\Util');
+        $sleep = $sleepBuilder->build();
+        $sleep->enable();
+        $this->registerForTearDown($sleep);
 
-        $mutex->expects(self::exactly($count))
-            ->method('acquireMutex')
-            ->willReturnCallback(static function () use ($delay) {
-                usleep((int) ($delay * 1e6));
+        global $cc;
+        $cc = 0;
 
-                return true;
-            });
+        $mutexes = array_map(
+            function (int $i) {
+                $mutex = $this->getMockBuilder(AbstractSpinlockWithTokenMutex::class)
+                    ->setConstructorArgs(['test', \INF])
+                    ->onlyMethods(['acquireWithToken2'])
+                    ->getMock();
 
-        $this->expectException(LockAcquireTimeoutException::class);
-        $this->expectExceptionMessage('Lock acquire timeout of ' . LockUtil::getInstance()->formatTimeout($timeout) . ' seconds has been exceeded');
-        $mutex->synchronized(static function () {
-            self::fail();
-        });
-    }
+                /* $mutex
+                    ->method('acquireWithToken2')
+                    ->willReturn('x'); */
 
-    /**
-     * @return iterable<list<mixed>>
-     */
-    public static function provideAcquireTimeoutsCases(): iterable
-    {
-        yield [1, 1.2, 1.201];
-        yield [2, 20.4, 10.201];
-    }
+                return $mutex;
+            },
+            range(0, $count - 1)
+        );
 
-    /**
-     * Tests synchronized() works if the majority of keys was acquired.
-     *
-     * @param int $count     The total count of servers
-     * @param int $available The count of available servers
-     *
-     * @dataProvider provideMajorityCases
-     */
-    #[DataProvider('provideMajorityCases')]
-    public function testAcquireWithMajority(int $count, int $available): void
-    {
-        $mutex = $this->createDistributedMutexMock($count);
-        $mutex->expects(self::exactly($available))
-            ->method('releaseMutex')
-            ->willReturn(true);
+        $mutex = $this->getMockBuilder(DistributedMutex::class)
+            ->setConstructorArgs([$mutexes, 1, \INF])
+            ->onlyMethods(['acquireMutex'])
+            ->getMock();
 
         $i = 0;
-        $mutex->expects(self::exactly($count))
+        $mutex
             ->method('acquireMutex')
-            ->willReturnCallback(static function () use (&$i, $available) {
+            /* ->willReturnCallback(static function () use (&$i, $available) {
                 return ++$i <= $available;
-            });
-
-        $mutex->synchronized(static function () {});
-    }
-
-    /**
-     * Tests releasing fails because too few servers are available.
-     *
-     * @param int $count     The total count of servers
-     * @param int $available The count of available servers
-     *
-     * @dataProvider provideMinorityCases
-     */
-    #[DataProvider('provideMinorityCases')]
-    public function testTooFewServersToRelease(int $count, int $available): void
-    {
-        $mutex = $this->createDistributedMutexMock($count);
-        $mutex->expects(self::exactly($count))
-            ->method('acquireMutex')
-            ->willReturn(true);
-
-        $i = 0;
-        $mutex->expects(self::exactly($count))
-            ->method('releaseMutex')
-            ->willReturnCallback(static function () use (&$i, $available) {
-                if ($i++ < $available) {
-                    return true;
-                }
-
-                throw new LockReleaseException();
-            });
-
-        $this->expectException(LockReleaseException::class);
-        $mutex->synchronized(static function () {});
-    }
-
-    /**
-     * Tests releasing too few keys.
-     *
-     * @param int $count     The total count of servers
-     * @param int $available The count of available servers
-     *
-     * @dataProvider provideMinorityCases
-     */
-    #[DataProvider('provideMinorityCases')]
-    public function testReleaseTooFewKeys(int $count, int $available): void
-    {
-        $mutex = $this->createDistributedMutexMock($count);
-        $mutex->expects(self::exactly($count))
-            ->method('acquireMutex')
-            ->willReturn(true);
+            }) */;
 
-        $i = 0;
-        $mutex->expects(self::exactly($count))
-            ->method('releaseMutex')
-            ->willReturnCallback(static function () use (&$i, $available) {
-                return ++$i <= $available;
-            });
+        /* $mutex = new class($mutexes, 1, \INF) extends DistributedMutex {};
+        $mutex->available = $available; */
 
-        $this->expectException(LockReleaseException::class);
-        $mutex->synchronized(static function () {});
+        $this->expectException(LockAcquireTimeoutException::class);
+        $this->expectExceptionMessage('Lock acquire timeout of 1.0 seconds has been exceeded');
+        $mutex->synchronized(static function () {
+            self::fail();
+        });
     }
 
     /**
@@ -297,7 +111,7 @@ public function testReleaseTooFewKeys(int $count, int $available): void
      *
      * @return iterable<list<mixed>>
      */
-    public static function provideMinorityCases(): iterable
+    public static function provideAcquireTooFewKeysCases(): iterable
     {
         yield [1, 0];
         yield [2, 0];
@@ -311,64 +125,4 @@ public static function provideMinorityCases(): iterable
         yield [6, 2];
         yield [6, 3];
     }
-
-    /**
-     * Provides test cases with enough.
-     *
-     * @return iterable<list<mixed>>
-     */
-    public static function provideMajorityCases(): iterable
-    {
-        yield [1, 1];
-        yield [2, 2];
-        yield [3, 2];
-        yield [3, 3];
-        yield [4, 3];
-        yield [5, 3];
-    }
-
-    public function testAcquireMutexLogger(): void
-    {
-        $mutex = $this->createDistributedMutexMock(3);
-        $logger = $this->createMock(LoggerInterface::class);
-        $mutex->setLogger($logger);
-
-        $mutex->expects(self::exactly(2))
-            ->method('acquireMutex')
-            ->with(self::isInstanceOf(AbstractSpinlockMutex::class), 'distributed', 1.0, \INF)
-            ->willThrowException($this->createMock(/* PredisException::class */ LockAcquireException::class));
-
-        $logger->expects(self::exactly(2))
-            ->method('warning')
-            ->with('Could not set {key} = {token} at server #{index}', self::anything());
-
-        $this->expectException(LockAcquireException::class);
-        $this->expectExceptionMessage('It is not possible to acquire a lock because at least half of the servers are not available');
-        $mutex->synchronized(static function () {
-            self::fail();
-        });
-    }
-
-    public function testReleaseMutexLogger(): void
-    {
-        $mutex = $this->createDistributedMutexMock(3);
-        $logger = $this->createMock(LoggerInterface::class);
-        $mutex->setLogger($logger);
-
-        $mutex->expects(self::exactly(3))
-            ->method('acquireMutex')
-            ->willReturn(true);
-
-        $mutex->expects(self::exactly(3))
-            ->method('releaseMutex')
-            ->with(self::isInstanceOf(AbstractSpinlockMutex::class), 'distributed', \INF)
-            ->willThrowException($this->createMock(/* PredisException::class */ LockReleaseException::class));
-
-        $logger->expects(self::exactly(3))
-            ->method('warning')
-            ->with('Could not unset {key} = {token} at server #{index}', self::anything());
-
-        $this->expectException(LockReleaseException::class);
-        $mutex->synchronized(static function () {});
-    }
 }
diff --git a/tests/Mutex/FlockMutexTest.php b/tests/Mutex/FlockMutexTest.php
deleted file mode 100644
index cd147d1c..00000000
--- a/tests/Mutex/FlockMutexTest.php
+++ /dev/null
@@ -1,115 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Malkusch\Lock\Tests\Mutex;
-
-use Eloquent\Liberator\Liberator;
-use Malkusch\Lock\Exception\DeadlineException;
-use Malkusch\Lock\Exception\LockAcquireTimeoutException;
-use Malkusch\Lock\Mutex\FlockMutex;
-use Malkusch\Lock\Util\LockUtil;
-use Malkusch\Lock\Util\PcntlTimeout;
-use PHPUnit\Framework\Attributes\DataProvider;
-use PHPUnit\Framework\Attributes\RequiresPhpExtension;
-use PHPUnit\Framework\TestCase;
-
-class FlockMutexTest extends TestCase
-{
-    /** @var FlockMutex */
-    private $mutex;
-
-    private string $file;
-
-    #[\Override]
-    protected function setUp(): void
-    {
-        parent::setUp();
-
-        $this->file = LockUtil::getInstance()->makeRandomTemporaryFilePath('flock');
-        touch($this->file);
-        $this->mutex = Liberator::liberate(new FlockMutex(fopen($this->file, 'r'), 1)); // @phpstan-ignore assign.propertyType
-    }
-
-    #[\Override]
-    protected function tearDown(): void
-    {
-        unlink($this->file);
-
-        parent::tearDown();
-    }
-
-    /**
-     * @param FlockMutex::STRATEGY_* $strategy
-     *
-     * @dataProvider provideTimeoutableStrategiesCases
-     */
-    #[DataProvider('provideTimeoutableStrategiesCases')]
-    public function testCodeExecutedOutsideLockIsNotThrown(string $strategy): void
-    {
-        $this->mutex->strategy = $strategy; // @phpstan-ignore property.private
-
-        self::assertTrue($this->mutex->synchronized(static function () { // @phpstan-ignore staticMethod.alreadyNarrowedType
-            usleep(1100 * 1000);
-
-            return true;
-        }));
-    }
-
-    /**
-     * @param FlockMutex::STRATEGY_* $strategy
-     *
-     * @dataProvider provideTimeoutableStrategiesCases
-     */
-    #[DataProvider('provideTimeoutableStrategiesCases')]
-    public function testAcquireTimeoutOccurs(string $strategy): void
-    {
-        $anotherResource = fopen($this->file, 'r');
-        flock($anotherResource, \LOCK_EX);
-
-        $this->mutex->strategy = $strategy; // @phpstan-ignore property.private
-
-        $this->expectException(LockAcquireTimeoutException::class);
-        $this->expectExceptionMessage('Lock acquire timeout of 1.0 seconds has been exceeded');
-        try {
-            $this->mutex->synchronized(static function () {
-                self::fail();
-            });
-        } finally {
-            fclose($anotherResource);
-        }
-    }
-
-    /**
-     * @return iterable<list<mixed>>
-     */
-    public static function provideTimeoutableStrategiesCases(): iterable
-    {
-        if (extension_loaded('pcntl')) {
-            yield [\Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)()];
-        }
-
-        yield [\Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)()];
-    }
-
-    /**
-     * @requires extension pcntl
-     */
-    #[RequiresPhpExtension('pcntl')]
-    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
-
-        $timebox = new PcntlTimeout(1);
-
-        $this->expectException(DeadlineException::class);
-        $timebox->timeBoxed(function () {
-            $this->mutex->synchronized(static function () {
-                self::fail();
-            });
-        });
-    }
-}
diff --git a/tests/Mutex/MemcachedMutexTest.php b/tests/Mutex/MemcachedMutexTest.php
deleted file mode 100644
index e836ed28..00000000
--- a/tests/Mutex/MemcachedMutexTest.php
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Malkusch\Lock\Tests\Mutex;
-
-use Malkusch\Lock\Exception\LockAcquireTimeoutException;
-use Malkusch\Lock\Exception\LockReleaseException;
-use Malkusch\Lock\Mutex\MemcachedMutex;
-use PHPUnit\Framework\Attributes\RequiresPhpExtension;
-use PHPUnit\Framework\MockObject\MockObject;
-use PHPUnit\Framework\TestCase;
-
-/**
- * Please provide the environment variable MEMCACHE_HOST.
- *
- * @requires extension memcached
- */
-#[RequiresPhpExtension('memcached')]
-class MemcachedMutexTest extends TestCase
-{
-    /** @var \Memcached&MockObject */
-    protected $memcached;
-
-    /** @var MemcachedMutex */
-    private $mutex;
-
-    #[\Override]
-    protected function setUp(): void
-    {
-        parent::setUp();
-
-        $this->memcached = $this->createMock(\Memcached::class);
-        $this->mutex = new MemcachedMutex('test', $this->memcached, 1, 2);
-    }
-
-    public function testAcquireFail(): void
-    {
-        $this->memcached->expects(self::atLeastOnce())
-            ->method('add')
-            ->with('php-malkusch-lock:test', true, 3)
-            ->willReturn(false);
-
-        $this->expectException(LockAcquireTimeoutException::class);
-        $this->mutex->synchronized(static function () {
-            self::fail();
-        });
-    }
-
-    public function testReleaseFail(): void
-    {
-        $this->memcached->expects(self::once())
-            ->method('add')
-            ->with('php-malkusch-lock:test', true, 3)
-            ->willReturn(true);
-
-        $this->memcached->expects(self::once())
-            ->method('delete')
-            ->with('php-malkusch-lock:test')
-            ->willReturn(false);
-
-        $this->expectException(LockReleaseException::class);
-        $this->mutex->synchronized(static function () {});
-    }
-
-    public function testAcquireExpireTimeoutLimit(): void
-    {
-        $this->mutex = new MemcachedMutex('test', $this->memcached);
-
-        $this->memcached->expects(self::once())
-            ->method('add')
-            ->with('php-malkusch-lock:test', true, 0)
-            ->willReturn(true);
-
-        $this->memcached->expects(self::once())
-            ->method('delete')
-            ->with('php-malkusch-lock:test')
-            ->willReturn(true);
-
-        $this->mutex->synchronized(static function () {});
-    }
-}
diff --git a/tests/Mutex/MutexConcurrencyTest.php b/tests/Mutex/MutexConcurrencyTest.php
deleted file mode 100644
index 76a39e9d..00000000
--- a/tests/Mutex/MutexConcurrencyTest.php
+++ /dev/null
@@ -1,273 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Malkusch\Lock\Tests\Mutex;
-
-use Eloquent\Liberator\Liberator;
-use Malkusch\Lock\Mutex\DistributedMutex;
-use Malkusch\Lock\Mutex\FlockMutex;
-use Malkusch\Lock\Mutex\MemcachedMutex;
-use Malkusch\Lock\Mutex\Mutex;
-use Malkusch\Lock\Mutex\MySQLMutex;
-use Malkusch\Lock\Mutex\PostgreSQLMutex;
-use Malkusch\Lock\Mutex\RedisMutex;
-use Malkusch\Lock\Mutex\SemaphoreMutex;
-use Malkusch\Lock\Util\LockUtil;
-use PHPUnit\Framework\Attributes\DataProvider;
-use PHPUnit\Framework\Constraint\IsType;
-use PHPUnit\Framework\TestCase;
-use Predis\Client as PredisClient;
-use Spatie\Async\Pool;
-
-/**
- * If you want to run integration tests you should provide these environment variables:
- *
- * - MEMCACHE_HOST
- * - REDIS_URIS - a comma separated list of redis:// URIs.
- * - MYSQL_DSN, MYSQL_USER, MYSQL_PASSWORD
- * - PGSQL_DSN, PGSQL_USER, PGSQL_PASSWORD
- */
-class MutexConcurrencyTest extends TestCase
-{
-    /** @var list<string> */
-    protected static $temporaryFiles = [];
-
-    #[\Override]
-    public static function tearDownAfterClass(): void
-    {
-        foreach (self::$temporaryFiles as $temporaryFile) {
-            unlink($temporaryFile);
-        }
-        self::$temporaryFiles = [];
-
-        parent::tearDownAfterClass();
-    }
-
-    /**
-     * Forks, runs code in the children and wait until all finished.
-     *
-     * @param \Closure(): void $code The code for the fork
-     */
-    private function fork(int $concurrency, \Closure $code): void
-    {
-        $pool = Pool::create();
-
-        for ($i = 0; $i < $concurrency; ++$i) {
-            $pool[] = async($code);
-        }
-
-        await($pool);
-    }
-
-    /**
-     * Tests high contention empirically.
-     *
-     * @param \Closure(0|1): int     $code         The counter code
-     * @param \Closure(float): Mutex $mutexFactory
-     * @param \Closure(): void       $setUp
-     *
-     * @dataProvider provideHighContentionCases
-     */
-    #[DataProvider('provideHighContentionCases')]
-    public function testHighContention(\Closure $code, \Closure $mutexFactory, ?\Closure $setUp = null): void
-    {
-        if ($setUp !== null) {
-            $setUp();
-        }
-
-        $concurrency = 10;
-        $iterations = 1000 / $concurrency;
-        $timeout = $concurrency * 20;
-
-        $this->fork($concurrency, static function () use ($mutexFactory, $timeout, $iterations, $code) {
-            $mutex = $mutexFactory($timeout);
-            for ($i = 0; $i < $iterations; ++$i) {
-                $mutex->synchronized(static function () use ($code) {
-                    $code(1);
-                });
-            }
-        });
-
-        $counter = $code(0);
-        self::assertSame($concurrency * $iterations, $counter);
-    }
-
-    /**
-     * @return iterable<list<mixed>>
-     */
-    public static function provideHighContentionCases(): iterable
-    {
-        foreach (static::provideExecutionIsSerializedWhenLockedCases() as $name => [$mutexFactory]) {
-            $filename = LockUtil::getInstance()->makeRandomTemporaryFilePath('test-mutex-concurrency');
-
-            static::$temporaryFiles[] = $filename;
-
-            yield $name => [
-                static function (int $increment) use ($filename) {
-                    $counter = file_get_contents($filename);
-                    $counter += $increment;
-
-                    file_put_contents($filename, $counter);
-
-                    return $counter;
-                },
-                $mutexFactory,
-                static function () use ($filename) {
-                    file_put_contents($filename, '0');
-                },
-            ];
-        }
-    }
-
-    /**
-     * Tests that five processes run sequentially.
-     *
-     * @param \Closure(float): Mutex $mutexFactory
-     *
-     * @dataProvider provideExecutionIsSerializedWhenLockedCases
-     */
-    #[DataProvider('provideExecutionIsSerializedWhenLockedCases')]
-    public function testExecutionIsSerializedWhenLocked(\Closure $mutexFactory): void
-    {
-        $time = \microtime(true);
-
-        $this->fork(6, static function () use ($mutexFactory) {
-            $mutex = $mutexFactory(3);
-            $mutex->synchronized(static function () {
-                \usleep(200 * 1000);
-            });
-        });
-
-        $delta = \microtime(true) - $time;
-        self::assertGreaterThan(1.201, $delta);
-    }
-
-    /**
-     * Provides Mutex factories.
-     *
-     * @return iterable<list<mixed>>
-     */
-    public static function provideExecutionIsSerializedWhenLockedCases(): iterable
-    {
-        $filename = LockUtil::getInstance()->makeRandomTemporaryFilePath('test-mutex-concurrency');
-
-        self::$temporaryFiles[] = $filename;
-
-        yield 'flock' => [static function ($timeout) use ($filename) {
-            $file = fopen($filename, 'w');
-
-            return new FlockMutex($file, $timeout);
-        }];
-
-        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
-
-                return $lock->popsValue();
-            }];
-        }
-
-        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
-
-            return $lock->popsValue();
-        }];
-
-        if (extension_loaded('sysvsem')) {
-            yield 'semaphore' => [static function () use ($filename) {
-                $semaphore = sem_get(ftok($filename, 'b'));
-                self::assertThat(
-                    $semaphore,
-                    self::logicalOr(
-                        self::isInstanceOf(\SysvSemaphore::class),
-                        new IsType(IsType::TYPE_RESOURCE)
-                    )
-                );
-
-                return new SemaphoreMutex($semaphore);
-            }];
-        }
-
-        if (getenv('MEMCACHE_HOST')) {
-            yield 'memcached' => [static function ($timeout) {
-                $memcached = new \Memcached();
-                $memcached->addServer(getenv('MEMCACHE_HOST'), 11211);
-
-                return new MemcachedMutex('test', $memcached, $timeout);
-            }];
-        }
-
-        if (getenv('REDIS_URIS')) {
-            $uris = explode(',', getenv('REDIS_URIS'));
-
-            yield 'DistributedMutex RedisMutex /w Predis' => [static function ($timeout) use ($uris) {
-                $clients = array_map(
-                    static fn ($uri) => new PredisClient($uri),
-                    $uris
-                );
-
-                $mutexes = array_map(
-                    static fn ($client) => new RedisMutex($client, 'test', $timeout),
-                    $clients
-                );
-
-                return new DistributedMutex($mutexes, $timeout);
-            }];
-
-            if (class_exists(\Redis::class)) {
-                yield 'DistributedMutex RedisMutex /w PHPRedis' => [
-                    static function ($timeout) use ($uris) {
-                        $clients = array_map(
-                            static function (string $uri): \Redis {
-                                $redis = new \Redis();
-
-                                $uri = parse_url($uri);
-                                $redis->connect($uri['host'], $uri['port'] ?? 6379);
-                                if (!empty($uri['pass'])) {
-                                    $redis->auth(
-                                        empty($uri['user'])
-                                        ? $uri['pass']
-                                        : [$uri['user'], $uri['pass']]
-                                    );
-                                }
-
-                                return $redis;
-                            },
-                            $uris
-                        );
-
-                        $mutexes = array_map(
-                            static fn ($client) => new RedisMutex($client, 'test', $timeout),
-                            $clients
-                        );
-
-                        return new DistributedMutex($mutexes, $timeout);
-                    },
-                ];
-            }
-        }
-
-        if (getenv('MYSQL_DSN')) {
-            yield 'MySQLMutex' => [static function ($timeout) {
-                $pdo = new \PDO(getenv('MYSQL_DSN'), getenv('MYSQL_USER'), getenv('MYSQL_PASSWORD'));
-                $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
-
-                return new MySQLMutex($pdo, 'test', $timeout);
-            }];
-        }
-
-        if (getenv('PGSQL_DSN')) {
-            yield 'PostgreSQLMutex' => [static function () {
-                $pdo = new \PDO(getenv('PGSQL_DSN'), getenv('PGSQL_USER'), getenv('PGSQL_PASSWORD'));
-                $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
-
-                return new PostgreSQLMutex($pdo, 'test');
-            }];
-        }
-    }
-}
diff --git a/tests/Mutex/MutexTest.php b/tests/Mutex/MutexTest.php
deleted file mode 100644
index 84880d20..00000000
--- a/tests/Mutex/MutexTest.php
+++ /dev/null
@@ -1,245 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Malkusch\Lock\Tests\Mutex;
-
-use Eloquent\Liberator\Liberator;
-use Malkusch\Lock\Mutex\AbstractLockMutex;
-use Malkusch\Lock\Mutex\AbstractSpinlockMutex;
-use Malkusch\Lock\Mutex\DistributedMutex;
-use Malkusch\Lock\Mutex\FlockMutex;
-use Malkusch\Lock\Mutex\MemcachedMutex;
-use Malkusch\Lock\Mutex\Mutex;
-use Malkusch\Lock\Mutex\MySQLMutex;
-use Malkusch\Lock\Mutex\NullMutex;
-use Malkusch\Lock\Mutex\PostgreSQLMutex;
-use Malkusch\Lock\Mutex\RedisMutex;
-use Malkusch\Lock\Mutex\SemaphoreMutex;
-use org\bovigo\vfs\vfsStream;
-use PHPUnit\Framework\Attributes\DataProvider;
-use PHPUnit\Framework\Attributes\DoesNotPerformAssertions;
-use PHPUnit\Framework\TestCase;
-use Predis\Client as PredisClient;
-
-/**
- * If you want to run integrations tests you should provide these environment variables:
- *
- * - MEMCACHE_HOST
- * - REDIS_URIS - a comma separated list of redis:// URIs.
- */
-class MutexTest extends TestCase
-{
-    protected const TIMEOUT = 4;
-
-    #[\Override]
-    public static function setUpBeforeClass(): void
-    {
-        parent::setUpBeforeClass();
-
-        vfsStream::setup('test');
-    }
-
-    /**
-     * Provides Mutex factories.
-     *
-     * @return iterable<list<mixed>>
-     */
-    public static function provideMutexFactoriesCases(): iterable
-    {
-        yield 'NullMutex' => [static function () {
-            return new NullMutex();
-        }];
-
-        yield 'FlockMutex' => [static function () {
-            $file = fopen(vfsStream::url('test/lock'), 'w');
-
-            return new FlockMutex($file);
-        }];
-
-        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
-
-                return $lock->popsValue();
-            }];
-        }
-
-        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
-
-            return $lock->popsValue();
-        }];
-
-        if (extension_loaded('sysvsem')) {
-            yield 'SemaphoreMutex' => [static function () {
-                return new SemaphoreMutex(sem_get(ftok(__FILE__, 'a')));
-            }];
-        }
-
-        yield 'AbstractLockMutex' => [static function () {
-            $lock = new class extends AbstractLockMutex {
-                #[\Override]
-                protected function lock(): void {}
-
-                #[\Override]
-                protected function unlock(): void {}
-            };
-
-            return $lock;
-        }];
-
-        yield 'AbstractSpinlockMutex' => [static function () {
-            $lock = new class('test') extends AbstractSpinlockMutex {
-                #[\Override]
-                protected function acquire(string $key): bool
-                {
-                    return true;
-                }
-
-                #[\Override]
-                protected function release(string $key): bool
-                {
-                    return true;
-                }
-            };
-
-            return $lock;
-        }];
-
-        if (getenv('MEMCACHE_HOST')) {
-            yield 'MemcachedMutex' => [static function () {
-                $memcached = new \Memcached();
-                $memcached->addServer(getenv('MEMCACHE_HOST'), 11211);
-
-                return new MemcachedMutex('test', $memcached, self::TIMEOUT);
-            }];
-        }
-
-        if (getenv('REDIS_URIS')) {
-            $uris = explode(',', getenv('REDIS_URIS'));
-
-            yield 'DistributedMutex RedisMutex /w Predis' => [static function () use ($uris) {
-                $clients = array_map(
-                    static fn ($uri) => new PredisClient($uri),
-                    $uris
-                );
-
-                $mutexes = array_map(
-                    static fn ($client) => new RedisMutex($client, 'test', self::TIMEOUT),
-                    $clients
-                );
-
-                return new DistributedMutex($mutexes, self::TIMEOUT);
-            }];
-
-            if (class_exists(\Redis::class)) {
-                yield 'DistributedMutex RedisMutex /w PHPRedis' => [
-                    static function () use ($uris) {
-                        $clients = array_map(
-                            static function ($uri) {
-                                $redis = new \Redis();
-
-                                $uri = parse_url($uri);
-                                $redis->connect($uri['host'], $uri['port'] ?? 6379);
-                                if (!empty($uri['pass'])) {
-                                    $redis->auth(
-                                        empty($uri['user'])
-                                        ? $uri['pass']
-                                        : [$uri['user'], $uri['pass']]
-                                    );
-                                }
-
-                                return $redis;
-                            },
-                            $uris
-                        );
-
-                        $mutexes = array_map(
-                            static fn ($client) => new RedisMutex($client, 'test', self::TIMEOUT),
-                            $clients
-                        );
-
-                        return new DistributedMutex($mutexes, self::TIMEOUT);
-                    },
-                ];
-            }
-        }
-
-        if (getenv('MYSQL_DSN')) {
-            yield 'MySQLMutex' => [static function () {
-                $pdo = new \PDO(getenv('MYSQL_DSN'), getenv('MYSQL_USER'), getenv('MYSQL_PASSWORD'));
-                $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
-
-                return new MySQLMutex($pdo, 'test', self::TIMEOUT);
-            }];
-        }
-
-        if (getenv('PGSQL_DSN')) {
-            yield 'PostgreSQLMutex' => [static function () {
-                $pdo = new \PDO(getenv('PGSQL_DSN'), getenv('PGSQL_USER'), getenv('PGSQL_PASSWORD'));
-                $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
-
-                return new PostgreSQLMutex($pdo, 'test');
-            }];
-        }
-    }
-
-    /**
-     * Tests synchronized() executes the code and returns its result.
-     *
-     * @param \Closure(): Mutex $mutexFactory
-     *
-     * @dataProvider provideMutexFactoriesCases
-     */
-    #[DataProvider('provideMutexFactoriesCases')]
-    public function testSynchronizedDelegates(\Closure $mutexFactory): void
-    {
-        $mutex = $mutexFactory();
-        $result = $mutex->synchronized(static function () {
-            return 'test';
-        });
-        self::assertSame('test', $result);
-    }
-
-    /**
-     * Tests that synchronized() released the lock.
-     *
-     * @param \Closure(): Mutex $mutexFactory
-     *
-     * @doesNotPerformAssertions
-     *
-     * @dataProvider provideMutexFactoriesCases
-     */
-    #[DoesNotPerformAssertions]
-    #[DataProvider('provideMutexFactoriesCases')]
-    public function testRelease(\Closure $mutexFactory): void
-    {
-        $mutex = $mutexFactory();
-        $mutex->synchronized(static function () {});
-
-        $mutex->synchronized(static function () {});
-    }
-
-    /**
-     * Tests synchronized() rethrows the exception of the code.
-     *
-     * @param \Closure(): Mutex $mutexFactory
-     *
-     * @dataProvider provideMutexFactoriesCases
-     */
-    #[DataProvider('provideMutexFactoriesCases')]
-    public function testSynchronizedPassesExceptionThrough(\Closure $mutexFactory): void
-    {
-        $mutex = $mutexFactory();
-
-        $this->expectException(\DomainException::class);
-        $mutex->synchronized(static function () {
-            throw new \DomainException();
-        });
-    }
-}
diff --git a/tests/Mutex/MySQLMutexTest.php b/tests/Mutex/MySQLMutexTest.php
deleted file mode 100644
index f08d4f11..00000000
--- a/tests/Mutex/MySQLMutexTest.php
+++ /dev/null
@@ -1,64 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Malkusch\Lock\Tests\Mutex;
-
-use Malkusch\Lock\Mutex\MySQLMutex;
-use PHPUnit\Framework\MockObject\MockObject;
-use PHPUnit\Framework\TestCase;
-
-class MySQLMutexTest extends TestCase
-{
-    /** @var \PDO&MockObject */
-    private $pdo;
-
-    /** @var MySQLMutex */
-    private $mutex;
-
-    #[\Override]
-    protected function setUp(): void
-    {
-        parent::setUp();
-
-        $this->pdo = $this->createMock(\PDO::class);
-
-        $this->mutex = new MySQLMutex($this->pdo, 'test');
-    }
-
-    public function testAcquireLock(): void
-    {
-        $statement = $this->createMock(\PDOStatement::class);
-
-        $this->pdo->expects(self::once())
-            ->method('prepare')
-            ->with('SELECT GET_LOCK(?, ?)')
-            ->willReturn($statement);
-
-        $statement->expects(self::once())
-            ->method('execute')
-            ->with(['php-malkusch-lock:test', 0]);
-
-        $statement->expects(self::once())
-            ->method('fetch')
-            ->willReturn([1]);
-
-        \Closure::bind(static fn ($mutex) => $mutex->lock(), null, MySQLMutex::class)($this->mutex);
-    }
-
-    public function testReleaseLock(): void
-    {
-        $statement = $this->createMock(\PDOStatement::class);
-
-        $this->pdo->expects(self::once())
-            ->method('prepare')
-            ->with('DO RELEASE_LOCK(?)')
-            ->willReturn($statement);
-
-        $statement->expects(self::once())
-            ->method('execute')
-            ->with(['php-malkusch-lock:test']);
-
-        \Closure::bind(static fn ($mutex) => $mutex->unlock(), null, MySQLMutex::class)($this->mutex);
-    }
-}
diff --git a/tests/Mutex/PostgreSQLMutexTest.php b/tests/Mutex/PostgreSQLMutexTest.php
deleted file mode 100644
index c2e0fc31..00000000
--- a/tests/Mutex/PostgreSQLMutexTest.php
+++ /dev/null
@@ -1,100 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Malkusch\Lock\Tests\Mutex;
-
-use Malkusch\Lock\Mutex\PostgreSQLMutex;
-use PHPUnit\Framework\Constraint\IsType;
-use PHPUnit\Framework\MockObject\MockObject;
-use PHPUnit\Framework\TestCase;
-
-class PostgreSQLMutexTest extends TestCase
-{
-    /** @var \PDO&MockObject */
-    private $pdo;
-
-    /** @var PostgreSQLMutex */
-    private $mutex;
-
-    #[\Override]
-    protected function setUp(): void
-    {
-        parent::setUp();
-
-        $this->pdo = $this->createMock(\PDO::class);
-
-        $this->mutex = new PostgreSQLMutex($this->pdo, 'test-one-negative-key');
-    }
-
-    private function isPhpunit9x(): bool
-    {
-        return (new \ReflectionClass(self::class))->hasMethod('getStatus');
-    }
-
-    public function testAcquireLock(): void
-    {
-        $statement = $this->createMock(\PDOStatement::class);
-
-        $this->pdo->expects(self::once())
-            ->method('prepare')
-            ->with('SELECT pg_advisory_lock(?, ?)')
-            ->willReturn($statement);
-
-        $statement->expects(self::once())
-            ->method('execute')
-            ->with(self::logicalAnd(
-                new IsType(IsType::TYPE_ARRAY),
-                self::countOf(2),
-                self::callback(function (...$arguments) {
-                    if ($this->isPhpunit9x()) { // https://github.com/sebastianbergmann/phpunit/issues/5891
-                        $arguments = $arguments[0];
-                    }
-
-                    foreach ($arguments as $v) {
-                        self::assertLessThan(1 << 32, $v);
-                        self::assertGreaterThanOrEqual(-(1 << 32), $v);
-                        self::assertIsInt($v);
-                    }
-
-                    return true;
-                }),
-                [533558444, -1716795572]
-            ));
-
-        \Closure::bind(static fn ($mutex) => $mutex->lock(), null, PostgreSQLMutex::class)($this->mutex);
-    }
-
-    public function testReleaseLock(): void
-    {
-        $statement = $this->createMock(\PDOStatement::class);
-
-        $this->pdo->expects(self::once())
-            ->method('prepare')
-            ->with('SELECT pg_advisory_unlock(?, ?)')
-            ->willReturn($statement);
-
-        $statement->expects(self::once())
-            ->method('execute')
-            ->with(self::logicalAnd(
-                new IsType(IsType::TYPE_ARRAY),
-                self::countOf(2),
-                self::callback(function (...$arguments) {
-                    if ($this->isPhpunit9x()) { // https://github.com/sebastianbergmann/phpunit/issues/5891
-                        $arguments = $arguments[0];
-                    }
-
-                    foreach ($arguments as $v) {
-                        self::assertLessThan(1 << 32, $v);
-                        self::assertGreaterThanOrEqual(-(1 << 32), $v);
-                        self::assertIsInt($v);
-                    }
-
-                    return true;
-                }),
-                [533558444, -1716795572]
-            ));
-
-        \Closure::bind(static fn ($mutex) => $mutex->unlock(), null, PostgreSQLMutex::class)($this->mutex);
-    }
-}
diff --git a/tests/Mutex/RedisMutexTest.php b/tests/Mutex/RedisMutexTest.php
deleted file mode 100644
index a49136bf..00000000
--- a/tests/Mutex/RedisMutexTest.php
+++ /dev/null
@@ -1,316 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Malkusch\Lock\Tests\Mutex;
-
-use Malkusch\Lock\Exception\LockAcquireException;
-use Malkusch\Lock\Exception\LockReleaseException;
-use Malkusch\Lock\Exception\MutexException;
-use Malkusch\Lock\Mutex\DistributedMutex;
-use Malkusch\Lock\Mutex\RedisMutex;
-use PHPUnit\Framework\Attributes\DataProvider;
-use PHPUnit\Framework\Attributes\RequiresPhpExtension;
-use PHPUnit\Framework\Constraint\IsType;
-use PHPUnit\Framework\TestCase;
-use Predis\ClientInterface as PredisClientInterface;
-
-if (\PHP_MAJOR_VERSION >= 8) {
-    trait RedisCompatibilityTrait
-    {
-        /**
-         * @param list<mixed> $args
-         */
-        #[\Override] // @phpstan-ignore method.childParameterType
-        public function eval($script, $args = [], $numKeys = 0): mixed
-        {
-            return $this->_eval($script, $args, $numKeys);
-        }
-
-        /**
-         * @param mixed $options
-         */
-        #[\Override]
-        public function set($key, $value, $options = null): /* \Redis|string| */ bool
-        {
-            return $this->_set($key, $value, $options);
-        }
-    }
-} else {
-    trait RedisCompatibilityTrait
-    {
-        /**
-         * @return mixed
-         */
-        #[\Override]
-        public function eval($script, $args = [], $numKeys = 0)
-        {
-            return $this->_eval($script, $args, $numKeys);
-        }
-
-        /**
-         * @return \Redis|string|bool
-         */
-        #[\Override]
-        public function set($key, $value, $options = null)
-        {
-            return $this->_set($key, $value, $options);
-        }
-    }
-}
-
-interface PredisClientInterfaceWithSetAndEvalMethods extends PredisClientInterface
-{
-    /**
-     * @return mixed
-     */
-    public function eval();
-
-    /**
-     * @return mixed
-     */
-    public function set();
-}
-
-/**
- * These tests require the environment variable:
- *
- * REDIS_URIS - a comma separated list of redis:// URIs.
- *
- * @requires extension redis
- */
-#[RequiresPhpExtension('redis')]
-class RedisMutexTest extends TestCase
-{
-    /** @var \Redis[] */
-    private $connections = [];
-
-    /** @var RedisMutex */
-    private $mutex;
-
-    #[\Override]
-    protected function setUp(): void
-    {
-        parent::setUp();
-
-        if (!getenv('REDIS_URIS')) {
-            self::markTestSkipped('Redis server is required');
-        }
-
-        $redisUris = explode(',', getenv('REDIS_URIS'));
-
-        foreach ($redisUris as $redisUri) {
-            $uri = parse_url($redisUri);
-
-            // original Redis::set and Redis::eval calls will reopen the connection
-            $connection = new class extends \Redis {
-                use RedisCompatibilityTrait;
-
-                private bool $isClosed = false;
-
-                #[\Override]
-                public function close(): bool
-                {
-                    $res = parent::close();
-                    $this->isClosed = true;
-
-                    return $res;
-                }
-
-                /**
-                 * @param mixed $value
-                 * @param mixed $timeout
-                 *
-                 * @return \Redis|string|bool
-                 */
-                private function _set(string $key, $value, $timeout = 0)
-                {
-                    if ($this->isClosed) {
-                        throw new \RedisException('Connection is closed');
-                    }
-
-                    return parent::set($key, $value, $timeout);
-                }
-
-                /**
-                 * @param list<mixed> $args
-                 *
-                 * @return mixed
-                 */
-                private function _eval(string $script, array $args = [], int $numKeys = 0)
-                {
-                    if ($this->isClosed) {
-                        throw new \RedisException('Connection is closed');
-                    }
-
-                    return parent::eval($script, $args, $numKeys);
-                }
-            };
-
-            $connection->connect($uri['host'], $uri['port'] ?? 6379);
-            if (!empty($uri['pass'])) {
-                $connection->auth(
-                    empty($uri['user'])
-                    ? $uri['pass']
-                    : [$uri['user'], $uri['pass']]
-                );
-            }
-
-            $connection->flushAll(); // Clear any existing locks.
-
-            $this->connections[] = $connection;
-        }
-
-        $this->mutex = new DistributedMutex(array_map(static fn ($v) => new RedisMutex($v, 'test'), $this->connections)); // @phpstan-ignore assign.propertyType
-    }
-
-    #[\Override]
-    protected function assertPostConditions(): void
-    {
-        // workaround for burn testing
-        $this->connections = [];
-
-        parent::assertPostConditions();
-    }
-
-    private function closeMajorityConnections(): void
-    {
-        $numberToClose = (int) ceil(count($this->connections) / 2);
-
-        foreach ((array) array_rand($this->connections, $numberToClose) as $keyToClose) {
-            $this->connections[$keyToClose]->close();
-        }
-    }
-
-    private function closeMinorityConnections(): void
-    {
-        if (count($this->connections) === 1) {
-            self::markTestSkipped('Cannot test this with only a single Redis server');
-        }
-
-        $numberToClose = (int) ceil(count($this->connections) / 2) - 1;
-        if (0 >= $numberToClose) {
-            return;
-        }
-
-        foreach ((array) array_rand($this->connections, $numberToClose) as $keyToClose) {
-            $this->connections[$keyToClose]->close();
-        }
-    }
-
-    public function testAddFails(): void
-    {
-        $this->closeMajorityConnections();
-
-        $this->expectException(LockAcquireException::class);
-        $this->expectExceptionCode(MutexException::CODE_REDLOCK_NOT_ENOUGH_SERVERS);
-        $this->mutex->synchronized(static function () {
-            self::fail();
-        });
-    }
-
-    /**
-     * Tests evalScript() fails.
-     */
-    public function testEvalScriptFails(): void
-    {
-        $this->expectException(LockReleaseException::class);
-        $this->mutex->synchronized(function () {
-            $this->closeMajorityConnections();
-        });
-    }
-
-    public function testAcquireExpireTimeoutLimit(): void
-    {
-        $client = $this->createMock(PredisClientInterfaceWithSetAndEvalMethods::class);
-
-        $this->mutex = new RedisMutex($client, 'test');
-
-        $client->expects(self::once())
-            ->method('set')
-            ->with('php-malkusch-lock:test', new IsType(IsType::TYPE_STRING), 'PX', 31_557_600_000_000, 'NX')
-            ->willReturnSelf();
-
-        $client->expects(self::once())
-            ->method('eval')
-            ->with(self::anything(), 1, 'php-malkusch-lock:test', new IsType(IsType::TYPE_STRING))
-            ->willReturn(true);
-
-        $this->mutex->synchronized(static function () {});
-    }
-
-    /**
-     * @param \Redis::SERIALIZER_*  $serializer
-     * @param \Redis::COMPRESSION_* $compressor
-     *
-     * @dataProvider provideSerializersAndCompressorsCases
-     */
-    #[DataProvider('provideSerializersAndCompressorsCases')]
-    public function testSerializersAndCompressors(int $serializer, int $compressor): void
-    {
-        foreach ($this->connections as $connection) {
-            $connection->setOption(\Redis::OPT_SERIALIZER, $serializer);
-            $connection->setOption(\Redis::OPT_COMPRESSION, $compressor);
-        }
-
-        self::assertSame('test', $this->mutex->synchronized(static function () {
-            return 'test';
-        }));
-    }
-
-    public function testResistantToPartialClusterFailuresForAcquiringLock(): void
-    {
-        $this->closeMinorityConnections();
-
-        self::assertSame('test', $this->mutex->synchronized(static function () {
-            return 'test';
-        }));
-    }
-
-    public function testResistantToPartialClusterFailuresForReleasingLock(): void
-    {
-        self::assertNull($this->mutex->synchronized(function () { // @phpstan-ignore staticMethod.alreadyNarrowedType
-            $this->closeMinorityConnections();
-
-            return null;
-        }));
-    }
-
-    /**
-     * @return iterable<list<mixed>>
-     */
-    public static function provideSerializersAndCompressorsCases(): iterable
-    {
-        if (!class_exists(\Redis::class)) {
-            return;
-        }
-
-        yield [\Redis::SERIALIZER_NONE, \Redis::COMPRESSION_NONE];
-        yield [\Redis::SERIALIZER_PHP, \Redis::COMPRESSION_NONE];
-
-        if (defined('Redis::SERIALIZER_IGBINARY') && extension_loaded('igbinary')) {
-            yield [
-                constant('Redis::SERIALIZER_IGBINARY'),
-                \Redis::COMPRESSION_NONE,
-            ];
-        }
-
-        if (defined('Redis::COMPRESSION_LZF') && extension_loaded('lzf')) {
-            yield [
-                \Redis::SERIALIZER_NONE,
-                constant('Redis::COMPRESSION_LZF'),
-            ];
-            yield [
-                \Redis::SERIALIZER_PHP,
-                constant('Redis::COMPRESSION_LZF'),
-            ];
-
-            if (defined('Redis::SERIALIZER_IGBINARY') && extension_loaded('igbinary')) {
-                yield [
-                    constant('Redis::SERIALIZER_IGBINARY'),
-                    constant('Redis::COMPRESSION_LZF'),
-                ];
-            }
-        }
-    }
-}
diff --git a/tests/Util/DoubleCheckedLockingTest.php b/tests/Util/DoubleCheckedLockingTest.php
deleted file mode 100644
index 0fa22cd5..00000000
--- a/tests/Util/DoubleCheckedLockingTest.php
+++ /dev/null
@@ -1,178 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Malkusch\Lock\Tests\Util;
-
-use Malkusch\Lock\Mutex\Mutex;
-use Malkusch\Lock\Util\DoubleCheckedLocking;
-use PHPUnit\Framework\Attributes\DataProvider;
-use PHPUnit\Framework\MockObject\MockObject;
-use PHPUnit\Framework\TestCase;
-
-class DoubleCheckedLockingTest extends TestCase
-{
-    /** @var Mutex&MockObject */
-    private $mutex;
-
-    #[\Override]
-    protected function setUp(): void
-    {
-        parent::setUp();
-
-        $this->mutex = $this->createMock(Mutex::class);
-    }
-
-    public function testCheckFailsAcquiresNoLock(): void
-    {
-        $this->mutex->expects(self::never())
-            ->method('synchronized');
-
-        $checkedLocking = new DoubleCheckedLocking($this->mutex, static function () {
-            return false;
-        });
-
-        $result = $checkedLocking->then(static function () {
-            self::fail();
-        });
-
-        self::assertFalse($result); // @phpstan-ignore staticMethod.impossibleType
-    }
-
-    public function testLockedCheckAndExecution(): void
-    {
-        $lock = 0;
-        $check = 0;
-
-        $this->mutex->expects(self::once())
-            ->method('synchronized')
-            ->willReturnCallback(static function (\Closure $block) use (&$lock) {
-                ++$lock;
-                $result = $block();
-                ++$lock;
-
-                return $result;
-            });
-
-        $checkedLocking = new DoubleCheckedLocking($this->mutex, static function () use (&$lock, &$check) {
-            if ($check === 1) {
-                self::assertSame(1, $lock);
-            }
-            ++$check;
-
-            return true;
-        });
-
-        $result = $checkedLocking->then(static function () use (&$lock) {
-            self::assertSame(1, $lock);
-
-            return 'foo';
-        });
-
-        self::assertSame(2, $check);
-
-        self::assertSame('foo', $result);
-    }
-
-    /**
-     * @param \Closure(): bool $check
-     *
-     * @dataProvider provideCodeNotExecutedCases
-     */
-    #[DataProvider('provideCodeNotExecutedCases')]
-    public function testCodeNotExecuted(\Closure $check): void
-    {
-        $this->mutex->expects(self::any())
-            ->method('synchronized')
-            ->willReturnCallback(static fn (\Closure $block) => $block());
-
-        $checkedLocking = new DoubleCheckedLocking($this->mutex, $check);
-        $result = $checkedLocking->then(static function () {
-            self::fail();
-        });
-
-        self::assertFalse($result); // @phpstan-ignore staticMethod.impossibleType
-    }
-
-    /**
-     * @return iterable<list<mixed>>
-     */
-    public static function provideCodeNotExecutedCases(): iterable
-    {
-        yield 'failFirstCheck' => [static function () {
-            return false;
-        }];
-
-        $checkCounter = 0;
-        yield 'failSecondCheck' => [static function () use (&$checkCounter) {
-            return $checkCounter++ === 0;
-        }];
-    }
-
-    public function testCodeExecuted(): void
-    {
-        $this->mutex->expects(self::once())
-            ->method('synchronized')
-            ->willReturnCallback(static fn (\Closure $block) => $block());
-
-        $checkedLocking = new DoubleCheckedLocking($this->mutex, static function () {
-            return true;
-        });
-
-        $executedCount = 0;
-        $result = $checkedLocking->then(static function () use (&$executedCount) {
-            ++$executedCount;
-
-            return 'foo';
-        });
-
-        self::assertSame(1, $executedCount);
-        self::assertSame('foo', $result);
-    }
-
-    public function testFailCodeExecutedBeforeLock(): void
-    {
-        $this->mutex->expects(self::never())
-            ->method('synchronized');
-
-        $checkedLocking = new DoubleCheckedLocking($this->mutex, static function () {
-            return false;
-        });
-
-        $executedCount = 0;
-        $result = $checkedLocking->then(static function () {
-            self::fail();
-        }, static function () use (&$executedCount) {
-            ++$executedCount;
-
-            return 'foo';
-        });
-
-        self::assertSame(1, $executedCount);
-        self::assertSame('foo', $result);
-    }
-
-    public function testFailCodeExecutedAfterLock(): void
-    {
-        $this->mutex->expects(self::once())
-            ->method('synchronized')
-            ->willReturnCallback(static fn (\Closure $block) => $block());
-
-        $i = 0;
-        $checkedLocking = new DoubleCheckedLocking($this->mutex, static function () use (&$i) {
-            return $i++ === 0;
-        });
-
-        $executedCount = 0;
-        $result = $checkedLocking->then(static function () {
-            self::fail();
-        }, static function () use (&$executedCount) {
-            ++$executedCount;
-
-            return 'foo';
-        });
-
-        self::assertSame(1, $executedCount);
-        self::assertSame('foo', $result);
-    }
-}
diff --git a/tests/Util/LockUtilTest.php b/tests/Util/LockUtilTest.php
deleted file mode 100644
index 65b36637..00000000
--- a/tests/Util/LockUtilTest.php
+++ /dev/null
@@ -1,115 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Malkusch\Lock\Tests\Util;
-
-use Malkusch\Lock\Util\LockUtil;
-use PHPUnit\Framework\Attributes\DataProvider;
-use PHPUnit\Framework\TestCase;
-
-class LockUtilTest extends TestCase
-{
-    public function testGetInstance(): void
-    {
-        $instance = LockUtil::getInstance();
-
-        self::assertSame(LockUtil::class, get_class($instance));
-        self::assertSame($instance, LockUtil::getInstance());
-    }
-
-    public function testGetKeyPrefix(): void
-    {
-        self::assertSame('php-malkusch-lock', LockUtil::getInstance()->getKeyPrefix());
-    }
-
-    public function testMakeRandomToken(): void
-    {
-        $tokens = [];
-        for ($i = 0; $i < 1_000; ++$i) {
-            $token = LockUtil::getInstance()->makeRandomToken();
-            self::assertMatchesRegularExpression('~^(?!0)[0-9a-f]{64}$~', $token);
-
-            $tokens[] = $token;
-        }
-
-        self::assertSame($tokens, array_unique($tokens));
-    }
-
-    public function testMakeRandomTemporaryFilePath(): void
-    {
-        $pathPrefixRegex = preg_quote(sys_get_temp_dir() . \DIRECTORY_SEPARATOR . LockUtil::getInstance()->getKeyPrefix(), '~');
-
-        $paths = [];
-        for ($i = 0; $i < 1_000; ++$i) {
-            $path = LockUtil::getInstance()->makeRandomTemporaryFilePath();
-            self::assertMatchesRegularExpression('~^' . $pathPrefixRegex . '-\w{64}.txt$~', $path);
-
-            $paths[] = $path;
-        }
-
-        self::assertSame($paths, array_unique($paths));
-
-        self::assertMatchesRegularExpression('~^' . $pathPrefixRegex . '-foo-\w{64}.txt$~', LockUtil::getInstance()->makeRandomTemporaryFilePath('foo'));
-        self::assertMatchesRegularExpression('~^' . $pathPrefixRegex . '-fo-o-\w{64}.txt$~', LockUtil::getInstance()->makeRandomTemporaryFilePath('fo/o'));
-        self::assertMatchesRegularExpression('~^' . $pathPrefixRegex . '-fo-o-\w{64}.txt$~', LockUtil::getInstance()->makeRandomTemporaryFilePath('fo\o'));
-        self::assertMatchesRegularExpression('~^' . $pathPrefixRegex . '-fo-o-\w{64}.txt$~', LockUtil::getInstance()->makeRandomTemporaryFilePath('fo:o'));
-    }
-
-    /**
-     * @dataProvider provideCastFloatToIntCases
-     */
-    #[DataProvider('provideCastFloatToIntCases')]
-    public function testCastFloatToInt(int $expectedResult, float $value): void
-    {
-        self::assertSame($expectedResult, LockUtil::getInstance()->castFloatToInt($value));
-    }
-
-    /**
-     * @return iterable<list<mixed>>
-     */
-    public static function provideCastFloatToIntCases(): iterable
-    {
-        yield [10, 10];
-        yield [0, 0];
-        yield [0, -0.0];
-        yield [0, 0.49];
-        yield [0, -0.49];
-        yield [1, 0.5];
-        yield [-1, -0.5];
-        yield [1020304050607080, 1020304050607080];
-        yield [-1020304050607080, -1020304050607080];
-        yield [\PHP_INT_MAX, \PHP_INT_MAX];
-        yield [\PHP_INT_MIN, \PHP_INT_MIN];
-        yield [\PHP_INT_MAX, (float) \PHP_INT_MAX + 2000];
-        yield [\PHP_INT_MIN, (float) \PHP_INT_MIN - 2000];
-        yield [\PHP_INT_MAX, \INF];
-        yield [\PHP_INT_MIN, -\INF];
-    }
-
-    /**
-     * @dataProvider provideFormatTimeoutCases
-     */
-    #[DataProvider('provideFormatTimeoutCases')]
-    public function testFormatTimeout(string $expectedResult, float $value): void
-    {
-        self::assertSame($expectedResult, LockUtil::getInstance()->formatTimeout($value));
-    }
-
-    /**
-     * @return iterable<list<mixed>>
-     */
-    public static function provideFormatTimeoutCases(): iterable
-    {
-        yield ['0.5', 0.5];
-        yield ['10.123456', 10.123_456_4];
-        yield ['10.123457', 10.123_456_5];
-        yield ['-10.123456', -10.123_456_4];
-        yield ['-10.123457', -10.123_456_5];
-        yield ['0.0', 0];
-        yield ['0.0', -0];
-        yield ['-123456789.5', -123_456_789.5];
-        yield ['INF', \INF];
-        yield ['NAN', \NAN];
-    }
-}
diff --git a/tests/Util/LoopTest.php b/tests/Util/LoopTest.php
deleted file mode 100644
index df08c90f..00000000
--- a/tests/Util/LoopTest.php
+++ /dev/null
@@ -1,146 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Malkusch\Lock\Tests\Util;
-
-use Malkusch\Lock\Exception\LockAcquireTimeoutException;
-use Malkusch\Lock\Util\LockUtil;
-use Malkusch\Lock\Util\Loop;
-use phpmock\environment\SleepEnvironmentBuilder;
-use phpmock\MockEnabledException;
-use phpmock\phpunit\PHPMock;
-use PHPUnit\Framework\Attributes\DataProvider;
-use PHPUnit\Framework\Attributes\DoesNotPerformAssertions;
-use PHPUnit\Framework\TestCase;
-
-class LoopTest extends TestCase
-{
-    use PHPMock;
-
-    #[\Override]
-    protected function setUp(): void
-    {
-        parent::setUp();
-
-        $sleepBuilder = new SleepEnvironmentBuilder();
-        $sleepBuilder->addNamespace(__NAMESPACE__);
-        $sleepBuilder->addNamespace('Malkusch\Lock\Util');
-        $sleep = $sleepBuilder->build();
-        try {
-            $sleep->enable();
-            $this->registerForTearDown($sleep);
-        } catch (MockEnabledException $e) {
-            // workaround for burn testing
-            \assert($e->getMessage() === 'microtime is already enabled. Call disable() on the existing mock.');
-        }
-    }
-
-    /**
-     * @dataProvider provideInvalidAcquireTimeoutCases
-     */
-    #[DataProvider('provideInvalidAcquireTimeoutCases')]
-    public function testInvalidAcquireTimeout(float $acquireTimeout): void
-    {
-        $loop = new Loop();
-
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionMessage('The lock acquire timeout must be greater than or equal to 0.0 (' . LockUtil::getInstance()->formatTimeout($acquireTimeout) . ' was given)');
-        $loop->execute(static function () {
-            self::fail();
-        }, $acquireTimeout);
-    }
-
-    /**
-     * @return iterable<list<mixed>>
-     */
-    public static function provideInvalidAcquireTimeoutCases(): iterable
-    {
-        yield [-2];
-        yield [-0.1];
-        yield [-\INF];
-        yield [\NAN];
-    }
-
-    /**
-     * @doesNotPerformAssertions
-     */
-    #[DoesNotPerformAssertions]
-    public function testExecutionWithinAcquireTimeout(): void
-    {
-        $loop = new Loop();
-        $loop->execute(static function () use ($loop) {
-            usleep(499 * 1000);
-            $loop->end();
-        }, 0.5);
-    }
-
-    public function testExecutionWithinAcquireTimeoutWithoutCallingEnd(): void
-    {
-        $loop = new Loop();
-
-        $this->expectException(LockAcquireTimeoutException::class);
-        $this->expectExceptionMessage('Lock acquire timeout of 0.5 seconds has been exceeded');
-        $loop->execute(static function () {
-            usleep(10 * 1000);
-        }, 0.5);
-    }
-
-    /**
-     * @doesNotPerformAssertions
-     */
-    #[DoesNotPerformAssertions]
-    public function testExceedAcquireTimeoutIsAcceptableIfEndWasCalled(): void
-    {
-        $loop = new Loop();
-        $loop->execute(static function () use ($loop) {
-            usleep(501 * 1000);
-            $loop->end();
-        }, 0.5);
-    }
-
-    public function testExceedAcquireTimeoutWithoutCallingEnd(): void
-    {
-        $loop = new Loop();
-
-        $this->expectException(LockAcquireTimeoutException::class);
-        $this->expectExceptionMessage('Lock acquire timeout of 0.5 seconds has been exceeded');
-        $loop->execute(static function () {
-            usleep(501 * 1000);
-        }, 0.5);
-    }
-
-    public function testExceptionStopsIteration(): void
-    {
-        $loop = new Loop();
-
-        $this->expectException(\DomainException::class);
-        $loop->execute(static function () {
-            throw new \DomainException();
-        }, 1);
-    }
-
-    public function testEndCodeExecutedOnce(): void
-    {
-        $i = 0;
-        $loop = new Loop();
-        $loop->execute(static function () use ($loop, &$i) {
-            ++$i;
-            $loop->end();
-        }, 1);
-        self::assertSame(1, $i);
-    }
-
-    public function testEndCodeExecutedTwice(): void
-    {
-        $i = 0;
-        $loop = new Loop();
-        $loop->execute(static function () use ($loop, &$i) {
-            ++$i;
-            if ($i > 1) {
-                $loop->end();
-            }
-        }, 1);
-        self::assertSame(2, $i);
-    }
-}
diff --git a/tests/Util/PcntlTimeoutTest.php b/tests/Util/PcntlTimeoutTest.php
deleted file mode 100644
index e4980d49..00000000
--- a/tests/Util/PcntlTimeoutTest.php
+++ /dev/null
@@ -1,108 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Malkusch\Lock\Tests\Util;
-
-use Malkusch\Lock\Exception\DeadlineException;
-use Malkusch\Lock\Exception\LockAcquireException;
-use Malkusch\Lock\Util\PcntlTimeout;
-use PHPUnit\Framework\Attributes\RequiresPhpExtension;
-use PHPUnit\Framework\TestCase;
-
-/**
- * @requires extension pcntl
- */
-#[RequiresPhpExtension('pcntl')]
-class PcntlTimeoutTest extends TestCase
-{
-    /**
-     * A long running system call should be interrupted.
-     */
-    public function testShouldTimeout(): void
-    {
-        $timeout = new PcntlTimeout(1);
-
-        $this->expectException(DeadlineException::class);
-        $timeout->timeBoxed(static function () {
-            sleep(2);
-        });
-    }
-
-    /**
-     * A short running system call should complete its execution.
-     */
-    public function testShouldNotTimeout(): void
-    {
-        $timeout = new PcntlTimeout(1);
-
-        $result = $timeout->timeBoxed(static function () {
-            return 42;
-        });
-
-        self::assertSame(42, $result);
-    }
-
-    /**
-     * Thrown exceptions from the subject code should be rethrown.
-     */
-    public function testShouldThrowException(): void
-    {
-        $timeout = new PcntlTimeout(1);
-
-        $this->expectException(\DomainException::class);
-        $timeout->timeBoxed(static function () {
-            throw new \DomainException();
-        });
-    }
-
-    /**
-     * When a previous scheduled alarm exists, it should fail.
-     */
-    public function testShouldFailOnExistingAlarm(): void
-    {
-        $origSignalHandler = pcntl_signal_get_handler(\SIGALRM);
-        try {
-            pcntl_alarm(1);
-            $timeout = new PcntlTimeout(1);
-
-            $this->expectException(LockAcquireException::class);
-            $this->expectExceptionMessage('Existing process alarm is not supported');
-            $timeout->timeBoxed(static function () {
-                sleep(1);
-            });
-        } finally {
-            pcntl_alarm(0);
-            self::assertSame($origSignalHandler, pcntl_signal_get_handler(\SIGALRM));
-        }
-    }
-
-    /**
-     * After not timing out, there should be no alarm scheduled.
-     */
-    public function testShouldResetAlarmWhenNotTimeout(): void
-    {
-        $timeout = new PcntlTimeout(3);
-
-        $timeout->timeBoxed(static function () {});
-
-        self::assertSame(0, pcntl_alarm(0));
-    }
-
-    /**
-     * After not timing out and throwing an exception, there should be no alarm scheduled.
-     */
-    public function testShouldResetAlarmWhenNotTimeoutAndException(): void
-    {
-        $timeout = new PcntlTimeout(3);
-
-        $this->expectException(\DomainException::class);
-        try {
-            $timeout->timeBoxed(static function () {
-                throw new \DomainException();
-            });
-        } finally {
-            self::assertSame(0, pcntl_alarm(0));
-        }
-    }
-}
