Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
rela589n committed Nov 18, 2023
0 parents commit 46662b3
Show file tree
Hide file tree
Showing 25 changed files with 923 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .gitattributes
@@ -0,0 +1,9 @@
/.github export-ignore
/Tests export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/ecs.php export-ignore
/phpstan.dist.neon export-ignore
/phpunit.xml.dist export-ignore
/psalm.xml export-ignore
/rector.php export-ignore
137 changes: 137 additions & 0 deletions .github/workflows/ci.yaml
@@ -0,0 +1,137 @@
name: 'Continuous Integration'

on:
pull_request: ~
push:
branches: [ main ]

jobs:
composer-validate:
name: Composer Validate
runs-on: ubuntu-latest
steps:
- name: 'Checkout Code'
uses: actions/checkout@v4

- name: 'Validate composer.json'
run: composer validate --strict --ansi

code-style:
needs: composer-validate
name: ${{ matrix.actions.name }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
actions:
- name: 'Coding Standard'
run: vendor/bin/ecs check --no-progress-bar --ansi
- name: 'Rector'
run: vendor/bin/rector process --dry-run --no-progress-bar --ansi
steps:
- name: 'Checkout Code'
uses: actions/checkout@v4

- name: 'Setup PHP'
uses: shivammathur/setup-php@v2
with:
php-version: 8.2
coverage: none
tools: composer:v2

- name: 'Install Dependencies'
uses: "ramsey/composer-install@v2"
with:
composer-options: "--optimize-autoloader"

- run: ${{ matrix.actions.run }}
static-analysis:
needs: composer-validate
runs-on: ubuntu-latest
name: ${{ matrix.actions.name }} (${{ matrix.php }}, ${{ matrix.dependencies }})
strategy:
fail-fast: false
matrix:
php: [ '8.0', '8.2' ]
dependencies: [ highest, lowest ]
actions:
- name: 'Psalm'
run: vendor/bin/psalm --stats --no-progress --output-format=github --threads=$(nproc)

- name: 'PHPStan'
run: vendor/bin/phpstan analyze --no-progress --error-format=github --ansi --configuration=phpstan.dist.neon ./
steps:
- name: 'Checkout Code'
uses: actions/checkout@v4

- name: 'Setup PHP'
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: none
tools: composer:v2

- name: 'Install Dependencies'
uses: "ramsey/composer-install@v2"
with:
composer-options: "--optimize-autoloader"
dependency-versions: ${{ matrix.dependencies }}

- run: ${{ matrix.actions.run }}
test:
needs: composer-validate
name: PHPUnit
runs-on: ubuntu-latest
strategy:
matrix:
php: [ '8.0', '8.2' ]
dependencies: [ highest, lowest ]
steps:
- name: 'Checkout Code'
uses: actions/checkout@v4

- name: 'Setup PHP'
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: none
tools: composer:v2

- name: 'Install Dependencies'
uses: "ramsey/composer-install@v2"
with:
composer-options: "--optimize-autoloader"
dependency-versions: ${{ matrix.dependencies }}

- name: 'Run PHPUnit'
run: vendor/bin/phpunit --testdox --colors=always --configuration=phpunit.xml.dist

code-coverage:
needs: [ test, static-analysis ]
name: PHPUnit Coverage
runs-on: ubuntu-latest
steps:
- name: 'Checkout Code'
uses: actions/checkout@v4

- name: 'Setup PHP'
uses: shivammathur/setup-php@v2
with:
php-version: 8.2
coverage: xdebug
tools: composer:v2

- name: 'Install Dependencies'
uses: "ramsey/composer-install@v2"
with:
composer-options: "--optimize-autoloader"

- name: 'Run PHPUnit with Coverage'
run: vendor/bin/phpunit --configuration=phpunit.xml.dist --coverage-clover=coverage.xml

- name: 'Upload coverage reports to Codecov'
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml
fail_ci_if_error: true
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
/vendor/
/.phpunit.cache/
composer.lock
60 changes: 60 additions & 0 deletions Accessor/TestContainerAccessor.php
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace PhPhD\CacheTestBundle\Accessor;

use Closure;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\DependencyInjection\ContainerInterface;

/** @internal */
final class TestContainerAccessor
{
/** @param class-string<KernelTestCase> $testClassName */
public function runWithContainer(string $testClassName, callable $callback): void
{
$container = $this->getContainer($testClassName);

try {
$callback($container);
} finally {
$this->ensureKernelShutdown($testClassName);
}
}

/** @param class-string<KernelTestCase> $testClassName */
private function getContainer(string $testClassName): ContainerInterface
{
/**
* @var Closure $getContainer
*
* @psalm-suppress NonStaticSelfCall
* @psalm-suppress TooFewArguments
*
* @phpstan-ignore-next-line
*/
$getContainer = (static fn (): ContainerInterface => self::getContainer())->bindTo(null, $testClassName);

/** @var ContainerInterface $container */
$container = $getContainer();

return $container;
}

/** @param class-string<KernelTestCase> $testClassName */
private function ensureKernelShutdown(string $testClassName): void
{
/**
* @var Closure $shutDown
*
* @psalm-suppress NonStaticSelfCall
* @psalm-suppress TooFewArguments
*
* @phpstan-ignore-next-line
*/
$shutDown = (static fn () => self::ensureKernelShutdown())->bindTo(null, $testClassName);

$shutDown();
}
}
21 changes: 21 additions & 0 deletions Attribute/ClearPool.php
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace PhPhD\CacheTestBundle\Attribute;

use Attribute;

#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class ClearPool
{
public function __construct(
private string $name,
) {
}

public function getName(): string
{
return $this->name;
}
}
55 changes: 55 additions & 0 deletions Clearer/CacheItemPoolsClearer.php
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace PhPhD\CacheTestBundle\Clearer;

use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use UnexpectedValueException;

use function array_map;
use function sprintf;

/** @internal */
final class CacheItemPoolsClearer
{
public function __construct(
/** @var list<string> */
private array $poolsServiceNames,
) {
}

public function __invoke(ContainerInterface $container): void
{
$pools = $this->getPools($container);

$this->clearPools($pools);
}

/** @return list<CacheItemPoolInterface> */
private function getPools(ContainerInterface $container): array
{
return array_map(static function (string $poolServiceName) use ($container) {
/** @var CacheItemPoolInterface $pool */
$pool = $container->get($poolServiceName);

if (!$pool instanceof CacheItemPoolInterface) {
throw new UnexpectedValueException(
sprintf('Expected the service "%s" to be instance of %s', $poolServiceName, CacheItemPoolInterface::class),
);
}

return $pool;
}, $this->poolsServiceNames);
}

/** @param list<CacheItemPoolInterface> $pools */
private function clearPools(array $pools): void
{
array_map(
static fn (CacheItemPoolInterface $pool): bool => $pool->clear(),
$pools,
);
}
}
79 changes: 79 additions & 0 deletions Clearer/TestClearer.php
@@ -0,0 +1,79 @@
<?php

/** @noinspection PhpDocMissingThrowsInspection */

declare(strict_types=1);

namespace PhPhD\CacheTestBundle\Clearer;

use PhPhD\CacheTestBundle\Accessor\TestContainerAccessor;
use PhPhD\CacheTestBundle\Attribute\ClearPool;
use ReflectionAttribute;
use ReflectionClass;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

use function array_map;
use function is_a;
use function strtok;

/** @internal */
final class TestClearer
{
private TestContainerAccessor $containerAccessor;

public function __construct()
{
$this->containerAccessor = new TestContainerAccessor();
}

public function __invoke(string $testName): void
{
/** @var class-string $testClassName */
$testClassName = strtok($testName, '::');

if (!$this->supports($testClassName)) {
return;
}

$poolServiceNames = $this->getPoolServiceNames($testClassName);

if ([] === $poolServiceNames) {
return;
}

$cacheItemPoolsClearer = new CacheItemPoolsClearer($poolServiceNames);

$this->containerAccessor->runWithContainer($testClassName, $cacheItemPoolsClearer);
}

/** @psalm-assert-if-true class-string<KernelTestCase> $testClassName */
private function supports(string $testClassName): bool
{
return is_a($testClassName, KernelTestCase::class, true);
}

/**
* @param class-string<KernelTestCase> $testClassName
*
* @return list<string>
*/
private function getPoolServiceNames(string $testClassName): array
{
$clearPools = $this->getClearPoolAttributes($testClassName);

return array_map(static fn (ClearPool $pool): string => $pool->getName(), $clearPools);
}

/**
* @param class-string<KernelTestCase> $testClassName
*
* @return list<ClearPool>
*/
private function getClearPoolAttributes(string $testClassName): array
{
$reflectionClass = new ReflectionClass($testClassName);
$attributes = $reflectionClass->getAttributes(ClearPool::class);

return array_map(static fn (ReflectionAttribute $attribute): object => $attribute->newInstance(), $attributes);
}
}

0 comments on commit 46662b3

Please sign in to comment.