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