From 9a38aa32176273f0a14c440c51e447be4733bd68 Mon Sep 17 00:00:00 2001 From: marvin255 Date: Sun, 13 Aug 2023 12:18:41 +0200 Subject: [PATCH] Libs update (#13) * Update composer libs * Fix CachedItem * Fix CompositeCache * Fix InvalidArgumentException * Fix InMemoryCache * Re-format code in CompositeCacheTest * Re-format code in InMemoryCacheTest * Add Timer object * Add TimerFactory object * Use timer object in InMemoryCache * Add comments to InMemoryCacheTest * Use Timer mock in InMemoryCacheTest to avoid sleep() * Fix psalm settings * Remove infection exception related to InMemoryCache::__construct * Remove infection exception related to InMemoryCache * Add dependabot * Add opcache to docker container * Update phpunit to v10 * Update infection to v0.27 * Add php 8.2 to actions --- .github/dependabot.yml | 18 +++ .github/workflows/php.yml | 2 +- composer.json | 6 +- docker/php/Dockerfile | 7 +- infection.json | 17 +- phpunit.xml.dist | 29 ++-- psalm.xml | 2 + src/CachedItem.php | 12 +- src/CompositeCache.php | 18 +-- src/InMemoryCache.php | 38 ++--- src/InvalidArgumentException.php | 5 + src/Timer.php | 16 ++ src/TimerFactory.php | 31 ++++ src/TimerInternal.php | 21 +++ tests/BaseCase.php | 2 + tests/CompositeCacheTest.php | 214 ++++++++++++++---------- tests/InMemoryCacheTest.php | 268 ++++++++++++++++++++++++++----- tests/TimerFactoryTest.php | 21 +++ tests/TimerInternalTest.php | 23 +++ 19 files changed, 541 insertions(+), 209 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 src/Timer.php create mode 100644 src/TimerFactory.php create mode 100644 src/TimerInternal.php create mode 100644 tests/TimerFactoryTest.php create mode 100644 tests/TimerInternalTest.php diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..3f6210b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ + +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "composer" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + allow: + - dependency-name: "psr/simple-cache" + - dependency-name: "phpunit/phpunit" + - dependency-name: "friendsofphp/php-cs-fixer" + - dependency-name: "vimeo/psalm" + - dependency-name: "infection/infection" \ No newline at end of file diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index ea8c2eb..a034141 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: true matrix: - php-versions: ['8.1'] + php-versions: ['8.1', '8.2'] steps: - uses: actions/checkout@v2 - name: Install PHP diff --git a/composer.json b/composer.json index 6f205ec..832c7f1 100644 --- a/composer.json +++ b/composer.json @@ -9,11 +9,10 @@ "psr/simple-cache": "^3.0" }, "require-dev": { - "phpunit/phpunit": "^9.0", + "phpunit/phpunit": "^10.0", "friendsofphp/php-cs-fixer": "^3.0", - "sebastian/phpcpd": "^6.0", "vimeo/psalm": "^5.0", - "infection/infection": "^0.26.13" + "infection/infection": "^0.27.0" }, "autoload": { "psr-4": { @@ -37,7 +36,6 @@ ], "linter": [ "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -v --dry-run --stop-on-violation --allow-risky=yes", - "vendor/bin/phpcpd ./ --exclude vendor --exclude docker", "vendor/bin/psalm --show-info=true --php-version=$(php -r \"echo phpversion();\")" ], "infection": [ diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index 36a6177..b7a08a2 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -12,10 +12,11 @@ RUN set -xe && apk update && apk add --no-cache \ git \ autoconf \ g++ \ - make + make \ + linux-headers -RUN docker-php-ext-install zip soap \ +RUN docker-php-ext-install zip opcache \ && docker-php-source extract \ && pecl install xdebug \ && docker-php-ext-enable xdebug \ @@ -24,7 +25,7 @@ RUN docker-php-ext-install zip soap \ && echo 'xdebug.mode=coverage' >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini -RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --version=2.4.2 \ +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --version=2.5.8 \ && mkdir -p /.composer && chmod -Rf 777 /.composer diff --git a/infection.json b/infection.json index 652411a..e2ab411 100644 --- a/infection.json +++ b/infection.json @@ -9,21 +9,6 @@ "text": "infection.log" }, "mutators": { - "@default": true, - "DecrementInteger": { - "ignore": [ - "Marvin255\\InMemoryCache\\InMemoryCache::__construct" - ] - }, - "IncrementInteger": { - "ignore": [ - "Marvin255\\InMemoryCache\\InMemoryCache::__construct" - ] - }, - "GreaterThanOrEqualTo": { - "ignore": [ - "Marvin255\\InMemoryCache\\InMemoryCache::isItemValid" - ] - } + "@default": true } } \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 008d986..f60c083 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,19 +1,14 @@ - - - - ./src - - - - - - ./tests - - + + + + + ./tests + + + + + ./src + + diff --git a/psalm.xml b/psalm.xml index b7c1308..dc2babf 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,6 +1,8 @@ payload = $payload; - $this->validTill = $validTill; + public function __construct( + private readonly mixed $payload, + private readonly int $validTill + ) { } public function getValidTill(): int diff --git a/src/CompositeCache.php b/src/CompositeCache.php index f5de7d1..746b52e 100644 --- a/src/CompositeCache.php +++ b/src/CompositeCache.php @@ -13,17 +13,15 @@ * The second is something that require socket connection e.g. redis based cache. * Composite cache tries to find data in the light cache and if there is no data * in light cache makes a request for heavy cache. + * + * @psalm-api */ final class CompositeCache implements CacheInterface { - private readonly CacheInterface $lightCache; - - private readonly CacheInterface $heavyCache; - - public function __construct(CacheInterface $lightCache, CacheInterface $heavyCache) - { - $this->lightCache = $lightCache; - $this->heavyCache = $heavyCache; + public function __construct( + private readonly CacheInterface $lightCache, + private readonly CacheInterface $heavyCache + ) { } /** @@ -44,7 +42,7 @@ public function get(string $key, mixed $default = null): mixed /** * {@inheritDoc} */ - public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool + public function set(string $key, mixed $value, int|\DateInterval $ttl = null): bool { return $this->heavyCache->set($key, $value, $ttl) && $this->lightCache->set($key, $value, $ttl); @@ -84,7 +82,7 @@ public function getMultiple(iterable $keys, mixed $default = null): iterable /** * {@inheritDoc} */ - public function setMultiple(iterable $values, null|int|\DateInterval $ttl = null): bool + public function setMultiple(iterable $values, int|\DateInterval $ttl = null): bool { return $this->heavyCache->setMultiple($values, $ttl) && $this->lightCache->setMultiple($values, $ttl); diff --git a/src/InMemoryCache.php b/src/InMemoryCache.php index cc74a74..7493999 100644 --- a/src/InMemoryCache.php +++ b/src/InMemoryCache.php @@ -8,29 +8,33 @@ /** * Simple PSR-16 implementation that uses internal array. + * + * @psalm-api */ final class InMemoryCache implements CacheInterface { - private readonly int $stackSize; + public const DEFAULT_STACK_SIZE = 1000; + public const DEFAULT_TTL = 60; - private readonly int $defaultTTL; + private Timer $timer; /** * @var array */ private array $stack = []; - public function __construct(int $stackSize = 1000, int $defaultTTL = 60) - { - if ($stackSize < 1) { + public function __construct( + private readonly int $stackSize = self::DEFAULT_STACK_SIZE, + private readonly int $defaultTTL = self::DEFAULT_TTL, + Timer $timer = null + ) { + if ($this->stackSize < 1) { throw new InvalidArgumentException('Stack size must be greater than 0'); } - if ($defaultTTL < 1) { + if ($this->defaultTTL < 1) { throw new InvalidArgumentException('Default TTL must be greater than 0'); } - - $this->stackSize = $stackSize; - $this->defaultTTL = $defaultTTL; + $this->timer = $timer ?: TimerFactory::create(); } /** @@ -48,7 +52,7 @@ public function get(string $key, mixed $default = null): mixed /** * {@inheritDoc} */ - public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool + public function set(string $key, mixed $value, int|\DateInterval $ttl = null): bool { if (\count($this->stack) >= $this->stackSize) { $this->clearStack(); @@ -96,7 +100,7 @@ public function getMultiple(iterable $keys, mixed $default = null): iterable /** * {@inheritDoc} */ - public function setMultiple(iterable $values, null|int|\DateInterval $ttl = null): bool + public function setMultiple(iterable $values, int|\DateInterval $ttl = null): bool { foreach ($values as $key => $value) { $this->set((string) $key, $value, $ttl); @@ -130,7 +134,7 @@ public function has(string $key): bool */ private function createValidTill(null|int|\DateInterval $ttl): int { - $validTill = $this->getCurrentTimestamp(); + $validTill = $this->timer->getCurrentTimestamp(); if ($ttl === null) { $validTill += $this->defaultTTL; @@ -176,7 +180,7 @@ private function clearStack(): void */ private function isItemValid(CachedItem $item): bool { - return $item->getValidTill() >= $this->getCurrentTimestamp(); + return $item->getValidTill() >= $this->timer->getCurrentTimestamp(); } /** @@ -186,12 +190,4 @@ private function calculateItemSortScore(CachedItem $item): int { return $item->getSelectCount(); } - - /** - * Returns current timestamp. - */ - private function getCurrentTimestamp(): int - { - return time(); - } } diff --git a/src/InvalidArgumentException.php b/src/InvalidArgumentException.php index 95b875a..f6f84fd 100644 --- a/src/InvalidArgumentException.php +++ b/src/InvalidArgumentException.php @@ -4,6 +4,11 @@ namespace Marvin255\InMemoryCache; +/** + * Specific cache related exception. + * + * @internal + */ final class InvalidArgumentException extends \Exception implements \Psr\SimpleCache\InvalidArgumentException { } diff --git a/src/Timer.php b/src/Timer.php new file mode 100644 index 0000000..fa04556 --- /dev/null +++ b/src/Timer.php @@ -0,0 +1,16 @@ +getMockBuilder(CacheInterface::class)->getMock(); + $light = $this->createCacheInterfaceMock(); $light->method('has') ->with($this->equalTo($key)) - ->willReturn(true) - ; + ->willReturn(true); $light->method('get') - ->with($this->equalTo($key), $this->equalTo($default)) - ->willReturn($value) - ; + ->with( + $this->equalTo($key), + $this->equalTo($default) + ) + ->willReturn($value); - $heavy = $this->getMockBuilder(CacheInterface::class)->getMock(); + $heavy = $this->createCacheInterfaceMock(); $heavy->expects($this->never())->method('get'); $cache = new CompositeCache($light, $heavy); @@ -43,23 +45,26 @@ public function testGetHeavy(): void $value = 'value'; $default = 'default'; - $light = $this->getMockBuilder(CacheInterface::class)->getMock(); + $light = $this->createCacheInterfaceMock(); $light->expects($this->once()) ->method('has') ->with($this->equalTo($key)) - ->willReturn(false) - ; + ->willReturn(false); $light->expects($this->once()) ->method('set') - ->with($this->equalTo($key), $this->equalTo($value)) - ; + ->with( + $this->equalTo($key), + $this->equalTo($value) + ); - $heavy = $this->getMockBuilder(CacheInterface::class)->getMock(); + $heavy = $this->createCacheInterfaceMock(); $heavy->expects($this->once()) ->method('get') - ->with($this->equalTo($key), $this->equalTo($default)) - ->willReturn($value) - ; + ->with( + $this->equalTo($key), + $this->equalTo($default) + ) + ->willReturn($value); $cache = new CompositeCache($light, $heavy); $gotValue = $cache->get($key, $default); @@ -73,62 +78,79 @@ public function testSet(): void $value = 'value'; $ttl = 60; - $light = $this->getMockBuilder(CacheInterface::class)->getMock(); + $light = $this->createCacheInterfaceMock(); $light->expects($this->once()) ->method('set') - ->with($this->equalTo($key), $this->equalTo($value), $this->equalTo($ttl)) - ->willReturn(true) - ; + ->with( + $this->equalTo($key), + $this->equalTo($value), + $this->equalTo($ttl) + ) + ->willReturn(true); - $heavy = $this->getMockBuilder(CacheInterface::class)->getMock(); + $heavy = $this->createCacheInterfaceMock(); $heavy->expects($this->once()) ->method('set') - ->with($this->equalTo($key), $this->equalTo($value), $this->equalTo($ttl)) - ->willReturn(true) - ; + ->with( + $this->equalTo($key), + $this->equalTo($value), + $this->equalTo($ttl) + ) + ->willReturn(true); $cache = new CompositeCache($light, $heavy); - $cache->set($key, $value, $ttl); + $res = $cache->set($key, $value, $ttl); + + $this->assertTrue( + $res, + 'set method must return true in case of success' + ); } public function testDelete(): void { $key = 'key'; - $light = $this->getMockBuilder(CacheInterface::class)->getMock(); + $light = $this->createCacheInterfaceMock(); $light->expects($this->once()) ->method('delete') ->with($this->equalTo($key)) - ->willReturn(true) - ; + ->willReturn(true); - $heavy = $this->getMockBuilder(CacheInterface::class)->getMock(); + $heavy = $this->createCacheInterfaceMock(); $heavy->expects($this->once()) ->method('delete') ->with($this->equalTo($key)) - ->willReturn(true) - ; + ->willReturn(true); $cache = new CompositeCache($light, $heavy); - $cache->delete($key); + $res = $cache->delete($key); + + $this->assertTrue( + $res, + 'delete method must return true in case of success' + ); } public function testClear(): void { - $light = $this->getMockBuilder(CacheInterface::class)->getMock(); + $light = $this->createCacheInterfaceMock(); $light->expects($this->once()) ->method('clear') - ->willReturn(true) - ; + ->willReturn(true); - $heavy = $this->getMockBuilder(CacheInterface::class)->getMock(); + $heavy = $this->createCacheInterfaceMock(); $heavy->expects($this->once()) ->method('clear') - ->willReturn(true) - ; + ->willReturn(true); $cache = new CompositeCache($light, $heavy); - $cache->clear(); + $res = $cache->clear(); + + $this->assertTrue( + $res, + 'clear method must return true in case of success' + ); } public function testGetMultiple(): void @@ -139,35 +161,40 @@ public function testGetMultiple(): void $value1 = 'value 1'; $default = 'default'; - $light = $this->getMockBuilder(CacheInterface::class)->getMock(); + $light = $this->createCacheInterfaceMock(); $light->method('has') ->willReturnMap( [ [$key, false], [$key1, true], ] - ) - ; + ); $light->method('get') ->with($this->equalTo($key1), $this->equalTo($default)) - ->willReturn($value1) - ; + ->willReturn($value1); $light->expects($this->once()) ->method('set') - ->with($this->equalTo($key), $this->equalTo($value)) - ; + ->with( + $this->equalTo($key), + $this->equalTo($value) + ); - $heavy = $this->getMockBuilder(CacheInterface::class)->getMock(); + $heavy = $this->createCacheInterfaceMock(); $heavy->method('get') - ->with($this->equalTo($key), $this->equalTo($default)) - ->willReturn($value) - ; + ->with( + $this->equalTo($key), + $this->equalTo($default) + ) + ->willReturn($value); $cache = new CompositeCache($light, $heavy); $multiple = $cache->getMultiple([$key, $key1], $default); $this->assertSame( - [$key => $value, $key1 => $value1], + [ + $key => $value, + $key1 => $value1, + ], $multiple ); } @@ -177,62 +204,72 @@ public function testSetMultiple(): void $values = ['key' => 'value']; $ttl = 60; - $light = $this->getMockBuilder(CacheInterface::class)->getMock(); + $light = $this->createCacheInterfaceMock(); $light->expects($this->once()) ->method('setMultiple') - ->with($this->equalTo($values), $this->equalTo($ttl)) - ->willReturn(true) - ; + ->with( + $this->equalTo($values), + $this->equalTo($ttl) + ) + ->willReturn(true); - $heavy = $this->getMockBuilder(CacheInterface::class)->getMock(); + $heavy = $this->createCacheInterfaceMock(); $heavy->expects($this->once()) ->method('setMultiple') - ->with($this->equalTo($values), $this->equalTo($ttl)) - ->willReturn(true) - ; + ->with( + $this->equalTo($values), + $this->equalTo($ttl) + ) + ->willReturn(true); $cache = new CompositeCache($light, $heavy); - $cache->setMultiple($values, $ttl); + $res = $cache->setMultiple($values, $ttl); + + $this->assertTrue( + $res, + 'setMultiple method must return true in case of success' + ); } public function testDeleteMultiple(): void { $keys = ['key']; - $light = $this->getMockBuilder(CacheInterface::class)->getMock(); + $light = $this->createCacheInterfaceMock(); $light->expects($this->once()) ->method('deleteMultiple') ->with($this->equalTo($keys)) - ->willReturn(true) - ; + ->willReturn(true); - $heavy = $this->getMockBuilder(CacheInterface::class)->getMock(); + $heavy = $this->createCacheInterfaceMock(); $heavy->expects($this->once()) ->method('deleteMultiple') ->with($this->equalTo($keys)) - ->willReturn(true) - ; + ->willReturn(true); $cache = new CompositeCache($light, $heavy); - $cache->deleteMultiple($keys); + $res = $cache->deleteMultiple($keys); + + $this->assertTrue( + $res, + 'deleteMultiple method must return true in case of success' + ); } public function testHasLight(): void { $key = 'key'; - $light = $this->getMockBuilder(CacheInterface::class)->getMock(); + $light = $this->createCacheInterfaceMock(); $light->method('has') ->with($this->equalTo($key)) - ->willReturn(true) - ; + ->willReturn(true); - $heavy = $this->getMockBuilder(CacheInterface::class)->getMock(); + $heavy = $this->createCacheInterfaceMock(); $heavy->expects($this->never()) ->method('has') ->with($this->equalTo($key)) - ->willReturn(false) - ; + ->willReturn(false); $cache = new CompositeCache($light, $heavy); $has = $cache->has($key); @@ -244,17 +281,15 @@ public function testHasHeavy(): void { $key = 'key'; - $light = $this->getMockBuilder(CacheInterface::class)->getMock(); + $light = $this->createCacheInterfaceMock(); $light->method('has') ->with($this->equalTo($key)) - ->willReturn(false) - ; + ->willReturn(false); - $heavy = $this->getMockBuilder(CacheInterface::class)->getMock(); + $heavy = $this->createCacheInterfaceMock(); $heavy->method('has') ->with($this->equalTo($key)) - ->willReturn(true) - ; + ->willReturn(true); $cache = new CompositeCache($light, $heavy); $has = $cache->has($key); @@ -266,21 +301,30 @@ public function testDoesNotHave(): void { $key = 'key'; - $light = $this->getMockBuilder(CacheInterface::class)->getMock(); + $light = $this->createCacheInterfaceMock(); $light->method('has') ->with($this->equalTo($key)) - ->willReturn(false) - ; + ->willReturn(false); - $heavy = $this->getMockBuilder(CacheInterface::class)->getMock(); + $heavy = $this->createCacheInterfaceMock(); $heavy->method('has') ->with($this->equalTo($key)) - ->willReturn(false) - ; + ->willReturn(false); $cache = new CompositeCache($light, $heavy); $has = $cache->has($key); $this->assertFalse($has); } + + /** + * @return MockObject&CacheInterface + */ + private function createCacheInterfaceMock(): CacheInterface + { + /** @var MockObject&CacheInterface */ + $mock = $this->getMockBuilder(CacheInterface::class)->getMock(); + + return $mock; + } } diff --git a/tests/InMemoryCacheTest.php b/tests/InMemoryCacheTest.php index 5b05ef3..45b4f40 100644 --- a/tests/InMemoryCacheTest.php +++ b/tests/InMemoryCacheTest.php @@ -6,19 +6,20 @@ use Marvin255\InMemoryCache\InMemoryCache; use Marvin255\InMemoryCache\InvalidArgumentException; +use Marvin255\InMemoryCache\Timer; /** * @internal */ class InMemoryCacheTest extends BaseCase { - public function testConstructStackSize(): void + public function testConstructStackSizeException(): void { $this->expectException(InvalidArgumentException::class); new InMemoryCache(0, 1); } - public function testConstructDefaultTTL(): void + public function testConstructDefaultTTLException(): void { $this->expectException(InvalidArgumentException::class); new InMemoryCache(1, 0); @@ -32,8 +33,9 @@ public function testGet(): void $cache = new InMemoryCache(1, 1); $cache->set($key, $value, $ttl); + $res = $cache->get($key); - $this->assertSame($value, $cache->get($key)); + $this->assertSame($value, $res); } public function testGetObject(): void @@ -45,8 +47,9 @@ public function testGetObject(): void $cache = new InMemoryCache(); $cache->set($key, $value, $ttl); + $res = $cache->get($key); - $this->assertSame($value, $cache->get($key)); + $this->assertSame($value, $res); } public function testGetDefault(): void @@ -55,8 +58,9 @@ public function testGetDefault(): void $default = 'default'; $cache = new InMemoryCache(); + $res = $cache->get($key, $default); - $this->assertSame($default, $cache->get($key, $default)); + $this->assertSame($default, $res); } public function testGetAfterTtl(): void @@ -64,12 +68,41 @@ public function testGetAfterTtl(): void $key = 'test'; $value = 'test value'; $ttl = 1; + $timestamp = 123; - $cache = new InMemoryCache(); + $timerMock = $this->createTimerMock( + [ + $timestamp, + $timestamp + 2, + ] + ); + + $cache = new InMemoryCache(timer: $timerMock); $cache->set($key, $value, $ttl); - sleep(2); + $res = $cache->get($key, null); - $this->assertNull($cache->get($key, null)); + $this->assertNull($res); + } + + public function testGetRigntInTheEndOfTtl(): void + { + $key = 'test'; + $value = 'test value'; + $ttl = 1; + $timestamp = 123; + + $timerMock = $this->createTimerMock( + [ + $timestamp, + $timestamp + 1, + ] + ); + + $cache = new InMemoryCache(timer: $timerMock); + $cache->set($key, $value, $ttl); + $res = $cache->get($key, null); + + $this->assertSame($res, $value); } public function testGetDateInterval(): void @@ -90,12 +123,20 @@ public function testGetAfterDateInterval(): void $key = 'test'; $value = 'test value'; $ttl = new \DateInterval('PT1S'); + $timestamp = 123; - $cache = new InMemoryCache(); + $timerMock = $this->createTimerMock( + [ + $timestamp, + $timestamp + 2, + ] + ); + + $cache = new InMemoryCache(timer: $timerMock); $cache->set($key, $value, $ttl); - sleep(2); + $res = $cache->get($key, null); - $this->assertNull($cache->get($key, null)); + $this->assertNull($res); } public function testSetReturnsTrue(): void @@ -119,11 +160,28 @@ public function testSetMultiple(): void $ttl = 60; $cache = new InMemoryCache(); - $res = $cache->setMultiple([$key => $value, $key1 => $value1], $ttl); + $res = $cache->setMultiple( + [ + $key => $value, + $key1 => $value1, + ], + $ttl + ); - $this->assertSame($value, $cache->get($key)); - $this->assertSame($value1, $cache->get($key1)); - $this->assertTrue($res); + $this->assertTrue( + $res, + 'value returned by method must be true' + ); + $this->assertSame( + $value, + $cache->get($key), + 'first cache item must be saved' + ); + $this->assertSame( + $value1, + $cache->get($key1), + 'second cache item must be saved' + ); } public function testSetMultipleIntegerKey(): void @@ -135,11 +193,28 @@ public function testSetMultipleIntegerKey(): void $ttl = 60; $cache = new InMemoryCache(); - $res = $cache->setMultiple([$key => $value, $key1 => $value1], $ttl); + $res = $cache->setMultiple( + [ + $key => $value, + $key1 => $value1, + ], + $ttl + ); - $this->assertSame($value, $cache->get($key)); - $this->assertSame($value1, $cache->get((string) $key1)); - $this->assertTrue($res); + $this->assertTrue( + $res, + 'value returned by method must be true' + ); + $this->assertSame( + $value, + $cache->get($key), + 'first cache item must be saved' + ); + $this->assertSame( + $value1, + $cache->get((string) $key1), + 'second cache item must be saved' + ); } public function testGetMultiple(): void @@ -155,6 +230,14 @@ public function testGetMultiple(): void $cache = new InMemoryCache(); $cache->set($key, $value, $ttl); $cache->set($key1, $value1, $ttl); + $res = $cache->getMultiple( + [ + $key2, + $key1, + $key, + ], + $default + ); $this->assertSame( [ @@ -162,7 +245,7 @@ public function testGetMultiple(): void $key1 => $value1, $key => $value, ], - $cache->getMultiple([$key2, $key1, $key], $default) + $res ); } @@ -174,8 +257,9 @@ public function testHas(): void $cache = new InMemoryCache(); $cache->set($key, $value, $ttl); + $res = $cache->has($key); - $this->assertTrue($cache->has($key)); + $this->assertTrue($res); } public function testDoesNotHave(): void @@ -186,8 +270,9 @@ public function testDoesNotHave(): void $cache = new InMemoryCache(); $cache->set($key, $value, $ttl); + $res = $cache->has('unexisted'); - $this->assertFalse($cache->has('unexisted')); + $this->assertFalse($res); } public function testHasExpired(): void @@ -195,12 +280,20 @@ public function testHasExpired(): void $key = 'test'; $value = 'test value'; $ttl = 1; + $timestamp = 123; - $cache = new InMemoryCache(); + $timerMock = $this->createTimerMock( + [ + $timestamp, + $timestamp + 2, + ] + ); + + $cache = new InMemoryCache(timer: $timerMock); $cache->set($key, $value, $ttl); - sleep(2); + $res = $cache->has($key); - $this->assertFalse($cache->has($key)); + $this->assertFalse($res); } public function testDelete(): void @@ -213,8 +306,14 @@ public function testDelete(): void $cache->set($key, $value, $ttl); $res = $cache->delete($key); - $this->assertFalse($cache->has($key)); - $this->assertTrue($res); + $this->assertTrue( + $res, + 'delete method must return true' + ); + $this->assertFalse( + $cache->has($key), + 'has method must return false after deleting' + ); } public function testDeleteMultiple(): void @@ -231,12 +330,30 @@ public function testDeleteMultiple(): void $cache->set($key, $value, $ttl); $cache->set($key1, $value1, $ttl); $cache->set($key2, $value2, $ttl); - $res = $cache->deleteMultiple([$key2, $key]); + $res = $cache->deleteMultiple( + [ + $key2, + $key, + ] + ); - $this->assertFalse($cache->has($key)); - $this->assertSame($value1, $cache->get($key1)); - $this->assertFalse($cache->has($key2)); - $this->assertTrue($res); + $this->assertTrue( + $res, + 'deleteMultiple method must return true' + ); + $this->assertFalse( + $cache->has($key), + 'has method for the firts item must return false after deleting' + ); + $this->assertSame( + $value1, + $cache->get($key1), + 'deleteMultiple mustn\'t remove keys thet were not set' + ); + $this->assertFalse( + $cache->has($key2), + 'has method for the second item must return false after deleting' + ); } public function testClear(): void @@ -257,7 +374,7 @@ public function testClear(): void $this->assertTrue($res); } - public function testStackSize(): void + public function testStackSizeOverflow(): void { $key = 'test'; $value = 'test value'; @@ -272,9 +389,18 @@ public function testStackSize(): void $cache->get($key1); $cache->set($key2, $value2); - $this->assertFalse($cache->has($key), 'Item that cleared from cache'); - $this->assertTrue($cache->has($key1), 'Item that was selected once'); - $this->assertTrue($cache->has($key2), 'New item'); + $this->assertFalse( + $cache->has($key), + 'item that has the smallest counter must be removed' + ); + $this->assertTrue( + $cache->has($key1), + 'item with the bigger counter must be saved' + ); + $this->assertTrue( + $cache->has($key2), + 'new item must be added' + ); } public function testStackSizeTTL(): void @@ -285,16 +411,37 @@ public function testStackSizeTTL(): void $value1 = 'test value 1'; $key2 = 'test_2'; $value2 = 'test value 2'; + $timestamp = 123; + $nextTimestamp = 125; - $cache = new InMemoryCache(2, 60); + $timerMock = $this->createTimerMock( + [ + $timestamp, + $timestamp, + $nextTimestamp, + $nextTimestamp, + $nextTimestamp, + $nextTimestamp, + ] + ); + + $cache = new InMemoryCache(2, 60, $timerMock); $cache->set($key, $value, 1); $cache->set($key1, $value1); - sleep(2); $cache->set($key2, $value2); - $this->assertFalse($cache->has($key), 'Item that was expired'); - $this->assertTrue($cache->has($key1), 'Common item'); - $this->assertTrue($cache->has($key2), 'New item'); + $this->assertFalse( + $cache->has($key), + 'expired item must be removed' + ); + $this->assertTrue( + $cache->has($key1), + 'valid item must be saved' + ); + $this->assertTrue( + $cache->has($key2), + 'new item must be added' + ); } public function testStackSizeUseFirstOneToReplace(): void @@ -313,8 +460,41 @@ public function testStackSizeUseFirstOneToReplace(): void $cache->get($key1); $cache->set($key2, $value2); - $this->assertFalse($cache->has($key), 'Item that cleared from cache'); - $this->assertTrue($cache->has($key1), 'Item that was selected once'); - $this->assertTrue($cache->has($key2), 'New item'); + $this->assertFalse( + $cache->has($key), + 'in case of equal counters, first item must be removed' + ); + $this->assertTrue( + $cache->has($key1), + 'in case of equal counters, next items must be saved' + ); + $this->assertTrue( + $cache->has($key2), + 'new item must be added' + ); + } + + /** + * @param int[] $freezeAt + */ + private function createTimerMock(array $freezeAt = []): Timer + { + return new class($freezeAt) implements Timer { + private int $counter = 0; + + public function __construct( + /** @var int[] */ + private readonly array $freezeAt + ) { + } + + public function getCurrentTimestamp(): int + { + $frozen = $this->freezeAt[$this->counter] ?? null; + ++$this->counter; + + return $frozen !== null ? $frozen : time(); + } + }; } } diff --git a/tests/TimerFactoryTest.php b/tests/TimerFactoryTest.php new file mode 100644 index 0000000..9e04a57 --- /dev/null +++ b/tests/TimerFactoryTest.php @@ -0,0 +1,21 @@ +assertInstanceOf(Timer::class, $res); + } +} diff --git a/tests/TimerInternalTest.php b/tests/TimerInternalTest.php new file mode 100644 index 0000000..aef8478 --- /dev/null +++ b/tests/TimerInternalTest.php @@ -0,0 +1,23 @@ +getCurrentTimestamp(); + + $this->assertSame($time, $res); + } +}