cql-rb 1.1.0.pre3 → 1.1.0.pre6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/README.md +2 -2
  2. data/lib/cql/client.rb +9 -5
  3. data/lib/cql/client/asynchronous_client.rb +105 -192
  4. data/lib/cql/client/asynchronous_prepared_statement.rb +51 -9
  5. data/lib/cql/client/connection_helper.rb +155 -0
  6. data/lib/cql/client/connection_manager.rb +56 -0
  7. data/lib/cql/client/keyspace_changer.rb +27 -0
  8. data/lib/cql/client/null_logger.rb +21 -0
  9. data/lib/cql/client/request_runner.rb +5 -3
  10. data/lib/cql/client/synchronous_client.rb +5 -5
  11. data/lib/cql/client/synchronous_prepared_statement.rb +4 -8
  12. data/lib/cql/future.rb +320 -210
  13. data/lib/cql/io/connection.rb +5 -5
  14. data/lib/cql/io/io_reactor.rb +21 -23
  15. data/lib/cql/protocol/cql_protocol_handler.rb +69 -38
  16. data/lib/cql/protocol/encoding.rb +5 -1
  17. data/lib/cql/protocol/requests/register_request.rb +2 -0
  18. data/lib/cql/protocol/type_converter.rb +1 -0
  19. data/lib/cql/version.rb +1 -1
  20. data/spec/cql/client/asynchronous_client_spec.rb +368 -175
  21. data/spec/cql/client/asynchronous_prepared_statement_spec.rb +132 -22
  22. data/spec/cql/client/connection_helper_spec.rb +335 -0
  23. data/spec/cql/client/connection_manager_spec.rb +118 -0
  24. data/spec/cql/client/keyspace_changer_spec.rb +50 -0
  25. data/spec/cql/client/request_runner_spec.rb +12 -12
  26. data/spec/cql/client/synchronous_client_spec.rb +15 -15
  27. data/spec/cql/client/synchronous_prepared_statement_spec.rb +15 -11
  28. data/spec/cql/future_spec.rb +529 -301
  29. data/spec/cql/io/connection_spec.rb +12 -12
  30. data/spec/cql/io/io_reactor_spec.rb +61 -61
  31. data/spec/cql/protocol/cql_protocol_handler_spec.rb +26 -12
  32. data/spec/cql/protocol/encoding_spec.rb +5 -0
  33. data/spec/cql/protocol/type_converter_spec.rb +1 -1
  34. data/spec/cql/time_uuid_spec.rb +7 -7
  35. data/spec/integration/client_spec.rb +2 -2
  36. data/spec/integration/io_spec.rb +20 -20
  37. data/spec/integration/protocol_spec.rb +17 -17
  38. data/spec/integration/regression_spec.rb +6 -0
  39. data/spec/integration/uuid_spec.rb +4 -0
  40. data/spec/support/fake_io_reactor.rb +38 -8
  41. data/spec/support/fake_server.rb +3 -3
  42. 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
- stub(:connection)
14
+ double(:connection)
15
15
  end
16
16
 
17
17
  let :request do
18
- stub(:request)
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.completed(response))
63
- runner.execute(connection, rq).get
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.completed(rows_response))
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!('No error was raised')
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
- stub(:async_client)
14
+ double(:async_client)
15
15
  end
16
16
 
17
17
  let :future do
18
- stub(:future, get: nil)
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(:get)
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(:get)
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(:get)
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 = stub(:result)
73
+ result = double(:result)
74
74
  async_client.stub(:execute).with('SELECT * FROM something', :one).and_return(future)
