cql-rb 1.1.0.pre3 → 1.1.0.pre6
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.
- data/README.md +2 -2
- data/lib/cql/client.rb +9 -5
- data/lib/cql/client/asynchronous_client.rb +105 -192
- data/lib/cql/client/asynchronous_prepared_statement.rb +51 -9
- data/lib/cql/client/connection_helper.rb +155 -0
- data/lib/cql/client/connection_manager.rb +56 -0
- data/lib/cql/client/keyspace_changer.rb +27 -0
- data/lib/cql/client/null_logger.rb +21 -0
- data/lib/cql/client/request_runner.rb +5 -3
- data/lib/cql/client/synchronous_client.rb +5 -5
- data/lib/cql/client/synchronous_prepared_statement.rb +4 -8
- data/lib/cql/future.rb +320 -210
- data/lib/cql/io/connection.rb +5 -5
- data/lib/cql/io/io_reactor.rb +21 -23
- data/lib/cql/protocol/cql_protocol_handler.rb +69 -38
- data/lib/cql/protocol/encoding.rb +5 -1
- data/lib/cql/protocol/requests/register_request.rb +2 -0
- data/lib/cql/protocol/type_converter.rb +1 -0
- data/lib/cql/version.rb +1 -1
- data/spec/cql/client/asynchronous_client_spec.rb +368 -175
- data/spec/cql/client/asynchronous_prepared_statement_spec.rb +132 -22
- data/spec/cql/client/connection_helper_spec.rb +335 -0
- data/spec/cql/client/connection_manager_spec.rb +118 -0
- data/spec/cql/client/keyspace_changer_spec.rb +50 -0
- data/spec/cql/client/request_runner_spec.rb +12 -12
- data/spec/cql/client/synchronous_client_spec.rb +15 -15
- data/spec/cql/client/synchronous_prepared_statement_spec.rb +15 -11
- data/spec/cql/future_spec.rb +529 -301
- data/spec/cql/io/connection_spec.rb +12 -12
- data/spec/cql/io/io_reactor_spec.rb +61 -61
- data/spec/cql/protocol/cql_protocol_handler_spec.rb +26 -12
- data/spec/cql/protocol/encoding_spec.rb +5 -0
- data/spec/cql/protocol/type_converter_spec.rb +1 -1
- data/spec/cql/time_uuid_spec.rb +7 -7
- data/spec/integration/client_spec.rb +2 -2
- data/spec/integration/io_spec.rb +20 -20
- data/spec/integration/protocol_spec.rb +17 -17
- data/spec/integration/regression_spec.rb +6 -0
- data/spec/integration/uuid_spec.rb +4 -0
- data/spec/support/fake_io_reactor.rb +38 -8
- data/spec/support/fake_server.rb +3 -3
- metadata +12 -2
@@ -0,0 +1,118 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
|
6
|
+
module Cql
|
7
|
+
module Client
|
8
|
+
describe ConnectionManager do
|
9
|
+
let :manager do
|
10
|
+
described_class.new
|
11
|
+
end
|
12
|
+
|
13
|
+
let :connections do
|
14
|
+
[double(:connection1), double(:connection2), double(:connection3)]
|
15
|
+
end
|
16
|
+
|
17
|
+
before do
|
18
|
+
connections.each do |c|
|
19
|
+
c.stub(:on_closed) do |&listener|
|
20
|
+
c.stub(:closed_listener).and_return(listener)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#add_connections' do
|
26
|
+
it 'registers as a close listener on each connection' do
|
27
|
+
manager.add_connections(connections)
|
28
|
+
connections.each { |c| c.should have_received(:on_closed) }
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'stops managing the connection when the connection closes' do
|
32
|
+
manager.add_connections(connections)
|
33
|
+
connections.each { |c| c.closed_listener.call }
|
34
|
+
expect { manager.random_connection }.to raise_error(NotConnectedError)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#connected?' do
|
39
|
+
it 'returns true when there are connections' do
|
40
|
+
manager.add_connections(connections)
|
41
|
+
manager.should be_connected
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'returns false when there are no' do
|
45
|
+
manager.should_not be_connected
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#snapshot' do
|
50
|
+
it 'returns a copy of the list of connections' do
|
51
|
+
manager.add_connections(connections)
|
52
|
+
s = manager.snapshot
|
53
|
+
s.should == connections
|
54
|
+
s.should_not equal(connections)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#random_connection' do
|
59
|
+
before do
|
60
|
+
connections.each { |c| c.stub(:on_closed) }
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'returns one of the connections it is managing' do
|
64
|
+
manager.add_connections(connections)
|
65
|
+
connections.should include(manager.random_connection)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'raises a NotConnectedError when there are no connections' do
|
69
|
+
expect { manager.random_connection }.to raise_error(NotConnectedError)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '#each_connection' do
|
74
|
+
it 'yields each connection to the given block' do
|
75
|
+
manager.add_connections(connections)
|
76
|
+
yielded = []
|
77
|
+
manager.each_connection { |c| yielded << c }
|
78
|
+
yielded.should == connections
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'is aliased as #each' do
|
82
|
+
manager.add_connections(connections)
|
83
|
+
yielded = []
|
84
|
+
manager.each { |c| yielded << c }
|
85
|
+
yielded.should == connections
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'returns an Enumerable when no block is given' do
|
89
|
+
manager.each.should be_an(Enumerable)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'raises a NotConnectedError when there are no connections' do
|
93
|
+
expect { manager.each_connection { } }.to raise_error(NotConnectedError)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'as an Enumerable' do
|
98
|
+
before do
|
99
|
+
connections.each_with_index { |c, i| c.stub(:index).and_return(i) }
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'can be mapped' do
|
103
|
+
manager.add_connections(connections)
|
104
|
+
manager.map { |c| c.index }.should == [0, 1, 2]
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'can be filtered' do
|
108
|
+
manager.add_connections(connections)
|
109
|
+
manager.select { |c| c.index % 2 == 0 }.should == [connections[0], connections[2]]
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'raises a NotConnectedError when there are no connections' do
|
113
|
+
expect { manager.select { } }.to raise_error(NotConnectedError)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
|
6
|
+
module Cql
|
7
|
+
module Client
|
8
|
+
describe KeyspaceChanger do
|
9
|
+
let :keyspace_changer do
|
10
|
+
described_class.new
|
11
|
+
end
|
12
|
+
|
13
|
+
let :connection do
|
14
|
+
double(:connection)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#use_keyspace' do
|
18
|
+
it 'sends a query request with a USE statement' do
|
19
|
+
connection.stub(:send_request).with(Protocol::QueryRequest.new('USE important_stuff', :one)).and_return(Future.resolved)
|
20
|
+
f = keyspace_changer.use_keyspace('important_stuff', connection)
|
21
|
+
connection.should have_received(:send_request)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'accepts quoted keyspace names' do
|
25
|
+
connection.stub(:send_request).with(Protocol::QueryRequest.new('USE "ImportantStuff"', :one)).and_return(Future.resolved)
|
26
|
+
f = keyspace_changer.use_keyspace('"ImportantStuff"', connection)
|
27
|
+
connection.should have_received(:send_request)
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'returns a future that' do
|
31
|
+
it 'immediately resolves to the given connection when the keyspace is nil' do
|
32
|
+
f = keyspace_changer.use_keyspace(nil, connection)
|
33
|
+
f.value.should equal(connection)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'fails with an InvalidKeyspaceNameError when the keyspace name is invalid' do
|
37
|
+
f = keyspace_changer.use_keyspace('TRUNCATE important_stuff', connection)
|
38
|
+
expect { f.value }.to raise_error(InvalidKeyspaceNameError)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'resolves to the given connection' do
|
42
|
+
connection.stub(:send_request).with(Protocol::QueryRequest.new('USE important_stuff', :one)).and_return(Future.resolved)
|
43
|
+
f = keyspace_changer.use_keyspace('important_stuff', connection)
|
44
|
+
f.value.should equal(connection)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -11,11 +11,11 @@ module Cql
|
|
11
11
|
end
|
12
12
|
|
13
13
|
let :connection do
|
14
|
-
|
14
|
+
double(:connection)
|
15
15
|
end
|
16
16
|
|
17
17
|
let :request do
|
18
|
-
|
18
|
+
double(:request)
|
19
19
|
end
|
20
20
|
|
21
21
|
let :metadata do
|
@@ -59,12 +59,12 @@ module Cql
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def run(response, rq=request)
|
62
|
-
connection.stub(:send_request).and_return(Future.
|
63
|
-
runner.execute(connection, rq).
|
62
|
+
connection.stub(:send_request).and_return(Future.resolved(response))
|
63
|
+
runner.execute(connection, rq).value
|
64
64
|
end
|
65
65
|
|
66
66
|
it 'executes the request' do
|
67
|
-
connection.should_receive(:send_request).and_return(Future.
|
67
|
+
connection.should_receive(:send_request).and_return(Future.resolved(rows_response))
|
68
68
|
runner.execute(connection, request)
|
69
69
|
end
|
70
70
|
|
@@ -78,12 +78,6 @@ module Cql
|
|
78
78
|
result.should be_nil
|
79
79
|
end
|
80
80
|
|
81
|
-
it 'transforms a PreparedResultResponse to a prepared statement' do
|
82
|
-
result = run(prepared_response)
|
83
|
-
result.should be_a(AsynchronousPreparedStatement)
|
84
|
-
result.metadata['my_column'].should == ColumnMetadata.new('my_keyspace', 'my_table', 'my_column', :int)
|
85
|
-
end
|
86
|
-
|
87
81
|
it 'transforms a AuthenticateResponse to an authentication required object' do
|
88
82
|
result = run(authenticate_response)
|
89
83
|
result.should be_a(AuthenticationRequired)
|
@@ -106,7 +100,7 @@ module Cql
|
|
106
100
|
rescue QueryError => e
|
107
101
|
e.cql.should == 'SELECT * FROM everything'
|
108
102
|
else
|
109
|
-
fail
|
103
|
+
fail('No error was raised')
|
110
104
|
end
|
111
105
|
end
|
112
106
|
|
@@ -114,6 +108,12 @@ module Cql
|
|
114
108
|
result = run('hibbly hobbly')
|
115
109
|
result.should be_nil
|
116
110
|
end
|
111
|
+
|
112
|
+
it 'allows the caller to transform unknown responses' do
|
113
|
+
connection.stub(:send_request).and_return(Future.resolved('hibbly hobbly'))
|
114
|
+
result = runner.execute(connection, request) { |response| response.reverse }.value
|
115
|
+
result.should == 'hibbly hobbly'.reverse
|
116
|
+
end
|
117
117
|
end
|
118
118
|
end
|
119
119
|
end
|
@@ -11,17 +11,17 @@ module Cql
|
|
11
11
|
end
|
12
12
|
|
13
13
|
let :async_client do
|
14
|
-
|
14
|
+
double(:async_client)
|
15
15
|
end
|
16
16
|
|
17
17
|
let :future do
|
18
|
-
|
18
|
+
double(:future, value: nil)
|
19
19
|
end
|
20
20
|
|
21
21
|
describe '#connect' do
|
22
22
|
it 'calls #connect on the async client and waits for the result' do
|
23
23
|
async_client.should_receive(:connect).and_return(future)
|
24
|
-
future.should_receive(:
|
24
|
+
future.should_receive(:value)
|
25
25
|
client.connect
|
26
26
|
end
|
27
27
|
|
@@ -34,7 +34,7 @@ module Cql
|
|
34
34
|
describe '#close' do
|
35
35
|
it 'calls #close on the async client and waits for the result' do
|
36
36
|
async_client.should_receive(:close).and_return(future)
|
37
|
-
future.should_receive(:
|
37
|
+
future.should_receive(:value)
|
38
38
|
client.close
|
39
39
|
end
|
40
40
|
|
@@ -63,31 +63,31 @@ module Cql
|
|
63
63
|
describe '#use' do
|
64
64
|
it 'calls #use on the async client and waits for the result' do
|
65
65
|
async_client.should_receive(:use).with('foo').and_return(future)
|
66
|
-
future.should_receive(:
|
66
|
+
future.should_receive(:value)
|
67
67
|
client.use('foo')
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
71
|
describe '#execute' do
|
72
72
|
it 'calls #execute on the async client and waits for, and returns the result' do
|
73
|
-
result =
|
73
|
+
result = double(:result)
|
74
74
|
async_client.stub(:execute).with('SELECT * FROM something', :one).and_return(future)
|
75
|
-
future.stub(:
|
75
|
+
future.stub(:value).and_return(result)
|
76
76
|
client.execute('SELECT * FROM something', :one).should equal(result)
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
80
80
|
describe '#prepare' do
|
81
81
|
it 'calls #prepare on the async client, waits for the result and returns a SynchronousFuture' do
|
82
|
-
result =
|
83
|
-
metadata =
|
84
|
-
async_statement =
|
85
|
-
another_future =
|
82
|
+
result = double(:result)
|
83
|
+
metadata = double(:metadata)
|
84
|
+
async_statement = double(:async_statement, metadata: metadata)
|
85
|
+
another_future = double(:another_future)
|
86
86
|
async_client.stub(:prepare).with('SELECT * FROM something').and_return(future)
|
87
|
-
future.stub(:
|
87
|
+
future.stub(:value).and_return(async_statement)
|
88
88
|
statement = client.prepare('SELECT * FROM something')
|
89
89
|
async_statement.should_receive(:execute).and_return(another_future)
|
90
|
-
another_future.stub(:
|
90
|
+
another_future.stub(:value).and_return(result)
|
91
91
|
statement.execute.should equal(result)
|
92
92
|
statement.metadata.should equal(metadata)
|
93
93
|
end
|
@@ -103,7 +103,7 @@ module Cql
|
|
103
103
|
it 'replaces the backtrace of the asynchronous call to make it less confusing' do
|
104
104
|
error = CqlError.new('Bork')
|
105
105
|
error.set_backtrace(['Hello', 'World'])
|
106
|
-
future.stub(:
|
106
|
+
future.stub(:value).and_raise(error)
|
107
107
|
async_client.stub(:execute).and_return(future)
|
108
108
|
begin
|
109
109
|
client.execute('SELECT * FROM something')
|
@@ -113,7 +113,7 @@ module Cql
|
|
113
113
|
end
|
114
114
|
|
115
115
|
it 'does not replace the backtrace of non-CqlError errors' do
|
116
|
-
future.stub(:
|
116
|
+
future.stub(:value).and_raise('Bork')
|
117
117
|
async_client.stub(:execute).and_return(future)
|
118
118
|
begin
|
119
119
|
client.execute('SELECT * FROM something')
|
@@ -11,15 +11,19 @@ module Cql
|
|
11
11
|
end
|
12
12
|
|
13
13
|
let :async_statement do
|
14
|
-
|
14
|
+
double(:async_statement, metadata: metadata)
|
15
15
|
end
|
16
16
|
|
17
17
|
let :metadata do
|
18
|
-
|
18
|
+
double(:metadata)
|
19
|
+
end
|
20
|
+
|
21
|
+
let :promise do
|
22
|
+
Promise.new
|
19
23
|
end
|
20
24
|
|
21
25
|
let :future do
|
22
|
-
|
26
|
+
promise.future
|
23
27
|
end
|
24
28
|
|
25
29
|
describe '#metadata' do
|
@@ -30,19 +34,19 @@ module Cql
|
|
30
34
|
|
31
35
|
describe '#execute' do
|
32
36
|
it 'it calls #execute on the async statement and waits for the result' do
|
33
|
-
result =
|
37
|
+
result = double(:result)
|
34
38
|
async_statement.should_receive(:execute).with('one', 'two', :three).and_return(future)
|
35
|
-
|
39
|
+
promise.fulfill(result)
|
36
40
|
statement.execute('one', 'two', :three).should equal(result)
|
37
41
|
end
|
38
42
|
end
|
39
43
|
|
40
44
|
describe '#pipeline' do
|
41
45
|
it 'executes the statement multiple times and waits for all the results' do
|
42
|
-
result1 =
|
43
|
-
result2 =
|
44
|
-
async_statement.stub(:execute).with('one', 'two', :three).and_return(Future.
|
45
|
-
async_statement.stub(:execute).with('four', 'file', :all).and_return(Future.
|
46
|
+
result1 = double(:result1)
|
47
|
+
result2 = double(:result2)
|
48
|
+
async_statement.stub(:execute).with('one', 'two', :three).and_return(Future.resolved(result1))
|
49
|
+
async_statement.stub(:execute).with('four', 'file', :all).and_return(Future.resolved(result2))
|
46
50
|
results = statement.pipeline do |p|
|
47
51
|
p.execute('one', 'two', :three)
|
48
52
|
p.execute('four', 'file', :all)
|
@@ -65,7 +69,7 @@ module Cql
|
|
65
69
|
it 'replaces the backtrace of the asynchronous call to make it less confusing' do
|
66
70
|
error = CqlError.new('Bork')
|
67
71
|
error.set_backtrace(['Hello', 'World'])
|
68
|
-
future.stub(:
|
72
|
+
future.stub(:value).and_raise(error)
|
69
73
|
async_statement.stub(:execute).and_return(future)
|
70
74
|
begin
|
71
75
|
statement.execute('SELECT * FROM something')
|
@@ -75,7 +79,7 @@ module Cql
|
|
75
79
|
end
|
76
80
|
|
77
81
|
it 'does not replace the backtrace of non-CqlError errors' do
|
78
|
-
future.stub(:
|
82
|
+
future.stub(:value).and_raise('Bork')
|
79
83
|
async_statement.stub(:execute).and_return(future)
|
80
84
|
begin
|
81
85
|
statement.execute('SELECT * FROM something')
|
data/spec/cql/future_spec.rb
CHANGED
@@ -4,457 +4,685 @@ require 'spec_helper'
|
|
4
4
|
|
5
5
|
|
6
6
|
module Cql
|
7
|
-
describe
|
8
|
-
let :
|
7
|
+
describe Promise do
|
8
|
+
let :promise do
|
9
9
|
described_class.new
|
10
10
|
end
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
let :future do
|
13
|
+
promise.future
|
14
|
+
end
|
15
|
+
|
16
|
+
let :error do
|
17
|
+
StandardError.new('bork')
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#fulfill' do
|
21
|
+
it 'resolves its future' do
|
22
|
+
promise.fulfill
|
23
|
+
future.should be_resolved
|
16
24
|
end
|
17
25
|
|
18
|
-
it '
|
19
|
-
|
20
|
-
|
26
|
+
it 'raises an error if fulfilled a second time' do
|
27
|
+
promise.fulfill
|
28
|
+
expect { promise.fulfill }.to raise_error(FutureError)
|
21
29
|
end
|
22
30
|
|
23
|
-
it '
|
24
|
-
|
25
|
-
|
31
|
+
it 'raises an error if failed after being fulfilled' do
|
32
|
+
promise.fulfill
|
33
|
+
expect { promise.fail(error) }.to raise_error(FutureError)
|
26
34
|
end
|
27
35
|
|
28
|
-
it 'returns
|
29
|
-
|
30
|
-
future.get.should == 'foo'
|
36
|
+
it 'returns nil' do
|
37
|
+
promise.fulfill(:foo).should be_nil
|
31
38
|
end
|
39
|
+
end
|
32
40
|
|
33
|
-
|
34
|
-
|
35
|
-
|
41
|
+
describe '#fail' do
|
42
|
+
it 'fails its future' do
|
43
|
+
promise.fail(error)
|
44
|
+
future.should be_failed
|
36
45
|
end
|
37
46
|
|
38
|
-
it '
|
39
|
-
|
40
|
-
|
41
|
-
future.on_complete { |v| v2 = v }
|
42
|
-
future.complete!('bar')
|
43
|
-
v1.should == 'bar'
|
44
|
-
v2.should == 'bar'
|
47
|
+
it 'raises an error if failed a second time' do
|
48
|
+
promise.fail(error)
|
49
|
+
expect { promise.fail(error) }.to raise_error(FutureError)
|
45
50
|
end
|
46
51
|
|
47
|
-
it '
|
48
|
-
|
49
|
-
|
50
|
-
future.on_complete { |v| value = v }
|
51
|
-
future.complete!('bar')
|
52
|
-
value.should == 'bar'
|
52
|
+
it 'raises an error if fulfilled after being failed' do
|
53
|
+
promise.fail(error)
|
54
|
+
expect { promise.fulfill }.to raise_error(FutureError)
|
53
55
|
end
|
54
56
|
|
55
|
-
it '
|
56
|
-
|
57
|
-
future.complete!('bar')
|
58
|
-
future.on_complete { |v| v1 = v }
|
59
|
-
future.on_complete { |v| v2 = v }
|
60
|
-
v1.should == 'bar'
|
61
|
-
v2.should == 'bar'
|
57
|
+
it 'returns nil' do
|
58
|
+
promise.fail(error).should be_nil
|
62
59
|
end
|
60
|
+
end
|
63
61
|
|
64
|
-
|
65
|
-
|
66
|
-
|
62
|
+
describe '#observe' do
|
63
|
+
it 'resolves its future when the specified future is resolved' do
|
64
|
+
p2 = Promise.new
|
65
|
+
promise.observe(p2.future)
|
66
|
+
p2.fulfill
|
67
|
+
promise.future.should be_resolved
|
67
68
|
end
|
68
69
|
|
69
|
-
it '
|
70
|
-
|
71
|
-
|
72
|
-
|
70
|
+
it 'fails its future when the specified future fails' do
|
71
|
+
p2 = Promise.new
|
72
|
+
promise.observe(p2.future)
|
73
|
+
p2.fail(error)
|
74
|
+
promise.future.should be_failed
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'silently ignores double fulfillment/failure' do
|
78
|
+
p2 = Promise.new
|
79
|
+
promise.observe(p2.future)
|
80
|
+
promise.fail(error)
|
81
|
+
p2.fulfill
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'returns nil' do
|
85
|
+
promise.observe(Promise.new.future).should be_nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#try' do
|
90
|
+
it 'fulfills the promise with the result of the block' do
|
91
|
+
promise.try do
|
92
|
+
3 + 4
|
73
93
|
end
|
74
|
-
future.value.should ==
|
94
|
+
promise.future.value.should == 7
|
75
95
|
end
|
76
96
|
|
77
|
-
it '
|
78
|
-
|
79
|
-
|
80
|
-
future.complete!
|
97
|
+
it 'fails the promise when the block raises an error' do
|
98
|
+
promise.try do
|
99
|
+
raise error
|
81
100
|
end
|
82
|
-
future.value.
|
83
|
-
future.value.should be_nil
|
101
|
+
expect { promise.future.value }.to raise_error(/bork/)
|
84
102
|
end
|
85
103
|
|
86
|
-
it '
|
87
|
-
|
88
|
-
|
89
|
-
future.fail!(StandardError.new('FAIL!'))
|
104
|
+
it 'calls the block with the specified arguments' do
|
105
|
+
promise.try(:foo, 3) do |a, b|
|
106
|
+
a.length + b
|
90
107
|
end
|
91
|
-
|
108
|
+
promise.future.value.should == 6
|
92
109
|
end
|
93
110
|
|
94
|
-
it '
|
95
|
-
|
96
|
-
expect { future.complete!('foo') }.to raise_error(FutureError)
|
111
|
+
it 'returns nil' do
|
112
|
+
promise.try { }.should be_nil
|
97
113
|
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe Future do
|
118
|
+
let :promise do
|
119
|
+
Promise.new
|
120
|
+
end
|
98
121
|
|
99
|
-
|
100
|
-
|
101
|
-
|
122
|
+
let :future do
|
123
|
+
promise.future
|
124
|
+
end
|
125
|
+
|
126
|
+
let :error do
|
127
|
+
StandardError.new('bork')
|
128
|
+
end
|
129
|
+
|
130
|
+
def async(*context, &listener)
|
131
|
+
Thread.start(*context, &listener)
|
132
|
+
end
|
133
|
+
|
134
|
+
def delayed(*context, &listener)
|
135
|
+
async(*context) do |*ctx|
|
136
|
+
sleep(0.1)
|
137
|
+
listener.call(*context)
|
102
138
|
end
|
103
139
|
end
|
104
140
|
|
105
|
-
|
106
|
-
it 'is
|
107
|
-
|
108
|
-
future.should
|
141
|
+
describe '#completed?' do
|
142
|
+
it 'is true when the promise is fulfilled' do
|
143
|
+
promise.fulfill
|
144
|
+
future.should be_completed
|
109
145
|
end
|
110
146
|
|
111
|
-
it '
|
112
|
-
|
113
|
-
|
147
|
+
it 'is true when the promise is failed' do
|
148
|
+
promise.fail(StandardError.new('bork'))
|
149
|
+
future.should be_completed
|
114
150
|
end
|
151
|
+
end
|
115
152
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
future.
|
120
|
-
future.fail!(StandardError.new('FAIL!'))
|
121
|
-
e1.message.should == 'FAIL!'
|
122
|
-
e2.message.should == 'FAIL!'
|
153
|
+
describe '#resolved?' do
|
154
|
+
it 'is true when the promise is fulfilled' do
|
155
|
+
promise.fulfill('foo')
|
156
|
+
future.should be_resolved
|
123
157
|
end
|
124
158
|
|
125
|
-
it '
|
126
|
-
|
127
|
-
future.
|
128
|
-
future.on_failure { |e| error = e }
|
129
|
-
future.fail!(StandardError.new('FAIL!'))
|
130
|
-
error.message.should == 'FAIL!'
|
159
|
+
it 'is true when the promise is fulfilled with something falsy' do
|
160
|
+
promise.fulfill(nil)
|
161
|
+
future.should be_resolved
|
131
162
|
end
|
132
163
|
|
133
|
-
it '
|
134
|
-
|
135
|
-
future.
|
136
|
-
future.on_failure { |e| e1 = e }
|
137
|
-
future.on_failure { |e| e2 = e }
|
138
|
-
e1.message.should == 'FAIL!'
|
139
|
-
e2.message.should == 'FAIL!'
|
164
|
+
it 'is false when the promise is failed' do
|
165
|
+
promise.fail(StandardError.new('bork'))
|
166
|
+
future.should_not be_resolved
|
140
167
|
end
|
168
|
+
end
|
141
169
|
|
142
|
-
|
143
|
-
|
144
|
-
|
170
|
+
describe '#failed?' do
|
171
|
+
it 'is true when the promise is failed' do
|
172
|
+
promise.fail(error)
|
173
|
+
future.should be_failed
|
145
174
|
end
|
146
175
|
|
147
|
-
it '
|
148
|
-
|
149
|
-
|
176
|
+
it 'is false when the promise is fulfilled' do
|
177
|
+
promise.fulfill
|
178
|
+
future.should_not be_failed
|
150
179
|
end
|
180
|
+
end
|
181
|
+
|
182
|
+
describe '#on_complete' do
|
183
|
+
context 'registers listeners and' do
|
184
|
+
it 'notifies all listeners when the promise is fulfilled' do
|
185
|
+
v1, v2 = nil, nil
|
186
|
+
future.on_complete { |f| v1 = f.value }
|
187
|
+
future.on_complete { |f| v2 = f.value }
|
188
|
+
promise.fulfill('bar')
|
189
|
+
v1.should == 'bar'
|
190
|
+
v2.should == 'bar'
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'notifies all listeners when the promise fails' do
|
194
|
+
e1, e2 = nil, nil
|
195
|
+
future.on_complete { |f| begin; f.value; rescue => err; e1 = err; end }
|
196
|
+
future.on_complete { |f| begin; f.value; rescue => err; e2 = err; end }
|
197
|
+
future.fail(error)
|
198
|
+
e1.message.should == error.message
|
199
|
+
e2.message.should == error.message
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'notifies all listeners when the promise is fulfilled, even when one raises an error' do
|
203
|
+
value = nil
|
204
|
+
future.on_complete { |f| raise 'Blurgh' }
|
205
|
+
future.on_complete { |f| value = f.value }
|
206
|
+
promise.fulfill('bar')
|
207
|
+
value.should == 'bar'
|
208
|
+
end
|
151
209
|
|
152
|
-
|
153
|
-
|
154
|
-
|
210
|
+
it 'notifies all listeners when the promise fails, even when one raises an error' do
|
211
|
+
err = nil
|
212
|
+
future.on_complete { |f| raise 'Blurgh' }
|
213
|
+
future.on_complete { |f| begin; f.value; rescue => err; e = err; end }
|
214
|
+
promise.fail(error)
|
215
|
+
err.message.should == 'bork'
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'notifies listeners registered after the promise was fulfilled' do
|
219
|
+
promise.fulfill('bar')
|
220
|
+
expect { future.on_complete { |v| raise 'blurgh' } }.to_not raise_error
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'notifies listeners registered after the promise failed' do
|
224
|
+
promise.fail(error)
|
225
|
+
expect { future.on_complete { |v| raise 'blurgh' } }.to_not raise_error
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'returns nil' do
|
229
|
+
future.on_complete { :foo }.should be_nil
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'returns nil when the future is already resolved' do
|
233
|
+
promise.fulfill
|
234
|
+
future.on_complete { :foo }.should be_nil
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'returns nil when the future already has failed' do
|
238
|
+
promise.fail(error)
|
239
|
+
future.on_complete { :foo }.should be_nil
|
240
|
+
end
|
155
241
|
end
|
156
242
|
end
|
157
243
|
|
158
|
-
describe '
|
159
|
-
context '
|
160
|
-
it '
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
f3.should be_complete
|
244
|
+
describe '#on_value' do
|
245
|
+
context 'registers listeners and' do
|
246
|
+
it 'notifies all value listeners when the promise is fulfilled' do
|
247
|
+
v1, v2 = nil, nil
|
248
|
+
future.on_value { |v| v1 = v }
|
249
|
+
future.on_value { |v| v2 = v }
|
250
|
+
promise.fulfill('bar')
|
251
|
+
v1.should == 'bar'
|
252
|
+
v2.should == 'bar'
|
168
253
|
end
|
169
254
|
|
170
|
-
it '
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
f1.on_complete { sequence << 1 }
|
177
|
-
f2.on_complete { sequence << 2 }
|
178
|
-
f3.on_complete { sequence << 3 }
|
179
|
-
f2.complete!
|
180
|
-
f1.complete!
|
181
|
-
f3.complete!
|
182
|
-
sequence.should == [2, 1, 3]
|
255
|
+
it 'notifies all listeners even when one raises an error' do
|
256
|
+
value = nil
|
257
|
+
future.on_value { |v| raise 'Blurgh' }
|
258
|
+
future.on_value { |v| value = v }
|
259
|
+
promise.fulfill('bar')
|
260
|
+
value.should == 'bar'
|
183
261
|
end
|
184
262
|
|
185
|
-
it '
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
f3.complete!(3)
|
193
|
-
f4.get.should == [1, 2, 3]
|
263
|
+
it 'notifies listeners registered after the promise was resolved' do
|
264
|
+
v1, v2 = nil, nil
|
265
|
+
promise.fulfill('bar')
|
266
|
+
future.on_value { |v| v1 = v }
|
267
|
+
future.on_value { |v| v2 = v }
|
268
|
+
v1.should == 'bar'
|
269
|
+
v2.should == 'bar'
|
194
270
|
end
|
195
271
|
|
196
|
-
it '
|
197
|
-
|
198
|
-
|
199
|
-
f3 = Future.new
|
200
|
-
f4 = Future.new
|
201
|
-
f5 = Future.combine(f1, f2, f3, f4)
|
202
|
-
f2.complete!
|
203
|
-
f1.fail!(StandardError.new('hurgh'))
|
204
|
-
f3.fail!(StandardError.new('murgasd'))
|
205
|
-
f4.complete!
|
206
|
-
expect { f5.get }.to raise_error('hurgh')
|
207
|
-
f5.should be_failed
|
272
|
+
it 'does not raise any error when the listener raises an error when already resolved' do
|
273
|
+
promise.fulfill('bar')
|
274
|
+
expect { future.on_value { |v| raise 'blurgh' } }.to_not raise_error
|
208
275
|
end
|
209
276
|
|
210
|
-
it '
|
211
|
-
|
212
|
-
expect { f.complete! }.to raise_error(FutureError)
|
277
|
+
it 'returns nil' do
|
278
|
+
future.on_value { :foo }.should be_nil
|
213
279
|
end
|
214
280
|
|
215
|
-
it '
|
216
|
-
|
217
|
-
|
281
|
+
it 'returns nil when the future is already resolved' do
|
282
|
+
promise.fulfill
|
283
|
+
future.on_failure { :foo }.should be_nil
|
218
284
|
end
|
285
|
+
end
|
286
|
+
end
|
219
287
|
|
220
|
-
|
221
|
-
|
288
|
+
describe '#on_failure' do
|
289
|
+
context 'registers listeners and' do
|
290
|
+
it 'notifies all failure listeners when the promise fails' do
|
291
|
+
e1, e2 = nil, nil
|
292
|
+
future.on_failure { |err| e1 = err }
|
293
|
+
future.on_failure { |err| e2 = err }
|
294
|
+
promise.fail(error)
|
295
|
+
e1.message.should eql(error.message)
|
296
|
+
e2.message.should eql(error.message)
|
297
|
+
end
|
298
|
+
|
299
|
+
it 'notifies all listeners even if one raises an error' do
|
300
|
+
e = nil
|
301
|
+
future.on_failure { |err| raise 'Blurgh' }
|
302
|
+
future.on_failure { |err| e = err }
|
303
|
+
promise.fail(error)
|
304
|
+
e.message.should eql(error.message)
|
305
|
+
end
|
306
|
+
|
307
|
+
it 'notifies new listeners even when already failed' do
|
308
|
+
e1, e2 = nil, nil
|
309
|
+
promise.fail(error)
|
310
|
+
future.on_failure { |e| e1 = e }
|
311
|
+
future.on_failure { |e| e2 = e }
|
312
|
+
e1.message.should eql(error.message)
|
313
|
+
e2.message.should eql(error.message)
|
314
|
+
end
|
315
|
+
|
316
|
+
it 'does not raise any error when the listener raises an error when already failed' do
|
317
|
+
promise.fail(error)
|
318
|
+
expect { future.on_failure { |e| raise 'Blurgh' } }.to_not raise_error
|
319
|
+
end
|
320
|
+
|
321
|
+
it 'returns nil' do
|
322
|
+
future.on_failure { :foo }.should be_nil
|
323
|
+
end
|
324
|
+
|
325
|
+
it 'returns nil when the future already has failed' do
|
326
|
+
promise.fail(error)
|
327
|
+
future.on_failure { :foo }.should be_nil
|
222
328
|
end
|
223
329
|
end
|
224
330
|
end
|
225
331
|
|
226
|
-
describe '
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
it 'is unaffected by the completion of the other futures' do
|
247
|
-
f1 = Future.new
|
248
|
-
f2 = Future.new
|
249
|
-
f3 = Future.new
|
250
|
-
ff = Future.first(f1, f2, f3)
|
251
|
-
f2.complete!
|
252
|
-
f1.complete!
|
253
|
-
f3.complete!
|
332
|
+
describe '#value' do
|
333
|
+
it 'is nil by default' do
|
334
|
+
promise.fulfill
|
335
|
+
future.value.should be_nil
|
336
|
+
end
|
337
|
+
|
338
|
+
it 'is the object passed to Promise#fulfill' do
|
339
|
+
obj = 'hello world'
|
340
|
+
promise.fulfill(obj)
|
341
|
+
future.value.should equal(obj)
|
342
|
+
end
|
343
|
+
|
344
|
+
it 'raises the error passed to Promise#fail' do
|
345
|
+
promise.fail(StandardError.new('bork'))
|
346
|
+
expect { future.value }.to raise_error(/bork/)
|
347
|
+
end
|
348
|
+
|
349
|
+
it 'blocks until the promise is completed' do
|
350
|
+
d = delayed(promise) do |p|
|
351
|
+
p.fulfill('bar')
|
254
352
|
end
|
353
|
+
d.value
|
354
|
+
future.value.should == 'bar'
|
355
|
+
end
|
255
356
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
f3 = Future.new
|
260
|
-
ff = Future.first(f1, f2, f3)
|
261
|
-
f2.fail!(StandardError.new('bork'))
|
262
|
-
f1.fail!(StandardError.new('bork'))
|
263
|
-
f3.fail!(StandardError.new('bork'))
|
264
|
-
ff.should be_failed
|
357
|
+
it 'blocks on #value until fulfilled, when value is nil' do
|
358
|
+
d = delayed(promise) do |p|
|
359
|
+
p.fulfill
|
265
360
|
end
|
361
|
+
d.value
|
362
|
+
future.value.should be_nil
|
363
|
+
end
|
266
364
|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
f3 = Future.new
|
271
|
-
ff = Future.first(f1, f2, f3)
|
272
|
-
f2.fail!(StandardError.new('bork2'))
|
273
|
-
f1.fail!(StandardError.new('bork1'))
|
274
|
-
f3.fail!(StandardError.new('bork3'))
|
275
|
-
expect { ff.get }.to raise_error('bork3')
|
365
|
+
it 'blocks on #value until failed' do
|
366
|
+
d = delayed(promise) do |p|
|
367
|
+
p.fail(StandardError.new('bork'))
|
276
368
|
end
|
369
|
+
d.value
|
370
|
+
expect { future.value }.to raise_error('bork')
|
371
|
+
end
|
372
|
+
|
373
|
+
it 'allows multiple threads to block on #value until fulfilled' do
|
374
|
+
listeners = Array.new(10) do
|
375
|
+
async(future) do |f|
|
376
|
+
f.value
|
377
|
+
end
|
378
|
+
end
|
379
|
+
sleep 0.1
|
380
|
+
promise.fulfill(:hello)
|
381
|
+
listeners.map(&:value).should == Array.new(10, :hello)
|
277
382
|
end
|
278
383
|
end
|
279
384
|
|
280
385
|
describe '#map' do
|
281
386
|
context 'returns a new future that' do
|
282
|
-
it 'will
|
387
|
+
it 'will be fulfilled with the result of the given block' do
|
283
388
|
mapped_value = nil
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
389
|
+
p = Promise.new
|
390
|
+
f = p.future.map { |v| v * 2 }
|
391
|
+
f.on_value { |v| mapped_value = v }
|
392
|
+
p.fulfill(3)
|
288
393
|
mapped_value.should == 3 * 2
|
289
394
|
end
|
290
395
|
|
291
396
|
it 'fails when the original future fails' do
|
292
397
|
failed = false
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
398
|
+
p = Promise.new
|
399
|
+
f = p.future.map { |v| v * 2 }
|
400
|
+
f.on_failure { failed = true }
|
401
|
+
p.fail(StandardError.new('Blurgh'))
|
297
402
|
failed.should be_true
|
298
403
|
end
|
299
404
|
|
300
405
|
it 'fails when the block raises an error' do
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
f1.complete!
|
406
|
+
p = Promise.new
|
407
|
+
f = p.future.map { |v| raise 'blurgh' }
|
408
|
+
d = delayed do
|
409
|
+
p.fulfill
|
306
410
|
end
|
307
|
-
|
411
|
+
d.value
|
412
|
+
expect { f.value }.to raise_error('blurgh')
|
308
413
|
end
|
309
414
|
end
|
310
415
|
end
|
311
416
|
|
312
417
|
describe '#flat_map' do
|
313
418
|
it 'works like #map, but expects that the block returns a future' do
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
419
|
+
p = Promise.new
|
420
|
+
f = p.future.flat_map { |v| Future.resolved(v * 2) }
|
421
|
+
p.fulfill(3)
|
422
|
+
f.value.should == 3 * 2
|
318
423
|
end
|
319
424
|
|
320
425
|
it 'fails when the block raises an error' do
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
expect {
|
426
|
+
p = Promise.new
|
427
|
+
f = p.future.flat_map { |v| raise 'Hurgh' }
|
428
|
+
p.fulfill(3)
|
429
|
+
expect { f.value }.to raise_error('Hurgh')
|
325
430
|
end
|
326
431
|
end
|
327
432
|
|
328
433
|
describe '#recover' do
|
329
434
|
context 'returns a new future that' do
|
330
|
-
it '
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
435
|
+
it 'becomes fulfilled with a value when the source future fails' do
|
436
|
+
p = Promise.new
|
437
|
+
f = p.future.recover { 'foo' }
|
438
|
+
p.fail(error)
|
439
|
+
f.value.should == 'foo'
|
335
440
|
end
|
336
441
|
|
337
442
|
it 'yields the error to the block' do
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
443
|
+
p = Promise.new
|
444
|
+
f = p.future.recover { |e| e.message }
|
445
|
+
p.fail(error)
|
446
|
+
f.value.should == error.message
|
342
447
|
end
|
343
448
|
|
344
|
-
it '
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
449
|
+
it 'becomes fulfilled with the value of the source future when the source future is fulfilled' do
|
450
|
+
p = Promise.new
|
451
|
+
f = p.future.recover { 'foo' }
|
452
|
+
p.fulfill('bar')
|
453
|
+
f.value.should == 'bar'
|
349
454
|
end
|
350
455
|
|
351
456
|
it 'fails with the error raised in the given block' do
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
expect {
|
457
|
+
p = Promise.new
|
458
|
+
f = p.future.recover { raise 'snork' }
|
459
|
+
p.fail(StandardError.new('bork'))
|
460
|
+
expect { f.value }.to raise_error('snork')
|
356
461
|
end
|
357
462
|
end
|
358
463
|
end
|
359
464
|
|
360
465
|
describe '#fallback' do
|
361
466
|
context 'returns a new future that' do
|
362
|
-
it '
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
467
|
+
it 'is resolved with the value of the fallback future when the source future fails' do
|
468
|
+
p1 = Promise.new
|
469
|
+
p2 = Promise.new
|
470
|
+
f = p1.future.fallback { p2.future }
|
471
|
+
p1.fail(error)
|
472
|
+
p2.fulfill('foo')
|
473
|
+
f.value.should == 'foo'
|
369
474
|
end
|
370
475
|
|
371
476
|
it 'yields the error to the block' do
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
Future.
|
477
|
+
p1 = Promise.new
|
478
|
+
p2 = Promise.new
|
479
|
+
f = p1.future.fallback do |error|
|
480
|
+
Future.resolved(error.message)
|
376
481
|
end
|
377
|
-
|
378
|
-
|
482
|
+
p1.fail(error)
|
483
|
+
f.value.should == error.message
|
379
484
|
end
|
380
485
|
|
381
|
-
it '
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
486
|
+
it 'is resolved with the value of the source future when the source future fullfills' do
|
487
|
+
p1 = Promise.new
|
488
|
+
p2 = Promise.new
|
489
|
+
f = p1.future.fallback { p2.future }
|
490
|
+
p2.fulfill('bar')
|
491
|
+
p1.fulfill('foo')
|
492
|
+
f.value.should == 'foo'
|
388
493
|
end
|
389
494
|
|
390
495
|
it 'fails when the block raises an error' do
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
expect {
|
496
|
+
p = Promise.new
|
497
|
+
f = p.future.fallback { raise 'bork' }
|
498
|
+
p.fail(StandardError.new('splork'))
|
499
|
+
expect { f.value }.to raise_error('bork')
|
395
500
|
end
|
396
501
|
|
397
502
|
it 'fails when the fallback future fails' do
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
expect {
|
503
|
+
p1 = Promise.new
|
504
|
+
p2 = Promise.new
|
505
|
+
f = p1.future.fallback { p2.future }
|
506
|
+
p2.fail(StandardError.new('bork'))
|
507
|
+
p1.fail(StandardError.new('fnork'))
|
508
|
+
expect { f.value }.to raise_error('bork')
|
404
509
|
end
|
405
510
|
end
|
406
511
|
end
|
407
512
|
|
408
|
-
describe '.
|
409
|
-
|
410
|
-
|
411
|
-
|
513
|
+
describe '.all' do
|
514
|
+
context 'returns a new future which' do
|
515
|
+
it 'is resolved when the source futures are resolved' do
|
516
|
+
p1 = Promise.new
|
517
|
+
p2 = Promise.new
|
518
|
+
f = Future.all(p1.future, p2.future)
|
519
|
+
p1.fulfill
|
520
|
+
f.should_not be_resolved
|
521
|
+
p2.fulfill
|
522
|
+
f.should be_resolved
|
523
|
+
end
|
412
524
|
|
413
|
-
|
414
|
-
|
415
|
-
|
525
|
+
it 'resolves when the source futures are resolved' do
|
526
|
+
sequence = []
|
527
|
+
p1 = Promise.new
|
528
|
+
p2 = Promise.new
|
529
|
+
p3 = Promise.new
|
530
|
+
f = Future.all(p1.future, p2.future, p3.future)
|
531
|
+
p1.future.on_value { sequence << 1 }
|
532
|
+
p2.future.on_value { sequence << 2 }
|
533
|
+
p3.future.on_value { sequence << 3 }
|
534
|
+
p2.fulfill
|
535
|
+
p1.fulfill
|
536
|
+
p3.fulfill
|
537
|
+
sequence.should == [2, 1, 3]
|
538
|
+
end
|
416
539
|
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
540
|
+
it 'returns an array of the values of the source futures, in order ' do
|
541
|
+
p1 = Promise.new
|
542
|
+
p2 = Promise.new
|
543
|
+
p3 = Promise.new
|
544
|
+
f = Future.all(p1.future, p2.future, p3.future)
|
545
|
+
p2.fulfill(2)
|
546
|
+
p1.fulfill(1)
|
547
|
+
p3.fulfill(3)
|
548
|
+
f.value.should == [1, 2, 3]
|
549
|
+
end
|
422
550
|
|
423
|
-
|
424
|
-
|
425
|
-
|
551
|
+
it 'fails if any of the source futures fail' do
|
552
|
+
p1 = Promise.new
|
553
|
+
p2 = Promise.new
|
554
|
+
p3 = Promise.new
|
555
|
+
p4 = Promise.new
|
556
|
+
f = Future.all(p1.future, p2.future, p3.future, p4.future)
|
557
|
+
p2.fulfill
|
558
|
+
p1.fail(StandardError.new('hurgh'))
|
559
|
+
p3.fail(StandardError.new('murgasd'))
|
560
|
+
p4.fulfill
|
561
|
+
expect { f.value }.to raise_error('hurgh')
|
562
|
+
f.should be_failed
|
563
|
+
end
|
426
564
|
|
427
|
-
|
428
|
-
|
565
|
+
it 'completes with an empty list when no futures are given' do
|
566
|
+
Future.all.value.should == []
|
567
|
+
end
|
429
568
|
end
|
569
|
+
end
|
570
|
+
|
571
|
+
describe '.first' do
|
572
|
+
context 'it returns a new future which' do
|
573
|
+
it 'is resolved when the first of the source futures is resolved' do
|
574
|
+
p1 = Promise.new
|
575
|
+
p2 = Promise.new
|
576
|
+
p3 = Promise.new
|
577
|
+
f = Future.first(p1.future, p2.future, p3.future)
|
578
|
+
p2.fulfill
|
579
|
+
f.should be_resolved
|
580
|
+
end
|
581
|
+
|
582
|
+
it 'fullfills with the value of the first source future' do
|
583
|
+
p1 = Promise.new
|
584
|
+
p2 = Promise.new
|
585
|
+
p3 = Promise.new
|
586
|
+
f = Future.first(p1.future, p2.future, p3.future)
|
587
|
+
p2.fulfill('foo')
|
588
|
+
f.value.should == 'foo'
|
589
|
+
end
|
590
|
+
|
591
|
+
it 'is unaffected by the fullfillment of the other futures' do
|
592
|
+
p1 = Promise.new
|
593
|
+
p2 = Promise.new
|
594
|
+
p3 = Promise.new
|
595
|
+
f = Future.first(p1.future, p2.future, p3.future)
|
596
|
+
p2.fulfill
|
597
|
+
p1.fulfill
|
598
|
+
p3.fulfill
|
599
|
+
f.value
|
600
|
+
end
|
601
|
+
|
602
|
+
it 'is unaffected by a future failing when at least one resolves' do
|
603
|
+
p1 = Promise.new
|
604
|
+
p2 = Promise.new
|
605
|
+
p3 = Promise.new
|
606
|
+
f = Future.first(p1.future, p2.future, p3.future)
|
607
|
+
p2.fail(error)
|
608
|
+
p1.fail(error)
|
609
|
+
p3.fulfill
|
610
|
+
expect { f.value }.to_not raise_error
|
611
|
+
end
|
612
|
+
|
613
|
+
it 'fails if all of the source futures fail' do
|
614
|
+
p1 = Promise.new
|
615
|
+
p2 = Promise.new
|
616
|
+
p3 = Promise.new
|
617
|
+
f = Future.first(p1.future, p2.future, p3.future)
|
618
|
+
p2.fail(error)
|
619
|
+
p1.fail(error)
|
620
|
+
p3.fail(error)
|
621
|
+
f.should be_failed
|
622
|
+
end
|
623
|
+
|
624
|
+
it 'fails with the error of the last future to fail' do
|
625
|
+
p1 = Promise.new
|
626
|
+
p2 = Promise.new
|
627
|
+
p3 = Promise.new
|
628
|
+
f = Future.first(p1.future, p2.future, p3.future)
|
629
|
+
p2.fail(StandardError.new('bork2'))
|
630
|
+
p1.fail(StandardError.new('bork1'))
|
631
|
+
p3.fail(StandardError.new('bork3'))
|
632
|
+
expect { f.value }.to raise_error('bork3')
|
633
|
+
end
|
430
634
|
|
431
|
-
|
432
|
-
|
635
|
+
it 'completes with nil when no futures are given' do
|
636
|
+
Future.first.value.should be_nil
|
637
|
+
end
|
433
638
|
end
|
639
|
+
end
|
434
640
|
|
435
|
-
|
436
|
-
|
437
|
-
|
641
|
+
describe '.resolved' do
|
642
|
+
context 'returns a future which' do
|
643
|
+
let :future do
|
644
|
+
described_class.resolved('hello world')
|
645
|
+
end
|
646
|
+
|
647
|
+
it 'returns a future which is resolved' do
|
648
|
+
future.should be_resolved
|
649
|
+
end
|
650
|
+
|
651
|
+
it 'calls callbacks immediately' do
|
652
|
+
value = nil
|
653
|
+
future.on_value { |v| value = v }
|
654
|
+
value.should == 'hello world'
|
655
|
+
end
|
656
|
+
|
657
|
+
it 'does not block on #value' do
|
658
|
+
future.value.should == 'hello world'
|
659
|
+
end
|
660
|
+
|
661
|
+
it 'defaults to the value nil' do
|
662
|
+
described_class.resolved.value.should be_nil
|
663
|
+
end
|
438
664
|
end
|
439
665
|
end
|
440
666
|
|
441
667
|
describe '.failed' do
|
442
668
|
let :future do
|
443
|
-
described_class.failed(
|
669
|
+
described_class.failed(error)
|
444
670
|
end
|
445
671
|
|
446
|
-
|
447
|
-
|
448
|
-
|
672
|
+
context 'returns a future which' do
|
673
|
+
it 'is failed when created' do
|
674
|
+
future.should be_failed
|
675
|
+
end
|
449
676
|
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
677
|
+
it 'calls callbacks immediately' do
|
678
|
+
error = nil
|
679
|
+
future.on_failure { |e| error = e }
|
680
|
+
error.message.should == 'bork'
|
681
|
+
end
|
455
682
|
|
456
|
-
|
457
|
-
|
683
|
+
it 'does not block on #value' do
|
684
|
+
expect { future.value }.to raise_error('bork')
|
685
|
+
end
|
458
686
|
end
|
459
687
|
end
|
460
688
|
end
|