ione 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -822,6 +822,17 @@ module Ione
822
822
  future.value.should == {'foo' => 'bar', 'qux' => 'baz', 'hello' => 'world'}
823
823
  end
824
824
 
825
+ it 'accepts boolean accumulators' do
826
+ futures = [
827
+ Future.resolved([:foo]),
828
+ Future.resolved([]),
829
+ ]
830
+ future = Future.reduce(futures, false) do |accumulator, value|
831
+ accumulator || value.empty?
832
+ end
833
+ future.value.should == true
834
+ end
835
+
825
836
  it 'calls the block with the values in the order of the source futures' do
826
837
  promises = [Promise.new, Promise.new, Promise.new, Promise.new, Promise.new]
827
838
  futures = promises.map(&:future)
@@ -868,6 +879,18 @@ module Ione
868
879
  future.should be_failed
869
880
  end
870
881
 
882
+ it 'allows invocations to return nil' do
883
+ futures = [Future.resolved(1), Future.resolved(2), Future.resolved(3)]
884
+ future = Future.reduce(futures, []) do |accumulator, value|
885
+ if value == 2
886
+ nil
887
+ else
888
+ value
889
+ end
890
+ end
891
+ future.value.should eq(3)
892
+ end
893
+
871
894
  context 'when the list of futures is empty' do
872
895
  it 'returns a future that resolves to the initial value' do
873
896
  Future.reduce([], :foo).value.should == :foo
@@ -938,6 +961,95 @@ module Ione
938
961
  end
939
962
  end
940
963
 
964
+ describe '.after' do
965
+ context 'returns a new future which' do
966
+ it 'is resolved when the source futures are resolved' do
967
+ p1 = Promise.new
968
+ p2 = Promise.new
969
+ f = Future.after(p1.future, p2.future)
970
+ p1.fulfill
971
+ f.should_not be_resolved
972
+ p2.fulfill
973
+ f.should be_resolved
974
+ end
975
+
976
+ it 'returns no value' do
977
+ p1 = Promise.new
978
+ p2 = Promise.new
979
+ p3 = Promise.new
980
+ f = Future.after(p1.future, p2.future, p3.future)
981
+ p2.fulfill(2)
982
+ p1.fulfill(1)
983
+ p3.fulfill(3)
984
+ f.value.should be_nil
985
+ end
986
+
987
+ it 'fails if any of the source futures fail' do
988
+ p1 = Promise.new
989
+ p2 = Promise.new
990
+ p3 = Promise.new
991
+ p4 = Promise.new
992
+ f = Future.after(p1.future, p2.future, p3.future, p4.future)
993
+ p2.fulfill
994
+ p1.fail(StandardError.new('hurgh'))
995
+ p3.fail(StandardError.new('murgasd'))
996
+ p4.fulfill
997
+ expect { f.value }.to raise_error(/hurgh|murgasd/)
998
+ f.should be_failed
999
+ end
1000
+
1001
+ it 'completes with nil when no futures are given' do
1002
+ Future.after.value.should be_nil
1003
+ end
1004
+
1005
+ it 'completes with nil when an empty list is given' do
1006
+ Future.after([]).value.should be_nil
1007
+ end
1008
+
1009
+ it 'completes with nil when an empty enumerable is given' do
1010
+ Future.after([].to_enum).value.should be_nil
1011
+ end
1012
+
1013
+ it 'completes with nil when a single future is given' do
1014
+ f = Future.resolved(1)
1015
+ Future.after(f).value.should be_nil
1016
+ end
1017
+
1018
+ it 'accepts a list of futures' do
1019
+ promises = [Promise.new, Promise.new, Promise.new]
1020
+ futures = promises.map(&:future)
1021
+ f = Future.after(futures)
1022
+ promises.each(&:fulfill)
1023
+ f.value.should be_nil
1024
+ end
1025
+
1026
+ it 'accepts an enumerable of futures' do
1027
+ promises = [Promise.new, Promise.new, Promise.new]
1028
+ futures = promises.map(&:future).to_enum
1029
+ f = Future.after(futures)
1030
+ promises.each(&:fulfill)
1031
+ f.value.should be_nil
1032
+ end
1033
+
1034
+ it 'accepts an enumerable of one future' do
1035
+ promises = [Promise.new]
1036
+ futures = promises.map(&:future).to_enum
1037
+ f = Future.after(futures)
1038
+ promises.each(&:fulfill)
1039
+ f.value.should be_nil
1040
+ end
1041
+
1042
+ it 'accepts anything that implements #on_complete as futures' do
1043
+ ff1, ff2, ff3 = double, double, double
1044
+ ff1.stub(:on_complete) { |&listener| listener.call(1, nil) }
1045
+ ff2.stub(:on_complete) { |&listener| listener.call(2, nil) }
1046
+ ff3.stub(:on_complete) { |&listener| listener.call(3, nil) }
1047
+ future = Future.after(ff1, ff2, ff3)
1048
+ future.value.should be_nil
1049
+ end
1050
+ end
1051
+ end
1052
+
941
1053
  describe '.all' do
