ione 1.2.0.pre1 → 1.2.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
@@ -63,6 +63,7 @@ module Ione
63
63
  socket_impl.stub(:new)
64
64
  .with('FAMILY2', 'TYPE2', 0)
65
65
  .and_return(socket)
66
+ socket.stub(:close)
66
67
  socket.stub(:bind)
67
68
  socket.stub(:listen)
68
69
  end
@@ -100,6 +101,12 @@ module Ione
100
101
  expect { f.value }.to raise_error(Errno::EADDRNOTAVAIL)
101
102
  end
102
103
 
104
+ it 'closes the socket when none of the addresses worked' do
105
+ socket.stub(:bind).and_raise(Errno::EADDRNOTAVAIL)
106
+ acceptor.bind.value rescue nil
107
+ socket.should have_received(:close)
108
+ end
109
+
103
110
  it 'returns a future that resolves to itself when the socket has been bound' do
104
111
  f = acceptor.bind
105
112
  f.should be_resolved
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- shared_examples_for 'a connection' do
3
+ shared_examples_for 'a connection' do |options|
4
4
  describe '#close' do
5
5
  it 'closes the socket' do
6
6
  socket.should_receive(:close)
@@ -219,36 +219,38 @@ shared_examples_for 'a connection' do
219
219
  end
220
220
  end
221
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!')
222
+ if options.nil? || options.fetch(:skip_read, false) == false
223
+ describe '#read/#on_data' do
224
+ it 'reads a chunk from the socket' do
225
+ socket.should_receive(:read_nonblock).with(instance_of(Fixnum)).and_return('foo bar')
226
+ handler.read
240
227
  end
241
228
 
242
- it 'closes the socket' do
243
- socket.should_receive(:close)
229
+ it 'calls the data listener with the new data' do
230
+ socket.should_receive(:read_nonblock).with(instance_of(Fixnum)).and_return('foo bar')
231
+ data = nil
232
+ handler.on_data { |d| data = d }
244
233
  handler.read
234
+ data.should == 'foo bar'
245
235
  end
246
236
 
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)
237
+ context 'when #read_nonblock raises an error' do
238
+ before do
239
+ socket.stub(:close)
240
+ socket.stub(:read_nonblock).and_raise('Bork!')
241
+ end
242
+
243
+ it 'closes the socket' do
244
+ socket.should_receive(:close)
245
+ handler.read
246
+ end
247
+
248
+ it 'passes the error to the close handler' do
249
+ error = nil
250
+ handler.on_closed { |e| error = e }
251
+ handler.read
252
+ error.should be_a(Exception)
253
+ end
252
254
  end
253
255
  end
254
256
  end
@@ -29,7 +29,11 @@ module Ione
29
29
  end
30
30
 
31
31
  def fake_connected(connection)
32
- connection.to_io.stub(:connect_nonblock)
32
+ if connection.is_a?(SslConnection)
33
+ connection.instance_variable_get(:@io).stub(:connect_nonblock)
34
+ else
35
+ connection.to_io.stub(:connect_nonblock)
36
+ end
33
37
  end
34
38
 
35
39
  after do
@@ -159,6 +163,18 @@ module Ione
159
163
  x.should == :foo
160
164
  end
161
165
 
166
+ it 'defaults to 5 as the connection timeout' do
167
+ reactor.start.value
168
+ connection = reactor.connect('example.com', 9999).value
169
+ connection.connection_timeout.should == 5
170
+ end
171
+
172
+ it 'takes the connection timeout from the :timeout option' do
173
+ reactor.start.value
174
+ connection = reactor.connect('example.com', 9999, timeout: 9).value
175
+ connection.connection_timeout.should == 9
176
+ end
177
+
162
178
  it 'returns the connection when no block is given' do
163
179
  reactor.start.value
164
180
  reactor.connect('example.com', 9999, 5).value.should be_a(Connection)
@@ -170,6 +186,19 @@ module Ione
170
186
  await { selector.last_arguments[0].length > 1 }
171
187
  selector.last_arguments[0].should include(connection)
