Skip to content

Commit

Permalink
Merge pull request #1 from ebln/testing
Browse files Browse the repository at this point in the history
Release 1.1.0 → Full coverage & usage of PSR-16 bulk methods
  • Loading branch information
ebln committed Oct 15, 2021
2 parents 0732d2d + 5ace347 commit 1b1392e
Show file tree
Hide file tree
Showing 13 changed files with 469 additions and 20 deletions.
1 change: 1 addition & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
'phpdoc_summary' => false,
'cast_spaces' => ['space' => 'none'],
'binary_operator_spaces' => ['default' => null, 'operators' => ['=' => 'align_single_space_minimal', '=>' => 'align_single_space_minimal']],
'php_unit_strict' => false,
]
)
->setFinder($finder);
Expand Down
17 changes: 13 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,23 @@ Intended to follow [«Keep a Changelog»](https://keepachangelog.com/en/)

### TODO

- Refactor `NowFactory` to PSR-20 [BREAKING]
- Refactor `NowFactory` to PSR-20 [BREAKING]
- Revisit `\Brnc\CachePsr16Adapter\CacheItemPool::getTimeToLive`
- Also `NowFactory` also for `CacheItem`
- Add tests to ensure PSR-6 commits make use of PSR-16's bulk setters
- Increase test coverage (using PSR-16 returning false & exceptions)

----

## [1.1.0] - 2021-10-15

### Changed

- Change `getItems` to use `getMultiple`
- Using `NowFactory` also for `CacheItem`

### Added

- Increase test coverage (using PSR-16 exceptions)
- Add tests to ensure PSR-6 commits make use of PSR-16's bulk setters

## [1.0.0] - 2021-10-10

### Added
Expand Down
14 changes: 10 additions & 4 deletions makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ DOCKER_COMPOSE=docker-compose -f $(DOCKER_COMPOSE_YML)
MAKE=make -s
.DEFAULT_GOAL := help

.PHONY: help build rm enter test test-all
.PHONY: help build rm down stop enter test quality style-fix coverage

help: ## Show this help.
@grep -E '^[a-zA-Z_-]+:.*?##\s*.*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?##\\s*"}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
Expand All @@ -26,7 +26,13 @@ enter:##Log into the main container
$(DOCKER_COMPOSE) run ${DEFAULT_CONTAINER} /bin/bash

test:##Run unit tests
$(DOCKER_COMPOSE) run ${DEFAULT_CONTAINER} composer phpunit
$(DOCKER_COMPOSE) run ${DEFAULT_CONTAINER} composer test-unit

test-all:##Run the complete code quality suite
$(DOCKER_COMPOSE) run ${DEFAULT_CONTAINER} composer test
quality:##Run the complete code quality suite
$(DOCKER_COMPOSE) run ${DEFAULT_CONTAINER} composer quality

style-fix:##Apply code style
$(DOCKER_COMPOSE) run ${DEFAULT_CONTAINER} composer style-fix

coverage:##Generate coverage report
$(DOCKER_COMPOSE) run ${DEFAULT_CONTAINER} composer coverage
30 changes: 22 additions & 8 deletions src/CacheItemPool.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public function getItem($key): CacheItem
}

/**
* NOTE: Returned key order is not preserved from the argument!
*
* @psalm-param array<mixed> $keys
*
* @return array<string, CacheItem>
Expand All @@ -50,22 +52,29 @@ public function getItems(array $keys = []): array
$items = [];

try {
$downstreamKeys = [];
foreach ($keys as $key) {
$this->validateKey($key);

if (isset($this->defered[$key])) {
$item = new CacheItem($key, $this->defered[$key]->getValue(), true, $this->defered[$key]->getExpiresAt());
// "Revive" defered item. // NOTE In constrast to hasItem() this apparently always returns the defered item, even expired ones
$items[$key] = $this->unserializeItem($key, $this->defered[$key]);
} else {
/** @var ?SerializedItem $rawItem */
$rawItem = $this->cache->get($key);
$downstreamKeys[] = $key;
}
}

if ($downstreamKeys) {
/**
* @var string $key
* @var ?SerializedItem $rawItem
*/
foreach ($this->cache->getMultiple($downstreamKeys) as $key => $rawItem) {
if ($rawItem instanceof SerializedItem) {
$item = new CacheItem($key, $rawItem->getValue(), true, $rawItem->getExpiresAt());
$items[$key] = $this->unserializeItem($key, $rawItem);
} else {
$item = new CacheItem($key, null, false, null);
$items[$key] = new CacheItem($key, null, false, null, $this->nowFactory);
}
}

$items[$key] = $item;
}
} catch (InvalidArgumentException $k) {
throw $k;
Expand Down Expand Up @@ -184,6 +193,11 @@ public function commit(): bool
return true;
}

