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