ione 1.2.0.pre1 → 1.2.0.pre2

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.
@@ -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