75
- future.stub(:get).and_return(result)
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 = stub(:result)
83
- metadata = stub(:metadata)
84
- async_statement = stub(:async_statement, metadata: metadata)
85
- another_future = stub(: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(:get).and_return(async_statement)
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(:get).and_return(result)
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(:get).and_raise(error)
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(:get).and_raise('Bork')
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
- stub(:async_statement, metadata: metadata)
14
+ double(:async_statement, metadata: metadata)
15
15
  end
16
16
 
17
17
  let :metadata do
18
- stub(:metadata)
18
+ double(:metadata)
19
+ end
20
+
21
+ let :promise do
22
+ Promise.new
19
23
  end
20
24
 
21
25
  let :future do
22
- Future.new
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 = stub(:result)
37
+ result = double(:result)
34
38
  async_statement.should_receive(:execute).with('one', 'two', :three).and_return(future)
35
- future.complete!(result)
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 = stub(:result1)
43
- result2 = stub(:result2)
44
- async_statement.stub(:execute).with('one', 'two', :three).and_return(Future.completed(result1))
45
- async_statement.stub(:execute).with('four', 'file', :all).and_return(Future.completed(result2))
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(:get).and_raise(error)
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(:get).and_raise('Bork')
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')
@@ -4,457 +4,685 @@ require 'spec_helper'
4
4
 
5
5
 
6
6
  module Cql
7
- describe Future do
8
- let :future do
7
+ describe Promise do
8
+ let :promise do
9
9
  described_class.new
10
10
  end
11
11
 
12
- context 'when completed' do
13
- it 'is complete' do
14
- future.complete!('foo')
15
- future.should be_complete
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 'has a value' do
19
- future.complete!('foo')
20
- future.value.should == 'foo'
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 'is complete even when the value is falsy' do
24
- future.complete!(nil)
25
- future.should be_complete
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 the value from #get, too' do
29
- future.complete!('foo')
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
- it 'has the value nil by default' do
34
- future.complete!
35
- future.value.should be_nil
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 'notifies all completion listeners' do
39
- v1, v2 = nil, nil
40
- future.on_complete { |v| v1 = v }
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 'notifies all listeners even when one raises an error' do
48
- value = nil
49
- future.on_complete { |v| raise 'Blurgh' }
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 'notifies new listeners even when already completed' do
56
- v1, v2 = nil, nil
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
- it 'does not raise any error when the listener raises an error when already completed' do
65
- future.complete!('bar')
66
- expect { future.on_complete { |v| raise 'Blurgh' } }.to_not raise_error
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 'blocks on #value until completed' do
70
- Thread.start(future) do |f|
71
- sleep 0.1
72
- future.complete!('bar')
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 == 'bar'
94
+ promise.future.value.should == 7
75
95
  end
76
96
 
77
- it 'blocks on #value until completed, when value is nil' do
78
- Thread.start(future) do |f|
79
- sleep 0.1
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.should be_nil
83
- future.value.should be_nil
101
+ expect { promise.future.value }.to raise_error(/bork/)
84
102
  end
85
103
 
86
- it 'blocks on #value until failed' do
87
- Thread.start(future) do |f|
88
- sleep 0.1
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
- expect { future.value }.to raise_error('FAIL!')
108
+ promise.future.value.should == 6
92
109
  end
93
110
 
94
- it 'cannot be completed again' do
95
- future.complete!('bar')
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
- it 'cannot be failed again' do
100
- future.complete!('bar')
101
- expect { future.fail!(StandardError.new('FAIL!')) }.to raise_error(FutureError)
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
- context 'when failed' do
106
- it 'is failed' do
107
- future.fail!(StandardError.new('FAIL!'))
108
- future.should be_failed
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 'raises the error from #value' do
112
- future.fail!(StandardError.new('FAIL!'))
113
- expect { future.value }.to raise_error('FAIL!')
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
- it 'notifies all failure listeners' do
117
- e1, e2 = nil, nil
118
- future.on_failure { |e| e1 = e }
119
- future.on_failure { |e| e2 = e }
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 'notifies all listeners even if one raises an error' do
126
- error = nil
127
- future.on_failure { |e| raise 'Blurgh' }
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 'notifies new listeners even when already failed' do
134
- e1, e2 = nil, nil
135
- future.fail!(StandardError.new('FAIL!'))
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
- it 'does not raise any error when the listener raises an error when already failed' do
143
- future.fail!(StandardError.new('FAIL!'))
144
- expect { future.on_failure { |e| raise 'Blurgh' } }.to_not raise_error
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 'cannot be failed again' do
148
- future.fail!(StandardError.new('FAIL!'))
149
- expect { future.fail!(StandardError.new('FAIL!')) }.to raise_error(FutureError)
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
- it 'cannot be completed' do
153
- future.fail!(StandardError.new('FAIL!'))
154
- expect { future.complete!('hurgh') }.to raise_error(FutureError)
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 '.combine' do
159
- context 'returns a new future which' do
160
- it 'is complete when the source futures are complete' do
161
- f1 = Future.new
162
- f2 = Future.new
163
- f3 = Future.combine(f1, f2)
164
- f1.complete!
165
- f3.should_not be_complete
166
- f2.complete!
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 'completes when the source futures have completed' do
171
- sequence = []
172
- f1 = Future.new
173
- f2 = Future.new
174
- f3 = Future.new
175
- f4 = Future.combine(f1, f2, f3)
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 'returns an array of the values of the source futures, in order' do
186
- f1 = Future.new
187
- f2 = Future.new
188
- f3 = Future.new
189
- f4 = Future.combine(f1, f2, f3)
190
- f2.complete!(2)
191
- f1.complete!(1)
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 'fails if any of the source futures fail' do
197
- f1 = Future.new
198
- f2 = Future.new
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 'raises an error when #complete! is called' do
211
- f = Future.combine(Future.new, Future.new)
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 'raises an error when #fail! is called' do
216
- f = Future.combine(Future.new, Future.new)
217
- expect { f.fail!(StandardError.new('Blurgh')) }.to raise_error(FutureError)
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
- it 'completes with an empty list when no futures are given' do
221
- Future.combine.get.should == []
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 '.first' do
227
- context 'it returns a new future which' do
228
- it 'is complete when the first of the source futures is complete' do
229
- f1 = Future.new
230
- f2 = Future.new
231
- f3 = Future.new
232
- ff = Future.first(f1, f2, f3)
233
- f2.complete!
234
- ff.should be_complete
235
- end
236
-
237
- it 'completes with the value of the first source future' do
238
- f1 = Future.new
239
- f2 = Future.new
240
- f3 = Future.new
241
- ff = Future.first(f1, f2, f3)
242
- f2.complete!('foo')
243
- ff.get.should == 'foo'
244
- end
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
- it 'fails if all of the source futures fail' do
257
- f1 = Future.new
258
- f2 = Future.new
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
- it 'fails with the error of the last future to fail' do
268
- f1 = Future.new
269
- f2 = Future.new
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 complete with the result of the given block' do
387
+ it 'will be fulfilled with the result of the given block' do
283
388
  mapped_value = nil
284
- f1 = Future.new
285
- f2 = f1.map { |v| v * 2 }
286
- f2.on_complete { |v| mapped_value = v }
287
- f1.complete!(3)
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
- f1 = Future.new
294
- f2 = f1.map { |v| v * 2 }
295
- f2.on_failure { failed = true }
296
- f1.fail!(StandardError.new('Blurgh'))
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
- f1 = Future.new
302
- f2 = f1.map { |v| raise 'Blurgh' }
303
- Thread.start do
304
- sleep(0.01)
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
- expect { f2.get }.to raise_error('Blurgh')
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
- f1 = Future.new
315
- f2 = f1.flat_map { |v| Future.completed(v * 2) }
316
- f1.complete!(3)
317
- f2.value.should == 3 * 2
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
- f1 = Future.new
322
- f2 = f1.flat_map { |v| raise 'Hurgh' }
323
- f1.complete!(3)
324
- expect { f2.get }.to raise_error('Hurgh')
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 'completes with a value when the source future fails' do
331
- f1 = Future.new
332
- f2 = f1.recover { 'foo' }
333
- f1.fail!(StandardError.new('Bork!'))
334
- f2.get.should == 'foo'
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
- f1 = Future.new
339
- f2 = f1.recover { |e| e.message }
340
- f1.fail!(StandardError.new('Bork!'))
341
- f2.get.should == 'Bork!'
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 'completes with the value of the source future when the source future is successful' do
345
- f1 = Future.new
346
- f2 = f1.recover { 'foo' }
347
- f1.complete!('bar')
348
- f2.get.should == 'bar'
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
- f1 = Future.new
353
- f2 = f1.recover { raise 'Snork!' }
354
- f1.fail!(StandardError.new('Bork!'))
355
- expect { f2.get }.to raise_error('Snork!')
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 'completes with the value of the fallback future when the source future fails' do
363
- f1 = Future.new
364
- f2 = Future.new
365
- f3 = f1.fallback { f2 }
366
- f1.fail!(StandardError.new('Bork!'))
367
- f2.complete!('foo')
368
- f3.get.should == 'foo'
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
- f1 = Future.new
373
- f2 = Future.new
374
- f3 = f1.fallback do |error|
375
- Future.completed(error.message)
477
+ p1 = Promise.new
478
+ p2 = Promise.new
479
+ f = p1.future.fallback do |error|
480
+ Future.resolved(error.message)
376
481
  end
377
- f1.fail!(StandardError.new('Bork!'))
378
- f3.get.should == 'Bork!'
482
+ p1.fail(error)
483
+ f.value.should == error.message
379
484
  end
380
485
 
381
- it 'completes with the value of the source future when the source future succeeds' do
382
- f1 = Future.new
383
- f2 = Future.new
384
- f3 = f1.fallback { f2 }
385
- f2.complete!('bar')
386
- f1.complete!('foo')
387
- f3.get.should == 'foo'
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
- f1 = Future.new
392
- f2 = f1.fallback { raise 'Bork!' }
393
- f1.fail!(StandardError.new('Splork!'))
394
- expect { f2.get }.to raise_error('Bork!')
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
- f1 = Future.new
399
- f2 = Future.new
400
- f3 = f1.fallback { f2 }
401
- f2.fail!(StandardError.new('Bork!'))
402
- f1.fail!(StandardError.new('Fnork!'))
403
- expect { f3.get }.to raise_error('Bork!')
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 '.completed' do
409
- let :future do
410
- described_class.completed('hello world')
411
- end
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
- it 'is complete when created' do
414
- future.should be_complete
415
- end
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
- it 'calls callbacks immediately' do
418
- value = nil
419
- future.on_complete { |v| value = v }
420
- value.should == 'hello world'
421
- end
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
- it 'does not block on #value' do
424
- future.value.should == 'hello world'
425
- end
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
- it 'defaults to the value nil' do
428
- described_class.completed.value.should be_nil
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
- it 'handles #map' do
432
- described_class.completed('foo').map(&:upcase).value.should == 'FOO'
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
- it 'handles #map' do
436
- f = described_class.completed('foo').map { |v| raise 'Blurgh' }
437
- f.should be_failed
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(StandardError.new('Blurgh'))
669
+ described_class.failed(error)
444
670
  end
445
671
 
446
- it 'is failed when created' do
447
- future.should be_failed
448
- end
672
+ context 'returns a future which' do
673
+ it 'is failed when created' do
674
+ future.should be_failed
675
+ end
449
676
 
450
- it 'calls callbacks immediately' do
451
- error = nil
452
- future.on_failure { |e| error = e }
453
- error.message.should == 'Blurgh'
454
- end
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
- it 'does not block on #value' do
457
- expect { future.value }.to raise_error('Blurgh')
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