172
188
  end
189
+
190
+ it 'upgrades the connection to SSL' do
191
+ reactor.start.value
192
+ connection = reactor.connect('example.com', 9999, ssl: true).value
193
+ connection.should be_a(SslConnection)
194
+ end
195
+
196
+ it 'passes an SSL context to the SSL connection' do
197
+ ssl_context = double(:ssl_context)
198
+ reactor.start.value
199
+ f = reactor.connect('example.com', 9999, ssl: ssl_context)
200
+ expect { f.value }.to raise_error
201
+ end
173
202
  end
174
203
 
175
204
  describe '#bind' do
@@ -192,6 +221,18 @@ module Ione
192
221
  x.should == :foo
193
222
  end
194
223
 
224
+ it 'defaults to a backlog of 5' do
225
+ reactor.start.value
226
+ acceptor = reactor.bind(ENV['SERVER_HOST'], port).value
227
+ acceptor.backlog.should == 5
228
+ end
229
+
230
+ it 'takes the backlog from the :backlog option' do
231
+ reactor.start.value
232
+ acceptor = reactor.bind(ENV['SERVER_HOST'], port, backlog: 9).value
233
+ acceptor.backlog.should == 9
234
+ end
235
+
195
236
  it 'returns the acceptor when no block is given' do
196
237
  reactor.start.value
197
238
  acceptor = reactor.bind(ENV['SERVER_HOST'], port, 5).value
@@ -201,9 +242,16 @@ module Ione
201
242
  it 'creates an acceptor and passes it to the selector as a readable' do
202
243
  reactor.start.value
203
244
  acceptor = reactor.bind(ENV['SERVER_HOST'], port, 5).value
204
- await { selector.last_arguments[0].length > 1 }
245
+ await { selector.last_arguments && selector.last_arguments[0].length > 1 }
205
246
  selector.last_arguments[0].should include(acceptor)
206
247
  end
248
+
249
+ it 'creates an SSL acceptor' do
250
+ ssl_context = double(:ssl_context)
251
+ reactor.start.value
252
+ acceptor = reactor.bind(ENV['SERVER_HOST'], port, ssl: ssl_context).value
253
+ acceptor.should be_an(SslAcceptor)
254
+ end
207
255
  end
208
256
 
209
257
  describe '#schedule_timer' do