private function unserializeItem(string $key, SerializedItem $serialized): CacheItem
{
return new CacheItem($key, $serialized->getValue(), true, $serialized->getExpiresAt(), $this->nowFactory);
}

/**
* @psalm-assert CacheItem $item
*/
Expand Down
9 changes: 6 additions & 3 deletions src/Model/CacheItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Brnc\CachePsr16Adapter\Model;

use Brnc\CachePsr16Adapter\Exception\InvalidArgumentException;
use Brnc\CachePsr16Adapter\NowFactory;
use DateInterval;
use DateTimeImmutable;
use Psr\Cache\CacheItemInterface;
Expand All @@ -16,16 +17,18 @@ class CacheItem implements CacheItemInterface
private string $key;
private bool $hit;
private ?DateTimeImmutable $expiry;
private NowFactory $clock;

/**
* @psalm-param mixed $value
*/
public function __construct(string $key, $value, bool $hit, ?DateTimeImmutable $expiry)
public function __construct(string $key, $value, bool $hit, ?DateTimeImmutable $expiry, NowFactory $clock)
{
$this->key = $key;
$this->value = $value;
$this->hit = $hit;
$this->expiry = $expiry;
$this->clock = $clock;
}

public function getKey(): string
Expand Down Expand Up @@ -90,9 +93,9 @@ public function expiresAfter($time)
if (null === $time) {
$this->setExpiry(null);
} elseif ($time instanceof DateInterval) {
$this->setExpiry((new DateTimeImmutable())->add($time));
$this->setExpiry($this->clock->now()->add($time));
} elseif (is_int($time)) {
$this->setExpiry((new DateTimeImmutable())->add(new DateInterval('PT' . $time . 'S')));
$this->setExpiry($this->clock->now()->add(new DateInterval('PT' . $time . 'S')));
} else {
throw new InvalidArgumentException();
}
Expand Down
65 changes: 65 additions & 0 deletions tests/BrokenPsr16Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace Brnc\tests\CachePsr16Adapter;

use Brnc\CachePsr16Adapter\CacheItemPool;
use Brnc\CachePsr16Adapter\Model\CacheItem;
use Brnc\CachePsr16Adapter\NowFactory;
use Brnc\tests\CachePsr16Adapter\helper\BrokenPsr16Mock;
use PHPUnit\Framework\TestCase;

/**
* @internal
* @covers \Brnc\CachePsr16Adapter\CacheItemPool
*/
final class BrokenPsr16Test extends TestCase
{
public function testHasItem(): void
{
$cache = $this->createCache();
static::assertFalse($cache->hasItem('foo'));
}

public function testClear(): void
{
$cache = $this->createCache();
static::assertFalse($cache->clear());
}

public function testCommit(): void
{
$cache = $this->createCache();
$cache->saveDeferred($this->createItem());
static::assertFalse($cache->commit());
}

public function testSave(): void
{
$cache = $this->createCache();
static::assertFalse($cache->save($this->createItem()));
}

public function testDeleteItem(): void
{
$cache = $this->createCache();
static::assertFalse($cache->deleteItem('foo'));
}

public function testDeleteItems(): void
{
$cache = $this->createCache();
static::assertFalse($cache->deleteItems(['foo']));
}

private function createItem(): CacheItem
{
return new CacheItem('foo_' . bin2hex(random_bytes(2)), bin2hex(random_bytes(4)), true, null, new NowFactory());
}

private function createCache(): CacheItemPool
{
return new CacheItemPool(new BrokenPsr16Mock());
}
}
51 changes: 51 additions & 0 deletions tests/CacheItemPoolTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Brnc\tests\CachePsr16Adapter;

use Brnc\CachePsr16Adapter\CacheItemPool;
use Brnc\CachePsr16Adapter\Exception\InvalidArgumentException;
use Brnc\CachePsr16Adapter\Model\CacheItem;
use Brnc\CachePsr16Adapter\NowFactory;
use Brnc\tests\CachePsr16Adapter\helper\BrokenPsr16Mock;
use Brnc\tests\CachePsr16Adapter\helper\Psr16Array;
use PHPUnit\Framework\TestCase;
use Psr\Cache\CacheItemInterface;
use Psr\SimpleCache\CacheInterface;