942
1054
  context 'returns a new future which' do
943
1055
  it 'is resolved when the source futures are resolved' do
@@ -312,15 +312,15 @@ module Ione
312
312
  end
313
313
 
314
314
  it 'includes the connection state' do
315
- handler.to_s.should include('connecting')
315
+ handler.to_s.should include('CONNECTING')
316
316
  socket.stub(:connect_nonblock).and_raise(Errno::EINPROGRESS)
317
317
  handler.connect
318
- handler.to_s.should include('connecting')
318
+ handler.to_s.should include('CONNECTING')
319
319
  socket.stub(:connect_nonblock)
320
320
  handler.connect
321
- handler.to_s.should include('connected')
321
+ handler.to_s.should include('CONNECTED')
322
322
  handler.close
323
- handler.to_s.should include('closed')
323
+ handler.to_s.should include('CLOSED')
324
324
  end
325
325
  end
326
326
  end
@@ -59,12 +59,6 @@ module Ione
59
59
  reactor.should be_running
60
60
  end
61
61
 
62
- it 'cannot be started again once stopped' do
63
- reactor.start.value
64
- reactor.stop.value
65
- expect { reactor.start }.to raise_error(ReactorError)
66
- end
67
-
68
62
  it 'calls the selector' do
69
63
  called = false
70
64
  selector.handler { called = true; [[], [], []] }
@@ -73,6 +67,95 @@ module Ione
73
67
  reactor.stop.value
74
68
  called.should be_true, 'expected the selector to have been called'
75
69
  end
70
+
71
+ context 'when stopping' do
72
+ it 'waits for the reactor to stop, then starts it again' do
73
+ barrier = Queue.new
74
+ selector.handler do
75
+ barrier.pop
76
+ [[], [], []]
77
+ end
78
+ reactor.start.value
79
+ stopped_future = reactor.stop
80
+ restarted_future = reactor.start
81
+ sequence = []
82
+ stopped_future.on_complete { sequence << :stopped }
83
+ restarted_future.on_complete { sequence << :restarted }
84
+ barrier.push(nil)
85
+ stopped_future.value
86
+ restarted_future.value
87
+ begin
88
+ sequence.should == [:stopped, :restarted]
89
+ ensure
90
+ reactor.stop
91
+ barrier.push(nil) while reactor.running?
92
+ end
93
+ end
94
+
95
+ it 'restarts the reactor even when restarted before a failed stop' do
96
+ barrier = Queue.new
97
+ selector.handler do
98
+ if barrier.pop == :fail
99
+ raise 'Blurgh'
100
+ else
101
+ [[], [], []]
102
+ end
103
+ end
104
+ reactor.start.value
105
+ stopped_future = reactor.stop
106
+ restarted_future = reactor.start
107
+ crashed = false
108
+ restarted = false
109
+ stopped_future.on_failure { crashed = true }
110
+ restarted_future.on_complete { restarted = true }
111
+ barrier.push(:fail)
112
+ stopped_future.value rescue nil
113
+ restarted_future.value
114
+ begin
115
+ crashed.should be_true
116
+ restarted.should be_true
117
+ ensure
118
+ reactor.stop
119
+ barrier.push(nil) while reactor.running?
120
+ end
121
+ end
122
+ end
123
+
124
+ context 'when stopped' do
125
+ before do
126
+ reactor.start.value
127
+ reactor.stop.value
128
+ end
129
+
130
+ it 'starts the reactor again' do
131
+ reactor.start.value
132
+ reactor.should be_running
133
+ end
134
+ end
135
+
136
+ context 'when already started' do
137
+ it 'is not started again' do
138
+ calls = 0
139
+ lock = Mutex.new
140
+ ticks = Queue.new
141
+ barrier = Queue.new
142
+ selector.handler do
143
+ ticks.push(:tick)
144
+ barrier.pop
145
+ [[], [], []]
146
+ end
147
+ reactor.start.value
148
+ reactor.start.value
149
+ reactor.start.value
150
+ begin
151
+ ticks.pop.should_not be_nil
152
+ ticks.size.should be_zero
153
+ ensure
154
+ reactor.stop
155
+ barrier.push(nil) while reactor.running?
156
+ end
157
+ end
158
+ end
76
159
  end
