@@ -217,6 +217,77 @@ public function testConnectWillStartConnectingWithAttemptTimerButWithoutResoluti
217217 $ deferred ->reject (new \RuntimeException ());
218218 }
219219
220+ public function testConnectWillStartConnectingWithAttemptTimerWhenIpv6AndIpv4ResolvesAndWillStartNextConnectionAttemptWithoutAttemptTimerImmediatelyWhenFirstConnectionAttemptFails ()
221+ {
222+ $ timer = $ this ->getMockBuilder ('React\EventLoop\TimerInterface ' )->getMock ();
223+ $ loop = $ this ->getMockBuilder ('React\EventLoop\LoopInterface ' )->getMock ();
224+ $ loop ->expects ($ this ->once ())->method ('addTimer ' )->with (0.1 , $ this ->anything ())->willReturn ($ timer );
225+ $ loop ->expects ($ this ->once ())->method ('cancelTimer ' )->with ($ timer );
226+
227+ $ deferred = new Deferred ();
228+ $ connector = $ this ->getMockBuilder ('React\Socket\ConnectorInterface ' )->getMock ();
229+ $ connector ->expects ($ this ->exactly (2 ))->method ('connect ' )->withConsecutive (
230+ array ('tcp://[::1]:80?hostname=reactphp.org ' ),
231+ array ('tcp://127.0.0.1:80?hostname=reactphp.org ' )
232+ )->willReturnOnConsecutiveCalls (
233+ $ deferred ->promise (),
234+ new Promise (function () { })
235+ );
236+
237+ $ resolver = $ this ->getMockBuilder ('React\Dns\Resolver\ResolverInterface ' )->getMock ();
238+ $ resolver ->expects ($ this ->exactly (2 ))->method ('resolveAll ' )->withConsecutive (
239+ array ('reactphp.org ' , Message::TYPE_AAAA ),
240+ array ('reactphp.org ' , Message::TYPE_A )
241+ )->willReturnOnConsecutiveCalls (
242+ \React \Promise \resolve (array ('::1 ' )),
243+ \React \Promise \resolve (array ('127.0.0.1 ' ))
244+ );
245+
246+ $ uri = 'tcp://reactphp.org:80 ' ;
247+ $ host = 'reactphp.org ' ;
248+ $ parts = parse_url ($ uri );
249+
250+ $ builder = new HappyEyeBallsConnectionBuilder ($ loop , $ connector , $ resolver , $ uri , $ host , $ parts );
251+
252+ $ builder ->connect ();
253+
254+ $ deferred ->reject (new \RuntimeException ());
255+ }
256+
257+ public function testConnectWillStartConnectingWithAttemptTimerWhenOnlyIpv6ResolvesAndWillStartNextConnectionAttemptWithoutAttemptTimerImmediatelyWhenFirstConnectionAttemptFails ()
258+ {
259+ $ timer = $ this ->getMockBuilder ('React\EventLoop\TimerInterface ' )->getMock ();
260+ $ loop = $ this ->getMockBuilder ('React\EventLoop\LoopInterface ' )->getMock ();
261+ $ loop ->expects ($ this ->once ())->method ('addTimer ' )->with (0.1 , $ this ->anything ())->willReturn ($ timer );
262+ $ loop ->expects ($ this ->once ())->method ('cancelTimer ' )->with ($ timer );
263+
264+ $ connector = $ this ->getMockBuilder ('React\Socket\ConnectorInterface ' )->getMock ();
265+ $ connector ->expects ($ this ->exactly (2 ))->method ('connect ' )->withConsecutive (
266+ array ('tcp://[::1]:80?hostname=reactphp.org ' ),
267+ array ('tcp://[::2]:80?hostname=reactphp.org ' )
268+ )->willReturnOnConsecutiveCalls (
269+ \React \Promise \reject (new \RuntimeException ()),
270+ new Promise (function () { })
271+ );
272+
273+ $ resolver = $ this ->getMockBuilder ('React\Dns\Resolver\ResolverInterface ' )->getMock ();
274+ $ resolver ->expects ($ this ->exactly (2 ))->method ('resolveAll ' )->withConsecutive (
275+ array ('reactphp.org ' , Message::TYPE_AAAA ),
276+ array ('reactphp.org ' , Message::TYPE_A )
277+ )->willReturnOnConsecutiveCalls (
278+ \React \Promise \resolve (array ('::1 ' , '::2 ' )),
279+ \React \Promise \reject (new \RuntimeException ())
280+ );
281+
282+ $ uri = 'tcp://reactphp.org:80 ' ;
283+ $ host = 'reactphp.org ' ;
284+ $ parts = parse_url ($ uri );
285+
286+ $ builder = new HappyEyeBallsConnectionBuilder ($ loop , $ connector , $ resolver , $ uri , $ host , $ parts );
287+
288+ $ builder ->connect ();
289+ }
290+
220291 public function testConnectWillStartConnectingAndWillStartNextConnectionWithoutNewAttemptTimerWhenNextAttemptTimerFiresAfterIpv4Rejected ()
221292 {
222293 $ timer = null ;
@@ -492,4 +563,89 @@ public function testAttemptConnectionWillConnectViaConnectorToGivenIpv6WithAllUr
492563
493564 $ builder ->attemptConnection ('::1 ' );
494565 }
566+
567+ public function testCheckCallsRejectFunctionImmediateWithoutLeavingDanglingPromiseWhenConnectorRejectsImmediately ()
568+ {
569+ $ loop = $ this ->getMockBuilder ('React\EventLoop\LoopInterface ' )->getMock ();
570+
571+ $ connector = $ this ->getMockBuilder ('React\Socket\ConnectorInterface ' )->getMock ();
572+ $ connector ->expects ($ this ->once ())->method ('connect ' )->with ('tcp://[::1]:80/path?test=yes&hostname=reactphp.org#start ' )->willReturn (\React \Promise \reject (new \RuntimeException ()));
573+
574+ $ resolver = $ this ->getMockBuilder ('React\Dns\Resolver\ResolverInterface ' )->getMock ();
575+ $ resolver ->expects ($ this ->never ())->method ('resolveAll ' );
576+
577+ $ uri = 'tcp://reactphp.org:80/path?test=yes#start ' ;
578+ $ host = 'reactphp.org ' ;
579+ $ parts = parse_url ($ uri );
580+
581+ $ builder = new HappyEyeBallsConnectionBuilder ($ loop , $ connector , $ resolver , $ uri , $ host , $ parts );
582+
583+ $ ref = new \ReflectionProperty ($ builder , 'connectQueue ' );
584+ $ ref ->setAccessible (true );
585+ $ ref ->setValue ($ builder , array ('::1 ' ));
586+
587+ $ builder ->check ($ this ->expectCallableNever (), function () { });
588+
589+ $ ref = new \ReflectionProperty ($ builder , 'connectionPromises ' );
590+ $ ref ->setAccessible (true );
591+ $ promises = $ ref ->getValue ($ builder );
592+
593+ $ this ->assertEquals (array (), $ promises );
594+ }
595+
596+ public function testCleanUpCancelsAllPendingConnectionAttempts ()
597+ {
598+ $ loop = $ this ->getMockBuilder ('React\EventLoop\LoopInterface ' )->getMock ();
599+
600+ $ connector = $ this ->getMockBuilder ('React\Socket\ConnectorInterface ' )->getMock ();
601+ $ connector ->expects ($ this ->exactly (2 ))->method ('connect ' )->with ('tcp://[::1]:80/path?test=yes&hostname=reactphp.org#start ' )->willReturnOnConsecutiveCalls (
602+ new Promise (function () { }, $ this ->expectCallableOnce ()),
603+ new Promise (function () { }, $ this ->expectCallableOnce ())
604+ );
605+
606+ $ resolver = $ this ->getMockBuilder ('React\Dns\Resolver\ResolverInterface ' )->getMock ();
607+ $ resolver ->expects ($ this ->never ())->method ('resolveAll ' );
608+
609+ $ uri = 'tcp://reactphp.org:80/path?test=yes#start ' ;
610+ $ host = 'reactphp.org ' ;
611+ $ parts = parse_url ($ uri );
612+
613+ $ builder = new HappyEyeBallsConnectionBuilder ($ loop , $ connector , $ resolver , $ uri , $ host , $ parts );
614+
615+ $ ref = new \ReflectionProperty ($ builder , 'connectQueue ' );
616+ $ ref ->setAccessible (true );
617+ $ ref ->setValue ($ builder , array ('::1 ' , '::1 ' ));
618+
619+ $ builder ->check ($ this ->expectCallableNever (), function () { });
620+ $ builder ->check ($ this ->expectCallableNever (), function () { });
621+
622+ $ builder ->cleanUp ();
623+ }
624+
625+ public function testCleanUpCancelsAllPendingConnectionAttemptsWithoutStartingNewAttemptsDueToCancellationRejection ()
626+ {
627+ $ loop = $ this ->getMockBuilder ('React\EventLoop\LoopInterface ' )->getMock ();
628+
629+ $ connector = $ this ->getMockBuilder ('React\Socket\ConnectorInterface ' )->getMock ();
630+ $ connector ->expects ($ this ->once ())->method ('connect ' )->with ('tcp://[::1]:80/path?test=yes&hostname=reactphp.org#start ' )->willReturn (new Promise (function () { }, function () {
631+ throw new \RuntimeException ();
632+ }));
633+
634+ $ resolver = $ this ->getMockBuilder ('React\Dns\Resolver\ResolverInterface ' )->getMock ();
635+ $ resolver ->expects ($ this ->never ())->method ('resolveAll ' );
636+
637+ $ uri = 'tcp://reactphp.org:80/path?test=yes#start ' ;
638+ $ host = 'reactphp.org ' ;
639+ $ parts = parse_url ($ uri );
640+
641+ $ builder = new HappyEyeBallsConnectionBuilder ($ loop , $ connector , $ resolver , $ uri , $ host , $ parts );
642+
643+ $ ref = new \ReflectionProperty ($ builder , 'connectQueue ' );
644+ $ ref ->setAccessible (true );
645+ $ ref ->setValue ($ builder , array ('::1 ' , '::1 ' ));
646+
647+ $ builder ->check ($ this ->expectCallableNever (), function () { });
648+
649+ $ builder ->cleanUp ();
650+ }
495651}
0 commit comments