ione 1.0.0 → 1.1.0.pre0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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