@@ -65,10 +65,10 @@ public function testConnectWillRejectWhenBothDnsLookupsReject()
6565 $ this ->assertEquals ('Connection to tcp://reactphp.org:80 failed during DNS lookup: DNS lookup error ' , $ exception ->getMessage ());
6666 }
6767
68- public function testConnectWillStartTimerWhenIpv4ResolvesAndIpv6IsPending ()
68+ public function testConnectWillStartDelayTimerWhenIpv4ResolvesAndIpv6IsPending ()
6969 {
7070 $ loop = $ this ->getMockBuilder ('React\EventLoop\LoopInterface ' )->getMock ();
71- $ loop ->expects ($ this ->once ())->method ('addTimer ' );
71+ $ loop ->expects ($ this ->once ())->method ('addTimer ' )-> with ( 0.05 , $ this -> anything ()) ;
7272 $ loop ->expects ($ this ->never ())->method ('cancelTimer ' );
7373
7474 $ connector = $ this ->getMockBuilder ('React\Socket\ConnectorInterface ' )->getMock ();
@@ -92,10 +92,76 @@ public function testConnectWillStartTimerWhenIpv4ResolvesAndIpv6IsPending()
9292 $ builder ->connect ();
9393 }
9494
95- public function testConnectWillStartConnectingWithoutTimerWhenIpv6ResolvesAndIpv4IsPending ()
95+ public function testConnectWillStartConnectingWithAttemptTimerButWithoutResolutionTimerWhenIpv6ResolvesAndIpv4IsPending ()
9696 {
9797 $ loop = $ this ->getMockBuilder ('React\EventLoop\LoopInterface ' )->getMock ();
98- $ loop ->expects ($ this ->never ())->method ('addTimer ' );
98+ $ loop ->expects ($ this ->once ())->method ('addTimer ' )->with (0.1 , $ this ->anything ());
99+ $ loop ->expects ($ this ->never ())->method ('cancelTimer ' );
100+
101+ $ connector = $ this ->getMockBuilder ('React\Socket\ConnectorInterface ' )->getMock ();
102+ $ connector ->expects ($ this ->once ())->method ('connect ' )->with ('tcp://[::1]:80?hostname=reactphp.org ' )->willReturn (new Promise (function () { }));
103+
104+ $ resolver = $ this ->getMockBuilder ('React\Dns\Resolver\ResolverInterface ' )->getMock ();
105+ $ resolver ->expects ($ this ->exactly (2 ))->method ('resolveAll ' )->withConsecutive (
106+ array ('reactphp.org ' , Message::TYPE_AAAA ),
107+ array ('reactphp.org ' , Message::TYPE_A )
108+ )->willReturnOnConsecutiveCalls (
109+ \React \Promise \resolve (array ('::1 ' )),
110+ new Promise (function () { })
111+ );
112+
113+ $ uri = 'tcp://reactphp.org:80 ' ;
114+ $ host = 'reactphp.org ' ;
115+ $ parts = parse_url ($ uri );
116+
117+ $ builder = new HappyEyeBallsConnectionBuilder ($ loop , $ connector , $ resolver , $ uri , $ host , $ parts );
118+
119+ $ builder ->connect ();
120+ }
121+
122+ public function testConnectWillStartConnectingAndWillStartNextConnectionWithNewAttemptTimerWhenNextAttemptTimerFiresWithIpv4StillPending ()
123+ {
124+ $ timer = null ;
125+ $ loop = $ this ->getMockBuilder ('React\EventLoop\LoopInterface ' )->getMock ();
126+ $ loop ->expects ($ this ->exactly (2 ))->method ('addTimer ' )->with (0.1 , $ this ->callback (function ($ cb ) use (&$ timer ) {
127+ $ timer = $ cb ;
128+ return true ;
129+ }));
130+ $ loop ->expects ($ this ->never ())->method ('cancelTimer ' );
131+
132+ $ connector = $ this ->getMockBuilder ('React\Socket\ConnectorInterface ' )->getMock ();
133+ $ connector ->expects ($ this ->exactly (2 ))->method ('connect ' )->willReturn (new Promise (function () { }));
134+
135+ $ resolver = $ this ->getMockBuilder ('React\Dns\Resolver\ResolverInterface ' )->getMock ();
136+ $ resolver ->expects ($ this ->exactly (2 ))->method ('resolveAll ' )->withConsecutive (
137+ array ('reactphp.org ' , Message::TYPE_AAAA ),
138+ array ('reactphp.org ' , Message::TYPE_A )
139+ )->willReturnOnConsecutiveCalls (
140+ \React \Promise \resolve (array ('::1 ' , '::2 ' )),
141+ new Promise (function () { })
142+ );
143+
144+ $ uri = 'tcp://reactphp.org:80 ' ;
145+ $ host = 'reactphp.org ' ;
146+ $ parts = parse_url ($ uri );
147+
148+ $ builder = new HappyEyeBallsConnectionBuilder ($ loop , $ connector , $ resolver , $ uri , $ host , $ parts );
149+
150+ $ builder ->connect ();
151+
152+ $ this ->assertNotNull ($ timer );
153+ $ timer ();
154+ }
155+
156+ public function testConnectWillStartConnectingAndWillDoNothingWhenNextAttemptTimerFiresWithNoOtherIps ()
157+ {
158+ $ timer = null ;
159+ $ loop = $ this ->getMockBuilder ('React\EventLoop\LoopInterface ' )->getMock ();
160+ $ loop ->expects ($ this ->once ())->method ('addTimer ' )->with (0.1 , $ this ->callback (function ($ cb ) use (&$ timer ) {
161+ $ timer = $ cb ;
162+ return true ;
163+ }));
164+ $ loop ->expects ($ this ->never ())->method ('cancelTimer ' );
99165
100166 $ connector = $ this ->getMockBuilder ('React\Socket\ConnectorInterface ' )->getMock ();
101167 $ connector ->expects ($ this ->once ())->method ('connect ' )->with ('tcp://[::1]:80?hostname=reactphp.org ' )->willReturn (new Promise (function () { }));
@@ -116,15 +182,93 @@ public function testConnectWillStartConnectingWithoutTimerWhenIpv6ResolvesAndIpv
116182 $ builder = new HappyEyeBallsConnectionBuilder ($ loop , $ connector , $ resolver , $ uri , $ host , $ parts );
117183
118184 $ builder ->connect ();
185+
186+ $ this ->assertNotNull ($ timer );
187+ $ timer ();
119188 }
120189
121- public function testConnectWillStartTimerAndCancelTimerWhenIpv4ResolvesAndIpv6ResolvesAfterwardsAndStartConnectingToIpv6 ()
190+ public function testConnectWillStartConnectingWithAttemptTimerButWithoutResolutionTimerWhenIpv6ResolvesAndWillCancelAttemptTimerWhenIpv4Rejects ()
122191 {
123192 $ timer = $ this ->getMockBuilder ('React\EventLoop\TimerInterface ' )->getMock ();
124193 $ loop = $ this ->getMockBuilder ('React\EventLoop\LoopInterface ' )->getMock ();
125- $ loop ->expects ($ this ->once ())->method ('addTimer ' )->willReturn ($ timer );
194+ $ loop ->expects ($ this ->once ())->method ('addTimer ' )->with ( 0.1 , $ this -> anything ())-> willReturn ($ timer );
126195 $ loop ->expects ($ this ->once ())->method ('cancelTimer ' )->with ($ timer );
127- $ loop ->expects ($ this ->once ())->method ('addPeriodicTimer ' )->willReturn ($ this ->getMockBuilder ('React\EventLoop\TimerInterface ' )->getMock ());
196+
197+ $ connector = $ this ->getMockBuilder ('React\Socket\ConnectorInterface ' )->getMock ();
198+ $ connector ->expects ($ this ->once ())->method ('connect ' )->with ('tcp://[::1]:80?hostname=reactphp.org ' )->willReturn (new Promise (function () { }));
199+
200+ $ deferred = new Deferred ();
201+ $ resolver = $ this ->getMockBuilder ('React\Dns\Resolver\ResolverInterface ' )->getMock ();
202+ $ resolver ->expects ($ this ->exactly (2 ))->method ('resolveAll ' )->withConsecutive (
203+ array ('reactphp.org ' , Message::TYPE_AAAA ),
204+ array ('reactphp.org ' , Message::TYPE_A )
205+ )->willReturnOnConsecutiveCalls (
206+ \React \Promise \resolve (array ('::1 ' )),
207+ $ deferred ->promise ()
208+ );
209+
210+ $ uri = 'tcp://reactphp.org:80 ' ;
211+ $ host = 'reactphp.org ' ;
212+ $ parts = parse_url ($ uri );
213+
214+ $ builder = new HappyEyeBallsConnectionBuilder ($ loop , $ connector , $ resolver , $ uri , $ host , $ parts );
215+
216+ $ builder ->connect ();
217+ $ deferred ->reject (new \RuntimeException ());
218+ }
219+
220+ public function testConnectWillStartConnectingAndWillStartNextConnectionWithoutNewAttemptTimerWhenNextAttemptTimerFiresAfterIpv4Rejected ()
221+ {
222+ $ timer = null ;
223+ $ loop = $ this ->getMockBuilder ('React\EventLoop\LoopInterface ' )->getMock ();
224+ $ loop ->expects ($ this ->once ())->method ('addTimer ' )->with (0.1 , $ this ->callback (function ($ cb ) use (&$ timer ) {
225+ $ timer = $ cb ;
226+ return true ;
227+ }));
228+ $ loop ->expects ($ this ->never ())->method ('cancelTimer ' );
229+
230+ $ connector = $ this ->getMockBuilder ('React\Socket\ConnectorInterface ' )->getMock ();
231+ $ connector ->expects ($ this ->exactly (2 ))->method ('connect ' )->willReturn (new Promise (function () { }));
232+
233+ $ deferred = new Deferred ();
234+ $ resolver = $ this ->getMockBuilder ('React\Dns\Resolver\ResolverInterface ' )->getMock ();
235+ $ resolver ->expects ($ this ->exactly (2 ))->method ('resolveAll ' )->withConsecutive (
236+ array ('reactphp.org ' , Message::TYPE_AAAA ),
237+ array ('reactphp.org ' , Message::TYPE_A )
238+ )->willReturnOnConsecutiveCalls (
239+ \React \Promise \resolve (array ('::1 ' , '::2 ' )),
240+ $ deferred ->promise ()
241+ );
242+
243+ $ uri = 'tcp://reactphp.org:80 ' ;
244+ $ host = 'reactphp.org ' ;
245+ $ parts = parse_url ($ uri );
246+
247+ $ builder = new HappyEyeBallsConnectionBuilder ($ loop , $ connector , $ resolver , $ uri , $ host , $ parts );
248+
249+ $ builder ->connect ();
250+ $ deferred ->reject (new \RuntimeException ());
251+
252+ $ this ->assertNotNull ($ timer );
253+ $ timer ();
254+ }
255+
256+ public function testConnectWillStartAndCancelResolutionTimerAndStartAttemptTimerWhenIpv4ResolvesAndIpv6ResolvesAfterwardsAndStartConnectingToIpv6 ()
257+ {
258+ $ timerDelay = $ this ->getMockBuilder ('React\EventLoop\TimerInterface ' )->getMock ();
259+ $ timerAttempt = $ this ->getMockBuilder ('React\EventLoop\TimerInterface ' )->getMock ();
260+ $ loop = $ this ->getMockBuilder ('React\EventLoop\LoopInterface ' )->getMock ();
261+ $ loop ->expects ($ this ->exactly (2 ))->method ('addTimer ' )->withConsecutive (
262+ array (
263+ 0.05 ,
264+ $ this ->anything ()
265+ ),
266+ array (
267+ 0.1 ,
268+ $ this ->anything ()
269+ )
270+ )->willReturnOnConsecutiveCalls ($ timerDelay , $ timerAttempt );
271+ $ loop ->expects ($ this ->once ())->method ('cancelTimer ' )->with ($ timerDelay );
128272
129273 $ connector = $ this ->getMockBuilder ('React\Socket\ConnectorInterface ' )->getMock ();
130274 $ connector ->expects ($ this ->once ())->method ('connect ' )->with ('tcp://[::1]:80?hostname=reactphp.org ' )->willReturn (new Promise (function () { }));
@@ -149,6 +293,44 @@ public function testConnectWillStartTimerAndCancelTimerWhenIpv4ResolvesAndIpv6Re
149293 $ deferred ->resolve (array ('::1 ' ));
150294 }
151295
296+ public function testConnectWillRejectWhenOnlyTcpConnectionRejectsAndCancelNextAttemptTimerImmediately ()
297+ {
298+ $ timer = $ this ->getMockBuilder ('React\EventLoop\TimerInterface ' )->getMock ();
299+ $ loop = $ this ->getMockBuilder ('React\EventLoop\LoopInterface ' )->getMock ();
300+ $ loop ->expects ($ this ->once ())->method ('addTimer ' )->with (0.1 , $ this ->anything ())->willReturn ($ timer );
301+ $ loop ->expects ($ this ->once ())->method ('cancelTimer ' )->with ($ timer );
302+
303+ $ deferred = new Deferred ();
304+ $ connector = $ this ->getMockBuilder ('React\Socket\ConnectorInterface ' )->getMock ();
305+ $ connector ->expects ($ this ->once ())->method ('connect ' )->with ('tcp://[::1]:80?hostname=reactphp.org ' )->willReturn ($ deferred ->promise ());
306+
307+ $ resolver = $ this ->getMockBuilder ('React\Dns\Resolver\ResolverInterface ' )->getMock ();
308+ $ resolver ->expects ($ this ->exactly (2 ))->method ('resolveAll ' )->withConsecutive (
309+ array ('reactphp.org ' , Message::TYPE_AAAA ),
310+ array ('reactphp.org ' , Message::TYPE_A )
311+ )->willReturnOnConsecutiveCalls (
312+ \React \Promise \resolve (array ('::1 ' )),
313+ \React \Promise \reject (new \RuntimeException ('ignored ' ))
314+ );
315+
316+ $ uri = 'tcp://reactphp.org:80 ' ;
317+ $ host = 'reactphp.org ' ;
318+ $ parts = parse_url ($ uri );
319+
320+ $ builder = new HappyEyeBallsConnectionBuilder ($ loop , $ connector , $ resolver , $ uri , $ host , $ parts );
321+
322+ $ promise = $ builder ->connect ();
323+ $ deferred ->reject (new \RuntimeException ('Connection refused ' ));
324+
325+ $ exception = null ;
326+ $ promise ->then (null , function ($ e ) use (&$ exception ) {
327+ $ exception = $ e ;
328+ });
329+
330+ $ this ->assertInstanceOf ('RuntimeException ' , $ exception );
331+ $ this ->assertEquals ('Connection to tcp://reactphp.org:80 failed: Connection refused ' , $ exception ->getMessage ());
332+ }
333+
152334 public function testCancelConnectWillRejectPromiseAndCancelBothDnsLookups ()
153335 {
154336 $ loop = $ this ->getMockBuilder ('React\EventLoop\LoopInterface ' )->getMock ();
@@ -193,7 +375,7 @@ public function testCancelConnectWillRejectPromiseAndCancelBothDnsLookups()
193375 $ this ->assertEquals ('Connection to tcp://reactphp.org:80 cancelled during DNS lookup ' , $ exception ->getMessage ());
194376 }
195377
196- public function testCancelConnectWillRejectPromiseAndCancelPendingIpv6LookupAndCancelTimer ()
378+ public function testCancelConnectWillRejectPromiseAndCancelPendingIpv6LookupAndCancelDelayTimer ()
197379 {
198380 $ timer = $ this ->getMockBuilder ('React\EventLoop\TimerInterface ' )->getMock ();
199381 $ loop = $ this ->getMockBuilder ('React\EventLoop\LoopInterface ' )->getMock ();
@@ -230,10 +412,12 @@ public function testCancelConnectWillRejectPromiseAndCancelPendingIpv6LookupAndC
230412 $ this ->assertEquals ('Connection to tcp://reactphp.org:80 cancelled during DNS lookup ' , $ exception ->getMessage ());
231413 }
232414
233- public function testCancelConnectWillRejectPromiseAndCancelPendingIpv6ConnectionAttemptAndPendingIpv4Lookup ()
415+ public function testCancelConnectWillRejectPromiseAndCancelPendingIpv6ConnectionAttemptAndPendingIpv4LookupAndCancelAttemptTimer ()
234416 {
417+ $ timer = $ this ->getMockBuilder ('React\EventLoop\TimerInterface ' )->getMock ();
235418 $ loop = $ this ->getMockBuilder ('React\EventLoop\LoopInterface ' )->getMock ();
236- $ loop ->expects ($ this ->never ())->method ('addTimer ' );
419+ $ loop ->expects ($ this ->once ())->method ('addTimer ' )->with (0.1 , $ this ->anything ())->willReturn ($ timer );
420+ $ loop ->expects ($ this ->once ())->method ('cancelTimer ' )->with ($ timer );
237421
238422 $ cancelled = 0 ;
239423 $ connector = $ this ->getMockBuilder ('React\Socket\ConnectorInterface ' )->getMock ();
0 commit comments