77
160
 
78
161
  describe '#stop' do
@@ -114,6 +197,20 @@ module Ione
114
197
  active_timer1.should be_failed
115
198
  active_timer2.should be_failed
116
199
  end
200
+
201
+ context 'when not started' do
202
+ it 'does nothing' do
203
+ reactor = described_class.new(selector: selector, clock: clock)
204
+ expect { reactor.stop.value }.to_not raise_error
205
+ end
206
+ end
207
+
208
+ context 'when already stopped' do
209
+ it 'does nothing' do
210
+ reactor.stop.value
211
+ expect { reactor.stop.value }.to_not raise_error
212
+ end
213
+ end
117
214
  end
118
215
 
119
216
  describe '#on_error' do
@@ -145,6 +242,36 @@ module Ione
145
242
  await { called }
146
243
  called.should be_true, 'expected all close listeners to have been called'
147
244
  end
245
+
246
+ it 'calls all listeners when the reactor crashes after being restarted' do
247
+ calls = []
248
+ barrier = Queue.new
249
+ selector.handler { barrier.pop; raise 'Blurgh' }
250
+ reactor.on_error { calls << :pre_started }
251
+ reactor.start
252
+ reactor.on_error { calls << :post_started }
253
+ barrier.push(nil)
254
+ await { !reactor.running? }
255
+ reactor.on_error { calls << :pre_restarted }
256
+ calls.should == [
257
+ :pre_started,
258
+ :post_started,
259
+ :pre_restarted,
260
+ ]
261
+ reactor.start
262
+ reactor.on_error { calls << :post_restarted }
263
+ barrier.push(nil)
264
+ await { !reactor.running? }
265
+ calls.should == [
266
+ :pre_started,
267
+ :post_started,
268
+ :pre_restarted,
269
+ :pre_started,
270
+ :post_started,
271
+ :pre_restarted,
272
+ :post_restarted,
273
+ ]
274
+ end
148
275
  end
149
276
 
150
277
  describe '#connect' do
@@ -199,6 +326,24 @@ module Ione
199
326
  f = reactor.connect('example.com', 9999, ssl: ssl_context)
200
327
  expect { f.value }.to raise_error
201
328
  end
329
+
330
+ context 'when called before the reactor is started' do
331
+ it 'waits for the reactor to start' do
332
+ f = reactor.connect('example.com', 9999)
333
+ reactor.start.value
334
+ f.value
335
+ end
336
+ end
337
+
338
+ context 'when called after the reactor has stopped' do
339
+ it 'waits for the reactor to be restarted' do
340
+ reactor.start.value
341
+ reactor.stop.value
342
+ f = reactor.connect('example.com', 9999)
343
+ reactor.start.value
344
+ f.value
345
+ end
346
+ end
202
347
  end
203
348
 
204
349
  describe '#bind' do
@@ -252,22 +397,72 @@ module Ione
252
397
  acceptor = reactor.bind(ENV['SERVER_HOST'], port, ssl: ssl_context).value
