ione 1.0.0 → 1.1.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,223 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module Ione
7
+ module Io
8
+ describe Acceptor do
9
+ let :acceptor do
10
+ described_class.new('example.com', 4321, backlog, unblocker, reactor, socket_impl)
11
+ end
12
+
13
+ let :backlog do
14
+ 3
15
+ end
16
+
17
+ let :unblocker do
18
+ double(:unblocker)
19
+ end
20
+
21
+ let :reactor do
22
+ double(:reactor)
23
+ end
24
+
25
+ let :socket_impl do
26
+ double(:socket_impl)
27
+ end
28
+
29
+ let :socket do
30
+ double(:socket)
31
+ end
32
+
33
+ shared_context 'accepting_connections' do
34
+ let :client_socket do
35
+ double(:client_socket)
36
+ end
37
+
38
+ let :accepted_handlers do
39
+ []
40
+ end
41
+
42
+ before do
43
+ socket_impl.stub(:unpack_sockaddr_in).with('SOCKADDRX').and_return([3333, 'example.com'])
44
+ socket.stub(:bind)
45
+ socket.stub(:accept_nonblock).and_return([client_socket, 'SOCKADDRX'])
46
+ reactor.stub(:accept) { |h| accepted_handlers << h }
47
+ end
48
+ end
49
+
50
+ before do
51
+ socket_impl.stub(:getaddrinfo)
52
+ .with('example.com', 4321, nil, Socket::SOCK_STREAM)
53
+ .and_return([[nil, 'PORT', nil, 'IP1', 'FAMILY1', 'TYPE1'], [nil, 'PORT', nil, 'IP2', 'FAMILY2', 'TYPE2']])
54
+ socket_impl.stub(:sockaddr_in)
55
+ .with('PORT', 'IP1')
56
+ .and_return('SOCKADDR1')
57
+ socket_impl.stub(:sockaddr_in)
58
+ .with('PORT', 'IP2')
59
+ .and_return('SOCKADDR2')
60
+ socket_impl.stub(:new)
61
+ .with('FAMILY1', 'TYPE1', 0)
62
+ .and_return(socket)
63
+ socket_impl.stub(:new)
64
+ .with('FAMILY2', 'TYPE2', 0)
65
+ .and_return(socket)
66
+ socket.stub(:bind)
67
+ socket.stub(:listen)
68
+ end
69
+
70
+ describe '#bind' do
71
+ if RUBY_ENGINE == 'jruby'
72
+ it 'creates a new socket and binds it to all interfaces' do
73
+ acceptor.bind
74
+ socket.should have_received(:bind).with('SOCKADDR1', backlog)
75
+ end
76
+
77
+ it 'tries the next address when the first one raises EADDRNOTAVAIL' do
78
+ socket.stub(:bind).with('SOCKADDR1', anything).and_raise(Errno::EADDRNOTAVAIL)
79
+ acceptor.bind
80
+ socket.should have_received(:bind).with('SOCKADDR2', backlog)
81
+ end
82
+ else
83
+ it 'creates a new socket and binds it to all interfaces' do
84
+ acceptor.bind
85
+ socket.should have_received(:bind).with('SOCKADDR1')
86
+ socket.should have_received(:listen).with(backlog)
87
+ end
88
+
89
+ it 'tries the next address when the first one raises EADDRNOTAVAIL' do
90
+ socket.stub(:bind).with('SOCKADDR1').and_raise(Errno::EADDRNOTAVAIL)
91
+ acceptor.bind
92
+ socket.should have_received(:bind).with('SOCKADDR2')
93
+ socket.should have_received(:listen).with(backlog)
94
+ end
95
+ end
96
+
97
+ it 'returns a failed future when none of the addresses worked' do
98
+ socket.stub(:bind).and_raise(Errno::EADDRNOTAVAIL)
99
+ f = acceptor.bind
100
+ expect { f.value }.to raise_error(Errno::EADDRNOTAVAIL)
101
+ end
102
+
103
+ it 'returns a future that resolves to itself when the socket has been bound' do
104
+ f = acceptor.bind
105
+ f.should be_resolved
106
+ f.value.should equal(acceptor)
107
+ end
108
+ end
109
+
110
+ describe '#close' do
111
+ before do
112
+ socket.stub(:close)
113
+ end
114
+
115
+ it 'closes the socket' do
116
+ acceptor.bind
117
+ acceptor.close
118
+ socket.should have_received(:close)
119
+ end
120
+
121
+ it 'does nothing when called before #bind' do
122
+ acceptor.close
123
+ end
124
+
125
+ it 'does nothing when called again' do
126
+ acceptor.bind
127
+ acceptor.close
128
+ acceptor.close
129
+ acceptor.close
130
+ end
131
+
132
+ it 'ignores IOError' do
133
+ socket.stub(:close).and_raise(IOError)
134
+ acceptor.bind
135
+ expect { acceptor.close }.to_not raise_error
136
+ end
137
+
138
+ it 'ignores Errno::*' do
139
+ socket.stub(:close).and_raise(Errno::EINVAL)
140
+ acceptor.bind
141
+ expect { acceptor.close }.to_not raise_error
142
+ end
143
+
144
+ it 'is closed afterwards' do
145
+ acceptor.bind
146
+ acceptor.close
147
+ acceptor.should be_closed
148
+ end
149
+
150
+ it 'is is not connected afterwards' do
151
+ acceptor.bind
152
+ acceptor.close
153
+ acceptor.should_not be_connected
154
+ end
155
+ end
156
+
157
+ describe '#read' do
158
+ include_context 'accepting_connections'
159
+
160
+ it 'accepts a new connection' do
161
+ acceptor.bind
162
+ acceptor.read
163
+ socket.should have_received(:accept_nonblock)
164
+ end
165
+
166
+ it 'creates a new connection handler and registers it with the reactor' do
167
+ acceptor.bind
168
+ acceptor.read
169
+ accepted_handlers.should have(1).item
170
+ accepted_handlers.first.host.should == 'example.com'
171
+ accepted_handlers.first.port.should == 3333
172
+ end
173
+
174
+ it 'passes the unblocker along to the connection handler' do
175
+ unblocker.stub(:unblock!)
176
+ acceptor.bind
177
+ acceptor.read
178
+ accepted_handlers.first.write('foo')
179
+ unblocker.should have_received(:unblock!)
180
+ end
181
+ end
182
+
183
+ describe '#on_accept' do
184
+ include_context 'accepting_connections'
185
+
186
+ it 'calls accept listeners with new connections' do
187
+ received_connection1 = nil
188
+ received_connection2 = nil
189
+ acceptor.on_accept { |c| received_connection1 = c }
190
+ acceptor.on_accept { |c| received_connection2 = c }
191
+ acceptor.bind
192
+ acceptor.read
193
+ received_connection1.host.should == 'example.com'
194
+ received_connection2.host.should == 'example.com'
195
+ received_connection2.port.should == 3333
196
+ end
197
+
198
+ it 'ignores exceptions raised by the connection callback' do
199
+ called = false
200
+ acceptor.on_accept { |c| raise 'bork!' }
201
+ acceptor.on_accept { |c| called = true }
202
+ acceptor.bind
203
+ acceptor.read
204
+ called.should be_true
205
+ end
206
+ end
207
+
208
+ describe '#to_io' do
209
+ it 'returns the socket' do
210
+ acceptor.bind
211
+ acceptor.to_io.should equal(socket)
212
+ end
213
+
214
+ it 'returns nil when the socket has been closed' do
215
+ socket.stub(:close)
216
+ acceptor.bind
217
+ acceptor.close
218
+ acceptor.to_io.should be_nil
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,255 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'a connection' do
4
+ describe '#close' do
5
+ it 'closes the socket' do
6
+ socket.should_receive(:close)
7
+ handler.close
8
+ end
9
+
10
+ it 'returns true' do
11
+ handler.close.should be_true
12
+ end
13
+
14
+ it 'does nothing when called again' do
15
+ handler.close
16
+ handler.close
17
+ handler.close
18
+ end
19
+
20
+ it 'swallows SystemCallErrors' do
21
+ socket.stub(:close).and_raise(SystemCallError.new('Bork!', 9999))
22
+ handler.close
23
+ end
24
+
25
+ it 'swallows IOErrors' do
26
+ socket.stub(:close).and_raise(IOError.new('Bork!'))
27
+ handler.close
28
+ end
29
+
30
+ it 'calls the closed listener' do
31
+ called = false
32
+ handler.on_closed { called = true }
33
+ handler.close
34
+ called.should be_true, 'expected the close listener to have been called'
35
+ end
36
+
37
+ it 'does nothing when closed a second time' do
38
+ socket.should_receive(:close).once
39
+ calls = 0
40
+ handler.on_closed { calls += 1 }
41
+ handler.close
42
+ handler.close
43
+ calls.should == 1
44
+ end
45
+
46
+ it 'returns false if it did nothing' do
47
+ handler.close
48
+ handler.close.should be_false
49
+ end
50
+
51
+ it 'is not writable when closed' do
52
+ handler.write('foo')
53
+ handler.close
54
+ handler.should_not be_writable
55
+ end
56
+
57
+ it 'is closed afterwards' do
58
+ handler.close
59
+ handler.should be_closed
60
+ end
61
+
62
+ it 'is is not connected afterwards' do
63
+ handler.close
64
+ handler.should_not be_connected
65
+ end
66
+ end
67
+
68
+ describe '#drain' do
69
+ before do
70
+ socket.stub(:write_nonblock) { |s| s.bytesize }
71
+ end
72
+
73
+ it 'waits for the buffer to drain and then closes the socket' do
74
+ handler.write('hello world')
75
+ handler.drain
76
+ handler.should_not be_closed
77
+ handler.flush
78
+ handler.should be_closed
79
+ end
80
+
81
+ it 'closes the socket immediately when the buffer is empty' do
82
+ handler.drain
83
+ handler.should be_closed
84
+ end
85
+
86
+ it 'returns a future that completes when the socket has closed' do
87
+ handler.write('hello world')
88
+ f = handler.drain
89
+ f.should_not be_completed
90
+ handler.flush
91
+ f.should be_completed
92
+ end
93
+ end
94
+
95
+ describe '#write/#flush' do
96
+ before do
97
+ socket.stub(:write_nonblock)
98
+ unblocker.stub(:unblock!)
99
+ end
100
+
101
+ it 'appends to its buffer when #write is called' do
102
+ handler.write('hello world')
103
+ end
104
+
105
+ it 'unblocks the reactor' do
106
+ unblocker.should_receive(:unblock!)
107
+ handler.write('hello world')
108
+ end
109
+
110
+ it 'is writable when there are bytes to write' do
111
+ handler.should_not be_writable
112
+ handler.write('hello world')
113
+ handler.should be_writable
114
+ socket.should_receive(:write_nonblock).with('hello world').and_return(11)
115
+ handler.flush
116
+ handler.should_not be_writable
117
+ end
118
+
119
+ it 'writes to the socket from its buffer when #flush is called' do
120
+ handler.write('hello world')
121
+ socket.should_receive(:write_nonblock).with('hello world').and_return(11)
122
+ handler.flush
123
+ end
124
+
125
+ 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
126
+ handler.write('hello world')
127
+ socket.should_receive(:write_nonblock).with('hello world').and_return(6)
128
+ handler.flush
129
+ socket.should_receive(:write_nonblock).with('world').and_return(5)
130
+ handler.flush
131
+ end
132
+
133
+ it 'does not call #write_nonblock if the buffer is empty' do
134
+ handler.flush
135
+ handler.write('hello world')
136
+ socket.should_receive(:write_nonblock).with('hello world').and_return(11)
137
+ handler.flush
138
+ socket.should_not_receive(:write_nonblock)
139
+ handler.flush
140
+ end
141
+
142
+ context 'with a block' do
143
+ it 'yields a byte buffer to the block' do
144
+ socket.should_receive(:write_nonblock).with('hello world').and_return(11)
145
+ handler.write do |buffer|
146
+ buffer << 'hello world'
147
+ end
148
+ handler.flush
149
+ end
150
+ end
151
+
152
+ context 'when #write_nonblock raises an error' do
153
+ before do
154
+ socket.stub(:close)
155
+ socket.stub(:write_nonblock).and_raise('Bork!')
156
+ end
157
+
158
+ it 'closes the socket' do
159
+ socket.should_receive(:close)
160
+ handler.write('hello world')
161
+ handler.flush
162
+ end
163
+
164
+ it 'passes the error to the close handler' do
165
+ error = nil
166
+ handler.on_closed { |e| error = e }
167
+ handler.write('hello world')
168
+ handler.flush
169
+ error.should be_a(Exception)
170
+ end
171
+ end
172
+
173
+ context 'when closed' do
174
+ it 'discards the bytes' do
175
+ handler.close
176
+ handler.write('hello world')
177
+ handler.flush
178
+ socket.should_not have_received(:write_nonblock)
179
+ end
180
+
181
+ it 'does not yield the buffer' do
182
+ called = false
183
+ handler.close
184
+ handler.write { called = true }
185
+ handler.flush
186
+ called.should be_false
187
+ end
188
+
189
+ it 'does not unblock the reactor' do
190
+ handler.close
191
+ handler.write('hello world')
192
+ handler.flush
193
+ unblocker.should_not have_received(:unblock!)
194
+ end
195
+ end
196
+
197
+ context 'when draining' do
198
+ it 'discards the bytes' do
199
+ handler.drain
200
+ handler.write('hello world')
201
+ handler.flush
202
+ socket.should_not have_received(:write_nonblock)
203
+ end
204
+
205
+ it 'does not yield the buffer' do
206
+ called = false
207
+ handler.drain
208
+ handler.write { called = true }
209
+ handler.flush
210
+ called.should be_false
211
+ end
212
+
213
+ it 'does not unblock the reactor' do
214
+ handler.drain
215
+ handler.write('hello world')
216
+ handler.flush
217
+ unblocker.should_not have_received(:unblock!)
218
+ end
219
+ end
220
+ end
221
+
222
+ describe '#read/#on_data' do
223
+ it 'reads a chunk from the socket' do
224
+ socket.should_receive(:read_nonblock).with(instance_of(Fixnum)).and_return('foo bar')
225
+ handler.read
226
+ end
227
+
228
+ it 'calls the data listener with the new data' do
229
+ socket.should_receive(:read_nonblock).with(instance_of(Fixnum)).and_return('foo bar')
230
+ data = nil
231
+ handler.on_data { |d| data = d }
232
+ handler.read
233
+ data.should == 'foo bar'
234
+ end
235
+
236
+ context 'when #read_nonblock raises an error' do
237
+ before do
238
+ socket.stub(:close)
239
+ socket.stub(:read_nonblock).and_raise('Bork!')
240
+ end
241
+
242
+ it 'closes the socket' do
243
+ socket.should_receive(:close)
244
+ handler.read
245
+ end
246
+
247
+ it 'passes the error to the close handler' do
248
+ error = nil
249
+ handler.on_closed { |e| error = e }
250
+ handler.read
251
+ error.should be_a(Exception)
252
+ end
253
+ end
254
+ end
255
+ end