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,360 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module Ione
7
+ module Io
8
+ describe IoReactor do
9
+ let :reactor do
10
+ described_class.new(selector: selector, clock: clock)
11
+ end
12
+
13
+ let! :selector do
14
+ IoReactorSpec::FakeSelector.new
15
+ end
16
+
17
+ let :clock do
18
+ double(:clock, now: 0)
19
+ end
20
+
21
+ shared_context 'running_reactor' do
22
+ before do
23
+ selector.handler do |readables, writables, _, _|
24
+ writables.each do |writable|
25
+ fake_connected(writable)
26
+ end
27
+ [[], writables, []]
28
+ end
29
+ end
30
+
31
+ def fake_connected(connection)
32
+ connection.to_io.stub(:connect_nonblock)
33
+ end
34
+
35
+ after do
36
+ reactor.stop if reactor.running?
37
+ end
38
+ end
39
+
40
+ describe '#start' do
41
+ after do
42
+ reactor.stop.value if reactor.running?
43
+ end
44
+
45
+ it 'returns a future that is resolved when the reactor has started' do
46
+ reactor.start.value
47
+ end
48
+
49
+ it 'returns a future that resolves to the reactor' do
50
+ reactor.start.value.should equal(reactor)
51
+ end
52
+
53
+ it 'is running after being started' do
54
+ reactor.start.value
55
+ reactor.should be_running
56
+ end
57
+
58
+ it 'cannot be started again once stopped' do
59
+ reactor.start.value
60
+ reactor.stop.value
61
+ expect { reactor.start }.to raise_error(ReactorError)
62
+ end
63
+
64
+ it 'calls the selector' do
65
+ called = false
66
+ selector.handler { called = true; [[], [], []] }
67
+ reactor.start.value
68
+ await { called }
69
+ reactor.stop.value
70
+ called.should be_true, 'expected the selector to have been called'
71
+ end
72
+ end
73
+
74
+ describe '#stop' do
75
+ include_context 'running_reactor'
76
+
77
+ it 'returns a future that is resolved when the reactor has stopped' do
78
+ reactor.start.value
79
+ reactor.stop.value
80
+ end
81
+
82
+ it 'returns a future which resolves to the reactor' do
83
+ reactor.start.value
84
+ reactor.stop.value.should equal(reactor)
85
+ end
86
+
87
+ it 'is not running after being stopped' do
88
+ reactor.start.value
89
+ reactor.stop.value
90
+ reactor.should_not be_running
91
+ end
92
+
93
+ it 'closes all sockets' do
94
+ reactor.start.value
95
+ connection = reactor.connect('example.com', 9999, 5).value
96
+ reactor.stop.value
97
+ connection.should be_closed
98
+ end
99
+
100
+ it 'cancels all active timers' do
101
+ reactor.start.value
102
+ clock.stub(:now).and_return(1)
103
+ expired_timer = reactor.schedule_timer(1)
104
+ active_timer1 = reactor.schedule_timer(999)
105
+ active_timer2 = reactor.schedule_timer(111)
106
+ expired_timer.should_not_receive(:fail)
107
+ clock.stub(:now).and_return(2)
108
+ await { expired_timer.completed? }
109
+ reactor.stop.value
110
+ active_timer1.should be_failed
111
+ active_timer2.should be_failed
112
+ end
113
+ end
114
+
115
+ describe '#on_error' do
116
+ before do
117
+ selector.handler { raise 'Blurgh' }
118
+ end
119
+
120
+ it 'calls the listeners when the reactor crashes' do
121
+ error = nil
122
+ reactor.on_error { |e| error = e }
123
+ reactor.start
124
+ await { error }
125
+ error.message.should == 'Blurgh'
126
+ end
127
+
128
+ it 'calls the listener immediately when the reactor has already crashed' do
129
+ error = nil
130
+ reactor.start.value
131
+ await { !reactor.running? }
132
+ reactor.on_error { |e| error = e }
133
+ await { error }
134
+ end
135
+
136
+ it 'ignores errors raised by listeners' do
137
+ called = false
138
+ reactor.on_error { raise 'Blurgh' }
139
+ reactor.on_error { called = true }
140
+ reactor.start
141
+ await { called }
142
+ called.should be_true, 'expected all close listeners to have been called'
143
+ end
144
+ end
145
+
146
+ describe '#connect' do
147
+ include_context 'running_reactor'
148
+
149
+ it 'returns the connected connection when no block is given' do
150
+ reactor.start.value
151
+ reactor.connect('example.com', 9999, 5).value.should be_a(Connection)
152
+ end
153
+ end
154
+
155
+ describe '#schedule_timer' do
156
+ before do
157
+ reactor.start.value
158
+ end
159
+
160
+ after do
161
+ reactor.stop.value
162
+ end
163
+
164
+ it 'returns a future that is resolved after the specified duration' do
165
+ clock.stub(:now).and_return(1)
166
+ f = reactor.schedule_timer(0.1)
167
+ clock.stub(:now).and_return(1.1)
168
+ await { f.resolved? }
169
+ end
170
+ end
171
+
172
+ describe '#to_s' do
173
+ context 'returns a string that' do
174
+ it 'includes the class name' do
175
+ reactor.to_s.should include('Ione::Io::IoReactor')
176
+ end
177
+
178
+ it 'includes a list of its connections' do
179
+ reactor.to_s.should include('@connections=[')
180
+ reactor.to_s.should include('#<Ione::Io::Unblocker>')
181
+ end
182
+ end
183
+ end
184
+ end
185
+
186
+ describe IoLoopBody do
187
+ let :loop_body do
188
+ described_class.new(selector: selector, clock: clock)
189
+ end
190
+
191
+ let :selector do
192
+ double(:selector)
193
+ end
194
+
195
+ let :clock do
196
+ double(:clock, now: 0)
197
+ end
198
+
199
+ let :socket do
200
+ double(:socket, connected?: false, connecting?: false, writable?: false, closed?: false)
201
+ end
202
+
203
+ describe '#tick' do
204
+ before do
205
+ loop_body.add_socket(socket)
206
+ end
207
+
208
+ it 'passes connected sockets as readables to the selector' do
209
+ socket.stub(:connected?).and_return(true)
210
+ selector.should_receive(:select).with([socket], anything, anything, anything).and_return([nil, nil, nil])
211
+ loop_body.tick
212
+ end
213
+
214
+ it 'passes writable sockets as writable to the selector' do
215
+ socket.stub(:writable?).and_return(true)
216
+ selector.should_receive(:select).with(anything, [socket], anything, anything).and_return([nil, nil, nil])
217
+ loop_body.tick
218
+ end
219
+
220
+ it 'passes connecting sockets as writable to the selector' do
221
+ socket.stub(:connecting?).and_return(true)
222
+ socket.stub(:connect)
223
+ selector.should_receive(:select).with(anything, [socket], anything, anything).and_return([nil, nil, nil])
224
+ loop_body.tick
225
+ end
226
+
227
+ it 'filters out closed sockets' do
228
+ socket.stub(:closed?).and_return(true)
229
+ selector.should_receive(:select).with([], [], anything, anything).and_return([nil, nil, nil])
230
+ loop_body.tick
231
+ socket.stub(:connected?).and_return(true)
232
+ selector.should_receive(:select).with([], [], anything, anything).and_return([nil, nil, nil])
233
+ loop_body.tick
234
+ end
235
+
236
+ it 'calls #read on all readable sockets returned by the selector' do
237
+ socket.stub(:connected?).and_return(true)
238
+ socket.should_receive(:read)
239
+ selector.stub(:select) do |r, w, _, _|
240
+ [[socket], nil, nil]
241
+ end
242
+ loop_body.tick
243
+ end
244
+
245
+ it 'calls #connect on all connecting sockets' do
246
+ socket.stub(:connecting?).and_return(true)
247
+ socket.should_receive(:connect)
248
+ selector.stub(:select).and_return([nil, nil, nil])
249
+ loop_body.tick
250
+ end
251
+
252
+ it 'calls #flush on all writable sockets returned by the selector' do
253
+ socket.stub(:writable?).and_return(true)
254
+ socket.should_receive(:flush)
255
+ selector.stub(:select) do |r, w, _, _|
256
+ [nil, [socket], nil]
257
+ end
258
+ loop_body.tick
259
+ end
260
+
261
+ it 'allows the caller to specify a custom timeout' do
262
+ selector.should_receive(:select).with(anything, anything, anything, 99).and_return([[], [], []])
263
+ loop_body.tick(99)
264
+ end
265
+
266
+ it 'completes timers that have expired' do
267
+ selector.stub(:select).and_return([nil, nil, nil])
268
+ clock.stub(:now).and_return(1)
269
+ promise = Promise.new
270
+ loop_body.schedule_timer(1, promise)
271
+ loop_body.tick
272
+ promise.future.should_not be_completed
273
+ clock.stub(:now).and_return(2)
274
+ loop_body.tick
275
+ promise.future.should be_completed
276
+ end
277
+
278
+ it 'clears out timers that have expired' do
279
+ selector.stub(:select).and_return([nil, nil, nil])
280
+ clock.stub(:now).and_return(1)
281
+ promise = Promise.new
282
+ loop_body.schedule_timer(1, promise)
283
+ clock.stub(:now).and_return(2)
284
+ loop_body.tick
285
+ promise.future.should be_completed
286
+ promise.should_not_receive(:fulfill)
287
+ loop_body.tick
288
+ end
289
+ end
290
+
291
+ describe '#close_sockets' do
292
+ it 'closes all sockets' do
293
+ socket1 = double(:socket1, closed?: false)
294
+ socket2 = double(:socket2, closed?: false)
295
+ socket1.should_receive(:close)
296
+ socket2.should_receive(:close)
297
+ loop_body.add_socket(socket1)
298
+ loop_body.add_socket(socket2)
299
+ loop_body.close_sockets
300
+ end
301
+
302
+ it 'closes all sockets, even when one of them raises an error' do
303
+ socket1 = double(:socket1, closed?: false)
304
+ socket2 = double(:socket2, closed?: false)
305
+ socket1.stub(:close).and_raise('Blurgh')
306
+ socket2.should_receive(:close)
307
+ loop_body.add_socket(socket1)
308
+ loop_body.add_socket(socket2)
309
+ loop_body.close_sockets
310
+ end
311
+
312
+ it 'does not close already closed sockets' do
313
+ socket.stub(:closed?).and_return(true)
314
+ socket.should_not_receive(:close)
315
+ loop_body.add_socket(socket)
316
+ loop_body.close_sockets
317
+ end
318
+ end
319
+
320
+ describe '#cancel_timers' do
321
+ before do
322
+ selector.stub(:select).and_return([nil, nil, nil])
323
+ end
324
+
325
+ it 'fails all active timers with a CancelledError' do
326
+ p1 = Promise.new
327
+ p2 = Promise.new
328
+ p3 = Promise.new
329
+ clock.stub(:now).and_return(1)
330
+ loop_body.schedule_timer(1, p1)
331
+ loop_body.schedule_timer(3, p2)
332
+ loop_body.schedule_timer(3, p3)
333
+ clock.stub(:now).and_return(2)
334
+ loop_body.tick
335
+ loop_body.cancel_timers
336
+ p1.future.should be_completed
337
+ p2.future.should be_failed
338
+ p3.future.should be_failed
339
+ expect { p3.future.value }.to raise_error(CancelledError)
340
+ end
341
+ end
342
+ end
343
+ end
344
+ end
345
+
346
+ module IoReactorSpec
347
+ class FakeSelector
348
+ def initialize
349
+ handler { [[], [], []] }
350
+ end
351
+
352
+ def handler(&body)
353
+ @body = body
354
+ end
355
+
356
+ def select(*args)
357
+ @body.call(*args)
358
+ end
359
+ end
360
+ end
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+
3
+ require 'ione'
4
+
5
+ require 'support/fake_server'
6
+ require 'support/await_helper'
7
+
8
+ ENV['SERVER_HOST'] ||= '127.0.0.1'.freeze
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ module AwaitHelper
4
+ def await(timeout=5, &test)
5
+ started_at = Time.now
6
+ until test.call
7
+ yield
8
+ time_taken = Time.now - started_at
9
+ if time_taken > timeout
10
+ fail('Test took more than %.1fs' % [time_taken.to_f])
11
+ else
12
+ sleep(0.01)
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ RSpec.configure do |c|
19
+ c.include(AwaitHelper)
20
+ end
@@ -0,0 +1,106 @@
1
+ # encoding: utf-8
2
+
3
+ class FakeServer
4
+ attr_reader :port, :connects, :disconnects
5
+
6
+ def initialize(port=(2**15 + rand(2**15)))
7
+ @port = port
8
+ @state = {}
9
+ @lock = Mutex.new
10
+ @connects = 0
11
+ @disconnects = 0
12
+ @connections = []
13
+ @received_bytes = ''
14
+ end
15
+
16
+ def start!(options={})
17
+ @lock.synchronize do
18
+ return if @running
19
+ @running = true
20
+ end
21
+ @sockets = [TCPServer.new(@port)]
22
+ @started = Ione::Promise.new
23
+ @thread = Thread.start do
24
+ Thread.current.abort_on_exception = true
25
+ sleep(options[:accept_delay] || 0)
26
+ @started.fulfill
27
+ io_loop
28
+ end
29
+ @started.future.value
30
+ self
31
+ end
32
+
33
+ def stop!
34
+ @lock.synchronize do
35
+ return unless @running
36
+ @running = false
37
+ end
38
+ if defined? @started
39
+ @thread.join
40
+ @sockets.each(&:close)
41
+ end
42
+ end
43
+
44
+ def broadcast!(bytes)
45
+ @lock.synchronize do
46
+ @connections.each { |c| c.write_nonblock(bytes) }
47
+ end
48
+ end
49
+
50
+ def await_connects!(n=1)
51
+ started_at = Time.now
52
+ until @connects >= n
53
+ sleep(0.01)
54
+ raise 'Waited longer than 5s!' if (Time.now - started_at) > 5
55
+ end
56
+ end
57
+
58
+ def await_disconnects!(n=1)
59
+ started_at = Time.now
60
+ until @disconnects >= n
61
+ sleep(0.01)
62
+ raise 'Waited longer than 5s!' if (Time.now - started_at) > 5
63
+ end
64
+ end
65
+
66
+ def received_bytes
67
+ @lock.synchronize do
68
+ return @received_bytes.dup
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def io_loop
75
+ while @running
76
+ acceptables, _ = IO.select(@sockets, @connections, nil, 0)
77
+ readables, writables, _ = IO.select(@connections, @connections, nil, 0)
78
+
79
+ if acceptables
80
+ acceptables.each do |socket|
81
+ connection, _ = socket.accept_nonblock
82
+ @lock.synchronize do
83
+ @connects += 1
84
+ @connections << connection
85
+ end
86
+ end
87
+ end
88
+
89
+ if readables
90
+ readables.each do |readable|
91
+ begin
92
+ bytes = readable.read_nonblock(2**16)
93
+ @lock.synchronize do
94
+ @received_bytes << bytes
95
+ end
96
+ rescue EOFError
97
+ @lock.synchronize do
98
+ @connections.delete(readable)
99
+ @disconnects += 1
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end