From a3e92a4ed42579d5fce0e2cc95a0ccd9cb8e5092 Mon Sep 17 00:00:00 2001 From: Eugene Leonovich Date: Wed, 10 Jun 2026 17:26:40 +0200 Subject: [PATCH 01/14] Migrate to PHP 8.2, PHPUnit 10 and PHP Attributes --- composer.json | 21 ++-- phpunit-extension.xml | 67 ++++------ phpunit.xml.dist | 44 +++---- psalm.xml | 17 +-- src/Annotation/AnnotationExtension.php | 38 ++++-- src/Annotation/Attribute/Lua.php | 39 ++++++ src/Annotation/Attribute/Sql.php | 39 ++++++ src/Annotation/Processor/LuaProcessor.php | 2 + src/Annotation/Processor/SqlProcessor.php | 2 + .../Requirement/LuaConditionRequirement.php | 2 + .../TarantoolVersionRequirement.php | 13 +- src/Client/IsRequestType.php | 4 +- src/Client/MockClientBuilder2.php.bak | 103 ++++++++++++++++ src/Client/MockClientBuilderOld.php.bak | 115 ++++++++++++++++++ src/Client/TestDoubleClientBuilder.php | 58 +++------ src/Client/TestDoubleFactory.php | 21 ---- .../PreparedStatementCountContext.php | 2 + .../ExpressionContext/RequestCountContext.php | 2 + .../ExpressionContext/RequestCounter.php | 1 + tests/Annotation/AnnotationExtension.php | 5 + tests/Annotation/AnnotationExtensionTest.php | 47 ++++--- .../TarantoolVersionRequirementTest.php | 4 +- tests/Expectation/RequestExpectationsTest.php | 4 +- 23 files changed, 460 insertions(+), 190 deletions(-) create mode 100644 src/Annotation/Attribute/Lua.php create mode 100644 src/Annotation/Attribute/Sql.php create mode 100755 src/Client/MockClientBuilder2.php.bak create mode 100755 src/Client/MockClientBuilderOld.php.bak diff --git a/composer.json b/composer.json index f4c4d7d..41a23bd 100644 --- a/composer.json +++ b/composer.json @@ -11,17 +11,17 @@ } ], "require": { - "php": "^7.2.5|^8", + "php": ">=8.2", "composer/package-versions-deprecated": "1.11.99.5", "composer/semver": "^3.3", - "rybakit/phpunit-extras": "^0.2.4", - "symfony/expression-language": "^3.3|^4|^5|^6", + "rybakit/phpunit-extras": "dev-feature/php82-phpunit10-attributes-migration", + "symfony/expression-language": "^7.0", "tarantool/client": "^0.10" }, "require-dev": { - "php": "^7.2.5|^8", - "friendsofphp/php-cs-fixer": "^2.19", - "vimeo/psalm": "^3.9|^4" + "friendsofphp/php-cs-fixer": "^3", + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^5.23|^6" }, "autoload": { "psr-4": { @@ -38,5 +38,12 @@ "*": "dist" }, "sort-packages": true - } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "repositories": [{ + "name": "local", + "type": "path", + "url": "/Users/gen/dev/php/phpunit-extras" + }] } diff --git a/phpunit-extension.xml b/phpunit-extension.xml index fef869a..24118d7 100644 --- a/phpunit-extension.xml +++ b/phpunit-extension.xml @@ -1,45 +1,26 @@ - - - - - - - - - - - - tests/Annotation/AnnotationExtensionTest.php - - - - - - src - - - - - - - - - true - - - 42 - - - foobar - - - - - + + + + + + + + + + tests/Annotation/AnnotationExtensionTest.php + + + + + src + + + + + + + + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c48d998..205118c 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,28 +1,20 @@ - - - - - - - - - - - - tests - tests/Annotation/AnnotationExtensionTest.php - - - - - - src - - + + + + + + + + + + tests + tests/Annotation/AnnotationExtensionTest.php + + + + + src + + diff --git a/psalm.xml b/psalm.xml index 18db1fb..fbc3fcb 100644 --- a/psalm.xml +++ b/psalm.xml @@ -14,15 +14,16 @@ - + + + + + + + - - - - - - + - + diff --git a/src/Annotation/AnnotationExtension.php b/src/Annotation/AnnotationExtension.php index 2c8ae49..c39baa7 100644 --- a/src/Annotation/AnnotationExtension.php +++ b/src/Annotation/AnnotationExtension.php @@ -17,24 +17,46 @@ use PHPUnitExtras\Annotation\AnnotationExtension as BaseAnnotationExtension; use Tarantool\Client\Client; -class AnnotationExtension extends BaseAnnotationExtension +final class AnnotationExtension extends BaseAnnotationExtension { use Annotations; /** @var array|string */ - private $clientConfig; + private $clientConfig = 'tcp://127.0.0.1:3301'; /** @var Client|null */ private $client; - /** - * @param array|string $clientConfig - */ - public function __construct($clientConfig = 'tcp://127.0.0.1:3301') + + + #[\Override] + public function bootstrap(\PHPUnit\TextUI\Configuration\Configuration $configuration, \PHPUnit\Runner\Extension\Facade $facade, \PHPUnit\Runner\Extension\ParameterCollection $parameters) : void { - $this->clientConfig = $clientConfig; + $this->parseParameters($parameters); + parent::bootstrap($configuration, $facade, $parameters); + } + + protected function parseParameters(\PHPUnit\Runner\Extension\ParameterCollection $parameters) : void + { + if ($parameters->has('dsn')) { + $this->clientConfig = $parameters->get('dsn'); + } else { + $closure = \Closure::bind(function () { + /** + * @psalm-suppress InaccessibleProperty + * @var \PHPUnit\Runner\Extension\ParameterCollection $this + */ + return $this->parameters; + }, $parameters, \PHPUnit\Runner\Extension\ParameterCollection::class); + $options = $closure ? $closure() : []; + + if ($options !== []) { + $this->clientConfig = $options; + } + } } + #[\Override] protected function getClient() : Client { if ($this->client) { @@ -71,7 +93,7 @@ final protected function getClientConfig(bool $resolveEnvVars = true) private static function resolveEnvValues(string $configValue) : string { - return preg_replace_callback('/%env\((?P.+?)\)%/', static function (array $matches) : string { + return (string) preg_replace_callback('/%env\((?P.+?)\)%/', static function (array $matches) : string { if (false !== $value = getenv($matches['name'])) { return $value; } diff --git a/src/Annotation/Attribute/Lua.php b/src/Annotation/Attribute/Lua.php new file mode 100644 index 0000000..119b002 --- /dev/null +++ b/src/Annotation/Attribute/Lua.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Tarantool\PhpUnit\Annotation\Attribute; + +use PHPUnitExtras\Annotation\Attribute\AnnotationAttribute; + +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +final class Lua implements AnnotationAttribute +{ + private $code; + + public function __construct(string $code) + { + $this->code = $code; + } + + #[\Override] + public function getName() : string + { + return 'lua'; + } + + #[\Override] + public function getValue() : string + { + return $this->code; + } +} diff --git a/src/Annotation/Attribute/Sql.php b/src/Annotation/Attribute/Sql.php new file mode 100644 index 0000000..45370d9 --- /dev/null +++ b/src/Annotation/Attribute/Sql.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Tarantool\PhpUnit\Annotation\Attribute; + +use PHPUnitExtras\Annotation\Attribute\AnnotationAttribute; + +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +final class Sql implements AnnotationAttribute +{ + private $code; + + public function __construct(string $code) + { + $this->code = $code; + } + + #[\Override] + public function getName() : string + { + return 'sql'; + } + + #[\Override] + public function getValue() : string + { + return $this->code; + } +} diff --git a/src/Annotation/Processor/LuaProcessor.php b/src/Annotation/Processor/LuaProcessor.php index 375bd67..d76eca9 100644 --- a/src/Annotation/Processor/LuaProcessor.php +++ b/src/Annotation/Processor/LuaProcessor.php @@ -25,11 +25,13 @@ public function __construct(Client $client) $this->client = $client; } + #[\Override] public function getName() : string { return 'lua'; } + #[\Override] public function process(string $value) : void { $this->client->evaluate($value); diff --git a/src/Annotation/Processor/SqlProcessor.php b/src/Annotation/Processor/SqlProcessor.php index 144c6b5..3c8261e 100644 --- a/src/Annotation/Processor/SqlProcessor.php +++ b/src/Annotation/Processor/SqlProcessor.php @@ -25,11 +25,13 @@ public function __construct(Client $client) $this->client = $client; } + #[\Override] public function getName() : string { return 'sql'; } + #[\Override] public function process(string $value) : void { $this->client->executeUpdate($value); diff --git a/src/Annotation/Requirement/LuaConditionRequirement.php b/src/Annotation/Requirement/LuaConditionRequirement.php index 9b1b950..8482496 100644 --- a/src/Annotation/Requirement/LuaConditionRequirement.php +++ b/src/Annotation/Requirement/LuaConditionRequirement.php @@ -25,11 +25,13 @@ public function __construct(Client $client) $this->client = $client; } + #[\Override] public function getName() : string { return 'luaCondition'; } + #[\Override] public function check(string $value) : ?string { [$result] = $this->client->evaluate("return ($value)"); diff --git a/src/Annotation/Requirement/TarantoolVersionRequirement.php b/src/Annotation/Requirement/TarantoolVersionRequirement.php index 7d8ecb4..6487de6 100644 --- a/src/Annotation/Requirement/TarantoolVersionRequirement.php +++ b/src/Annotation/Requirement/TarantoolVersionRequirement.php @@ -29,15 +29,17 @@ public function __construct(Client $client) $this->client = $client; } + #[\Override] public function getName() : string { return 'Tarantool'; } + #[\Override] public function check(string $value) : ?string { // Replace dash with dot. - $constraints = preg_replace('/(\d+\.\d+\.\d+)-(\d+)/', '$1.$2', $value); + $constraints = (string) preg_replace('/(\d+\.\d+\.\d+)-(\d+)/', '$1.$2', $value); if (Semver::satisfies($this->getVersion(), $constraints)) { return null; @@ -52,14 +54,17 @@ private function getVersion() : string return $this->version; } - $version = $this->client->call('box.info')[0]['version']; + $version = $this->client->evaluate('return box.info.version')[0]; + if (!\is_string($version)) { + throw new \UnexpectedValueException('Tarantool version must be a string'); + } // Normalize 2.2.1-3-g878e2a42c to 2.2.1.3. - $version = preg_replace('/-(\d+)-[^-]+$/', '.$1', $version); + $version = (string) preg_replace('/-(\d+)-[^-]+$/', '.$1', $version); // Treat "entrypoint" versions as "dev", // so 2.11.0-entrypoint.8 becomes 2.11.0-dev+entrypoint.8. - $version = preg_replace('/(\d)-entrypoint/', '$1-dev+entrypoint', $version); + $version = (string) preg_replace('/(\d)-entrypoint/', '$1-dev+entrypoint', $version); return $this->version = $version; } diff --git a/src/Client/IsRequestType.php b/src/Client/IsRequestType.php index b8b4c57..68bb713 100644 --- a/src/Client/IsRequestType.php +++ b/src/Client/IsRequestType.php @@ -25,18 +25,20 @@ final class IsRequestType extends Constraint public function __construct(int $requestType) { // needed for backward compatibility with PHPUnit 7 - if (\is_callable('parent::__construct')) { + if (\method_exists(parent::class, '__construct')) { parent::__construct(); } $this->requestType = $requestType; } + #[\Override] public function toString() : string { return sprintf('is a "%s" request', strtoupper(RequestTypes::getName($this->requestType))); } + #[\Override] protected function matches($other) : bool { return $other instanceof Request && $other->getType() === $this->requestType; diff --git a/src/Client/MockClientBuilder2.php.bak b/src/Client/MockClientBuilder2.php.bak new file mode 100755 index 0000000..ef7ad90 --- /dev/null +++ b/src/Client/MockClientBuilder2.php.bak @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Tarantool\PhpUnit\Client; + +use Prophecy\Argument; +use Prophecy\Prophecy\ObjectProphecy; +use Tarantool\Client\Client; +use Tarantool\Client\Connection\Connection; +use Tarantool\Client\Handler\Handler; +use Tarantool\Client\Packer\Packer; +use Tarantool\Client\Request\Request; + +final class MockClientBuilder2 +{ + private $prophesize; + private $requests; + private $packer; + + public function __construct(\Closure $prophesize) + { + $this->prophesize = $prophesize; + $this->requests = new \SplObjectStorage(); + } + + public function shouldHandle($request, ...$responses) : self + { + $this->requests->attach($request, $responses); + + return $this; + } + + public function withPacker($packer) : self + { + //$self = clone $this; + $this->packer = $packer; + + return $this; + } + + public function build() : Client + { + $handler = $this->createHandler(); + + return new Client($handler->reveal()); + } + + private function createHandler() : ObjectProphecy + { + $handler = ($this->prophesize)(Handler::class); + + $connection = $this->createConnection(); + $handler->getConnection()->willReturn($connection); + + $packer = $this->createPacker(); + $handler->getPacker()->willReturn($packer); + + $defaultResponse = DummyFactory::createEmptyResponse(); + + if (!$this->requests->count()) { + $handler->handle(Argument::type(Request::class))->willReturn($defaultResponse); + + return $handler; + } + + foreach ($this->requests as $request) { + if (!$responses = $this->requests->getInfo()) { + $handler->handle($request)->willReturn($defaultResponse); + continue; + } + + $handler->handle($request)->willReturn(...$responses) + ->shouldBeCalledTimes(\count($responses)); + } + + return $handler; + } + + private function createConnection() : ObjectProphecy + { + return ($this->prophesize)(Connection::class); + } + + private function createPacker() // : ObjectProphecy + { + if ($this->packer) { + return $this->packer; + //return $this->prophet->prophesize($this->packer); + } + + return ($this->prophesize)(Packer::class); + } +} diff --git a/src/Client/MockClientBuilderOld.php.bak b/src/Client/MockClientBuilderOld.php.bak new file mode 100755 index 0000000..7b6b7d7 --- /dev/null +++ b/src/Client/MockClientBuilderOld.php.bak @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Tarantool\PhpUnit\Client; + +use Prophecy\Argument; +use Prophecy\Prophecy\ObjectProphecy; +use Prophecy\Prophet; +use Tarantool\Client\Client; +use Tarantool\Client\Connection\Connection; +use Tarantool\Client\Handler\Handler; +use Tarantool\Client\Packer\Packer; +use Tarantool\Client\Request\Request; + +final class MockClientBuilderOld +{ + /** @var Prophet */ + private $prophet; + private $requests; + private $packer; + + public function __construct() + { + $this->prophet = new Prophet(); + $this->requests = new \SplObjectStorage(); + } + + public static function buildDefault() : Client + { + return (new self())->build(); + } + + public function shouldHandle($request, ...$responses) : self + { + $this->requests->attach($request, $responses); + + return $this; + } + + public function withPacker($packer) : self + { + //$self = clone $this; + $this->packer = $packer; + + return $this; + } + + public function build() : Client + { + $handler = $this->createHandler(); + + return new Client($handler->reveal()); + } + + public function checkPredictions() : void + { + $this->prophet->checkPredictions(); + } + + private function createHandler() : ObjectProphecy + { + $handler = $this->prophet->prophesize(Handler::class); + + $connection = $this->createConnection(); + $handler->getConnection()->willReturn($connection); + + $packer = $this->createPacker(); + $handler->getPacker()->willReturn($packer); + + $defaultResponse = DummyFactory::createEmptyResponse(); + + if (!$this->requests->count()) { + $handler->handle(Argument::type(Request::class))->willReturn($defaultResponse); + + return $handler; + } + + foreach ($this->requests as $request) { + if (!$responses = $this->requests->getInfo()) { + $handler->handle($request)->willReturn($defaultResponse); + continue; + } + + $handler->handle($request)->willReturn(...$responses) + ->shouldBeCalledTimes(\count($responses)); + } + + return $handler; + } + + private function createConnection() : ObjectProphecy + { + return $this->prophet->prophesize(Connection::class); + } + + private function createPacker() // : ObjectProphecy + { + if ($this->packer) { + return $this->packer; + //return $this->prophet->prophesize($this->packer); + } + + return $this->prophet->prophesize(Packer::class); + } +} diff --git a/src/Client/TestDoubleClientBuilder.php b/src/Client/TestDoubleClientBuilder.php index 8ce18b3..1a212ab 100755 --- a/src/Client/TestDoubleClientBuilder.php +++ b/src/Client/TestDoubleClientBuilder.php @@ -49,18 +49,6 @@ public function __construct(TestCase $testCase) $this->responses = [TestDoubleFactory::createEmptyResponse()]; } - public static function buildDummy() : Client - { - /** - * @psalm-suppress InternalMethod - * @psalm-suppress PropertyNotSetInConstructor - */ - $self = new self(new class() extends TestCase { - }); - - return $self->build(); - } - /** * @param Request|Constraint|int $request * @param Request|Constraint|int ...$requests @@ -77,22 +65,6 @@ public function shouldSend($request, ...$requests) : self return $this; } - /** - * @param Request|Constraint|int $request - * @param Response ...$responses - */ - public function shouldHandle($request, ...$responses) : self - { - $this->shouldSend($request); - $this->willReceive(...$responses); - - if ($responses) { - $this->shouldBeCalledTimes = \count($responses); - } - - return $this; - } - public function willReceive(Response $response, Response ...$responses) : self { $this->responses = \func_get_args(); @@ -100,20 +72,6 @@ public function willReceive(Response $response, Response ...$responses) : self return $this; } - public function willUseConnection(Connection $connection) : self - { - $this->connection = $connection; - - return $this; - } - - public function willUsePacker(Packer $packer) : self - { - $this->packer = $packer; - - return $this; - } - public function build() : Client { /** @var Handler $handler */ @@ -137,7 +95,21 @@ private function createHandler() : MockObject : $handler->method('handle'); if ($this->requests) { - $handleMocker->withConsecutive(...array_chunk($this->requests, 1)); + $invocationCount = 0; + $requests = $this->requests; + $handleMocker->with(TestCase::callback(static function ($request) use (&$invocationCount, $requests) { + if (!isset($requests[$invocationCount])) { + $invocationCount++; + return true; + } + $expected = $requests[$invocationCount++]; + + if ($expected instanceof Constraint) { + return (bool) $expected->evaluate($request, '', true); + } + + return $expected == $request; + })); } if (1 === \count($this->responses)) { diff --git a/src/Client/TestDoubleFactory.php b/src/Client/TestDoubleFactory.php index 62798da..6b40d43 100755 --- a/src/Client/TestDoubleFactory.php +++ b/src/Client/TestDoubleFactory.php @@ -32,25 +32,4 @@ public static function createEmptyResponse() : Response { return self::createResponseFromData([null]); } - - public static function createErrorResponse(string $errorMessage = '', int $errorCode = 0) : Response - { - return self::createResponse( - [Keys::ERROR_24 => $errorMessage], - [Keys::CODE => Response::TYPE_ERROR + $errorCode] - ); - } - - public static function createErrorResponseFromStack(array $errorStack) : Response - { - $errorMessage = $errorStack[0][Keys::ERROR_MESSAGE] ?? ''; - $errorCode = $errorStack[0][Keys::ERROR_CODE] ?? 0; - - return self::createResponse([ - Keys::ERROR_24 => $errorMessage, - Keys::ERROR => [Keys::ERROR_STACK => $errorStack], - ], [ - Keys::CODE => Response::TYPE_ERROR + $errorCode, - ]); - } } diff --git a/src/Expectation/ExpressionContext/PreparedStatementCountContext.php b/src/Expectation/ExpressionContext/PreparedStatementCountContext.php index 0d8d71d..ab69daa 100644 --- a/src/Expectation/ExpressionContext/PreparedStatementCountContext.php +++ b/src/Expectation/ExpressionContext/PreparedStatementCountContext.php @@ -52,11 +52,13 @@ public static function atMost(Client $client, int $count) : self return new self($client, "new_count <= old_count + $count"); } + #[\Override] public function getExpression() : string { return $this->expression; } + #[\Override] public function getValues() : array { if (null === $this->finalValue) { diff --git a/src/Expectation/ExpressionContext/RequestCountContext.php b/src/Expectation/ExpressionContext/RequestCountContext.php index b5f821a..20d4226 100644 --- a/src/Expectation/ExpressionContext/RequestCountContext.php +++ b/src/Expectation/ExpressionContext/RequestCountContext.php @@ -75,11 +75,13 @@ public static function atMost(Client $client, RequestCounter $requestCounter, st return new self($client, $requestCounter, $requestName, "new_count <= old_count + $count"); } + #[\Override] public function getExpression() : string { return $this->expression; } + #[\Override] public function getValues() : array { if (null === $this->finalValue) { diff --git a/src/Expectation/ExpressionContext/RequestCounter.php b/src/Expectation/ExpressionContext/RequestCounter.php index c020cea..a1cfee8 100644 --- a/src/Expectation/ExpressionContext/RequestCounter.php +++ b/src/Expectation/ExpressionContext/RequestCounter.php @@ -25,6 +25,7 @@ final class RequestCounter implements Middleware */ private $requestCount = []; + #[\Override] public function process(Request $request, Handler $handler) : Response { $response = $handler->handle($request); diff --git a/tests/Annotation/AnnotationExtension.php b/tests/Annotation/AnnotationExtension.php index d2a9e83..fe084e6 100755 --- a/tests/Annotation/AnnotationExtension.php +++ b/tests/Annotation/AnnotationExtension.php @@ -27,4 +27,9 @@ protected function getClient() : Client return TestDoubleClientBuilder::buildDummy(); } + + public function doParseParameters(array $parameters) : void + { + $this->parseParameters(\PHPUnit\Runner\Extension\ParameterCollection::fromArray($parameters)); + } } diff --git a/tests/Annotation/AnnotationExtensionTest.php b/tests/Annotation/AnnotationExtensionTest.php index cf9c334..821bec1 100755 --- a/tests/Annotation/AnnotationExtensionTest.php +++ b/tests/Annotation/AnnotationExtensionTest.php @@ -14,35 +14,36 @@ namespace Tarantool\PhpUnit\Tests\Annotation; use PHPUnit\Framework\TestCase; +use Tarantool\PhpUnit\Annotation\Attribute\Lua; final class AnnotationExtensionTest extends TestCase { - /** - * @lua dummy_code_to_trigger_annotation_processing = true - */ - public function testConstructorUsesDefaultDsn() : void + private function bootstrapExtension(string $method, array $parameters = []) : AnnotationExtension { $ext = new AnnotationExtension(); + $ext->doParseParameters($parameters); + $ext->processTestAttributes(self::class, $method); + return $ext; + } + + #[Lua('dummy_code_to_trigger_annotation_processing = true')] + public function testConstructorUsesDefaultDsn() : void + { + $ext = $this->bootstrapExtension(__FUNCTION__); - $ext->executeBeforeTest(__METHOD__); self::assertSame('tcp://127.0.0.1:3301', $ext->resolvedDnsOrOptions); } - /** - * @lua dummy_code_to_trigger_annotation_processing = true - */ + #[Lua('dummy_code_to_trigger_annotation_processing = true')] public function testConstructorUsesCustomDsn() : void { $dsn = 'tcp://tnt_foobar:3302'; - $ext = new AnnotationExtension($dsn); + $ext = $this->bootstrapExtension(__FUNCTION__, ['dsn' => $dsn]); - $ext->executeBeforeTest(__METHOD__); self::assertSame($dsn, $ext->resolvedDnsOrOptions); } - /** - * @lua dummy_code_to_trigger_annotation_processing = true - */ + #[Lua('dummy_code_to_trigger_annotation_processing = true')] public function testGetClientConfigNormalizesDsnString() : void { $hostname = 'tnt_foobar'; @@ -51,15 +52,12 @@ public function testGetClientConfigNormalizesDsnString() : void $envPortName = 'tnt_phpunit_env_port_'.random_int(1, 1000); putenv("$envHostName=$hostname"); putenv("$envPortName=$port"); - $ext = new AnnotationExtension("tcp://%env($envHostName)%:%env($envPortName)%"); + $ext = $this->bootstrapExtension(__FUNCTION__, ['dsn' => "tcp://%env($envHostName)%:%env($envPortName)%"]); - $ext->executeBeforeTest(__METHOD__); self::assertSame("tcp://$hostname:$port", $ext->resolvedDnsOrOptions); } - /** - * @lua dummy_code_to_trigger_annotation_processing = true - */ + #[Lua('dummy_code_to_trigger_annotation_processing = true')] public function testGetClientConfigNormalizesOptionArray() : void { $hostname = 'tnt_foobar'; @@ -68,17 +66,16 @@ public function testGetClientConfigNormalizesOptionArray() : void $envPortName = 'tnt_phpunit_env_port_'.random_int(1, 1000); putenv("$envHostName=$hostname"); putenv("$envPortName=$port"); - $ext = new AnnotationExtension([ + $ext = $this->bootstrapExtension(__FUNCTION__, [ 'uri' => "tcp://%env($envHostName)%:%env($envPortName)%", - 'socket_timeout' => 10, - 'persistent' => true, + 'socket_timeout' => '10', + 'persistent' => '1', ]); - $ext->executeBeforeTest(__METHOD__); - self::assertSame([ + self::assertEquals([ 'uri' => "tcp://$hostname:$port", - 'socket_timeout' => 10, - 'persistent' => true, + 'socket_timeout' => '10', + 'persistent' => '1', ], $ext->resolvedDnsOrOptions); } } diff --git a/tests/Annotation/Requirement/TarantoolVersionRequirementTest.php b/tests/Annotation/Requirement/TarantoolVersionRequirementTest.php index 57983dc..774b814 100755 --- a/tests/Annotation/Requirement/TarantoolVersionRequirementTest.php +++ b/tests/Annotation/Requirement/TarantoolVersionRequirementTest.php @@ -39,7 +39,7 @@ public function testCheckPassesForValidConstraints(string $serverVersion, string self::assertNull($requirement->check($constraints)); } - public function provideCheckPassesForValidConstraintsData() : iterable + public static function provideCheckPassesForValidConstraintsData() : iterable { $v2_3_1_3 = '2.3.1-3-g878e2a42c'; @@ -112,7 +112,7 @@ public function testCheckFailsForInvalidConstraints(string $serverVersion, strin self::assertSame($errorMessage, $requirement->check($constraints)); } - public function provideCheckFailsForInvalidConstraintsData() : iterable + public static function provideCheckFailsForInvalidConstraintsData() : iterable { $v2_3_1_3 = '2.3.1-3-g878e2a42c'; diff --git a/tests/Expectation/RequestExpectationsTest.php b/tests/Expectation/RequestExpectationsTest.php index 3453ec6..76ab378 100755 --- a/tests/Expectation/RequestExpectationsTest.php +++ b/tests/Expectation/RequestExpectationsTest.php @@ -33,7 +33,7 @@ protected function getClient() : Client { // increase values for eval requests // to eliminate RequestCounter's count adjustments - if ('Eval' === $this->getProvidedData()[0]) { + if ('Eval' === $this->providedData()[0]) { ++$this->oldValue; $this->newValue += 2; } @@ -47,7 +47,7 @@ protected function getClient() : Client ->build(); } - public function provideRequestNames() : iterable + public static function provideRequestNames() : iterable { return [ ['Auth'], From f030d87631ba9fff687ed67522403152e441847c Mon Sep 17 00:00:00 2001 From: Eugene Leonovich Date: Sat, 25 Jul 2026 19:49:14 +0200 Subject: [PATCH 02/14] Fix Psalm and response shape for evaluate --- src/Annotation/AnnotationExtension.php | 2 +- src/Client/TestDoubleClientBuilder.php | 22 +++++++ .../TarantoolVersionRequirementTest.php | 19 +++---- tests/Expectation/RequestExpectationsTest.php | 57 +++++-------------- 4 files changed, 46 insertions(+), 54 deletions(-) diff --git a/src/Annotation/AnnotationExtension.php b/src/Annotation/AnnotationExtension.php index c39baa7..834f6ef 100644 --- a/src/Annotation/AnnotationExtension.php +++ b/src/Annotation/AnnotationExtension.php @@ -17,7 +17,7 @@ use PHPUnitExtras\Annotation\AnnotationExtension as BaseAnnotationExtension; use Tarantool\Client\Client; -final class AnnotationExtension extends BaseAnnotationExtension +class AnnotationExtension extends BaseAnnotationExtension { use Annotations; diff --git a/src/Client/TestDoubleClientBuilder.php b/src/Client/TestDoubleClientBuilder.php index 1a212ab..d4260bb 100755 --- a/src/Client/TestDoubleClientBuilder.php +++ b/src/Client/TestDoubleClientBuilder.php @@ -49,6 +49,13 @@ public function __construct(TestCase $testCase) $this->responses = [TestDoubleFactory::createEmptyResponse()]; } + public static function buildDummy() : Client + { + /** @psalm-suppress InternalMethod */ + $self = new self(new class('dummy') extends TestCase {}); + return $self->build(); + } + /** * @param Request|Constraint|int $request * @param Request|Constraint|int ...$requests @@ -65,6 +72,21 @@ public function shouldSend($request, ...$requests) : self return $this; } + /** + * @param Request|Constraint|int $request + */ + public function shouldHandle($request, Response ...$responses) : self + { + $this->shouldSend($request); + $this->willReceive(...$responses); + + if ($responses) { + $this->shouldBeCalledTimes = \count($responses); + } + + return $this; + } + public function willReceive(Response $response, Response ...$responses) : self { $this->responses = \func_get_args(); diff --git a/tests/Annotation/Requirement/TarantoolVersionRequirementTest.php b/tests/Annotation/Requirement/TarantoolVersionRequirementTest.php index 774b814..14ceb75 100755 --- a/tests/Annotation/Requirement/TarantoolVersionRequirementTest.php +++ b/tests/Annotation/Requirement/TarantoolVersionRequirementTest.php @@ -13,8 +13,9 @@ namespace Tarantool\PhpUnit\Tests\Annotation\Requirement; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; -use Tarantool\Client\Request\CallRequest; +use Tarantool\Client\Request\EvaluateRequest; use Tarantool\PhpUnit\Annotation\Requirement\TarantoolVersionRequirement; use Tarantool\PhpUnit\Client\TestDoubleClient; use Tarantool\PhpUnit\Client\TestDoubleFactory; @@ -23,15 +24,13 @@ final class TarantoolVersionRequirementTest extends TestCase { use TestDoubleClient; - /** - * @dataProvider provideCheckPassesForValidConstraintsData() - */ + #[DataProvider('provideCheckPassesForValidConstraintsData')] public function testCheckPassesForValidConstraints(string $serverVersion, string $constraints) : void { $mockClient = $this->getTestDoubleClientBuilder() ->shouldHandle( - new CallRequest('box.info'), - TestDoubleFactory::createResponseFromData([['version' => $serverVersion]])) + new EvaluateRequest('return box.info.version'), + TestDoubleFactory::createResponseFromData([$serverVersion])) ->build(); $requirement = new TarantoolVersionRequirement($mockClient); @@ -95,15 +94,13 @@ public static function provideCheckPassesForValidConstraintsData() : iterable ]; } - /** - * @dataProvider provideCheckFailsForInvalidConstraintsData() - */ + #[DataProvider('provideCheckFailsForInvalidConstraintsData')] public function testCheckFailsForInvalidConstraints(string $serverVersion, string $constraints) : void { $mockClient = $this->getTestDoubleClientBuilder() ->shouldHandle( - new CallRequest('box.info'), - TestDoubleFactory::createResponseFromData([['version' => $serverVersion]])) + new EvaluateRequest('return box.info.version'), + TestDoubleFactory::createResponseFromData([$serverVersion])) ->build(); $requirement = new TarantoolVersionRequirement($mockClient); diff --git a/tests/Expectation/RequestExpectationsTest.php b/tests/Expectation/RequestExpectationsTest.php index 76ab378..160b194 100755 --- a/tests/Expectation/RequestExpectationsTest.php +++ b/tests/Expectation/RequestExpectationsTest.php @@ -13,6 +13,7 @@ namespace Tarantool\PhpUnit\Tests\Expectation; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\ExpectationFailedException; use PHPUnitExtras\TestCase; use Tarantool\Client\Client; @@ -63,9 +64,7 @@ public static function provideRequestNames() : iterable ]; } - /** - * @dataProvider provideRequestNames - */ + #[DataProvider('provideRequestNames')] public function testExpectToBeCalledSucceeds(string $requestName) : void { $this->oldValue = 1; @@ -74,9 +73,7 @@ public function testExpectToBeCalledSucceeds(string $requestName) : void $this->verifyExpectations(); } - /** - * @dataProvider provideRequestNames - */ + #[DataProvider('provideRequestNames')] public function testExpectToBeCalledFails(string $requestName) : void { $this->oldValue = 1; @@ -87,9 +84,7 @@ public function testExpectToBeCalledFails(string $requestName) : void $this->verifyExpectations(); } - /** - * @dataProvider provideRequestNames - */ + #[DataProvider('provideRequestNames')] public function testExpectToBeCalledAtLeastSucceeds(string $requestName) : void { $this->oldValue = 1; @@ -98,9 +93,7 @@ public function testExpectToBeCalledAtLeastSucceeds(string $requestName) : void $this->verifyExpectations(); } - /** - * @dataProvider provideRequestNames - */ + #[DataProvider('provideRequestNames')] public function testExpectToBeCalledAtLeastFails(string $requestName) : void { $this->oldValue = 1; @@ -111,9 +104,7 @@ public function testExpectToBeCalledAtLeastFails(string $requestName) : void $this->verifyExpectations(); } - /** - * @dataProvider provideRequestNames - */ + #[DataProvider('provideRequestNames')] public function testExpectToBeCalledAtMostSucceeds(string $requestName) : void { $this->oldValue = 1; @@ -122,9 +113,7 @@ public function testExpectToBeCalledAtMostSucceeds(string $requestName) : void $this->verifyExpectations(); } - /** - * @dataProvider provideRequestNames - */ + #[DataProvider('provideRequestNames')] public function testExpectToBeCalledAtMostFails(string $requestName) : void { $this->oldValue = 1; @@ -135,9 +124,7 @@ public function testExpectToBeCalledAtMostFails(string $requestName) : void $this->verifyExpectations(); } - /** - * @dataProvider provideRequestNames - */ + #[DataProvider('provideRequestNames')] public function testExpectToBeCalledOnceSucceeds(string $requestName) : void { $this->oldValue = 1; @@ -146,9 +133,7 @@ public function testExpectToBeCalledOnceSucceeds(string $requestName) : void $this->verifyExpectations(); } - /** - * @dataProvider provideRequestNames - */ + #[DataProvider('provideRequestNames')] public function testExpectToBeCalledOnceFails(string $requestName) : void { $this->oldValue = 1; @@ -159,9 +144,7 @@ public function testExpectToBeCalledOnceFails(string $requestName) : void $this->verifyExpectations(); } - /** - * @dataProvider provideRequestNames - */ + #[DataProvider('provideRequestNames')] public function testExpectToBeNeverCalledSucceeds(string $requestName) : void { $this->oldValue = 1; @@ -170,9 +153,7 @@ public function testExpectToBeNeverCalledSucceeds(string $requestName) : void $this->verifyExpectations(); } - /** - * @dataProvider provideRequestNames - */ + #[DataProvider('provideRequestNames')] public function testExpectToBeNeverCalledFails(string $requestName) : void { $this->oldValue = 1; @@ -183,9 +164,7 @@ public function testExpectToBeNeverCalledFails(string $requestName) : void $this->verifyExpectations(); } - /** - * @dataProvider provideRequestNames - */ + #[DataProvider('provideRequestNames')] public function testExpectToBeCalledAtLeastOnceSucceeds(string $requestName) : void { $this->oldValue = 1; @@ -194,9 +173,7 @@ public function testExpectToBeCalledAtLeastOnceSucceeds(string $requestName) : v $this->verifyExpectations(); } - /** - * @dataProvider provideRequestNames - */ + #[DataProvider('provideRequestNames')] public function testExpectToBeCalledAtLeastOnceFails(string $requestName) : void { $this->oldValue = 1; @@ -207,9 +184,7 @@ public function testExpectToBeCalledAtLeastOnceFails(string $requestName) : void $this->verifyExpectations(); } - /** - * @dataProvider provideRequestNames - */ + #[DataProvider('provideRequestNames')] public function testExpectToBeCalledAtMostOnceSucceeds(string $requestName) : void { $this->oldValue = 1; @@ -218,9 +193,7 @@ public function testExpectToBeCalledAtMostOnceSucceeds(string $requestName) : vo $this->verifyExpectations(); } - /** - * @dataProvider provideRequestNames - */ + #[DataProvider('provideRequestNames')] public function testExpectToBeCalledAtMostOnceFails(string $requestName) : void { $this->oldValue = 1; From bb367f48a6eb0e67c0fbd692d2114110055d6522 Mon Sep 17 00:00:00 2001 From: Eugene Leonovich Date: Sat, 25 Jul 2026 19:56:05 +0200 Subject: [PATCH 03/14] Restore library API methods incorrectly removed by Psalm --- src/Client/TestDoubleClientBuilder.php | 14 ++++++++++++++ src/Client/TestDoubleFactory.php | 21 +++++++++++++++++++++ 2 files changed, 35 insertions(+) mode change 100755 => 100644 src/Client/TestDoubleFactory.php diff --git a/src/Client/TestDoubleClientBuilder.php b/src/Client/TestDoubleClientBuilder.php index d4260bb..a067baf 100755 --- a/src/Client/TestDoubleClientBuilder.php +++ b/src/Client/TestDoubleClientBuilder.php @@ -94,6 +94,20 @@ public function willReceive(Response $response, Response ...$responses) : self return $this; } + public function willUseConnection(Connection $connection) : self + { + $this->connection = $connection; + + return $this; + } + + public function willUsePacker(Packer $packer) : self + { + $this->packer = $packer; + + return $this; + } + public function build() : Client { /** @var Handler $handler */ diff --git a/src/Client/TestDoubleFactory.php b/src/Client/TestDoubleFactory.php old mode 100755 new mode 100644 index 6b40d43..62798da --- a/src/Client/TestDoubleFactory.php +++ b/src/Client/TestDoubleFactory.php @@ -32,4 +32,25 @@ public static function createEmptyResponse() : Response { return self::createResponseFromData([null]); } + + public static function createErrorResponse(string $errorMessage = '', int $errorCode = 0) : Response + { + return self::createResponse( + [Keys::ERROR_24 => $errorMessage], + [Keys::CODE => Response::TYPE_ERROR + $errorCode] + ); + } + + public static function createErrorResponseFromStack(array $errorStack) : Response + { + $errorMessage = $errorStack[0][Keys::ERROR_MESSAGE] ?? ''; + $errorCode = $errorStack[0][Keys::ERROR_CODE] ?? 0; + + return self::createResponse([ + Keys::ERROR_24 => $errorMessage, + Keys::ERROR => [Keys::ERROR_STACK => $errorStack], + ], [ + Keys::CODE => Response::TYPE_ERROR + $errorCode, + ]); + } } From ee84ea0f434d30a71c86740f1df89b67d0b8648f Mon Sep 17 00:00:00 2001 From: Eugene Leonovich Date: Sat, 25 Jul 2026 20:08:10 +0200 Subject: [PATCH 04/14] Fix composer.json and phpunit xml --- composer.json | 2 +- phpunit-extension.xml | 2 +- phpunit.xml.dist | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 41a23bd..429cc17 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ ], "require": { "php": ">=8.2", - "composer/package-versions-deprecated": "1.11.99.5", + "composer/package-versions-deprecated": "^1.11", "composer/semver": "^3.3", "rybakit/phpunit-extras": "dev-feature/php82-phpunit10-attributes-migration", "symfony/expression-language": "^7.0", diff --git a/phpunit-extension.xml b/phpunit-extension.xml index 24118d7..f343a3e 100644 --- a/phpunit-extension.xml +++ b/phpunit-extension.xml @@ -1,5 +1,5 @@ - + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 205118c..c6e4b88 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,5 +1,5 @@ - + From d5295a9113bce7118ee1aa260a0c3ce73536d174 Mon Sep 17 00:00:00 2001 From: Eugene Leonovich Date: Sat, 25 Jul 2026 20:10:58 +0200 Subject: [PATCH 05/14] Use ^8.2 for PHP constraint --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 429cc17..d2d2cd5 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ } ], "require": { - "php": ">=8.2", + "php": "^8.2", "composer/package-versions-deprecated": "^1.11", "composer/semver": "^3.3", "rybakit/phpunit-extras": "dev-feature/php82-phpunit10-attributes-migration", From 5586fdc045aeac2c33ea4eabd2855ec3a6439771 Mon Sep 17 00:00:00 2001 From: Eugene Leonovich Date: Sat, 25 Jul 2026 20:31:07 +0200 Subject: [PATCH 06/14] Drop unsupported PHP versions from CI matrix --- .github/workflows/qa.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index c2fe040..8ba218d 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest] - php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1'] + php-versions: ['8.2', '8.3', '8.4'] runs-on: ${{ matrix.operating-system }} steps: - name: Checkout @@ -51,7 +51,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.0' + php-version: '8.2' coverage: none - name: Get composer cache directory @@ -81,7 +81,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.0 + php-version: '8.2' coverage: none - name: Get composer cache directory From 9785da37f7c1e851ff6840b551a5eadb7326dec6 Mon Sep 17 00:00:00 2001 From: Eugene Leonovich Date: Sat, 25 Jul 2026 20:33:42 +0200 Subject: [PATCH 07/14] Update GitHub Actions to v4 --- .github/workflows/qa.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index 8ba218d..0712db4 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -14,7 +14,7 @@ jobs: runs-on: ${{ matrix.operating-system }} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -27,7 +27,7 @@ jobs: run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache composer dependencies - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} @@ -46,7 +46,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -59,7 +59,7 @@ jobs: run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache composer dependencies - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} @@ -76,7 +76,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -89,7 +89,7 @@ jobs: run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache composer dependencies - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} From 61e586909a4d16d4fbbc56c6a7928564d4845f77 Mon Sep 17 00:00:00 2001 From: Eugene Leonovich Date: Sat, 25 Jul 2026 20:34:32 +0200 Subject: [PATCH 08/14] Fix composer repositories for CI --- composer.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index d2d2cd5..641dad4 100644 --- a/composer.json +++ b/composer.json @@ -42,8 +42,7 @@ "minimum-stability": "dev", "prefer-stable": true, "repositories": [{ - "name": "local", - "type": "path", - "url": "/Users/gen/dev/php/phpunit-extras" + "type": "vcs", + "url": "https://github.com/rybakit/phpunit-extras" }] } From 52157805d576a59082a910b371e79750cfe51b35 Mon Sep 17 00:00:00 2001 From: Eugene Leonovich Date: Sat, 25 Jul 2026 20:38:05 +0200 Subject: [PATCH 09/14] Rename .php_cs.dist to .php-cs-fixer.dist.php --- .php_cs.dist => .php-cs-fixer.dist.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .php_cs.dist => .php-cs-fixer.dist.php (100%) diff --git a/.php_cs.dist b/.php-cs-fixer.dist.php similarity index 100% rename from .php_cs.dist rename to .php-cs-fixer.dist.php From 043ed1272a2cd451642e6d5846437e8d7e87d1c2 Mon Sep 17 00:00:00 2001 From: Eugene Leonovich Date: Sat, 25 Jul 2026 20:38:50 +0200 Subject: [PATCH 10/14] Remove accidental .bak files --- src/Client/MockClientBuilder2.php.bak | 103 --------------------- src/Client/MockClientBuilderOld.php.bak | 115 ------------------------ 2 files changed, 218 deletions(-) delete mode 100755 src/Client/MockClientBuilder2.php.bak delete mode 100755 src/Client/MockClientBuilderOld.php.bak diff --git a/src/Client/MockClientBuilder2.php.bak b/src/Client/MockClientBuilder2.php.bak deleted file mode 100755 index ef7ad90..0000000 --- a/src/Client/MockClientBuilder2.php.bak +++ /dev/null @@ -1,103 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace Tarantool\PhpUnit\Client; - -use Prophecy\Argument; -use Prophecy\Prophecy\ObjectProphecy; -use Tarantool\Client\Client; -use Tarantool\Client\Connection\Connection; -use Tarantool\Client\Handler\Handler; -use Tarantool\Client\Packer\Packer; -use Tarantool\Client\Request\Request; - -final class MockClientBuilder2 -{ - private $prophesize; - private $requests; - private $packer; - - public function __construct(\Closure $prophesize) - { - $this->prophesize = $prophesize; - $this->requests = new \SplObjectStorage(); - } - - public function shouldHandle($request, ...$responses) : self - { - $this->requests->attach($request, $responses); - - return $this; - } - - public function withPacker($packer) : self - { - //$self = clone $this; - $this->packer = $packer; - - return $this; - } - - public function build() : Client - { - $handler = $this->createHandler(); - - return new Client($handler->reveal()); - } - - private function createHandler() : ObjectProphecy - { - $handler = ($this->prophesize)(Handler::class); - - $connection = $this->createConnection(); - $handler->getConnection()->willReturn($connection); - - $packer = $this->createPacker(); - $handler->getPacker()->willReturn($packer); - - $defaultResponse = DummyFactory::createEmptyResponse(); - - if (!$this->requests->count()) { - $handler->handle(Argument::type(Request::class))->willReturn($defaultResponse); - - return $handler; - } - - foreach ($this->requests as $request) { - if (!$responses = $this->requests->getInfo()) { - $handler->handle($request)->willReturn($defaultResponse); - continue; - } - - $handler->handle($request)->willReturn(...$responses) - ->shouldBeCalledTimes(\count($responses)); - } - - return $handler; - } - - private function createConnection() : ObjectProphecy - { - return ($this->prophesize)(Connection::class); - } - - private function createPacker() // : ObjectProphecy - { - if ($this->packer) { - return $this->packer; - //return $this->prophet->prophesize($this->packer); - } - - return ($this->prophesize)(Packer::class); - } -} diff --git a/src/Client/MockClientBuilderOld.php.bak b/src/Client/MockClientBuilderOld.php.bak deleted file mode 100755 index 7b6b7d7..0000000 --- a/src/Client/MockClientBuilderOld.php.bak +++ /dev/null @@ -1,115 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace Tarantool\PhpUnit\Client; - -use Prophecy\Argument; -use Prophecy\Prophecy\ObjectProphecy; -use Prophecy\Prophet; -use Tarantool\Client\Client; -use Tarantool\Client\Connection\Connection; -use Tarantool\Client\Handler\Handler; -use Tarantool\Client\Packer\Packer; -use Tarantool\Client\Request\Request; - -final class MockClientBuilderOld -{ - /** @var Prophet */ - private $prophet; - private $requests; - private $packer; - - public function __construct() - { - $this->prophet = new Prophet(); - $this->requests = new \SplObjectStorage(); - } - - public static function buildDefault() : Client - { - return (new self())->build(); - } - - public function shouldHandle($request, ...$responses) : self - { - $this->requests->attach($request, $responses); - - return $this; - } - - public function withPacker($packer) : self - { - //$self = clone $this; - $this->packer = $packer; - - return $this; - } - - public function build() : Client - { - $handler = $this->createHandler(); - - return new Client($handler->reveal()); - } - - public function checkPredictions() : void - { - $this->prophet->checkPredictions(); - } - - private function createHandler() : ObjectProphecy - { - $handler = $this->prophet->prophesize(Handler::class); - - $connection = $this->createConnection(); - $handler->getConnection()->willReturn($connection); - - $packer = $this->createPacker(); - $handler->getPacker()->willReturn($packer); - - $defaultResponse = DummyFactory::createEmptyResponse(); - - if (!$this->requests->count()) { - $handler->handle(Argument::type(Request::class))->willReturn($defaultResponse); - - return $handler; - } - - foreach ($this->requests as $request) { - if (!$responses = $this->requests->getInfo()) { - $handler->handle($request)->willReturn($defaultResponse); - continue; - } - - $handler->handle($request)->willReturn(...$responses) - ->shouldBeCalledTimes(\count($responses)); - } - - return $handler; - } - - private function createConnection() : ObjectProphecy - { - return $this->prophet->prophesize(Connection::class); - } - - private function createPacker() // : ObjectProphecy - { - if ($this->packer) { - return $this->packer; - //return $this->prophet->prophesize($this->packer); - } - - return $this->prophet->prophesize(Packer::class); - } -} From e1b9eb63715b6b50d584683091bb0dd0666758cb Mon Sep 17 00:00:00 2001 From: Eugene Leonovich Date: Sat, 25 Jul 2026 20:44:44 +0200 Subject: [PATCH 11/14] Use no-api for composer VCS repositories to prevent GitHub rate limits --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 641dad4..b5c5609 100644 --- a/composer.json +++ b/composer.json @@ -43,6 +43,7 @@ "prefer-stable": true, "repositories": [{ "type": "vcs", - "url": "https://github.com/rybakit/phpunit-extras" + "url": "https://github.com/rybakit/phpunit-extras", + "no-api": true }] } From e73bed09289fba4681fc48a6d1b537ac5516f0b0 Mon Sep 17 00:00:00 2001 From: Eugene Leonovich Date: Sat, 25 Jul 2026 20:47:58 +0200 Subject: [PATCH 12/14] Fix code style issues for php-cs-fixer 3.0 --- .php-cs-fixer.dist.php | 2 +- src/Annotation/AnnotationExtension.php | 6 ++---- .../Requirement/LuaConditionRequirement.php | 2 +- .../Requirement/TarantoolVersionRequirement.php | 2 +- src/Client/IsRequestType.php | 4 ++-- src/Client/TestDoubleClientBuilder.php | 12 +++++++----- tests/Annotation/AnnotationExtensionTest.php | 1 + .../Requirement/LuaConditionRequirementTest.php | 2 +- .../Requirement/TarantoolVersionRequirementTest.php | 2 +- 9 files changed, 17 insertions(+), 16 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index da6bde7..992b9a0 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -15,7 +15,7 @@ file that was distributed with this source code. EOF; -return Config::create() +return (new Config()) ->setUsingCache(false) ->setRiskyAllowed(true) ->setRules([ diff --git a/src/Annotation/AnnotationExtension.php b/src/Annotation/AnnotationExtension.php index 834f6ef..e7e2352 100644 --- a/src/Annotation/AnnotationExtension.php +++ b/src/Annotation/AnnotationExtension.php @@ -27,8 +27,6 @@ class AnnotationExtension extends BaseAnnotationExtension /** @var Client|null */ private $client; - - #[\Override] public function bootstrap(\PHPUnit\TextUI\Configuration\Configuration $configuration, \PHPUnit\Runner\Extension\Facade $facade, \PHPUnit\Runner\Extension\ParameterCollection $parameters) : void { @@ -50,7 +48,7 @@ protected function parseParameters(\PHPUnit\Runner\Extension\ParameterCollection }, $parameters, \PHPUnit\Runner\Extension\ParameterCollection::class); $options = $closure ? $closure() : []; - if ($options !== []) { + if ([] !== $options) { $this->clientConfig = $options; } } @@ -98,7 +96,7 @@ private static function resolveEnvValues(string $configValue) : string return $value; } - $errorMessage = sprintf('Environment variable "%s" does not exist', $matches['name']); + $errorMessage = \sprintf('Environment variable "%s" does not exist', $matches['name']); throw new class($errorMessage) extends \RuntimeException implements Exception { }; }, $configValue); } diff --git a/src/Annotation/Requirement/LuaConditionRequirement.php b/src/Annotation/Requirement/LuaConditionRequirement.php index 8482496..976d586 100644 --- a/src/Annotation/Requirement/LuaConditionRequirement.php +++ b/src/Annotation/Requirement/LuaConditionRequirement.php @@ -40,6 +40,6 @@ public function check(string $value) : ?string return null; } - return sprintf('"%s" is not evaluated to true', $value); + return \sprintf('"%s" is not evaluated to true', $value); } } diff --git a/src/Annotation/Requirement/TarantoolVersionRequirement.php b/src/Annotation/Requirement/TarantoolVersionRequirement.php index 6487de6..5d3c2fa 100644 --- a/src/Annotation/Requirement/TarantoolVersionRequirement.php +++ b/src/Annotation/Requirement/TarantoolVersionRequirement.php @@ -45,7 +45,7 @@ public function check(string $value) : ?string return null; } - return sprintf('%s version %s is required', $this->getName(), $value); + return \sprintf('%s version %s is required', $this->getName(), $value); } private function getVersion() : string diff --git a/src/Client/IsRequestType.php b/src/Client/IsRequestType.php index 68bb713..141c510 100644 --- a/src/Client/IsRequestType.php +++ b/src/Client/IsRequestType.php @@ -25,7 +25,7 @@ final class IsRequestType extends Constraint public function __construct(int $requestType) { // needed for backward compatibility with PHPUnit 7 - if (\method_exists(parent::class, '__construct')) { + if (method_exists(parent::class, '__construct')) { parent::__construct(); } @@ -35,7 +35,7 @@ public function __construct(int $requestType) #[\Override] public function toString() : string { - return sprintf('is a "%s" request', strtoupper(RequestTypes::getName($this->requestType))); + return \sprintf('is a "%s" request', strtoupper(RequestTypes::getName($this->requestType))); } #[\Override] diff --git a/src/Client/TestDoubleClientBuilder.php b/src/Client/TestDoubleClientBuilder.php index a067baf..9ec42ef 100755 --- a/src/Client/TestDoubleClientBuilder.php +++ b/src/Client/TestDoubleClientBuilder.php @@ -41,7 +41,7 @@ final class TestDoubleClientBuilder private $packer; /** @var int|null */ - private $shouldBeCalledTimes = null; + private $shouldBeCalledTimes; public function __construct(TestCase $testCase) { @@ -53,6 +53,7 @@ public static function buildDummy() : Client { /** @psalm-suppress InternalMethod */ $self = new self(new class('dummy') extends TestCase {}); + return $self->build(); } @@ -135,16 +136,17 @@ private function createHandler() : MockObject $requests = $this->requests; $handleMocker->with(TestCase::callback(static function ($request) use (&$invocationCount, $requests) { if (!isset($requests[$invocationCount])) { - $invocationCount++; + ++$invocationCount; + return true; } $expected = $requests[$invocationCount++]; - + if ($expected instanceof Constraint) { return (bool) $expected->evaluate($request, '', true); } - - return $expected == $request; + + return $expected === $request; })); } diff --git a/tests/Annotation/AnnotationExtensionTest.php b/tests/Annotation/AnnotationExtensionTest.php index 821bec1..ca5fddc 100755 --- a/tests/Annotation/AnnotationExtensionTest.php +++ b/tests/Annotation/AnnotationExtensionTest.php @@ -23,6 +23,7 @@ private function bootstrapExtension(string $method, array $parameters = []) : An $ext = new AnnotationExtension(); $ext->doParseParameters($parameters); $ext->processTestAttributes(self::class, $method); + return $ext; } diff --git a/tests/Annotation/Requirement/LuaConditionRequirementTest.php b/tests/Annotation/Requirement/LuaConditionRequirementTest.php index 24096a2..b1f1076 100755 --- a/tests/Annotation/Requirement/LuaConditionRequirementTest.php +++ b/tests/Annotation/Requirement/LuaConditionRequirementTest.php @@ -50,7 +50,7 @@ public function testCheckFailsForFalsyExpression() : void ) ->build(); - $errorMessage = sprintf('"%s" is not evaluated to true', $luaExpression); + $errorMessage = \sprintf('"%s" is not evaluated to true', $luaExpression); $requirement = new LuaConditionRequirement($mockClient); self::assertSame($errorMessage, $requirement->check($luaExpression)); diff --git a/tests/Annotation/Requirement/TarantoolVersionRequirementTest.php b/tests/Annotation/Requirement/TarantoolVersionRequirementTest.php index 14ceb75..262709c 100755 --- a/tests/Annotation/Requirement/TarantoolVersionRequirementTest.php +++ b/tests/Annotation/Requirement/TarantoolVersionRequirementTest.php @@ -104,7 +104,7 @@ public function testCheckFailsForInvalidConstraints(string $serverVersion, strin ->build(); $requirement = new TarantoolVersionRequirement($mockClient); - $errorMessage = sprintf('Tarantool version %s is required', $constraints); + $errorMessage = \sprintf('Tarantool version %s is required', $constraints); self::assertSame($errorMessage, $requirement->check($constraints)); } From 84c984e09aba179af0b257c7e4848b69e7cc3380 Mon Sep 17 00:00:00 2001 From: Eugene Leonovich Date: Sun, 26 Jul 2026 11:31:33 +0200 Subject: [PATCH 13/14] Fix strict comparison in mock builder causing tests to fail --- src/Client/TestDoubleClientBuilder.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Client/TestDoubleClientBuilder.php b/src/Client/TestDoubleClientBuilder.php index 9ec42ef..5f37445 100755 --- a/src/Client/TestDoubleClientBuilder.php +++ b/src/Client/TestDoubleClientBuilder.php @@ -146,7 +146,9 @@ private function createHandler() : MockObject return (bool) $expected->evaluate($request, '', true); } - return $expected === $request; + $constraint = new \PHPUnit\Framework\Constraint\IsEqual($expected); + + return (bool) $constraint->evaluate($request, '', true); })); } From 042b8c16f71cc04247670136c1b66af912813446 Mon Sep 17 00:00:00 2001 From: Eugene Leonovich Date: Sun, 26 Jul 2026 11:43:05 +0200 Subject: [PATCH 14/14] Revert evaluate back to call for box.info --- .../Requirement/TarantoolVersionRequirement.php | 2 +- .../Requirement/TarantoolVersionRequirementTest.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Annotation/Requirement/TarantoolVersionRequirement.php b/src/Annotation/Requirement/TarantoolVersionRequirement.php index 5d3c2fa..dcb5d63 100644 --- a/src/Annotation/Requirement/TarantoolVersionRequirement.php +++ b/src/Annotation/Requirement/TarantoolVersionRequirement.php @@ -54,7 +54,7 @@ private function getVersion() : string return $this->version; } - $version = $this->client->evaluate('return box.info.version')[0]; + $version = $this->client->call('box.info')[0]['version']; if (!\is_string($version)) { throw new \UnexpectedValueException('Tarantool version must be a string'); } diff --git a/tests/Annotation/Requirement/TarantoolVersionRequirementTest.php b/tests/Annotation/Requirement/TarantoolVersionRequirementTest.php index 262709c..12a2808 100755 --- a/tests/Annotation/Requirement/TarantoolVersionRequirementTest.php +++ b/tests/Annotation/Requirement/TarantoolVersionRequirementTest.php @@ -15,7 +15,7 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; -use Tarantool\Client\Request\EvaluateRequest; +use Tarantool\Client\Request\CallRequest; use Tarantool\PhpUnit\Annotation\Requirement\TarantoolVersionRequirement; use Tarantool\PhpUnit\Client\TestDoubleClient; use Tarantool\PhpUnit\Client\TestDoubleFactory; @@ -29,8 +29,8 @@ public function testCheckPassesForValidConstraints(string $serverVersion, string { $mockClient = $this->getTestDoubleClientBuilder() ->shouldHandle( - new EvaluateRequest('return box.info.version'), - TestDoubleFactory::createResponseFromData([$serverVersion])) + new CallRequest('box.info'), + TestDoubleFactory::createResponseFromData([['version' => $serverVersion]])) ->build(); $requirement = new TarantoolVersionRequirement($mockClient); @@ -99,8 +99,8 @@ public function testCheckFailsForInvalidConstraints(string $serverVersion, strin { $mockClient = $this->getTestDoubleClientBuilder() ->shouldHandle( - new EvaluateRequest('return box.info.version'), - TestDoubleFactory::createResponseFromData([$serverVersion])) + new CallRequest('box.info'), + TestDoubleFactory::createResponseFromData([['version' => $serverVersion]])) ->build(); $requirement = new TarantoolVersionRequirement($mockClient);