diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3289dc1..1777d139 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -133,8 +133,8 @@ jobs: - run: mv build/coverage-db/coverage-db.cov build/coverage-db.cov - run: mv build/coverage-api/coverage-api.cov build/coverage-api.cov - run: mv build/coverage-cli/coverage-cli.cov build/coverage-cli.cov - - run: wget https://phar.phpunit.de/phpcov-9.0.0.phar - - run: php phpcov-9.0.0.phar merge build --clover build/clover.xml + - run: wget https://phar.phpunit.de/phpcov-10.0.0.phar + - run: php phpcov-10.0.0.phar merge build --clover build/clover.xml - name: Publish coverage uses: codecov/codecov-action@v1 with: diff --git a/.gitignore b/.gitignore index 04c8ed56..a7f9b895 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ docs/mercure.html docker-compose.override.yml .phpunit.result.cache docs/swagger/swagger-inlined.json +phpcov* diff --git a/composer.json b/composer.json index 20da872a..1ffb6ab2 100644 --- a/composer.json +++ b/composer.json @@ -71,7 +71,7 @@ "phpunit/phpunit": "^10.4", "roave/security-advisories": "dev-master", "shlinkio/php-coding-standard": "~2.3.0", - "shlinkio/shlink-test-utils": "^3.8.1", + "shlinkio/shlink-test-utils": "^3.9", "symfony/var-dumper": "^6.4", "veewee/composer-run-parallel": "^1.3" }, diff --git a/config/test/bootstrap_api_tests.php b/config/test/bootstrap_api_tests.php index b82e5bc6..8f757c05 100644 --- a/config/test/bootstrap_api_tests.php +++ b/config/test/bootstrap_api_tests.php @@ -7,12 +7,6 @@ namespace Shlinkio\Shlink\TestUtils; use Doctrine\ORM\EntityManager; use Psr\Container\ContainerInterface; -use function register_shutdown_function; -use function sprintf; - -use const ShlinkioTest\Shlink\API_TESTS_HOST; -use const ShlinkioTest\Shlink\API_TESTS_PORT; - /** @var ContainerInterface $container */ $container = require __DIR__ . '/../container.php'; $testHelper = $container->get(Helper\TestHelper::class); @@ -20,14 +14,6 @@ $config = $container->get('config'); $em = $container->get(EntityManager::class); $httpClient = $container->get('shlink_test_api_client'); -// Dump code coverage when process shuts down -register_shutdown_function(function () use ($httpClient): void { - $httpClient->request( - 'GET', - sprintf('http://%s:%s/api-tests/stop-coverage', API_TESTS_HOST, API_TESTS_PORT), - ); -}); - $testHelper->createTestDb( createDbCommand: ['bin/cli', 'db:create'], migrateDbCommand: ['bin/cli', 'db:migrate'], diff --git a/config/test/test_config.global.php b/config/test/test_config.global.php index 0fed88ee..578e5c24 100644 --- a/config/test/test_config.global.php +++ b/config/test/test_config.global.php @@ -6,15 +6,9 @@ namespace Shlinkio\Shlink; use GuzzleHttp\Client; use Laminas\ConfigAggregator\ConfigAggregator; -use Laminas\Diactoros\Response\EmptyResponse; use Laminas\ServiceManager\Factory\InvokableFactory; -use League\Event\EventDispatcher; use Monolog\Level; use PHPUnit\Runner\Version; -use Psr\Container\ContainerInterface; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Server\RequestHandlerInterface; use SebastianBergmann\CodeCoverage\CodeCoverage; use SebastianBergmann\CodeCoverage\Driver\Selector; use SebastianBergmann\CodeCoverage\Filter; @@ -22,13 +16,12 @@ use SebastianBergmann\CodeCoverage\Report\Html\Facade as Html; use SebastianBergmann\CodeCoverage\Report\PHP; use SebastianBergmann\CodeCoverage\Report\Xml\Facade as Xml; use Shlinkio\Shlink\Common\Logger\LoggerType; +use Shlinkio\Shlink\TestUtils\ApiTest\CoverageMiddleware; +use Shlinkio\Shlink\TestUtils\CliTest\CliCoverageDelegator; use Symfony\Component\Console\Application; -use Symfony\Component\Console\Event\ConsoleCommandEvent; -use Symfony\Component\Console\Event\ConsoleTerminateEvent; -use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use function file_exists; -use function Laminas\Stratigility\middleware; +use function register_shutdown_function; use function Shlinkio\Shlink\Config\env; use function Shlinkio\Shlink\Core\ArrayUtils\contains; use function sprintf; @@ -36,7 +29,7 @@ use function sprintf; use const ShlinkioTest\Shlink\API_TESTS_HOST; use const ShlinkioTest\Shlink\API_TESTS_PORT; -$isApiTest = env('TEST_ENV') === 'api'; +$isApiTest = env('TEST_ENV') === 'api' && env('RR_MODE') === 'http'; $isCliTest = env('TEST_ENV') === 'cli'; $isE2eTest = $isApiTest || $isCliTest; $coverageType = env('GENERATE_COVERAGE'); @@ -75,6 +68,13 @@ $exportCoverage = static function (string $type = 'api') use (&$coverage, $cover } }; +// Dump code coverage when process shuts down, only if running in HTTP mode +register_shutdown_function(function () use ($exportCoverage, $isApiTest): void { + if ($isApiTest) { + $exportCoverage(); + } +}); + $buildDbConnection = static function (): array { $driver = env('DB_DRIVER', 'sqlite'); $isCi = env('CI', false); @@ -135,34 +135,9 @@ return [ ], ], - 'routes' => !$isApiTest ? [] : [ - [ - 'name' => 'dump_coverage', - 'path' => '/api-tests/stop-coverage', - 'middleware' => middleware(static function () use ($exportCoverage) { - // TODO I have tried moving this block to a listener so that it's invoked automatically, - // but then the coverage is generated empty ¯\_(ツ)_/¯ - $exportCoverage(); - return new EmptyResponse(); - }), - 'allowed_methods' => ['GET'], - ], - ], - 'middleware_pipeline' => !$isApiTest ? [] : [ 'capture_code_coverage' => [ - 'middleware' => middleware(static function ( - ServerRequestInterface $req, - RequestHandlerInterface $handler, - ) use (&$coverage): ResponseInterface { - $coverage?->start($req->getHeaderLine('x-coverage-id')); - - try { - return $handler->handle($req); - } finally { - $coverage?->stop(); - } - }), + 'middleware' => new CoverageMiddleware($coverage), 'priority' => 9999, ], ], @@ -185,58 +160,7 @@ return [ ], 'delegators' => $isCliTest ? [ Application::class => [ - static function ( - ContainerInterface $c, - string $serviceName, - callable $callback, - ) use ( - &$coverage, - $exportCoverage, - ) { - /** @var Application $app */ - $app = $callback(); - $wrappedEventDispatcher = new EventDispatcher(); - - // When the command starts, start collecting coverage - $wrappedEventDispatcher->subscribeTo( - ConsoleCommandEvent::class, - static function () use (&$coverage): void { - $id = env('COVERAGE_ID'); - if ($id === null) { - return; - } - - $coverage?->start($id); - }, - ); - // When the command ends, stop collecting coverage - $wrappedEventDispatcher->subscribeTo( - ConsoleTerminateEvent::class, - static function () use (&$coverage, $exportCoverage): void { - $id = env('COVERAGE_ID'); - if ($id === null) { - return; - } - - $coverage?->stop(); - $exportCoverage('cli'); - }, - ); - - $app->setDispatcher(new class ($wrappedEventDispatcher) implements EventDispatcherInterface { - public function __construct(private EventDispatcher $wrappedDispatcher) - { - } - - public function dispatch(object $event, ?string $eventName = null): object - { - $this->wrappedDispatcher->dispatch($event); - return $event; - } - }); - - return $app; - }, + new CliCoverageDelegator($exportCoverage(...), $coverage), ], ] : [], ],