diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c8fb916..7ca7a730 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,7 @@ jobs: name: PHPUnit (PHP ${{ matrix.php }} on ${{ matrix.os }}) runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: - ubuntu-20.04 diff --git a/composer.json b/composer.json index ec50942e..479bcefd 100644 --- a/composer.json +++ b/composer.json @@ -30,14 +30,14 @@ "evenement/evenement": "^3.0 || ^2.0 || ^1.0", "react/dns": "^1.8", "react/event-loop": "^1.2", - "react/promise": "^2.6.0 || ^1.2.1", - "react/promise-timer": "^1.8", + "react/promise": "^3@dev || ^2.6 || ^1.2.1", + "react/promise-timer": "^1.9", "react/stream": "^1.2" }, "require-dev": { "clue/block-react": "^1.5", "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", - "react/promise-stream": "^1.2" + "react/promise-stream": "^1.4" }, "autoload": { "psr-4": { @@ -48,5 +48,6 @@ "psr-4": { "React\\Tests\\Socket\\": "tests" } - } + }, + "minimum-stability": "dev" } diff --git a/src/DnsConnector.php b/src/DnsConnector.php index 29523ebf..dad7af32 100644 --- a/src/DnsConnector.php +++ b/src/DnsConnector.php @@ -4,7 +4,6 @@ use React\Dns\Resolver\ResolverInterface; use React\Promise; -use React\Promise\CancellablePromiseInterface; final class DnsConnector implements ConnectorInterface { @@ -103,7 +102,7 @@ function ($_, $reject) use (&$promise, &$resolved, $uri) { } // (try to) cancel pending DNS lookup / connection attempt - if ($promise instanceof CancellablePromiseInterface) { + if ($promise instanceof Promise\PromiseInterface && \method_exists($promise, 'cancel')) { // overwrite callback arguments for PHP7+ only, so they do not show // up in the Exception trace and do not cause a possible cyclic reference. $_ = $reject = null; diff --git a/src/HappyEyeBallsConnectionBuilder.php b/src/HappyEyeBallsConnectionBuilder.php index 6bd07168..5caf2206 100644 --- a/src/HappyEyeBallsConnectionBuilder.php +++ b/src/HappyEyeBallsConnectionBuilder.php @@ -7,7 +7,6 @@ use React\EventLoop\LoopInterface; use React\EventLoop\TimerInterface; use React\Promise; -use React\Promise\CancellablePromiseInterface; /** * @internal @@ -248,13 +247,13 @@ public function cleanUp() $this->connectQueue = array(); foreach ($this->connectionPromises as $connectionPromise) { - if ($connectionPromise instanceof CancellablePromiseInterface) { + if ($connectionPromise instanceof Promise\PromiseInterface && \method_exists($connectionPromise, 'cancel')) { $connectionPromise->cancel(); } } foreach ($this->resolverPromises as $resolverPromise) { - if ($resolverPromise instanceof CancellablePromiseInterface) { + if ($resolverPromise instanceof Promise\PromiseInterface && \method_exists($resolverPromise, 'cancel')) { $resolverPromise->cancel(); } } diff --git a/src/SecureConnector.php b/src/SecureConnector.php index a5087ca0..7679da46 100644 --- a/src/SecureConnector.php +++ b/src/SecureConnector.php @@ -43,13 +43,14 @@ public function connect($uri) $context = $this->context; $encryption = $this->streamEncryption; $connected = false; + /** @var array<\React\Promise\PromiseInterface> $promises */ + $promises = array(); /** @var \React\Promise\PromiseInterface $promise */ - $promise = $this->connector->connect( + $promise = $promises[] = $this->connector->connect( \str_replace('tls://', '', $uri) - )->then(function (ConnectionInterface $connection) use ($context, $encryption, $uri, &$promise, &$connected) { + )->then(function (ConnectionInterface $connection) use ($context, $encryption, $uri, &$promises, &$connected) { // (unencrypted) TCP/IP connection succeeded $connected = true; - if (!$connection instanceof Connection) { $connection->close(); throw new \UnexpectedValueException('Base connector does not use internal Connection class exposing stream resource'); @@ -61,7 +62,7 @@ public function connect($uri) } // try to enable encryption - return $promise = $encryption->enable($connection)->then(null, function ($error) use ($connection, $uri) { + return $promises[] = $encryption->enable($connection)->then(null, function ($error) use ($connection, $uri) { // establishing encryption failed => close invalid connection and return error $connection->close(); @@ -104,10 +105,10 @@ public function connect($uri) }); return new \React\Promise\Promise( - function ($resolve, $reject) use ($promise) { + function ($resolve, $reject) use (&$promise) { $promise->then($resolve, $reject); }, - function ($_, $reject) use (&$promise, $uri, &$connected) { + function ($_, $reject) use ($promise, &$promises, $uri, &$connected) { if ($connected) { $reject(new \RuntimeException( 'Connection to ' . $uri . ' cancelled during TLS handshake (ECONNABORTED)', @@ -115,8 +116,10 @@ function ($_, $reject) use (&$promise, $uri, &$connected) { )); } - $promise->cancel(); - $promise = null; + foreach ($promises as $promise) { + $promise->cancel(); + } + $promises = array(); } ); } diff --git a/src/StreamEncryption.php b/src/StreamEncryption.php index 4aa7fca0..b7aa3f24 100644 --- a/src/StreamEncryption.php +++ b/src/StreamEncryption.php @@ -115,7 +115,7 @@ public function toggleCrypto($socket, Deferred $deferred, $toggle, $method) \restore_error_handler(); if (true === $result) { - $deferred->resolve(); + $deferred->resolve(null); } else if (false === $result) { // overwrite callback arguments for PHP7+ only, so they do not show // up in the Exception trace and do not cause a possible cyclic reference. diff --git a/tests/DnsConnectorTest.php b/tests/DnsConnectorTest.php index 5d37a237..6054e7b1 100644 --- a/tests/DnsConnectorTest.php +++ b/tests/DnsConnectorTest.php @@ -26,7 +26,7 @@ public function setUpMocks() public function testPassByResolverIfGivenIp() { $this->resolver->expects($this->never())->method('resolve'); - $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('127.0.0.1:80'))->will($this->returnValue(Promise\reject())); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('127.0.0.1:80'))->will($this->returnValue(Promise\reject(new \Exception('reject')))); $this->connector->connect('127.0.0.1:80'); } @@ -34,7 +34,7 @@ public function testPassByResolverIfGivenIp() public function testPassThroughResolverIfGivenHost() { $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('1.2.3.4'))); - $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4:80?hostname=google.com'))->will($this->returnValue(Promise\reject())); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4:80?hostname=google.com'))->will($this->returnValue(Promise\reject(new \Exception('reject')))); $this->connector->connect('google.com:80'); } @@ -42,7 +42,7 @@ public function testPassThroughResolverIfGivenHost() public function testPassThroughResolverIfGivenHostWhichResolvesToIpv6() { $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('::1'))); - $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('[::1]:80?hostname=google.com'))->will($this->returnValue(Promise\reject())); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('[::1]:80?hostname=google.com'))->will($this->returnValue(Promise\reject(new \Exception('reject')))); $this->connector->connect('google.com:80'); } @@ -50,7 +50,7 @@ public function testPassThroughResolverIfGivenHostWhichResolvesToIpv6() public function testPassByResolverIfGivenCompleteUri() { $this->resolver->expects($this->never())->method('resolve'); - $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://127.0.0.1:80/path?query#fragment'))->will($this->returnValue(Promise\reject())); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://127.0.0.1:80/path?query#fragment'))->will($this->returnValue(Promise\reject(new \Exception('reject')))); $this->connector->connect('scheme://127.0.0.1:80/path?query#fragment'); } @@ -58,7 +58,7 @@ public function testPassByResolverIfGivenCompleteUri() public function testPassThroughResolverIfGivenCompleteUri() { $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('1.2.3.4'))); - $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://1.2.3.4:80/path?query&hostname=google.com#fragment'))->will($this->returnValue(Promise\reject())); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://1.2.3.4:80/path?query&hostname=google.com#fragment'))->will($this->returnValue(Promise\reject(new \Exception('reject')))); $this->connector->connect('scheme://google.com:80/path?query#fragment'); } @@ -66,7 +66,7 @@ public function testPassThroughResolverIfGivenCompleteUri() public function testPassThroughResolverIfGivenExplicitHost() { $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('1.2.3.4'))); - $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://1.2.3.4:80/?hostname=google.de'))->will($this->returnValue(Promise\reject())); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://1.2.3.4:80/?hostname=google.de'))->will($this->returnValue(Promise\reject(new \Exception('reject')))); $this->connector->connect('scheme://google.com:80/?hostname=google.de'); } @@ -288,7 +288,7 @@ public function testRejectionDuringDnsLookupShouldNotCreateAnyGarbageReferences( $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.com'))->willReturn($dns->promise()); $this->tcp->expects($this->never())->method('connect'); - $promise = $this->connector->connect('example.com:80'); + $promise = $this->connector->connect('example.com:80')->then(null, function () { }); $dns->reject(new \RuntimeException('DNS failed')); unset($promise, $dns); @@ -309,7 +309,7 @@ public function testRejectionAfterDnsLookupShouldNotCreateAnyGarbageReferences() $tcp = new Deferred(); $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4:80?hostname=example.com'))->willReturn($tcp->promise()); - $promise = $this->connector->connect('example.com:80'); + $promise = $this->connector->connect('example.com:80')->then(null, function () { }); $dns->resolve('1.2.3.4'); $tcp->reject(new \RuntimeException('Connection failed')); unset($promise, $dns, $tcp); @@ -334,7 +334,7 @@ public function testRejectionAfterDnsLookupShouldNotCreateAnyGarbageReferencesAg }); $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4:80?hostname=example.com'))->willReturn($tcp->promise()); - $promise = $this->connector->connect('example.com:80'); + $promise = $this->connector->connect('example.com:80')->then(null, function () { }); $dns->resolve('1.2.3.4'); unset($promise, $dns, $tcp); @@ -356,7 +356,7 @@ public function testCancelDuringDnsLookupShouldNotCreateAnyGarbageReferences() $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.com'))->willReturn($dns->promise()); $this->tcp->expects($this->never())->method('connect'); - $promise = $this->connector->connect('example.com:80'); + $promise = $this->connector->connect('example.com:80')->then(null, function () { }); $promise->cancel(); unset($promise, $dns); @@ -379,7 +379,7 @@ public function testCancelDuringTcpConnectionShouldNotCreateAnyGarbageReferences }); $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4:80?hostname=example.com'))->willReturn($tcp); - $promise = $this->connector->connect('example.com:80'); + $promise = $this->connector->connect('example.com:80')->then(function () { }, function () { }); $dns->resolve('1.2.3.4'); $promise->cancel(); diff --git a/tests/FunctionalConnectorTest.php b/tests/FunctionalConnectorTest.php index 767b92f0..70756704 100644 --- a/tests/FunctionalConnectorTest.php +++ b/tests/FunctionalConnectorTest.php @@ -143,7 +143,7 @@ public function testCancelPendingTlsConnectionDuringTlsHandshakeShouldCloseTcpCo $deferred = new Deferred(); $server->on('connection', function (ConnectionInterface $connection) use ($promise, $deferred) { $connection->on('close', function () use ($deferred) { - $deferred->resolve(); + $deferred->resolve(null); }); Loop::futureTick(function () use ($promise) { diff --git a/tests/FunctionalTcpServerTest.php b/tests/FunctionalTcpServerTest.php index 0965f90d..9a261129 100644 --- a/tests/FunctionalTcpServerTest.php +++ b/tests/FunctionalTcpServerTest.php @@ -18,7 +18,9 @@ public function testEmitsConnectionForNewConnection() $server->on('connection', $this->expectCallableOnce()); $peer = new Promise(function ($resolve, $reject) use ($server) { - $server->on('connection', $resolve); + $server->on('connection', function () use ($resolve) { + $resolve(null); + }); }); $connector = new TcpConnector(); @@ -57,7 +59,9 @@ public function testConnectionForNewConnectionWhenResumedAfterPause() $server->resume(); $peer = new Promise(function ($resolve, $reject) use ($server) { - $server->on('connection', $resolve); + $server->on('connection', function () use ($resolve) { + $resolve(null); + }); }); $connector = new TcpConnector(); @@ -207,7 +211,9 @@ public function testEmitsConnectionEvenIfClientConnectionIsCancelled() $server->on('connection', $this->expectCallableOnce()); $peer = new Promise(function ($resolve, $reject) use ($server) { - $server->on('connection', $resolve); + $server->on('connection', function () use ($resolve) { + $resolve(null); + }); }); $connector = new TcpConnector(); @@ -232,7 +238,9 @@ public function testEmitsConnectionForNewIpv6Connection() $server->on('connection', $this->expectCallableOnce()); $peer = new Promise(function ($resolve, $reject) use ($server) { - $server->on('connection', $resolve); + $server->on('connection', function () use ($resolve) { + $resolve(null); + }); }); $connector = new TcpConnector(); diff --git a/tests/HappyEyeBallsConnectionBuilderTest.php b/tests/HappyEyeBallsConnectionBuilderTest.php index 59b1c1fd..7c178124 100644 --- a/tests/HappyEyeBallsConnectionBuilderTest.php +++ b/tests/HappyEyeBallsConnectionBuilderTest.php @@ -2,6 +2,8 @@ namespace React\Tests\Socket; +use Clue\React\Block; +use React\EventLoop\Loop; use React\Promise\Promise; use React\Socket\HappyEyeBallsConnectionBuilder; use React\Dns\Model\Message; @@ -53,7 +55,6 @@ public function testConnectWillRejectWhenBothDnsLookupsReject() $parts = parse_url($uri); $builder = new HappyEyeBallsConnectionBuilder($loop, $connector, $resolver, $uri, $host, $parts); - $promise = $builder->connect(); $exception = null; @@ -257,8 +258,8 @@ public function testConnectWillStartConnectingWithAttemptTimerButWithoutResoluti $builder = new HappyEyeBallsConnectionBuilder($loop, $connector, $resolver, $uri, $host, $parts); - $builder->connect(); - $deferred->reject(new \RuntimeException()); + $promise = $builder->connect(); + $deferred->reject(new \RuntimeException('reject')); } public function testConnectWillStartConnectingWithAttemptTimerWhenIpv6AndIpv4ResolvesAndWillStartNextConnectionAttemptWithoutAttemptTimerImmediatelyWhenFirstConnectionAttemptFails() @@ -302,8 +303,8 @@ public function testConnectWillStartConnectingWithAlternatingIPv6AndIPv4WhenReso { $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock(); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $loop->expects($this->once())->method('addTimer')->with(0.1, $this->anything())->willReturn($timer); - $loop->expects($this->once())->method('cancelTimer')->with($timer); + $loop->expects($this->any())->method('addTimer')->with(0.1, $this->anything())->willReturn($timer); + $loop->expects($this->any())->method('cancelTimer')->with($timer); $deferred = new Deferred(); $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock(); @@ -334,17 +335,17 @@ public function testConnectWillStartConnectingWithAlternatingIPv6AndIPv4WhenReso $builder = new HappyEyeBallsConnectionBuilder($loop, $connector, $resolver, $uri, $host, $parts); - $builder->connect(); + $promise = $builder->connect(); - $deferred->reject(new \RuntimeException()); + $deferred->reject(new \RuntimeException('reject')); } public function testConnectWillStartConnectingWithAttemptTimerWhenOnlyIpv6ResolvesAndWillStartNextConnectionAttemptWithoutAttemptTimerImmediatelyWhenFirstConnectionAttemptFails() { $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock(); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $loop->expects($this->once())->method('addTimer')->with(0.1, $this->anything())->willReturn($timer); - $loop->expects($this->once())->method('cancelTimer')->with($timer); + $loop->expects($this->any())->method('addTimer')->with(0.1, $this->anything())->willReturn($timer); + $loop->expects($this->any())->method('cancelTimer')->with($timer); $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock(); $connector->expects($this->exactly(2))->method('connect')->withConsecutive( @@ -361,7 +362,7 @@ public function testConnectWillStartConnectingWithAttemptTimerWhenOnlyIpv6Resolv array('reactphp.org', Message::TYPE_A) )->willReturnOnConsecutiveCalls( \React\Promise\resolve(array('::1', '::1')), - \React\Promise\reject(new \RuntimeException()) + \React\Promise\reject(new \RuntimeException('reject')) ); $uri = 'tcp://reactphp.org:80'; @@ -370,21 +371,21 @@ public function testConnectWillStartConnectingWithAttemptTimerWhenOnlyIpv6Resolv $builder = new HappyEyeBallsConnectionBuilder($loop, $connector, $resolver, $uri, $host, $parts); - $builder->connect(); + $builder->connect()->then(); } public function testConnectWillStartConnectingAndWillStartNextConnectionWithoutNewAttemptTimerWhenNextAttemptTimerFiresAfterIpv4Rejected() { $timer = null; $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $loop->expects($this->once())->method('addTimer')->with(0.1, $this->callback(function ($cb) use (&$timer) { + $loop->expects($this->any())->method('addTimer')->with(0.1, $this->callback(function ($cb) use (&$timer) { $timer = $cb; return true; })); $loop->expects($this->never())->method('cancelTimer'); $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock(); - $connector->expects($this->exactly(2))->method('connect')->willReturn(new Promise(function () { })); + $connector->expects($this->any())->method('connect')->willReturn(new Promise(function () { })); $deferred = new Deferred(); $resolver = $this->getMockBuilder('React\Dns\Resolver\ResolverInterface')->getMock(); @@ -403,7 +404,7 @@ public function testConnectWillStartConnectingAndWillStartNextConnectionWithoutN $builder = new HappyEyeBallsConnectionBuilder($loop, $connector, $resolver, $uri, $host, $parts); $builder->connect(); - $deferred->reject(new \RuntimeException()); + $deferred->reject(new \RuntimeException('reject')); $this->assertNotNull($timer); $timer(); @@ -663,7 +664,7 @@ public function testCancelConnectWillRejectPromiseAndCancelBothDnsLookups() $builder = new HappyEyeBallsConnectionBuilder($loop, $connector, $resolver, $uri, $host, $parts); - $promise = $builder->connect(); + $promise = $builder->connect()->then(); $promise->cancel(); $this->assertEquals(2, $cancelled); diff --git a/tests/HappyEyeBallsConnectorTest.php b/tests/HappyEyeBallsConnectorTest.php index 6a26fd63..5df85d9b 100644 --- a/tests/HappyEyeBallsConnectorTest.php +++ b/tests/HappyEyeBallsConnectorTest.php @@ -58,7 +58,7 @@ public function testHappyFlow() $first = new Deferred(); $this->resolver->expects($this->exactly(2))->method('resolveAll')->with($this->equalTo('example.com'), $this->anything())->willReturn($first->promise()); $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock(); - $this->tcp->expects($this->exactly(1))->method('connect')->with($this->equalTo('1.2.3.4:80?hostname=example.com'))->willReturn(Promise\resolve($connection)); + $this->tcp->expects($this->any())->method('connect')->with($this->equalTo('1.2.3.4:80?hostname=example.com'))->willReturn(Promise\resolve($connection)); $promise = $this->connector->connect('example.com:80'); $first->resolve(array('1.2.3.4')); @@ -97,7 +97,7 @@ public function testThatAnyOtherPendingConnectionAttemptsWillBeCanceledOnceAConn public function testPassByResolverIfGivenIp() { $this->resolver->expects($this->never())->method('resolveAll'); - $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('127.0.0.1:80'))->will($this->returnValue(Promise\resolve())); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('127.0.0.1:80'))->will($this->returnValue(Promise\resolve(null))); $this->connector->connect('127.0.0.1:80'); @@ -107,61 +107,61 @@ public function testPassByResolverIfGivenIp() public function testPassByResolverIfGivenIpv6() { $this->resolver->expects($this->never())->method('resolveAll'); - $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('[::1]:80'))->will($this->returnValue(Promise\reject())); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('[::1]:80'))->will($this->returnValue(Promise\reject(new \Exception('reject')))); - $this->connector->connect('[::1]:80'); + $this->connector->connect('[::1]:80')->then(null, function () { }); $this->loop->run(); } public function testPassThroughResolverIfGivenHost() { - $this->resolver->expects($this->exactly(2))->method('resolveAll')->with($this->equalTo('google.com'), $this->anything())->will($this->returnValue(Promise\resolve(array('1.2.3.4')))); - $this->tcp->expects($this->exactly(2))->method('connect')->with($this->equalTo('1.2.3.4:80?hostname=google.com'))->will($this->returnValue(Promise\reject())); + $this->setExpectedException('RuntimeException', 'Connection to tcp://google.com:80 failed: Last error for IPv4: reject. Previous error for IPv6:'); - $this->connector->connect('google.com:80'); + $this->resolver->expects($this->exactly(2))->method('resolveAll')->with($this->equalTo('google.com'), $this->anything())->will($this->returnValue(Promise\resolve(array('1.2.3.4')))); + $this->tcp->expects($this->exactly(2))->method('connect')->with($this->equalTo('1.2.3.4:80?hostname=google.com'))->will($this->returnValue(Promise\reject(new \Exception('reject')))); - $this->loop->run(); + Block\await($this->connector->connect('google.com:80'), $this->loop); } public function testPassThroughResolverIfGivenHostWhichResolvesToIpv6() { - $this->resolver->expects($this->exactly(2))->method('resolveAll')->with($this->equalTo('google.com'), $this->anything())->will($this->returnValue(Promise\resolve(array('::1')))); - $this->tcp->expects($this->exactly(2))->method('connect')->with($this->equalTo('[::1]:80?hostname=google.com'))->will($this->returnValue(Promise\reject())); + $this->setExpectedException('RuntimeException', 'Connection to tcp://google.com:80 failed: Last error for IPv6: reject. Previous error for IPv4:'); - $this->connector->connect('google.com:80'); + $this->resolver->expects($this->exactly(2))->method('resolveAll')->with($this->equalTo('google.com'), $this->anything())->will($this->returnValue(Promise\resolve(array('::1')))); + $this->tcp->expects($this->exactly(2))->method('connect')->with($this->equalTo('[::1]:80?hostname=google.com'))->will($this->returnValue(Promise\reject(new \Exception('reject')))); - $this->loop->run(); + Block\await($this->connector->connect('google.com:80'), $this->loop); } public function testPassByResolverIfGivenCompleteUri() { $this->resolver->expects($this->never())->method('resolveAll'); - $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://127.0.0.1:80/path?query#fragment'))->will($this->returnValue(Promise\reject())); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://127.0.0.1:80/path?query#fragment'))->will($this->returnValue(Promise\reject(new \Exception('reject')))); - $this->connector->connect('scheme://127.0.0.1:80/path?query#fragment'); + $this->connector->connect('scheme://127.0.0.1:80/path?query#fragment')->then(null, function () { }); $this->loop->run(); } public function testPassThroughResolverIfGivenCompleteUri() { - $this->resolver->expects($this->exactly(2))->method('resolveAll')->with($this->equalTo('google.com'), $this->anything())->will($this->returnValue(Promise\resolve(array('1.2.3.4')))); - $this->tcp->expects($this->exactly(2))->method('connect')->with($this->equalTo('scheme://1.2.3.4:80/path?query&hostname=google.com#fragment'))->will($this->returnValue(Promise\reject())); + $this->setExpectedException('RuntimeException', 'Connection to scheme://google.com:80/path?query#fragment failed: Last error for IPv4: reject. Previous error for IPv6:'); - $this->connector->connect('scheme://google.com:80/path?query#fragment'); + $this->resolver->expects($this->exactly(2))->method('resolveAll')->with($this->equalTo('google.com'), $this->anything())->will($this->returnValue(Promise\resolve(array('1.2.3.4')))); + $this->tcp->expects($this->exactly(2))->method('connect')->with($this->equalTo('scheme://1.2.3.4:80/path?query&hostname=google.com#fragment'))->will($this->returnValue(Promise\reject(new \Exception('reject')))); - $this->loop->run(); + Block\await($this->connector->connect('scheme://google.com:80/path?query#fragment'), $this->loop); } public function testPassThroughResolverIfGivenExplicitHost() { - $this->resolver->expects($this->exactly(2))->method('resolveAll')->with($this->equalTo('google.com'), $this->anything())->will($this->returnValue(Promise\resolve(array('1.2.3.4')))); - $this->tcp->expects($this->exactly(2))->method('connect')->with($this->equalTo('scheme://1.2.3.4:80/?hostname=google.de'))->will($this->returnValue(Promise\reject())); + $this->setExpectedException('RuntimeException', 'Connection to scheme://google.com:80/?hostname=google.de failed: Last error for IPv4: reject. Previous error for IPv6:'); - $this->connector->connect('scheme://google.com:80/?hostname=google.de'); + $this->resolver->expects($this->exactly(2))->method('resolveAll')->with($this->equalTo('google.com'), $this->anything())->will($this->returnValue(Promise\resolve(array('1.2.3.4')))); + $this->tcp->expects($this->exactly(2))->method('connect')->with($this->equalTo('scheme://1.2.3.4:80/?hostname=google.de'))->will($this->returnValue(Promise\reject(new \Exception('reject')))); - $this->loop->run(); + Block\await($this->connector->connect('scheme://google.com:80/?hostname=google.de'), $this->loop); } /** @@ -178,12 +178,12 @@ public function testIpv6ResolvesFirstSoIsTheFirstToConnect(array $ipv6, array $i $this->returnValue(Promise\resolve($ipv6)), $this->returnValue($deferred->promise()) ); - $this->tcp->expects($this->any())->method('connect')->with($this->stringContains(']:80/?hostname=google.com'))->will($this->returnValue(Promise\reject())); + $this->tcp->expects($this->any())->method('connect')->with($this->stringContains(']:80/?hostname=google.com'))->will($this->returnValue(new Promise\Promise(function () { }))); - $this->connector->connect('scheme://google.com:80/?hostname=google.com'); + $this->connector->connect('scheme://google.com:80/?hostname=google.com')->then(null, function () { }); $this->loop->addTimer(0.07, function () use ($deferred) { - $deferred->reject(new \RuntimeException()); + $deferred->reject(new \Exception('deferred-reject')); }); $this->loop->run(); @@ -203,12 +203,12 @@ public function testIpv6DoesntResolvesWhileIpv4DoesFirstSoIpv4Connects(array $ip $this->returnValue($deferred->promise()), $this->returnValue(Promise\resolve($ipv4)) ); - $this->tcp->expects($this->any())->method('connect')->with($this->stringContains(':80/?hostname=google.com'))->will($this->returnValue(Promise\reject())); + $this->tcp->expects($this->any())->method('connect')->with($this->stringContains(':80/?hostname=google.com'))->will($this->returnValue(Promise\reject(new \Exception('reject')))); - $this->connector->connect('scheme://google.com:80/?hostname=google.com'); + $this->connector->connect('scheme://google.com:80/?hostname=google.com')->then(null, function () { }); $this->loop->addTimer(0.07, function () use ($deferred) { - $deferred->reject(new \RuntimeException()); + $deferred->reject(new \Exception('reject')); }); $this->loop->run(); @@ -271,10 +271,8 @@ public function testCancelDuringDnsCancelsDnsAndDoesNotStartTcpConnection() $this->tcp->expects($this->never())->method('connect'); $promise = $this->connector->connect('example.com:80'); - $this->loop->addTimer(0.05, function () use ($that, $promise) { + $this->loop->addTimer(0.05, function () use ($promise) { $promise->cancel(); - - $that->throwRejection($promise); }); $this->setExpectedException( @@ -283,6 +281,7 @@ public function testCancelDuringDnsCancelsDnsAndDoesNotStartTcpConnection() \defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103 ); $this->loop->run(); + $this->throwRejection($promise); } public function testCancelDuringTcpConnectionCancelsTcpConnectionIfGivenIp() diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index faa91294..a8bc0396 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -120,7 +120,7 @@ public function testCancellingPendingConnectionWithoutTimeoutShouldNotCreateAnyG gc_collect_cycles(); gc_collect_cycles(); // clear twice to avoid leftovers in PHP 7.4 with ext-xdebug and code coverage turned on - $promise = $connector->connect('8.8.8.8:80'); + $promise = $connector->connect('8.8.8.8:80')->then(function () { }, function () { }); $promise->cancel(); unset($promise); @@ -136,7 +136,7 @@ public function testCancellingPendingConnectionShouldNotCreateAnyGarbageReferenc $connector = new Connector(array()); gc_collect_cycles(); - $promise = $connector->connect('8.8.8.8:80'); + $promise = $connector->connect('8.8.8.8:80')->then(function () { }, function () { }); $promise->cancel(); unset($promise); @@ -160,7 +160,7 @@ function ($e) use (&$wait) { $wait = false; throw $e; } - ); + )->then(function () { }, function () { }); // run loop for short period to ensure we detect connection refused error Block\sleep(0.01); @@ -195,7 +195,7 @@ function ($e) use (&$wait) { $wait = false; throw $e; } - ); + )->then(function () { }, function () { }); // run loop for short period to ensure we detect a connection timeout error Block\sleep(0.01); @@ -227,7 +227,7 @@ function ($e) use (&$wait) { $wait = false; throw $e; } - ); + )->then(function () { }, function () { }); // run loop for short period to ensure we detect a connection timeout error Block\sleep(0.01); @@ -259,7 +259,7 @@ function ($e) use (&$wait) { $wait = false; throw $e; } - ); + )->then(function () { }, function () { }); // run loop for short period to ensure we detect a DNS error Block\sleep(0.01); @@ -301,7 +301,7 @@ function ($e) use (&$wait) { $wait = false; throw $e; } - ); + )->then(function () { }, function () { }); // run loop for short period to ensure we detect a TLS error Block\sleep(0.1); diff --git a/tests/LimitingServerTest.php b/tests/LimitingServerTest.php index 119fba40..e3434d77 100644 --- a/tests/LimitingServerTest.php +++ b/tests/LimitingServerTest.php @@ -153,7 +153,9 @@ public function testSocketDisconnectionWillRemoveFromList() $peer = new Promise(function ($resolve, $reject) use ($server) { $server->on('connection', function (ConnectionInterface $connection) use ($resolve) { - $connection->on('close', $resolve); + $connection->on('close', function () use ($resolve) { + $resolve(null); + }); }); }); @@ -172,7 +174,9 @@ public function testPausingServerWillEmitOnlyOneButAcceptTwoConnectionsDueToOper $server->on('error', $this->expectCallableNever()); $peer = new Promise(function ($resolve, $reject) use ($server) { - $server->on('connection', $resolve); + $server->on('connection', function () use ($resolve) { + $resolve(null); + }); }); $first = stream_socket_client($server->getAddress()); @@ -198,7 +202,7 @@ public function testPausingServerWillEmitTwoConnectionsFromBacklog() ++$connections; if ($connections >= 2) { - $resolve(); + $resolve(null); } }); }); diff --git a/tests/SecureConnectorTest.php b/tests/SecureConnectorTest.php index 591012e5..f6c0f67e 100644 --- a/tests/SecureConnectorTest.php +++ b/tests/SecureConnectorTest.php @@ -2,6 +2,8 @@ namespace React\Tests\Socket; +use Clue\React\Block; +use React\EventLoop\Loop; use React\Promise; use React\Promise\Deferred; use React\Socket\SecureConnector; @@ -261,7 +263,7 @@ public function testRejectionDuringConnectionShouldNotCreateAnyGarbageReferences $tcp = new Deferred(); $this->tcp->expects($this->once())->method('connect')->willReturn($tcp->promise()); - $promise = $this->connector->connect('example.com:80'); + $promise = $this->connector->connect('example.com:80')->then(function () { }, function () { }); $tcp->reject(new \RuntimeException()); unset($promise, $tcp); @@ -289,7 +291,7 @@ public function testRejectionDuringTlsHandshakeShouldNotCreateAnyGarbageReferenc $ref->setAccessible(true); $ref->setValue($this->connector, $encryption); - $promise = $this->connector->connect('example.com:80'); + $promise = $this->connector->connect('example.com:80')->then(function () { }, function () { }); $tcp->resolve($connection); $tls->reject(new \RuntimeException()); unset($promise, $tcp, $tls); diff --git a/tests/ServerTest.php b/tests/ServerTest.php index 7c6af61d..a3ec3887 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -123,7 +123,9 @@ public function testEmitsConnectionForNewConnection() $server->on('connection', $this->expectCallableOnce()); $peer = new Promise(function ($resolve, $reject) use ($server) { - $server->on('connection', $resolve); + $server->on('connection', function () use ($resolve) { + $resolve(null); + }); }); $client = stream_socket_client($server->getAddress()); @@ -151,7 +153,9 @@ public function testDoesEmitConnectionForNewConnectionToResumedServer() $server->on('connection', $this->expectCallableOnce()); $peer = new Promise(function ($resolve, $reject) use ($server) { - $server->on('connection', $resolve); + $server->on('connection', function () use ($resolve) { + $resolve(null); + }); }); $client = stream_socket_client($server->getAddress()); diff --git a/tests/SocketServerTest.php b/tests/SocketServerTest.php index 0011fbaa..b6a8fb28 100644 --- a/tests/SocketServerTest.php +++ b/tests/SocketServerTest.php @@ -158,7 +158,9 @@ public function testEmitsConnectionForNewConnection() $socket->on('connection', $this->expectCallableOnce()); $peer = new Promise(function ($resolve, $reject) use ($socket) { - $socket->on('connection', $resolve); + $socket->on('connection', function () use ($resolve) { + $resolve(null); + }); }); $client = stream_socket_client($socket->getAddress()); @@ -186,7 +188,9 @@ public function testDoesEmitConnectionForNewConnectionToResumedServer() $socket->on('connection', $this->expectCallableOnce()); $peer = new Promise(function ($resolve, $reject) use ($socket) { - $socket->on('connection', $resolve); + $socket->on('connection', function () use ($resolve) { + $resolve(null); + }); }); $client = stream_socket_client($socket->getAddress()); diff --git a/tests/TcpConnectorTest.php b/tests/TcpConnectorTest.php index 9fc2fd4b..5cb73aa6 100644 --- a/tests/TcpConnectorTest.php +++ b/tests/TcpConnectorTest.php @@ -323,7 +323,7 @@ public function cancellingConnectionShouldRemoveResourceFromLoopAndCloseResource $server->on('connection', $this->expectCallableNever()); $loop->expects($this->once())->method('addWriteStream'); - $promise = $connector->connect($server->getAddress()); + $promise = $connector->connect($server->getAddress())->then(function () { }, function () { }); $resource = null; $valid = false; @@ -348,6 +348,8 @@ public function cancellingConnectionShouldRejectPromise() $server = new TcpServer(0); + $this->setExpectedException('RuntimeException', 'Connection to ' . $server->getAddress() . ' cancelled during TCP/IP handshake'); + $promise = $connector->connect($server->getAddress()); $promise->cancel(); @@ -376,7 +378,7 @@ public function testCancelDuringConnectionShouldNotCreateAnyGarbageReferences() $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $connector = new TcpConnector($loop); - $promise = $connector->connect('127.0.0.1:9999'); + $promise = $connector->connect('127.0.0.1:9999')->then(function () { }, function () { }); $promise->cancel(); unset($promise); diff --git a/tests/TimeoutConnectorTest.php b/tests/TimeoutConnectorTest.php index 806b16c5..16173253 100644 --- a/tests/TimeoutConnectorTest.php +++ b/tests/TimeoutConnectorTest.php @@ -59,7 +59,7 @@ public function testRejectsWithOriginalReasonWhenConnectorRejects() public function testResolvesWhenConnectorResolves() { - $promise = Promise\resolve(); + $promise = Promise\resolve(null); $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock(); $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise)); @@ -120,7 +120,7 @@ public function testRejectionDuringConnectionShouldNotCreateAnyGarbageReferences $timeout = new TimeoutConnector($connector, 0.01); - $promise = $timeout->connect('example.com:80'); + $promise = $timeout->connect('example.com:80')->then(function () { }, function () { }); $connection->reject(new \RuntimeException('Connection failed')); unset($promise, $connection); @@ -143,7 +143,7 @@ public function testRejectionDueToTimeoutShouldNotCreateAnyGarbageReferences() $timeout = new TimeoutConnector($connector, 0); - $promise = $timeout->connect('example.com:80'); + $promise = $timeout->connect('example.com:80')->then(function () { }, function () { }); Loop::run(); unset($promise, $connection);