/**
* @internal
* @covers \Brnc\CachePsr16Adapter\CacheItemPool
*/
final class CacheItemPoolTest extends TestCase
{
public function testOnlyOwnItems(): void
{
$cache = new CacheItemPool(new Psr16Array());
$item = $this->createStub(CacheItemInterface::class);
$this->expectException(InvalidArgumentException::class);
// ensure that exception is implementing the right interface for the PSR-6
// phpstan is quite strict: expects class-string<Throwable> yet PSR-6 doesn't implement throwable yet
$implementing = class_implements(InvalidArgumentException::class);
$implementing = $implementing ?: [];
static::assertArrayHasKey(\Psr\Cache\InvalidArgumentException::class, $implementing);
$cache->save($item);
}

public function testGetItemsWithFatalError(): void
{
$pool = new CacheItemPool(new BrokenPsr16Mock());
static::assertSame([], $pool->getItems(['foo', 'bar']));
}

public function testFailingCommit(): void
{
$psr16 = $this->createStub(CacheInterface::class);
$psr16->method('setMultiple')->willReturn(false);
$pool = new CacheItemPool($psr16);
$item = new CacheItem('foo', null, false, null, new NowFactory());
$pool->saveDeferred($item);
static::assertFalse($pool->commit());
}
}
99 changes: 99 additions & 0 deletions tests/CacheItemTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

declare(strict_types=1);

namespace Brnc\tests\CachePsr16Adapter;

use Brnc\CachePsr16Adapter\Exception\InvalidArgumentException;
use Brnc\CachePsr16Adapter\Model\CacheItem;
use Brnc\CachePsr16Adapter\NowFactory;
use DateInterval;
use DateTime;
use DateTimeImmutable;
use PHPUnit\Framework\TestCase;

/**
* @internal
* @covers \Brnc\CachePsr16Adapter\Model\CacheItem
*/
final class CacheItemTest extends TestCase
{
public function testExpiresAtWithDateTime(): void
{
$item = new CacheItem('foo', null, false, null, $this->getClock());
$dateTime = new DateTime();
$item->expiresAt($dateTime);

static::assertEquals($dateTime, $item->getExpiry());
static::assertNotSame($dateTime, $item->getExpiry());
}

public function testExpiresAtWithDateTimeImmutable(): void
{
$item = new CacheItem('foo', null, false, null, $this->getClock());
$dateTime = new DateTimeImmutable();
$item->expiresAt($dateTime);
static::assertSame($dateTime, $item->getExpiry());
}

public function testExpiresAtWithBogus(): void
{
$this->expectException(InvalidArgumentException::class);
$implementing = class_implements(InvalidArgumentException::class);
$implementing = $implementing ?: [];
static::assertArrayHasKey(\Psr\Cache\InvalidArgumentException::class, $implementing);
$item = new CacheItem('foo', null, false, null, $this->getClock());
$item->expiresAt((object)['foo']);
}

public function testExpiresAtWithNull(): void
{
$item = new CacheItem('foo', null, false, null, $this->getClock());
$item->expiresAt(null);
static::assertNull($item->getExpiry());
}

public function testExpiresAfterWithNull(): void
{
$item = new CacheItem('foo', null, false, null, $this->getClock());
$item->expiresAfter(null);
static::assertNull($item->getExpiry());
}

public function testExpiresAfterWithBogus(): void
{
$this->expectException(InvalidArgumentException::class);
$item = new CacheItem('foo', null, false, null, $this->getClock());
$item->expiresAfter('foobar');
}

public function testExpiresAfterWithInt(): void
{
$item = new CacheItem('foo', null, false, null, $this->getFixedClock());
$item->expiresAfter(60);
static::assertEquals($this->getFixedClock()->now()->add(new DateInterval('PT60S')), $item->getExpiry());
}

public function testExpiresAfterWithDateInterval(): void
{
$item = new CacheItem('foo', null, false, null, $this->getFixedClock());
$interval = new DateInterval('PT60S');
$item->expiresAfter($interval);
static::assertEquals($this->getFixedClock()->now()->add(new DateInterval('PT60S')), $item->getExpiry());
}

private function getClock(): NowFactory
{
return new NowFactory();
}

private function getFixedClock(): NowFactory
{
$stub = $this->createStub(NowFactory::class);
$stub->method('now')
->willReturn(new DateTimeImmutable('2037-12-31'))
;

return $stub;
}
}
1 change: 1 addition & 0 deletions tests/Psr6IntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* @covers \Brnc\CachePsr16Adapter\CacheItemPool
* @covers \Brnc\CachePsr16Adapter\Model\CacheItem
* @covers \Brnc\CachePsr16Adapter\Model\SerializedItem
* @covers \Brnc\CachePsr16Adapter\NowFactory
* @covers \Brnc\tests\CachePsr16Adapter\helper\Psr16ArraySingleton
* @codeCoverageIgnore
*/
Expand Down
Loading

0 comments on commit 1b1392e

Please sign in to comment.