@@ -0,0 +1,116 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module Ione
7
+ module Io
8
+ describe SslAcceptor do
9
+ let :acceptor do
10
+ described_class.new('example.com', 4321, backlog = 3, unblocker, reactor, ssl_context, socket_impl, ssl_socket_impl)
11
+ end
12
+
13
+ let :unblocker do
14
+ double(:unblocker)
15
+ end
16
+
17
+ let :reactor do
18
+ double(:reactor)
19
+ end
20
+
21
+ let :ssl_context do
22
+ double(:ssl_context)
23
+ end
24
+
25
+ let :socket_impl do
26
+ double(:socket_impl)
27
+ end
28
+
29
+ let :ssl_socket_impl do
30
+ double(:ssl_socket_impl)
31
+ end
32
+
33
+ let :socket do
34
+ double(:socket)
35
+ end
36
+
37
+ let :ssl_socket do
38
+ double(:ssl_socket)
39
+ end
40
+
41
+ let :client_socket do
42
+ double(:client_socket)
43
+ end
44
+
45
+ describe '#read' do
46
+ let :accepted_handlers do
47
+ []
48
+ end
49
+
50
+ before do
51
+ socket_impl.stub(:getaddrinfo).and_return([[nil, 'PORT', nil, 'IP1', 'FAMILY1', 'TYPE1']])
52
+ socket_impl.stub(:sockaddr_in).with('PORT', 'IP1').and_return('SOCKADDRX')
53
+ socket_impl.stub(:unpack_sockaddr_in).with('SOCKADDRX').and_return([3333, 'example.com'])
54
+ socket_impl.stub(:new).and_return(socket)
55
+ socket.stub(:bind)
56
+ socket.stub(:listen)
57
+ socket.stub(:accept_nonblock).and_return([client_socket, 'SOCKADDRX'])
58
+ reactor.stub(:accept) { |h| accepted_handlers << h }
59
+ ssl_socket_impl.stub(:new).with(client_socket, ssl_context).and_return(ssl_socket)
60
+ ssl_socket.stub(:accept_nonblock)
61
+ end
62
+
63
+ it 'accepts a new connection' do
64
+ acceptor.bind
65
+ acceptor.read
66
+ socket.should have_received(:accept_nonblock)
67
+ end
68
+
69
+ it 'notifies the accept handlers' do
70
+ connection = nil
71
+ acceptor.on_accept { |c| connection = c }
72
+ acceptor.bind
73
+ acceptor.read
74
+ accepted_handlers.first.read
75
+ connection.should equal(accepted_handlers.first)
76
+ end
77
+
78
+ context 'creates a new connection handler that' do
79
+ it 'is registered it with the reactor' do
80
+ acceptor.bind
81
+ acceptor.read
82
+ accepted_handlers.should have(1).item
83
+ accepted_handlers.first.host.should == 'example.com'
84
+ accepted_handlers.first.port.should == 3333
85
+ end
86
+
87
+ it 'returns the raw socket from #to_io' do
88
+ ssl_socket.stub(:to_io).and_return(client_socket)
89
+ acceptor.bind
90
+ acceptor.read
91
+ accepted_handlers.first.to_io.should equal(client_socket)
92
+ end
93
+
94
+ it 'has a reference to the unblocker' do
95
+ unblocker.stub(:unblock!)
96
+ acceptor.bind
97
+ acceptor.read
98
+ accepted_handlers.first.write('foo')
99
+ unblocker.should have_received(:unblock!)
100
+ end
101
+
102
+ it 'writes to the SSL socket' do
103
+ unblocker.stub(:unblock!)
104
+ ssl_socket.stub(:write_nonblock) { |b| b.bytesize }
105
+ acceptor.bind
106
+ acceptor.read
107
+ accepted_handlers.first.read
108
+ accepted_handlers.first.write('foo')
109
+ accepted_handlers.first.flush
110
+ ssl_socket.should have_received(:write_nonblock).with('foo')
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,165 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require 'ione/io/connection_common'
5
+
6
+
7
+ module Ione
8
+ module Io
9
+ describe SslConnection do
10
+ let :handler do
11
+ described_class.new('example.com', 55555, raw_socket, unblocker, ssl_context, socket_impl)
12
+ end
13
+
14
+ let :socket_impl do
15
+ double(:socket_impl)
16
+ end
17
+
18
+ let :raw_socket do
19
+ double(:raw_socket)
20
+ end
21
+
22
+ let :ssl_socket do
23
+ double(:ssl_socket)
24
+ end
25
+
26
+ let :unblocker do
27
+ double(:unblocker, unblock!: nil)
28
+ end
29
+
30
+ let :ssl_context do
31
+ double(:ssl_context)
32
+ end
33
+
34
+ before do
35
+ socket_impl.stub(:new).with(raw_socket, ssl_context).and_return(ssl_socket)
36
+ end
37
+
38
+ before do
39
+ ssl_socket.stub(:connect_nonblock)
40
+ ssl_socket.stub(:close)
41
+ end
42
+
43
+ it_behaves_like 'a connection', skip_read: true do
44
+ let :socket do
45
+ ssl_socket
46
+ end
47
+
48
+ before do
49
+ handler.connect
50
+ end
51
+ end
52
+
53
+ describe '#connect' do
54
+ it 'creates an SSL socket and passes in the specified SSL context' do
55
+ handler.connect
56
+ socket_impl.should have_received(:new).with(raw_socket, ssl_context)
57
+ end
58
+
59
+ it 'does not pass the context parameter when the SSL context is nil' do
60
+ socket_impl.stub(:new).and_return(ssl_socket)
61
+ h = described_class.new('example.com', 55555, raw_socket, unblocker, nil, socket_impl)
62
+ h.connect
63
+ socket_impl.should have_received(:new).with(raw_socket)
64
+ end
65
+
66
+ it 'calls #connect_nonblock on the SSL socket' do
67
+ handler.connect
68
+ ssl_socket.should have_received(:connect_nonblock)
69
+ end
70
+
71
+ it 'does nothing when the socket raises a "would block" error' do
72
+ error = OpenSSL::SSL::SSLError.new('would block')
73
+ error.extend(IO::WaitReadable)
74
+ ssl_socket.stub(:connect_nonblock).and_raise(error)
75
+ expect { handler.connect }.to_not raise_error
76
+ end
77
+
78
+ it 'returns a future that resolves when the socket is connected' do
79
+ error = OpenSSL::SSL::SSLError.new('would block')
80
+ error.extend(IO::WaitReadable)
81
+ ssl_socket.stub(:connect_nonblock).and_raise(error)
82
+ f = handler.connect
83
+ f.should_not be_resolved
84
+ ssl_socket.stub(:connect_nonblock).and_return(nil)
85
+ handler.connect
86
+ f.should be_resolved
87
+ end
88
+
89
+ it 'is connected when #connect_nonblock does not raise' do
90
+ handler.connect
91
+ handler.should be_connected
92
+ end
93
+
94
+ it 'fails when #connect_nonblock raises an error that does not include the words "would block"' do
95
+ ssl_socket.stub(:connect_nonblock).and_raise(OpenSSL::SSL::SSLError.new('general bork'))
96
+ expect { handler.connect.value }.to raise_error(Ione::Io::ConnectionError)
97
+ end
98
+
99
+ it 'is closed when #connect_nonblock raises something that is not a "would block" error' do
100
+ ssl_socket.stub(:connect_nonblock).and_raise(OpenSSL::SSL::SSLError.new('general bork'))
101
+ handler.connect
102
+ handler.should be_closed
103
+ end
104
+ end
105
+
106
+ describe '#to_io' do
107
+ it 'returns the raw socket' do
108
+ handler.to_io.should equal(raw_socket)
109
+ end
110
+ end
111
+
112
+ describe '#read' do
113
+ if RUBY_ENGINE == 'jruby'
114
+ it 'reads chunks until #read_nonblock raises OpenSSL::SSL::SSLErrorWaitReadable' do
115
+ read_sizes = []
116
+ counter = 3
117
+ ssl_socket.stub(:read_nonblock) do |read_size|
118
+ read_sizes << read_size
119
+ if counter == 0
120
+ raise OpenSSL::SSL::SSLErrorWaitReadable, 'read would block'
121
+ end
122
+ counter -= 1
123
+ 'bar'
124
+ end
125
+ handler.connect
126
+ handler.read
127
+ read_sizes.drop(1).should == [read_sizes.first] * 3
128
+ end
129
+ else
130
+ it 'reads and initial chunk of data' do
131
+ data = []
132
+ handler.on_data { |d| data << d }
133
+ ssl_socket.stub(:pending).and_return(0)
134
+ ssl_socket.stub(:read_nonblock).and_return('fooo')
135
+ handler.connect
136
+ handler.read
137
+ data.should == ['fooo']
138
+ end
139
+
140
+ it 'reads once, and then again with the value of #pending, until #pending returns zero' do
141
+ read_sizes = []
142
+ counter = 3
143
+ ssl_socket.stub(:pending).and_return(0)
144
+ ssl_socket.stub(:read_nonblock) do |read_size|
145
+ read_sizes << read_size
146
+ ssl_socket.stub(:pending).and_return(counter)
147
+ counter -= 1
148
+ 'bar'
149
+ end
150
+ handler.connect
151
+ handler.read
152
+ read_sizes.drop(1).should == [3, 2, 1]
153
+ end
154
+ end
155
+
156
+ it 'closes the connection when an error occurs' do
157
+ ssl_socket.stub(:read_nonblock).and_raise('boork')
158
+ handler.connect
159
+ handler.read
160
+ handler.should be_closed
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end