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.
- checksums.yaml +4 -4
- data/lib/ione/future.rb +44 -24
- data/lib/ione/io.rb +3 -0
- data/lib/ione/io/acceptor.rb +25 -11
- data/lib/ione/io/base_connection.rb +4 -1
- data/lib/ione/io/connection.rb +1 -4
- data/lib/ione/io/io_reactor.rb +46 -5
- data/lib/ione/io/server_connection.rb +1 -4
- data/lib/ione/io/ssl_acceptor.rb +21 -0
- data/lib/ione/io/ssl_connection.rb +79 -0
- data/lib/ione/io/ssl_server_connection.rb +43 -0
- data/lib/ione/version.rb +1 -1
- data/spec/integration/ssl_spec.rb +97 -0
- data/spec/ione/future_spec.rb +145 -30
- data/spec/ione/io/acceptor_spec.rb +7 -0
- data/spec/ione/io/connection_common.rb +28 -26
- data/spec/ione/io/io_reactor_spec.rb +50 -2
- data/spec/ione/io/ssl_acceptor_spec.rb +116 -0
- data/spec/ione/io/ssl_connection_spec.rb +165 -0
- data/spec/ione/io/ssl_server_connection_spec.rb +108 -0
- metadata +13 -2
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
|
4
|
+
module Ione
|
5
|
+
module Io
|
6
|
+
# @private
|
7
|
+
class SslAcceptor < Acceptor
|
8
|
+
def initialize(host, port, backlog, unblocker, reactor, ssl_context, socket_impl=nil, ssl_socket_impl=nil)
|
9
|
+
super(host, port, backlog, unblocker, reactor, socket_impl)
|
10
|
+
@ssl_context = ssl_context
|
11
|
+
@ssl_socket_impl = ssl_socket_impl
|
12
|
+
end
|
13
|
+
|
14
|
+
def read
|
15
|
+
client_socket, host, port = accept
|
16
|
+
connection = SslServerConnection.new(client_socket, host, port, @unblocker, @ssl_context, method(:notify_accept_listeners), @ssl_socket_impl)
|
17
|
+
@reactor.accept(connection)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
|
6
|
+
module Ione
|
7
|
+
module Io
|
8
|
+
# @private
|
9
|
+
class SslConnection < BaseConnection
|
10
|
+
def initialize(host, port, io, unblocker, ssl_context=nil, socket_impl=OpenSSL::SSL::SSLSocket)
|
11
|
+
super(host, port, unblocker)
|
12
|
+
@socket_impl = socket_impl
|
13
|
+
@ssl_context = ssl_context
|
14
|
+
@raw_io = io
|
15
|
+
@connected_promise = Promise.new
|
16
|
+
on_closed(&method(:cleanup_on_close))
|
17
|
+
end
|
18
|
+
|
19
|
+
def connect
|
20
|
+
if @io.nil? && @ssl_context
|
21
|
+
@io = @socket_impl.new(@raw_io, @ssl_context)
|
22
|
+
elsif @io.nil?
|
23
|
+
@io = @socket_impl.new(@raw_io)
|
24
|
+
end
|
25
|
+
@io.connect_nonblock
|
26
|
+
@state = :connected
|
27
|
+
@connected_promise.fulfill(self)
|
28
|
+
@connected_promise.future
|
29
|
+
rescue IO::WaitReadable, IO::WaitWritable
|
30
|
+
# WaitReadable in JRuby, WaitWritable in MRI
|
31
|
+
@connected_promise.future
|
32
|
+
rescue => e
|
33
|
+
close(e)
|
34
|
+
@connected_promise.future
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_io
|
38
|
+
@raw_io
|
39
|
+
end
|
40
|
+
|
41
|
+
if RUBY_ENGINE == 'jruby'
|
42
|
+
# @private
|
43
|
+
def read
|
44
|
+
while true
|
45
|
+
new_data = @io.read_nonblock(2**16)
|
46
|
+
@data_listener.call(new_data) if @data_listener
|
47
|
+
end
|
48
|
+
rescue IO::WaitReadable
|
49
|
+
# no more data available
|
50
|
+
rescue => e
|
51
|
+
close(e)
|
52
|
+
end
|
53
|
+
else
|
54
|
+
# @private
|
55
|
+
def read
|
56
|
+
read_size = 2**16
|
57
|
+
while read_size > 0
|
58
|
+
new_data = @io.read_nonblock(read_size)
|
59
|
+
@data_listener.call(new_data) if @data_listener
|
60
|
+
read_size = @io.pending
|
61
|
+
end
|
62
|
+
rescue => e
|
63
|
+
close(e)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def cleanup_on_close(cause)
|
70
|
+
if cause && !cause.is_a?(IoError)
|
71
|
+
cause = ConnectionError.new(cause.message)
|
72
|
+
end
|
73
|
+
unless @connected_promise.future.completed?
|
74
|
+
@connected_promise.fail(cause)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Ione
|
4
|
+
module Io
|
5
|
+
# @private
|
6
|
+
class SslServerConnection < ServerConnection
|
7
|
+
def initialize(socket, host, port, unblocker, ssl_context, accept_callback, ssl_socket_impl=nil)
|
8
|
+
super(socket, host, port, unblocker)
|
9
|
+
@ssl_context = ssl_context
|
10
|
+
@accept_callback = accept_callback
|
11
|
+
@ssl_socket_impl = ssl_socket_impl || OpenSSL::SSL::SSLSocket
|
12
|
+
@ssl_state = :accepting
|
13
|
+
end
|
14
|
+
|
15
|
+
# @private
|
16
|
+
def to_io
|
17
|
+
if @ssl_state == :established
|
18
|
+
@io.to_io
|
19
|
+
else
|
20
|
+
@io
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def read
|
25
|
+
if @ssl_state == :accepting
|
26
|
+
begin
|
27
|
+
@ssl_io ||= @ssl_socket_impl.new(@io, @ssl_context)
|
28
|
+
@ssl_io.accept_nonblock
|
29
|
+
@io = @ssl_io
|
30
|
+
@ssl_state = :established
|
31
|
+
@accept_callback.call(self)
|
32
|
+
rescue IO::WaitReadable
|
33
|
+
# connection not ready yet
|
34
|
+
rescue => e
|
35
|
+
close(e)
|
36
|
+
end
|
37
|
+
else
|
38
|
+
super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/ione/version.rb
CHANGED
@@ -0,0 +1,97 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
|
6
|
+
describe 'SSL' do
|
7
|
+
let :io_reactor do
|
8
|
+
Ione::Io::IoReactor.new
|
9
|
+
end
|
10
|
+
|
11
|
+
let :port do
|
12
|
+
2**15 + rand(2**15)
|
13
|
+
end
|
14
|
+
|
15
|
+
let :ssl_key do
|
16
|
+
OpenSSL::PKey::RSA.new(2048)
|
17
|
+
end
|
18
|
+
|
19
|
+
let :ssl_cert do
|
20
|
+
cert = OpenSSL::X509::Certificate.new
|
21
|
+
cert.version = 2
|
22
|
+
cert.serial = 1
|
23
|
+
name = OpenSSL::X509::Name.new([['CN', 'localhost']])
|
24
|
+
cert.subject = name
|
25
|
+
cert.issuer = name
|
26
|
+
cert.not_before = Time.now
|
27
|
+
cert.not_after = Time.now + (365*24*60*60)
|
28
|
+
cert.public_key = ssl_key.public_key
|
29
|
+
cert.sign(ssl_key, OpenSSL::Digest::SHA1.new)
|
30
|
+
cert
|
31
|
+
end
|
32
|
+
|
33
|
+
let :ssl_context do
|
34
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
35
|
+
ctx.cert = ssl_cert
|
36
|
+
ctx.key = ssl_key
|
37
|
+
ctx
|
38
|
+
end
|
39
|
+
|
40
|
+
let :server_received_data do
|
41
|
+
Ione::ByteBuffer.new
|
42
|
+
end
|
43
|
+
|
44
|
+
let :client_received_data do
|
45
|
+
Ione::ByteBuffer.new
|
46
|
+
end
|
47
|
+
|
48
|
+
def start_server
|
49
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
50
|
+
ssl_context.key = OpenSSL::PKey::RSA.new(ssl_key)
|
51
|
+
ssl_context.cert = OpenSSL::X509::Certificate.new(ssl_cert)
|
52
|
+
|
53
|
+
f = io_reactor.start
|
54
|
+
f = f.flat_map do
|
55
|
+
io_reactor.bind(ENV['SERVER_HOST'], port, ssl: ssl_context) do |acceptor|
|
56
|
+
acceptor.on_accept do |connection|
|
57
|
+
connection.on_data do |data|
|
58
|
+
server_received_data << data
|
59
|
+
connection.write(data.reverse)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
f
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'establishes an encrypted connection' do
|
68
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
69
|
+
ssl_context.cert = OpenSSL::X509::Certificate.new(ssl_cert)
|
70
|
+
|
71
|
+
response_received = Ione::Promise.new
|
72
|
+
f = start_server
|
73
|
+
f = f.flat_map do
|
74
|
+
io_reactor.connect(ENV['SERVER_HOST'], port, ssl: ssl_context)
|
75
|
+
end
|
76
|
+
client = f.value
|
77
|
+
client.on_data do |data|
|
78
|
+
client_received_data << data
|
79
|
+
response_received.fulfill(data)
|
80
|
+
end
|
81
|
+
client.write('hello world')
|
82
|
+
response_received.future.value
|
83
|
+
server_received_data.to_s.should == 'hello world'
|
84
|
+
client_received_data.to_s.should == 'dlrow olleh'
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'fails to send a message when not using encryption' do
|
88
|
+
f = start_server
|
89
|
+
f = f.flat_map do
|
90
|
+
io_reactor.connect(ENV['SERVER_HOST'], port)
|
91
|
+
end
|
92
|
+
client = f.value
|
93
|
+
client.write('hello world')
|
94
|
+
await { client.closed? }
|
95
|
+
client.should be_closed
|
96
|
+
end
|
97
|
+
end
|
data/spec/ione/future_spec.rb
CHANGED
@@ -211,15 +211,91 @@ module Ione
|
|
211
211
|
f2.should equal(future)
|
212
212
|
end
|
213
213
|
|
214
|
-
it 'passes the value as the
|
214
|
+
it 'passes the value as the first parameter to the block when it expects two arguments' do
|
215
215
|
v1, v2 = nil, nil
|
216
|
-
future.on_complete { |
|
217
|
-
future.on_complete { |
|
216
|
+
future.on_complete { |v, _| v1 = v }
|
217
|
+
future.on_complete { |v, _| v2 = v }
|
218
218
|
promise.fulfill('bar')
|
219
219
|
v1.should == 'bar'
|
220
220
|
v2.should == 'bar'
|
221
221
|
end
|
222
222
|
|
223
|
+
it 'passes future as the third parameter to the block when it expects three arguments' do
|
224
|
+
f1, f2 = nil, nil
|
225
|
+
future.on_complete { |_, _, f| f1 = f }
|
226
|
+
future.on_complete { |_, _, f| f2 = f }
|
227
|
+
promise.fulfill('bar')
|
228
|
+
f1.should equal(future)
|
229
|
+
f2.should equal(future)
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'passes the value, error and future to the block when it expects any number of arguments' do
|
233
|
+
value = 'bar'
|
234
|
+
error = StandardError.new('bork')
|
235
|
+
args1, args2 = nil, nil
|
236
|
+
p1 = Promise.new
|
237
|
+
p2 = Promise.new
|
238
|
+
p1.future.on_complete { |*a| args1 = a }
|
239
|
+
p2.future.on_complete { |*a| args2 = a }
|
240
|
+
p1.fulfill(value)
|
241
|
+
p2.fail(error)
|
242
|
+
args1[0].should equal(value)
|
243
|
+
args1[1].should be_nil
|
244
|
+
args1[2].should equal(p1.future)
|
245
|
+
args2[0].should be_nil
|
246
|
+
args2[1].should equal(error)
|
247
|
+
args2[2].should equal(p2.future)
|
248
|
+
end
|
249
|
+
|
250
|
+
it 'passes the value, error and future to the listener when the listener is a lambda' do
|
251
|
+
value = 'bar'
|
252
|
+
error = StandardError.new('bork')
|
253
|
+
args1, args2 = nil, nil
|
254
|
+
p1 = Promise.new
|
255
|
+
p2 = Promise.new
|
256
|
+
p1.future.on_complete(&lambda { |v, e, f| args1 = [v, e, f] })
|
257
|
+
p2.future.on_complete(&lambda { |v, e, f| args2 = [v, e, f] })
|
258
|
+
p1.fulfill(value)
|
259
|
+
p2.fail(error)
|
260
|
+
args1[0].should equal(value)
|
261
|
+
args1[1].should be_nil
|
262
|
+
args1[2].should equal(p1.future)
|
263
|
+
args2[0].should be_nil
|
264
|
+
args2[1].should equal(error)
|
265
|
+
args2[2].should equal(p2.future)
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'ignores optional arguments for lambdas' do
|
269
|
+
value = 'bar'
|
270
|
+
error = StandardError.new('bork')
|
271
|
+
args1, args2 = nil, nil
|
272
|
+
p1 = Promise.new
|
273
|
+
p2 = Promise.new
|
274
|
+
p1.future.on_complete(&lambda { |v, e, f=nil| args1 = [v, e, f] })
|
275
|
+
p2.future.on_complete(&lambda { |v, e, f, x=1, y=2| args2 = [v, e, f, x, y] })
|
276
|
+
p1.fulfill(value)
|
277
|
+
p2.fail(error)
|
278
|
+
args1[0].should equal(value)
|
279
|
+
args1[1].should be_nil
|
280
|
+
args1[2].should be_nil
|
281
|
+
args2[0].should be_nil
|
282
|
+
args2[1].should equal(error)
|
283
|
+
args2[2].should equal(p2.future)
|
284
|
+
end
|
285
|
+
|
286
|
+
it 'does not handle listeners that are lambdas and have one optional argument' do
|
287
|
+
pending 'MRI 1.9.3 thinks these lambas have arity 1 and not -2' if RUBY_ENGINE == 'ruby' && RUBY_VERSION < '2.0.0'
|
288
|
+
called1, called2 = false, false
|
289
|
+
p1 = Promise.new
|
290
|
+
p2 = Promise.new
|
291
|
+
p1.future.on_complete(&lambda { |v, e=nil| called1 = true })
|
292
|
+
p2.future.on_complete(&lambda { |v, e=nil| called2 = true })
|
293
|
+
p1.fulfill('bar')
|
294
|
+
p2.fail(StandardError.new('bork'))
|
295
|
+
called1.should be_false
|
296
|
+
called2.should be_false
|
297
|
+
end
|
298
|
+
|
223
299
|
it 'notifies all listeners when the promise fails' do
|
224
300
|
c1, c2 = nil, nil
|
225
301
|
future.on_complete { c1 = true }
|
@@ -229,10 +305,10 @@ module Ione
|
|
229
305
|
c2.should be_true
|
230
306
|
end
|
231
307
|
|
232
|
-
it 'passes the error as the
|
308
|
+
it 'passes the error as the second parameter to the block when it expects two arguments' do
|
233
309
|
e1, e2 = nil, nil
|
234
|
-
future.on_complete { |_,
|
235
|
-
future.on_complete { |_,
|
310
|
+
future.on_complete { |_, e| e1 = e }
|
311
|
+
future.on_complete { |_, e| e2 = e }
|
236
312
|
future.fail(error)
|
237
313
|
e1.should equal(error)
|
238
314
|
e2.should equal(error)
|
@@ -257,7 +333,7 @@ module Ione
|
|
257
333
|
it 'notifies listeners registered after the promise was fulfilled' do
|
258
334
|
f, v, e = nil, nil, nil
|
259
335
|
promise.fulfill('bar')
|
260
|
-
future.on_complete { |
|
336
|
+
future.on_complete { |vv, ee, ff| v = vv; e = ee; f = ff }
|
261
337
|
f.should equal(future)
|
262
338
|
v.should == 'bar'
|
263
339
|
e.should be_nil
|
@@ -266,7 +342,7 @@ module Ione
|
|
266
342
|
it 'notifies listeners registered after the promise failed' do
|
267
343
|
f, v, e = nil, nil, nil
|
268
344
|
promise.fail(StandardError.new('bork'))
|
269
|
-
future.on_complete { |
|
345
|
+
future.on_complete { |vv, ee, ff| v = vv; e = ee; f = ff }
|
270
346
|
f.should equal(future)
|
271
347
|
v.should be_nil
|
272
348
|
e.message.should == 'bork'
|
@@ -274,7 +350,7 @@ module Ione
|
|
274
350
|
|
275
351
|
it 'notifies listeners registered after the promise failed' do
|
276
352
|
promise.fail(error)
|
277
|
-
expect { future.on_complete {
|
353
|
+
expect { future.on_complete { raise 'blurgh' } }.to_not raise_error
|
278
354
|
end
|
279
355
|
|
280
356
|
it 'returns nil' do
|
@@ -432,6 +508,12 @@ module Ione
|
|
432
508
|
promise.fulfill(:hello)
|
433
509
|
listeners.map(&:value).should == Array.new(10, :hello)
|
434
510
|
end
|
511
|
+
|
512
|
+
it 'is aliased as #get' do
|
513
|
+
obj = 'hello world'
|
514
|
+
promise.fulfill(obj)
|
515
|
+
future.get.should equal(obj)
|
516
|
+
end
|
435
517
|
end
|
436
518
|
|
437
519
|
describe '#map' do
|
@@ -512,7 +594,7 @@ module Ione
|
|
512
594
|
|
513
595
|
it 'accepts anything that implements #on_complete as a chained future' do
|
514
596
|
fake_future = double(:fake_future)
|
515
|
-
fake_future.stub(:on_complete) { |&listener| listener.call(
|
597
|
+
fake_future.stub(:on_complete) { |&listener| listener.call(:foobar, nil) }
|
516
598
|
p = Promise.new
|
517
599
|
f = p.future.flat_map { fake_future }
|
518
600
|
p.fulfill
|
@@ -531,13 +613,26 @@ module Ione
|
|
531
613
|
end
|
532
614
|
|
533
615
|
context 'when the block returns something that quacks like a future' do
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
616
|
+
context 'and yields a value from #on_complete' do
|
617
|
+
it 'works like #flat_map' do
|
618
|
+
fake_future = double(:fake_future)
|
619
|
+
fake_future.stub(:on_complete) { |&listener| listener.call(:foobar) }
|
620
|
+
p = Promise.new
|
621
|
+
f = p.future.then { |v| fake_future }
|
622
|
+
p.fulfill
|
623
|
+
f.value.should == :foobar
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
context 'and yields an error from #on_complete' do
|
628
|
+
it 'works like #flat_map' do
|
629
|
+
fake_future = double(:fake_future)
|
630
|
+
fake_future.stub(:on_complete) { |&listener| listener.call(nil, StandardError.new('bork')) }
|
631
|
+
p = Promise.new
|
632
|
+
f = p.future.then { |v| fake_future }
|
633
|
+
p.fulfill
|
634
|
+
expect { f.value }.to raise_error(StandardError, 'bork')
|
635
|
+
end
|
541
636
|
end
|
542
637
|
end
|
543
638
|
|
@@ -662,7 +757,7 @@ module Ione
|
|
662
757
|
|
663
758
|
it 'accepts anything that implements #on_complete as a fallback future' do
|
664
759
|
fake_future = double(:fake_future)
|
665
|
-
fake_future.stub(:on_complete) { |&listener| listener.call(
|
760
|
+
fake_future.stub(:on_complete) { |&listener| listener.call('foo', nil) }
|
666
761
|
p = Promise.new
|
667
762
|
f = p.future.fallback { fake_future }
|
668
763
|
p.fail(error)
|
@@ -703,7 +798,7 @@ module Ione
|
|
703
798
|
|
704
799
|
it 'accepts anything that implements #on_complete as futures' do
|
705
800
|
fake_future = double(:fake_future)
|
706
|
-
fake_future.stub(:on_complete) { |&listener| listener.call(
|
801
|
+
fake_future.stub(:on_complete) { |&listener| listener.call(:foobar, nil) }
|
707
802
|
future = Future.traverse([1, 2, 3]) { fake_future }
|
708
803
|
future.value.should == [:foobar, :foobar, :foobar]
|
709
804
|
end
|
@@ -785,9 +880,9 @@ module Ione
|
|
785
880
|
|
786
881
|
it 'accepts anything that implements #on_complete as futures' do
|
787
882
|
ff1, ff2, ff3 = double, double, double
|
788
|
-
ff1.stub(:on_complete) { |&listener| listener.call(
|
789
|
-
ff2.stub(:on_complete) { |&listener| listener.call(
|
790
|
-
ff3.stub(:on_complete) { |&listener| listener.call(
|
883
|
+
ff1.stub(:on_complete) { |&listener| listener.call(1, nil) }
|
884
|
+
ff2.stub(:on_complete) { |&listener| listener.call(2, nil) }
|
885
|
+
ff3.stub(:on_complete) { |&listener| listener.call(3, nil) }
|
791
886
|
future = Future.reduce([ff1, ff2, ff3], 0) { |sum, n| sum + n }
|
792
887
|
future.value.should == 6
|
793
888
|
end
|
@@ -907,9 +1002,9 @@ module Ione
|
|
907
1002
|
|
908
1003
|
it 'accepts anything that implements #on_complete as futures' do
|
909
1004
|
ff1, ff2, ff3 = double, double, double
|
910
|
-
ff1.stub(:on_complete) { |&listener| listener.call(
|
911
|
-
ff2.stub(:on_complete) { |&listener| listener.call(
|
912
|
-
ff3.stub(:on_complete) { |&listener| listener.call(
|
1005
|
+
ff1.stub(:on_complete) { |&listener| listener.call(1, nil) }
|
1006
|
+
ff2.stub(:on_complete) { |&listener| listener.call(2, nil) }
|
1007
|
+
ff3.stub(:on_complete) { |&listener| listener.call(3, nil) }
|
913
1008
|
future = Future.all(ff1, ff2, ff3)
|
914
1009
|
future.value.should == [1, 2, 3]
|
915
1010
|
end
|
@@ -1006,8 +1101,8 @@ module Ione
|
|
1006
1101
|
|
1007
1102
|
it 'accepts anything that implements #on_complete as futures' do
|
1008
1103
|
ff1, ff2 = double, double
|
1009
|
-
ff1.stub(:on_complete) { |&listener| listener.call(
|
1010
|
-
ff2.stub(:on_complete) { |&listener| listener.call(
|
1104
|
+
ff1.stub(:on_complete) { |&listener| listener.call(1, nil) }
|
1105
|
+
ff2.stub(:on_complete) { |&listener| listener.call(2, nil) }
|
1011
1106
|
future = Future.first(ff1, ff2)
|
1012
1107
|
future.value.should == 1
|
1013
1108
|
end
|
@@ -1040,11 +1135,21 @@ module Ione
|
|
1040
1135
|
|
1041
1136
|
it 'calls its complete callbacks immediately' do
|
1042
1137
|
f, v = nil, nil
|
1043
|
-
future.on_complete { |
|
1138
|
+
future.on_complete { |vv, _, ff| f = ff; v = vv }
|
1044
1139
|
f.should equal(future)
|
1045
1140
|
v.should == 'hello world'
|
1046
1141
|
end
|
1047
1142
|
|
1143
|
+
it 'calls its complete callbacks with the right arity' do
|
1144
|
+
f1, v, f2 = nil, nil, nil
|
1145
|
+
future.on_complete { |ff| f1 = ff }
|
1146
|
+
future.on_complete { |vv, ee| v = vv }
|
1147
|
+
future.on_complete { |vv, ee, ff| f2 = ff }
|
1148
|
+
f1.should equal(future)
|
1149
|
+
f2.should equal(future)
|
1150
|
+
v.should == 'hello world'
|
1151
|
+
end
|
1152
|
+
|
1048
1153
|
it 'does not block on #value' do
|
1049
1154
|
future.value.should == 'hello world'
|
1050
1155
|
end
|
@@ -1081,15 +1186,25 @@ module Ione
|
|
1081
1186
|
|
1082
1187
|
it 'calls its complete callbacks immediately' do
|
1083
1188
|
f, e = nil, nil
|
1084
|
-
future.on_complete { |
|
1189
|
+
future.on_complete { |_, ee, ff| f = ff; e = ee }
|
1085
1190
|
f.should equal(future)
|
1086
1191
|
e.message.should == 'bork'
|
1087
1192
|
end
|
1088
1193
|
|
1194
|
+
it 'calls its complete callbacks with the right arity' do
|
1195
|
+
f1, e, f2 = nil, nil, nil
|
1196
|
+
future.on_complete { |ff| f1 = ff }
|
1197
|
+
future.on_complete { |vv, ee| e = ee }
|
1198
|
+
future.on_complete { |vv, ee, ff| f2 = ff }
|
1199
|
+
f1.should equal(future)
|
1200
|
+
f2.should equal(future)
|
1201
|
+
e.message.should == 'bork'
|
1202
|
+
end
|
1203
|
+
|
1089
1204
|
it 'does not block on #value' do
|
1090
1205
|
expect { future.value }.to raise_error('bork')
|
1091
1206
|
end
|
1092
1207
|
end
|
1093
1208
|
end
|
1094
1209
|
end
|
1095
|
-
end
|
1210
|
+
end
|