ione 1.0.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,484 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module Ione
7
+ module Io
8
+ describe Connection do
9
+ let :handler do
10
+ described_class.new('example.com', 55555, 5, unblocker, clock, socket_impl)
11
+ end
12
+
13
+ let :unblocker do
14
+ double(:unblocker, unblock!: nil)
15
+ end
16
+
17
+ let :socket_impl do
18
+ double(:socket_impl)
19
+ end
20
+
21
+ let :clock do
22
+ double(:clock, now: 0)
23
+ end
24
+
25
+ let :socket do
26
+ double(:socket)
27
+ end
28
+
29
+ before do
30
+ socket_impl.stub(:getaddrinfo)
31
+ .with('example.com', 55555, nil, Socket::SOCK_STREAM)
32
+ .and_return([[nil, 'PORT', nil, 'IP1', 'FAMILY1', 'TYPE1'], [nil, 'PORT', nil, 'IP2', 'FAMILY2', 'TYPE2']])
33
+ socket_impl.stub(:sockaddr_in)
34
+ .with('PORT', 'IP1')
35
+ .and_return('SOCKADDR1')
36
+ socket_impl.stub(:new)
37
+ .with('FAMILY1', 'TYPE1', 0)
38
+ .and_return(socket)
39
+ end
40
+
41
+ describe '#connect' do
42
+ it 'creates a socket and calls #connect_nonblock' do
43
+ socket.should_receive(:connect_nonblock).with('SOCKADDR1')
44
+ handler.connect
45
+ end
46
+
47
+ it 'handles EINPROGRESS that #connect_nonblock raises' do
48
+ socket.stub(:connect_nonblock).and_raise(Errno::EINPROGRESS)
49
+ handler.connect
50
+ end
51
+
52
+ it 'is connecting after #connect has been called' do
53
+ socket.stub(:connect_nonblock).and_raise(Errno::EINPROGRESS)
54
+ handler.connect
55
+ handler.should be_connecting
56
+ end
57
+
58
+ it 'is connecting even after the second call' do
59
+ socket.should_receive(:connect_nonblock).twice.and_raise(Errno::EINPROGRESS)
60
+ handler.connect
61
+ handler.connect
62
+ handler.should be_connecting
63
+ end
64
+
65
+ it 'does not create a new socket the second time' do
66
+ socket_impl.should_receive(:new).once.and_return(socket)
67
+ socket.stub(:connect_nonblock).and_raise(Errno::EINPROGRESS)
68
+ handler.connect
69
+ handler.connect
70
+ end
71
+
72
+ it 'attempts another connect the second time' do
73
+ socket.should_receive(:connect_nonblock).twice.and_raise(Errno::EINPROGRESS)
74
+ handler.connect
75
+ handler.connect
76
+ end
77
+
78
+ shared_examples 'on successfull connection' do
79
+ it 'fulfilles the returned future and returns itself' do
80
+ f = handler.connect
81
+ f.should be_resolved
82
+ f.value.should equal(handler)
83
+ end
84
+
85
+ it 'is connected' do
86
+ handler.connect
87
+ handler.should be_connected
88
+ end
89
+ end
90
+
91
+ context 'when #connect_nonblock does not raise any error' do
92
+ before do
93
+ socket.stub(:connect_nonblock)
94
+ end
95
+
96
+ include_examples 'on successfull connection'
97
+ end
98
+
99
+ context 'when #connect_nonblock raises EISCONN' do
100
+ before do
101
+ socket.stub(:connect_nonblock).and_raise(Errno::EISCONN)
102
+ end
103
+
104
+ include_examples 'on successfull connection'
105
+ end
106
+
107
+ context 'when #connect_nonblock raises EALREADY' do
108
+ it 'it does nothing' do
109
+ socket.stub(:connect_nonblock).and_raise(Errno::EALREADY)
110
+ f = handler.connect
111
+ f.should_not be_resolved
112
+ f.should_not be_failed
113
+ end
114
+ end
115
+
116
+ context 'when #connect_nonblock raises EINVAL' do
117
+ before do
118
+ socket_impl.stub(:sockaddr_in)
119
+ .with('PORT', 'IP2')
120
+ .and_return('SOCKADDR2')
121
+ socket_impl.stub(:new)
122
+ .with('FAMILY2', 'TYPE2', 0)
123
+ .and_return(socket)
124
+ socket.stub(:close)
125
+ end
126
+
127
+ it 'attempts to connect to the next address given by #getaddinfo' do
128
+ socket.should_receive(:connect_nonblock).with('SOCKADDR1').and_raise(Errno::EINVAL)
129
+ socket.should_receive(:connect_nonblock).with('SOCKADDR2')
130
+ handler.connect
131
+ end
132
+
133
+ it 'fails if there are no more addresses to try' do
134
+ socket.stub(:connect_nonblock).and_raise(Errno::EINVAL)
135
+ f = handler.connect
136
+ expect { f.value }.to raise_error(ConnectionError)
137
+ end
138
+ end
139
+
140
+ context 'when #connect_nonblock raises ECONNREFUSED' do
141
+ before do
142
+ socket_impl.stub(:sockaddr_in)
143
+ .with('PORT', 'IP2')
144
+ .and_return('SOCKADDR2')
145
+ socket_impl.stub(:new)
146
+ .with('FAMILY2', 'TYPE2', 0)
147
+ .and_return(socket)
148
+ socket.stub(:close)
149
+ end
150
+
151
+ it 'attempts to connect to the next address given by #getaddinfo' do
152
+ socket.should_receive(:connect_nonblock).with('SOCKADDR1').and_raise(Errno::ECONNREFUSED)
153
+ socket.should_receive(:connect_nonblock).with('SOCKADDR2')
154
+ handler.connect
155
+ end
156
+
157
+ it 'fails if there are no more addresses to try' do
158
+ socket.stub(:connect_nonblock).and_raise(Errno::ECONNREFUSED)
159
+ f = handler.connect
160
+ expect { f.value }.to raise_error(ConnectionError)
161
+ end
162
+ end
163
+
164
+ context 'when #connect_nonblock raises SystemCallError' do
165
+ before do
166
+ socket.stub(:connect_nonblock).and_raise(SystemCallError.new('Bork!', 9999))
167
+ socket.stub(:close)
168
+ end
169
+
170
+ it 'fails the future with a ConnectionError' do
171
+ f = handler.connect
172
+ expect { f.value }.to raise_error(ConnectionError)
173
+ end
174
+
175
+ it 'closes the socket' do
176
+ socket.should_receive(:close)
177
+ handler.connect
178
+ end
179
+
180
+ it 'calls the closed listener' do
181
+ called = false
182
+ handler.on_closed { called = true }
183
+ handler.connect
184
+ called.should be_true, 'expected the close listener to have been called'
185
+ end
186
+
187
+ it 'passes the error to the close listener' do
188
+ error = nil
189
+ handler.on_closed { |e| error = e }
190
+ handler.connect
191
+ error.should be_a(Exception)
192
+ end
193
+
194
+ it 'is closed' do
195
+ handler.connect
196
+ handler.should be_closed
197
+ end
198
+ end
199
+
200
+ context 'when Socket.getaddrinfo raises SocketError' do
201
+ before do
202
+ socket_impl.stub(:getaddrinfo).and_raise(SocketError)
203
+ end
204
+
205
+ it 'fails the returned future with a ConnectionError' do
206
+ f = handler.connect
207
+ expect { f.value }.to raise_error(ConnectionError)
208
+ end
209
+
210
+ it 'calls the close listener' do
211
+ called = false
212
+ handler.on_closed { called = true }
213
+ handler.connect
214
+ called.should be_true, 'expected the close listener to have been called'
215
+ end
216
+
217
+ it 'passes the error to the close listener' do
218
+ error = nil
219
+ handler.on_closed { |e| error = e }
220
+ handler.connect
221
+ error.should be_a(Exception)
222
+ end
223
+
224
+ it 'is closed' do
225
+ handler.connect
226
+ handler.should be_closed
227
+ end
228
+ end
229
+
230
+ context 'when it takes longer than the connection timeout to connect' do
231
+ before do
232
+ socket.stub(:connect_nonblock).and_raise(Errno::EINPROGRESS)
233
+ socket.stub(:close)
234
+ end
235
+
236
+ it 'fails the returned future with a ConnectionTimeoutError' do
237
+ f = handler.connect
238
+ clock.stub(:now).and_return(1)
239
+ handler.connect
240
+ socket.should_receive(:close)
241
+ clock.stub(:now).and_return(7)
242
+ handler.connect
243
+ f.should be_failed
244
+ expect { f.value }.to raise_error(ConnectionTimeoutError)
245
+ end
246
+
247
+ it 'closes the connection' do
248
+ handler.connect
249
+ clock.stub(:now).and_return(1)
250
+ handler.connect
251
+ socket.should_receive(:close)
252
+ clock.stub(:now).and_return(7)
253
+ handler.connect
254
+ end
255
+
256
+ it 'delivers a ConnectionTimeoutError to the close handler' do
257
+ error = nil
258
+ handler.on_closed { |e| error = e }
259
+ handler.connect
260
+ clock.stub(:now).and_return(7)
261
+ handler.connect
262
+ error.should be_a(ConnectionTimeoutError)
263
+ end
264
+ end
265
+ end
266
+
267
+ describe '#close' do
268
+ before do
269
+ socket.stub(:connect_nonblock)
270
+ socket.stub(:close)
271
+ handler.connect
272
+ end
273
+
274
+ it 'closes the socket' do
275
+ socket.should_receive(:close)
276
+ handler.close
277
+ end
278
+
279
+ it 'returns true' do
280
+ handler.close.should be_true
281
+ end
282
+
283
+ it 'swallows SystemCallErrors' do
284
+ socket.stub(:close).and_raise(SystemCallError.new('Bork!', 9999))
285
+ handler.close
286
+ end
287
+
288
+ it 'swallows IOErrors' do
289
+ socket.stub(:close).and_raise(IOError.new('Bork!'))
290
+ handler.close
291
+ end
292
+
293
+ it 'calls the closed listener' do
294
+ called = false
295
+ handler.on_closed { called = true }
296
+ handler.close
297
+ called.should be_true, 'expected the close listener to have been called'
298
+ end
299
+
300
+ it 'does nothing when closed a second time' do
301
+ socket.should_receive(:close).once
302
+ calls = 0
303
+ handler.on_closed { calls += 1 }
304
+ handler.close
305
+ handler.close
306
+ calls.should == 1
307
+ end
308
+
309
+ it 'returns false if it did nothing' do
310
+ handler.close
311
+ handler.close.should be_false
312
+ end
313
+
314
+ it 'is not writable when closed' do
315
+ handler.write('foo')
316
+ handler.close
317
+ handler.should_not be_writable
318
+ end
319
+ end
320
+
321
+ describe '#to_io' do
322
+ before do
323
+ socket.stub(:connect_nonblock)
324
+ socket.stub(:close)
325
+ end
326
+
327
+ it 'returns nil initially' do
328
+ handler.to_io.should be_nil
329
+ end
330
+
331
+ it 'returns the socket when connected' do
332
+ handler.connect
333
+ handler.to_io.should equal(socket)
334
+ end
335
+
336
+ it 'returns nil when closed' do
337
+ handler.connect
338
+ handler.close
339
+ handler.to_io.should be_nil
340
+ end
341
+ end
342
+
343
+ describe '#write/#flush' do
344
+ before do
345
+ socket.stub(:connect_nonblock)
346
+ handler.connect
347
+ end
348
+
349
+ it 'appends to its buffer when #write is called' do
350
+ handler.write('hello world')
351
+ end
352
+
353
+ it 'unblocks the reactor' do
354
+ unblocker.should_receive(:unblock!)
355
+ handler.write('hello world')
356
+ end
357
+
358
+ it 'is writable when there are bytes to write' do
359
+ handler.should_not be_writable
360
+ handler.write('hello world')
361
+ handler.should be_writable
362
+ socket.should_receive(:write_nonblock).with('hello world').and_return(11)
363
+ handler.flush
364
+ handler.should_not be_writable
365
+ end
366
+
367
+ it 'writes to the socket from its buffer when #flush is called' do
368
+ handler.write('hello world')
369
+ socket.should_receive(:write_nonblock).with('hello world').and_return(11)
370
+ handler.flush
371
+ end
372
+
373
+ it 'takes note of how much the #write_nonblock call consumed and writes the rest of the buffer on the next call to #flush' do
374
+ handler.write('hello world')
375
+ socket.should_receive(:write_nonblock).with('hello world').and_return(6)
376
+ handler.flush
377
+ socket.should_receive(:write_nonblock).with('world').and_return(5)
378
+ handler.flush
379
+ end
380
+
381
+ it 'does not call #write_nonblock if the buffer is empty' do
382
+ handler.flush
383
+ handler.write('hello world')
384
+ socket.should_receive(:write_nonblock).with('hello world').and_return(11)
385
+ handler.flush
386
+ socket.should_not_receive(:write_nonblock)
387
+ handler.flush
388
+ end
389
+
390
+ context 'with a block' do
391
+ it 'yields a byte buffer to the block' do
392
+ socket.should_receive(:write_nonblock).with('hello world').and_return(11)
393
+ handler.write do |buffer|
394
+ buffer << 'hello world'
395
+ end
396
+ handler.flush
397
+ end
398
+ end
399
+
400
+ context 'when #write_nonblock raises an error' do
401
+ before do
402
+ socket.stub(:close)
403
+ socket.stub(:write_nonblock).and_raise('Bork!')
404
+ end
405
+
406
+ it 'closes the socket' do
407
+ socket.should_receive(:close)
408
+ handler.write('hello world')
409
+ handler.flush
410
+ end
411
+
412
+ it 'passes the error to the close handler' do
413
+ error = nil
414
+ handler.on_closed { |e| error = e }
415
+ handler.write('hello world')
416
+ handler.flush
417
+ error.should be_a(Exception)
418
+ end
419
+ end
420
+ end
421
+
422
+ describe '#read' do
423
+ before do
424
+ socket.stub(:connect_nonblock)
425
+ handler.connect
426
+ end
427
+
428
+ it 'reads a chunk from the socket' do
429
+ socket.should_receive(:read_nonblock).with(instance_of(Fixnum)).and_return('foo bar')
430
+ handler.read
431
+ end
432
+
433
+ it 'calls the data listener with the new data' do
434
+ socket.should_receive(:read_nonblock).with(instance_of(Fixnum)).and_return('foo bar')
435
+ data = nil
436
+ handler.on_data { |d| data = d }
437
+ handler.read
438
+ data.should == 'foo bar'
439
+ end
440
+
441
+ context 'when #read_nonblock raises an error' do
442
+ before do
443
+ socket.stub(:close)
444
+ socket.stub(:read_nonblock).and_raise('Bork!')
445
+ end
446
+
447
+ it 'closes the socket' do
448
+ socket.should_receive(:close)
449
+ handler.read
450
+ end
451
+
452
+ it 'passes the error to the close handler' do
453
+ error = nil
454
+ handler.on_closed { |e| error = e }
455
+ handler.read
456
+ error.should be_a(Exception)
457
+ end
458
+ end
459
+ end
460
+
461
+ describe '#to_s' do
462
+ context 'returns a string that' do
463
+ it 'includes the class name' do
464
+ handler.to_s.should include('Ione::Io::Connection')
465
+ end
466
+
467
+ it 'includes the host and port' do
468
+ handler.to_s.should include('example.com:55555')
469
+ end
470
+
471
+ it 'includes the connection state' do
472
+ handler.to_s.should include('closed')
473
+ socket.stub(:connect_nonblock).and_raise(Errno::EINPROGRESS)
474
+ handler.connect
475
+ handler.to_s.should include('connecting')
476
+ socket.stub(:connect_nonblock)
477
+ handler.connect
478
+ handler.to_s.should include('connected')
479
+ end
480
+ end
481
+ end
482
+ end
483
+ end
484
+ end