diff --git a/composer.json b/composer.json index 5a621910..e181d21c 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,7 @@ "laminas/laminas-servicemanager": "^3.7", "phpunit/phpunit": "^9.5.5", "psalm/plugin-phpunit": "^0.15.1", + "symfony/process": "^5.3.7", "vimeo/psalm": "^4.7" }, "suggest": { diff --git a/composer.lock b/composer.lock index 85bc1461..cd845781 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1a7c0799aa0d9458ce5b1e553f90fc4e", + "content-hash": "5831a1c6e28db10afe471ae508c67b81", "packages": [ { "name": "container-interop/container-interop", @@ -1324,16 +1324,16 @@ }, { "name": "laminas/laminas-db", - "version": "2.13.3", + "version": "2.13.4", "source": { "type": "git", "url": "https://github.com/laminas/laminas-db.git", - "reference": "e1bcf243f6e56f02590f11a149cd75403e873241" + "reference": "cdabb4bfa669c2c0edb0cb4e014c15b41afd3fb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-db/zipball/e1bcf243f6e56f02590f11a149cd75403e873241", - "reference": "e1bcf243f6e56f02590f11a149cd75403e873241", + "url": "https://api.github.com/repos/laminas/laminas-db/zipball/cdabb4bfa669c2c0edb0cb4e014c15b41afd3fb1", + "reference": "cdabb4bfa669c2c0edb0cb4e014c15b41afd3fb1", "shasum": "" }, "require": { @@ -1391,7 +1391,7 @@ "type": "community_bridge" } ], - "time": "2021-09-19T07:38:14+00:00" + "time": "2021-09-21T18:59:44+00:00" }, { "name": "laminas/laminas-math", @@ -4238,6 +4238,68 @@ ], "time": "2021-07-28T13:41:28+00:00" }, + { + "name": "symfony/process", + "version": "v5.3.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "38f26c7d6ed535217ea393e05634cb0b244a1967" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/38f26c7d6ed535217ea393e05634cb0b244a1967", + "reference": "38f26c7d6ed535217ea393e05634cb0b244a1967", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.3.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-08-04T21:20:46+00:00" + }, { "name": "symfony/service-contracts", "version": "v2.4.0", diff --git a/psalm-baseline.xml b/psalm-baseline.xml index f7abf843..8cd51f52 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -2751,6 +2751,12 @@ $sender + + + + $type + + ProtocolTrait::class diff --git a/src/Protocol/AbstractProtocol.php b/src/Protocol/AbstractProtocol.php index 394223b0..304dfe15 100644 --- a/src/Protocol/AbstractProtocol.php +++ b/src/Protocol/AbstractProtocol.php @@ -290,7 +290,7 @@ protected function _receive($timeout = null) // Check meta data to ensure connection is still valid $info = stream_get_meta_data($this->socket); - if (! $info['timed_out']) { + if ($info['timed_out']) { throw new Exception\RuntimeException($this->host . ' has timed out'); } diff --git a/test/Protocol/AbstractProtocolTest.php b/test/Protocol/AbstractProtocolTest.php new file mode 100644 index 00000000..dbf6a7ac --- /dev/null +++ b/test/Protocol/AbstractProtocolTest.php @@ -0,0 +1,81 @@ + + */ +final class AbstractProtocolTest extends TestCase +{ + /** @var Process */ + private $process; + + protected function setUp(): void + { + $this->process = new Process([ + PHP_BINARY, + '-S', + '127.0.0.1:8080', + '-t', + __DIR__ . '/HttpStatusService', + ]); + $this->process->start(); + $this->process->waitUntil(static function (string $type, string $output): bool { + return false !== strpos($output, 'started'); + }); + } + + protected function tearDown(): void + { + $this->process->stop(); + } + + /** + * @requires PHP >= 7.4 + */ + public function testExceptionShouldBeRaisedWhenConnectionHasTimedOut(): void + { + $protocol = new class('127.0.0.1', 8080) extends AbstractProtocol { + use ProtocolTrait; + + public function connect(): void + { + $this->_disconnect(); + $this->socket = $this->setupSocket('tcp', $this->host, $this->port, 2); + } + + public function send(string $path, ?int $readTimeout): string + { + $this->_send('GET ' . $path . ' HTTP/1.1'); + $this->_send('Host: ' . $this->host); + $this->_send(''); + + return $this->_receive($readTimeout); + } + }; + + $protocol->connect(); + self::assertSame('HTTP/1.1 200 OK' . AbstractProtocol::EOL, $protocol->send('/', null)); + + $protocol->connect(); + $this->expectExceptionObject(new \Laminas\Mail\Protocol\Exception\RuntimeException('127.0.0.1 has timed out')); + $protocol->send('/?sleep=3', 1); + } +} diff --git a/test/Protocol/HttpStatusService/index.php b/test/Protocol/HttpStatusService/index.php new file mode 100644 index 00000000..cfc31d48 --- /dev/null +++ b/test/Protocol/HttpStatusService/index.php @@ -0,0 +1,7 @@ +