253
398
  acceptor.should be_an(SslAcceptor)
254
399
  end
400
+
401
+ context 'when called before the reactor is started' do
402
+ it 'waits for the reactor to start' do
403
+ f = reactor.bind(ENV['SERVER_HOST'], port, 5)
404
+ reactor.start.value
405
+ f.value
406
+ end
407
+ end
408
+
409
+ context 'when called after the reactor has stopped' do
410
+ it 'waits for the reactor to be restarted' do
411
+ reactor.start.value
412
+ reactor.stop.value
413
+ f = reactor.bind(ENV['SERVER_HOST'], port, 5)
414
+ reactor.start.value
415
+ f.value
416
+ end
417
+ end
255
418
  end
256
419
 
257
420
  describe '#schedule_timer' do
258
- before do
259
- reactor.start.value
421
+ context 'when the reactor is running' do
422
+ before do
423
+ reactor.start.value
424
+ end
425
+
426
+ after do
427
+ reactor.stop.value
428
+ end
429
+
430
+ it 'returns a future that is resolved after the specified duration' do
431
+ clock.stub(:now).and_return(1)
432
+ f = reactor.schedule_timer(0.1)
433
+ clock.stub(:now).and_return(1.1)
434
+ await { f.resolved? }
435
+ end
260
436
  end
261
437
 
262
- after do
263
- reactor.stop.value
438
+ context 'when called before the reactor is started' do
439
+ after do
440
+ reactor.stop.value if reactor.running?
441
+ end
442
+
443
+ it 'waits for the reactor to start' do
444
+ clock.stub(:now).and_return(1)
445
+ f = reactor.schedule_timer(0.1)
446
+ clock.stub(:now).and_return(2)
447
+ reactor.start.value
448
+ f.value
449
+ end
264
450
  end
265
451
 
266
- it 'returns a future that is resolved after the specified duration' do
267
- clock.stub(:now).and_return(1)
268
- f = reactor.schedule_timer(0.1)
269
- clock.stub(:now).and_return(1.1)
270
- await { f.resolved? }
452
+ context 'when called after the reactor has stopped' do
453
+ after do
454
+ reactor.stop.value if reactor.running?
455
+ end
456
+
457
+ it 'waits for the reactor to be restarted' do
458
+ reactor.start.value
459
+ reactor.stop.value
460
+ clock.stub(:now).and_return(1)
461
+ f = reactor.schedule_timer(0.1)
462
+ clock.stub(:now).and_return(2)
463
+ reactor.start.value
464
+ f.value
465
+ end
271
466
  end
272
467
  end
273
468
 
@@ -322,6 +517,29 @@ module Ione
322
517
  it 'does nothing when given nil' do
323
518
  reactor.cancel_timer(nil)
324
519
  end
520
+
521
+ context 'when called before the reactor is started' do
522
+ it 'removes the timer before the reactor starts' do
523
+ clock.stub(:now).and_return(1)
524
+ f = reactor.schedule_timer(0.1)
525
+ reactor.cancel_timer(f)
526
+ clock.stub(:now).and_return(2)
527
+ f.should be_failed
528
+ reactor.start.value
529
+ end
530
+ end
531
+
532
+ context 'when called after the reactor has stopped' do
533
+ it 'removes the timer before the reactor is started again' do
534
+ reactor.start.value
535
+ reactor.stop.value
536
+ clock.stub(:now).and_return(1)
537
+ f = reactor.schedule_timer(0.1)
538
+ reactor.cancel_timer(f)
539
+ f.should be_failed
540
+ reactor.start.value
541
+ end
542
+ end
325
543
  end
326
544
 
327
545
  describe '#to_s' do
@@ -330,9 +548,8 @@ module Ione
330
548
  reactor.to_s.should include('Ione::Io::IoReactor')
331
549
  end
332
550
 
333
- it 'includes a list of its connections' do
334
- reactor.to_s.should include('@connections=[')
335
- reactor.to_s.should include('#<Ione::Io::Unblocker>')
551
+ it 'includes the state' do
552
+ reactor.to_s.should include('PENDING')
336
553
  end
337
554
  end
338
555
  end